当前位置: 首页 > news >正文

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

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

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

  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等)、数组类型等。具体的返回值类型取决于方法的定义和实现。 ↩︎

相关文章:

Java_理解方法调用

理解方法调用 首先什么是隐式参数 --->隐式参数是调用该方法的对象本身。 接下来方法的名称和参数列表被称为方法的签名&#xff08;signature&#xff09;。在Java中&#xff0c;方法的签名由方法的名称和参数列表组成&#xff0c;用于唯一标识一个方法。返回类型不是签名的…...

Mysql 性能分析(慢日志、profiling、explain)、读写分离(主从架构)、分库分表(垂直分库、垂直分表、水平分表)

查看系统性能参数 一条sql查询语句在执行前&#xff0c;需要确定查询执行计划&#xff0c;如果存在多种执行计划的话&#xff0c;mysql会计算每个执行计划所需要的成本&#xff0c;从中选择 成本最小的一个作为最终执行的执行计划 想要查看某条sql语句的查询成本&#xff0c;可…...

获取Linux内核源码

在嵌入式平台上做Linux开发的时候&#xff0c;我们用的kernel都是芯片厂家移植到自家平台上的&#xff0c;但是最初的原生Linux内核的源码是从哪里来的呢&#xff1f;下面我们介绍一下怎么获取原生的Linux源码。 从Linux社区获取内核kernel源码 Linux社区的官方网站是 https:…...

【Maven教程】(四)坐标与依赖:坐标概念,依赖配置、范围、传递性和最佳实践 ~

Maven 坐标与依赖 1️⃣ 什么是Maven 坐标2️⃣ 坐标详解3️⃣ 依赖的配置4️⃣ 依赖范围5️⃣ 传递性依赖6️⃣ 依赖调解7️⃣ 可选依赖8️⃣ 最佳实践8.1 排除依赖8.2 归类依赖8.3 优化依赖 &#x1f33e; 总结 正如前面文章所述&#xff0c;Maven 的一大功能是管理项目依赖…...

Java“牵手”京东店铺所有商品API接口数据,通过店铺ID获取整店商品详情数据,京东店铺所有商品API申请指南

京东平台店铺所有商品数据接口是开放平台提供的一种API接口&#xff0c;通过调用API接口&#xff0c;开发者可以获取京东整店的商品的标题、价格、库存、月销量、总销量、库存、详情描述、图片、价格信息等详细信息 。 获取店铺所有商品接口API是一种用于获取电商平台上商品详…...

TuyaOS开发学习笔记(1)——NB-IoT开发搭建环境、编译烧写(MT2625)

一、搭建环境 1.1 官方资料 TuyaOS 1.2 安装VMware 官网下载&#xff1a;https://customerconnect.vmware.com/en/downloads/info/slug/desktop_end_user_computing/vmware_workstation_pro/16_0 百度网盘&#xff1a;https://pan.baidu.com/s/1oN7H81GV0g6cD9zsydg6vg 提取…...

Css 将div设置透明度,并向上移50px,盖住上面的元素一部分

可以使用CSS中的opacity和position属性来实现。 首先&#xff0c;将div的opacity属性设置为小于1的值&#xff0c;比如0.5&#xff0c;这样就可以设置透明度了。其次&#xff0c;将div的position设置为relative&#xff0c;然后再将它向上移动50px&#xff0c;即可盖住上面的元…...

HTTPS安全通信和SSL Pinning

随着互联网的迅速发展&#xff0c;网络通信安全问题日益凸显。在这一背景下&#xff0c;HTTPS作为一种加密通信协议得到了广泛应用&#xff0c;以保障用户的数据隐私和信息安全。本文将介绍HTTPS的基本原理、发展历程&#xff0c;以及与之相关的中间人攻击和防护方法。 1. HTT…...

PHP自己的框架PDO数据表前缀、alias、model、table、join方法实现(完善篇九--结束)

一、实现功能&#xff0c;数据表前缀、alias、model、table、join方法实现 二、表前缀实现 1、config.php增加表前缀 DB_PEX>fa_,//数据库前缀 2、增加表前缀方法function.php function model($table){$modelnew ModelBase($table,config("DB_PEX"));return $m…...

华为OD:敏感字段加密

题目描述&#xff1a; 给定一个由多个命令字组成的命令字符串&#xff1a; 1、字符串长度小于等于127字节,只包含大小写字母,数字,下划线和偶数个双引号&#xff1b; 2、命令字之间以一个或多个下划线_进行分割&#xff1b; 3、可以通过两个双引号”"来标识包含下划线…...

IDEA新建SpringBoot项目时启动编译报错:Error:java: 无效的源发行版: 17

文章目录 原因检查解决步骤修改jdk修改SpringBoot版本 原因 出现这种错误的原因可能是&#xff1a; 本机默认使用&#xff08;编译&#xff09;的jdk与该项目所使用的jdk版本不同。 jdk版本不适用于这个Idea&#xff0c;很典型的一个例子就是使用的Idea是2020的&#xff0c;而…...

【云原生进阶之PaaS中间件】第一章Redis-2.3.3集群模式

1 集群模式 Redis集群是一个提供在多个Redis节点之间共享数据的程序集。它并不像Redis主从复制模式那样只提供一个master节点提供写服务,而是会提供多个master节点提供写服务,每个master节点中存储的数据都不一样,这些数据通过数据分片的方式被自动分割到不同的master节点上…...

游戏发行商能够提供什么服务?

游戏发行商可以为游戏开发者提供广泛的服务&#xff0c;以帮助他们将游戏成功地引入市场并取得更好的业绩。以下是游戏发行商可能提供的一些服务&#xff1a; 市场营销和宣传&#xff1a;发行商通常具有丰富的市场营销经验&#xff0c;可以制定并执行有效的宣传和营销策略。他们…...

Linux 多进程解决客户端与服务器端通信

写一个服务器端用多进程处理并发&#xff0c;使两个以上的客户端可以同时连接服务器端得到响应。每当接受一个新的连接就fork产生一个子进程&#xff0c;让子进程去处理这个连接&#xff0c;父进程只用来接受连接。 与多线程相比的不同点&#xff1a;多线程如果其中一个线程操…...

Scala的模式匹配

Scala的模式匹配 Scala 中的模式匹配类似于Java 中的 switch 语法&#xff1a;下面是java中switch代码&#xff1a; int i 10 switch (i) {case 10 :System.out.println("10");break; case 20 :System.out.println("20");break; default :System.out.pr…...

HttPClient简介及示例:学习如何与Web服务器进行通信

文章目录 前言一、引入依赖二、使用步骤1.创建被调用者2.创建调用者三、结果被调用者服务&#xff1a;调用者服务&#xff1a; 总结 前言 欢迎来到本篇博客&#xff0c;这是一个关于HttPClient的入门案例的指南。&#x1f389; 在今天的网络世界中&#xff0c;与服务器进行数据…...

STS4 New 安装Spring Bean Configuration File

背景介绍 在创建spring项目后&#xff0c;如果想想创建spring bean Configuration的时候&#xff0c;发下菜单没有这个选项&#xff0c;需要通过下载Spring Roo插件可满足该操作。 参考案例 参考地址&#xff1a; STS4 New 菜单没有Spring Bean Configuration File选项_SQZHA…...

Java经典面试题(异或运算)

不爱生姜不吃醋⭐️⭐️⭐️ &#x1f33b;如果本文有什么错误的话欢迎在评论区中指正哦&#x1f497; &#x1f33b;看完之后觉得不错的话麻烦动动小手点个赞赞吧&#x1f44d; &#x1f33b;与其明天开始&#xff0c;不如现在行动&#xff01;&#x1f4aa; &#x1f33b;大家…...

No primary or single unique constructor found for interface java.util.List

1.问题描述 前端 请求的参数 是 query形式&#xff0c; 参数在url中 报错信息&#xff1a; java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.List2.解决办法 controller中的 请求方法 参数 加上 RequestParam...

C#关于WebService中File.Exists()处理远程路径的异常记录

目录 前言方案一打开网站对应的程序池的高级设置按下图步骤设置凭据重启网站若方案一未能解决&#xff0c;请继续尝试方案二&#x1f447; 方案二从控制面板进入到 凭据管理器为windows凭据添加凭据点击**Windows凭据**&#xff0c;并点击**添加Windows凭据**键入远程路径的地址…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...