1.对象在内存中的存在形式
假设代码如下:
Cat c = new Cat() c.name = "小白"; c.age = 12;
- 由于name属性为字符串类型,所以存放在方法区的常量池中
- 在执行
Cat c = new Cat()
时会在方法区中加载类信息c
只是对象的引用,真正的对象是由new cat()创建的对象空间(在堆以及方法区中)
2.类和对象的内存分配机制
假设代码如下:
Cat c = new Cat() c.name = "小白"; c.age = 12; Cat d = c;
d
和c
都是指向同一块内存地址,如果修改d.age=20
,c.age
的值也会改变- 如果执行再
d=null
,d
就不会再指向地址0x0011
的内存空间,而是变为空指针(执行b.age
会报错),c
还是指向原来的地址空间
2.1.java内存的结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(数组等)
- 方法区:常量池(字符串等)、类加载信息
2.2.java创建对象的流程分析
① 先加载Cat类信息(属性和方法信息只会加载一次)
② 在堆中分配空间,进行默认初始化
③ 把地址赋值给
c
,c
就指向对象④ 进行指定初始化,比如
c.age=15
tips:
- 当对象指向
null
时,调用对象的方法会报错,所以判断时最好写为下面形式:// 如果a指向null,不会报错 if("abc".equals(a)){ return 0; } // 如果a指向null,会报错 if(a.equals("abc")){ return 0; }
3.方法的调用机制分析
假设代码如下:
Cat c = new Cat(); c.getSum(10,20); /** --------- getSum----------- void getSum(int a, int b) { System.out.println(a + b); } */
- 当程序执行到getSum方法时,会开辟一个独立的空间(栈空间)
- getSum执行完毕后栈空间会自动销毁,main方法执行完毕时main栈也会自动销毁
4.方法的传参机制
4.1 基本数据类型
假设代码如下:
A o = new A(); int a = 10; int b = 20; o.swap(a, b); System.out.println("main方法中a=" + a + "\tb=" + b); // main方法中a=10 b=20 /**---------swap-------- public void swap(int a, int b) { int temp = a; a = b; b = temp; } */
- main栈和swap栈是两个独立的空间,所以两个栈的a和b尽管名字一样,但是也是独立的
- 基本数据类型传递是值拷贝,形参的改变不会影响实参
4.2 引用数据类型
假设代码如下:
A o = new A(); int[] a = {1,2,3}; o.test(a); System.out.println("main方法中的a[0]=" + a[0]); // main方法中的a[0]=100 /**---------test-------- public void test(int[] a) { a[0] = 100; } */
- main栈和test栈是两个独立的空间,所以两个栈的a尽管名字一样,但是也是独立的。虽然是独立的,但是指向的是同一个地址
- 引用类型传递是地址传递,形参的改变会影响实参
tips:
- 如果将test函数中改为
a=null
,最后输出a[0]还是1。因为执行a=null
后test栈中的a就不再指向地址0x0022
,即不会影响该地址中的值
5.方法重载注意事项
假设代码如下:
public void test(int[] a) { System.out.println(a[0]); } public int test(int[] a) { return 1; }
上述两个方法不属于方法重载,会报错
- 方法名相同
- 形参列表必须不同(类型或个数或顺序至少一个不同,参数名无要求)
- 对于返回类型无要求,所以上述代码会报错(如果执行
a.test(...)
,即使函数有返回值也可以不接收,和函数没有返回值一样,系统不知道要执行哪个函数)
6.可变参数
假设代码如下:
public void test(int[]... nums) { System.out.println(nums.length); }
- java允许将同一个类中多个同名同功能但参数个数不同的方法封装为一个方法,可以通过可变参数实现
- int[]…:表示接收的是可变参数,类型为int[],可以接收0个或多个
- 使用可变参数时,可以当作数组进行使用,即nums为数组
tips:
- 可变参数可以和普通类型放在一起,但是可变参数一定要放在最后
- 一个形参列表只能出现一个可变参数
7.作用域
- 全局变量:就是属性,作用域为整个类体。可以不赋值直接使用,因为有默认值
- 局部变量:除了属性外的其他变量,作用域在定义它的代码块中。必须赋值,因为没有默认值
- 全部和局部变量可以重名,访问时遵循就近原则
- 全局变量生命周期长,随着对象创建而创建;局部变量生命周期短,随着代码块执行而创建
- 全局变量可以加修饰符,局部变量不行
8.对象创建流程分析
假设代码如下:
public static void main(String[] args) { Person p = new Person("psw", 20); } /**---------Person类-------- class Person { int age = 10; String name = "psj"; Person(String n, int a){ name = n; age = a; } } */
① 在方法区中加载Person类信息(Person.class),且只会加载一次
② 在堆中分配空间
③ 完成对象初始化
- 先完成默认初始化
age=0,name=null
- 再完成显示初始化
age=10,name=”psj“
- 最后才完成构造器初始化
age=20,name=”psw“
④ 把对象在堆中的地址返回给对象引用p
9.this的使用
假设代码如下:
public static void main(String[] args) { Person p = new Person("psw", 20); p.info(); } /**---------Person类-------- class Person { int age; String name; Person(String name, int age) { name = name; age = age; } void info(){ System.out.println(name + '\t' + age); } } */
- 此时执行
p.info()
后打印出null 0
,即打印的是Person类属性的默认值。这是因为构造器的name和age是局部变量,不是属性(符合就近原则)- 修改为
this.name=name
和this.age=age
后,this.name
指的就当前对象的属性name
- 堆中对象空间存在this指向当前对象的地址
- 哪个对象调用(比如创建Person p1、p2等),this就代表哪个对象
tips:
- this可以访问本类的属性、方法、构造器。在访问构造器语法为:this(参数列表),但是只能在本类中一个构造器中调用另一个构造器,不能在普通方法中去调用构造器,并且调用语句一定是第一条语句
- this不能在类定义外部使用