A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

齐静

中级黑马

  • 黑马币:0

  • 帖子:13

  • 精华:0

© 齐静 中级黑马   /  2013-4-1 21:46  /  2220 人查看  /  10 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 齐静 于 2013-4-2 23:05 编辑
  1. class Meal{
  2.         Meal() {
  3.                 System.out.println("Meal()");
  4.         }
  5. }

  6. class Bread{
  7.         Bread(){
  8.                 System.out.println("Bread()");
  9.         }
  10. }

  11. class Cheese{
  12.         Cheese() {
  13.                 System.out.println("cheese()");
  14.         }
  15. }

  16. class Lettuce{
  17.         Lettuce() {
  18.                 System.out.println("Lettuce()");
  19.         }
  20. }

  21. class Lunch extends Meal{
  22.         public Lunch() {
  23.                 System.out.println("Lunch()");
  24.         }
  25. }

  26. class PortableLunch extends Lunch{
  27.         public PortableLunch() {
  28.                 System.out.println("portabkeLunch()");
  29.         }
  30. }

  31. public class SandWich extends PortableLunch {
  32.         private Bread m =new Bread();
  33.         private Cheese s = new Cheese();
  34.         private Lettuce l = new Lettuce();
  35.         
  36.         SandWich(){
  37.                 System.out.println("sandwich()");
  38.         }

  39.         public static void main(String[] args) {
  40.                 new SandWich();

  41.         }

  42. }
复制代码
输出结果:
Meal()
Lunch()
portabkeLunch()
Bread()
cheese()
Lettuce()
sandwich()
为什么Bread() cheese() Lettuce() 会比sandwich() 先执行?? 感谢~~

评分

参与人数 1技术分 +1 收起 理由
陈丽莉 + 1

查看全部评分

10 个回复

倒序浏览
你的这代码是子类不断继承父类,

子类定义了自己的方法,并且继承了父类的方法

先执行父类方法在执行子类方法

最后调用的方法最后执行

评分

参与人数 1技术分 +1 收起 理由
陈丽莉 + 1

查看全部评分

回复 使用道具 举报
本帖最后由 薛飞 于 2013-4-1 22:14 编辑

你好,楼主,你的问题走一下虚拟机的执行顺序即可解决。以下步骤是分析你产生疑问的地方:
对象new SandWich()在虚拟机中的初始化顺序:1,在堆内存中开辟空间。2,在堆内存中建立对象的特有属性并进行默认初始化,此处为Bread ,Cheese,Lettuce三个类引用变量,类变量的默认值为null。3,对类变量进行显示初始化,此时执行等号右边的new Bread()...,执行后打印Bread(),cheese(),Lettuce()。4,对对象进行对应的构造函数初始化,即此时打印sandwich()。
由以上分析可知Bread() cheese() Lettuce() 确实会比sandwich() 先执行。

评分

参与人数 1技术分 +1 收起 理由
陈丽莉 + 1

查看全部评分

回复 使用道具 举报
此段代码主要体现的是子父类继承时,

隐式的super()方法的执行;

在SandWich类中的main主函数中,执行new SandWich()语句前,

要去SandWich类中的父类PortableLunch,,,,,一直执行到meal类的无参构造函数

评分

参与人数 1技术分 +1 收起 理由
陈丽莉 + 1

查看全部评分

回复 使用道具 举报
也就是说 private Bread m =new Bread(); private Cheese s = new Cheese(); private Lettuce l = new Lettuce(); 是和定义成员变量类似。在堆中开辟空间后即对其进行初始化。 那这应该是成员对象?
回复 使用道具 举报
java编译器采用的是懒加载模式,以上代码步骤为
1,根据主函数要生andwich实例故欲加载Sandwich类,但要先加载PortableLunch
2,加载PortableLunch时先加载其父类Lunch(),同理先加载Meal()
故先有
Meal()
Lunch()
portabkeLunch()
3,还要有它的成员变量类型private Cheese s = new Cheese();
        private Lettuce l = new Lettuce();
根据这句代码可知,它要加载类Cheese和Lettuce

4等以上类加载成功,才可以形成SandWich类的实例

评分

参与人数 1技术分 +1 收起 理由
陈丽莉 + 1

查看全部评分

回复 使用道具 举报
运行class文件时内存加载顺序。
测试程序一:
class Father
        {
                
                Father(){}
                
                Father(int age)
                {
                        System.out.println("father age:"+age);
                }
        }
       
class Son extends Father
{
        static int i = 5;
        static {
                System.out.println("静态代码块"+i);
        }
        {
                System.out.println("构造代码块");
        }
       
        Son()
        {
                //默认这里有一个东西 super();
                //this(20);
                System.out.println("son");
        }

        Son(int age)
        {
                super(age);
                System.out.println("son age:"+age);
        }
}
public class Test1 {
        public static void main(String[] args) {
                Son s = new Son();  //father;son

    Son s1 = new Son(20);//father;son age:20
        }
       

}
测试程序二:
class Person {
        int i = 5;
        int j;
        int k;
        {
                System.out.println("这是构造代码块"+i+j+k);
        }
        Person(int j, int k) {
                System.out.println("i="+i);
                System.out.println("j="+j);
                System.out.println("ceshiyuju");
                System.out.println("k="+k);
                this.k = k;
                System.out.println("k="+k);
                this.j = j;
       
        }
}
public class Test1 {
        public static void main(String[] args) {
                Person p = new Person(10,20);
        }
}
        运行class文件时内存加载顺序。

类加载的过程:类的成员是按代码顺序加载的,静态代码块随着类的加载而被执行,也就是说一旦加载上独立代码块就立即执行。故静态代码块中的语句只能访问写在它前面的静态成员,若访问后面的会编译报错。类加载完了独立代码块也就执行完了。
整个类加载完毕完毕后,先从主函数开始执行。若其中的语句调用了别的类,则继续加载别的类。
这里重点介绍一下:主函数中有创建对象的语句,则对象的初始化过程如下:
以“Person p = new Person(10,20);”为例
这句话的一系列执行顺序:
0.  先在占内存中开辟一个空间给类类型变量P;
1.        因为new用到了Person.class,所以会先找到Person.class文件并加载到内存中。
2.        执行该类中的static 代码块(如果有的话),给Person.class进行初始化。
3.        在堆内存中开辟空间,分配内存地址。
4.        在堆内存中建立对象的特有属性。并进行默认初始化。
5.        对属性进行显示初始化。(即在 类 中定义变量时指定的初始化值,如程序二中的i)
6.        对对象进行构造代码块初始化。
7.        对对象进行对应的构造函数初始化。(即执行构造函数中的初始化语句对对象特有属性进行初始化,如下面程序中的j,k)
8.        执行构造函数中除赋值外的其他语句(如程序2中Person()方法内的打印语句);
9.        将内存地址赋给栈内存中的变量P。
10.        另外,第4步之前应先执行本构造方法中调用的父类构造方法。其执行方法与Person()构造方法相似。
弄清了这些过程,你就再也不用为所有执行顺序的问题而纠结了。

评分

参与人数 1技术分 +2 收起 理由
陈丽莉 + 2

查看全部评分

回复 使用道具 举报
          程序从这里开始执行  
  • public static void main(String[] args)
  • {
  •         new SandWich();  
  • }
  • 在new StandWich();之后执行下面的代码
  • public class SandWich extends PortableLunch
  • {
  •         private Bread m =new Bread();
  •         private Cheese s = new Cheese();
  •         private Lettuce l = new Lettuce();
  •         
  •         SandWich()
  •         {
  •                 System.out.println("sandwich()");
  •         }
  • }
  • 这段程序的内部执行顺序:
  • 由于SandWich有父类PortableLunch,会首先访问父类中的构造方法
  • 而父类又有父类故根据继承关系依次向上,最先加载Meal() Lunch() portabkeLunch()这三个类
  • 然后是上面的代码按顺序首先访问三个成员变量然后是构造函数,故依次输出
  • Bread()
    cheese()
    Lettuce()
    sandwich()

评分

参与人数 1技术分 +1 收起 理由
陈丽莉 + 1

查看全部评分

回复 使用道具 举报
本帖最后由 薛飞 于 2013-4-2 09:44 编辑
齐静 发表于 2013-4-1 22:30
也就是说 private Bread m =new Bread(); private Cheese s = new Cheese(); private Lettuce l = new Lett ...

private Bread m,private Cheese s,private Lettuce l 这些并不是对象,所以不能叫成员对象,它们还是变量,只是这些变量是引用类型中的类类型, new sandwich()对象的特有变量private Bread m 中存的是new Bread()在堆内存中开辟空间的地址。new Bread(),new Cheese(),new Lettuce()这些才是对象,才在堆内存中开辟空间。
回复 使用道具 举报
类的加载顺序
什么时候类加载
第一次需要使用类信息时加载。
类加载的原则:延迟加载,能不加载就不加载。

触发类加载的几种情况:
(1)、调用静态成员时,会加载静态成员真正所在的类及其父类。
通过子类调用父类的静态成员时,只会加载父类而不会加载子类。
(2)、第一次 new 对象的时候 加载(第二次再 new 同一个类时,不需再加载)。
(3)、加载子类会先加载父类。(覆盖父类方法时所抛出的异常不能超过父类定义的范围)
注:如果静态属性有 final 修饰时,则不会加载,当成常量使用。
例:public static final int a =123;
但是如果上面的等式右值改成表达式(且该表达式在编译时不能确定其值)时则会加载类。
例:public static final int a = math.PI
如果访问的是类的公开静态常量,那么如果编译器在编译的时候能确定这个常量的值,就不会被加载;
如果编译时不能确定其值的话,则运行时加载


类加载的顺序:
1.加载静态成员/代码块:
先递归地加载父类的静态成员/代码块(Object的最先);再依次加载到本类的静态成员。
同一个类里的静态成员/代码块,按写代码的顺序加载。
如果其间调用静态方法,则调用时会先运行静态方法,再继续加载。同一个类里调用静态方法时,可以不理会写代码的顺序。
调用父类的静态成员,可以像调用自己的一样;但调用其子类的静态成员,必须使用“子类名.成员名”来调用。
2.加载非静态成员/代码块:(实例块在创建对象时才会被加载。而静态成员在不创建对象时可以加载)
先递归地加载父类的非静态成员/代码块(Object的最先);再依次加载到本类的非静态成员。
同一个类里的非静态成员/代码块,按写代码的顺序加载。同一个类里调用方法时,可以不理会写代码的顺序。
但调用属性时,必须注意加载顺序。一般编译不通过,如果能在加载前调用,值为默认初始值(如:null 或者 0)。
调用父类的非静态成员(private 除外),也可以像调用自己的一样。
3.调用构造方法:
先递归地调用父类的构造方法(Object的最先)也就是上溯下行;默认调用父类空参的,也可在第一行写明调用父类某个带参的。
再依次到本类的构造方法;构造方法内,也可在第一行写明调用某个本类其它的构造方法。
注意:如果加载时遇到 override 的成员,可看作是所需创建的类型赋值给当前类型。
其调用按多态用法:只有非静态方法有多态;而静态方法、静态属性、非静态属性都没有多态。
假设子类override父类的所有成员,包括静态成员、非静态属性和非静态方法。
由于构造子类时会先构造父类;而构造父类时,其所用的静态成员和非静态属性是父类的,但非静态方法却是子类的;
由于构造父类时,子类并未加载;如果此时所调用的非静态方法里有成员,则这个成员是子类的,且非静态属性是默认初始值的。

评分

参与人数 1技术分 +2 收起 理由
陈丽莉 + 2

查看全部评分

回复 使用道具 举报
若还有问题,继续追问; 没有的话,将帖子分类改成【已解决】哦~
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马