之前看完了《java核心技术》,其中学习到了java传参是 传的是值还是引用的讨论,现在在看《java编程思想》时,更加理解了对象和引用的概念,结合两本书针对“对象和引用 ”的内容 做一次总结。
1.java中用引用操作对象
String s;
这句代码没有用“new”创建对象,这里只是创建一个引用,并没有对象和它进行关联。假设你对s做操作,比如:
String s;
s.length();
这时候程序就直接报错了,因为这个引用没有和任何对象做关联。
对于String来说,这才是安全的做法
String s = "abcd";
s.length();
2.java中引用和对象分别存在什么地方
一些基本类型的变量和对象的引用都是在栈内存中分配。堆内存用于存放由new创建的对象和数组。
用堆进行存储和清理要比栈进行存储和清理付出更多的时间,但是堆的灵活性更高,编译器不需要知道堆中数据的生命周期,即使栈中对象的引用不再关联对象,堆中的对象也会保留,直到java的垃圾回收机制回收 没有被任何对象的“引用” 引用过的对象,而栈的数据 需要明确它自己的生命周期,因为它在生命周期结束时会被释放内存。
3.java中的数组
当创建一个数组对象时,实际上就是创建了一个引用数组,每个引用都会初始化为null,如果试图使用一个还是null的引用,会报错。如果是new一个基本数据类型的数组,会被初始化置为0
String[] str = new String[5];
System.out.println(Arrays.toString(str));
//结果:[null, null, null, null, null]
//如果是基本类型,会默认置为0
int[] str1 = new int[5];
System.out.println(Arrays.toString(str1));
//结果:[0, 0, 0, 0, 0]
4.java对象的作用域
大多数语言都有作用域的概念,作用域决定了“引用”的可见性和生命周期
虽然a和b字符串内容相同,但是引用的是不同的字符串对象,而且b变量的生命周期更短
{
String a = "abcd";
{
String b = "abcd";
}
}
有人这样想,能不能用作用域的特性来定义两个同名的变量(引用)
看似这段代码具有合理性,在更小的作用域里面,应该可以定义一个变量名为a的String,较大作用域的a变量在小作用域里被隐藏,但是事实上java不允许那么做,设计者认为这样会造成程序混乱。
{
String a = "abcd";
{
String a = "abcd";
}
}
了解了作用域的范围,再讨论作用域结束以后,引用和对象都消失了吗?
{
Object a = new Object();
}
引用a在作用域结束以后就消失了,但是new Object()并没有,a指向的Object对象仍然占据着内存空间,这应该是对象和引用分别存放在堆和栈内存的缘故(到现在还没看到具体的解释,以后会补充,这只是我的猜想),但是因为引用a的消失,你无法再操作这个Object对象,如果没有变量再引用它,这个对象最终的命运会被java的“垃圾回收器”回收,java垃圾回收机制的存在避免了 不用的对象无法及时销毁而导致内存溢出问题。
5.类中的成员变量默认值
根据面向对象的思想,java设计了类,如果是自己设计的类,里面也有其它的类成员,如果我们new一个类,而且是空参构造器,那么类中的对象都有自己的默认值。
基本数据类型的默认值参考下图,如果是非基本数据类型,那默认值就是null
6.方法里的参数
首先明确,java里面都是按值调用,准确的说,传递过来的是一个拷贝,但是针对基本数据类型和非基本数据类型又有不同的情况
基本类型: 方法得到的是值拷贝。所以对拷贝的修改不涉及对原值的修改。
非基本类型: 方法得到的仍然是拷贝,但不是值拷贝,是引用的拷贝
定义一个类
package JDK.Array;
public class Man {
int age;
}
public static void sum(int a, Man l) {
a++;
l.age = 2;
}
int i = 1;
Man man = new Man();
man.age = 1;
System.out.println("i:" + i + " man:" + man.age);
Array_1.sum(i, man);
System.out.println("i:" + i + " man:" + man.age);
结果是:
i:1 man:1
i:1 man:2
基本数据类型没有变化,因为它是值拷贝,但是Man类因为传的是引用的拷贝,l变量和man变量都指向了对象,所以数据发生了变化。
很多人认为传参在非基本数据类型里是引用调用,这种说法是不对的,下面举一个反例:
public static void sum1(Man l, Man l1) {
Man temp = l;
l = l1;
l1 = temp;
}
Man man = new Man();
Man man1 = new Man();
man.age = 1;
man1.age = 0;
System.out.println(" man:" + man.age + " man1:" + man1.age);
Array_1.sum1(man, man1);
System.out.println(" man:" + man.age + " man1:" + man1.age);
这一次创建了两个Man对象,同时传递给sum1方法,按照之前 按引用调用 的说法,进入方法后引用的对象发生了对调, 输出的结果应该是age 的值对调了。
man:1 man1:0
man:0 man1:1
但是真正的结果是
man:1 man1:0
man:1 man1:0
age字段的值没有发生对调,所以 l和l1是 man 和man1引用的拷贝,方法交换的是拷贝,不影响引用所指向的对象。