java构造器

  中午看Java编程思想,偶然间遇到一个关于构造器执行过程的问题。代码如下:

package reusing;

class A {
    public A(int a) {
        System.out.println("A()");
    }
}

class B {
    public B(int a) {
        System.out.println("B()");
    }
}

class C extends A {
    public C() {
        super(10);
        System.out.println("C()");
    }

    B b = new B(10);
}

public class E05_SimpleInheritance {
    public static void main(String args[]) {
        new C();
    }
} /* Output:
    A()
    B()
    C()
  */// :~

  B()为什么会出现在中间呢?问了百老师,好像略微明白了所以然:

  下面大部分笔记来自:http://tech.it168.com/j/2006-05-18/200605181021879.shtml

  在定义一个类的时候,如果没有定义构造器,则会编译器默认定义一个无参构造器,如果手动定义了有参构造器,则不会编译器不会自动添加无参构造器。

  子类构造器中总默认会调用父类构造器。但是,如果没有默认的父类构造器,或者想调用一个带参数的父类构造器,就必须用关键字super显式地调用父类构造器的语句。

  在多层继承关系上,构造器的构建过程是从父类“向外”扩散的,所以父类在导出类构造器可以访问它之前,就已经完成了初始化。

  首先要注意的是Java的构造器并不是函数,所以他并不能被继承,这在我们extends的时候写子类的构造器时比较的常见,即使子类构造器参数和父类的完全一样,我们也要写super就是因为这个原因。

  构造器的修饰符比较的有限,仅仅只有public private protected这三个,其他的例如任何修饰符都不能对其使用,也就是说构造器不允许被成名成抽象、同步、静态等等访问限制以外的形式。

  因为构造器不是函数,所以它是没有返回值的,也不允许有返回值。但是这里要说明一下,构造器中允许存在return语句,但是return什么都不返回,如果你指定了返回值,虽然编译器不会报出任何错误,但是JVM会认为他是一个与构造器同名的函数罢了,这样就会出现一些莫名其妙的无法找到构造器的错误,这里是要加倍注意的。

  在我们extends一个子类的时候经常会出现一些意想不到的问题,我在这里说一些和构造器有关的。

  

  首先说一下Java在构造实例时的顺序(不讨论装载类的过程)。

  构造的粗略过程如下:

  1、分配对象空间,并将对象中成员初始化为0或者空,java不允许用户操纵一个不定值的对象。

  2、执行属性值的显式初始化(这里有一点变化,一会解释,但大体是这样的)。

  3、执行构造器

  4、将变量关联到堆中的对象上

  this() super()是你如果想用传入当前构造器中的参数或者构造器中的数据调用其他构造器或者控制父类构造器时使用的,在一个构造器中你只能使用this()或者super()之中的一个,而且调用的位置只能在构造器的第一行,在子类中如果你希望调用父类的构造器来初始化父类的部分,那就用合适的参数来调用super(),如果你用没有参数的super()来调用父类的构造器(同时也没有使用this()来调用其他构造器),父类缺省的构造器会被调用,如果父类没有缺省的构造器,那编译器就会报一个错误,注意此处,我们经常在继承父类的时候构造器中并不写和父类有关的内容,此时如果父类没有缺省构造器,就会出现编译器添加的缺省构造器给你添麻烦的问题了哦。例如:Class b extends a{public b(){}}就没有任何有关父类构造器的信息,这时父类的缺省构造器就会被调用。

public class Demo1 {
    public static void main(String[] args) {
        B8 b1 = new B8();

    }
}

class A8{
    public A8(int a){
        System.out.println("A8 constructor");
    }
}

class B8 extends A8{
    public B8() {
        super(10);  //,由于A8没有默认构造器,如果这行注释了,编译器会报错
        System.out.println("B81");
    }
}

  你必须在构造器的第一行放置super或者this构造器,否则编译器会自动地放一个空参数的super构造器的,其他的构造器也可以调用super或者this,调用成一个递归构造链,最后的结果是父类的构造器(可能有多级父类构造器)始终在子类的构造器之前执行,递归的调用父类构造器。

 在具体构造类实例的过程中,上边过程的第二步和第三步是有一些变化的,这里的顺序是这样的,分配了对象空间及对象成员初始化为默认值之后,构造器就递归的从继承树由根部向下调用,每个构造器的执行过程是这样的:

  1、Bind构造器的参数。

  2、如果显式的调用了this,那就递归调用this构造器然后跳到步骤5。

  3、递归调用显式或者隐式的父类构造器,除了Object以外,因为它没有父类。

  4、执行显式的实例变量初始化(也就是上边的流程中的第二步,调用返回以后执行,这个步骤相当于在父构造器执行后隐含执行的,看样子像一个特殊处理)。

  5、执行构造器的其它部分。

  这里的步骤很重要哦!!!!!

  从这个步骤中可以很明显的发现这个实例初始化时的递归调用过程,估计看过这个你应该能够理解这个递归构造链是怎么样回事了。

  这里还是给出SL-275中的一个例子,让你充分理解一下这个递归的过程。

  

public class Object {
...
public Object() {}
...
}

public class Employee extends Object {
    private String name;
    private double salary = 15000.00;
    private Date birthDate;
    public Employee(String n, Date DoB) {
        // implicit super();
        name = n;
        birthDate = DoB;
    }
    public Employee(String n) {
        this(n, null);
    }
}

public class Manager extends Employee {
    private String department;
    public Manager(String n, String d) {
        super(n);
        department = d;
    }
}

在创建Manager("Joe Smith","Sales"):时,步骤如下:

0 basic initialization

0.1 allocate memory for the complete Manager object

0.2 initialize all instance variables to their default values (0 or null)

1 call constructor: Manager("Joe Smith", "Sales")

1.1 bind constructor parameters: n="Joe Smith", d="Sales"

1.2 no explicit this() call

1.3 call super(n) for Employee(String)

1.3.1 bind constructor parameters: n="Joe Smith"

1.3.2 call this(n, null) for Employee(String, Date)

1.3.2.1 bind constructor parameters: n="Joe Smith", DoB=null

1.3.2.2 no explicit this() call

1.3.2.3 call super() for Object()

1.3.2.3.1 no binding necessary

1.3.2.3.2 no this() call

1.3.2.3.3 no super() call (Object is the root)

1.3.2.3.4 no explicit variable initialization for Object

1.3.2.3.5 no method body to call

1.3.2.4 initialize explicit Employee variables: salary=15000.00;

注意:在父构造器返回后子类才会初始化实例变量的值。

1.3.2.5 execute body: name="Joe Smith"; date=null;

1.3.3 - 1.3.4 steps skipped

1.3.5 execute body: no body in Employee(String)

1.4 no explicit initializers for Manager

1.5 execute body: department="Sales"

这个流程就说明了一切,这个步骤是要注意的。一会还有些内容是要涉及到这里的。

写在后边的一些在使用构造器中的注意事项。

  一、构造器中一定不要创建自身的实例,否则会造成调用栈溢出错误。这个规则也适用于对象的实例变量,如果对象中有自身的引用,这个引用一定不能在定义中或者构造器中初始化。

  二、如果父类是一个抽象类,那通过调用父类的构造器,也可以将它初始化,并且初始化其中的数据。

  三、如果你要在构造器中调用一个方法时,将该方法声明为private。

  对于这个规则是需要一些说明的,假使你的父类构造器中要调用一个非静态方法,而这个方法不是private的又被子类所重载,这样在实际创建子类的过程中递归调用到了父类的构造器时,父类构造器对这个方法的调用就会由于多态而实际上调用了子类的方法,当这个子类方法需要用到子类中实例变量的时候,就会由于变量没有初始化而出现异常(至于为什么子类中的实例变量没有初始化可以参考上边的实例初始化过程),这是Java不想看到的情况。而当父类构造器中调用的方法是一个private方法时,多态就不会出现,也就不会出现父类构造器调用子类方法的情况,这样可以保证父类始终调用自己的方法,即使这个方法中调用了父类中的实例变量也不会出现变量未初始化的情况(变量初始化总是在当前类构造器主体执行之前进行)。

时间: 2024-07-30 03:22:20

java构造器的相关文章

Java——构造器

Java 构造器的由来:构造器是一个创建对象时被自动调用的特殊方法,为的是初始化. 当创建一个个对象时,系统会该对象的属性默认初始化,基本类型属性的值为0(数值类型),false(布尔类型),把所有的引用类型设置为null.构造器可以改变这种默认的初始化.构造器不是函数. 要求:构造器的名称应与类的名称一致.调用构造器是编译器的责任,所以必须让编译器知道调用的是哪一个方法.所以Java采取了与类同名的方法命名构造器. public class Person { public String nam

Java构造器内部的多态方法

本文主要详解java构造器内部的多态方法,更多Java技术知识,请登陆疯狂软件教育官网. 我们知道,动态绑定的调用是在运行时才决定的,对象无法知道到底调用的是哪个类的方法. 当我们在构造器中调用动态绑定的方法,就会用到该方法被覆盖之后的定义.但是这种调用的效果难以预计,因为被覆盖的方法在对象被完全构造之前就会被调用.我们先来看看下面这段代码: Java代码 class Base{ private String name = "base"; Base(){ tellName(); pri

Java学习之道:Java构造器和方法的区别

摘要 要学习Java,你必须理解构造器.因为构造器可以提供许多特殊的方法,这个对于初学者经常混淆.但是,构造器和方法又有很多重要的区别. 原作者:Robert Nielsen 原站:www.javaworld.com 我们说构造器是一种方法,就象讲澳大利亚的鸭嘴兽是一种哺育动物.(按:老外喜欢打比喻,我也就照着翻译).要理解鸭嘴兽,那么先必须理解它和其他哺育动物的区别.同样地,要理解构造器,那么就要了解构造器和方法的区别.所有学习java的人,尤其是对那些要认证考试的,理解构造器是非常重要的.下

Java构造器内部的多态方法的行为

Java构造器调用的层次结构带来了一个有趣的两难问题.如果在一个构造器的内部调用正在构造的对象的某个动态绑定方法,那会发生什么情况? class Glyph{ void draw(){System.out.println("Glyph.draw()");} Glyph() { System.out.print("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()

JAVA构造器,重载与重写

1. java构造器 构造器也叫构造方法(constructor), 用于对象初始化. 构造器是一个创建对象时被自动创建的特殊方法,目的是对象的初始化. 构造器 的名称与类的名称一致. JAVA通过new关键子来调用构造器,从而返回该类的实例,是一种特殊的方法. 备注要点: a. 通过new关键字来调用 b. 构造器虽然有返回值,但是不能定义返回值类型(返回值的类型是本类), 不能在构造器使用return返回某个值. c. 如果没有我们定义构造器,则编译器会自动定义一个无参构造器. 如果已定义构

Java构造器的理解

构造器的引入 在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化.创建对象时,如果其类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造器.从而保证了初始化的进行. 构造器的命名 所取得任何名字可能与类得某个成员名称相冲突 调用构造器是编译器得责任,所以必须让编译器知道应该调用哪个方法 Java构造器采用与类相同的名称 class Example{ public String var1; public String var2; // 默认构造器(无参构造器) p

java 构造器 (构造方法)

构造器 (constructor,构造方法) 3W what ? why? how? 是什么 , 为什么, 怎么用? 注意事项? What ? 是什么: 构造器(又叫构造方法)本身是一种特殊的方法,(但是和方法又没有什么大的关系,习惯上叫构造器,以免和方法混淆,)它只在新对象实例化的时候调用.Why ? 为什么用它: 为什么要用构造器 ,如果要实例化新的对象,需要使用关键字new 来完成,但是除了new这个关键字以外,还有可能在对象实例化时为其进行一些初始化的操作准备,这个时候需要构造方法的支持

java构造器内部多态方法

/** * Created by Administrator on 2017/3/7. */ /** * @author zhenghong * @date 2017-03-07-21:21 **/public class TestC { public static void main(String []args) { new Graph(5); }}class Grp{ void draw() { System.out.println("draw...."); } Grp() { d

JAVA构造器、this、super

构造器是为了创建一个类的实例.这个过程也可以在创建一个对象的时候用到: Platypus p1 = new Platypus(); 相反,方法的作用是为了执行java代码. 修饰符,返回值和命名的不同 构造器和方法在下面三个方便的区别:修饰符,返回值,命名.和方法一样, 构造器可以有任何访问的修饰: public, protected, private 或者没有修饰(通常被package 和friendly调用). 不同于方法的是,构造器不能有以下非访问性质的修饰: abstract, fina

有关java构造器的笔记

1. 不涉及继承和多态的构造器初始化过程 当程序中出现new A a() , 或者使用了A类的静态方法等代码时(声明一个A类对象不算) java虚拟机会首先加载A类, 然后执行A的静态初始化, 静态初始化会先将所有的静态成员变量进行默认初始化, 也就是基本数据类型初始化为0, 引用数据类型初始化为null. 然后按照静态变量和静态区的初始化顺序和声明顺序一致这一准则进行初始化. 此时执行的静态初始化只执行这一次, 执行之后就再也不执行了. 然后如果出现new A a() , 就会为对象分配存储空