浅谈Java继承中的转型及其内存分配
作者:超超boy 发布时间:2023-11-03 22:56:04
看书的时候被一段代码能凌乱啦,代码是这样的:
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私有包中有绕过构造方法生成对象的方式,可以证明之上说法,具体如何我这里不陈述)。
然后回答你的第二个问题,抽象类中的构造方法其实是用来给继承的子类来用的,因为构造方法相当于初始化方法,当子类调用构造方法时必须调用父类构造方法,
所以你可以在子类产生对象时抽象类中按需求初始化抽象类中的字段以及执行一些初始化代码。其实并不是一定要生成某个类的实例才调用构造方法,子类也需要调用父类构造方法。
而生成实例也并不一定会调用构造方法,在某些特殊实现中或者特殊情况下,生成实例不会调用构造方法。而调用了构造方法也不一定就生成了一个实例,但是那一定是一个实例调用的,就像一个普通的实例方法一样。
来源:http://www.cnblogs.com/jycboy/p/5373690.html


猜你喜欢
- java有四种访问控制修饰符。publicprotectedprivatedefault(默认为空,不加任何修饰)为了用代码对比出其中的区别
- Collectors.toMap空指针问题在工作中遇到了一个List转Map的时候的一个NullPointException.情形很简单,问
- 1. 前言对于写Crud的老司机来说时间非常宝贵,一些样板代码写不但费时费力,而且枯燥无味。经常有小伙伴问我,胖哥你怎么天天那么有时间去搞新
- 前言在数据库连接池分析的代码实例中,看到其中使用Enumeration来遍历Vector集合。后来就找了一些资料查看都有哪些方法可以遍历集合
- 在Excel中如果能够将具有多级明细的数据进行分组显示,可以清晰地展示数据表格的整体结构,使整个文档具有一定层次感。根据需要设置显示或者隐藏
- 权限上篇文章 Android 获取IP和UA中提及了获取WIFI的IP地址,本篇文章介绍下如何扫描WIFI。官方文档根据官方文档描述,扫描W
- 1.封装分页Page类package com.framework.common.page.impl;import java.io.Seria
- 本文实例讲述了Android EditText自定义样式的方法。分享给大家供大家参考,具体如下:1.去掉边框EditText的backgro
- 1, * 的概念java里的 * 是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行
- 最近一直都在学习Java,发现目前Java招聘中,mybatis出现的频率挺高的,可能是目前Java开发中使用比较多的数据库ORM框架。于是
- 目录1 CyclicBarrier方法说明2 CyclicBarrier实例3 CyclicBarrier源码解析CyclicBarrier
- 首先,确保 IDEA 软件正确安装完成,Java 开发工具包 JDK 安装完成。IDEA 的 Java 项目 (Project) 则相当于
- 简介对于一个APP来说,肯定会有一个AppBar,这个AppBar一般包含了APP的导航信息等。虽然我们可以用一个固定的组件来做为AppBa
- 本文实例讲述了C#简单创建和删除目录的方法。分享给大家供大家参考。具体如下:using System;using System.IO;cla
- C#学习笔记- 浅谈数组复制,排序,取段,元组using System;using System.Collections.Generic;n
- forword跳转页面的三种方式:1.使用serlvet/** * 使用forward跳转,传递基本类型参数到页面  
- Android仿微信activity滑动关闭功能1.利用具体利用v4包下的slidingPaneLayout实现透明的activity,代码
- 一.前言在日常的开发中,经常需要对方法参数进行校验(非空、长度等)。如果采用hardcode去校验(if..else..),会造成代码冗余,
- 任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的th
- Java的static关键字和C/C++语言的关键字有所不同:一旦在Java里使用了static关键字,那么这样的内容不再属于对象自己,而是