Java中dynamic和静态多态性有什么区别?
任何人都可以提供一个简单的例子,解释在Java中的dynamic和静态多态性之间的区别?
多态性
1.静态绑定/编译时绑定/早期绑定/方法重载(在同一个类中)
2.dynamic绑定/运行时绑定/后期绑定/方法覆盖(在不同的类中)
重载例子:
class Calculation { void sum(int a,int b){System.out.println(a+b);} void sum(int a,int b,int c){System.out.println(a+b+c);} public static void main(String args[]) { Calculation obj=new Calculation(); obj.sum(10,10,10); // 30 obj.sum(20,20); //40 } }
压倒一切的例子:
class Animal { public void move(){ System.out.println("Animals can move"); } } class Dog extends Animal { public void move() { System.out.println("Dogs can walk and run"); } } public class TestDog { public static void main(String args[]) { Animal a = new Animal(); // Animal reference and object Animal b = new Dog(); // Animal reference but Dog object a.move();//output: Animals can move b.move();//output:Dogs can walk and run } }
-
方法重载将是静态多态的一个例子
-
而重写将是一个dynamic多态的例子。
因为在重载的情况下,在编译时编译器知道链接到调用的方法。 然而,在运行时确定dynamic多态性
dynamic(运行时)多态是运行时存在的多态。 在这里,Java编译器不知道在编译时调用哪个方法。 只有JVM决定在运行时调用哪个方法。 方法重载和使用实例方法重写方法是dynamic多态的例子。
例如,
-
考虑一个序列化和反序列化不同types文档的应用程序。
-
我们可以将“Document”作为基类,并从中派生出不同的文档types类。 如XMLDocument,WordDocument等
-
Document类将'Serialize()'和'De-serialize()'方法定义为虚拟的,每个派生类将根据文档的实际内容以自己的方式实现这些方法。
-
当需要序列化/反序列化不同types的文档时,文档对象将被“文档”类引用(或指针)引用,并且当调用“Serialize()”或“De-serialize()在它上面,调用适当版本的虚拟方法。
静态(编译时)多态是编译时显示的多态。 在这里,Java编译器知道哪个方法被调用。 方法重载和方法重载使用静态方法; 方法重载使用私有或最终方法是静态多态的例子
例如,
-
员工对象可能有两个print()方法,一个不带参数,一个带前缀string,与员工数据一起显示。
-
给定这些接口,当没有任何参数调用print()方法时,编译器会查看函数参数,知道哪个函数被调用,并相应地生成目标代码。
有关更多详细信息,请阅读“什么是多态性”(Google it)。
多态性:多态性是一个对象采取多种forms的能力。 在使用父类引用来引用子类对象时,在OOP中最常见的使用多态。
dynamic绑定/运行时多态性:
运行时多态也被称为方法覆盖。 在此机制中,在运行时解决了对重写函数的调用。
public class DynamicBindingTest { public static void main(String args[]) { Vehicle vehicle = new Car(); //here Type is vehicle but object will be Car vehicle.start(); //Car's start called because start() is overridden method } } class Vehicle { public void start() { System.out.println("Inside start method of Vehicle"); } } class Car extends Vehicle { @Override public void start() { System.out.println("Inside start method of Car"); } }
输出:
车内启动方法
静态绑定/编译时多态:
哪个方法被调用只在编译时决定。
public class StaticBindingTest { public static void main(String args[]) { Collection c = new HashSet(); StaticBindingTest et = new StaticBindingTest(); et.sort(c); } //overloaded method takes Collection argument public Collection sort(Collection c){ System.out.println("Inside Collection sort method"); return c; } //another overloaded method which takes HashSet argument which is sub class public Collection sort(HashSet hs){ System.out.println("Inside HashSet sort method"); return hs; } }
输出:内部收集sorting方法
方法重载是compile time
/ static polymorphism
一个例子,因为方法调用和方法定义之间的方法绑定发生在编译时,并且取决于类的引用(在编译时创build的引用并进入堆栈)。
方法重写是run time
/ dynamic polymorphism
一个例子,因为方法调用和方法定义之间的方法绑定在运行时发生,并且依赖于类的对象(在运行时创build的对象并进入堆)。
简单来说 :
静态多态性 :相同的方法名称在同一类 (不同的签名)中被不同types或数量的参数重载 。 目标方法调用在编译时parsing。
dynamic多态 :在不同的类中,相同的方法被相同的签名覆盖 。 调用方法的对象的types在编译时是未知的,但是将在运行时决定。
一般来说,重载不会被视为多态。
从java教程页面 :
类的子类可以定义自己的唯一行为,但是可以共享父类的一些相同的function
绑定是指方法调用和方法定义之间的联系。
这张照片清楚地表明了什么是绑定。
在此图片中,“a1.methodOne()”调用绑定到相应的methodOne()定义,“a1.methodTwo()”调用绑定到相应的methodTwo()定义。
对于每个方法调用都应该有适当的方法定义。 这在java中是一个规则。 如果编译器没有为每个方法调用看到正确的方法定义,则会引发错误。
现在,来到java中的静态绑定和dynamic绑定。
Java中的静态绑定:
静态绑定是编译期间发生的绑定。 它也被称为早期绑定,因为绑定发生在程序实际运行之前
。
静态绑定可以如下图所示。
在这幅图中,'a1'是指向类A的对象的typesA的引用variables。'a2'也是类A的types的引用variables,但是指向类B的对象。
编译期间,编译器在绑定时不检查特定引用variables指向的对象的types。 它只是检查一个方法被调用的引用variables的types,并检查是否存在该types的方法定义。
例如,对于上图中的“a1.method()”方法调用,编译器检查A类中method()是否存在方法定义。因为'a1'是Atypes。 同样,对于“a2.method()”方法调用,它将检查A类中method()是否存在方法定义。因为'a2'也是Atypes。 它不检查“a1”和“a2”指向哪个对象。 这种types的绑定称为静态绑定。
Java中的dynamic绑定:
dynamic绑定是运行时发生的绑定。 它也被称为后期绑定,因为绑定发生在程序实际运行时。
运行期间,实际对象用于绑定。 例如,对于上图中的“a1.method()”调用,将调用“a1”指向的实际对象的方法()。 对于“a2.method()”调用,将调用“a2”指向的实际对象的method()。 这种绑定称为dynamic绑定。
上面的例子的dynamic绑定可以像下面那样演示。
参考static-binding-and-dynamic-binding-in-java
方法重载称为静态多态 ,也称为编译时多态或静态绑定,因为重载的方法调用在编译时根据参数列表和我们调用方法的引用在编译时得到解决。
方法重写被称为dynamic多态或简单多态或运行时方法调度或dynamic绑定,因为重写的方法调用在运行时得到解决。
为了理解为什么这样,我们举一个Mammal
和Human
的例子
class Mammal { public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); } } class Human extends Mammal { @Override public void speak() { System.out.println("Hello"); } public void speak(String language) { if (language.equals("Hindi")) System.out.println("Namaste"); else System.out.println("Hello"); } }
我在下面的代码行中包含了输出以及字节码
Mammal anyMammal = new Mammal(); anyMammal.speak(); // Output - ohlllalalalalalaoaoaoa // 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V Mammal humanMammal = new Human(); humanMammal.speak(); // Output - Hello // 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V Human human = new Human(); human.speak(); // Output - Hello // 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V human.speak("Hindi"); // Output - Namaste // 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
通过查看上面的代码,我们可以看到,humanMammal.speak(),human.speak()和human.speak(“Hindi”)的字节码是完全不同的,因为编译器能够根据参数列表区分它们和类参考。 这就是为什么方法重载被称为静态多态性的原因 。
但是anyMammal.speak()和humanMammal.speak()的字节码是相同的,因为根据编译器,两个方法都在Mammal引用上被调用,但是这两个方法调用的输出是不同的,因为在运行时JVM知道引用正在持有的对象和JVM调用在对象上的方法,这就是为什么方法覆盖被称为dynamic多态性。
所以从上面的代码和字节码可以看出,在编译阶段,调用方法是从引用types考虑的。 但在执行时,方法将从引用所持有的对象中调用。
如果你想知道更多关于这个,你可以阅读更多的JVM如何处理方法重载和覆盖内部 。
方法重载是一个编译时多态,让我们以一个例子来理解这个概念。
class Person //person.java file { public static void main ( String[] args ) { Eat e = new Eat(); e.eat(noodle); //line 6 } void eat (Noodles n) //Noodles is a object line 8 { } void eat ( Pizza p) //Pizza is a object { } }
在这个例子中,Person有一个吃法,表示他可以吃披萨或面条。 当我们编译这个Person.java时,方法eat被重载,编译器用第8行指定的方法定义来parsing方法调用“e.eat(noodles)[在第6行],这是以面条为参数的方法整个过程由Compiler完成,因此它是Compile time Polymorphism,将方法调用replace为方法定义的过程称为绑定,在这种情况下,由编译器完成,所以称为早期绑定。
静态多态性:决定在编译期间确定要完成的方法。 方法重载可以是一个例子。
dynamic多态性:select执行哪种方法的决定是在运行期间设置的。 方法重写可能是一个例子。