当前位置:编程学习 > JAVA >>

JAVA构造方法执行的一个问题,思考许久未果,求大神给解释解释


public class Base {

    private String name = "base";

    public Base() {
            tellName();
            printName(name);
    }

    public void tellName() {
        System.out.println("Base tell name:" + name);
    }

    public void printName(String name) {

        System.out.println("Base print name:" + name);
    }


}

public class Derived extends Base {

    private String name = "Derived";

    public Derived() {
            tellName();
            printName(name);
    }

    public void tellName() {
        System.out.println("Derived tell name:" + name);
    }

    public void printName(String name) {

        System.out.println("Derived print name:" + name);
    }

    public static void main(String[] args) {
        Derived derived = new Derived();

    }



}



针对程序跑出的结果,思考许久,想了2种解释,一种是多态,一种是程序空间的解释,貌似稍微改一下程序就无法解释了。
还求大神给分析分析这个到底是如何执行的?

java 多态 --------------------编程问答-------------------- 为什么第一行的name输出为null,真是有点奇怪。 --------------------编程问答--------------------
引用 1 楼 u011540494 的回复:
为什么第一行的name输出为null,真是有点奇怪。

我一开始认为子类构造器的this引用会传递给父类构造器,进而引发多态,而根据执行顺序,在执行父类的构造器中得tellName时,因为多态,所以执行的是子类的tellName,而此时在执行父类构造器(即super())时,Base中得name已经初始化了,但是Derived中的name此时还没有初始化,因而是Null。

我这么理解有一个漏洞,就是假设父类的tellName方法是private的,那么就不存在多态了,但是如果按之前的假设this会传递给父类构造器,那么应该是执行子类的tellName,但是实际执行的却是父类的tellName,所以我就凌乱了,求大神解释 --------------------编程问答-------------------- 确实很奇怪~~ --------------------编程问答-------------------- 除 --------------------编程问答-------------------- http://hi.baidu.com/linyongboole/item/74c32815dbe5c10fd1d66d04  可以看看这篇文章,你就懂为什么会这样了! --------------------编程问答-------------------- 这个也很好理解。子类继承父类后,new 一个子类的实例,就会调用父类的构造方法,又因为你在子类中重写了父类的方法,所以会调用子类的方法。这时就是多态的形式了。但在调用父类构造方法里的方法时,第一个方法调用时,name字段还没有值,所以会出现null(这点会很让人迷惑),因为在调用这个无参方法时,会把这个name看作局部的变量,离方法最近的变量。这个变量就是光声明了,并没有赋值,所以为null。第二个方法调用时,指明了从外部传入参数,所以会name是有值的。就是你声明的字段name。一家之言,请多赐教。 --------------------编程问答-------------------- 构造函数的目的是为各字段初始化,所以除了这些操作之外的方法一般不要在构造函数中调用。如果把代码改成下面这样,你就会看出来了。
class Base {
 
    //private String name = "base";
int name = 2;
    public Base() {
printName(name);
            tellName();
            
    }
 
    public void tellName() {
        System.out.println("Base tell name:" + name);
    }
 
    public void printName(int name) {
 
        System.out.println("Base print name:" + name);
    }
 
 
}
 
public class Derived extends Base {
 
    //private String name = "Derived";
int name = 3;
    public Derived() {
            tellName();
            printName(name);
    }
 
    public void tellName() {
        System.out.println("Derived is name:" + name);
    }
 
    public void printName(int name) {
 
        System.out.println("Derived print name:" + name);
    }
 
    public static void main(String[] args) {
        Derived derived = new Derived();
 
    }
 
}
--------------------编程问答--------------------
引用 6 楼 wgx13145188 的回复:
这个也很好理解。子类继承父类后,new 一个子类的实例,就会调用父类的构造方法,又因为你在子类中重写了父类的方法,所以会调用子类的方法。这时就是多态的形式了。但在调用父类构造方法里的方法时,第一个方法调用时,name字段还没有值,所以会出现null(这点会很让人迷惑),因为在调用这个无参方法时,会把这个name看作局部的变量,离方法最近的变量。这个变量就是光声明了,并没有赋值,所以为null。第二个方法调用时,指明了从外部传入参数,所以会name是有值的。就是你声明的字段name。一家之言,请多赐教。


Hi,谢谢你的回答
针对你的回家,我有两个不明白的地方:
1.“又因为你在子类中重写了父类的方法,所以会调用子类的方法。这时就是多态的形式了。”这句话你能详细解释下原理吗?因为是多态,那么就是由调用tellName方法的对象在运行时决定到底调用父类还是子类的tellName方法,那么这个对象我认为是this引用。那么是否是在Derived构造器开始执行的时候,跳转到执行Base的构造器时,将this引用传递过去了?我觉得我这种理解是没有道理的,还请指教这里的多态到底是怎么一回事?
2.如果我把Base的tellName的public改为private,那么对于tellName方法的多态条件就不在成立,此时又该如何解释呢?

谢谢 --------------------编程问答-------------------- Derived是Base的子类,在new时,它就会调用父类的构造函数,所以也会调用其中的方法。而方法又被子类重写,所以会调用子类的同名方法。但字段是不可以重写的.也就说在父类的构造函数中调用tellName()方法时,会用两个name字段同时存在的,jvm也搞不明白也你是调用哪一个了,所以会出现在null。再一个String类型的字段是比较特殊的字段。因为String类型是不可变量,java在声明String类型的变量的时候,如果是个直接量(String  name="张三";这就是直接量,而String str = new String("张三");这是new 出来的.这两种声明的方式在内存是不一样的。所以在内存中使用的静态变量区来保存直接量以提高性能。如果一个静态变量区有一个变量时,则在次声明一个同名的变量时,就会把静态变量区中的String类型的变量直接给你,而不用再声明一个了。回到你的第2个问题上来,那就是你把public void tellName()方法修改成private void tellName()后,因这个方法是父类私有的,它不会被子类继承,所以它当中的变量会有一个明确的指向那就是父类的name的字段。 --------------------编程问答-------------------- 首先你给的代码 有点疑问、如果上面的两个类在同一个包那么你的public 只能有一个 还有你的main方法在 类的里面 有问题(根据你的代码我猜测应该放在 base这个类)
开始分析:程序首先调用父类的构造器
 public Base() {
            tellName();//这里tellName 在子类 Driver中被重写了所以执行的是子类的tellname和 printName 有疑问请在子类中加上@Override
            printName(name1);
    }
看子类 Drivered中的方法
@Override
    public void tellName() {/**由于没有传入参数name是父类的name没有赋值默认为null 所以第一次输出null 不信可以在父类构造器中传入参数name试试看看是不是输出Base 继续看printName**/
        System.out.println("Derived tell name:" + name);
    }
    @Override
    public void printName(String name) {/**这里传入了参数name所以默认就是
     父类的 name 所以 编译器会去找name的值 开始初始化 所以输出 Base
 
        System.out.println("Derived print name:" + name);
    }
下面就是 子类调用自己的构造器 我就不啰嗦了 

请多指正!

--------------------编程问答--------------------
引用 9 楼 wgx13145188 的回复:
Derived是Base的子类,在new时,它就会调用父类的构造函数,所以也会调用其中的方法。而方法又被子类重写,所以会调用子类的同名方法。但字段是不可以重写的.也就说在父类的构造函数中调用tellName()方法时,会用两个name字段同时存在的,jvm也搞不明白也你是调用哪一个了,所以会出现在null。再一个String类型的字段是比较特殊的字段。因为String类型是不可变量,java在声明String类型的变量的时候,如果是个直接量(String  name="张三";这就是直接量,而String str = new String("张三");这是new 出来的.这两种声明的方式在内存是不一样的。所以在内存中使用的静态变量区来保存直接量以提高性能。如果一个静态变量区有一个变量时,则在次声明一个同名的变量时,就会把静态变量区中的String类型的变量直接给你,而不用再声明一个了。回到你的第2个问题上来,那就是你把public void tellName()方法修改成private void tellName()后,因这个方法是父类私有的,它不会被子类继承,所以它当中的变量会有一个明确的指向那就是父类的name的字段。


貌似被你这么一说就更糊涂了,关于String那个,不是同名变量,而是同字符串吧?针对这句话“但字段是不可以重写的.也就说在父类的构造函数中调用tellName()方法时,会用两个name字段同时存在的,jvm也搞不明白也你是调用哪一个了,所以会出现在null。”我的理解略有不同,我觉得是动态分配导致tellName的实际执行是在子类的上下文中,因此输出的是子类上下文哄的name,而此时子类的name还没有被初始化,因而是默认值null。我的问题是这个动态分配是如何实现的,因为以前一直认为多态就是靠obj.function()中的obj来决定到底执行谁,所以这里联想到是不是有一个子类对象的引用在这里起作用,如果是,那么这个引用又是如何传到父类的构造器里的? --------------------编程问答-------------------- 以上只是个人理解、说错多多包涵!再提一点 在子类的 执行过程中是先初始化子类的变量(静态变量是加载在方法区的)然后在调用构造方法 --------------------编程问答--------------------
引用 10 楼 u010076059 的回复:
首先你给的代码 有点疑问、如果上面的两个类在同一个包那么你的public 只能有一个 还有你的main方法在 类的里面 有问题(根据你的代码我猜测应该放在 base这个类)
开始分析:程序首先调用父类的构造器
 public Base() {
            tellName();//这里tellName 在子类 Driver中被重写了所以执行的是子类的tellname和 printName 有疑问请在子类中加上@Override
            printName(name1);
    }
看子类 Drivered中的方法
@Override
    public void tellName() {/**由于没有传入参数name是父类的name没有赋值默认为null 所以第一次输出null 不信可以在父类构造器中传入参数name试试看看是不是输出Base 继续看printName**/
        System.out.println("Derived tell name:" + name);
    }
    @Override
    public void printName(String name) {/**这里传入了参数name所以默认就是
     父类的 name 所以 编译器会去找name的值 开始初始化 所以输出 Base
 
        System.out.println("Derived print name:" + name);
    }
下面就是 子类调用自己的构造器 我就不啰嗦了 

请多指正!


代码没有问题,这点请放心,我是自己跑过的,两个类,main方法位于Derived中不会有任何问题的。 --------------------编程问答-------------------- 除 --------------------编程问答--------------------
引用 12 楼 u010076059 的回复:
以上只是个人理解、说错多多包涵!再提一点 在子类的 执行过程中是先初始化子类的变量(静态变量是加载在方法区的)然后在调用构造方法


这个我知道的哈,其实我经过一晚上的思考,已经确定了是动态分配主导了这一结果,但是以往的经验让我认为这个动态分配是靠对象.方法()的方式在运行时确定的,那就需要一个对象在父类的构造器中起作用,不用质疑肯定是Derived,这点我也调试确认过了,但是我不清楚的是这个子类引用是如何进入到父类构造器中的? --------------------编程问答-------------------- 你最开始给的代码1楼 有两个public我看的是第一楼顶的 反正是那个意思!我把 class Derived extends Base {的 public去掉  main方法仿造base中 反正是一样 的 --------------------编程问答-------------------- 我都说了是 方法的重写 也就说 在 调用父类的构造器中父类的  tellName();printName(name);
两个方法已经被重写 父类构造器调用 这个两个方法的时候 就是调用的子类的 这两个方法,不知道我这样说 说明白没 --------------------编程问答-------------------- 首先是初始化Base类的name,然后Base的构造函数,调用的是Derived的tellName(),但是子类的name属性还没有被初始化,所以打印出null,接着调用Derived的printName(name)方法,传的是Base类的name,此时Base类的name已经初始化为"base"了,所以打印的是base。然后继续执行,初始化Derived的name属性,然后调用构造函数,结果可想而知了
             --------------------编程问答--------------------
引用 18 楼 u011512492 的回复:
首先是初始化Base类的name,然后Base的构造函数,调用的是Derived的tellName(),但是子类的name属性还没有被初始化,所以打印出null,接着调用Derived的printName(name)方法,传的是Base类的name,此时Base类的name已经初始化为"base"了,所以打印的是base。然后继续执行,初始化Derived的name属性,然后调用构造函数,结果可想而知了
            
正解 --------------------编程问答--------------------
引用 18 楼 u011512492 的回复:
首先是初始化Base类的name,然后Base的构造函数,调用的是Derived的tellName(),但是子类的name属性还没有被初始化,所以打印出null,接着调用Derived的printName(name)方法,传的是Base类的name,此时Base类的name已经初始化为"base"了,所以打印的是base。然后继续执行,初始化Derived的name属性,然后调用构造函数,结果可想而知了
            
同意 --------------------编程问答-------------------- 对, 上面说的对, 其实最关键的问题是在[重写], 其他的大家都懂
人家都说是重写了, 也就是说这个子类创建的对象里, 就没有父类那两个方法什么事了 --------------------编程问答-------------------- 除 --------------------编程问答-------------------- 应该是楼上说的这个顺序了。 --------------------编程问答-------------------- 这个主要是继承中重写的父类方法,程序的调用顺序问题,由于重写了父类的方法在初始化的时候先初始化父类,由于重写了父类的方法所以后直接调用子类的方法,此时子类中的 private String name = "Derived";稍为初始化所以第一个输出就为null了.如果子类中的private String name = "Derived"设置问static情况就不一样了.
这个简单的例子其实考的地方蛮多的.只要好好的理解继承,重写和static的使用就不难判断了. --------------------编程问答-------------------- 我可以给你讲明白,但是我懒得打字 --------------------编程问答-------------------- 18楼 +1 --------------------编程问答--------------------
引用 18 楼 u011512492 的回复:
首先是初始化Base类的name,然后Base的构造函数,调用的是Derived的tellName(),但是子类的name属性还没有被初始化,所以打印出null,接着调用Derived的printName(name)方法,传的是Base类的name,此时Base类的name已经初始化为"base"了,所以打印的是base。然后继续执行,初始化Derived的name属性,然后调用构造函数,结果可想而知了
            
正解 --------------------编程问答-------------------- 这个题的类似版本在李刚的疯狂java一书中看到过,这个和类的初始化顺序以及this有关,大概的理解就是在调用时调用的是子类的方法,而子类方法中的对应的属性还未来得及初始化,所以就是null,具体怎么说的忘记了。 --------------------编程问答-------------------- public class Base {
private String name = "base";
public Base() {
System.out.println("-6-");
tellName();
printName(name);
}
public void tellName() {
System.out.println("-4-");
System.out.println("Base tell name:" + name);
}
public void printName(String name) {
System.out.println("-5-");
System.out.println("Base print name:" + name);
}
}

public class Derived extends Base {
private String name = "Derived";
public Derived() {
System.out.println("-3-");
tellName();
printName(name);
}
public void tellName() {
System.out.println("-1-");
System.out.println("Derived tell name:" + name);
}
public void printName(String name) {
System.out.println("-2-");
System.out.println("Derived print name:" + name);
}
public static void main(String[] args) {
Derived derived = new Derived();
}
}
这样就可以看出来是走的顺序了。
结果是:
-6-
-1-
Derived tell name:null
-2-
Derived print name:base
-3-
-1-
Derived tell name:Derived
-2-
Derived print name:Derived

至于为什么,真的不能理解


--------------------编程问答-------------------- quote]这个也很好理解。子类继承父类后,new 一个子类的实例,就会调用父类的构造方法,又因为你在子类中重写了父类的方法,所以会调用子类的方法。
说得很好 --------------------编程问答--------------------
补充:Java ,  Java SE
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,