JAVA中new关键字及对象(object)与引用(reference)介绍
Java使用关键字 new 来创建对象,这是常用的创建对象的方法,有人给出语法格式如下:
类名 对象名 = new 类名([参数]);
特别提示,实际上应是或应理解为:
类名 对象名 = new 构造方法名([参数列表]);
上句和如下两句:
类名 对象名;
对象名 = new 构造方法名([参数列表]);
等价。
赋值号右边的new是为新建对象开辟内存空间的运算符,用new运算符开辟新建对象的内存之后,系统自动调用构造方法初始化该对象。
New后是构造方法名,只不过类的构造方法名和类名一样。Java每个类都有构造方法,如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认的无参数构造方法。构造方法定义了当创建一个对象时要进行的操作。关于构造方法请参见其它资料。
其中的对象名,也称为对象引用名;[参数列表]是可选项,多个参数用英文逗号分隔。
【官网对new 关键字介绍见 Creating Objects https://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html 】
下面详细解析之。
《Thinking in Java》一书中提到,引用和对象就像瑶控器和电视机。用瑶控器(引用)来操作电视机(对象),想换频道什么的直接操作瑶控器就可以了,瑶控器再来调控电视机。这个比喻非常好说明了对象与引用的关系。
创建的对象可以通过引用来操作。
通常用new关键字来创建一个对象,就可以通过引用来操作。
那么对象是怎样存储的,内存又是怎样分配的呢?
栈(stack):位于通用RAM中,通过栈指针的移动来分配和释放内存,指针向下移动分配新的内存;指针向上移动则释放内存。速度仅次于寄存器。创建程序时,Java编译器必须知道存储在栈内所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动栈指针,这就限制了程序的灵活性。所以java中的对象并不存放在栈当中,但对象的引用存放在栈中。
堆(heap):也是位于RAM中的内存池,用于存放所有的JAVA对象。编译器不需要知道要从堆里分配多少存储区域,也不需要知道存储的数据在堆里面存活多长时间,因此堆要比栈灵活很多。当你new创建一个对象时,编译器会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用栈进行存储存储需要更多的时间。
静态存储(static storage):这里的“静态”是指“在固定的位置”(也在RAM里)。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,即存放类中的静态成员,但JAVA对象本身从来不会存放在静态存储空间里。
常量存储(constant storage):存放字符串常量和基本类型常量(public static final)。常量值通常直接存放在程序代码内部,它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中。
寄存器(register):由于寄存器是在CPU内部的,所以它的速度最快,但是数量有限,所以由编译器根据需求进行分配。
Java垃圾回收机制
垃圾回收回收的是无任何引用的对象占据的内存空间(堆)而不是对象本身。
1)引用计数器方式:一种简单但是速度很慢的垃圾回收策略。即每个对象都有一个引用计数器,当有引用连接至对象时计数器加1;当引用离开时计数器减1。垃圾回收器会在含有全部对象的列表中遍历,发现某个对象的引用计数器为0时,就释放其占用的内存。
优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。
缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
2)自适应、分代的、停止——复制、标记——清扫 垃圾回收方式:
停止——复制:先暂停程序的运行,然后将所有活的对象从当前堆复制到另一个堆,没有被复制的都是垃圾。当对象从一个堆复制到另一个堆,它们的排列是一个挨着一个的,所以新堆保持紧凑排列。
标记——清扫:遍历所有的引用,找出所有活的对象,然后对它们进行标记,这个过程不会回收任何对象,只有全部标记工作完成时才开始清除工作。没有被标记的对象将会被释放,不发生任何复制动作,所以剩下的堆空间不是连续的。
对象:要理解什么是对象,需要跟类一起结合起来理解。下面这段话引自《Java编程思想》中的一段原话:
“按照通俗的说法,每个对象都是某个类(class)的一个实例(instance),这里,‘类'就是‘类型'的同义词。”
从这一句话就可以理解到对象的本质,简而言之,它就是类的实例,比如所有的人统称为“人类”,这里的“人类”就是一个类(物种的一种类型),而具体到每个人,比如张三这个人,它就是对象,就是“人类”的实例。
对象引用:《Java编程思想》一段话: “每种编程语言都有自己的数据处理方式。有些时候,程序员必须注意将要处理的数据是什么类型。你是直接操纵元素,还是用某种基于特殊语法的间接表示(例如C/C++里的指针)来操作对象。所有这些在 Java 里都得到了简化,一切都被视为对象。因此,我们可采用一种统一的语法。尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“引用”(reference)。”
基本数据类型和引用数据类型:Java的其中基本类型变量有四类8种:byte、 short 、int 、long 、float 、double、 char、 boolean,除了8种基本数据类型变量,其他变量都是引用数据类型,如类、接口、数组等。
基本数据类型,只有一块存储空间, 在栈中,存放的是具体数据值。
引用数据类型,有两块存储空间一个在栈(Stack)中,一个在堆(heap)中。堆中存放对象实体(使用new关键字,即表示在堆中开辟一块新的存储空间),栈中存放对象在堆中所在位置的首地址。new 操作符的返回值是一个对象的引用。
一个引用类型变量(栈中的一块内存空间)保存了一个该类型对象在堆中所在位置的首地址,也称作一个引用类型变量指向了一个该类型的对象,通过这个变量就可以操作对象中的数据。
下面借助一个简单的例子解释之
// Person类部分
class Person {
String name;
int age;
}
//PersonTest 类部分
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "张伟";
p1.age = 17;
System.out.println("姓名:" + p1.name + ",年龄:" + p1.age);
Person p2 = new Person();
p2.name = "李丽";
p2.age = 12;
System.out.println("姓名:" + p2.name + ",年龄:" + p2.age);
p1 = p2;
p1.age = 13;
System.out.println("姓名:" + p1.name + ",年龄:" + p1.age);
System.out.println("姓名:" + p2.name + ",年龄:" + p2.age);
}
}
下面使用bluej(由澳大利亚墨尔本Monash大学BlueJ小组设计并开发的Java开发环境)编译运行情况
运行结果如下:
解释如下
内存分配示意图如下:
当多个对象的引用指向堆同一块内存空间(p1 = p2; 即将一个对象的引用赋值给另一个变量,两个变量所记录的地址值是一样的,此时p1指向 p2所指向的对象,不再指向 p1原本指向的对象,如上图所示),只要通过任何一个对象引用修改了对象中的数据,随后,无论使用哪一个对象引用获取数据,得到的都是修改后的值,因此,最后两行打印语句的结果是一样的。