Java核心技术之深入理解对象的生命周期

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);
        }
    }
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-20 12:26:31

Java核心技术之深入理解对象的生命周期的相关文章

Java虚拟机(三)垃圾标记算法与Java对象的生命周期

相关文章 Java虚拟机系列 前言 这一节我们来简单的介绍垃圾收集器,并学习垃圾标记的算法:引用计数算法和根搜索算法,为了更好的理解根搜索算法,会在文章的最后介绍Java对象在虚拟机中的生命周期. 1.垃圾收集器概述 垃圾收集器(Garbage Collection),通常被称作GC.提到GC,很多人认为它是伴随Java而出现的,其实GC出现的时间要比Java早太多了,它是1960诞生于MIT的Lisp. GC主要做了两个工作,一个是内存的划分和分配,一个是对垃圾进行回收.关于内存的划分和分配,

Java对象的生命周期与作用域的讨论(转)

导读: Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除.因此,对象的生命周期长度可用如下的表达式表示:T = T1 + T2 +T3.其中T1表示对象的创建时间,T2表示对象的使用时间,而T3则表示其清除时间.由此,我们可以看出,只有T2是真正有效的时间,而T1.T3则是对象本身的开销.下面再看看T1.T3在对象的整个生命周期中所占的比例. 我们知道,Java对象是通过构造函数来创建的,在这一过程中,该构造函数链中的所有构造函数也都会被自动调用.另外,默认情况下,调用

Java 对象的生命周期

Java对象的生命周期 在Java中,对象的生命周期包含下面几个阶段: 1.      创建阶段(Created) 2.      应用阶段(In Use) 3.      不可见阶段(Invisible) 4.      不可达阶段(Unreachable) 5.      收集阶段(Collected) 6.      终结阶段(Finalized) 7.      对象空间重分配阶段(De-allocated) 图1. JavaObject Life Cycle 1.创建阶段(Create

[原创]java WEB学习笔记94:Hibernate学习之路---session 的管理,Session 对象的生命周期与本地线程绑定

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

hibernate中持久化对象的生命周期(三态:自由态,持久态,游离态 之间的转换)

三态的基本概念: 1,  暂时状态(Transient):也叫自由态,仅仅存在于内存中,而在数据库中没有对应数据.用new创建的对象,它没有持久化,没有处于Session中,处于此状态的对象叫暂时对象: 2,  持久化状态(Persistent):与session关联而且在数据库中有对应数据.已经持久化,添?到了Session缓存中.如通过hibernate语句保存的对象.处于此状态的对象叫持久对象: 3,  游离状态(Detached):持久化对象脱离了Session的对象.如Session缓

Java 多线程(三) 线程的生命周期及优先级

Java 多线程(三) 线程的生命周期及优先级 线程的生命周期 线程的生命周期:一个线程从创建到消亡的过程. 如下图,表示线程生命周期中的各个状态: 线程的生命周期可以分为四个状态: 1.创建状态: 当用new操作符创建一个新的线程对象时,该线程处于创建状态. 处于创建状态的线程只是一个空的线程对象,系统不为它分配资源. 2.可运行状态: 执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体——run()方法,这样就使得该线程处于可运行状态(Runnable). 这一

hibernate--持久对象的生命周期介绍

持久化对象的状态 : 1. 瞬时对象(Transient Object):使用new操作符初始化的对象不是立刻就持久的.它们的状态是瞬时的,也就是说它们没有任何跟数据库表相关联的行为,只要应用不再引用这些对象(不再被任何其它对象所引用),它们的状态将会丢失,并由垃圾回收机制回收 2. 持久化对象(Persistent Object):持久实例是任何具有数据库标识的实例,它有持久化管理器Session统一管理,持久实例是在事务中进行操作的----它们的状态在事务结束时同数据库进行同步.当事务提交时

第十一章 对象的生命周期

第11章 对象的生命周期 11.1  创建对象的方式 用new语句创建对象 运用反射手段,调用java.lang.Class 或者 java.lang.Constructor 类的newInstance()实例方法. 调用对象的clone()方法. 运用反序列化手段 11.2 构造方法 在多数情况下,初始化对象的最终步骤是去调用这个对象的构造方法.构造方法负责对象的初始化工作,为实例变量赋予合适的初始化值. 构造方法满足的语法规则: 方法名必须与类名相同 1 public class Sampl

Bean对象的生命周期

单例对象 出生:当容器创建对象出生 活着:只要容器还在,对象一直活着 死亡:容器销毁,对象消亡 总结:单例对象的生命周期和容器相同 多例对象 出生:当我们使用对象时spring框架为我们创建 活着:只要使用就一直在活着 死亡:当对象长时间不用,且没有别的对象使用时,由Java回收机制回收 举个例子:单例对象 package com.xuefei.service.impl; import com.xuefei.service.AccountService; /** * 账户业务层实现类 */ pu