1.类与对象
1.1 类与对象关系示意图
- 类是抽象的,概念的,代表一类事物,比如人类,猫类... 即它是数据类型
- 对象是具体的,实际的,代表一个具体事物,即是实例
- 类是对象的模板,对象是类的一个个体,对应一个实例
1.2 对象在内存中存在的形式
String类型会先放在常量池中,将地址引用 在执行new的时候,要加载类的信息,主要加载属性和方法
1.3 属性/成员变量/字段
- 从概念或叫法上看: 成员变量 = 属性 = field字段(即 成员变量是用来表示属性的)
- 属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象、数组)。
1.4 注意事项和使用细节
- 属性的定义语法同变量,示例: 访问修饰符 属性类型 属性名; 访问修饰符: 控制属性的访问范围 有四种访问修饰符 public protected 默认 private
- 属性的定义类型可以为任意类型,包含基本类型或引用类型
- 属性如果不赋值,有默认值,规则和数组一致。
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | '\u0000' |
boolean | false |
引用类型 | null |
- Person p1 = new Person(); p1是对象名(对象的引用),new Person();才是实在的对象,在堆中创建空间
1.5 如何创建对象
- 先声明后创建
Cat cat ; //声明对象 cat
cat = new Cat(); //创建
- 直接创建
Cat cat = new Cat();
- 访问属性
基本语法
对象名.属性名;
案例演示赋值和输出
cat.name ;
cat.age;
cat.color;
1.6 类与对象的内存分配机制
1.7 类和对象的内存分配机制
Java 内存的结构分析
- 栈: 一般存放基本数据类型(局部变量)
- 堆: 存放对象(Cat cat , 数组等)
- 方法区: 常量池(常量,比如字符串), 类加载信息
Java 创建对象的流程简单分析 Person p = new Person(); p.name = “jack”; p.age = 10
- 先加载 Person 类信息(属性和方法信息, 只会加载一次)
- 在堆中分配空间, 进行默认初始化(看规则)
- 把地址赋给 p , p 就指向对象
- 进行指定初始化, 比如 p.name =”jack” p.age = 10
2.成员方法
在某些情况下,我们需要定义成员方法(简称方法)。比如人类:除了有一些属性(年龄、姓名、身高、体重等)外,我们人类还有一些行为比如:可以说话、跑步等,通过学习还可以做算术题。这时就要用成员方法来完成。现在要求对Person类完事。
2.1 方法的快速入门
//1. public 表示方法是公开的
//2. int :表示方法执行后,返回一个 int 值
//3. getSum 方法名
//4. (int num1, int num2) 形参列表,2 个形参,可以接收用户传入的两个数
//5. return res; 表示把 res 的值, 返回
public int getSum(int num1, int num2) {
int res = num1 + num2;
return res;
}
2.2 方法调用机制
- 在栈中加载main方法创建对象
- 在栈中创建getSum方法,在该空间中给参数变量赋值
- 执行逻辑,返回结果
2.3 成员方法的定义
访问修饰符 返回数据类型 方法名(形参列表..) {//方法体
语句;
return 返回值;
}
- 参数列表: 表示成员方法输入。例如:cal(int n),getSum(int num1,int num2)
- 返回数据类型: 表示成员方法输出,void表示没有返回值
- 方法主体: 表示为了实现某一功能代码块
- return语句不是必须的
2.4 成员方法使用细节
访问修饰符(作用是控制方法使用范围)
如果不写默认访问有四种: public、protected、默认、private
返回数据类型
- 一个方法最多有一个返回值
- 返回类型可以为任意类型,包含基本类型和引用类型(数组、对象)
- 如果方法要求有返回类型。则方法体最后执行的语句必须为return值;而且要求返回值类型必须和return的值类型一致或者兼容
- 如果方法是void。则方法体中可以没有return语句,或者只写return;
方法名
遵循驼峰命名法,最好见名知义,表达出该功能的意思即可,比如得到两个数的和命名为getSum。
如果有特殊情况要按照开发文档规范命名
形参列表
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如getSum(int n1,int n2)
- 参数类型可以为任意类型,包含基本类型或引用类型,比如printArr(int[][] map)
- 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
- 方法定义时的参数称为形式参数,简称形参。方法调用时的传入参数称为实际参数,简称实参。实参和形参的类型要一致或兼容,个数、顺序必须一致
方法体 里面写完成功能的具体语句,可以为输入、输出、变量、预算、分支、循环、方法调用,但里面不能在定义方法。
方法调用细节
- 在同一个类中方法调用: 直接调用即可。比如print(参数)
- 跨类中的方法A类调用方法B类: 需要通过对象名调用。比如: 对象名.方法名(参数)
- 跨类的方法调用和方法的访问修饰符相关
3.成员方法传参机制
3.1 基本数据类型的传参机制
//编写一个 main 方法
public static void main(String[] args) {
int a = 10;
int b = 20;
//创建 AA 对象 名字 obj
AA obj = new AA();
obj.swap(a, b); //调用 swap
System.out.println("main 方法 a=" + a + " b=" + b);//a=10 b=20
}
class AA {
public void swap(int a,int b){
System.out.println("\na 和 b 交换前的值\na=" + a + "\tb=" + b);//a=10 b=20
//完成了 a 和 b 的交换
int tmp = a;
a = b;
b = tmp;
System.out.println("\na 和 b 交换后的值\na=" + a + "\tb=" + b);//a=20 b=10
}
}
3.2 引用数据类型的传参机制
B类中编写一个方法test100,可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化?会变化
public static void main(String[] args) {
int[] arr = {1,2,3};
test100(arr);
System.out.println("主方法");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static void test100(int[] arr){
arr[0] = 100;
System.out.println("test100方法");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
B类中编写一个方法test200,可以接收一个Person(age,sal)对象,在方法中修改该对象属性,看看原来的对象是否变化?会变化
public static void main(String[] args) {
B b = new B();
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
System.out.println("main 的 p.age=" + p.age);//10000
}
class B {
public void test200(Person p) {
p.age = 10000; //修改对象属性
}
}
public static void main(String[] args) {
B b = new B();
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
System.out.println("main 的 p.age=" + p.age);//10
}
class B {
public void test200(Person p) {
p = null;
}
}
public static void main(String[] args) {
B b = new B();
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
System.out.println("main 的 p.age=" + p.age);//10
}
class B {
public void test200(Person p) {
p = new Person();
p.name = "tom";
p.age = 99;
}
}
结论
引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参。
3.3 成员方法返回类型是引用类型的细节
编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。克隆对象,注意要求得到新对象和原来的对象。对象是两个独立的对象,只是他们的属性相同
public static void main(String[] args) {
Person p = new Person();
p.name = "milan";
p.age = 100;
//创建 tools
MyTools tools = new MyTools();
Person p2 = tools.copyPerson(p);
//到此 p 和 p2 是 Person 对象,但是是两个独立的对象,属性相同
System.out.println("p 的属性 age=" + p.age + " 名字=" + p.name);
System.out.println("p2 的属性 age=" + p2.age + " 名字=" + p2.name);
//这里老师提示: 可以同 对象比较看看是否为同一个对象
System.out.println(p == p2);//false
}
class Person {
String name;
int age;
}
class MyTools {
//编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象,
//注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
//编写方法的思路
//1. 方法的返回类型 Person
//2. 方法的名字 copyPerson
//3. 方法的形参 (Person p)
//4. 方法体, 创建一个新对象,并复制属性,返回即可
public Person copyPerson(Person p) {
//创建一个新的对象
Person p2 = new Person();
p2.name = p.name; //把原来对象的名字赋给 p2.name
p2.age = p.age; //把原来对象的年龄赋给 p2.age
return p2;
}
}
4.方法递归调用
简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂问题,同时可以让代码变得简洁
4.1 递归的举例
打印问题
public static void main(String[] args) {
TP tp = new TP();
tp.test(4);
}
class TP{
public void test(int n) {
if (n > 2) {
test(n - 1);
}
System.out.println("n=" + n);
}
}
阶乘问题
public static void main(String[] args) {
TP tp = new TP();
int res = tp.factorial(5);
System.out.println("5 的阶乘 res =" + res);
}
class TP{
//factorial 阶乘
public int factorial(int n) {
if (n == 1) {
return 1;
} else {
return factorial(n - 1) * n;
}
}
}
4.2 递归使用遵守规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响,比如n变量
- 如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用数据类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,会出现StackOverflowError栈内存溢出异常
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
4.3 递归使用练习
- 请使用递归的方式求出斐波那契数1,1,2,3,5,8,13...给你一个整数,求出它的值是多少
public static void main(String[] args) {
TP tp = new TP();
int fibonacci = tp.fibonacci(7);
System.out.println(fibonacci);
}
class TP{
public int fibonacci(int n) {
if (n > 0) {
if (n == 1 || n == 2) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
} else {
System.out.println("输入参数有误");
return -1;
}
}
}
- 猴子吃桃问题: 有一堆桃子,猴子第一天吃了其中的一半,并在多吃了一个。以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时发现只有一个桃子了。问题最初有多少个桃子?
public static void main(String[] args) {
TP tp = new TP();
int fibonacci = tp.fibonacci(1);
System.out.println(fibonacci);
}
class TP{
public int fibonacci(int n) {
if (n == 10) {
return 1;
}else if (n >= 1 && n < 10){
return (fibonacci(n + 1) + 1) * 2;
}else {
return -1;
}
}
}
4.4 方法递归应用案例
4.4.1 迷宫问题
public static void main(String[] args) {
//创建一个8行7列地图
int[][] map = new int[8][7];
//设置每行的第一个和最后一个是1
for(int i = 0; i < map.length;i++){
map[i][0] = 1;
map[i][map[i].length - 1] = 1;
}
//设置每列的第一个和最后一个是1
for(int i = 0; i < map[i].length;i++){
map[0][i] = 1;
map[map.length - 1][i] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
System.out.println("当前地图");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println("");
}
T t = new T();
t.findWay(map,1,1);
System.out.println("找完路之后的地图");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println("");
}
}
class T{
//1. findWay 方法就是专门来找出迷宫的路径
//2. 如果该位置是0,就返回 true ,是1代表是墙 2代表已经走过不能再走 3 代表该位置上下左右都走不通 返回false
//3. map 就是二维数组,即表示迷宫
//4. i,j 就是老鼠的位置,初始化的位置为(1,1)
//5. 因为我们是递归的找路,所以我先规定 map 数组的各个位置值的含义
// 0 表示可以走 1 表示障碍物 2 表示可以走 3 表示走过,但是走不通是死路
//6. 当 map[6][5] =2 就说明找到通路,就可以结束,否则就继续找.
// 7. 先确定老鼠找路策略 上->右->下->左
public Boolean findWay(int[][] map,int i,int j){
//设定最终找到坐标是map[6][5] 就不再找了
if (map[6][5] == 2){
return true;
}else {
//如果当前位置是0 代表可以走
if (map[i][j] == 0){
//先把该点设置为2 表示可以走
map[i][j] = 2;
//以上->右->下->左的策略往其他四个方向走
if (findWay(map,i-1,j)){
return true;
}else if (findWay(map,i,j+1)){
return true;
}else if (findWay(map,i+1,j)){
return true;
}else if (findWay(map,i,j-1)){
return true;
}else {
//如果四个位置都走不通,就将该位置置为3
map[i][j] = 3;
return false;
}
}
//当前位置除了0其他都代表走不通
return false;
}
}
}
执行流程解析
最开始在map[1][1]
位置,该位置是0所以可以走
先将该位置置为2,代表已经走过,走map[1-1][1]
的位置
递归调用该方法,传入的参数是map 0 1
,map[0][1]
位置是1代表走不了,返回false,所以返回直接返回上一个方法栈
上一个方法栈继续以参数1,1走if的第二个分支判断,就是map[1][1+1]
,代表向右走
map[1][2]
位置是0,则将该位置置为2,然后再继续往上尝试以此类推
回溯现象
将初始地图22位置改为1,以下右上左的方向找
就会出现一个3的位置,就是找了一圈都找不到能走的位置就回到原来的位置找下一个方向
思路分析
从main方法带着1 1参数进入第一个方法栈,1 1位置是0所以开始走if 先将该位置置为2 然后开始再次调用该方法走下一个位置
进入第二个方法栈 带着 1 + 1 1 也就是2 1 参数进入,2 1 位置也是0 也可以进入if,先把该位置置为2,然后再次调用方法
进入第三个方法栈带着3 1 参数该位置是1,所以直接返回false 弹出栈返回第二个方法栈
返回第二个方法栈之后,还是2 1 参数 继续往下执行if分支
再次调用方法,进入第三个方法栈判断,该位置还是1,返回false,继续第二方法栈
第二方法栈再次往下进行if分支判断进入方法
第三方法栈带着1 1 参数,发现还不是0,还是返回false,所以再次进入第二方法栈
再次进入第三方法栈 带着2 0 参数发现还是不行,所以再次返回false
进入到第二方法栈,走到else分支,将该位置值置为3,返回false
返回false之后,进入第一个方法栈,还是1 1 参数只不过进行第二次分支判断,过程和之前类似,还是再次进入第二方法栈只不过换了参数判断,直到有ruturn返回
4.4.2 汉诺塔
public static void main(String[] args) {
Tower tower = new Tower();
tower.tower(3,'A','B','C');
}
class Tower{
public void tower(int num,char a,char b,char c){
//如果只有一个盘就直接a -> c
if (num==1){
System.out.println(a + "->" + c);
}else {
//先移动最大的盘上面的所有到b,借助c
tower(num-1,a,c,b);
//然后再将最大的盘移动到c
System.out.println(a + "->" + c);
//最后再将b的盘移动到c
tower(num-1,b,a,c);
}
}
}
4.4.3 递归实现树形列表
数据库表有一个树形结构所有数据的表,parent_id = 0的是顶级节点,剩下的就找对应id是哪个哪个就是他的父节点
树节点数据类
/**
* TreeNode 树节点 (定义每一个节点的信息,即每一个节点对应一条数据信息)
*/
@Data
public class TreeNode {
/** 节点ID */
private Integer id;
/** 父节点ID:顶级节点为0 */
private Integer parentId;
/** 节点名称 */
private String label;
/** 子节点 */
private List<TreeNode> children;
public TreeNode(Integer id, Integer parentId, String label) {
this.id = id;
this.parentId = parentId;
this.label = label;
}
}
构建树
// 保存参与构建树形的所有数据(通常数据库查询结果)
public List<TreeNode> nodeList = new ArrayList<>();
/**
* 构造方法
* @param nodeList 将数据集合赋值给nodeList,即所有数据作为所有节点。
*/
public TreeBuild(List<TreeNode> nodeList){
this.nodeList = nodeList;
}
/**
* 获取需构建的所有根节点(顶级节点) "0"
* @return 所有根节点List集合
*/
public List<TreeNode> getRootNode(){
// 保存所有根节点(所有根节点的数据)
List<TreeNode> rootNodeList = new ArrayList<>();
// treeNode:查询出的每一条数据(节点)
for (TreeNode treeNode : nodeList){
// 判断当前节点是否为根节点,此处注意:若parentId类型是String,则要采用equals()方法判断。
if (0 == treeNode.getParentId()) {
// 是,添加
rootNodeList.add(treeNode);
}
}
return rootNodeList;
}
/**
* 根据每一个顶级节点(根节点)进行构建树形结构
* @return 构建整棵树
*/
public List<TreeNode> buildTree(){
// treeNodes:保存一个顶级节点所构建出来的完整树形
List<TreeNode> treeNodes = new ArrayList<TreeNode>();
// getRootNode():获取所有的根节点
for (TreeNode treeRootNode : getRootNode()) {
// 将顶级节点进行构建子树
treeRootNode = buildChildTree(treeRootNode);
// 完成一个顶级节点所构建的树形,增加进来
treeNodes.add(treeRootNode);
}
return treeNodes;
}
/**
* 递归-----构建子树形结构
* @param pNode 根节点(顶级节点)
* @return 整棵树
*/
public TreeNode buildChildTree(TreeNode pNode){
List<TreeNode> childTree = new ArrayList<TreeNode>();
// nodeList:所有节点集合(所有数据)
for (TreeNode treeNode : nodeList) {
// 判断当前节点的父节点ID是否等于根节点的ID,即当前节点为其下的子节点
if (treeNode.getParentId().equals(pNode.getId())) {
// 再递归进行判断当前节点的情况,调用自身方法
childTree.add(buildChildTree(treeNode));
}
}
// for循环结束,即节点下没有任何节点,树形构建结束,设置树结果
pNode.setChildren(childTree);
return pNode;
}
测试案例
/**
* TreeController 树控制层
* 方式:传递所有数据集合作为参数,调用buildTree()构建树形。
*/
@RestController
@RequestMapping("/tree")
public class TreeController {
@GetMapping("/treeTest")
public AjaxResult treeTest(){
// 模拟测试数据(通常为数据库的查询结果)
List<TreeNode> treeNodeList = new ArrayList<>();
treeNodeList.add(new TreeNode(1,0,"顶级节点A"));
treeNodeList.add(new TreeNode(2,0,"顶级节点B"));
treeNodeList.add(new TreeNode(3,1,"父节点是A"));
treeNodeList.add(new TreeNode(4,2,"父节点是B"));
treeNodeList.add(new TreeNode(5,2,"父节点是B"));
treeNodeList.add(new TreeNode(6,3,"父节点的ID是3"));
// 创建树形结构(数据集合作为参数)
TreeBuild treeBuild = new TreeBuild(treeNodeList);
// 原查询结果转换树形结构
treeNodeList = treeBuild.buildTree();
// AjaxResult:个人封装返回的结果体
return AjaxResult.success("测试数据",treeNodeList);
}
}
结果
{
"msg”:“ 测试数据”,
"code": 200,
"data": [
{
"id": 1,
"parentId": 0,
"label":"顶级节点A",
"children": [
{
"id": 3,
"parentId": 1,
"label":“ 父节点是A"
"children": [
"id": 6,
"parentId": 3,
"label":“ 父节点的ID是3
}
]
}
]
},
{
"id": 2,
"parentId": 0,
"labe1":“ 顶级节点B",
"children": [{
"id": 4,
"parentId": 2,
"label":“ 父节点是B"
},
{
"id": 5,
"parentId": 2,
"label":" 父节点是B
}
]
}
]
}
5.方法的重载
java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!
比如:System.out.println();
out 是 PrintStream 类型
5.1 方法重载的案例
class MyCalculator {
//下面的四个 calculate 方法构成了重载
//两个整数的和
public int calculate(int n1, int n2) {
System.out.println("calculate(int n1, int n2) 被调用");
return n1 + n2;
}
//没有构成方法重载, 仍然是错误的,因为是方法的重复定义
// public void calculate(int n1, int n2) {
// System.out.println("calculate(int n1, int n2) 被调用");
// int res = n1 + n2;
// }
//看看下面是否构成重载, 没有构成,而是方法的重复定义,就错了
// public int calculate(int a1, int a2) {
// System.out.println("calculate(int n1, int n2) 被调用");
// return a1 + a2;
// }
//一个整数,一个 double 的和
public double calculate(int n1, double n2) {
return n1 + n2;
}
//一个 double ,一个 Int 和
public double calculate(double n1, int n2) {
System.out.println("calculate(double n1, int n2) 被调用..");
return n1 + n2;
}
//三个 int 的和
public int calculate(int n1, int n2, int n3) {
return n1 + n2 + n2;
}
}
5.2 方法重载的细节
- 方法名: 必须相同
- 形参列表: 必须不同(形参列表或个数或顺序,至少有一样不同,参数名无要求)
- 返回类型: 无要求
6.可变参数
java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现
访问修饰符 返回类型 方法名(数据类型... 形参名) {
}
6.1 案例
编写一个方法sum,可以计算2个数的和,3个数的和,4,5....
//可以计算 2 个数的和,3 个数的和 , 4. 5, 。。
//可以使用方法重载
public int sum(int n1, int n2) {//2 个数的和
return n1 + n2;
}
public int sum(int n1, int n2, int n3) {//3 个数的和
return n1 + n2 + n3;
}
public int sum(int n1, int n2, int n3, int n4) {//4 个数的和
return n1 + n2 + n3 + n4;
}
//使用可变参数
//1. int... 表示接受的是可变参数,类型是 int ,即可以接收多个 int(0-多)
//2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组
//3. 遍历 nums 求和即可
public int sum(int... nums) {
//System.out.println("接收的参数个数=" + nums.length);
int res = 0;
for(int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
6.2 注意事项
- 可变参数的实参可以是0个或是任意多个
- 可变参数的实参可以是数组
- 可变参数的本质就是数组
- 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
- 一个形参列表中只能出现一个可变参数
7.作用域
7.1 基本使用
- 在java编程中,主要的变量就是属性(成员变量)和局部变量
- 我们说的局部变量一般是指在成员方法中定义的变量。
- java中作用域的分类 全局变量: 也就是属性,作用域为整个类体
局部变量: 也就是除了属性之外的其他变量,作用域为定义它的代码块中 - 全局变量(属性)可以不赋值,直接使用,因为有默认值。局部变量必须赋值后才能使用,因为没有默认值。
class Cat {
//全局变量:也就是属性,作用域为整个类体 Cat 类:cry eat 等方法使用属性
//属性在定义时,可以直接赋值
int age = 10; //指定的值是 10
//全局变量(属性)可以不赋值,直接使用,因为有默认值,
double weight; //默认值是 0.0
public void hi() {
//局部变量必须赋值后,才能使用,因为没有默认值
int num = 1;
String address = "北京的猫";
System.out.println("num=" + num);
System.out.println("address=" + address);
System.out.println("weight=" + weight);//属性
}
public void cry() {
//1. 局部变量一般是指在成员方法中定义的变量
//2. n 和 name 就是局部变量
//3. n 和 name 的作用域在 cry 方法中
int n = 10;
String name = "jack";
System.out.println("在 cry 中使用属性 age=" + age);
}
public void eat() {
System.out.println("在 eat 中使用属性 age=" + age);
//System.out.println("在 eat 中使用 cry 的变量 name=" + name);//错误
}
}
7.2 注意事项和使用细节
- 属性和局部变量可以重名,访问时遵循就近原则
- 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
- 属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块执行而创建,伴随着代码块的结束而销毁。即在一次方法调用过程中。
- 作用域范围不同 全局变量(属性): 可以被本类使用,或其他类使用(通过对象调用) 局部变量: 只能在本类中对应的方法中使用
- 修饰符不同 全局变量(属性)可以加修饰符 局部变量不可以加修饰符
8.构造方法/构造器
8.1 基本介绍
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。它有几个特点:
- 方法名和类名相同
- 没有返回值
- 在创建对象时,系统会自动调用该类的构造器完成对象的初始化
8.2 使用方法
[修饰符] 方法名(形参列表){
方法体;
}
- 构造器的修饰符可以默认,也可以是public protected private
- 构造器没有返回值
- 方法名和类名必须一样
- 参数列表和成员方法一样的规则
- 构造器的调用由系统完成
8.3 基本使用
public class ConstructorTest {
public static void main(String[] args) {
person jack = new person("jack", 18);
System.out.println(jack.age);
System.out.println(jack.name);
}
}
class person{
String name;
int age;
public person(String pName, int pAge){
System.out.println("person constructor");
name = pName;
age = pAge;
}
}
8.4 注意事项和使用细节
- 一个类可以定义多个不同的构造器,即构造器重载 比如: 我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名,不需要指定年龄
- 构造器名和类名要相同
- 构造器没有返回值
- 构造器是完成对象的初始化,不是创建对象
- 在创建对象时,系统自动调用该类的构造方法
- 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),比如
Dog(){}
。使用javap反编译指令可以查看 - 一旦定义了自己的构造器,默认构造器就被覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下。
9.javap的使用
- javap是jdk提供的一个命令行工具,javap能对给定的class文件提供的字节代码进行反编译
- 通过它,可以对照源代码和字节码,从而了解很多编译器内部的工作,对更加深入的理解如何提高程序执行效率等问题有极大帮助
- 使用格式
# 反汇编并显示字节码指令
javap -c MyClass
# 显示所有信息(包括私有成员)
javap -p -v MyClass
# 显示静态常量
javap -constants MyClass
以下是 javap
工具常用的命令及其说明:
命令选项 | 说明 |
---|---|
-help | 显示 javap 的帮助信息 |
-version | 显示 javap 的版本信息 |
-v 或 -verbose | 输出附加信息(如行号、局部变量表等) |
-l | 输出行号和局部变量表 |
-public | 仅显示公共类和成员(默认) |
-protected | 显示受保护/公共类和成员 |
-package | 显示包/受保护/公共类和成员(默认) |
-p 或 -private | 显示所有类和成员(包括私有) |
-c | 反汇编代码,显示字节码指令 |
-s | 输出内部类型签名 |
-sysinfo | 显示类的系统信息(路径、大小、日期、MD5 哈希等) |
-constants | 显示静态最终常量 |
-classpath <path> | 指定查找类文件的路径 |
-bootclasspath <path> | 覆盖引导类加载器的位置 |
10.对象创建的流程分析
class Person{
int age = 90;
String name;
Person(String n,int a){
name = n;
age = a;
}
}
Person p = new Person("小倩",20);
流程分析
- 加载Person类信息(Person.class),只会加载一次
- 在堆中分配空间(地址)
- 完成对象初始化 默认初始化age=0 name=null 显示初始化age=90 name=null 构造器初始化age=20 name="小倩"
- 在对象在堆中的地址,返回给p(p是对象名,也可以理解成是对象的引用)
11.this关键字
11.1 this的引出与概述
将构造器中的dName和dAge改成name和age之后,再次通过该构造器初始化对象,就会发现两个参数变成null和0了,就是因为改完名之后,由于就近原则,现在的age和name是该类没有赋值的age和name,所以就需要this关键字
什么是this
java虚拟机会给每个对象分配一个this,代表当前对象。不同的对象的this指代的不同
使用this解决赋值问题
class person{
String name;
int age;
public person(String name, int age){
System.out.println("person constructor");
this.name = name;
this.age = age;
}
}
11.2 this的深入理解
由于传进该构造方法的参数名本身要赋值给的类的成员变量值重名,所以在赋值的时候,等号左面的变量找的是该方法自己的局部变量,这样写相当于在该方法内,自己给自己的局部变量再改了一遍相同的值,由于就近原则,所以在走完该构造方法该方法出栈之后,局部变量就被销毁,对象中的属性值还是空的
如果构造器形参列表的属性名跟该类的属性名不相同,那么等号左面的name就代表着该类的成员变量,是给对象的属性赋值,所以堆中的person对象的两个属性就被赋值了,即使运行完构造方法弹出栈,形参列表局部变量消失,但是对象还存在,所以在主方法栈中在调用person对象的属性是有值得
在该类的构造方法中使用this.属性其实就相当于在main方法中使用jack.属性的效果是一样的,因为在new的时候是先将类的基本信息加载好的,里面的属性都是在堆中存在且有默认值的,所以在调用构造方法初始化对象的时候,使用this.属性就是调用堆中的属性给其赋值,所以this.name代表的是堆中的person对象的那么属性,而name是该方法的形参局部变量的name,所以可以成功赋值,而且this的hashcode和person对象的hascode值是一样的,所有可以判断this和main中的person对象指的是同一个对象
11.3 this关键字的使用细节
- this 关键字可以用来访问本类的属性、方法、构造器
- this 用于区分当前类的属性和局部变量
- 访问成员方法的语法: this.方法名(参数列表);
- 访问构造器语法: this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一 条语句)
- this 不能在类定义的外部使用,只能在类定义的方法中使用。
11.4 this的使用案例
public class ConstructorTest {
public static void main(String[] args) {
person jack = new person("jack", 18);
person tom = new person("tom", 20);
System.out.println(jack.ComparePerson(tom));
}
}
class person{
String name;
int age;
public person(String name, int age){
this.name = name;
this.age = age;
}
public Boolean ComparePerson(person person) {
//this代表对象jack,谁调用代表谁,要是tom待用就代表tom对象,传进来的参数代表tom
if(this.name.equals(person.name) && this.age == person.age){
return true;
}else {
return false;
}
}
}