如何做网络推广人员,长沙官网seo服务,平面广告设计主题,wordpress关联adsense理解方法调用 首先什么是隐式参数 ---隐式参数是调用该方法的对象本身。 接下来方法的名称和参数列表被称为方法的签名#xff08;signature#xff09;。在Java中#xff0c;方法的签名由方法的名称和参数列表组成#xff0c;用于唯一标识一个方法。返回类型不是签名的… 理解方法调用 首先什么是隐式参数 ---隐式参数是调用该方法的对象本身。 接下来方法的名称和参数列表被称为方法的签名signature。在Java中方法的签名由方法的名称和参数列表组成用于唯一标识一个方法。返回类型不是签名的一部分。不过在覆盖一个方法时需要保证返回类型的兼容性---允许子类将覆盖方法的返回类型改为原返回类型的子类型。具体的例子详细 协变covariant是一种类型关系 ---子类型的某些属性或方法可以比父类型更具体特化 协变covariance是指子类型的返回类型可以是父类型的子类型而逆变contravariance是指子类型的返回类型可以是父类型的超类型。注意 1.具体说明是怎么实现_协变2.具体说明是怎么实现_逆变 静态绑定Static Binding和动态绑定Dynamic Binding1. **静态绑定Static Binding**编译时期确定的方法或函数【可共享的】静态绑定适用于以下情况举例代码静态常量和静态绑定是两个不同的概念。在Java中static 和 final 都是关键字用于定义变量的特性。它们有不同的含义和用途。需要注意的是尝试修改 final 和 static final 变量的值会导致编译错误因为它们被声明为不可修改的常量。总结 - Math类中的定义 2. **动态绑定Dynamic Binding**动态绑定适用于以下情况代码举例总结 假设要针对x.f(args),其中 隐式参数 x 是一个声明为类 C 对象的隐式参数。以下是详细描述方法调用的过程首先
编译器会检查对象在声明过程中具备的声明类型以及所调用的方法名 f。由于可能存在重名的情况可能存在多个名为 f 但参数类型不同的方法。例如可能同时存在 f(int) 和 f(String) 这样的方法。编译器会逐一列举在类 C 中所有名为 f 的方法还会查找超类中所有可访问的名为 f 的方法 [ 注意超类的私有方法除外 ]。这是编译器获得了所有可能被调用的备用的方法。【在此形成方法表1或候选方法列表接下来会进一步筛选出最终应该调用的方法。】
什么是隐式参数 —隐式参数是调用该方法的对象本身。
在Java中隐式参数是指在方法调用过程中自动传递的参数而不需要显式地在方法调用中提供。隐式参数通常是通过对象的方法调用来使用的。 示例代码
public class Example {private int value;public Example(int value) {this.value value;}public void printValue() {System.out.println(value);}public static void main(String[] args) {Example example new Example(10);example.printValue();}
}在上面的代码中printValue() 方法是一个实例方法它没有显式的参数。然而它可以访问隐式参数 value该参数是通过对象 example 的方法调用来传递的。在 example.printValue() 这行代码中example 就是隐式参数。 与隐式参数相对应的是显示参数。显示参数是在方法调用时显式地提供的参数。例如以下是一个使用显示参数的示例
public class Example {public static void printValue(int value) {System.out.println(value);}public static void main(String[] args) {int value 10;printValue(value);}
}在上面的代码中printValue() 方法接受一个显式参数 value。在 printValue(value) 这行代码中value 就是显示参数。 总结一下隐式参数是在方法调用中自动传递的参数而不需要显式提供通常是通过对象的方法调用来使用的。相比之下显示参数是在方法调用时显式地提供的参数。
接下来
编译器会分析方法调用中提供的参数类型。它会匹配这些参数类型与所有候选的方法参数类型寻找一个与提供参数类型完全匹配的方法。这一步骤被称为重载解析overloading resolution。 假设我们调用 x.f(“Hello_world_work0806”)编译器会选择参数类型为 String 的 f 方法而不是参数类型为 int 的。 需要注意的是由于允许进行类型转换例如int 可以转换成 doubleManager 可以转换成 Employee 等这个过程可能会变得相当复杂。 【如果编译器无法找到与提供参数类型完全匹配的方法或者在进行类型转换后有多个方法与之匹配编译器将会报告错误。】到这一步编译器已经成功 确定了需要调用的方法名和参数类型.这意味着在方法调用的过程中编译器通过上述步骤精确地确定了应该调用的方法。这个过程确保了方法调用的准确性和可靠性。
public class Mains {public static void main(String[] args) {printNumber(5); // 输出 Printing int: 5printNumber(3.14); // 输出 Printing double: 3.14}static void printNumber(int num) {System.out.println(Printing int: num);}static void printNumber(double num) {System.out.println(Printing double: num);}
}如果存在多个名为f的方法它们的参数类型都可以接受你提供的参数那么编译器会选择参数类型与你提供的参数类型最接近的那个方法。例如如果你调用f(5)并且存在一个参数类型为int的f方法和一个参数类型为double的f方法那么编译器会选择参数类型为int的f方法因为int类型比double类型更接近你提供的参数类型。 这个过程可能会变得复杂因为Java允许进行类型转换。例如int可以转换成doubleManager可以转换成Employee等。所以如果你提供的参数类型和任何一个候选方法的参数类型都不完全匹配编译器会尝试进行类型转换以找到一个可以接受你提供的参数的方法。 方法的名称和参数列表被称为方法的签名signature。
方法的参数列表包括参数的数量、类型和顺序。方法的签名不包括方法的返回类型和访问修饰符。在Java中方法的签名由方法的名称和参数列表组成用于唯一标识一个方法。
public void calculateSum(int a, int b)在上面的示例中方法的名称是calculateSum参数列表是int a和int b因此方法的签名是calculateSum(int, int)。 f(int) 和 f(String) 是两个有着相同名字但参数不同的方法则他们的签名是不同的。 在子类里如果你定义了一个和超类有着相同签名的方法那这个子类方法会“覆盖”override超类中同签名的方法。 返回类型不是签名的一部分。不过在覆盖一个方法时需要保证返回类型的兼容性—允许子类将覆盖方法的返回类型改为原返回类型的子类型。
允许子类在覆盖重写父类方法时将方法的返回类型修改为父类方法返回类型的子类型。换句话说子类可以返回更具体的子类型而不必仅仅返回与父类方法完全相同的类型。在Java中方法的签名由方法的名称和参数列表组成而不包括方法的返回类型。这意味着如果两个方法具有相同的名称和参数列表但返回类型不同它们的方法签名是相同的。这种情况下编译器无法区分这两个方法。
然而当涉及到子类覆盖重写父类的方法时确实需要考虑返回类型的兼容性。尽管方法的签名相同但返回类型必须满足协变性的原则即子类方法的返回类型必须是父类方法返回类型的子类型。这样做是为了确保子类对象可以被正确地视为父类对象并且在使用父类方法时能够处理返回值2。如果不满足这个条件编译器会报错因为这可能会导致类型不匹配的错误。
例如假设有一个父类A和一个子类B它们分别定义了一个同名方法foo()父类方法的返回类型是A子类方法的返回类型是B。子类B可以重写父类A的方法并将返回类型改为B(1)因为B是A的子类所以B对象可以被视为A对象。(2)那么当我们通过子类对象调用foo()方法时返回的是B类型的对象。
子类型返回的是父类型的子类型。也就是说如果在子类中重写父类的方法并将返回类型改为父类返回类型的子类型那么子类方法的返回值将是子类型的对象。class A {public A foo() {return new A();}
}
class B extends A {Overridepublic B foo() {return new B();}
}这样当我们通过父类引用调用子类对象的foo()方法时返回的是子类对象B。这种方式称为协变【后面有解释】返回类型它允许我们在子类中返回更具体的类型提供了更好的灵活性和可读性。 因为 B 是 A 的子类所以 B 继承了 A 的方法。这包括方法名和参数列表。 当你在子类 B 中重写父类 A 的方法时你可以将返回类型更改为 B。这是合法的因为 B 是 A 的子类所以 B 的对象可以被视为 A 的对象。 当你创建一个子类 B 的对象并调用重写后的方法时实际上你可以将返回的 B 对象视为 A 对象的一种扩展。这是多态性的体现你可以通过父类引用调用子类方法而不必了解具体的子类类型。 具体的例子
class Animal {public Animal reproduce() {return new Animal();}
}
class Dog extends Animal {Overridepublic Dog reproduce() {return new Dog();}
}在上面的代码中有一个父类Animal和一个子类Dog。父类Animal定义了一个方法reproduce()返回类型为Animal。子类Dog重写了父类Animal的方法并将返回类型改为Dog。 当我们使用子类Dog的对象调用reproduce()方法时
Dog dog new Dog(); // 创建一个新的 Dog 对象赋值给 dog 变量
Dog newDog dog.reproduce(); // 调用 dog 对象的 reproduce() 方法返回一个新的 Dog 对象赋值给 newDog 变量通过子类重写父类方法我们可以更具体地表达子类对象的行为。在这个例子中子类Dog重写了父类Animal的reproduce()方法并确保返回的是Dog类型的对象。这样在使用子类Dog对象调用reproduce()方法时我们可以直接获得一个新的Dog对象。
详细
当一个父类有一个方法返回某种类型子类可以覆盖这个方法并返回该类型的子类型。
假设有一个父类 Animal 和一个子类 Dog并且在 Animal 类中有一个方法 makeSound()返回类型是 String表示动物发出的声音。在子类 Dog 中可以覆盖 makeSound() 方法并返回 Bark 类的实例表示狗的吠声。
class Animal {public String makeSound() {return Some generic animal sound;}
}class Dog extends Animal {public Bark makeSound() {return new Bark();}
}class Bark {public String sound() {return Woof woof!;}
}在这个例子中子类 Dog 覆盖了父类 Animal 的 makeSound() 方法并且返回类型从 String 改变为了 Bark 类的实例这是父类方法返回类型的子类型。这样你可以调用 Dog 类的 makeSound() 方法得到一个更具体的结果而不仅仅是通用的动物声音。
“协变”covariant是一种类型关系 —子类型的某些属性或方法可以比父类型更具体特化
“协变”covariant是一种类型关系指的是子类型与父类型之间的关系在这种关系下子类型的某些属性或方法可以比父类型更具体特化。在你提到的情况中协变的返回类型指的是子类方法的返回类型可以是原始方法返回类型的子类型。这意味着子类可以返回更具体的类型而不违反方法签名的规则。
协变返回类型主要体现在子类覆盖override了父类方法并改变了方法的返回类型使其返回比父类更具体的类型。具体来说以下部分展示了协变返回类型的使用
class Publication {private String title;public Publication(String title) {this.title title;}public String getTitle() {return title;}
}class Book extends Publication {private String author;public Book(String title, String author) {super(title);this.author author;}public String getAuthor() {return author;}
}class Magazine extends Publication {private int issueNumber;public Magazine(String title, int issueNumber) {super(title);this.issueNumber issueNumber;}public int getIssueNumber() {return issueNumber;}
}class Library {public Publication getRecommendation() {// 返回一个 Publication 对象作为推荐读物return new Publication(Generic Recommendation);}
}class BookLibrary extends Library {Overridepublic Book getRecommendation() {// 返回一个 Book 对象作为推荐读物return new Book(The Catcher in the Rye, J.D. Salinger);}
}class MagazineLibrary extends Library {Overridepublic Magazine getRecommendation() {// 返回一个 Magazine 对象作为推荐读物return new Magazine(National Geographic, 123);}
}
在这个例子中Library 类的 getRecommendation方法返回类型是 Publication而BookLibrary和 MagazineLibrary 分别覆盖了这个方法并将返回类型改为更具体的 Book 和 Magazine。这种方式允许子类方法返回比父类更具体的类型而仍然保持方法签名的一致性。
所以协变返回类型在这里的体现就是子类方法覆盖了父类方法并返回更具体的类型这符合子类型与父类型之间的关系在这种关系下子类型的某些属性或方法可以比父类型更具体特化的解释。
协变covariance是指子类型的返回类型可以是父类型的子类型而逆变contravariance是指子类型的返回类型可以是父类型的超类型。
逆变表示一个泛型类型的参数类型在继承关系中变得更加具体即变窄。逆变通常在方法参数中使用允许传递更通用的类型。
协变表示一个泛型类型的返回类型在继承关系中变得更加通用即变宽。协变通常在方法返回值中使用允许返回更具体的类型。协变如果类型B是类型A的子类型那么在某些上下文中我们可以使用类型B的对象替代类型A的对象。这通常应用于方法的返回类型。例如如果一个方法返回一个Animal类型的对象那么它也可以返回一个Dog类型的对象假设Dog是Animal的子类。 逆变如果类型B是类型A的子类型那么在某些上下文中我们可以使用类型A的对象替代类型B的对象。这通常应用于方法的参数类型。例如如果一个方法接受一个Dog类型的对象作为参数那么它也可以接受一个Animal类型的对象假设Dog是Animal的子类。 协变和逆变的主要目的是提供更大的灵活性和类型安全。它们允许我们在保持类型安全的同时编写更通用和可重用的代码。例如如果我们有一个处理Animal对象的方法通过逆变我们可以将这个方法应用于Dog对象而无需编写专门处理Dog对象的方法。
class Animal {public void eat() {System.out.println(Animal eats);}
}class Dog extends Animal {Overridepublic void eat() {System.out.println(Dog eats);}
}class AnimalHelper {// 协变返回类型是协变的public Dog getAnimal() {return new Dog();}// 逆变参数类型是逆变的public void feedAnimal(Animal animal) {animal.eat();}
}public class Mains {public static void main(String[] args) {AnimalHelper helper new AnimalHelper();// 协变我们可以将Dog赋值给AnimalAnimal animal helper.getAnimal();// 逆变我们可以将Dog传递给接受Animal的方法helper.feedAnimal(new Dog());}
}在这个例子中AnimalHelper 类中的两个方法展示了协变和逆变的概念
getAnimal() 方法使用协变返回类型 Dog 是协变的。这意味着你可以将一个返回类型为 Dog 的方法赋值给类型为 Animal 的引用变量。因为 Dog 是 Animal 的子类所以这个协变操作是安全的。
feedAnimal(Animal animal) 方法使用逆变参数类型 Animal 是逆变的。这意味着你可以将一个类型为 Dog 的对象传递给接受 Animal 类型参数的方法。由于 Dog 是 Animal 的子类所以逆变操作也是安全的。
在 main 方法中你展示了如何使用这些协变和逆变的特性
协变你调用 helper.getAnimal() 方法并将返回值赋给一个类型为 Animal 的引用变量 animal。这是因为你可以将 Dog 类型赋值给 Animal 类型利用了返回类型的协变特性。
逆变你调用 helper.feedAnimal(new Dog()) 方法将 Dog 对象传递给接受 Animal 类型参数的方法。这是因为你可以将 Dog 类型传递给接受 Animal 类型参数的方法利用了参数类型的逆变特性。
注意
协变 在协变中我们可以将派生类如 Dog的实例分配给基类如 Animal的引用。这是因为派生类是基类的一个特殊类型具有基类的所有属性和方法但可能还有额外的特性。 协变涉及返回类型。例如在协变中你可以在方法返回类型为 Dog 的情况下将返回值赋给类型为 Animal 的引用变量。
逆变 在逆变中我们可以将基类如 Animal的实例传递给接受派生类如 Dog类型参数的方法。这是因为基类是派生类的通用类型基类对象中包含了派生类对象的通用行为。 逆变涉及方法参数类型。例如在逆变中你可以将类型为 Dog 的参数传递给接受 Animal 类型参数的方法。 区别 在协变中“赋值” 涉及将一个具体类型如 Dog赋值给更通用的类型如 Animal的引用变量。这是由于返回类型的协变特性可以保证派生类对象的特性在基类引用中仍然有效。 在逆变中“传递” 涉及将一个基类类型的对象传递给接受派生类类型参数的方法。这是由于参数类型的逆变特性允许基类对象的通用属性在派生类方法中得到正确的处理。 通过你提供的代码你确实展示了这两种情况的应用。通过 AnimalHelper 类的 getAnimal() 方法你展示了协变因为你可以将 Dog 类型的实例分配给类型为 Animal 的引用变量。通过 feedAnimal(Animal animal) 方法你展示了逆变因为你可以将 Dog 类型的对象传递给接受 Animal 类型参数的方法。这两种特性一起允许你在泛型方法中更灵活地操作不同类型的对象。
1.具体说明是怎么实现_协变
具体来说getAnimal方法的返回类型是Dog但在main方法中我们将这个Dog对象赋值给了一个Animal类型的变量。这是因为Dog是Animal的子类型所以我们可以使用Dog对象替代Animal对象。这就是协变的概念。
class AnimalHelper {// 协变返回类型是协变的public Dog getAnimal() {return new Dog();}
}public class Contravariance {public static void main(String[] args) {AnimalHelper helper new AnimalHelper();// 协变我们可以将Dog赋值给AnimalAnimal animal helper.getAnimal();animal.eat();}
}在这段代码中Dog对象被赋值给了Animal类型的变量这是协变的一个例子。
2.具体说明是怎么实现_逆变
在AnimalHelper类的feedAnimal方法中参数类型是Animal。这意味着你可以传递任何Animal对象或其子类的对象给这个方法。因此你可以将Dog对象传递给这个方法即使它期望的是一个Animal对象。这就是逆变的概念。
在main方法中你创建了一个Dog对象并将其传递给了feedAnimal方法。尽管feedAnimal方法期望的是一个Animal对象但由于Dog是Animal的子类因此你可以将Dog对象传递给它。这就是逆变的一个具体应用。
这段代码的运行结果将是输出Dog eats因为feedAnimal方法调用了传入对象的eat方法而传入的对象是一个Dog对象所以调用的是Dog类中覆写的eat方法。
静态绑定Static Binding和动态绑定Dynamic Binding
总的来说静态绑定在编译时就确定方法调用速度快但不够灵活而动态绑定在运行时根据实际对象类型确定方法调用更加灵活但速度相对较慢。在Java中大部分方法调用使用的是动态绑定因为它能够支持多态性和继承等特性。
1. 静态绑定Static Binding编译时期确定的方法或函数【可共享的】
静态绑定是在**编译时期确定方法或函数的调用**不需要等到运行时期才决定。它适用于private方法、static方法、final方法和构造器的调用。静态绑定static binding是指在编译时期compile time就能确定方法或函数的调用不需要等到运行时期才决定。在静态绑定中方法或函数的调用是根据引用类型也称为编译时类型来决定的。当编译器在编译代码时会根据引用类型确定调用该类型定义的方法或函数。因此静态绑定的方法或函数调用是在编译时期就确定的不会受到运行时期对象的实际类型的影响。
发生在编译时compile time。适用于private、static、final方法以及构造器等情况。在编译时就能确定要调用的方法因为方法的选择是基于引用变量的声明类型。编译器可以在编译阶段直接解析方法调用因此速度较快。
静态绑定适用于以下情况
Private 方法由于private方法在同一类中是不可继承的编译器可以直接确定要调用的方法。 private方法是不能被子类重写的所以调用private方法时编译器可以准确地知道应该调用哪个方法。 Static 方法静态方法属于类而不是实例因此不需要实例化对象就可以调用。编译器可以根据声明的类来确定要调用的静态方法。 static方法是属于类而不是对象的所以调用static方法时编译器可以准确地知道应该调用哪个方法。 Final 方法final修饰的方法不能被子类重写因此编译器可以准确知道要调用的是声明的类中的final方法。 final方法是不能被子类重写的所以调用final方法时编译器可以准确地知道应该调用哪个方法。 构造器在创建对象时编译器根据构造器的参数列表来准确地选择合适的构造器。 构造器是用于创建对象的特殊方法调用构造器时编译器可以准确地知道应该调用哪个构造器。 静态绑定的一个特点是它在编译时就能够解析方法调用因此执行速度较快。但是静态绑定缺乏动态继承和多态性的特性因为它不会考虑对象的实际类型。相比之下动态绑定会在运行时根据实际对象类型来确定方法调用提供了更大的灵活性和多态性。
举例代码
class Animal {public static void printType() {System.out.println(This is an animal.);}
}class Dog extends Animal {public static void printType() {System.out.println(This is a dog.);}
}public class Main {public static void main(String[] args) {Animal animal new Animal();Animal dog new Dog();Dog realUnderDogClassDog new Dog();animal.printType(); // 静态绑定输出This is an animal.dog.printType(); // 静态绑定输出This is an animal.因为静态方法不会被子类重写realUnderDogClassDog.printType();// 静态绑定输出This is a dog.}
}由于printType()方法是静态方法静态方法的调用是根据引用类型来确定的不会受到运行时期对象的实际类型的影响。所以无论animal变量引用的是Animal对象还是dog变量引用的是Dog对象调用printType()方法时都会直接调用Animal类中定义的printType()方法。因此输出结果都是This is an animal.。 这就是静态绑定的特点方法调用在编译时期就已经确定不会根据对象的实际类型来决定。
静态常量和静态绑定是两个不同的概念。
静态常量是指在编译时期就确定并且不能被修改的常量。它通常使用关键字final来声明并且在声明时就必须初始化。静态常量在类加载时被初始化并且可以通过类名直接访问。静态常量是类级别的意味着它在整个类中都是共享的所有对象共享同一个静态常量的值。 静态绑定是指在编译时期就确定方法或函数的调用。它发生在静态方法、静态变量、静态常量的访问以及使用类名访问静态成员时。静态绑定是根据引用类型来决定方法或函数的调用不会受到对象的实际类型的影响。这是因为静态成员和静态方法是与类关联的不依赖于对象的创建。因此无论使用哪个对象引用去调用静态成员或静态方法都会调用到类中定义的静态成员或静态方法。 总结起来静态常量是在编译时期确定并且不能被修改的常量而静态绑定是在编译时期确定方法或函数的调用。静态常量是类级别的可以通过类名直接访问而静态绑定是根据引用类型来决定方法或函数的调用并且不受对象的实际类型的影响。
在Java中static 和 final 都是关键字用于定义变量的特性。它们有不同的含义和用途。
首先我们分别来看看 static、final 和 static final 的含义 static用于表示变量或方法是类级别的不依赖于对象的创建可以通过类名直接访问。但是 static 并不代表不可修改静态变量在程序运行期间是可以被修改的。 final用于表示常量一旦赋值后就不能再修改。可以用于变量、方法、类等地方。 static final将 static 和 final 结合在一起表示一个类级别的不可修改的常量。
现在我们将上述概念应用于代码并逐步思考输出结果
public class StaticFinalExample {public static String staticVariable Static Variable;public final String finalVariable Final Variable;public static final String staticFinalVariable Static Final Variable;public static void main(String[] args) {StaticFinalExample obj1 new StaticFinalExample();// 输出1访问静态变量System.out.println(Static Variable (obj1): obj1.staticVariable);obj1.staticVariable hello; // 修改静态变量的值System.out.println(Static Variable (obj1): obj1.staticVariable);// 输出2访问 final 变量System.out.println(Final Variable (obj1): obj1.finalVariable);// 尝试修改 final 变量的值不会导致编译错误但运行时会报错// obj1.finalVariable Modified Final Variable; // 输出3访问 static final 变量System.out.println(Static Final Variable (obj1): obj1.staticFinalVariable);// 尝试修改 static final 变量的值会导致编译错误// obj1.staticFinalVariable Modified Static Final Variable; // 输出4输出修改后的值System.out.println(Static Variable (obj1): obj1.staticVariable);}
}
需要注意的是尝试修改 final 和 static final 变量的值会导致编译错误因为它们被声明为不可修改的常量。 总结
static 表示类级别的可以通过类名直接访问的变量或方法。final 表示常量一旦赋值后不能再修改。static final 表示类级别的不可修改的常量。static 和 final 可以独立使用也可以结合在一起使用。
- Math类中的定义 /*** The {code double} value that is closer than any other to* ie/i, the base of the natural logarithms.*/public static final double E 2.7182818284590452354;/*** The {code double} value that is closer than any other to* ipi/i, the ratio of the circumference of a circle to its* diameter.*/public static final double PI 3.14159265358979323846;/*** Constant by which to multiply an angular value in degrees to obtain an* angular value in radians.*/private static final double DEGREES_TO_RADIANS 0.017453292519943295;/*** Constant by which to multiply an angular value in radians to obtain an* angular value in degrees.*/private static final double RADIANS_TO_DEGREES 57.29577951308232;2. 动态绑定Dynamic Binding
动态绑定Dynamic Binding是一种方法调用的绑定方式它发生在运行时runtime。在动态绑定中方法的选择是基于对象的实际类型而不仅仅是引用变量的声明类型。它适用于非私有、非静态、非final的实例方法和接口方法的调用实现了多态性。动态绑定dynamic binding是指在运行时期runtime根据对象的实际类型来决定方法或函数的调用。在动态绑定中方法或函数的调用是根据实际类型也称为运行时类型来决定的。当程序在运行时期调用方法或函数时会根据对象的实际类型来确定调用该类型定义的方法或函数。因此动态绑定的方法或函数调用是在运行时期根据对象的实际类型决定的。
发生在运行时runtime。适用于方法调用依赖于隐式参数的实际类型的情况。在运行时根据对象的实际类型来确定要调用的方法方法选择是基于对象的实际类型。需要在每次方法调用时进行实际的查找因此速度相对较慢但提供了更大的灵活性和多态性。
动态绑定适用于以下情况
调用非私有、非静态、非final的实例方法这些方法可以被子类重写所以在调用这些方法时会根据对象的实际类型来确定调用哪个方法。调用接口方法接口方法是抽象的方法定义实现类需要实现接口方法因此在调用接口方法时会根据实现类的实际类型来确定调用哪个方法。
代码举例
动态绑定是指在运行时期根据对象的实际类型来确定方法或函数的调用。class Animal {public void makeSound() {System.out.println(动物发出声音);}
}class Dog extends Animal {public void makeSound() {System.out.println(狗发出汪汪声);}
}class Cat extends Animal {public void makeSound() {System.out.println(猫发出喵喵声);}
}public class DynamicBindingExample {public static void main(String[] args) {Animal animal1 new Animal();Animal animal2 new Dog();Animal animal3 new Cat();animal1.makeSound(); // 输出动物发出声音animal2.makeSound(); // 输出狗发出汪汪声animal3.makeSound(); // 输出猫发出喵喵声}
}我们定义了用 Animal 类和它的两个子类 Dog 和 Cat。 Animal 类中有一个 makeSound() 方法而子类 Dog 和 Cat 分别覆盖了该方法并提供了它们自己的实现。 在 main 方法中我们创建了一个 Animal 对象 animal1以及两个子类对象 animal2 和 animal3。
当我们调用 makeSound() 方法时由于动态绑定的作用实际调用的方法是根据对象的实际类型来确定的。因此animal1.makeSound() 调用的是 Animal 类的 makeSound() 方法 animal2.makeSound() 调用的是 Dog 类的 makeSound() 方法 animal3.makeSound() 调用的是 Cat 类的 makeSound() 方法。 这就是动态绑定的特性它允许程序在运行时根据对象的实际类型来确定调用的方法而不是根据引用类型来确定。
总结
动态绑定的特性允许子类对象在运行时根据实际类型来调用适当的方法而不是根据编译时类型。这使得代码可以更具适应性当引入新的子类时无需修改现有的代码就可以调用新子类特有的方法。
为确保在子类中覆盖重写超类方法时子类方法的访问权限不能低于超类方法的访问权限。如果超类方法是 public那么子类方法也必须声明为 public。如果违反了这一规则编译器将会报错因为这可能导致在某些情况下无法正常访问继承的方法。 Para_1Java虚拟机JVM负责在运行时解析和执行Java程序【根据编译后的字节码文件生成和维护的。】。在Java程序启动时JVM会加载字节码文件并将类的方法信息存储在方法区Method Area中。包括创建方法表【也称为虚方法表__vtable】。 方法表用于存储类的实例方法信息和调用地址以便在运行时进行动态绑定和多态调用。因此形成方法表是由JVM在程序运行时完成的。【方法表的结构和存储方式是由JVM定义的因此不同的JVM实现可能会有一些细节上的差异。】 Para_2方法表(method table)是虚拟机为每个类预先计算【存储方法信息】的数据结构用于记录类中所有方法的签名和要调用的实际方法【它包含了类中定义的所有方法的信息包括方法的名称、参数类型、返回类型、访问修饰符等。】。 当程序运行并且采用动态绑定调用方法时虚拟机会根据对象的实际类型在方法表中查找对应的方法。如果找到了与实际类型对应的方法则调用该方法如果没有找到则会在父类中寻找直到找到对应的方法或者到达顶层父类为止。【方法表的主要作用是在运行时支持动态绑定和多态性。】 Para_3在程序运行时当调用一个方法时JVM会根据方法表中的信息来确定要调用的具体方法。由于方法表记录了方法的实际地址或偏移量因此可以实现多态特性即在运行时根据对象的实际类型来确定调用哪个具体的方法。 需要注意的是:方法表是在编译时生成的而不是在运行时动态生成的。因此对于动态添加的方法或者通过反射机制生成的方法方法表中是不会包含这些方法的信息的。 ↩︎ Java类的返回值类型可以是任何有效的数据类型包括原始数据类型 (如int、double.char等)、对象类型(如自定义类、String等)、数组类型等。具体的返回值类型取决于方法的定义和实现。 ↩︎