Java关于继承中的转型及其内存分配

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

    2011-10-14 19:53
提问者: luoyuehao89  |  浏览次数:602次
java 子类实例化时是否同时存在一个父类对象.
假如父类A中有个int a = 1;
子类B继承A,同时B中覆盖个int a = 2;

运行:
A test = new B();
system.out.println(test.a);

结果是1,是父类中的属性.这个时候是否存在父类对象,我的理解是存在的.
我又试,把父类用抽象abstract修饰,按理说abstract累不能实例化吧,肯定不能得到父类中的a属性,结果还是一样的.
怎么理解.

问题补充:

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

我来帮他解答
检举  | 2011-10-22 16:33
精彩回答
不会产生父类对象,只是用了父类的构造函数而已,并不是用到构造函数就会产生对象,构造函数只是起对象初始化作用的,而不是起产生对象作用的,如果new A();即只有new语句才会产生父类A的对象。


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

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


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

你自己想想,如果产生父类对象,如果父类是抽象类,抽象类允许产生对象吗?所以这种说法不严谨
 
   
 
   
 
   
 
   
 
   
 
   
    
package jc.cn;
public class Car
{
   public int a;
   public int b;
}
class Truck extends Car
{
   byte c;
   byte d;
}
class Smp
{
   public static void main(String[] args)
   {
      Truck t=new Truck();
   }
}

当执行完下句后:
Truck t=new Truck();

求对象的内存模型图?
我来帮他解答
检举  | 2009-11-17 07:55
满意回答

左边是栈空间,右边是堆空间,栈空间的变量t储存着一个指向堆空间某个对象的地址

动态绑定定义

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

编辑本段静态绑定与动态绑定

  除了限制访问,访问方式也决定哪个方法将被子类调用或哪个属性将被子类访问. 函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 在计算机语言中有两种主要的绑定方式,静态绑定和动态绑定. 静态绑定发生于数据结构和数据结构间,程序执行之前. 静态绑定发生于编译期, 因此不能利用任何运行期的信息. 它针对函数调用与函数的主体,或变量与内存中的区块.. 动态绑定则针对运行期产生的访问请求,只用到运行期的可用信息. 在面向对象的代码中,动态绑定意味着决定哪个方法被调用或哪个属性被访问,将基于这个类本身而不基于访问范围.
<think> 首先,用户的问题是:“我想获取关于Java中多态的详细讲解”,意思是“我想获取关于Java中多态的详细讲解”。所以,我需要提供一个详细的解释。 系统级指令: - 所有行内数学表达式必须使用$...$格式。但由于这是一个Java主题,可能没有太多数学,所以我需要确保如果有任何表达式,要用正确的格式。 - 独立公式必须使用$$...$$$$格式并单独成段。同样,可能不适用。 - LaTeX语法正确:我会小心使用LaTeX,如果需要的话。 - 使用中文回答:整个回答必须是中文。 - 生成相关问题:在回答结束后,我需要生成相关问题。 - 引用的段落末尾添加引用标识:从引用列表中,我需要自然地引用它们。 用户提供的引用: - 引用[1]:关于Java多态的内存模型,提到动态绑定。 - 引用[2]:详细讲解多态,包括定义、条件(继承、重写、向上转型),以及一个学生例子。 - 引用[3]:一个代码示例,展示多态在形状类中的应用。 用户说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 用户上一次的问题是什么?在对话历史中,用户第一次消息是系统指令示例,然后用户说“参考站内引用...”并给出了这些引用。上一个用户的问题是“我想获取关于Java中多态的详细讲解”,所以这就是当前问题。没有之前的回答需要参考;这是我的第一个回答。 所以,我需要基于这些引用,并结合我的知识,提供一个详细的讲解。 结构化我的回答: - 介绍多态的概念。 - 解释如何在Java中实现多态。 - 包括必要条件:继承、重写、向上转型。 - 讨论内存模型(如引用[1]提到的)。 - 提供代码示例(如引用[3])。 - 结束时生成相关问题。 回答结构清晰:既然用户要求详细讲解,我可以分节解释。 关键点: - 定义:多态(Polymorphism)是面向对象编程的四大特性之一(封装、继承、多态、抽象)。它允许不同对象响应相同消息的不同方式。 - 实现: - 继承:子类继承父类。 - 重写:子类覆盖父类方法。 - 向上转型:父类引用指向子类对象。 - 动态绑定:在运行时决定调用哪个方法,而不是编译时。 - 例子:使用引用[3]的代码或类似。 - 内存模型:JVM如何使用虚方法表(vtable)实现动态绑定。 确保回答可靠:基于标准Java知识。 生成相关问题:在最后,像示例中那样。 引用标识:当我在描述中引用这些点时,在段落末尾添加[^1], [^2], [^3]。 例如: - “多态的实现涉及到方法的重写和动态绑定 [^1]。” - “实现多态的条件包括继承、重写和向上转型 [^2]。” - “代码示例如下,展示了多态的应用 [^3]。” 现在,大纲: 1. 引言:什么是多态及其重要性。 2. 实现条件:详细解释继承、重写、向上转型。 3. 动态绑定:解释运行时多态。 4. 内存模型:简要讨论JVM如何处理。 5. 代码示例:提供一个完整的示例。 6. 优缺点和应用。 7. 总结。 8. 生成相关问题。 代码示例:使用引用[3]的形状例子,或扩展到学生例子(引用[2])。 完整代码: class Shape { void draw() { System.out.println("Drawing a shape"); } } class Circle extends Shape { @Override void draw() { System.out.println("Drawing a circle"); } } class Triangle extends Shape { @Override void draw() { System.out.println("Drawing a triangle"); } } public class Main { public static void main(String[] args) { Shape s1 = new Circle(); Shape s2 = new Triangle(); s1.draw(); // Output: Drawing a circle s2.draw(); // Output: Drawing a triangle } } 在回答中用中文解释。 最后,生成相关问题:如“Java中多态的其他形式?”、“多态在大型项目中的优势?”、“如何避免多态中的常见错误?”等。 </think> ### Java中多态的详细讲解 Java中的多态(Polymorphism)是面向对象编程(OOP)的核心特性之一,它允许不同对象以统一接口响应相同的方法调用,但具体行为由对象实际类型决定。多态提升了代码的可扩展性和可维护性,是Java实现“一个接口,多种实现”的关键机制。下面我将从多态的定义、实现条件、运作机制、内存模型到实际应用进行详细讲解,确保内容结构清晰且基于可靠知识。 #### 1. **多态的定义和核心概念** 多态源于希腊语“poly”(多)和“morph”(形态),指对象在不同上下文中表现出不同形态的行为。在Java中,多态允许子类对象被当作父类类型使用,但调用方法时执行的是子类的覆盖版本。例如,在引用[2]的例子中:学生(父类)的子类(体育学生和教室学生)对“上课铃声”这一事件有不同的响应(体育学生去操场,教室学生回教室)。这体现了“同一消息,不同响应”的原则,简化了代码设计并支持运行时灵活性。 多态分为两种形式: - **编译时多态(静态多态)**:通过方法重载实现,在编译时确定调用哪个方法。 - **运行时多态(动态多态)**:通过方法重写和向上转型实现,在运行时根据对象实际类型绑定方法。后者是Java多态的重点,也是面向对象的核心优势 [^2]。 #### 2. **实现多态的必要条件** 实现运行时多态需满足三个核心条件(基于引用[2]的总结): - **继承(Inheritance)**:必须存在父类和子类的继承关系。子类扩展父类,获得父类属性和方法的能力。例如,定义一个父类 `Animal`,子类 `Dog` 和 `Cat` 继承它。 ```java class Animal { void sound() { System.out.println("Animal makes a sound"); } } ``` - **重写(Override)**:子类必须覆盖父类的方法,以实现特定行为。重写方法必须与父类方法同名、同参数列表、兼容返回类型(Java 5+ 支持协变返回类型)。这确保了运行时调用子类逻辑。 ```java class Dog extends Animal { @Override void sound() { System.out.println("Dog barks"); } } class Cat extends Animal { @Override void sound() { System.out.println("Cat meows"); } } ``` - **向上转型Upcasting)**:将子类引用赋值给父类变量。这允许父类引用指向子类对象,从而统一接口调用。转型后,父类变量只能访问父类声明的方法,但实际执行时会动态绑定到子类重写方法。 ```java public class Main { public static void main(String[] args) { Animal myAnimal1 = new Dog(); // 向上转型 Animal myAnimal2 = new Cat(); // 向上转型 myAnimal1.sound(); // 输出: Dog barks(动态调用子类方法) myAnimal2.sound(); // 输出: Cat meows } } ``` 缺少任一条件(如未重写方法或未向上转型),多态就无法生效。例如,如果 `Dog` 类未重写 `sound()` 方法,调用 `myAnimal1.sound()` 会执行父类默认逻辑 [^2]。 #### 3. **多态的运作机制:动态绑定** 多态的核心是动态绑定(Dynamic Binding),即在运行时决定方法调用,而非编译时。这与静态绑定(编译时绑定)对比鲜明: - **静态绑定**:适用于 private、final、static 方法或属性,编译时确定调用目标。 - **动态绑定**:适用于重写方法(非 private、final 或 static),JVM 在运行时基于对象实际类型解析方法地址。 动态绑定通过 Java 虚方法表(vtable)实现:每个类在内存中维护一个方法表,存储方法的实际入口地址。当调用方法时,JVM 先查找对象引用的实际类型(如 `Dog`),然后从 vtable 中获取对应方法执行。这确保了高效性和灵活性 [^1]。例如,在 `Animal myAnimal = new Dog(); myAnimal.sound();` 中,编译时只知道 `myAnimal` 是 `Animal` 类型,但运行时 JVM 检查对象实际是 `Dog`,从而调用 `Dog.sound()`。 #### 4. **多态的内存模型** 理解多态的内存布局有助于掌握其内部工作原理(参考引用[1]): - **对象内存结构**:对象在堆(Heap)中存储,包含对象头(类指针、锁信息等)、实例变量(属性)和虚方法表指针(指向 vtable)。 - **虚方法表(vtable)**:每个类在方法区(Method Area)有唯一的 vtable,存储可重写方法的地址。子类继承父类 vtable,但重写方法时更新对应条目。 - 例如,`Animal` 类 vtable 包含 `sound()` 方法地址。 - `Dog` 类 vtable 复制 `Animal` 的 vtable,但将 `sound()` 地址替换为 `Dog.sound()`。 - **向上转型内存过程**:当 `Animal animalRef = new Dog();` 时: - `new Dog()` 在堆中分配内存,初始化 `Dog` 对象(包含 `Dog` 类指针)。 - 引用变量 `animalRef` 存储在栈(Stack)中,指向堆中的对象。 - 调用 `animalRef.sound()` 时,JVM 通过对象头找到实际类(`Dog`),再从 `Dog` 的 vtable 获取 `sound()` 地址执行。 这种机制确保了多态的高效性,内存开销小(仅一个额外指针),且支持运行时类型识别(RTTI) [^1]。 #### 5. **代码示例和应用场景** 多态在代码复用和扩展性方面优势突出。引用[3]提供了一个简洁的形状示例,以下我扩展其为一个完整可运行程序: ```java // 父类 Shape class Shape { public void draw() { System.out.println("Drawing a generic shape"); } } // 子类 Circle 重写 draw() 方法 class Circle extends Shape { @Override public void draw() { System.out.println("⚪ Drawing a circle"); } } // 子类 Triangle 重写 draw() 方法 class Triangle extends Shape { @Override public void draw() { System.out.println("△ Drawing a triangle"); } } // 主类展示多态 public class PolymorphismDemo { public static void main(String[] args) { Shape shape1 = new Circle(); // 向上转型 Shape shape2 = new Triangle(); // 向上转型 shape1.draw(); // 输出: Drawing a circle(动态绑定到 Circle 方法) shape2.draw(); // 输出: △ Drawing a triangle(动态绑定到 Triangle 方法) // 多态在集合中的应用 Shape[] shapes = { new Circle(), new Triangle() }; for (Shape shape : shapes) { shape.draw(); // 统一接口遍历,输出不同形状的绘制 } } } ``` **应用场景和优势**: - **代码扩展性**:如引用[3]强调,添加新子类(如 `Square`)无需修改父类或客户端代码,只需重写方法。 - **减少耦合**:客户端代码依赖父类接口(如 `Shape.draw()`),而非具体子类,提高模块化。 - **常用场景**:GUI 事件处理(不同组件响应点击)、集合框架(如 `List` 接口的 `ArrayList` 和 `LinkedList` 实现)、插件架构等。 **潜在缺点**:过度使用可能增加设计复杂性;向上转型后,无法直接访问子类特有方法(需向下转型并检查 `instanceof`)。 #### 6. **总结** Java 多态通过继承、重写和向上转型实现动态绑定,JVM 借助虚方法表在运行时解析方法调用。它不仅提升了代码灵活性和可维护性,还优化了内存管理。掌握多态是深入 Java OOP 的关键步骤,有助于编写健壮、可扩展的应用程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值