Java_理解方法调用

理解方法调用

  • 首先
    • 什么是隐式参数 --->隐式参数是调用该方法的对象本身。
  • 接下来
    • 方法的名称和参数列表被称为方法的签名(signature)。
      • 在Java中,方法的签名由方法的名称和参数列表组成,用于唯一标识一个方法。
        • 返回类型不是签名的一部分。不过在覆盖一个方法时,需要保证返回类型的兼容性:--->允许子类将覆盖方法的返回类型改为原返回类型的子类型。
          • 具体的例子:
            • 详细:
        • "协变"(covariant)是一种类型关系 --->子类型的某些属性或方法可以比父类型更具体(特化)"
      • 协变(covariance)是指子类型的返回类型可以是父类型的子类型,而逆变(contravariance)是指子类型的返回类型可以是父类型的超类型。
        • 1.具体说明是怎么实现_协变
        • 2.具体说明是怎么实现_逆变
  • 静态绑定(Static Binding)和动态绑定(Dynamic Binding)
    • 1. **静态绑定(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 可以转换成 double,Manager 可以转换成 Employee 等),这个过程可能会变得相当复杂。

【如果编译器无法找到与提供参数类型完全匹配的方法,或者在进行类型转换后有多个方法与之匹配,编译器将会报告错误。】到这一步,编译器已经成功 确定了需要调用的方法名和参数类型.这意味着在方法调用的过程中,编译器通过上述步骤精确地确定了应该调用的方法。这个过程确保了方法调用的准确性和可靠性。

public class Mains {public static void main(String[] args) {printNumber(5); // 输出 "Printing int: 5"printNumber(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可以转换成double,Manager可以转换成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 覆盖了父类 AnimalmakeSound() 方法,并且返回类型从 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,而BookLibraryMagazineLibrary 分别覆盖了这个方法,并将返回类型改为更具体的 BookMagazine。这种方式允许子类方法返回比父类更具体的类型,而仍然保持方法签名的一致性。

所以,协变返回类型在这里的体现就是子类方法覆盖了父类方法并返回更具体的类型,这符合"子类型与父类型之间的关系,在这种关系下,子类型的某些属性或方法可以比父类型更具体(特化)"的解释。

协变(covariance)是指子类型的返回类型可以是父类型的子类型,而逆变(contravariance)是指子类型的返回类型可以是父类型的超类型。

  1. 协变:如果类型B是类型A的子类型,那么在某些上下文中,我们可以使用类型B的对象替代类型A的对象。这通常应用于方法的返回类型。例如,如果一个方法返回一个Animal类型的对象,那么它也可以返回一个Dog类型的对象(假设Dog是Animal的子类)。

  2. 逆变:如果类型B是类型A的子类型,那么在某些上下文中,我们可以使用类型A的对象替代类型B的对象。这通常应用于方法的参数类型。例如,如果一个方法接受一个Dog类型的对象作为参数,那么它也可以接受一个Animal类型的对象(假设Dog是Animal的子类)。

  3. 协变和逆变的主要目的是提供更大的灵活性和类型安全。它们允许我们在保持类型安全的同时,编写更通用和可重用的代码。例如,如果我们有一个处理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());}
}

在这里插入图片描述

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方法以及构造器等情况。
  • 在编译时就能确定要调用的方法,因为方法的选择是基于引用变量的声明类型。
  • 编译器可以在编译阶段直接解析方法调用,因此速度较快。

静态绑定适用于以下情况:

  1. Private 方法:由于private方法在同一类中是不可继承的,编译器可以直接确定要调用的方法。

private方法是不能被子类重写的,所以调用private方法时,编译器可以准确地知道应该调用哪个方法。

  1. Static 方法:静态方法属于类而不是实例,因此不需要实例化对象就可以调用。编译器可以根据声明的类来确定要调用的静态方法。

static方法是属于类而不是对象的,所以调用static方法时,编译器可以准确地知道应该调用哪个方法。

  1. Final 方法:final修饰的方法不能被子类重写,因此编译器可以准确知道要调用的是声明的类中的final方法。

final方法是不能被子类重写的,所以调用final方法时,编译器可以准确地知道应该调用哪个方法。

  1. 构造器:在创建对象时,编译器根据构造器的参数列表来准确地选择合适的构造器。

构造器是用于创建对象的特殊方法,调用构造器时,编译器可以准确地知道应该调用哪个构造器。

静态绑定的一个特点是,它在编译时就能够解析方法调用,因此执行速度较快。但是,静态绑定缺乏动态继承和多态性的特性,因为它不会考虑对象的实际类型。相比之下,动态绑定会在运行时根据实际对象类型来确定方法调用,提供了更大的灵活性和多态性。

举例代码:

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中,staticfinal 都是关键字,用于定义变量的特性。它们有不同的含义和用途。

首先,我们分别来看看 staticfinalstatic final 的含义:

  1. static:用于表示变量或方法是类级别的,不依赖于对象的创建,可以通过类名直接访问。但是 static 并不代表不可修改,静态变量在程序运行期间是可以被修改的。

  2. final:用于表示常量,一旦赋值后就不能再修改。可以用于变量、方法、类等地方。

  3. static final:将 staticfinal 结合在一起,表示一个类级别的不可修改的常量。

现在,我们将上述概念应用于代码,并逐步思考输出结果:

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);}
}
需要注意的是,尝试修改 finalstatic final 变量的值会导致编译错误,因为它们被声明为不可修改的常量。

在这里插入图片描述

总结:
  • static 表示类级别的,可以通过类名直接访问的变量或方法。
  • final 表示常量,一旦赋值后不能再修改。
  • static final 表示类级别的不可修改的常量。
  • staticfinal 可以独立使用,也可以结合在一起使用。在这里插入图片描述
- Math类中的定义:
 /*** The {@code double} value that is closer than any other to* <i>e</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* <i>pi</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)。
  • 适用于方法调用依赖于隐式参数的实际类型的情况。
  • 在运行时根据对象的实际类型来确定要调用的方法,方法选择是基于对象的实际类型。
  • 需要在每次方法调用时进行实际的查找,因此速度相对较慢,但提供了更大的灵活性和多态性。

动态绑定适用于以下情况:

  1. 调用非私有、非静态、非final的实例方法:这些方法可以被子类重写,所以在调用这些方法时,会根据对象的实际类型来确定调用哪个方法。
  2. 调用接口方法:接口方法是抽象的方法定义,实现类需要实现接口方法,因此在调用接口方法时,会根据实现类的实际类型来确定调用哪个方法。

代码举例:

动态绑定是指在运行时期根据对象的实际类型来确定方法或函数的调用。
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 类和它的两个子类 DogCat
Animal 类中有一个 makeSound() 方法,而子类 DogCat 分别覆盖了该方法并提供了它们自己的实现。
main 方法中,我们创建了一个 Animal 对象 animal1,以及两个子类对象 animal2animal3

当我们调用 `makeSound()` 方法时,由于动态绑定的作用,实际调用的方法是根据对象的实际类型来确定的。因此,

animal1.makeSound() 调用的是 Animal 类的 makeSound() 方法,
animal2.makeSound() 调用的是 Dog 类的 makeSound() 方法,
animal3.makeSound() 调用的是 Cat 类的 makeSound() 方法。
这就是动态绑定的特性,它允许程序在运行时根据对象的实际类型来确定调用的方法,而不是根据引用类型来确定。

总结:

动态绑定的特性允许子类对象在运行时根据实际类型来调用适当的方法,而不是根据编译时类型。这使得代码可以更具适应性,当引入新的子类时,无需修改现有的代码就可以调用新子类特有的方法。

为确保在子类中覆盖(重写)超类方法时,子类方法的访问权限不能低于超类方法的访问权限。如果超类方法是 public,那么子类方法也必须声明为 public。如果违反了这一规则,编译器将会报错,因为这可能导致在某些情况下无法正常访问继承的方法。


  1. Para_1Java虚拟机(JVM)负责在运行时解析和执行Java程序根据编译后的字节码文件生成和维护的。】。在Java程序启动时,JVM会加载字节码文件,并将类的方法信息存储在方法区(Method Area)中。包括创建方法表【也称为虚方法表__vtable】。
    方法表用于存储类的实例方法信息和调用地址,以便在运行时进行动态绑定和多态调用。因此,形成方法表是由JVM在程序运行时完成的。【方法表的结构和存储方式是由JVM定义的,因此不同的JVM实现可能会有一些细节上的差异。】
    Para_2:方法表(method table)是虚拟机为每个类预先计算【存储方法信息】的数据结构,用于记录类中所有方法的签名和要调用的实际方法【它包含了类中定义的所有方法的信息,包括方法的名称、参数类型、返回类型、访问修饰符等。】。
    当程序运行并且采用动态绑定调用方法时,虚拟机会根据对象的实际类型在方法表中查找对应的方法。如果找到了与实际类型对应的方法,则调用该方法;如果没有找到,则会在父类中寻找,直到找到对应的方法或者到达顶层父类为止。【方法表的主要作用是在运行时支持动态绑定和多态性。】
    Para_3:在程序运行时,当调用一个方法时,JVM会根据方法表中的信息来确定要调用的具体方法。由于方法表记录了方法的实际地址或偏移量,因此可以实现多态特性,即在运行时根据对象的实际类型来确定调用哪个具体的方法。
    需要注意的是:方法表是在编译时生成的,而不是在运行时动态生成的。因此,对于动态添加的方法或者通过反射机制生成的方法,方法表中是不会包含这些方法的信息的。 ↩︎

  2. Java类的返回值类型可以是任何有效的数据类型,包括原始数据类型 (如int、double.char等)、对象类型(如自定义类、String等)、数组类型等。具体的返回值类型取决于方法的定义和实现。 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/80346.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql存储引擎InnoDB

一、存储引擎的简介 MySQL 5.7 支持的存储引擎有 InnoDB、MyISAM、Memory、Merge、Archive、Federated、CSV、BLACKHOLE 等。 1、InnoDB存储引擎 从MySQL5.5版本之后&#xff0c;默认内置存储引擎是InnoDB&#xff0c;主要特点有&#xff1a; &#xff08;1&#xff09;灾难恢…

【MCU学习】GD32F427VG开发

&#xff08;一&#xff09;学习文档和例程 兆易创新GD32 MCU参考资料下载 1.GD232F4xx的Keil芯片支持包 2.标准固件库和示例程序 3.GD32F4xx_固件库使用指南_Rev1.2 4.用户手册&#xff1a;GD32F4xx_User_Manual_Rev2.8_CN 5.数据手册&#xff1a;GD32F427xx_Datasheet_Rev…

SpringMVC的注解

文章目录 前言前期准备ResponseBody 返回JSONRequestMapping 映射控制器GetMapping、PostMapping 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; SpringMVC框架只需要少量的配置即可快速实现Web应用程序开发&#xff0c;不需要大量的XML配置文件。 不…

电脑第一次使用屏幕键盘

操作流程 1.在键盘上同时按WinR打开运行; 2.输入control 3.找到设置中心 4.点击屏幕键盘 效果 具体怎么使用 我不咋清除 简单 测试了一下 可以用鼠标点击屏幕键盘的按键 用键盘 按字母键和数字键 是和屏幕键盘不同步的 其他 tab、shift、后退、enter好像同步

分立式BUCK电路原理与制作持续更新

一、分立式BUCK电路总体原理图 下面改图包含了电压环和电流环。 二、BUCK电路与LDO的区别 LDO不适合在压差大的环境下使用&#xff0c;因为三极管因为CE极承受了压差&#xff0c;压差越大损耗的功率就越大&#xff0c;将三极管换成MOS管&#xff0c;MOS管两端的压差很小所以效…

梳理日常开发涉及的负载均衡

负载均衡是当前分布式微服务时代最能提及的词之一&#xff0c;出于对分层、解耦、弱依赖、可配置、可靠性等概念的解读&#xff0c;一对一的模式变得不再可信赖&#xff0c;千变万化的网络环境中&#xff0c;冗余和备份显得格外重要&#xff0c;稍大型的系统就会存在大量微服务…

TextBrewer:融合并改进了NLP和CV中的多种知识蒸馏技术、提供便捷快速的知识蒸馏框架、提升模型的推理速度,减少内存占用

TextBrewer:融合并改进了NLP和CV中的多种知识蒸馏技术、提供便捷快速的知识蒸馏框架、提升模型的推理速度&#xff0c;减少内存占用 TextBrewer是一个基于PyTorch的、为实现NLP中的知识蒸馏任务而设计的工具包&#xff0c; 融合并改进了NLP和CV中的多种知识蒸馏技术&#xff0…

C高级-day4

#!/bin/bash function fun1(){arr[0]id -u $1arr[1]id -g $1echo ${arr[*]} }arr(fun1 ubuntu) echo ${arr[*]}冒泡排序 void Maopao(int arr[],int len){for(int i1;i<len;i){int count0;for(int j0;j<len-i;j){if(arr[j]>arr[j1]){int tarr[j];arr[j]arr[j1];arr[j…

智能优化算法——哈里鹰算法(Matlab实现)

目录 1 算法简介 2 算法数学模型 2.1.全局探索阶段 2.2 过渡阶段 2.3.局部开采阶段 3 求解步骤与程序框图 3.1 步骤 3.2 程序框图 4 matlab代码及结果 4.1 代码 4.2 结果 1 算法简介 哈里斯鹰算法(Harris Hawks Optimization&#xff0c;HHO)&#xff0c;是由Ali…

如何解决 Elasticsearch 查询缓慢的问题以获得更好的用户体验

作者&#xff1a;Philipp Kahr Elasticsearch Service 用户的重要注意事项&#xff1a;目前&#xff0c;本文中描述的 Kibana 设置更改仅限于 Cloud 控制台&#xff0c;如果没有我们支持团队的手动干预&#xff0c;则无法进行配置。 我们的工程团队正在努力消除对这些设置的限制…

arcgis宗地或者地块四至权利人信息提取教程

ARCGIS怎样将图斑四邻的名称及方位加入其属性表 以前曾发表过一篇《 如何把相邻图斑的属性添加在某个字段中》的个人心得,有些会员提出了进一步的要求,不但要相邻图斑的名称,还要求有方位,下面讲一下自己的做法。 基本思路是:连接相邻图斑质心,根据连线的角度确定相邻图斑…

c++游戏制作指南(三):c++剧情类文字游戏的制作

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f35f;欢迎来到静渊隐者的csdn博文&#xff0c;本文是c游戏制作指南的一部&#x1f35f; &#x1f355;更多文章请点击下方链接&#x1f355; &#x1f368; c游戏制作指南&#x1f3…

21、p6spy输出执行SQL日志

文章目录 1、背景2、简介3、接入3.1、 引入依赖3.2、修改database参数&#xff1a;3.3、 创建P6SpyLogger类&#xff0c;自定义日志格式3.4、添加spy.properties3.5、 输出样例 4、补充4.1、参数说明 1、背景 在开发的过程中&#xff0c;总希望方法执行完了可以看到完整是sql语…

供水管网漏损监测,24小时保障城市供水安全

供水管网作为城市生命线重要组成部分&#xff0c;其安全运行是城市建设和人民生活的基本保障。随着我国社会经济的快速发展和城市化进程的加快&#xff0c;城市供水管网的建设规模日益增长。然而&#xff0c;由于管网老化、外力破坏和不当维护等因素导致的供水管网漏损&#xf…

js修改img的src属性显示变换图片到前端页面,img的src属性显示java后台读取返回的本地图片

文章目录 前言一、HTML 图像- 图像标签&#xff08; <img>&#xff09;1.1图像标签的源属性&#xff08;Src&#xff09;1.2图像标签源属性&#xff08;Src&#xff09;显示项目中图片1.3图像标签源属性&#xff08;Src&#xff09;显示网络图片 二、图像标签&#xff08…

Docker实战-关于Docker镜像的相关操作(二)

导语   之前的分享中&#xff0c;我们介绍了关于Docker镜像的查询操作相关的内容&#xff0c;下面我们继续来介绍删除清理、导入导出、创建镜像等操作。 如何删除和清理镜像&#xff1f; 使用标签删除镜像 可以使用docker rmi 或者是 docker image rm 命令来删除镜像&#x…

segment-anything使用说明

文章目录 一. segment-anything介绍二. 官网Demo使用说明三. 安装教程四. python调用生成掩码教程五. python调用SAM分割后转labelme数据集 一. segment-anything介绍 Segment Anything Model&#xff08;SAM&#xff09;根据点或框等输入提示生成高质量的对象遮罩&#xff0c…

spring eurake中使用IP注册

在开发spring cloud的时候遇到一个很奇葩的问题&#xff0c;就是服务向spring eureka中注册实例的时候使用的是机器名&#xff0c;然后出现localhost、xxx.xx等这样的内容&#xff0c;如下图&#xff1a; eureka.instance.perferIpAddresstrue 我不知道这朋友用的什么spring c…

爬虫009_字符串高级_替换_去空格_分割_取长度_统计字符_间隔插入---python工作笔记028

然后再来看字符串的高级操作 取长度 查找字符串下标位置 判断是否以某个字符,开头结尾 计算字符出现次数 替换

sigmoid ReLU 等激活函数总结

sigmoid ReLU sigoid和ReLU对比 1.sigmoid有梯度消失问题&#xff1a;当sigmoid的输出非常接近0或者1时&#xff0c;区域的梯度几乎为0&#xff0c;而ReLU在正区间的梯度总为1。如果Sigmoid没有正确初始化&#xff0c;它可能在正区间得到几乎为0的梯度。使模型无法有效训练。 …