1 总体说明
Java类的初始化
对象的创建与初始化
对象的销毁
对象的复制
2 Java类的初始化
自然界的任何生物都有个由生到死的过程。
而当源代码编译成class文件被JVM的类加载器装载到内存中后,也会经历这么一个由生到死的过程,接下来就分析Java类的初始化到底做了哪些事情。这里暂时不谈JVM将class文件装载到内存的详细细节,只是从类的五种成员来观察类的初始化
之前提到过类的五种成员,包含属性,方法,构造器,代码块以及静态代码块五个部分。其中静态代码块和静态属性(也就是static修饰的成员变量)在类第一次被真正使用时,JVM会对其按照出现的顺序执行初始化,而且该类初始化之前,它的直接父类也会先被初始化(因为按照常理:先有父亲,后有孩子),但是该类所实现的接口不会被初始化。
对于一个接口来说,当其被初始化时,它的父类接口不会被初始化。
Product类作为父类,从上到下依次定义了:
①静态代码块实例化Product对象,
②静态实例化Product对象,
③默认的构造器,该构造器主要是显示成员变量的值,
④重载的构造器,依然是显示成员变量的值,
⑤俩个成员变量以及get set方法,
⑥toString方法,用于显示当前对象的成员变量值
package tony.javacore.oop.init.classes;
import static tony.javacore.util.io.PrintUtils.println;
/**
* <p>包含静态代码初始化和静态初始化两种方式初始化的Product父类</p>
* @author tony [email protected]
* @project_name JavaCore
* @package_name tony.javacore.oop.init.classes
* @file_name Product.java
* @see
* @version 2015年9月4日下午1:21:20
* @since JDK8.0u45
*/
public class Product {
/*首先使用静态代码块实例化Product对象*/
static{
Product product =new Product("iphone1代",4999.0);
}
/*实例化静态Product对象*/
static Product product2 =new Product();
Product(){
println("执行Product父类默认的构造方法!!!");
println("父类成员变量的初始值为 : "+this.toString());
}
public Product(String name, double price) {
/*将构造器传递的参数值赋值给对象的成员变量*/
this.name = name;
this.price = price;
println("执行Product父类带参数的构造方法!!!");
println("父类成员变量的初始值为 : "+this.toString());
}
private String name;
private double price;
/*get set method*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Product [name=" + name + ", price=" + price + "]";
}
}
作为父类Product的子类MobilePhone也同样定义了和Product父类类似的结构
package tony.javacore.oop.init.classes;
import static tony.javacore.util.io.PrintUtils.println;
/**
* <p>子类MobilePhone</p>
* @author tony [email protected]
* @project_name JavaCore
* @package_name tony.javacore.oop.init.classes
* @file_name MobilePhone.java
* @see
* @version 2015年9月4日下午1:29:19
* @since JDK8.0u45
*/
public class MobilePhone extends Product {
/*使用静态代码块创建MobilePhone对象*/
static{
MobilePhone mobilePhone =new MobilePhone("iphone2代",5999.0,false);
}
static MobilePhone mobilePhone =new MobilePhone();
MobilePhone(){
println("执行子类MobilePhone的默认构造方法!!!");
println("子类成员变量的初始值为 : "+this.toString());
}
public MobilePhone(String name,double price ,boolean replaceable) {
super(name,price); //使用super关键字调用父类对应参数的构造器
this.replaceable = replaceable;
println("执行子类MobilePhone带参数的构造方法");
println("子类成员变量的初始值为 : "+this.toString());
}
private boolean replaceable; //是否可更换电池
/*get set method*/
public boolean isReplaceable() {
return replaceable;
}
public void setReplaceable(boolean replaceable) {
this.replaceable = replaceable;
}
@Override
public String toString() {
return "MobilePhone [replaceable=" + replaceable + ", name=" + getName() + ", price=" + getPrice()
+ "]";
}
}
程序的运行结果表明
1.如果类中有静态成员变量和静态方法,先执行静态成员变量的初始化。例如这里静态的Product类型的引用变量mobilePhone就在main方法之前执行了,main方法也是静态的方法,其中还调用了一个名为description的静态方法。
2.当实例化一个对象时,类会被初始化。例如Product mobilePhone =new MobilePhone();
3.当类被初始化时,会先执行该类对应父类照定义的顺序执行类中的静态代码块和静态对象的初始化,然后再执行子类的初始化。例如MobilePhone继承了Product,当使用多态实例化对象时,先执行父类Product的初始化,再执行子类MobilePhone的初始化。
package tony.javacore.oop.init.classes;
import static tony.javacore.util.io.PrintUtils.println;
/**
* <p>静态初始化的顺序</p>
* @author tony [email protected]
* @project_name JavaCore
* @package_name tony.javacore.oop.init.classes
* @file_name StaticInitOrderHandler.java
* @see
* @version 2015年9月4日下午1:34:02
* @since JDK8.0u45
*/
public class StaticInitOrderHandler {
static void description(){
println("执行静态方法 descrption()");
}
//这里使用了多态:父类的引用指向子类的对象
static Product mobilePhone =new MobilePhone();
public static void main(String[] args) {
println("执行StaticInitOrderHandler.main()方法!!!");
description();
}
}
/**
* 程序运行结果
* 执行Product父类带参数的构造方法!!!
父类成员变量的初始值为 : Product [name=iphone1代, price=4999.0]
执行Product父类默认的构造方法!!!
父类成员变量的初始值为 : Product [name=null, price=0.0]
执行Product父类带参数的构造方法!!!
父类成员变量的初始值为 : MobilePhone [replaceable=false, name=iphone2代, price=5999.0]
执行子类MobilePhone带参数的构造方法
子类成员变量的初始值为 : MobilePhone [replaceable=false, name=iphone2代, price=5999.0]
执行Product父类默认的构造方法!!!
父类成员变量的初始值为 : MobilePhone [replaceable=false, name=null, price=0.0]
执行子类MobilePhone的默认构造方法!!!
子类成员变量的初始值为 : MobilePhone [replaceable=false, name=null, price=0.0]
执行Product父类默认的构造方法!!!
父类成员变量的初始值为 : MobilePhone [replaceable=false, name=null, price=0.0]
执行子类MobilePhone的默认构造方法!!!
子类成员变量的初始值为 : MobilePhone [replaceable=false, name=null, price=0.0]
执行StaticInitOrderHandler.main()方法!!!
执行静态方法 descrption()
*
*/
static修饰的成员只会占据一份存储空间,通常用来修饰类中的成员变量(属性)以及方法,使用static修饰后可以直接通过类名调用属性和方法,而不需要通过实例化对象来调用它们。
static修饰的成员只会占据一份存储空间,不属于某个特定的对象,而是和类关联,如果在某处代码中修改了static修饰的属性值,那么所有的对象获取的属性值都是相同的:修改后的属性值。
有很多不同的原因会使一个Java类初始化,除了之前提到的实例化对象会完成初始化之外,还有如下情况:
① 调用一个Java类中的静态方法
② 为类或者接口中的静态成员赋值,并且成员的值不是常量。
③ 调用Class类和反射API中进行反射操作的方法也会初始化Java类。
3 对象的创建与初始化
在Java语言中,除了那八种基本数据类型,在运行的Java程序中出现的都是对象,也就是java.lang.Object的子类对象,JDK5.0中的新特性可以将基本数据类型通过自动装箱的功能将基本数据类型转换成对象类型,与其他对象进行交互。
Java语言中对象的创建一般都是采用构造器的方式来实例化对象,例如:
MobilePhone mobilePhone =new MobilePhone(“iphone2代”,5999.0,false);
那么问题来了:类中的属性以及代码块是什么时候执行初始化的呢?
首先指明一点,如果该类有继承自其他的父类,java.lang.Object类除外,先执行父类代码块以及成员变量的初始化。
Person类定义了四种类型的成员:
①静态代码块
②成员变量
③重载的构造器
④ toString()方法
package tony.javacore.oop.init.object;
import static tony.javacore.util.io.PrintUtils.println;
/**
* <p>父类Person</p>
* @author tony [email protected]
* @project_name JavaCore
* @package_name tony.javacore.oop.init.object
* @file_name Person.java
* @see
* @version 2015年9月4日下午3:53:46
* @since JDK8.0u45
*/
public class Person {
static{
println("Person父类静态代码块只会在类加载时执行一次");
}
int age;
String name;
Person(){
println("执行Person父类不带参数的构造器");
println("Person成员变量的值 : "+this.toString());
}
Person(int age,String name){
this.age=age;
this.name=name;
println("执行Person父类带参数的构造器");
println("Person成员变量的值 : "+this.toString());
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + "]";
}
}
子类Student定义了三种成员:
①代码块
②成员变量
③重载的构造器
package tony.javacore.oop.init.object;
import static tony.javacore.util.io.PrintUtils.println;
/**
* <p>子类Student</p>
* @author tony [email protected]
* @project_name JavaCore
* @package_name tony.javacore.oop.init.object
* @file_name Student.java
* @see
* @version 2015年9月4日下午3:40:12
* @since JDK8.0u45
*/
public class Student extends Person{
/**
* 使用代码块实例化两个Person类型的对象
* 注意这里不能实例化为Student类型的对象,不然代码会造成栈内存溢出
* 因为实例化对象,调用构造器之前会先执行{}的内容,递归调用,程序就挂了
*/
{
println("使用代码块初始化Person类型的引用变量boy");
Person boy =new Person();
boy.name="tony";
boy.age=26;
}
{
println("使用代码块初始化Person类型的引用变量girl");
Person girl =new Person(19,"jingjing");
}
double score;
public Student(){
println("执行Student子类不带参数的构造器");
println("Student子类的成员变量值 : "+toString());
}
public Student(int age, String name, double score) {
super(age, name);
this.score = score;
println("执行Student子类带参数的构造器");
println("Student子类的成员变量值 : "+toString());
}
}
程序运行结果表明
① 实例化子类对象之前会先执行父类的静态初始化,然后执行父类对应参数列表的构造器完成父类的初始化:给父类成员变量根据不同的数据类型开辟内存空间存储默认值。
② 接着执行子类的代码块(任务是:实例化父类Person对象),调用对应参数列表的构造方法完成初始化:给成员变量开辟内存空间存储默认值,根据不同的数据类型,如果构造方法中传递了参数,并在构造方法中赋值时,该对象的成员变量默认值会被覆盖。
③ 最后执行子类对应参数列表的构造器
子类使用构造器创建时会先调用父类对应参数列表的构造器,构造器主要是完成对象的初始化操作,代码块是在调用构造方法之前执行。
package tony.javacore.oop.init.object;
import static tony.javacore.util.io.PrintUtils.println;
/**
* <p>对象初始化顺序</p>
* @author tony [email protected]
* @project_name JavaCore
* @package_name tony.javacore.oop.init.object
* @file_name ObjectInitHandler.java
* @see
* @version 2015年9月4日下午2:43:52
* @since JDK8.0u45
*/
public class ObjectInitHandler {
public static void main(String[] args) {
Person person =new Student();
}
/**
* 在ObjectInitHandler类的main方法外部非静态初始化Person对象,不会触发Person类的初始化
*
*/
Person person =new Student();
}
/**
* 程序运行结果
Person父类静态代码块只会在类加载时执行一次
执行Person父类不带参数的构造器
Person成员变量的值 : Person [age=0, name=null]
使用代码块初始化Person类型的引用变量boy
执行Person父类不带参数的构造器
Person成员变量的值 : Person [age=0, name=null]
使用代码块初始化Person类型的引用变量girl
执行Person父类带参数的构造器
Person成员变量的值 : Person [age=19, name=jingjing]
执行Student子类不带参数的构造器
Student子类的成员变量值 : Person [age=0, name=null]
*
*/
4 对象的销毁
之前介绍的都是类和对象的创建以及初始化的流程,那么Java中的对象什么时候会被销毁呢?
首先明白一点,Java语言中除了那八种基本数据类型,其他一切都是对象。
而在创建和使用对象的过程中,可能申请了相关的资源,这些资源主要分为两类:
①内存资源:对象的实例域所占据的内存空间,当一个对象没有引用指向它时,说明该对象可以被销毁,而对象的销毁是JVM的垃圾回收机制来执行的,调用对象的finalize方法。
②非内存资源:程序运行时申请的其他资源。一个完整的Java程序往往会使用到IO操作,JDBC数据库链接和网络资源,这些资源需要在程序中显示释放,关于这些资源以及API的使用后面的博文会介绍。
5 对象的复制
之前阅读Object类的源码时得知该类中有个clone()方法,如果子类实现java.lang.Cloneable接口,就可以通过重写clone方法来实现对象的浅克隆。如果对象中只包含基本数据类型的成员变量或者不可变对象的成员变量,浅克隆就足够了。
但是浅克隆时如果克隆的对象包含引用数据类型时是有问题的,如下代码。
① 定义了引用数据类型Counter,该类中主要包含一个整数类型的成员变量以及俩方法,分别是自增该成员变量以及获取该成员变量的值。
② 定义了引用类型MuableObject,该类中主要组合之前定义的引用类型MuableObject,并定义了获取和更改MuableObject对象的值。MuableObject覆盖父类java.lang.Object类的clone方法,实现对象的浅克隆。
③ 在MuableObject的main方法中分别实例化一个对象和克隆被实例化的对象,无论是实例化(objInstance )或者是克隆(cloneObj)的对象修改Counter对象的属性值后,两个对象都是获取修改后的值。
package tony.javacore.source.object;
import static tony.javacore.util.io.PrintUtils.println;
/**
* <p>浅克隆的问题</p>
* @author tony [email protected]
* @project_name JavaCore
* @package_name tony.javacore.source.object
* @file_name MuableObject.java
* @see
* @version 2015年9月5日下午1:26:47
* @since JDK8.0u45
*/
public class MuableObject implements Cloneable{
private Counter counter =new Counter();
public void increase(){
counter.increase();
}
public int getValue(){
return counter.getValue();
}
//重写父类Object的克隆方法(浅克隆)
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
}
//浅克隆测试
public static void main(String[] args) {
MuableObject objInstance =new MuableObject();
objInstance .increase();
MuableObject cloneObj=(MuableObject) obj.clone();
cloneObj.increase();
objInstance .increase();
println(cloneObj.getValue()); //值为3
}
}
//定义一个引用类型
class Counter{
private int value =0;
public void increase(){
value++;
}
public int getValue(){
return value;
}
}
由于浅克隆只是将类中成员的值复制一份,实例化的对象和克隆的对象占据不同的内存空间,而如果类中持有引用数据类型的话,复制的是引用地址,如果在实例化对象或者是克隆对象修改该引用类型的值,两者获取的都是修改后的值。如果要避免这一现象,必须重写clone方法,来实现对象的深克隆。
package tony.javacore.source.object;
import static tony.javacore.util.io.PrintUtils.println;
/**
* <p>深克隆</p>
* @author tony [email protected]
* @project_name JavaCore
* @package_name tony.javacore.source.object
* @file_name MuableObject.java
* @see
* @version 2015年9月5日下午1:26:47
* @since JDK8.0u45
*/
public class MuableObject implements Cloneable{
private Counter counter =new Counter();
public void increase(){
counter.increase();
}
public int getValue(){
return counter.getValue();
}
//重写父类Object的克隆方法
@Override
public Object clone() {
//浅克隆
/* try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(e);
}*/
//深克隆
MuableObject cloneObj;
try {
cloneObj=(MuableObject) super.clone();
//调用引用类型的克隆方法
cloneObj.counter= (Counter) counter.clone();
return cloneObj;
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
}
public static void main(String[] args) {
//实例化对象
MuableObject instanceObj =new MuableObject();
//克隆对象
MuableObject cloneObj=(MuableObject) instanceObj.clone();
//修改前
println("修改前实例化对象的引用类型值 : "+instanceObj.getValue());
println("修改前克隆对象的引用类型值 : "+cloneObj.getValue());
cloneObj.increase();
instanceObj.increase();
println("修改后实例化对象的引用类型值 : "+instanceObj.getValue());
println("修改后克隆对象的引用类型值 : "+cloneObj.getValue());
cloneObj.increase();
println("单独修改克隆对象,实例化对象的引用类型值 : "+instanceObj.getValue());
println("单独修改克隆对象的引用类型值 : "+cloneObj.getValue());
}
}
//定义一个引用类型,该类需要实现Cloneable接口,并实现clone方法以实现深克隆
class Counter implements Cloneable{
private int value =0;
public void increase(){
value++;
}
public int getValue(){
return value;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
}
}
版权声明:本文为博主原创文章,未经博主允许不得转载。