zoukankan      html  css  js  c++  java
  • Java中对象构造

    构造函数

    作用:在构造对象的同时初始化对象。java强制要求对象 诞生同时被初始化,保证数据安全。

    调用过程和机制:①申请内存,②执行构造函数的函数体,③返回对象的引用。

    特点:与类同名,无返回类型,可以重载。

    每一个类都至少有1个构造函数,如果不显示定义构造函数,则javac会帮我们添加一个默认的无参数的构造函数。如果自己提供了构造函数,则javac不会再提供默认的无参构造函数。

    一个类中,对象构造时的构造过程

    顺序如下:

    1、字段获取相应类型的默认值(基本类型是 0 ,引用类型是null),定义时给了初始值的则获得给定的值。

    2、执行构造块中语句

    3、执行构造函数中的主体语句

    先来看一个类中,初始化时,各个构造方法的调用顺序

    下面是一个例子

    public class Main
    {
    
        public static void main(String[] args)
        {
    
            Base b = new Base();
            
            System.out.println("Base中baseVal的最终值为:"+b.baseVal);    

    /*
    baseVal=11
    baseVal=12
    Base中baseVal的最终值为:12
       */
    } }
    class Base { public int baseVal = 10; // { // ②构造块 baseVal = 11; System.out.println("baseVal=" + baseVal); } public Base() // { baseVal = 12; System.out.println("baseVal=" + baseVal); } }

    实质上,为了初始化实例字段,javac会对每一个构造函数进行"修改",原理如下图。所以,无论一个类中使用定义初始化,还是构造块,最终都是调用构造函数来起作用的。

    例子验证

    public class Test
    {
        
        private int x = 100;
        private int y;
        private int z;
        
        {y= 200;}
        
        public Test()
        {
            z = 300;    
        }
        
    }

    使用javap得到字节码的结果,可以很清楚的看到,字段的定义初始化,构造块初始化,其实都是语法特性,最终的内部实现都是依赖构造函数的执行。执行的顺序一目了然

    Compiled from "Test.java"
    public class Test {
      public Test();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: aload_0
           5: bipush        100
           7: putfield      #2                  // Field x:I
          10: aload_0
          11: sipush        200
          14: putfield      #3                  // Field y:I
          17: aload_0
          18: sipush        300
          21: putfield      #4                  // Field z:I
          24: return
    }

    一个继承链中,对象构造时的过程

    java中,子类的构造函数,如果不显式用super写出来的话,默认会在第一句调用父类的默认构造函数。可以通过super 显式指定调用父类的哪个构造函数。需要注意的是:这个super调用必须是子类构造函数的第一条语句。

    可以发现:构造一个子类,会先去调用父类的整套构造机制(注意:我并没有这样说:构造一个子类,会先去构造一个父类,原因待会解释),父类又会调用父类的父类的构造机制,.......一直带到Object为止。那么,这就会引发一条调用链,过程有点类似递归,只有高层的函数调用完了,底层的函数才会执行完。

                                

    public class Main
    {
    
        public static void main(String[] args)
        {
    
            SubClass sub = new SubClass();
            
            /*
             * console:
             *                  ①baseVal=11
                                ②baseVal=12
                                ③baseVal=12 sub Val=91
                                ④baseVal=12 sub Val=92
             */
            
        }
    
    }
    
    class Base
    {
    
        public int baseVal = 10;    
    
        {                           
            baseVal = 11;
            System.out.println("①baseVal=" + baseVal);
        }
    
        public Base()              
        {
            baseVal = 12;
            System.out.println("②baseVal=" + baseVal);
        }
    
    }
    
    class SubClass extends Base
    {
         public int subVal = 90;
    
        {
            subVal = 91;
            System.out.println("③baseVal="+baseVal+" sub Val=" + subVal);
            
        }
    
        public SubClass()
        {
            // super() //子类构造函数默认隐式调用父类的默认构造函数 ,当然你也可以显示调用
            subVal = 92;
            
            System.out.println("④baseVal="+baseVal+" sub Val=" + subVal);
        }
    
    }

    当然,子类还可以使用this来调用自己的另一个版本的构造函数完成构造,这个可以叫做委托构造。

    class Student
    {
        
        public static void main(String[] args)
        {
            
            Student s = new Student();
            
            System.out.println(s);
            
        }
        
        
        private double score ;
        
        private String name;
        
        
        //构造函数的第一句不是super(xx,xx...)就是this(xx,xx...)。super用来借用父类的构造函数来构造自己,这是java强制要求的,其它语言也多是这样。
        //this(xx,xx...)则是程序员自己复用本类构造函数代码的手段。
        //如果不显示的写super(xx,xx...),则编译javac会插入super()来调用父类默认的构造函数,如果父类没有默认的构造函数,则编译出错。
        //而this(xx,xx...)必须是程序员自己指定的,编译器不会插入。
        public Student(String name,double score )
        {
            //super()        第一句如果不是this(xx,xx),则默认就是super();因为这里Student无显示的父类,所以super()就是调用Object的构造函数。
            this.name = name;
            this.score = score;
        }
        
        public Student()
        {
            this("no name",0.0);   //无参数的构造函数委托有2个参数的构造函数完成。
        }
        
        @Override
        public String toString()
        {
            return String.format("name:%s , score:%.2f", name,score);
        }
    
    }

    子类的构造,会生成一个父类对象吗?

    这个问题的关键在于:是生成的一个内嵌的父类,还是生成一个独立的父类。

    显然,我认为是生成的是内嵌的父类,即子类包含父类的数据,父类并不是独立存在的。

    试想一下:如果new一个子类对象,会生成一个独立的父类,那么,对于深层次的继承链。比如10层,new一个最高层子类,岂不是会在内存生成 10个对象,显然是不可以的,不高效的。

    那么子类对象在构造时,为什么要调用父类地方构造 函数呢?那是因为,子类要借用父类的构造函数类构造自己。因为子类包含父类的数据。

    静态字段的构造

    javac在编译时,会自动每一个类提供一个"静态构造函数",将 静态字段的定义初始化 和 static初始化块 插入到这个"静态构造函数" 中去,先插入 静态字段的定义初始化,后插入 static初始化块。意味着  静态字段的定义初始化先于  static初始化块执行。

    当类第一次使用的时候(被JVM加载的时候),执行这个"静态构造函数",让static字段得到初始化。

    静态字段,无论是否使用了static初始化块,都是在类第一次被加载时,完成的初始化。

    例子:

    public class Test
    {
        
        private static  int x = 100;
        
        static { x = 200;}
        
        
    }

    字节代码:

    Compiled from "Test.java"
    public class Test {
      public Test();                       //<-----------javac提供的默认无参构造函数
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      static {};                           //<------------javac自动合成的 “静态构造函数”
        Code:
           0: bipush        100
           2: putstatic     #2                  // Field x:I
           5: sipush        200
           8: putstatic     #2                  // Field x:I
          11: return
    }

    细节总结

    1、每一个类至少有一个构造函数。如果不手动编写构造函数,javac会为这个类提供一个默认的,无参数的构造函数。如果手动编写了构造函数,则javac就不会提供默认的构造函数。

    2、如果想让一个类不能实例化对象,则让他仅有的默认构造函数私有(private),见Math类。

    3、java 中的对象都在堆中生成,而引用他们的引用变量则视情况而定。比如一个类对象中有一个String成员,变量名为name,那么name也在堆中。C++中,如果使用new,则对象生成在堆中,如果不用new,一般在栈中。

    4、类的成员变量如果不显式初始化,则赋值为默认值。但最好明确地个一个初始值。

    5、final成员变量必须在new对象时被初始化,包括定义final变量时给初始值,或者在初始化块中,或者在构造函数中。

    6、静态字段也会有默认值,也可以静态构造块中初始化。JVM第一次加载类时,这个过程就完成了。

    7、通过this来调用本类其他的构造函数,这个this也必须是构造函数中的第一句,所以,不能同时使用this个super调用构造函数。

    8、如果一个类不准在类外是例外对象,则应该定义仅有的1个默认私有无参构造函数:如 java.lang.System类。

     /** Don't let anyone instantiate this class */
        private System() {
        }
  • 相关阅读:
    vmodel.lazy详解
    import Vue from 'vue'如何解释?
    ubuntu安装deb文件包
    vue安装
    多线程和多进程的区别
    SLH(Location Sensitive Hash)学习
    hadoop学习笔记(一)简介
    [转]关于协同过滤的好文章
    Linear Regression练习
    regularized 线性回归练习
  • 原文地址:https://www.cnblogs.com/lulipro/p/5550639.html
Copyright © 2011-2022 走看看