• 热门专题

Java代码构造方法和执行顺序入门必看

作者:manxing  发布日期:2018-10-24 15:03:00
  • 在Java语言中,每个类至少有一个构造方法。为了保证这一点,每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。若定义了有参构造方法,编译器将不会提供默认的无参构造,如需要,需要自己构造。Java继承中对构造函数是不继承的,只是显式或者隐式调用。
    1、构造方法
    实例
    父类A:
1
2
3
4
5
6
7
8
9
10
11
public class A {
  
    public A(){
        System.out.println("A constructor");
    }
  
  
    public A(int a){
        System.out.println("A constructor ,num:"+a);
    }
}
子类B:
1
2
3
4
5
6
7
8
9
10
public class B extends A {
  
    public B (){
        System.out.println("B constructor");
    }
  
    public B(int b){
        System.out.println("B constructor ,num:"+b);
    }
}
测试类E:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class E {
    public static void main(String[] args) {
        System.out.println("E main");
        A a = new B();
        System.out.println("-------------AB可爱分割线-------------");
        B b = new B();
  
        System.out.println("\n---------小可爱分割线-----------\n");
  
        A a1 = new B(1);
        System.out.println("-------------AB可爱分割线-------------");
        B b1 = new B(2);
    }
}
测试结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
E main
A constructor
B constructor
-------------AB可爱分割线-------------
A constructor
B constructor
  
---------小可爱分割线-----------
  
A constructor
B constructor ,num:1
-------------AB可爱分割线-------------
A constructor
B constructor ,num:2
解析:
Java继承中对构造函数是不继承的,只是显式或者隐式调用,并且必须是在构造函数第一行。这里是隐式调用了super()。
子类不能继承父类的构造器(构造方法或者构造函数),但是父类的构造器带有参数的,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。
如果父类有无参构造器,则在子类的构造器中用super调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
所以先执行父类构造函数,再执行子类构造函数。
2、静态块、构造方法
静态代码块:在java中使用static关键字声明的代码块。
实例1:
父类A:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class A {
  
    static {
        System.out.println("A constructor before static");
    }
  
    public A(){
        System.out.println("A constructor");
    }
  
  
    public A(int a){
        System.out.println("A constructor ,num:"+a);
    }
  
    static {
        System.out.println("A constructor after static");
    }
}
子类B:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class B extends A {
    static {
        System.out.println("B constructor before static");
    }
    public B (){
        System.out.println("B constructor");
    }
  
    public B(int b){
        System.out.println("B constructor ,num:"+b);
    }
  
    static {
        System.out.println("B constructor after static");
    }
}
测试类E:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class E {
    public static void main(String[] args) {
        System.out.println("E main");
        A a = new B();
        System.out.println("-------------AB可爱分割线-------------");
        B b = new B();
  
        System.out.println("\n---------小可爱分割线-----------\n");
  
        A a1 = new B(1);
        System.out.println("-------------AB可爱分割线-------------");
        B b1 = new B(2);
    }
}
 
测试结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
E main
A constructor before static
A constructor after static
B constructor before static
B constructor after static
A constructor
B constructor
-------------AB可爱分割线-------------
A constructor
B constructor
  
---------小可爱分割线-----------
  
A constructor
B constructor ,num:1
-------------AB可爱分割线-------------
A constructor
B constructor ,num:2
解析:
静态块用于初始化类,为类的属性初始化。每个静态代码块只会执行一次。
由于JVM在加载类时会执行静态代码块,所以静态代码块先于主方法执行。
如果类中包含多个静态代码块,那么将按照"先定义的代码先执行,后定义的代码后执行"。
静态代码块不能存在于任何方法体内。
静态代码块不能直接访问静态实例变量和实例方法,需要通过类的实例对象来访问。
当父类与子类都有静态代码块和构造函数的时候,执行顺序如下:
 
父类静态代码块 > 子类静态代码块(Java虚拟机加载类时,就会执行该块代码)。
 
 
父类构造函数 > 子类构造函数 (先有父亲,后有孩子)
 
 
如果是多级继承关系的话,高层的父类首先执行,然后依次递减。
 
总结:静态优先执行,父类优先于子类执行。 静态代码块是在JVM加载类的时候执行的,而且静态代码块执行且仅执行一次
实例2:
子类包含静态属性---子类的实例化
父类A:同实例1
 
子类B:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class B extends A {
  
    public static A b = new B(3);
    public static A a = new A(2);
    public static B c = new B(1);
    static {
        System.out.println("B constructor before static");
    }
    public B (){
        System.out.println("B constructor");
    }
  
    public B(int b){
        System.out.println("B constructor ,num:"+b);
    }
  
    static {
        System.out.println("B constructor after static");
    }
}
测试类F:
 
1
2
3
4
5
6
public class F {
    public static void main(String[] args) {
        System.out.println("F main");
        A a = new B();
    }
}
 
测试结果:
1
2
3
4
5
6
7
8
9
10
11
12
F main
A constructor before static
A constructor after static
A constructor
B constructor ,num:3
A constructor ,num:2
A constructor
B constructor ,num:1
B constructor before static
B constructor after static
A constructor
B constructor
 
解析:
进入main函数,先执行
System.out.println("F main");
得到:      F main
A a = new B();
 
1
2
A constructor before static
A constructor after static
 
执行子类静态成员变量(方法块),由于子类包含静态属性和方法,按照“先定义的代码先执行,后定义的代码后执行”的规则,先执行静态属性部分,即:
1
2
3
 public static A b = new B(3);
 public static A a = new A(2);
 public static B c = new B(1);
此时由于父类静态部分已经执行,故执行相应的构造函数得到(感觉说是中断类加载过程,执行实例初始化过程更贴切些,这部分可以考虑参考看最后关于“暂停类加载”部分):
 
1
2
3
4
5
A constructor
B constructor ,num:3
A constructor ,num:2
A constructor
B constructor ,num:1
由于之后子类含有静态块,故继续执行静态块中的打印:
 
1
2
B constructor before static
B constructor after static
最后执行最终的无参构造函数:
 
1
2
A constructor
B constructor
若父类A,改成如下会更直观:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class A {
  
   public static A a = new B(12);
  
   static {
       System.out.println("A constructor before static");
   }
  
   public A(){
       System.out.println("A constructor");
   }
  
  
   public A(int a){
       System.out.println("A constructor ,num:"+a);
   }
  
   static {
       System.out.println("A constructor after static");
   }
}
此时结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
F main
A constructor
B constructor ,num:12
A constructor before static
A constructor after static
A constructor
B constructor ,num:3
A constructor ,num:2
A constructor
B constructor ,num:1
B constructor before static
B constructor after static
A constructor
B constructor
实例3:
子类和父类存在相同的静态方法和非静态方法时
父类A:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class A {
  
   static {
       System.out.println("A constructor before static");
   }
  
   public static void getClassStaticFunc(){
       System.out.println("get A static Function");
   }
  
   public  void getClassFunc(){
       System.out.println("get A  Function");
   }
  
   public A(){
       System.out.println("A constructor");
   }
  
  
   public A(int a){
       System.out.println("A constructor ,num:"+a);
   }
  
   static {
       System.out.println("A constructor after static");
   }
  
}
子类B:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class B extends A {
  
   static {
       System.out.println("B constructor before static");
   }
  
   public static void getClassStaticFunc(){
       System.out.println("get B static Function");
   }
  
   public  void getClassFunc(){
       System.out.println("get B  Function");
   }
  
   public B (){
       System.out.println("B constructor");
   }
  
   public B(int b){
       System.out.println("B constructor ,num:"+b);
   }
  
   static {
       System.out.println("B constructor after static");
   }
  
}
测试类F:
1
2
3
4
5
6
7
8
public class F {
   public static void main(String[] args) {
       System.out.println("F main");
       A a = new B();
       a.getClassStaticFunc();
       a.getClassFunc();
   }
}
测试结果:
1
2
3
4
5
6
7
8
9
F main
A constructor before static
A constructor after static
B constructor before static
B constructor after static
A constructor
B constructor
get A static Function
get B  Function
解析:
java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏。
原因:
1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
2). 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
3). 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
3、构造代码块、静态块、构造方法
构造块:直接在类中定义且没有加static关键字的代码块称为{}构造代码块。
构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数。
实例1
父类A:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class A {
  
   {
       System.out.println("A constructor before static before 构造块");
   }
   static {
       System.out.println("A constructor before static");
   }
  
   public A(){
       System.out.println("A constructor");
   }
  
  
   public A(int a){
       System.out.println("A constructor ,num:"+a);
   }
  
   static {
       System.out.println("A constructor after static");
   }
  
   {
       System.out.println("A constructor after static after 构造块");
   }
}
子类B:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class B extends A {
  
   {
       System.out.println("B constructor before static before 构造块");
   }
  
   static {
       System.out.println("B constructor before static");
   }
   public B (){
       System.out.println("B constructor");
   }
  
   public B(int b){
       System.out.println("B constructor ,num:"+b);
   }
  
   static {
       System.out.println("B constructor after static");
   }
  
   {
       System.out.println("B constructor after static after 构造块");
   }
}
测试类E:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class E {
    public static void main(String[] args) {
        System.out.println("E main");
        A a = new B();
        System.out.println("-------------AB可爱分割线-------------");
        B b = new B();
  
        System.out.println("\n---------小可爱分割线-----------\n");
  
        A a1 = new B(1);
        System.out.println("-------------AB可爱分割线-------------");
        B b1 = new B(2);
    }
}
测试结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
E main
A constructor before static
A constructor after static
B constructor before static
B constructor after static
A constructor before static before 构造块
A constructor after static after 构造块
A constructor
B constructor before static before 构造块
B constructor after static after 构造块
B constructor
-------------AB可爱分割线-------------
A constructor before static before 构造块
A constructor after static after 构造块
A constructor
B constructor before static before 构造块
B constructor after static after 构造块
B constructor
  
---------小可爱分割线-----------
  
A constructor before static before 构造块
A constructor after static after 构造块
A constructor
B constructor before static before 构造块
B constructor after static after 构造块
B constructor ,num:1
-------------AB可爱分割线-------------
A constructor before static before 构造块
A constructor after static after 构造块
A constructor
B constructor before static before 构造块
B constructor after static after 构造块
B constructor ,num:2
解析:
先静态后构造块,先父类后子类。
实例2:
对于非继承类
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class J {
    public static int k = 0;
    static {
        print("静态块2");
    }
    public static J t1 = new J("t1");
    public static J t2 = new J("t2");
    public static int i = print("i");
    public static int n = 99;
    public int j = print("j");
  
    {
        print("构造块");
    }
    static {
        print("静态块");
    }
  
    public J(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "    n=" + n);
        ++i;
        ++n;
    }
  
    public static int print(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "    n=" + n);
        ++n;
        return ++i;
    }
  
    public static void main(String args[]) {
        J t = new J("init");
    }
}
测试结果:
1
2
3
4
5
6
7
8
9
10
11
12
1:静态块2   i=0    n=0
2:j   i=1    n=1
3:构造块   i=2    n=2
4:t1   i=3    n=3
5:j   i=4    n=4
6:构造块   i=5    n=5
7:t2   i=6    n=6
8:i   i=7    n=7
9:静态块   i=8    n=99
10:j   i=9    n=100
11:构造块   i=10    n=101
12:init   i=11    n=102
解析:
典型的暂停类加载。即:类加载过程中,可能调用了实例化过程(因为static可以修饰方法,属性,代码块,内部类),此时则会暂停类加载过程而先执行实例化过程(被打断),执行结束再进行类加载过程。
类加载过程,不涉及构造方法。
实例化过程,涉及构造方法。
先类加载,执行static修饰的部分,遇到属性实例化,中断去执行实例化,完成后继续类加载,如此循环直至结束。
小结
无继承的的初始化顺序
静态成员变量(静态代码块)→普通成员变量→构造器
有继承的初始化顺序
父类静态成员变量、静态代码块→子类静态成员变量、静态代码块→父类普通成员变量、普通代码块→父类构造器→子类普通成员变量、普通代码块→子类构造器。
其他
类加载过程,不涉及构造方法
实例化过程,涉及构造方法
1、类中所有属性的默认值(一举而成)
2、父类静态属性初始化,静态块,静态方法的声明(按出现顺序执行)
3、子类静态属性初始化,静态块,静态方法的声明 (按出现顺序执行)
4 、调用父类的构造方法,
首先父类的非静态成员初始化,构造块,普通方法的声明(按出现顺序执行)
然后父类构造方法
5、 调用子类的构造方法,
首先子类的非静态成员初始化,构造块,普通方法的声明(按出现顺序执行)
然后子类构造方法
在创建子类的对象时,Java虚拟机首先执行父类的构造方法,然后再执行子类的构造方法。在多级继承的情况下,将从继承树的最上层的父类开始,依次执行各个类的构造方法,这可以保证子类对象从所有直接或间接父类中继承的实例变量都被正确的初始化。例如以下Base父类和Sub子类分别有一个实例变量 a和b,当构造Sub实例时,这两个实例变量都会被初始化。
public class Base{
private int a;
public Base(int a){ this.a=a;}
public int getA(){return a;}
public class Sub extends Base{
private int b;
public Base(int a,int b){super(a); this.b=b;}
public int getB(){return b;}
public static void main(String args[]){
Sub sub=new Sub(1,2);
System.out.println("a="+sub.getA()+" b="+sub.getB()); //打印a=1 b=2
在以下例程(Son.java)中,Son类继承Father类,Father类继承Grandpa类。这三个类都显式定义了默认的构造方法,此外还定义了一个带参数的构造方法。
例程Son.java
class Grandpa{
protected Grandpa(){
System.out.println("default Grandpa");
public Grandpa(String name){
System.out.println(name);
class Father extends Grandpa{
protected Father(){
System.out.println("default Father");
public Father(String grandpaName,String fatherName){
super(grandpaName);
System.out.println(fatherName);
public class Son extends Father{
public Son(){
System.out.println("default Son");
public Son(String grandpaName,String fatherName,String sonName){
super(grandpaName,fatherName);
System.out.println(sonName);
public static void main(String args[]){
Son s1= new Son("My Grandpa", "My Father", "My Son"); //①
Son s2=new Son(); //②
执行以上main()方法的第①条语句,打印结果如下:
My Grandpa
My Father
My Son
当子类的构造方法没有用super语句显式调用父类的构造方法,那么通过这个构造方法创建子类对象时,Java虚拟机会自动先调用父类的默认构造方法。执行以上Son类的main()方法的第②条语句,打印结果如下:
default Grandpa
default Father
default Son
当子类的构造方法没有用super语句显式调用父类的构造方法,而父类又没有提供默认构造方法时,将会出现编译错误。例如把例程Son.java做适当修改,删除Grandpa类中显式定义的默认构造方法:
// protected Grandpa(){
// System.out.println("default GrandPa");
// }
这样,Grandpa类就失去了默认构造方法,这时,在编译Father类的默认构造方法时,因为找不到Grandpa类的默认构造方法而编译出错。如果把Grandpa类的默认构造方法的protected访问级别改为private访问级别,也会导致编译错误,因为Father类的默认构造方法无法访问Grandpa类的私有默认构造方法。
在以下例子中,子类Sub的默认构造方法没有通过super语句调用父类的构造方法,而是通过this语句调用了自身的另一个构造方法Sub(int i),而在Sub(int i)中通过super语句调用了父类Base的Base(int i)构造方法。这样,无论通过Sub类的哪个构造方法来创建Sub实例,都会先调用父类Base的Base(int i)构造方法。
class Base{
Base(int i){System.out.println("call Base(int i)");}
public class Sub extends Base{
Sub(){this(0); System.out.println("call Sub()");}
Sub(int i){super(i); System.out.println("call Sub(int i)");}
public static void main(String args[]){
Sub sub=new Sub();
执行以上Sub类的main()方法的new Sub()语句,打印结果如下:
call Base(int i)
call Sub(int i)
call Sub()
在下面的例子中,Base类中没有定义任何构造方法,它实际上有一个隐含的默认构造方法:
Base(){}
Sub类的Sub(int i)构造方法没有用super语句显式调用父类的构造方法,因此当创建Sub实例时,会先调用Base父类的隐含默认构造方法。
class Base{} //具有隐含默认构造方法
public class Sub extends Base{
Sub(int i){System.out.println(i);}
public static void main(String args[]){
System.out.println(new Sub(1)); //打印1
构造方法的作用域
构造方法只能通过以下方式被调用:
(1) 当前类的其他构造方法通过this语句调用它。
(2) 当前类的子类的构造方法通过super语句调用它。
(3)在程序中通过new语句调用它。
对于例程(Sub.java)的代码,请读者自己分析某些语句编译出错的原因。
例程 Sub.java
class Base{
public Base(int i,int j){}
public Base(int i){
this(i,0); //合法
Base(i,0); //编译出错
class Sub extends Base{
public Sub(int i,int j){
super(i,0); //合法
void method1(int i,int j){
this(i,j); //编译出错
Sub(i,j); //编译出错
void method2(int i,int j){
super(i,j); //编译出错
void method3(int i,int j){
Base s=new Base(0,0); //合法
s.Base(0,0); //编译出错
构造方法的访问级别
构造方法可以处于public、protected、默认和private这四种访问级别之一。本节着重介绍构造方法处于private级别的意义。
当构造方法为private级别,意味着只能在当前类中访问它:在当前类的其他构造方法中可以通过this语句调用它,此外还可以在当前类的成员方法中通过new语句调用它。
在以下场合之一,可以把类的所有构造方法都声明为private类型。
(1)在这个类中仅仅包含了一些供其他程序调用的静态方法,没有任何实例方法。其他程序无需创建该类的实例,就能访问类的静态方法。例如 java.lang.Math类就符合这种情况,在Math类中提供了一系列用于数学运算的公共静态方法,为了禁止外部程序创建Math类的实例, Math类的惟一的构造方法是private类型的:
private Math(){}
abstract类型的类也不允许实例化。也许你会问,把Math类定义为如下abstract类型,不是也能禁止Math类被实例化吗?
public abstract class Math{…}
如果一个类是抽象类,意味着它是专门用于被继承的类,可以拥有子类,而且可以创建具体子类的实例。而JDK并不希望用户创建Math类的子类,在这种情况下,把类的构造方法定义为private类型更合适。
(2)禁止这个类被继承。当一个类的所有构造方法都是private类型,假如定义了它的子类,那么子类的构造方法无法调用父类的任何构造方法,因此会导致编译错误。把一个类声明为final类型,也能禁止这个类被继承。这两者的区别是:
1)如果一个类允许其他程序用new语句构造它的实例,但不允许拥有子类,那就把类声明为final类型。
2)如果一个类既不允许其他程序用new语句构造它的实例,又不允许拥有子类,那就把类的所有构造方法声明为private类型。
由于大多数类都允许其他程序用new语句构造它的实例,因此用final修饰符来禁止类被继承的做法更常见。
(3)这个类需要把构造自身实例的细节封装起来,不允许其他程序通过new语句创建这个类的实例,这个类向其他程序提供了获得自身实例的静态方法,这种方法称为静态工厂方法。
 
Java代码构造当一个类需要多个带有相同签名的构造函数时,可以用静态工厂方法代替构造函数,通过慎重地选择名称可以突出它们之间的区别。切忌第一反应就是提供公有的构造函数,而不优先考虑静态工厂。

延伸阅读:

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规