Java 中动态多态性和静态多态性的区别是什么?

有人能提供一个简单的例子来解释 Java 中 充满活力静电干扰多态性之间的区别吗?

258486 次浏览
  • 方法重载就是静态多态性的一个例子

  • 而重写则是动态多态性的一个例子。

    因为在重载的情况下,编译器在编译时知道链接到调用的方法。但是,它是在运行时为动态多态性确定的

多态性

1. 静态绑定/编译时绑定/早期绑定/方法重载

2. 动态绑定/运行时绑定/后期绑定/方法重写

过载例子:

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
}
}

动态(运行时)多态性 是运行时存在的多态性。在这里,Java 编译器不理解在编译时调用哪个方法。只有 JVM 决定在运行时调用哪个方法。使用实例方法的方法重载和方法重写是动态多态性的例子。

比如说,

  • 考虑一个序列化和反序列化不同的应用程序 文件类别

  • 我们可以将“ Document”作为基类和不同的文档类型 类,例如 XMLDocument、 WordDocument 等

  • 文档类将定义‘ Serialize ()’和‘ De-Series ()’ 方法,并且每个派生类将实现这些 方法以自己的方式根据文件的实际内容

  • 当不同类型的文件需要 序列化/反序列化后,文档对象将由 ’Document’类引用(或指针)以及当’ 对其调用 Serialize ()或‘ De-Series alize ()’方法, 调用虚方法的适当版本。

静态(编译时)多态性 是在编译时显示的多态性。在这里,Java 编译器知道调用哪个方法。使用静态方法的方法重载和方法重载; 使用私有或最终方法的方法重载是静态多态性的例子

比如说,

  • 一个雇员对象可以有两个 print ()方法,一个采用 no 参数和一个前缀字符串一起显示 员工资料

  • 如果给定这些接口,则在调用 print ()方法时不使用任何 参数,编译器,查看函数参数知道哪个函数应该被调用,并生成目标代码 因此

更多细节请阅读“什么是多态性”(谷歌它)。

方法重载 是编译时/静态多态性的一个例子,因为方法调用和方法定义之间的方法绑定发生在编译时,它依赖于类的引用(在编译时创建的引用进入堆栈)。

方法重写 是运行时/动态多态性的一个例子,因为方法调用和方法定义之间的方法绑定发生在运行时,它依赖于类的对象(在运行时创建并进入堆的对象)。

多态性: 多态性是一个对象具有多种形式的能力。在 OOP 中,多态性的最常见用法是使用父类引用来引用子类对象。

动态绑定/运行时多态性:

运行时多态性也称为方法重写。在此机制中,通过该机制在运行时解析对重写函数的调用。

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;
}


}

产出: 内部集合排序方法

简单地说:

静态多态性 : 相同的方法名称是 超载,在 同一类别中具有不同的类型或参数数目(不同的签名)。目标方法调用在编译时解析。

动态多态性 : 同样的方法是 被覆盖,在 不同类别中具有相同的签名。正在调用其方法的对象的类型在编译时不知道,但将在运行时确定。

通常,重载不会被认为是多态性。

来自 java 教程 呼叫:

类的子类可以定义它们自己独特的行为,并且共享父类的一些相同功能

方法重载 被称为 静态多态性,也被称为 编译时间多态性静态绑定,因为编译器在编译时根据参数列表和调用方法的引用解析重载方法调用。

因为重写的方法调用在运行时得到解析,所以 方法重写被称为 动态多态性或简单的 多态性运行时方法调度动态绑定

为了理解为什么会这样,让我们以 MammalHuman类为例

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

通过查看上面的代码,我们可以看到 human Mammal.speak () ,human.speak ()和 human.speak (“ Hindi”)的字节码完全不同,因为编译器能够根据参数列表和类引用区分它们。这就是为什么 方法超载被称为 静态多态性

但是,anyMammal.speak ()和 human Mammal.speak ()的字节码是相同的,因为根据编译器,两个方法都是在 Mammal 引用上调用的,但是两个方法调用的输出是不同的,因为在运行时,JVM 知道引用持有的对象是什么,JVM 调用对象上的方法,这就是为什么方法覆盖被称为动态多态性。

因此,从上面的代码和字节码可以清楚地看出,在编译阶段调用方法是从引用类型考虑的。但是在执行时,将从引用所持有的对象调用。

如果你想知道更多关于这方面的信息,你可以在 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 有一个 eat 方法,表示他可以吃披萨或面条。当我们编译 Person.java 时,方法 eat 被重载了编译器解析了方法调用“ e.eat (面条)[在第6行]和第8行中指定的方法定义,这是一个以面条为参数的方法,整个过程是由编译器完成的,所以它是编译时多态性。 用方法定义替换方法调用的过程称为绑定,在本例中,它由编译器完成,因此称为早期绑定。

绑定指的是方法调用和方法定义之间的链接。

这张图清楚地显示了什么是约束力。

binding

在这幅图中,“ a1.methodOne ()”调用绑定到相应的 methodOne ()定义,而“ a1.methodTwo ()”调用绑定到相应的 methodTwo ()定义。

对于每个方法调用,都应该有适当的方法定义。这是 Java 中的一条规则。如果编译器没有看到每个方法调用的正确方法定义,则抛出错误。

现在,让我们来看看 Java 中的静态绑定和动态绑定。

Java 中的静态绑定:

静态绑定是在编译期间发生的绑定 也称为早期绑定,因为绑定发生在程序之前 真的能跑

.

静态绑定可以如下图所示。

enter image description here

在这幅图中,‘ a1’是指向 A 类对象的 A 类引用变量。‘ a2’也是指向 B 类对象的 A 类引用变量。

在编译期间,当绑定时,编译器不检查特定引用变量所指向的对象的类型。它只检查调用方法的引用变量的类型,并检查该类型中是否存在该方法的方法定义。

例如,对于上图中的“ a1.method ()”方法调用,编译器检查类 A 中是否存在方法()的方法定义,因为‘ a1’是类 A 类型。类似地,对于“ a2.method ()”方法调用,它检查类 A 中是否存在方法()的方法定义,因为‘ a2’也是类 A 类型。它不检查指向哪个对象,‘ a1’和‘ a2’。这种类型的绑定称为静态绑定。

Java 中的动态绑定:

动态绑定是在运行时发生的绑定 称为后期绑定,因为绑定发生时程序实际上是 跑步。

在运行时,实际对象用于绑定。例如,对于上图中的“ a1.method ()”调用,将调用“ a1”所指向的实际对象的 method ()。对于“ a2.method ()”调用,将调用“ a2”所指向的实际对象的 method ()。这种类型的绑定称为动态绑定。

上面示例的动态绑定如下所示。

enter image description here

参考资料 静态绑定和动态绑定

静态多态性: 是在编译期间确定决定要完成哪个方法的地方。方法重载可以是这样的一个示例。

动态多态性: 是在运行时设置选择执行哪个方法的决策。方法重写可以是这样的一个示例。

编译时多态性(静态绑定/早期绑定) : 在静态多态性中,如果我们在代码中调用一个方法,那么实际上该方法的哪个定义将在编译时被解析。

(或)

在编译时,Java 通过检查方法签名知道要调用哪个方法。因此,这被称为编译时多态性或静态绑定。

动态多态性(后期绑定/运行时多态性) : 在运行时,Java 等到运行时才确定引用实际指向的是哪个对象。方法解析是在运行时进行的,因为我们称之为运行时多态性。

多态性是指对象在同一个触发器上表现不同的能力。

静态多态性(编译时多态性)

  • 静态多态决定在编译期间执行哪个方法 时间。
  • 方法重载是静态多态性的一个例子 要求发生静态多态性。
  • 通过静态绑定实现静态多态性。
  • 静态多态性发生在同一个类中。
  • 静态多态性不需要对象分配。
  • 静态多态性不涉及继承。

动态多态(运行时多态)

  • 动态多态决定在运行时执行哪个方法。
  • 方法重写是动态多态性的一个例子 要求发生动态多态性。
  • 通过动态绑定实现动态多态性。
  • 动态多态性发生在不同的类之间。
  • 当一个子类对象被分配给超类时,它是必需的 动态多态性对象。
  • 动态多态性所涉及的继承。

考虑下面的代码:

public class X
{
public void methodA() // Base class method
{
System.out.println ("hello, I'm methodA of class X");
}
}


public class Y extends X
{
public void methodA() // Derived Class method
{
System.out.println ("hello, I'm methodA of class Y");
}
}
public class Z
{
public static void main (String args []) {


//this takes input from the user during runtime
System.out.println("Enter x or y");
Scanner scanner = new Scanner(System.in);
String value= scanner.nextLine();


X obj1 = null;
if(value.equals("x"))
obj1 = new X(); // Reference and object X
else if(value.equals("y"))
obj2 = new Y(); // X reference but Y object
else
System.out.println("Invalid param value");


obj1.methodA();
}
}

现在,看看这些代码,您永远不会知道将执行 methodA ()的哪个实现,因为它取决于用户在运行时给出的值。因此,只有在运行时才决定调用哪个方法。因此,运行时多态性。

继 Naresh 的回答之后,动态多态性在 Java 中只是“动态”的,因为虚拟机的存在,以及它在运行时解释代码的能力,而不是本机运行的代码。

在 C + + 中,如果要使用 gcc 将其编译成本地二进制文件,那么必须在编译时解析它; 然而,虚拟表中的运行时跳转和 thunk 仍然被称为“查找”或“动态”。如果 C 继承了 B,并且你声明了 B* b = new C(); b->method1();,那么编译器就会将 b 解析为指向 C 内部的一个 B 对象(对于一个简单的类来说,C 和 C 内部的 B 对象将从相同的内存地址开始,因此不需要做任何事情; 它将指向它们都使用的 vptr)。如果 C 继承了 B 和 A,那么 method1的 C 条目中的 A 对象的虚函数表将会有一个 thunk,这个 thunk 将会偏移到封装 C 对象的开始的指针,然后将它传递到 C 已经覆盖的文本段中的真正的 A: : method1()。对于 C* c = new C(); c->method1(),c 将已经指向外部的 C 对象,并且指针将在文本段中传递给 C: : method1()。参考: http://www.programmersought.com/article/2572545946/

在 java 中,对于 B b = new C(); b.method1();,虚拟机能够动态检查与 b 配对的对象的类型,并能够传递正确的指针和调用正确的方法。虚拟机的额外步骤消除了对虚函数表或在编译时解析的类型的需要,即使在编译时可以知道它。这只是一种不同的方法,当涉及到虚拟机并且代码只被编译成字节码时,这种方法是有意义的。