精华内容
下载资源
问答
  • 多态实现原理 https://blog.csdn.net/SEU_Calvin/article/details/52191321

    多态的实现原理

    在这里推荐一个博客

    https://blog.csdn.net/SEU_Calvin/article/details/52191321
    

    多态的具体体现

    面向对象编程有四个特征:抽象,封装,继承,多态。
    多态有四种体现形式:

    • 接口和接口的继承。
    • 类和类的继承。
    • 重载。
    • 重写。

    其中重载和重写为核心
    重载:重载发生在同一个类中,在该类中如果存在多个同名方法,但是方法的参数类型和个数不一样,那么说明该方法被重载了。
    重写: 重写发生在子类继承父类的关系中,父类中的方法被子类继承,方法名,返回值类型,参数完全一样,但是方法体不一样,那么说明父类中的该方法被子类重写了。

    展开全文
  • 本文实例讲述了C#中多态现象和多态实现方法。分享给大家供大家参考。具体分析如下: 面向对象的特征封装、继承和多态。Polymorphism(多态性)来源于希腊单词,指“多种形态”。多态性的一个重要特征是方法的调用...
  •    三、多态实现    四、多态存在的三个必要条件    五、重写    六、抽象类和抽象方法    七、接口    八、向上转型实现多态    九、多态的优缺点 前言   多态多态多态到底是一个...

    编译环境

    • windows 10
    • JDK 1.8
    • eclipse



    目录

       一、多态的基本概念
       二、继承
       三、多态的实现
       四、多态存在的三个必要条件
       五、重写
       六、抽象类和抽象方法
       七、接口
       八、向上转型实现多态
       九、多态的优缺点



    前言

      多态,多态,多态到底是一个什么样的定义呢,充满着好奇心,决定去研究一番。



    多态(Polymorphism)的基本概念

      多态意味着父类的变量可以指向子类对象。多态是同一个行为具有多个不同表现形式或形态的能力,就是同一个接口,使用不同的实例而执行不同操作。多态是基类型对象访问派生类重写的方法。



    继承(Inheritance)

      一、简单说明
    ​  由于多态的很多内容都跟继承有相关,所以就先来介绍继承,然后再介绍多态。


      二、基本概念

    ​  继承是从已有的类中派生出新的类,新的类能具有已有类的数据和属性,并能扩展出新的能力。Java通常使用extends关键字来实现继承,比如说,class Parent{},class Children extends Parent{}。


      三、基类和派生类

    ​  我个人对于基类和派生类的理解是,基类就是父类派生类就是子类,比如说class A extends B{},其中的A就称为父类或者基类,B称为子类或者派生类。


      四、继承的特性

    ​ 1、子类拥有父类非private的属性和方法。

    ​ 2、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

    ​ 3、子类可以用自己的方式实现父类的方法。

    ​ 4、Java的继承不支持多继承,但是可以多重继承,通过单继承来实现类的继承,单继承是一个子类只能继承一个父类,多重继承是,比如说A继承B,B继承C。

    ​ 5、提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系约紧密,代码独立性越差)。


      五、构造器

    ​  子类是不继承父类的构造器的(构造方法或者构造函数),它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。

      如果父类构造器没有参数,则在子类的构造器中不需要使用super关键字调用父类构造器,系统会自动调用父类的无参数构造器。


      六、代码实例

      1、继承的简单实现

    public class Debug {
        /** Main method */
        public static void main(String[] args) {
            Children children = new Children();	//
            String string = children.testChildren();
            System.out.println(string);
        }
    }
    
    // 父类
    class Parent {
        // 父类方法
        public String testParent() {
            return "Parent";
        }
    }
    
    // 子类继承父类
    class Children extends Parent {
        // 子类方法
        public String testChildren() {
            return "Children visit " + testParent();
        }
    }
    

    输出:

    Children visit Parent
    

    解释说明:

    ​  通过上面的代码,可以简单的实现继承,子类就拥有了父类的所有属性和方法,除了使用private声明的属性和方法。


      ​2、继承中构造器的显式和隐式

    public class Debug {
        /** Main method */
        public static void main(String[] args) {
            Children children = new Children();
        }
    }
    
    // 父类
    class Parent {
        // 无参数构造器
        public Parent() {
            System.out.println("Parent");
        }
        
        // 有参数构造器
        public Parent(String string) {
            System.out.println(string);
        }
    }
    
    // 子类
    class Children extends Parent {
        // 在子类自动调用super();
        // 如果要调用父类有参数的构造器, 需要在super的括号里添加对应参数
    }
    

    输出:

    Parent
    

    解释说明:

    ​ 通过上面例子可以知道,系统在子类会自动调用super();的方法叫做隐式调用,如果super中带有参数,则叫做显性调用。



    多态的实现

    1、重写

    2、接口

    3、抽象类和抽象方法



    多态存在的三个必要条件

    1、继承

    2、重写

    3、父类引用指向子类对象(向上转型



    重写(Override)

    ​  一、基本概念

      重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变。外壳不变,核心重写。重写的好处在于子类可以根据需要,定义特定于自己的行为,也就是说子类能够根据细节要实现父类的方法。重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。


      二、方法重写规则

      1、参数列表与被重写方法的列表必须完全相同

      2、 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(Java 7 以上的版本)

      3、 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。

      4、父类的成员方法只能被它的子类重写。

      5、声明为final的方法不能被重写。

      6、声明为static的方法不能被重写,但是能够被再次声明。

      7、子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为prrivate和final的方法。

      8、子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非fianl方法。

      9、重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

      10、构造方法不能被重写。

      11、如果不能继承一个类,则不能重写该类的方法。

    类中成员的修饰符 在同一类内可访问 在同一包内可访问 在子类内可访问 在不同包可访问
    public
    protected ×
    default × ×
    private × × ×

      三、代码实例

    public class Debug {
    
    	public static void main(String[] args) {
    		PrintHello string1 = new PrintHello();
    		PrintWorld string2 = new PrintWorld();
    
    		string1.print(); // 调用父类的print方法
    		string2.print(); // 调用重写后的print方法
            string2.test(); // 调用父类的test方法
    	}
    }
    
    // 父类
    class PrintHello {
    
    	public void print() {
    		System.out.println("Hello");
    	}
    
    	public void test() {
    		System.out.println("Hello World");
    	}
    }
    
    // 子类继承父类,并重写print方法
    class PrintWorld extends PrintHello {
    
    	@Override	// 重写print方法
    	public void print() {
    		System.out.println("World");
    	}
    }
    

    输出

    Hello
    World
    Hello World
    

    解释说明

      重写的前提是存在继承,在上面的实例中,PrintWorld继承了父类PrintHello,具有父类的属性和方法,所以还可以使用父类的非重载方法,继承之后子类会优先调用子类重载的方法而不是父类的方法。如果使用的是eclipse,会看到这样的有这样的一个实心三角形,悬停在上面还会出现这样一句话:overrides PrintHello.print, 这就是代表重写的意思。

    在这里插入图片描述



      四、延伸

      都知道重载和重写的关系,但是重载和重写也是有一定的区别的。重载是在同一个类里面,方法名字相同,而参数不同。返回的类型可以相同也可以不同,每个重载的方法都必须有一个独一无二的参数类型列表,最常用的地方就是构造器的重载。重写和重载的区别如下表:

    区别点 重载方法 重写方法
    参数列表 必须修改 不能修改
    返回类型 可以修改 不能修改
    异常 可以修改 可以减少或删除,但是不能抛出新的或者更广的异常
    访问 可以修改 不能做更严格的限制(可以降低限制)



    抽象类(Abstract class)和抽象方法(Abstract method)

      一、简单说明

      为什么先将抽象类和抽象方法,而不是讲接口呢,其实接口在Java中时一个抽象类型,时抽象方法的集合,所以先介绍抽象类和抽象方法后,可能对接口会更容易理解。


      二、抽象类的基本概念

      在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中,没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类除了不能实例化对象之外,类的其他功能依然存在,成员变量、成员方法和构造方法的的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承才能被使用。父类包含了子类集合的常见方法,但是父类本身时抽象的,所以不能直接调用父类使用这些方法。简单来说就是,定义一个抽象类,你不能直接访问这个抽象类的属性和方法,只能通过继承,通过子类来访问这些属性和方法

      抽象类表示的是一种继承关系,一个类只能继承一个抽象类,但是一个类却可以实现多个接口,抽象类使用abstract class 来定义


      三、抽象方法的基本概念

      抽象方法的具体实现由它的子类来决定,在父类中声明该方法为抽象方法,用abstract关键字来声明抽象方法,抽象方法只包含一个方法名,没有方法体;抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。


      四、抽象类的规定

      1、抽象类不能被实例化,如果被实例化,就会报错,编译无法通过,只有抽象类的非抽象子类可以创建对象。

      2、抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类。

      3、抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

      4、构造方法,类方法(用static修饰的方法)不能声明为抽象方法。

      5、抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。


      五、代码实例

    /* 定义一个抽象类, 直接调用抽象类 */
    
    public class Debug {
    
    	public static void main(String[] args) {
    		Parent string = new Parent();
    	}
    }
    
    // 抽象类
    abstract class Parent {
    	private String string;
    
    	public Parent() {
    		this.string = "";
    	}
    
    	public void setString(String string) {
    		this.string = string;
    	}
    
    	public String getString() {
    		return this.string;
    	}
    }
    

    解释说明:

      如果是这样子使用的话,会发现编译器直接报错,因为不能直接访问抽象类的方法,我们只需要用一个子类来继承抽象类就可以了。

    /* 定义一个抽象类, 子类继承抽象类 */
    
    /* 
    public class Debug {
    
    	public static void main(String[] args) {
    		Children string = new Children();
    		string.setString("Test");
    		System.out.println(string.getString());	// 输出
    	}
    }
    
    // 抽象类
    abstract class Parent {
    	private String string;
    
    	public Parent() {
    		this.string = "";
    	}
    
    	public void setString(String string) {
    		this.string = string;
    	}
    
    	public String getString() {
    		return this.string;
    	}
    }
    
    // 继承抽象类的子类
    class Children extends Parent {
    
    }
    */
    
    // 或者可以这样子写
    public class Debug {
    
    	public static void main(String[] args) {
    		Children string = new Children();
    		string.setString("Test"); // 调用父类方法
    		System.out.println(string.getString()); // 输出
    	}
    }
    
    // 抽象类
    abstract class Parent {
    	protected String string;
    
    	public Parent() {
    		this.string = "";
    	}
    
    	abstract void setString(String string);
    
    	abstract String getString();
    }
    
    // 继承抽象类的子类
    class Children extends Parent {
    
    	@Override	// 重写父类setString方法
    	public void setString(String string) {
    		this.string = string;
    	}
    
    	@Override	// 重写父类getString方法
    	public String getString() {
    		return this.string;
    	}
    }
    

    输出:

    Test
    



    接口(Interface)

      一、基本概念

      一个类通过继承接口的方式,从而来继承的抽象方法,接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法,接口则包含类要实现的方法。接口无法被实例化,但是可以被实现,一个实现接口的类,必须实现接口内所描述的所有方法, 否则就必须声明为抽象类。使用interface关键字来创建接口。 一个类可以实现多个接口,一个接口可以继承一个或多个接口,但是只能继承一个类。接口和抽象类挺相似的,但是接口比抽象类更加灵活,因为一个子类只能继承一个父类,但是却可以实现任意个数的接口。

      接口是一种与类相似的结构,只包含常量和抽象方法,接口在许多方面都与抽象类很相近,但抽象类除了包含常量和抽象方法外,还可以包含变量和具体方法。(JDK 1.8 以后,接口里可以有静态方法和方法体了)


    ​  二、接口的说明

      1、接口与类相似点:

    • 一个接口可以有多个方法
    • 接口文件保存在后缀为.java的文件中,文件名使用接口名
    • 接口的字节码文件保存在后缀为.class的文件中
    • 接口相应的字节码文件必须在包名称想匹配的目录结构中

      2、接口与类的区别

    • 接口不能用于实例化对象
    • 接口没有构造方法
    • 接口中所有的方法必须是抽象方法
    • 接口不能包含成员变量,除了static和final变量
    • 接口不是被继承,而是被类实现
    • 接口支持多继承

      3、接口特性

    • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为public abstract
    • 接口中可以含有变量,但是接口中的变量会被隐式的指定为public static final变量
    • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法
    • 声明一个接口的时候,不必使用abstract关键字
    • 接口中的方法都是公开的

      4、抽象类和接口的区别

    • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行,因为接口中的方法会被隐式指定为public abstract

    • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的

    • 接口中不能含有静态代码块以及静态方法,而抽象类是可以有静态代码块和静态方法的


      三、代码实例

      1、接口回调实现多态:接口声明的变量指向其实现类实例化的对象,那么该接口变量就可以调用接口中的抽象方法。就好比说,使用列表时候是这样子定义的 List<Integer> list = new ArrayList<Integer>(); 其中的List就是接口,而ArrayList就是实现List的类。

    /** 接口的创建和实现 */
    
    public class Debug {
    	/** Main method */
    	public static void main(String[] args) {
    		Animal dog = new Dog(); // 调用实现接口的类
    		dog.run(); // 调用该类的方法
    	}
    }
    
    // 接口
    interface Animal {
    	// 创建一个方法
    	public void run();
    }
    
    // 实现接口
    class Dog implements Animal {
    
    	// 实现接口的方法
    	@Override
    	public void run() {
    		System.out.println("Dog is running");
    	}
    }
    

    输出:

    Dog is running
    

    解释说明:

    ​ 接口中的方法其实是抽象方法,通过类来实现。通过implements关键字来实现。



    向上转型实现多态

      一、基本概念

      1、上转型对象:子类创建的对象赋值给父类声明变量,则称该对象为上转型对象,这个过程称为对象上转型。

      2、下转型对象:上转型对象再强制转化为创建改对象的子类类型对象,即将上转型对象还原为子类对象。

      3、向上转型:通过子类对象实例化父类对象。

      4、向下转型:通过父类对象实例化子类对象。


      二、代码实例

    /** 向上转型和向下转型 */
    
    public class Debug {
    	/** Main method */
    	public static void main(String[] args) {
    		Dog dog = new Dog(); // 没有转型
    		dog.eat();
    		Animal animal = new Fish(); // 向上转型
    		System.out.println(animal instanceof Animal);
    		animal.eat();
    		Fish fish = (Fish) animal; // 向下转型
    		System.out.println(fish instanceof Fish);
    		fish.eat();
    	}
    }
    
    // 动物类
    class Animal {
    
    	public void eat() {
    		System.out.println("Animal eat anything");
    	}
    }
    
    // 鱼类
    class Fish extends Animal {
    
    	public void swim() {
    		System.out.println("Fish can swim");
    	}
    
    	@Override
    	public void eat() {
    		System.out.println("Fish eat feed");
    	}
    }
    
    // 狗类
    class Dog extends Animal {
    
    	public void run() {
    		System.out.println("Dog can run");
    	}
    
    	@Override
    	public void eat() {
    		System.out.println("Dog eat bone");
    	}
    }
    

    输出:

    Dog eat bone
    true
    Fish eat feed
    true
    Fish eat feed
    

    解释说明:

      调用Dog类是没有使用转型的,所以输出没有什么异议。但是在向上转型中,将Fish类转型为Animal类,就是子类转型为父类,在向上转型后,fish就属于Animal类了,但是输出却是Fish eat feed,理由是:在向上转型前,先调用的是new Fish(); Fish类先把父类的eat方法给重写覆盖了,所以调用的是Fish的方法。在输出结果也可以看到在向上转型后,fish是属于Animal类的,在向下转型后,fish又变回了Fish类了。向上转型用得比较多,向下转型用得比较少。(ps: 向上转型后,才能进行向下转型)



    多态的优缺点

      一、多态的优点

      1、可替换性:多态对已存在的代码具有可替换性。

      2、可扩充性:增加新的子类不影响已经存在的多态性,继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。

      3、接口性:多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它来实现的。

      4、灵活性:它在应用中体现了灵活多样的操作,提高了使用效率。

      5、简化性:多态简化了对应用软件的代码编写和修改过程。


      二、多态的缺点

      1、不能使用子类的特有功能。



    写在最后

      通过上面的了解,对多态还是有了比较浅显的认知,其实这只是对多态的很小的说明,多态在实际应用中非常广泛,需要慢慢去琢磨了解。

    ->回到顶部<-



    参考

    1、https://www.runoob.com/java/java-tutorial.html

    2、https://lingcoder.gitee.io/onjava8/#/sidebar

    3、https://blog.csdn.net/weixin_42867975/article/details/90300765

    展开全文
  • 1.多态具体实现是怎样的? 将父类作为方法形参,将子类的对象作为方法实参,从而实现多态 案例:主人与宠物玩耍 主人类 Master 宠物 Pet ——> Dog、Cat package duotai; public class Pet { public Pet() {...

    24.多态的实现1:介绍多态使用的一种具体情况???

    学习:第7遍


    1.介绍多态使用的一种具体情况???

    将父类作为方法形参
    将子类的对象作为方法实参
    从而实现多态

    展开全文
  • Java的多态实现

    2020-02-03 21:10:02
    Java的多态实现 概述 java实现多态有三个必要条件:继承、重写、向上转型(父类引用指向子类对象)。 1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何...

                                            Java的多态与实现

    概述

    java实现多态有三个必要条件:继承、重写、向上转型(父类引用指向子类对象)。

    1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
    2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
    3.接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
    4.灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
    5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

    1.普通类
    
     AnimalFu a =  new AnimalZi new();
    
    2.抽象类 animal  是父类抽象类
    
    Animal a = new Cat();
    
    3.接口 // 学生是类  smoking  是接口 
    
    Smoking sk = new Student() 
    
    多态的特点:
    
    变量:编译看父类
    
    方法:运行看子类
    

    Java多态的实现机制是父类或接口定义的引用变量可以指向子类或实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实现对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法

    一、子类型和子类

    只要是A类运用了extends关键字实现了对B类的继承,那么我们就可以说Class A是Class B的子类,子类是一个语法层面上的词,只要满足继承的语法,就存在子类关系。

    子类型比子类有更严格的要求,它不仅要求有继承的语法,同时要求如果存在子类对父类方法的改写(override),那么改写的内容必须符合父类原本的语义,其被调用后的作用应该和父类实现的效果方向一致

     对二者的对比是想强调一点:只有保证子类都是子类型,多态才有意义。Java父类强制转换子类原则

    二、多态的机制

    本质上多态分两种:

    1. 编译时多态(又称静态多态)
    2. 运行时多态(又称动态多态)

    重载(overload)就是编译时多态的一个例子,编译时多态在编译时就已经确定,运行时运行的时候调用的是确定的方法。

    我们通常所说的多态指的都是运行时多态,也就是编译时不确定究竟调用哪个具体方法,一直延迟到运行时才能确定。这也是为什么有时候多态方法又被称为延迟方法的原因。

    、多态通常有两种实现方法

    1. 子类继承父类(extends)
    2. 类实现接口(implements)

    无论是哪种方法,其核心之处就在于对父类方法的改写或对接口方法的实现,以取得在运行时不同的执行效果。

    要使用多态,在声明对象时就应该遵循一条法则:声明的总是父类类型或接口类型,创建的是实际类型。举例来说,假设我们要创建一个ArrayList对象,声明就应该采用这样的语句:

    List list = newArrayList();

    而不是

    ArrayList list = newArrayList();

    在定义方法参数时也通常总是应该优先使用父类类型或接口类型,例如某方法应该写成:

    public void doSomething(List list);

    而不是

    public void doSomething(ArrayList list);

    这样声明最大的好处在于结构的灵活性:假如某一天我认为ArrayList的特性无法满足我的要求,我希望能够用LinkedList来代替它,那么只需要在对象创建的地方把new ArrayList()改为new LinkedList即可,其它代码一概不用改动。

    虚拟机会在执行程序时动态调用实际类的方法,它会通过一种名为动态绑定(又称延迟绑定)的机制自动实现,这个过程对程序员来说是透明的。

    四、多态的用途

    多态最大的用途我认为在于对设计和架构的复用,更进一步来说,《设计模式》中提倡的针对接口编程而不是针对实现编程就是充分利用多态的典型例子。

    定义功能和组件时定义接口,实现可以留到之后的流程中。同时一个接口可以有多个实现,甚至于完全可以在一个设计中同时使用一个接口的多种实现(例如针对ArrayList和LinkedList不同的特性决定究竟采用哪种实现)。

    、多态的实现

    继承实现多态的内部机制

    在JVM执行Java字节码时,类型信息被存放在方法区中,通常为了优化对象调用方法的速度,方法区的类型信息中增加一个指针,该指针指向一张记录该类方法入口的表(称为方法表),表中的每一项都是指向相应方法的指针。

    由于Java的单继承机制,一个类只能继承一个父类,而所有的类又都继承自Object类。方法表中最先存放的是Object类的方法,接下来是该类的父类的方法,最后是该类本身的方法。这里关键的地方在于,如果子类改写了父类的方法,那么子类和父类的那些同名方法共享一个方法表项,都被认作是父类的方法

    注意这里只有非私有的实例方法才会出现,并且静态方法也不会出现在这里,原因很容易理解:静态方法跟对象无关,可以将方法地址直接引用,而不像实例方法需要间接引用。更深入地讲,静态方法是由虚拟机指令invokestatic调用的,私有方法和构造函数则是由invokespecial指令调用,只有被invokevirtual和invokeinterface指令调用的方法才会在方法表中出现。

    由于以上方法的排列特性(Object——父类——子类),使得方法表的偏移量总是固定的。例如,对于任何类来说,其方法表中equals方法的偏移量总是一个定值,所有继承某父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值。

    前面说过,方法表中的表项都是指向该类对应方法的指针,这里就开始了多态的实现:

     

    假设Class A是Class B的子类,并且A改写了B的方法method(),那么在B的方法表中,method方法的指针指向的就是B的method方法入口。而对于A来说,它的方法表中的method方法则会指向其自身的method方法而非其父类的(这在类加载器载入该类时已经保证,同时JVM会保证总是能从对象引用指向正确的类型信息)。结合方法指针偏移量是固定的以及指针总是指向实际类的方法域,我们不难发现多态的机制就在这里:

    在调用方法时,实际上必须首先完成实例方法的符号引用解析,结果是该符号引用被解析为方法表的偏移量。

    虚拟机通过对象引用得到方法区中类型信息的入口,查询类的方法表,当将子类对象声明为父类类型时,形式上调用的是父类方法,此时虚拟机会从实际类的方法表(虽然声明的是父类,但是实际上这里的类型信息中存放的是子类的信息)中查找该方法名对应的指针(这里用“查找”实际上是不合适的,前面提到过,方法的偏移量是固定的,所以只需根据偏移量就能获得指针),进而就能指向实际类的方法了。

    实现接口多态的内部机制

    事实上上面的过程仅仅是利用继承实现多态的内部机制,多态的另外一种实现方式:实现接口相比而言就更加复杂,原因在于,Java的单继承保证了类的线性关系,而接口可以同时实现多个,这样光凭偏移量就很难准确获得方法的指针。所以在JVM中,多态的实例方法调用实际上有两种指令:

    • invokevirtual指令用于调用声明为类的方法;
    • invokeinterface指令用于调用声明为接口的方法。

    当使用invokeinterface指令调用方法时,就不能采用固定偏移量的办法,只能老老实实挨个找了(当然实际实现并不一定如此,JVM规范并没有规定究竟如何实现这种查找,不同的JVM实现可以有不同的优化算法来提高搜索效率)。

    我们不难看出,在性能上,调用接口引用的方法通常总是比调用类的引用的方法要慢。这也告诉我们,在类和接口之间优先选择接口作为设计并不总是正确的,当然设计问题不在本文探讨的范围之内,但显然具体问题具体分析仍然不失为更好的选择。

     

    总结

    多态机制包括静态多态(编译时多态)和动态多态(运行时多态),静态多态比如说重载,动态多态是在编译时不能确定调用哪个方法,得在运行时确定。动态多态的实现方法包括子类继承父类和类实现接口。当多个子类上转型(不知道这么说对不)时,对象掉用的是相应子类的方法,这种实现是与JVM有关的。

     

    子类Child继承父类Father,我们可以编写一个指向子类的父类类型引用,该引用既可以处理父类Father对象,也可以处理子类Child对象,当相同的消息发送给子类或者父类对象时,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。即多态性就是相同的消息使得不同的类做出不同的响应。

    其它知识补充

    方法区和常量池。这两个都是Java虚拟在运行时的数据区域,常量池是方法区的一部分。

    方法区

        方法区与Java堆一样,是各个线程共享的内存区域,用于存储已经被虚拟机加载的类信息、常量、静态变

    量、即使编译器编译后的代码等数据。

    常量池

        常量池是方法区的一部分,用于存放编译期所生成的各种字面量和符号引用。常量池中共有11个常量表。

        常量表类型 标志值(占1 byte) 描述

        CONSTANT_Utf8     1 UTF-8编码的Unicode字符串

        CONSTANT_Integer 3 int类型的字面值

        CONSTANT_Float     4 float类型的字面值

        CONSTANT_Long     5 long类型的字面值

        CONSTANT_Double     6 double类型的字面值

        CONSTANT_Class     7 对一个类或接口的符号引用

        CONSTANT_String     8 String类型字面值的引用

        CONSTANT_Fieldref 9 对一个字段的符号引用

        CONSTANT_Methodref 10 对一个类中方法的符号引用

      CONSTANT_InterfaceMethodref 11 对一个接口中方法的符号引用

        CONSTANT_NameAndType 12 对一个字段或方法的部分符号引用

    ---------------------------------------------------------------------------------------------------

    对于多态的原理主要研究方法区。在Java中多态指的是它允许基类的指针或引用指向派生类的对象而在具体访问时实现方法的动态绑定。java中的方法调用有静态绑定和动态绑定之分,静态绑定指的是我们在编译期就已经确定了会执行那个方法的字节码,而动态绑定只有在运行时才能知晓。

    静态绑定

     Java中的静态方法、私有方法以及final修饰的方法的调用,都属于静态绑定,对于重载的实例方法的调用,也是采用静态绑定。静态绑定的原理主要是一个常量池解析的过程,下面来详解其过程:

       假如有两个类A、B,在A类中我们调用了B类中的一个静态方法,在编译期,这个调用的动作会被编译成一条静态调用指令,该指令就对应常量池中的CONSTANT_Methodref表中所存储的该方法的符号引用,通过这个符号引用可以得到静态方法的全类名B,JVM加载B类,便会得到B类中方法的直接地址,该地址会被存储到A类常量池中对应的常量表中,这便是常量池解析过程,再次发起调用时,就会直接根据这个直接地址调用对应方法。以上过程可以看出,在编译阶段我们就已经确定了应该执行哪一个字节码代码。

    动态绑定

        动态绑定讲解之前需要了解JVM管理的一个重要的数据结构--方法表。它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址 。

     动态绑定前面的流程与静态绑定类似,假如此处有两个类A,B继承了A类,B类中重写了A类中的f1()方法,我们采用向上转型的方式用指向B实例的A类型引用调用f1()方法,编译器会生成一条字节码指令,该指令会去常量表中找到f1()方法信息的符号引用,通过该引用确定调用该方法的类型全名,即A类的全名称,根据名称加载到A类的字节码,去A类所对应的方法表中找到f1()方法,将它的直接地址记录到调用f1()方法的类的对应的常量表中,常量池解析结束,可以思考,我们此时是否能确定调用f1()方法时执行的是哪一块的字节码,答案是不能,因为截至此时我们f1()方法指定执行的是父类中的方法,引用虽然父类类型,但他指向的是父类对象还是子类对象是不知道的(确切地说是此时的程序不知道,程序员肯定是知道的),假如指向父类那就是父类中的f1()方法,如果指向子类的实例,子类没有重写,依然执行父类f1()方法,如果子类重写了就应该是子类的f1()方法。此时动态绑定就登场了,确定f1()换需要拿到B类实例在堆中的引用,通过引用找到堆中B的对象,根据对象进一步获取它的方法表,找到方法表的f1()方法的直接地址,此时便最终确定了。

        所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用变量调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

    代码示例

    基于继承实现的多态

    public class Wine {
        private String name;
            public Wine(){
            }
     
        public String getName() {
            return name;
        }
     
        public void setName(String name) {
            this.name = name;
        }
     
        public String drink(){
            return "喝的是 " + getName();
        }
     
     
        /**
         * 重写toString()
         */
        public String toString(){
            return null;
        }
    }
     
     
    public class JNC extends Wine{
        public JNC(){
            setName("JNC");
        }
     
        /**
         * 重写父类方法,实现多态
         */
        public String drink(){
            return "喝的是 " + getName();
        }
     
        /**
         * 重写toString()
         */
        public String toString(){
            return "Wine : " + getName();
        }
    }
     
    public class JGJ extends Wine{
        public JGJ(){
            setName("JGJ");
        }
        
        /**
         * 重写父类方法,实现多态
         */
        public String drink(){
            return "喝的是 " + getName();
        }
        
        /**
         * 重写toString()
         */
        public String toString(){
            return "Wine : " + getName();
        }
    }
     
    public class Test {
        public static void main(String[] args) {
            //定义父类数组
            Wine[] wines = new Wine[2];
            //定义两个子类
            JNC jnc = new JNC();
            JGJ jgj = new JGJ();
            
            //父类引用子类对象
            wines[0] = jnc;
            wines[1] = jgj;
            
            for(int i = 0 ; i < 2 ; i++){
                System.out.println(wines[i].toString() + "--" + wines[i].drink());
            }
            System.out.println("-------------------------------");
        }
    }
    OUTPUT-------------------------------
    Wine : JNC--喝的是 JNC
    Wine : JGJ--喝的是 JGJ

    基于接口实现的多态

    public class A {
        public String show(D obj) {
            return ("A and D");
        }
     
        public String show(A obj) {
            return ("A and A");
        }
    }
     
    public class B extends A{
        public String show(B obj){
            return ("B and B");
        }
        
        public String show(A obj){
            return ("B and A");
        }
    }
     
    public class C extends B{
     
    }
     
    public class D extends B{
     
    }
     
    public class Test {
        public static void main(String[] args) {
            A a1 = new A();
            A a2 = new B();
            B b  = new B();
            C c  = new C();
            D d  = new D();
     
            System.out.println("1--" + a1.show(b));
            System.out.println("2--" + a1.show(c));
            System.out.println("3--" + a1.show(d));
            System.out.println("4--" + a2.show(b));
            System.out.println("5--" + a2.show(c));
            System.out.println("6--" + a2.show(d));
            System.out.println("7--" + b.show(b));
            System.out.println("8--" + b.show(c));
            System.out.println("9--" + b.show(d));      
        }
    }
     
    output-------------------------------------------------
    1--A and A
    2--A and A
    3--A and D
    4--B and A
    5--B and A
    6--A and D
    7--B and B
    8--B and B
    9--A and D

    参考地址

    https://blog.csdn.net/yz_cfm/article/details/85262909

    https://blog.51cto.com/12222886/2069357

    https://blog.csdn.net/weixin_42281036/article/details/81557468

    https://zhuanlan.zhihu.com/p/69764885

    展开全文
  • C++ 多态实现原理分析

    千次阅读 2018-08-28 11:31:15
    就是程序运行时,父类指针可以根据具体指向的子类对象,来执行不同的函数,表现为多态。   二、C++ 多态实现原理 1. 实现原理 当类中存在虚函数时,编译器会在类中自动生成一个虚函数表 虚函数表是一个存储...
  • 多态实现

    2016-04-11 20:34:20
    1. 消息  消息在C++编程中指的是对类的成员函数的调用。... 分为:重载多态,强制多态,参数多态和包含多态。  前两种称为专用多态。  1)普通函数的重载和类的成员函数的重载,都属于重载多态。  
  • 多态实现原理

    万次阅读 2017-10-20 18:09:05
    多态的使用大家应该都比较了解,但是多态实现原理就有点抽象了,查了很多很多资料,连续几天断断续续的看,有时候看着看着就走神了。毕竟太抽象,哈哈~ 不过依然硬着头皮看下来了(也不知道看了多少遍),并且将...
  • C++多态实现

    2017-08-14 10:48:32
    C++多态的分类C++多态...静态多态一般通过函数重载实现,即函数名相同,根据参数的类型不同实现多态。int add(int x,int y) { return x+y; }float add(float x,float y) { return x+y; }另外一种方法就是C++的模版
  • Java 多态实现原理

    2021-06-17 07:07:37
    一个对象变量可以指示多种实际类型的现象称为多态 允许不同类的对象对同一消息做出响应。方法的重载、类的覆盖正体现了多态。 1、多态的机制 1.1 本质上多态分两种 1、编译时多态(又称静态多态) 2、运行时多态(又...
  • go中多态实现

    千次阅读 2018-09-25 19:19:38
    Golang中的多态可以通过接口来实现。 定义接口的所有方法的任何类型都表示隐式实现该接口。类型接口的变量可以保存实现该接口的任何值。接口的这个属性用于实现GO的多态性。 具体直接上代码: package main ...
  • C++多态实现及原理详细解析

    千次阅读 多人点赞 2016-07-10 17:19:03
    C++多态实现及原理详细解析
  • 虚拟机中多态实现方式 多态的表现形式分为重载和重写。 重载是指同一类中的同名方法不同参数列表的表现形式,是一种静态的多态。 重写是指具有继承关系的多个类中子类对父类方法的重写,是一种动态的多态。 ...
  • Java多态的实现机制是父类或接口定义的引用变量可以指向子类或实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实现对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用...
  • Java面向对象的三大特征——封装、继承和多态多态比较难以理解,也通常被考察,所以掌握多态及其实现机制是尤为重要的。本篇主要总结了多态的定义,编译时多态和运行时多态,方法重载和重写的区别、关于多态的例子...
  • C/C++多态及其实现原理 多态的介绍 多态含义为一个事物有多种形态。在C ++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。一般来说多态分为两种: 静态...
  • Java 多态实现机制

    2019-11-27 18:51:08
    是父类或接口定义的引用变量可以指向子类或实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实现对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的...
  • 多态实现机制

    2018-06-15 16:36:26
    在Java运行时多态性:继承和接口的实现一文中,我们曾详细介绍了Java实现运行时多态性的动态方法调度;今天我们再次深入Java核心,一起学习Java中多态性的实现。“polymorphism(多态)”一词来自希腊语,意为“多种...
  • C++中多态实现

    2013-03-24 20:09:51
    其中,封装性使得代码模块化,继承性完成代码的复用,多态实现接口的重用。  在C++中,多态性是指具有不同功能的函数用同一个函数名。即用同一个函数名,调用不同内容的函数。下面主要从多态实现分析C++中的多态...
  • C++ 多态实现

    2016-10-12 17:00:13
    实现多态,有两种方式:覆盖和重载。 覆盖:子类重新定义父类的虚函数的做法。 重载:允许存在多个同名函数,而这些函数的参数列表不同(参数个数不同,参数类型不同等)。在面向对象编程中我们重点看下覆盖的实现吧...
  • 多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运。...1.用抽象类实现多态 我们先定义一个水果Fruit抽象类
  • 多态和C++多态实现(汇总) 多态概念: 多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父...
  • 多态实现的角度分为:静态多态和动态多态 静态多态也叫做编译时多态 动态多态也叫做运行时多态 函数重载是多态的一种实现形式: 重载分为: 1.相同函数不同参数和类型的重载 2.不同类,相同函数重载 第二...
  • Java技术——多态实现原理

    万次阅读 多人点赞 2016-08-12 15:19:12
    多态在Java技术里有很重要的地位,在面试中也会经常被问到。 但是多态太抽象了,查了很多很多资料,连续几天断断续续的看,有时候看着看着就走神了。毕竟太抽象,哈哈~ 不过依然硬着头皮看下来了(也不知道看了多少...
  • Java多态实现机制

    千次阅读 2017-05-02 07:34:28
    Java中多态性的实现 什么是多态 1. 面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最...3. 实现多态的技术称为:动态绑定(dynamic binding

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 136,998
精华内容 54,799
关键字:

多态的具体实现