浅谈Java继承中的转型及其内存分配

看书的时候被一段代码能凌乱啦,代码是这样的:

package 继承;
abstract class People
  {
    public String tag = "疯狂Java讲义";     //①
    public String name = "Parent";
    String getName(){
      return name;
    }
  }
  class Student extends People
  {
    //定义一个私有的tag实例变量来隐藏父类的tag实例变量
    String tag = "轻量级Java EE企业应用实战";     //②
    public String name = "Student";
  }
  public class HideTest2
  {
    public static void main(String[] args)
    {
      Student d = new Student();
      //将d变量显式地向上转型为Parent后,即可访问tag实例变量
      //程序将输出:“疯狂Java讲义”
      System.out.println(((People)d).tag);     //④
      System.out.println(d.getName()); //parent
    }
  }

运行结果:

疯狂Java讲义
Parent

在这个代码中,抽象父类People定义了两个变量和一个getName()方法,子类student也定义了两个和父类同名的变量,把父类的隐藏。

关于这段代码的两个困惑:1.子类实例化时必须首先实例化父类对象,而父类是抽象类,不能有对象。那到底子类实例化时产不产生父类对象???

2.d.getName();//返回的是parent,而不是student.不应该把父类的隐藏么??

书中是这么解释的:

Student对象会保存两份实例变量,一份是people中定义的实例变量,一份是Student中定义的实例变量,d变量引用一个Student对象,内存示意图如下:

将d向上转型为Parent对象,在通过它访问name变量是允许的,也就是输出“parent”。

但看着他的解释还是有点不明白,说的不是很清楚,又去网上搜了下:

java子类实例化时是否同时存在一个父类对象.

假如父类A中有个inta=1;

子类B继承A,同时B中覆盖个inta=2;

运行:

Atest=newB();

system.out.println(test.a);

结果是1,是父类中的属性.这个时候是否存在父类对象,我的理解是存在的.

我又试,把父类用抽象abstract修饰,按理说abstract累不能实例化吧,肯定不能得到父类中的a属性,结果还是一样的.

怎么理解.

问题补充:

是不是创建子类对象,肯定会出现一个父类的对象?

精彩回答

不会产生父类对象,只是用了父类的构造函数而已,并不是用到构造函数就会产生对象,构造函数只是起对象初始化作用的,而不是起产生对象作用的,如果newA();即只有new语句才会产生父类A的对象。

变量是静态绑定,方法是动态绑定。这里面变量在编译期间实现了变量调用语句与变量定义赋值语句的绑定,绑定的自然是父类的,因为调用时类型是父类的,所以值是父类中定义的值

其实你可以这么理解创建了一个子类对象时,在子类对象内存中,有两份这个变量,一份继承自父类,一份子类。

绝对不会产生父类对象,父类中的成员被继承到子类对象中,用指向子类对象的父类引用调用父类成员,只不过是从子类对象内存空间中找到那个被继承来的父类成员,也就是说实质是用子类对象调用变量a,这样就可以解释成员必须通过对象调用的规定,只不过这时调用的是子类对象中的继承自父类的a(子类对象中有两个a,一个继承自父类,一个属于自己)

哎,话说的有些乱。这个问题也困惑我很久,上网查询发现很多人是错误的,最后找到几篇好的文章才明白,可能很多java老手也都会犯“产生父类对象”这个错误,最近才搞明白。

你自己想想,如果产生父类对象,如果父类是抽象类,抽象类允许产生对象吗?所以这种说法不严谨

动态绑定定义

动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法

静态绑定与动态绑定

除了限制访问,访问方式也决定哪个方法将被子类调用或哪个属性将被子类访问.函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定.

在计算机语言中有两种主要的绑定方式,静态绑定和动态绑定.静态绑定发生于数据结构和数据结构间,程序执行之前.静态绑定发生于编译期,因此不能利用任何运行期的信息.

它针对函数调用与函数的主体,或变量与内存中的区块..动态绑定则针对运行期产生的访问请求,只用到运行期的可用信息.在面向对象的代码中,动态绑定意味着决定哪个方法被调用或哪个属性被访问,

将基于这个类本身而不基于访问范围.

子类在创建实例后,类初始化方法会调用父类的初始化方法(除了java.lang.Object类,因为java.lang.Object类没有父类),而这种调用会逐级追述,直到java.lang.Object的初始化方法。

这个地方我说的是初始化方法,而不是构造方法,因为构造方法是相对于java源程序而言,而编译后的class文件是初始化方法即"<init>"方法(红色部分为方法名),

初始化方法是由java源程序的三个部分组成的,一个部分是成员字段后的直接的初始化语句,例如privateinti=0;privateDatedate=newDate();等等,第二个部分是由初始化块组成,例如:

Javacode

publicclassTest{

privateinti=0;//初始化第一部分

//以下大括号内为初始化第二部分

{this.i=4;//dosomething......}}

第三个部分就是java源代码中的构造方法中的代码,java源代码中有几个构造方法,那么class文件中就有几个初始化方法,编译器会把第一部分与第二部分分别复制到每个初始化方法的前端,然后把初始化

方法对应参数的构造方法的代码复制到相应初始化方法中(这里说的复制其实应该说是编译,不过为了让你更好理解所以如此说).

那么说初始化方法如何追述其父类的,这也关系到初始化方法的结构,初始化方法的执行顺序以及结构就如上所说,但是每个初始化方法的第一个执行指令就是调用另外一个初始化方法,

这个初始化方法可能是自身类某个初始化方法,例如你的构造函数中第一句有类似this(...)这种语句,那么初始化方法就会调用自身类的指定构造方法;如果你的构造方法中没有指定构造方法调用,

那么初始化方法会默认调用父类无参数初始化方法,如果你的子类第一句为super(....),那么初始化方法会调用父类指定初始化方法。这种调用过程会递归进行调用,直到这个类是java.lang.Object类。

调用初始化方法并不代表会生成对象,你的java代码中出现new关键字加上构造方法的调用,只会生成一个对象,其父类对象不会生成,所以调用父类为抽象类的构造方法完全是合理的。

而且初始化方法对于虚拟机来说只是一个名称叫做"<init>"的普通方法,区别只是生成对象以后调用而已(sun的jdk私有包中有绕过构造方法生成对象的方式,可以证明之上说法,具体如何我这里不陈述)。

然后回答你的第二个问题,抽象类中的构造方法其实是用来给继承的子类来用的,因为构造方法相当于初始化方法,当子类调用构造方法时必须调用父类构造方法,

所以你可以在子类产生对象时抽象类中按需求初始化抽象类中的字段以及执行一些初始化代码。其实并不是一定要生成某个类的实例才调用构造方法,子类也需要调用父类构造方法。

而生成实例也并不一定会调用构造方法,在某些特殊实现中或者特殊情况下,生成实例不会调用构造方法。而调用了构造方法也不一定就生成了一个实例,但是那一定是一个实例调用的,就像一个普通的实例方法一样。

总结

以上就是本文关于浅谈Java继承中的转型及其内存分配的全部内容,希望对大家有所帮助,感兴趣的朋友可以继续参阅本站:Java编程之继承问题代码示例、Java面向对象编程(封装/继承/多态)实例解析等,有什么问题可以随时留言,小编会及时回复大家的。感谢朋友们对本站的支持!

(0)

相关推荐

  • java集合继承关系图分享

    面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式. 数组虽然也可以存储对象,但长度是固定的:集合长度是可变的,数组中可以存储基本数据类型,集合只能存储对象. 集合类的特点:集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象. 上述类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,A

  • Java集合类的组织结构和继承、实现关系详解

    Collection继承.实现关系如下(说明(I)表示接口, (C)表示Java类,<--表示继承,<<--表示实现): (I)Iterable |<-- (I)Collection |<-- (I)List |<<-- (C)ArrayList |<<-- (C)LinkedList |<<-- (C)Vector |<-- (I)Set |<<-- (C)HashSet |<-- (I)Queue [kju] M

  • Java编程之继承问题代码示例

    课堂练习: –在包bzu.aa中定义一个交通工具类(Vehicle): 1.属性--载客量(capacity) 2.方法 (1)无参构造方法(给capacity初始化值为2,并输出"执行交通工具类的无参构造方法.") (2)有参构造方法(传参给capacity初始化,并输出"执行交通工具的有参构造方法.") (3)capacity的set.get方法 (4)print方法:输出capacity –在包bzu.aa中定义一个汽车类(Car)继承交通工具类: 1.属性-

  • Java集合继承体系详解

    Java的集合类是一种特别有用的工具,它可以用于存储数量不等的多个对象,并可以实现常用的数据结构,如栈.队列等.Java集合还可以用于板寸具有映射关系的关联数组. java集合就像是一个容器,我们可以把多个对象(实际上是对象的引用,习惯上叫对象,)保存到集合容器中.在JDK1.5以前java集合会丢失容器中所有对象的数据类型,JDK1.5以后增加了泛型,java集合可以记住容器中对象的数据类型. Java集合大致可以分为三个体系:Set List Map.所有的集合类都位于java.util包下

  • Java面向对象编程(封装/继承/多态)实例解析

    本文主要介绍了面向对象的三大特征实例解析,下面看看具体内容. 封装 封装一个Teacher和Student类 package com.hz.test; public class Teacher { private String name; private String majorDirection; private String teachCourse; private int teachAge; public Teacher() { super(); } public Teacher(Stri

  • java异常继承何类,运行时异常与一般异常的区别(详解)

    一.基本概念 Throwable是所有异常的根,java.lang.Throwable Error是错误,java.lang.Error Exception是异常,java.lang.Exception Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类. Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题.大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java

  • java中继承测试代码分析

    继承:可以基于已经存在的类构造一个新类.继承已经存在的类就可以复用这些类的方法和域.在此基础上,可以添加新的方法和域,从而扩充了类的功能. public class ExtendsStu { /*动物类:动物都可以动 * 1.Dog 2.Cat * 在java中,子类可以继承父类的属性和功能; * 继承关系的指定: 子类 extends 父类 * 不能被继承的资源: * 1.子类不能继承父类的构造方法,而且必须调用一个父类的构造器(因为生成子类对象的时候会初始化父类属性) * 2.私有的资源不能

  • Java继承概念详细解读

    继承与合成基本概念 继承:可以基于已经存在的类构造一个新类.继承已经存在的类就可以复用这些类的方法和域.在此基础上,可以添加新的方法和域,从而扩充了类的功能. 合成:在新类里创建原有的对象称为合成.这种方式可以重复利用现有的代码而不更改它的形式. 1.继承的语法 关键字extends表明新类派生于一个已经存在的类.已存在的类称为父类或基类,新类称为子类或派生类.例如: class Student extends Person { } 类Student继承了Person,Person类称为父类或基

  • 浅谈Java继承中的转型及其内存分配

    看书的时候被一段代码能凌乱啦,代码是这样的: package 继承; abstract class People { public String tag = "疯狂Java讲义"; //① public String name = "Parent"; String getName(){ return name; } } class Student extends People { //定义一个私有的tag实例变量来隐藏父类的tag实例变量 String tag =

  • 浅谈java继承中是否创建父类对象

    1. 调用父类构造方法是真的,但是根本没有创建父类对象,只不过是调用父类构造方法来初始化属性. 如果说调用父类构造方法就等于创建父类对象,那就真的无稽之谈. new指令开辟空间,用于存放对象的各个属/性引用等,反编译字节码你会发现只有一个new指令,所以开辟的是一块空间,一块空间就放一个对象. 然后,子类调用父类的属性,方法啥的,那并不是一个实例化的对象. 在字节码中子类会有个u2类型的父类索引,属于CONSTANT_Class_info类型,通过CONSTANT_Class_info的描述可以

  • 浅谈Java编程中string的理解与运用

    一,"=="与equals() 运行以下代码,如何解释其输出结果? public class StringPool { public static void main(String args[]) { String s0="Hello"; String s1="Hello"; String s2="He"+"llo"; System.out.println(s0==s1);//true System.out

  • 浅谈java面向对象中四种权限

    俗话说没有规矩就没有方圆,java作为一门严谨的面向对象的高级编程语言,自然对权限整个重要的问题有严格的控制. Java中,可以通过一些Java关键字,来设置访问控制权限: 主要有 private(私有), package(包访问权限),protected(子类访问权限),public(公共访问权限) 在java里,这些语句都可以修饰类中的成员变量和方法,但是只有public和友好型可以修饰类.举个例子: 接下来就详细解释一下这几种权限的差别(博客最后有表格)按权限由低到高:(高权限有低权限所有

  • 浅谈Java 继承接口同名函数问题

    在Java中如果一个类同时继承接口A与B,并且这两个接口中具有同名方法,会怎么样? 动手做实验: interface A{ void fun(); } interface B{ void fun(); } interface C extends A,B{ } public class Test implements C{ @Override public void fun() { System.out.println("hehe"); } public static void main

  • 浅谈java Collection中的排序问题

    这里讨论list.set.map的排序,包括按照map的value进行排序. 1)list排序 list排序可以直接采用Collections的sort方法,也可以使用Arrays的sort方法,归根结底Collections就是调用Arrays的sort方法. public static <T> void sort(List<T> list, Comparator<? super T> c) { Object[] a = list.toArray(); Arrays.

  • 浅谈C++继承中的名字查找

    实例如下: #include<iostream> #include<string> using namespace std; class Base { public: void func() { cout << "func() in Base." << endl; } void func(int a) { cout << "func(int a) in Base." << endl; } voi

  • 浅谈java web中常用对象对应的实例化接口

    1. request对象 是javax.servlet.HttpServletRequest接口的实例化 2. response对象 是javax.servlet.HttpServletResponse接口的实例化 3. session 对象 是javax.servlet.HttpSession接口的实例化 4. application对象 是javax.servlet.ServletContext接口的实例化 以上是常用的对象 5. pageContext对象 是javax.servlet.j

  • 浅谈java线程中生产者与消费者的问题

    一.概念 生产者与消费者问题是一个金典的多线程协作的问题.生产者负责生产产品,并将产品存放到仓库:消费者从仓库中获取产品并消费.当仓库满时,生产者必须停止生产,直到仓库有位置存放产品:当仓库空时,消费者必须停止消费,直到仓库中有产品. 解决生产者/消费者问题主要用到如下几个技术:1.用线程模拟生产者,在run方法中不断地往仓库中存放产品.2.用线程模拟消费者,在run方法中不断地从仓库中获取产品.3  . 仓库类保存产品,当产品数量为0时,调用wait方法,使得当前消费者线程进入等待状态,当有新

  • 浅谈Java 类中各成分加载顺序和内存中的存放位置

    一.什么时候会加载类? 使用到类中的内容时加载:有三种情况 1.创建对象:new StaticCode(); 2.使用类中的静态成员:StaticCode.num=9;  StaticCode.show(); 3.在命令行中运行:java StaticCodeDemo 二.类所有内容加载顺序和内存中的存放位置 利用语句进行分析: 1.Person p=new Person("zhangsan",20); 该句话所做的事情: 1.在栈内存中,开辟main函数的空间,建立main函数的变量

随机推荐