精华内容
下载资源
问答
  • 木 头 课 堂小 木 正 文——————————————❶对象实例化过程——————————————理解Java中对象实例化过程是很重要的,实例化过程主要步骤如下:第1步:在创建类之前,检查类是否加载(是将硬盘上的...

    35d0771a26c8c48aaa83982632729447.png

    木 头 课 堂

    2c2c7ca1184baabfa1242a337ea7e213.png

    小 木 正 文

    ——————————————

    ❶对象实例化过程

    ——————————————

            理解Java中对象实例化过程是很重要的,实例化过程主要步骤如下:

            第1步:在创建类之前,检查类是否加载(是将硬盘上的.class文件加载到内存中), 如果没有加载就加载这个类,在这个类加载之前要加载所有父类。

            Java运行时采用的策略:懒惰式加载(按需加载) :如果第一次用到就加载,只加载一次。通过CL ASSPATH指定的路径寻找类文件(.class), 加载以后是一个对象,类型是Class。

            第2步:在内存堆中分配对象空间。递归分配所有父类和子类属性空间。

            属性默认自动初始化。自动初始化为"0"值。

            第3步:进行属性的赋值。

            第4步:递归调用父类构造器。(默认调用父类无参数构造器! )

            第5步:调用本类构造器。

            第1步:检查并加载类首先检查内存中Koo有没有加载,如果Koo没有加载会加载Koo并加载父类Foo。

            第2步:在内存堆中分配对象空间先递归的分配父类型空间(Foo) : int a=0 (int类型空间, 默认值为0)再分配子类型空间(Koo) : int b= 0。

    89cdbfd8b5c64382dc77d96e7c613507.png

            第3步:进行属性的赋值。

    511e83c3860a556f9927e162cc204114.png

            第4步:递归调用父类构造器。

    d5f49f4b56e57d5b9007573d96e61950.png

            第5步:调用本类构造器。

    8cc40d9384904b60942bd09df20c6c88.png

            new运算:创建对象实例(共3步)

            第1步根据类的属性在堆中分配对象空间,并且自动初始化实例变量。

    7ba5306706a0e268731789f4d6c4f750.png

            第2步根据参数类型调用构造器。

    7ca77d99a220371c34f3597dca4ed97b.png

    e2b0275bf2dab7770fc4b2d28858ce3a.png

            注: this编译时不存在,运行时才出现。

            第3步new运算返回对象的引用地址。

    a8a767584f0f5f50bc4c44277236f658.png

            Java的内存管理与分配

            1、栈是一块Java使用内存区域,使用的方式: LIFO(后来者居上)。

            2、堆也是一块Java使用内存区域,使用方式:随机分配。

            局部变量与堆对象空间分配

            1、Java局部变量和方法参数在栈中分配,大小是按照变量的类型分配。

            2、对象在堆中分配,按照类中声明属性(实例变量)分配空间。

            变量类型与分配

            1、变量分为:基本类型和引用类型。

            2、基本类型变量的空间大小:就是基本类型的空间大小,值是基本类型的值。

            3、引用变量的值是一个对象的地址值,引用变量通过地址引用了一个堆对象。

            4、引用类型变量的占用空间大小和值管理是"透明的(不可看见)"由Java系统管理:变量占用空间以及值的管理,都是透明的。

    ——————————————

    ❶构造器的作用

    ——————————————

            构造器(构造方法)的作用

            构造器(构造方法)的作用:用来描述对象的创建过程的。

            举例说明:

    9e70f4fa016d735f488a0b4f67a6a539.png

            Java中的构造器

            构造方法(构造器)要点:

            1、Java中的构造器(构造方法)声明在类内部。

            2、方法名与类名一致的方法叫构造方法。

            3、构造方法不能声明返回值类型。

            4、构造方法可以包含参数,参数一般是创建对象实例必须依赖的条件(前提条件)。

            下面我们写了一个带参数的构造器(构造方法),如下所示:

    public class Point2 {    int x;    int y;/***构造器* @param x* @param y*/    public Point2(int x, int y) {        this.x= x;        this.y=y;    }}

            注:常用写法,this的含义后续介绍。

    ——————————————

    ❶默认构造器

    ——————————————

            Java类一定有构造器。

            当使用new关键字时, JVM必定调用构造方法创建对象。

            如果类没有声明任何构造器,Javac自动提供一个。

    /***默认构造器java类一定有构造器*/public class ConstructorDemo {      public static void main(String[] args) {          Foo foo = new Foo();      }}class Foo {  // public Foo(){}默认构造器}

            默认构造器,当class中不写构造器时,javac提供一 个无参数默认构造器。

            如果提供构造器声明,Javac将不再提供默认构造器。

            调用默认无参构造器时会出错,而调用自定义有参构造器没有问题。

    /***默认构造器java类- 定有构造器*/public class ConstructorDemo2 {      public static void main(String[] args) {            Foo foo = new Foo(); //调用的是默认构造器            // Goo goo = new Goo();没有Goo            Goo goo = new Goo(2); //调用构造器Goo(int a)       }}class Foo {    // public Foo() {}默认构造器}class Goo {        int a;        public Goo(int a) {//如果提供了构造器,javac不提供默认构造器            this.a= a;        }}

    ——————————————

    ❶构造方法重载

    ——————————————

            构造方法重 载即写多个参数不一样的构造器。构造方法重载是为了实现构造方法的重用。

    public class Point {    int x;    int y;    /**    *表示对角线上的点    */    public Point(int x) {        this.x = x;//调用本类的构造器,根据参数列表匹配    }    /**    *构造器    * @param x    * @param y    */    public Point(int x, int y) {        this.x= x;        this.y=y;    }}

    注:

            1、方法签名=方法名+参数类型列表。

            2、方法重载有如下规则:

                a方法名一样。

                b方法参数不一样。

            3、重载方法也可以说是,方法名一样,方法签名不同的方法。

    2c2c7ca1184baabfa1242a337ea7e213.png

    无  成 本 赞 赏

    生活不易,还请小伙伴多多支持69be331c02cca6a805154a7098f78b5e.png69be331c02cca6a805154a7098f78b5e.png69be331c02cca6a805154a7098f78b5e.png,顺手给小编加个鸡腿,万分感谢~~~

    END

    — — — — — — — — — — — — —

     1fe559a994381d75e3417991039aa486.png

    更多信息,扫码关注我们哦!

    f58afb3365f436463ed7ca211a4acd4d.gif

    展开全文
  • Spring 实例化 bean 的方式构造器的方式静态工厂方式实例化工厂方式案例实操构造器的方式实例化 bean 对象<bean id="hello" name="hello" class="com.xxx.demo.Hello"></bean>通过默认构造器创建 空构造...

    490fdb0fc169db467e6fe79505c13fb8.png

    Spring 实例化 bean 的方式

    • 构造器的方式
    • 静态工厂方式
    • 实例化工厂方式

    案例实操

    构造器的方式实例化 bean 对象

    <bean id="hello" name="hello" class="com.xxx.demo.Hello"></bean>

    通过默认构造器创建 空构造方法必须存在 否则创建失败

    静态工厂模式

    特点:

    要有该工厂类及工厂方法

    工厂方法是静态的

    StaticFactory 静态工厂

    /**
     * 静态工厂模式
     *
     */
    public class StaticFactory {
        public static GoodsService createGoodsService() {//方法一定要是一个静态方法
            return new GoodsService();
        }
    }

    GoodsService实体类

    public class GoodsService {
        public void getGoodsInfo() {
            System.out.println("外星人贼便宜");
        }
    }

    Bean配置

    <bean id="goodsService" 
            class="com.xxx.demo.StaticFactory" factory-method="createGoodsService"></bean>

    当我们指定Spring使用静态工厂方法来创建Bean实例时,Spring将先解析配置文件,并根据配置文件指定的信息,通过反射调用静态工厂类的静态工厂方法,并将该静态工厂方法的返回值作为Bean实例,在这个过程中,Spring不再负责创建Bean实例,Bean实例是由用户提供的静态工厂方法提供的。

    实例化工厂方式创建 Bean

    相比较静态工厂实现

    1、工厂方法为非静态方法

    2、需要配置工厂bean,并在业务bean中配置factory-bean,factory-method属性

    实例化工厂定义

    /**
     * 实例化工厂
     * @author Best Liu
     *
     */
    public class InstanceFactory {
        public OrderService createOrderService() {
            return new OrderService();
        }
    }

    实体类定义

    public class OrderService {
        public void getOrderInfo() {
            System.out.println("亲,已经下单完成,但是想发货没门");
        }
    }

    Bean配置

    <!-- 
        实例化工厂 
        1、定义实例化工厂bean
        2、引用工厂bean指定工厂创建方法(方法为非静态)
    -->
        <bean id="instanceFactory" class="com.xxx.demo.InstanceFactory"></bean>
        <bean id="orderService" factory-bean="instanceFactory" factory-method="createOrderService"></bean>

    扩展

    Spring 三种实例化 bean 的方式比较

    方式一:通过bean的缺省构造函数创建,当各个bean的业务逻辑相互比较独立的时候或者和外界关联较少的时候可以使用。

    方式二:利用静态factory方法创建,可以统一管理各个bean的创建,如各个bean在创建之前需要相同的初始化处理,则可用这个factory方法先进行统一的处理等等。

    方式三:利用实例化factory方法创建,即将factory方法也作为了业务bean来控制,

    1、可用于集成其他框架的bean创建管理方法

    2、能够使bean和factory的角色互换

    开发中项目一般使用一种方式实例化bean,项目开发基本采用第一种方式,交给spring托管,使用时直接拿来使用即可,另外两种了解即可。

    展开全文
  • 如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。面向对象编程(Object ...

    现在都流行面前对象编程。文字介绍可能看得很晕,看不懂和不理解当然看得晕,不用怕,慢慢看到最后你会有所收获。

    3b22d4bddcfe5eb7e509c8e67afc1793.png
    1. 如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。
    2. 面向对象编程(Object Oriented Programming),简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
    3. Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。我们将详细介绍Python的面向对象编程。
    4. 在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

    python新手学习面向对象基本特征:

    在Python中,定义类是通过class关键字,class后面紧接着是类名,即ClassName,类名通常是大写开头的单词。我喜欢驼峰式写法首字母都大写。

    定义类:

    第一种类名后面直接跟冒号。

    8dd03864063decfb03b93befc9210fa6.png

    第二种类名后面跟小括号再冒号。

    583c549b1907867ed1a2b76c33fbea62.png

    第三种类名后面括号中object再跟冒号。

    222537beaa8cc56946e33ce83c827e1a.png

    三种的关系:第一种和第二种程序会默认就是第三种,第一种和第二种只是简写。如果没有继承自定义的类,就可以这样写。

    如果要继承自定义的类就不能够简写。(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

    比如你有个类叫object1,你想继承object1,你用简写就直接继承程序默认的object。


    方法:事实它就是一个函数,只不过在类中定义的时候叫方法。

    属性:事实它就是一个变量,只不过在类中定义的时候叫属性和有点不同。

    构造函数:__init__()该方法在类实例化时会自动调用,又叫初始化方法。

    ec1cee9210d8e6a9abf3393e05f8f110.png

    其中构造方法有两个参数(id 和 age),self.id和self.age就是两个属性。


    实例化对象:

    f94c789d1d992c5055229046ae139865.png

    创建多一个方法output(),打印构造函数的属性。

    创建实例:

    3d7fabc686df08ac7d507f7aabbbaece.png

    输出结果:

    d66647ca250ec2e5264d864e6f904230.png

    self代表类的实例,而非类

    类的方法与普通的函数只有一个特别的区别:

    在类地内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数self,且为第一个参数,self代表的是类的实例。

    self的名字并不是规定死的,也可以使用this,但是最好还是按照约定是用self。

    self就是实例化op自身,其中self.name可以等同op.name。

    以后再创建一个xh= ClassName('小红',18),这个时候self就是xh。

    注意点:

    注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

    有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去。


    类属性与方法

    类的私有属性

    __private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时self.__private_attrs

    类的私有方法

    __private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类地外部调用。self.__private_methods

    e6786fd061b548933a1dcdb1507bf0ed.png

    继承:Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。

    在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

    继承和方法重写:

    定义一个People人类,用Student学生类继承People人类。

    如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法。子类只需要写一个跟父类相同的方法(函数)就可以覆盖重写。

    b057abcd8299c61d6bf66f38f55ec373.png

    输出结果:

    d8982bde97484d0aefbb4392bb677d00.png

    总结概论:

    class关键字后面跟类名,继承就在小括号里面输入需要继承的类名。

    __init__()构造函数该方法在类实例化时会自动调用。比如你从外面回来要进家之前要用钥匙开门,这个步骤就是初始化。

    __del__() 析构函数,释放对象时使用。比如你每次锁门要出去,析构函数默认你锁上门就完了。突然有一次抽风对门说:芝麻关门。析构函数就是程序执行完之前要做的最后一件事。

    self的名字并不是规定死的,也可以使用this,但是最好还是按照约定是用self。

    子类方法和父类方法相同,子类方法会覆盖父类方法。

    继承用个例子说:

    定义一个商场类和一个人类,人类继承商场类。

    785ca77e48297d90c435ee39fbe68359.png

    实例化对象:

    0e574900e599432aaca3f936ddbd2f7f.png

    输出结果:

    5647b3555fec2562348f86b3df94c7b9.png

    分析例子:

    商场类中有两个方法分别是买和卖。人类中只有一个go()方法。人类继承商场类中两个方法。所以后面的实例化对象中都是人类People():

    xm.buy()是父类的商场类的方法,由于继承的关系,所以可以直接使用。

    xh.go()是人类People()中本身方法。

    xh.sell()是父类的商场类的方法,由于继承的关系,所以可以直接使用。

    基础的面向对象能够了解:定义类、构造函数、方法、属性、私有化、方法重写、继承这几点就算入门,其实还有很多高级的用法。再python深入学习中逐渐掌握。

    展开全文
  • 摘要:在Java中,一个对象在...在类初始化过程中初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Java对象...

    摘要:

    在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Java对象的创建过程。

    友情提示:

    一个Java对象的创建过程往往包括类初始化 和 类实例化 两个阶段。本文的姊妹篇《 JVM类加载机制概述:加载时机与加载过程》主要介绍了类的初始化时机和初始化过程,本文在此基础上,进一步阐述了一个Java对象创建的真实过程。

    一、Java对象创建时机

    我们知道,一个对象在可以被使用之前必须要被正确地实例化。在Java代码中,有很多行为可以引起对象的创建,最为直观的一种就是使用new关键字来调用一个类的构造函数显式地创建对象,这种方式在Java规范中被称为 : 由执行类实例创建表达式而引起的对象创建。除此之外,我们还可以使用反射机制(Class类的newInstance方法、使用Constructor类的newInstance方法)、使用Clone方法、使用反序列化等方式创建对象。下面笔者分别对此进行一一介绍:

    1). 使用new关键字创建对象

    这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们可以调用任意的构造函数(无参的和有参的)去创建对象。比如:

    Student student = new Student();

    2). 使用Class类的newInstance方法(反射机制)

    我们也可以通过Java的反射机制使用Class类的newInstance方法来创建对象,事实上,这个newInstance方法调用无参的构造器创建对象,比如:

    Student student2 = (Student)Class.forName("Student类全限定名").newInstance();

    或者:

    Student stu = Student.class.newInstance();

    3). 使用Constructor类的newInstance方法(反射机制)

    java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象,该方法和Class类中的newInstance方法很像,但是相比之下,Constructor类的newInstance方法更加强大些,我们可以通过这个newInstance方法调用有参数的和私有的构造函数,比如:

    public class Student {

    private int id;

    public Student(Integer id) {

    this.id = id;

    }

    public static void main(String[] args) throws Exception {

    Constructor constructor = Student.class

    .getConstructor(Integer.class);

    Student stu3 = constructor.newInstance(123);

    }

    }

    使用newInstance方法的这两种方式创建对象使用的就是Java的反射机制,事实上Class的newInstance方法内部调用的也是Constructor的newInstance方法。

    4). 使用Clone方法创建对象

    无论何时我们调用一个对象的clone方法,JVM都会帮我们创建一个新的、一样的对象,特别需要说明的是,用clone方法创建对象的过程中并不会调用任何构造函数。关于如何使用clone方法以及浅克隆/深克隆机制,笔者已经在博文《 Java String 综述(下篇)》做了详细的说明。简单而言,要想使用clone方法,我们就必须先实现Cloneable接口并实现其定义的clone方法,这也是原型模式的应用。比如:

    public class Student implements Cloneable{

    private int id;

    public Student(Integer id) {

    this.id = id;

    }

    @Override

    protected Object clone() throws CloneNotSupportedException {

    // TODO Auto-generated method stub

    return super.clone();

    }

    public static void main(String[] args) throws Exception {

    Constructor constructor = Student.class

    .getConstructor(Integer.class);

    Student stu3 = constructor.newInstance(123);

    Student stu4 = (Student) stu3.clone();

    }

    }

    5). 使用(反)序列化机制创建对象

    当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口,比如:

    public class Student implements Cloneable, Serializable {

    private int id;

    public Student(Integer id) {

    this.id = id;

    }

    @Override

    public String toString() {

    return "Student [id=" + id + "]";

    }

    public static void main(String[] args) throws Exception {

    Constructor constructor = Student.class

    .getConstructor(Integer.class);

    Student stu3 = constructor.newInstance(123);

    // 写对象

    ObjectOutputStream output = new ObjectOutputStream(

    new FileOutputStream("student.bin"));

    output.writeObject(stu3);

    output.close();

    // 读对象

    ObjectInputStream input = new ObjectInputStream(new FileInputStream(

    "student.bin"));

    Student stu5 = (Student) input.readObject();

    System.out.println(stu5);

    }

    }

    6). 完整实例

    public class Student implements Cloneable, Serializable {

    private int id;

    public Student() {

    }

    public Student(Integer id) {

    this.id = id;

    }

    @Override

    protected Object clone() throws CloneNotSupportedException {

    // TODO Auto-generated method stub

    return super.clone();

    }

    @Override

    public String toString() {

    return "Student [id=" + id + "]";

    }

    public static void main(String[] args) throws Exception {

    System.out.println("使用new关键字创建对象:");

    Student stu1 = new Student(123);

    System.out.println(stu1);

    System.out.println("\n---------------------------\n");

    System.out.println("使用Class类的newInstance方法创建对象:");

    Student stu2 = Student.class.newInstance(); //对应类必须具有无参构造方法,且只有这一种创建方式

    System.out.println(stu2);

    System.out.println("\n---------------------------\n");

    System.out.println("使用Constructor类的newInstance方法创建对象:");

    Constructor constructor = Student.class

    .getConstructor(Integer.class); // 调用有参构造方法

    Student stu3 = constructor.newInstance(123);

    System.out.println(stu3);

    System.out.println("\n---------------------------\n");

    System.out.println("使用Clone方法创建对象:");

    Student stu4 = (Student) stu3.clone();

    System.out.println(stu4);

    System.out.println("\n---------------------------\n");

    System.out.println("使用(反)序列化机制创建对象:");

    // 写对象

    ObjectOutputStream output = new ObjectOutputStream(

    new FileOutputStream("student.bin"));

    output.writeObject(stu4);

    output.close();

    // 读取对象

    ObjectInputStream input = new ObjectInputStream(new FileInputStream(

    "student.bin"));

    Student stu5 = (Student) input.readObject();

    System.out.println(stu5);

    }

    }/* Output:

    使用new关键字创建对象:

    Student [id=123]

    ---------------------------

    使用Class类的newInstance方法创建对象:

    Student [id=0]

    ---------------------------

    使用Constructor类的newInstance方法创建对象:

    Student [id=123]

    ---------------------------

    使用Clone方法创建对象:

    Student [id=123]

    ---------------------------

    使用(反)序列化机制创建对象:

    Student [id=123]

    *///:~

    从Java虚拟机层面看,除了使用new关键字创建对象的方式外,其他方式全部都是通过转变为invokevirtual指令直接创建对象的。

    二. Java 对象的创建过程

    当一个对象被创建时,虚拟机就会为其分配内存来存放对象自己的实例变量及其从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)。在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照程序猿的意志进行初始化。在Java对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是实例变量初始化、实例代码块初始化以及 构造函数初始化。

    1、实例变量初始化与实例代码块初始化

    我们在定义(声明)实例变量的同时,还可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。如果我们以这两种方式为实例变量进行初始化,那么它们将在构造函数执行之前完成这些初始化操作。实际上,如果我们对实例变量直接赋值或者使用实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后(还记得吗?Java要求构造函数的第一条语句必须是超类构造函数的调用语句),构造函数本身的代码之前。例如:

    public class InstanceVariableInitializer {

    private int i = 1;

    private int j = i + 1;

    public InstanceVariableInitializer(int var){

    System.out.println(i);

    System.out.println(j);

    this.i = var;

    System.out.println(i);

    System.out.println(j);

    }

    { // 实例代码块

    j += 3;

    }

    public static void main(String[] args) {

    new InstanceVariableInitializer(8);

    }

    }/* Output:

    1

    5

    8

    5

    *///:~

    上面的例子正好印证了上面的结论。特别需要注意的是,Java是按照编程顺序来执行实例变量初始化器和实例初始化器中的代码的,并且不允许顺序靠前的实例代码块初始化在其后面定义的实例变量,比如:

    public class InstanceInitializer {

    {

    j = i;

    }

    private int i = 1;

    private int j;

    }

    public class InstanceInitializer {

    private int j = i;

    private int i = 1;

    }

    上面的这些代码都是无法通过编译的,编译器会抱怨说我们使用了一个未经定义的变量。之所以要这么做是为了保证一个变量在被使用之前已经被正确地初始化。但是我们仍然有办法绕过这种检查,比如:

    public class InstanceInitializer {

    private int j = getI();

    private int i = 1;

    public InstanceInitializer() {

    i = 2;

    }

    private int getI() {

    return i;

    }

    public static void main(String[] args) {

    InstanceInitializer ii = new InstanceInitializer();

    System.out.println(ii.j);

    }

    }

    如果我们执行上面这段代码,那么会发现打印的结果是0。因此我们可以确信,变量j被赋予了i的默认值0,这一动作发生在实例变量i初始化之前和构造函数调用之前。

    2、构造函数初始化

    我们可以从上文知道,实例变量初始化与实例代码块初始化总是发生在构造函数初始化之前,那么我们下面着重看看构造函数初始化过程。众所周知,每一个Java中的对象都至少会有一个构造函数,如果我们没有显式定义构造函数,那么它将会有一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成()方法,参数列表与Java语言书写的构造函数的参数列表相同。

    我们知道,Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这一点是在构造函数中保证的:Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有对象构造函数的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用,比如:

    public class ConstructorExample {

    }

    对于上面代码中定义的类,我们观察编译之后的字节码,我们会发现编译器为我们生成一个构造函数,如下,

    aload_0

    invokespecial #8; //Method java/lang/Object."":()V

    return

    上面代码的第二行就是调用Object类的默认构造函数的指令。也就是说,如果我们显式调用超类的构造函数,那么该调用必须放在构造函数所有代码的最前面,也就是必须是构造函数的第一条指令。正因为如此,Java才可以使得一个对象在初始化之前其所有的超类都被初始化完成,并保证创建一个完整的对象出来。

    特别地,如果我们在一个构造函数中调用另外一个构造函数,如下所示,

    public class ConstructorExample {

    private int i;

    ConstructorExample() {

    this(1);

    ....

    }

    ConstructorExample(int i) {

    ....

    this.i = i;

    ....

    }

    }

    对于这种情况,Java只允许在ConstructorExample(int i)内调用超类的构造函数,也就是说,下面两种情形的代码编译是无法通过的:

    public class ConstructorExample {

    private int i;

    ConstructorExample() {

    super();

    this(1); // Error:Constructor call must be the first statement in a constructor

    ....

    }

    ConstructorExample(int i) {

    ....

    this.i = i;

    ....

    }

    }

    或者,

    public class ConstructorExample {

    private int i;

    ConstructorExample() {

    this(1);

    super(); //Error: Constructor call must be the first statement in a constructor

    ....

    }

    ConstructorExample(int i) {

    this.i = i;

    }

    }

    Java通过对构造函数作出这种限制以便保证一个类的实例能够在被使用之前正确地初始化。

    3、 小结

    总而言之,实例化一个类的对象的过程是一个典型的递归过程,如下图所示。进一步地说,在实例化一个类的对象时,具体过程是这样的:

    在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类。此时,首先实例化Object类,再依次对以下各类进行实例化,直到完成对目标类的实例化。具体而言,在实例化每个类时,都遵循如下顺序:先依次执行实例变量初始化和实例代码块初始化,再执行构造函数初始化。也就是说,编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。

    2730c5647c6845745b7e5b8695599c8a.png

    Ps: 关于递归的思想与内涵的介绍,请参见我的博文《 算法设计方法:递归的内涵与经典应用》。

    4、实例变量初始化、实例代码块初始化以及构造函数初始化综合实例

    笔者在《 JVM类加载机制概述:加载时机与加载过程》一文中详细阐述了类初始化时机和初始化过程,并在文章的最后留了一个悬念给各位,这里来揭开这个悬念。建议读者先看完《 JVM类加载机制概述:加载时机与加载过程》这篇再来看这个,印象会比较深刻,如若不然,也没什么关系~~

    //父类

    class Foo {

    int i = 1;

    Foo() {

    System.out.println(i); -----------(1)

    int x = getValue();

    System.out.println(x); -----------(2)

    }

    {

    i = 2;

    }

    protected int getValue() {

    return i;

    }

    }

    //子类

    class Bar extends Foo {

    int j = 1;

    Bar() {

    j = 2;

    }

    {

    j = 3;

    }

    @Override

    protected int getValue() {

    return j;

    }

    }

    public class ConstructorExample {

    public static void main(String... args) {

    Bar bar = new Bar();

    System.out.println(bar.getValue()); -----------(3)

    }

    }/* Output:

    2

    0

    2

    *///:~

    根据上文所述的类实例化过程,我们可以将Foo类的构造函数和Bar类的构造函数等价地分别变为如下形式:

    //Foo类构造函数的等价变换:

    Foo() {

    i = 1;

    i = 2;

    System.out.println(i);

    int x = getValue();

    System.out.println(x);

    }

    //Bar类构造函数的等价变换

    Bar() {

    Foo();

    j = 1;

    j = 3;

    j = 2

    }

    这样程序就好看多了,我们一眼就可以观察出程序的输出结果。在通过使用Bar类的构造方法new一个Bar类的实例时,首先会调用Foo类构造函数,因此(1)处输出是2,这从Foo类构造函数的等价变换中可以直接看出。(2)处输出是0,为什么呢?因为在执行Foo的构造函数的过程中,由于Bar重载了Foo中的getValue方法,所以根据Java的多态特性可以知道,其调用的getValue方法是被Bar重载的那个getValue方法。但由于这时Bar的构造函数还没有被执行,因此此时j的值还是默认值0,因此(2)处输出是0。最后,在执行(3)处的代码时,由于bar对象已经创建完成,所以此时再访问j的值时,就得到了其初始化后的值2,这一点可以从Bar类构造函数的等价变换中直接看出。

    三. 类的初始化时机与过程

    关于类的初始化时机,笔者在博文《 JVM类加载机制概述:加载时机与加载过程》已经介绍的很清楚了,此处不再赘述。简单地说,在类加载过程中,准备阶段是正式为类变量(static 成员变量)分配内存并设置类变量初始值(零值)的阶段,而初始化阶段是真正开始执行类中定义的java程序代码(字节码)并按程序猿的意图去初始化类变量的过程。更直接地说,初始化阶段就是执行类构造器()方法的过程。()方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块static{}中的语句合并产生的,其中编译器收集的顺序是由语句在源文件中出现的顺序所决定。

    类构造器()与实例构造器()不同,它不需要程序员进行显式调用,虚拟机会保证在子类类构造器()执行之前,父类的类构造()执行完毕。由于父类的构造器()先执行,也就意味着父类中定义的静态代码块/静态变量的初始化要优先于子类的静态代码块/静态变量的初始化执行。特别地,类构造器()对于类或者接口来说并不是必需的,如果一个类中没有静态代码块,也没有对类变量的赋值操作,那么编译器可以不为这个类生产类构造器()。此外,在同一个类加载器下,一个类只会被初始化一次,但是一个类可以任意地实例化对象。也就是说,在一个类的生命周期中,类构造器()最多会被虚拟机调用一次,而实例构造器()则会被虚拟机调用多次,只要程序员还在创建对象。

    注意,这里所谓的实例构造器()是指收集类中的所有实例变量的赋值动作、实例代码块和构造函数合并产生的,类似于上文对Foo类的构造函数和Bar类的构造函数做的等价变换。

    四. 总结

    1、一个实例变量在对象初始化的过程中会被赋值几次?

    我们知道,JVM在为一个对象分配完内存之后,会给每一个实例变量赋予默认值,这个时候实例变量被第一次赋值,这个赋值过程是没有办法避免的。如果我们在声明实例变量x的同时对其进行了赋值操作,那么这个时候,这个实例变量就被第二次赋值了。如果我们在实例代码块中,又对变量x做了初始化操作,那么这个时候,这个实例变量就被第三次赋值了。如果我们在构造函数中,也对变量x做了初始化操作,那么这个时候,变量x就被第四次赋值。也就是说,在Java的对象初始化过程中,一个实例变量最多可以被初始化4次。

    2、类的初始化过程与类的实例化过程的异同?

    类的初始化是指类加载过程中的初始化阶段对类变量按照程序猿的意图进行赋值的过程;而类的实例化是指在类完全加载到内存中后创建对象的过程。

    3、假如一个类还未加载到内存中,那么在创建一个该类的实例时,具体过程是怎样的?

    我们知道,要想创建一个类的实例,必须先将该类加载到内存并进行初始化,也就是说,类初始化操作是在类实例化操作之前进行的,但并不意味着:只有类初始化操作结束后才能进行类实例化操作。例如,笔者在博文《 JVM类加载机制概述:加载时机与加载过程》中所提到的下面这个经典案例:

    public class StaticTest {

    public static void main(String[] args) {

    staticFunction();

    }

    static StaticTest st = new StaticTest();

    static { //静态代码块

    System.out.println("1");

    }

    { // 实例代码块

    System.out.println("2");

    }

    StaticTest() { // 实例构造器

    System.out.println("3");

    System.out.println("a=" + a + ",b=" + b);

    }

    public static void staticFunction() { // 静态方法

    System.out.println("4");

    }

    int a = 110; // 实例变量

    static int b = 112; // 静态变量

    }/* Output:

    2

    3

    a=110,b=0

    1

    4

    *///:~

    大家能得到正确答案吗?笔者已经在博文《 JVM类加载机制概述:加载时机与加载过程》中解释过这个问题了,此不赘述。

    总的来说,类实例化的一般过程是:父类的类构造器() -> 子类的类构造器() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。

    五. 更多

    更多关于类初始化时机和初始化过程的介绍,请参见我的博文《 JVM类加载机制概述:加载时机与加载过程》。

    更多关于类加载器等方面的内容,包括JVM预定义的类加载器、双亲委派模型等知识点,请参见我的转载博文《深入理解Java类加载器(一):Java类加载原理解析》。

    关于递归的思想与内涵的介绍,请参见我的博文《 算法设计方法:递归的内涵与经典应用》。

    展开全文
  • Java自我学习路线一、实例化对象1. 直接通过反射实例化对象newInstance()2. 通过读属性文件实例化对象 一、实例化对象 使用反射机制创建对象比...创建一个类,通过类的 newInstance() 方法来实例化对象,JDK9之后 new
  • 深入理解Java对象创建过程:类的初始化与实例化

    万次阅读 多人点赞 2017-05-18 14:17:45
    在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。...本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Java对象创建过程。
  • 1) 通过构造器(有参无参)方式: 2) 通过静态工厂方法方式: 注: 工厂类实例没有创建3) 通过实例工厂方法(非静态方法)方式:注: 工厂类实例被创建方法实用示例1:需求:1 不想再bean.xml加载的时候实例化bean,...
  • 年龄必须小于50,性别只能是“男”“女” def showInfo(self):输出学生情况,包括性别、年龄、性别、专业 def study(self,stucou):学习。参数为学习的课程名,在方法中输出学习情况 def test(self,tcou):考试。...
  • spring内部创建对象的方式(bean实例化方式) 方式一:采用默认构造函数创建对象(重点)     方式二:采用静态工厂的方式 首先提供一个工厂类 public class HelloWorldFactory { /** * 静态方法 * ...
  • 一、为什么要实例化对象? 对象:属性+方法 类是指:描述一种事物的定义,是个抽象的概念 实例指:该种事物的一个具体的个体,是具体的东西 联系 类是现实世界思维世界中的实体在计算机中的反映,它将数据以及...
  • 我有一个作业,要求我写几个类,现在我终于用main方法编写了这个类 ...一个ArrayList,用于存储SavingsChecking类的10个对象 . 使用2%( . 02)储蓄账户费率 . 使用帐号100到109.使用1000到1000的初始余额一种名为b...
  • 1,实例化对象其实就是创建对象过程; Student st1 = new Student(); 2,为什么要实例化对象? 因为只有实例化之后,才能将这个对象放到内存中,然后才能在规定的范围内来调用。 二,方法重载 1,概念 (1),重载是方法名...
  • Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库既有项目整合。另一方面,当与现代的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动,比较适用于已了解关于 HTML、CSS ...
  • 作者:呆子Rico来源:...在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中初始化完毕后,根据具...
  • 1、使用构造函数初始时,可以不创建引用对象 构造函数在new创建对象时,如果对象未使用其它变量方法时,可以不创建引用对象 public class StaticDemo6 { public static void main(String[] args) { ...
  • 让我们以一个Foo类开始:class Foo(object):def __init__(self, x, y=0):self.x = xself.y = y当你实例化它(即创建该类的一个新的实例)时发生了什么?f = Foo(1, y=2)对Foo的调用到底调用了什么函数方法呢?大多数...
  • 就是一个模子,对象是根据类创建出来的。 一个形象的比喻: 类就是一个盖大楼的图纸,对象就是盖出来的大楼。 语法: [public] class 类名 { // 字段; // 属性; // 方法; } 如何在项目中创建一个类: 在...
  • 摘要:  在Java中,一个对象在可以...在类初始化过程中初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Ja...
  • 超级棒的博客~ ...在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中初始化完毕后,根据具体情...
  • Scala里可以使用new实例化对象或类实例。当你在Scala里实例化对象,可以使用值和类型把它参数化:parameterize。 参数化的意思是在你创建实例的时候“设置”它。通过把加在括号里的对象传递给实例的构造器的方式来...
  • (1)属性:name(str)、age(int)、male(性别...年龄必须小于50,性别只能是“男”“女” def showInfo(self):输出学生情况,包括性别、年龄、性别、专业 def study(self,stucou):学习。参数为学习的课程名,在方法...
  • 实例化对象过程及多态性解析 1:子类对象实例化的过程 * 1. 从结果上来说:体现为类的继承性 * 子类继承父类以后,当创建子类对象时,堆空间中除了会加载子类自己特有的属性之外,还会加载其所有的父类 * 中的属性...
  • 在Java中,一个对象在可以被使用之前...在类初始化过程中初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Ja...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,714
精华内容 1,885
关键字:

创建或实例化对象