Java的多态(Polymorphism)是面向对象编程中的一种特性,它允许不同的对象能够以统一的方式进行访问和操作。
它允许一个类的实例在运行时表现出多种形态。
Java多态的实现主要依赖于两个基本概念:继承和方法重写。
在Java中,一个子类可以继承父类的方法,并且可以通过重写这些方法来实现自己的特定行为。当我们创建一个对象时,它可以指向自身的类类型,也可以指向任何父类或者实现的接口类型。这就是Java多态性的核心思想:一个对象具有多种外观和行为,这取决于使用它的上下文。
Java多态的好处是可以提高代码的重用性和可维护性,使得代码更加灵活和可扩展。、
例如,一个方法可以接受一个基类(如Object)作为参数,
然后在运行时传入一个子类的实例,这样就可以处理各种不同的对象类型,而无需重写这个方法。
当你需要在一个方法中接受不同子类类型的实例,
而又不想为每个子类都单独重写方法,可以使用Java中的多态性和方法重载来实现这个目标。
多态性允许你通过基类类型引用来引用子类对象,并且在运行时调用正确的方法。
假设有一个基类 Shape
,和它的两个子类 Circle
和 Rectangle
:
class Shape {
// 基类方法
void draw() {
System.out.println("Drawing a shape");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle extends Shape {
@Override
void draw() {
System.out.println("Drawing a rectangle");
}
}
现在,你可以创建一个方法,接受基类类型的参数,并调用其 draw
方法:
public class Main {
// 接受基类对象作为参数并调用draw方法
static void drawShape(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Shape circle = new Circle();
Shape rectangle = new Rectangle();
drawShape(circle); // 调用Circle的draw方法
drawShape(rectangle); // 调用Rectangle的draw方法
}
}
在这个示例中,drawShape
方法接受一个 Shape
类型的参数。你可以传递 Circle
或者 Rectangle
类型的对象作为参数,因为它们都是 Shape
类型的子类。在运行时,Java 会根据实际对象的类型调用正确的 draw
方法,这就是多态性的体现。
父类引用引用一个具有子类身份信息的对象时,就创建了一个多态关系的实现效果。
Parent obj = new Child();
obj.show(); // 调用子类的方法
表现出多态度的过程:多态性的优点在于它增加了代码的灵活性和可扩展性,使得代码更易于维护和重用。
编译时多态:也称为静态多态,通过方法的重载实现。方法重载是指在一个类中定义多个同名的方法,但参数列表不同,编译器会根据传入的参数类型来选择调用哪个方法。
运行时多态:也称为动态多态,通过方法的重写实现。方法重写是指子类重新定义了父类中具有相同名称和参数列表的方法,子类对象在运行时会根据具体的对象类型来调用对应的方法。
父类类型 类型名称 = 子类对象
运行时多态性的实现需要满足以下条件:
存在继承关系:子类继承自父类。
存在方法重写:子类重写了父类的方法。
父类引用指向子类对象:通过父类的引用指向子类的对象,在运行时会根据实际对象的类型调用相应的方法。
"引用"是指变量或表达式,用于标识对象在内存中的位置。这些引用可以用来访问对象的属性和方法。
当父类的引用指向子类的对象时,意味着你可以使用父类的引用来操作子类的实例,而不需要直接使用子类的类型名称。
父类和子类关系的建立:
class Parent {
void show() {
System.out.println("Parent's show method");
}
}
class Child extends Parent {
void show() {
System.out.println("Child's show method");
}
}
使用多态的情况:
Parent obj1 = new Parent();
Parent obj2 = new Child();
obj1.show(); // 调用父类的方法
obj2.show(); // 调用子类的方法
方法重写(Override):
@Override
void show() {
System.out.println("Child's overridden show method");
}
父类引用调用子类方法:
Parent obj = new Child();
obj.show(); // 调用子类的方法
继承关系:当有多个类存在继承关系时,可以通过多态来处理它们的对象,从而实现更灵活的代码结构和简化代码逻辑。
方法的参数:如果一个方法需要接收不同类型的对象,并对它们进行相似的操作,使用多态可以使代码更加通用和易于扩展。
方法的返回值:当方法返回类型是基类或接口类型,但实际返回的是派生类的实例,可以使用多态来实现。
集合的使用:在使用集合(如List、Set、Map)时,可以使用多态来存储不同类型的对象,并且通过统一的接口来进行操作。
总的来说,多态使代码更加灵活、可维护性更高,能够提高代码的重用性和扩展性。
首先,你需要设计一个父类(基类),其中包含一些通用属性和方法。然后,创建一个或多个子类(派生类),它们继承了父类的属性和方法,并可以添加自己的特定属性和方法。
class Shape {
void draw() {
System.out.println("Drawing a shape");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle extends Shape {
@Override
void draw() {
System.out.println("Drawing a rectangle");
}
}
在子类中,你可以使用@Override
注解来重写父类的方法。确保重写的方法具有相同的方法签名(名称、参数列表和返回类型),以便在多态性中起作用。
使用父类的引用变量来引用子类的对象,这是实现多态性的关键。这允许你在编码时保持灵活性,可以在运行时决定调用哪个类的方法。
Shape myShape1 = new Circle();//`Shape` 是父类引用,而 `new Circle();` 是子类对象。
Shape myShape2 = new Rectangle();
myShape1.draw(); // 输出:Drawing a circle
myShape2.draw(); // 输出:Drawing a rectangle
多态性允许我们使用父类的引用来引用子类的对象,这是因为子类对象是可以赋值给父类引用的。这有助于实现代码的灵活性和可扩展性。
在Shape myShape1 = new Circle();
这行代码中,我们创建了一个 Circle
类的对象,并将其赋值给 Shape
类型的引用变量 myShape1
。由于 Circle
是 Shape
的子类,这种赋值是合法的。这意味着虽然myShape1
的类型是 Shape
,但它指向的实际上是一个 Circle
类型的对象。
通过这种方式,我们可以使用通用的 Shape 引用来引用不同的具体形状对象,从而实现了多态性和继承的概念。这种设计模式允许我们在代码中以一种更抽象的方式操作对象,同时保留了具体对象的特定功能。
当通过父类引用调用方法时,Java会在运行时动态地确定实际调用的方法,这称为运行时绑定。这意味着你可以在不同的上下文中使用相同的方法调用,但实际执行的是不同子类的方法。
如果你希望定义一组共享的方法签名,可以使用抽象类或接口。抽象类可以提供部分实现,而接口则强制实现所有方法。这使得多态性更加灵活,可以在不同类之间共享更多的行为。
interface Sound {
void makeSound();
}
class Dog implements Sound {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
class Cat implements Sound {
@Override
public void makeSound() {
System.out.println("Cat meows");
}
}
综上所述,实现多态性需要创建继承关系、重写方法、使用父类引用和子类对象、运行时绑定以及可能的抽象类或接口。这些概念共同使多态性成为面向对象编程中的强大特性,提高了代码的灵活性和可维护性。
变量调用:编译看左边,运行也看左边
方法调用:编译看左边,运行看右边
变量调用成员时,编译时会根据变量的声明类型(左边)来确定可访问的成员,运行时也会使用变量的实际类型(左边)来确定实际执行的代码。
编译时看左边: 在编译阶段,编译器会检查父类中是否存在被调用的成员变量。
如果存在,编译通过;如果不存在,编译失败。
运行时也看左边: 在运行阶段,程序实际获取的是左边父类中的成员变量的值,而不考虑实际对象的类型。
方法调用时,编译时会根据变量的声明类型(左边)来确定可调用的方法,但在运行时会根据实际对象的类型(右边)来决定实际执行的方法。
编译时看左边: 在编译阶段,编译器会检查变量的声明类型(左边)来确定可调用的方法。
如果左边的类型没有声明被调用的方法,编译会报错,即使实际对象具有相应的方法。
运行时看右边: 在运行阶段,方法调用会根据实际对象的类型(右边)来决定实际执行的方法。
即使使用父类的引用,程序也会根据实际对象的类型来调用相应的方法,这被称为动态方法分派。
这种行为允许你在运行时通过替换对象实例来实现不同的行为,这是多态性的一个关键概念。
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal1 = new Dog(); // 编译看左边,运行也看左边
Animal myAnimal2 = new Cat(); // 编译看左边,运行也看左边
myAnimal1.makeSound(); // 编译看左边,运行看右边 ("Dog barks")
myAnimal2.makeSound(); // 编译看左边,运行看右边 ("Cat meows")
}
}
使用 instanceof 进行类型检查和强制类型转换时,应该确保类型转换是安全的,
即要确保对象的实际类型与你尝试转换的类型是相符的。
如果类型不匹配,会在运行时抛出 ClassCastException 异常。
if (object instanceof MyClass) {
MyClass myObject = (MyClass) object;
// 此时可以使用 myObject 来访问 MyClass 特有的方法和属性
}
在这里,object 是要检查的对象,MyClass 是要检查的类名。如果 object 是 MyClass 类的实例或者其派生类的实例,条件就会为真,代码块会被执行。`
class ParentClass {
// Contents of the parent class
}
class ChildClass1 extends ParentClass {
// Contents of the first child class
}
class ChildClass2 extends ParentClass {
// Contents of the second child class
}
public class Main {
public static void main(String[] args) {
ParentClass obj = new ParentClass(); // This can be an instance of any class
if (obj instanceof ChildClass1) {
System.out.println("obj is an instance of ChildClass1");
} else if (obj instanceof ChildClass2) {
System.out.println("obj is an instance of ChildClass2");
} else {
System.out.println("obj is not an instance of ChildClass1 or ChildClass2");
}
}
}
在这个示例中,我们首先创建了一个 ParentClass 的实例,然后使用 instanceof 运算符检查它是否是 ChildClass1 或 ChildClass2 的实例。如果都不是,就输出相应的提示信息。
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
public void makeSound() {
System.out.println("Dog barks");
}
public void fetch() {
System.out.println("Dog fetches the ball");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog();
if (myAnimal instanceof Dog) {
Dog myDog = (Dog) myAnimal;
myDog.fetch(); // 可以调用 Dog 特有的方法
}
myAnimal.makeSound(); // 输出:Dog barks
}
}
if (object instanceof MyClass myObject) {
// 此时可以使用 myObject 来访问 MyClass 特有的方法和属性
}
class MyClass {
public void myMethod() {
System.out.println("MyClass method");
}
}
public class Main {
public static void main(String[] args) {
Object object = new MyClass();
if (object instanceof MyClass myObject) {
myObject.myMethod(); // 可以直接使用 myObject 调用 MyClass 的方法
}
}
}
在这种情况下,子类可能对相等性有特定的定义,与其超类不同。因此,你需要使用getClass
方法来检测对象的类类型,并进行相应的比较。
class Shape {
// 父类 Shape
}
class Circle extends Shape {
private double radius; // 定义一个私有的double类型的成员变量radius,表示圆的半径
public Circle(double radius) {
this.radius = radius; // 构造函数,接受一个double类型的参数radius,并将其赋值给类的成员变量radius
}
@Override
public boolean equals(Object obj) {
// 重写equals方法
if (obj == this) {
return true; // 如果obj等于当前对象,返回true
}
if (obj == null || obj.getClass() != this.getClass()) {
return false; // 如果obj为null或者其类类型与当前对象的类类型不同,返回false
}
Circle other = (Circle) obj; // 将obj强制转换为Circle类型
return this.radius == other.radius; // 比较其radius成员变量与当前对象的radius成员变量是否相等,如果相等,则返回true,否则返回false
}
}
class Square extends Shape {
public double side; // 定义一个公有的double类型的成员变量side,表示正方形的边长
public Square(double side) {
this.side = side; // 构造函数,接受一个double类型的参数side,并将其赋值给类的成员变量side
}
@Override
public boolean equals(Object obj) {
// 重写equals方法
if (obj == this) {
return true; // 如果obj等于当前对象,返回true
}
if (obj == null || obj.getClass() != this.getClass()) {
return false; // 如果obj为null或者其类类型与当前对象的类类型不同,返回false
}
Square other = (Square) obj; // 将obj强制转换为Square类型
return this.side == other.side; // 比较其side成员变量与当前对象的side成员变量是否相等,如果相等,则返回true,否则返回false
}
}
public class Mains {
public static void main(String[] args) {
Circle circle1 = new Circle(5.0); // 创建一个Circle对象,circle1,半径为5.0
Circle circle2 = new Circle(5.0); // 创建一个Circle对象,circle2,半径为5.0
System.out.println(circle1.equals(circle2)); // 调用circle1的equals方法,将circle2作为参数传入,然后将返回的结果打印到控制台
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
Circle other = (Circle) obj;
return this.radius == other.radius;
}
这是Circle类的equals方法,它接受一个Object类型的参数obj。这个方法首先检查obj是否等于当前对象,如果是,则返回true。然后,它检查obj是否为null或者其类类型是否与当前对象的类类型不同,如果是,则返回false。最后,它将obj强制转换为Circle类型,并比较其radius成员变量与当前对象的radius成员变量是否相等,如果相等,则返回true,否则返回false。
在if (obj == this)
这一行,this代表当前Circle对象。这个判断语句的意思是,如果传入的对象obj就是当前对象(也就是说,obj和this引用的是同一个对象),那么就返回true,表示它们是相等的。
在return this.radius == other.radius;
这一行,this同样代表当前Circle对象。这个返回语句的意思是,如果当前Circle对象的半径(this.radius)和传入的Circle对象(other)的半径(other.radius)相等,那么就返回true,表示这两个Circle对象是相等的。
在这个equals方法的定义中,Object obj是方法的参数。当你调用equals方法并传递一个参数时,这个参数的值就会被赋给obj。
例如,当你调用circle1.equals(circle2)时,circle2的引用就会被赋给obj。然后在equals方法的内部,你可以使用obj来访问传递的对象。
在情况1中,为了确保比较的对象是同一个类的实例,我们使用了getClass()方法。这是因为在Java中,equals()方法的参数是Object类型,它可以接受任何对象作为参数。因此,我们需要在equals()方法中进行类型检查,以确保传入的对象是与当前对象相同的类的实例。
在情况1中,我们首先检查传入的对象是否与当前对象是同一个对象,如果是,则返回true。这是一个优化步骤,因为一个对象总是等于它自身。
接下来,我们使用getClass()方法来检查传入的对象的类类型是否与当前对象的类类型相同。如果不相同,说明传入的对象不是当前对象的子类或父类,因此它们不可能相等,我们返回false。
使用getClass()方法进行类型检查是情况1中的一种常见做法,因为它可以确保比较的对象是同一个类的实例。这样可以避免在比较过程中出现类型不匹配的错误。
需要注意的是,在使用getClass()方法进行类型检查时,我们假设传入的对象不为null。因此,在进行类型检查之前,我们先检查传入的对象是否为null,如果是,则返回false。
总结起来,情况1中必须使用getClass()方法进行类型检查,以确保比较的对象是同一个类的实例。这样可以避免类型不匹配的错误,并确保equals()方法的正确性。
在这种情况下,超类决定了对象的相等性定义,不同子类的对象也可以被视为相等。你可以使用instanceof
关键字来检测对象是否是某个类的实例。
class Animal {
// 父类 Animal
protected String species;
public Animal(String species) {
this.species = species;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !(obj instanceof Animal)) {
return false;
}
Animal other = (Animal) obj;
return this.species.equals(other.species);
}
}
class Dog extends Animal {
public Dog() {
super("Dog");
}
}
class Cat extends Animal {
public Cat() {
super("Cat");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
System.out.println(dog.equals(cat)); // false,因为它们的种类不同
}
}
这些例子演示了在不同情况下如何根据对象的类型和相等性定义来实现对象的相等性比较。
由于Dog和Cat是Animal的子类,它们继承了父类Animal的equals()方法。在这种情况下,dog.equals(cat)返回false,因为它们的species属性不相等。
因此,在情况2中,equals()方法的比较是在子类之间进行的,通过比较继承自父类的属性来判断对象是否相等。
在Animal类中,重写了equals()方法来比较对象的相等性。在equals()方法中,首先检查传入的对象是否与当前对象是同一个对象,如果是,则返回true。接下来,使用instanceof来检查传入的对象是否是Animal类或其子类的实例。如果不是,或者传入的对象为null,则返回false。最后,将传入的对象转换为Animal类型,并比较它们的species属性来判断它们是否相等。
equals()
方法的原型如下:
public static boolean equals(type[] a, type[] b)
原始数据类型数组(例如 int[]、double[]、char[] 等)。
引用数据类型数组(例如 String[]、Object[]、自定义类数组等)。
java.lang.Object
类的equals
方法的原型如下:public boolean equals(Object obj)
equals
方法用于比较两个对象是否相等。默认情况下,equals
方法在Object
类中是使用对象的引用地址进行比较的,即判断两个对象是否引用同一个内存地址。但是,很多情况下我们需要根据对象的内容来判断它们是否相等,因此在许多自定义的类中,需要重写equals
方法来改变默认的比较行为。
要重写equals
方法,通常需要满足以下几个条件:
x
,x.equals(x)
应该返回 true
。x
和 y
,当且仅当 y.equals(x)
返回 true
时,x.equals(y)
应该返回 true
。x
、y
和 z
,如果 x.equals(y)
返回 true
,并且 y.equals(z)
也返回 true
,那么 x.equals(z)
应该返回 true
。x
和 y
,如果两个对象的属性没有发生改变,多次调用 x.equals(y)
应该始终返回相同的结果。x
,x.equals(null)
应该返回 false
。在实际使用中,特别是在自定义类中,要根据对象的属性来实现equals
方法,而不仅仅是比较引用地址。这样可以确保按照你定义的相等条件来判断两个对象是否相等。
需要根据具体的情况来决定使用哪种情况,以确保equals()方法的正确性和符合预期的行为。
选择使用哪种情况取决于你的需求和设计。
如果子类具有自己的相等性概念,并且需要比较特定的属性来确定对象是否相等,那么情况1是更合适的选择。
如果超类已经定义了对象的相等性概念,并且子类继承了这个概念,那么情况2是更合适的选择。