Java多态是如何实现的?

Java的多态和C++一样,是通过延时绑定(late binding)或者说运行时绑定(runtime binding)来实现的。当调用某一个对象引用的方法时,因为编译器并不知道这个引用到底指向的是变量声明时说明的类型对象,还是该类型子类的对象。因此编译器无法为这次调用绑定到具体的某个方法。只有通过java中的运行时类型识别(RTTI, Runtime type identification)在运行时绑定到具体的方法。下面是一个具体的例子:

class shape
{
public void draw()
{
print("shape");
}
}
class triangle extends shape
{
public void draw()
{
print("triangle");
}
}
public class Polymorphism {
public static void main(String[] args)
{
shape s=new triangle();
s.draw();
}

结果是triangle

s是一个shape引用,但是在运行时因为是triangle对象,所以还是调用了triangle的draw方法。

Java多态中的一些陷阱

重写私有方法?

Java里面是不能重写私有方法的,这个其实很好理解,因为私有方法在子类是不可见的。子类没有继承父类的私有方法,更谈不上重写了。因此在子类中的同名方法是一个全新的方法。

public class Polymorphism {
private void show()
{
print("show parent");
}
public static void main(String[] args)
{
Polymorphism p=new privateMethod();
p.show();
}
}
class privateMethod extends Polymorphism
{
public void show()
{
print("show derived");
}
}

结果是 show parent

字段和静态方法的多态?

子类可以继承父类的非私有字段,子类的字段是否也具有多态性呢?我们来看一个实际的例子:

class shape
{
protected int perimeter=1;
public void draw()
{
print("shape");
}
public int getPerimeter()
{
return perimeter;
}
}
class triangle extends shape
{
int perimeter=3;
public void draw()
{
print("triangle");
}
public int getPerimeter()
{
return perimeter;
}
public int getSuperPerimeter()
{
return super.perimeter;
}
}
public class Polymorphism { public static void main(String[] args)
{
shape s=new triangle();
print("s.perimeter:"+s.perimeter);
print("s.getperimeter:"+s.getPerimeter());
triangle t=new triangle();
print("t.perimeter:"+t.perimeter);
print("t.getperimeter:"+t.getPerimeter());
print("t.getsuperperimeter:"+t.getSuperPerimeter());
}
}

以下是运行结果:

这个运行结果包含了以下信息:

1.triangle对象向上转型成shape后字段直接访问都是由编译器确定的,因此不会表现出多态性,返回的是1。

2.triangle对象向上转型成shape后调用方法访问字段是根据运行时对象类型延时绑定调用了triangle的getperimeter方法,返回的是3

3.t对象中包含了两个perimeter字段,一个来自于他本身,一个来自于他的父类。同时用字段名去调用该字段时默认返回的是他本身的perimeter字段,要调用从父类继承的该字段,要用super.perimeter的方法。

这个结果看起来多多少少让人有些疑惑,为了避免这种情况出现,我们一般都把字段声明为private(子类就无法继承),同时我们在子类中声明的字段最好不要与从父类继承的字段同名。

静态方法是没有多态性的,因为静态方法是和类绑定的,不会存在不知道具体类型的情况。

构造函数的多态性?

构造函数是不具有多态性的,因为构造方法本身是静态方法(如果不是的话,就会陷入鸡生蛋,蛋生鸡的死循环了)。要引入我们的问题,先来看一下构造函数的调用顺序。

1.为这个对象分配的存储空间都被初始化为0(对象初始化为null)

2.父类的构造函数调用(这样才能保证在子类的构造函数中访问的字段被初始化)

3.成员变量初始化

4.子类的构造函数调用

现在假设如果在第二步中,我们在父类的构造函数里调用了某个方法,这个方法是不是多态的?还是来看一个具体的例子:

class shape
{
protected int perimeter=1;
public shape()
{
draw();
print("shape created");
}
public void draw()
{
print("draw shape "+perimeter);
} }
class triangle extends shape
{
int perimeter=3;
public triangle()
{
print("triangle created");
}
public void draw()
{
print("draw triangle "+perimeter);
}
public int getPerimeter()
{
return perimeter;
}
} public class Polymorphism { public static void main(String[] args)
{
shape s=new triangle();
}
}

运行结果:

我们可以看到虽然triangle对象还没有构造完毕,draw方法仍是动态绑定到了triangle的draw方法。同时注意到perimeter的值还没有初始化为3,而是0。

这样的结果就是我们在triangle对象还没有被初始化之前就访问了其中的字段。因此我们在实际应用中要避免在构造函数中调用其他方法,或者只调用私有方法(不会被继承,因此不会引发该问题)

Java多态的一些陷阱的更多相关文章

  1. Java 多态——与C++的比较

    学习了Java和C++之后,由于长期不使用C++,而java的基础知识掌握不牢,现在已经搞不清java多态了.现在先来谈谈java多态,稍后有时间再更新C++的多态,并进行比较~ 一. Java的多态 ...

  2. C++和java多态的区别

    C++和java多态的区别 分类: Java2015-06-04 21:38 2人阅读 评论(0) 收藏 举报  转载自:http://www.cnblogs.com/plmnko/archive ...

  3. 深入理解Java多态机制

    从字节码层面来看,Java中的所有方法调用,最终无外乎转换为如下几条调用指令. invokestatic: 调用静态方法. invokespecial: 调用实例构造器<init>方法,私 ...

  4. Java 多态 父类和子类方法的访问控制权限

    Java 多态 父类和子类方法的访问控制权限 @author ixenos 父类和子类方法的访问控制权限 继承是为了扩展类的功能,而这种扩展显然就是对一个原始类的扩展,目的还是向上转型来调用,所以这就 ...

  5. Java多态(二)

    public class ExtendsTest { public static void main(String[] args) { A a1 = new A(); A a2 = new B(); ...

  6. 从JVM角度看Java多态

    首先,明确一下,Java多态的三个必要条件: 1. 继承 2. 子类重写父类方法 3. 父类引用指向子类对象 然后看一个例子 package test.xing; class Father{ prot ...

  7. 关于java多态的理解

    要理解多态,就必须有一个大的理解方向,不然很容易绕进去. 首先知道多态的释义:多态性是指一个名词可以有多种语义. 对于java的多态性学习者来说,就是必须要知道多个同名方法在不同情况下的使用规则. j ...

  8. Java经验杂谈(2.对Java多态的理解)

    多态是面向对象的重要特性之一,我试着用最简单的方式解释Java多态: 要正确理解多态,我们需要明确如下概念:・定义类型和实际类型・重载和重写・编译和运行 其中实际类型为new关键字后面的类型. 重载发 ...

  9. 学JAVA第十六 天,JAVA多态

    今天老师讲了多态的使用 多态是同一个行为具有多个不同表现形式或形态的能力. 多态的优点: 1. 消除类型之间的耦合关系 2. 可替换性  3. 可扩充性 4. 接口性 5. 灵活性 6. 简化性 我个 ...

随机推荐

  1. 用idea简单创建web项目——两种方式

    最近同学让我教她们用idea创建web项目,于是我用两种方式创建web项目,并整理截图给她们看,一种是用maven创建,一种是不用maven创建,适合菜鸟哈哈~ 方法一:不用maven 1.解压tom ...

  2. 『IOS』 遇到问题记录(长期更新)

    遇到的很多问题,解决后都是自己记着,以为不会忘记,之后却会想不起来了. 所以把今后解决的问题记录在这. 一. 在二级页面设置了CAlayer的代理,在返回一级页面报错: EXC_BAD_ACCESS( ...

  3. python的数据存储

    Python存储数据 使用json.dump()和json.load() 不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中.用户关闭程序时,你几乎总是要保存他们提供的信息:一种简单 ...

  4. HTML/HTML5

    HTML/HTML5 一.文档加载顺序. 文档入下,数字编号为加载顺序. <html><!--1--> <head><!--2--> <link ...

  5. C# 图像自动切换

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  6. 《JavaScript高级程序设计》5.5 Function类型

    5.5 Function类型 函数实质上是对象, 每个函数都是Function类型的实例, 并且都和其他引用类型一样具有属性和方法. 因此函数名实际上也是一个指向函数对象的指针, 不会与某个函数绑定. ...

  7. 【转】4G18的低成本NA玩法

    首先是要再次强调一次,4G18的缸径是76MM,冲程是87.5MM.属于典型的长冲程低转发动机! 这种设计的优点是比较适合市区走停的工作状况,省油. 如果要针对改装方案而言因为这种低转时便可输出大扭矩 ...

  8. FunDA(8)- Static Source:保证资源使用安全 - Resource Safety

    我们在前面用了许多章节来讨论如何把数据从后台数据库中搬到内存,然后进行逐行操作运算.我们选定的解决方案是把后台数据转换成内存中的数据流.无论在打开数据库表或从数据库读取数据等环节都涉及到对数据库表这项 ...

  9. 利用nginx实现负载均衡

    1.安装nginx和tomcat 我这里是使用docker安装的.安装流程可参照 dockerfile 这里安装了两个tomcat,端口分别是42000和42001.第二个tomcat的首页随便加了些 ...

  10. Retrofit源码解析(上)

    简介Retrofit是Square公司开发的一款针对Android网络请求的框架,官网地址http://square.github.io/retrofit/ ,在官网上有这样的一句话介绍retrofi ...