Java教程-详解Java 协变返回类型
协变返回类型指定返回类型可以在与子类相同的方向上变化。
在Java5之前,无法通过改变返回类型来覆盖方法。但是自从Java5开始,如果子类重写了一个返回非原始类型的方法,并且将返回类型更改为子类类型,那么就可以使用协变返回类型来覆盖方法。下面是一个简单的示例::
注意:如果您是 Java 初学者,请跳在了解 OOP 概念后再返回阅读本篇教程。
协变返回类型的简单示例
文件名: B1.java
class A{
A get(){return this;}
}
class B1 extends A{
@Override
B1 get(){return this;}
void message(){System.out.println("欢迎使用协变返回类型");}
public static void main(String args[]){
new B1().get().message();
}
}
输出:
欢迎使用协变返回类型
上面的例子可以看到,A类的get()方法的返回类型是A,B类的get()方法的返回类型是B。两种方法返回类型不同,但都是方法重写. 这称为协变返回类型。
协变返回类型的优点
以下是协变返回类型的优点:
- 避免类型转换的困扰:使用协变返回类型可以避免在类层次结构中进行繁琐的类型转换,使代码更加易读和易维护。
- 自由性增加:在方法覆盖中,协变返回类型提供了更大的灵活性,允许子类在返回类型方面具有更多的选择。
- 防止运行时ClassCastException:使用协变返回类型可以帮助防止在返回对象时出现运行时的ClassCastException异常,因为返回类型已经被指定为子类类型。
让我们通过一个例子来更好地理解协变返回类型的优点。
文件名: CovariantExample.java
class A1
{
A1 foo()
{
return this;
}
void print()
{
System.out.println("Inside the class A1");
}
}
// A2 是 A1 的子类
class A2 extends A1
{
@Override
A1 foo()
{
return this;
}
void print()
{
System.out.println("Inside the class A2");
}
}
// A3 是 A2 的子类
class A3 extends A2
{
@Override
A1 foo()
{
return this;
}
@Override
void print()
{
System.out.println("Inside the class A3");
}
}
public class CovariantExample
{
// main方法
public static void main(String argvs[])
{
A1 a1 = new A1();
//这样写是没问题的
a1.foo().print();
A2 a2 = new A2();
// 我们需要进行类型转换才能实现
// 读者更清楚创建的对象类型
((A2)a2.foo()).print();
A3 a3 = new A3();
// 进行类型转换
((A3)a3.foo()).print();
}
}
输出:
Inside the class A1
Inside the class A2
Inside the class A3
解释:
在上述代码中,类A3继承自类A2,而类A2又继承自类A1。因此,A1是A2和A3的父类。这意味着A2和A3类的任何对象也可以视为A1类型的对象。由于方法foo()在每个类中的返回类型都相同,我们无法确定实际返回的对象的确切类型。我们只能推断返回的对象将是A1类型,这是最通用的类。我们无法确定返回的对象是A2还是A3。
这就是为什么我们需要进行类型转换来确定从foo()方法返回的对象的具体类型。然而,类型转换不仅使代码变得冗长,还需要程序员保证转换的准确性,以确保正确地执行类型转换,否则可能会导致ClassCastException异常。
更糟糕的是,考虑到类层次结构可能涉及到10到15个甚至更多的类,并且每个类中的foo()方法都具有相同的返回类型,这会使代码的阅读和编写变得非常困难和混乱。
写上面的更好的方法是:
文件名: CovariantExample.java
class A1
{
A1 foo()
{
return this;
}
void print()
{
System.out.println("Inside the class A1");
}
}
// A2 是 A1 的子类
class A2 extends A1
{
@Override
A2 foo()
{
return this;
}
void print()
{
System.out.println("Inside the class A2");
}
}
// A3 是 A2 的子类
class A3 extends A2
{
@Override
A3 foo()
{
return this;
}
@Override
void print()
{
System.out.println("Inside the class A3");
}
}
public class CovariantExample
{
// main方法
public static void main(String argvs[])
{
A1 a1 = new A1();
a1.foo().print();
A2 a2 = new A2();
a2.foo().print();
A3 a3 = new A3();
a3.foo().print();
}
}
输出:
Inside the class A1
Inside the class A2
Inside the class A3
解释:在上述程序中,我们不需要进行类型转换,因为返回类型是确定的。因此,我们知道从foo()方法返回的对象的类型不会引起混淆。即使我们编写了涉及10到15个类的代码,方法的返回类型也不会引起混淆。这是因为协变返回类型使这一切成为可能。
协变返回类型是如何实现的?
尽管Java不允许基于返回类型的重载,但JVM始终允许基于返回类型的重载。JVM使用方法的完整签名进行查找和解析。完整签名指的是除了参数类型外,还包括返回类型。换句话说,一个类可以有两个或多个方法,它们的返回类型不同。Java编译器(javac)利用了这个特性来实现协变返回类型。它允许子类方法的返回类型是父类方法返回类型的子类型,从而实现了协变返回类型的功能。这样,我们就能够以更灵活和直观的方式重写方法,并且不会引起类型转换或异常的问题。