参考:Java:什么是向上转型与向下转型(详细图解)_java转型-优快云博客
向上转型:
创建子类对象,将其当成父类对象进行使用,即将一个子类对象赋值给其父类的引用变量。
例子:
class Aminal {
public void display() {
System.out.println("Animal");
}
class Cat extends Aminal {
public void display() {
System.out.println("Cat");
}
}
class Dog extends Aminal {
}
public class Main{
public static void main(String[] args) {
Aminal aminal1 = new Aminal();
Aminal aminal2 = new Cat(); //向上转型
Aminal aminal3 = new Dog(); //向上转型
aminal1.display(); //输出:Animal
aminal2.display(); //输出:Cat
aminal3.display(); //输出:Animal
}
animal2中,Cat类 重写了 display方法,所以在实现时,打印的是Cat类中实现的内容。
animal3中,Dog类 没有重写 display方法,所以打印的还是父类中的内容。
由此我们可以得出:向上转型实现时
先看子类有没有
若是子类找不到
再看父类有没有
二者都无则报错!
使用场景:
1、当需要编写能够处理多种类型对象的通用代码时,尤其是这些对象共享一个共同的父类或接口。向上转型允许你使用父类或接口引用来指向子类对象,从而实现对不同子类的统一处理。
例子:考虑一个图形编辑器应用程序,其中包含多种形状(Shape),如圆形(Circle)、矩形(Rectangle)。所有形状都共享某些基本操作,如绘制(draw)自己。因此,可以定义一个基类Shape
,并在子类中实现这些操作。
abstract class Shape {
abstract void draw();
}
class Circle extends Shape {
void draw() {
System.out.println("Drawing Circle");
}
}
class Rectangle extends Shape {
void draw() {
System.out.println("Drawing Rectangle");
}
}
public class Editor {
public static void main(String[] args) {
Shape shape1 = new Circle(); // 向上转型
Shape shape2 = new Rectangle(); // 向上转型
shape1.draw(); // 根据实际类型调用不同方法
shape2.draw();
}
}
2、如果你的方法需要接受多种不同类型但有共同父类或实现了相同接口的对象时,可以将参数类型声明为父类类型或接口类型。这样,任何子类的对象都可以被传递给这个方法。
例子:一个printShapeInfo
方法,用于打印任何形状的信息,但不必知道具体是哪种形状。
void printShapeInfo(Shape shape) { // 参数为Shape类型,接受任何形状的子类对象
shape.draw();
}
// 调用示例
printShapeInfo(new Circle());
printShapeInfo(new Rectangle());
3、在使用集合(如List、Set)或数组存储对象时,为了能够容纳不同子类的对象,通常会将集合或数组的类型声明为它们的父类或实现的接口。
例子:创建一个形状列表,可以存储不同类型的形状对象。
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle());
shapes.add(new Rectangle());
for (Shape s : shapes) {
s.draw(); // 遍历列表,调用每个形状的draw方法
}
4、在设计系统时,可能一开始并不确定具体使用哪个子类,或者为了减少代码对具体实现的依赖,会倾向于使用父类或接口引用,这样可以在不修改现有代码的情况下,灵活替换子类实现。
abstract class Animal {
abstract void eat();
}
class Dog extends Animal {
void eat() {
System.out.println("Dog eats bones.");
}
}
class Cat extends Animal {
void eat() {
System.out.println("Cat eats fish.");
}
}
public class Shelter {
void feedAnimal(Animal animal) { // 接受任何动物类型
animal.eat();
}
public static void main(String[] args) {
Shelter shelter = new Shelter();
shelter.feedAnimal(new Dog()); // 向上转型
shelter.feedAnimal(new Cat()); // 向上转型
}
}
优点:让代码实现更简单灵活
缺点:不能调用到子类特有的方法
向下转型:
在Java中,向下转型(Downcasting)是指将父类引用转换为其子类类型的过程。这通常在你知道对象的实际类型是某个特定子类,并且你需要访问该子类特有的方法或属性时使用。
将一个子类对象向上转型之后可以当成父类对象使用,若需要调用子类特有的方法,则需要将父类对象再还原为子类对象。这就称作向下转型。
例子:
class Animal {
public void display() {
System.out.println("Animal");
}
}
class Dog extends Animal {
public void display() {
System.out.println("dog");
}
public void eat() {
System.out.println("吃骨头");
}
}
public class Main{
public static void main(String[] args) {
//向上转型
Animal animal = new Dog();
animal.display();
//向下转型
//Animal类中原本没有 eat方法,在向下转型之前如果调用eat方法会报错
//向下转型为子类Dog类后,就可以调用子类中特有的方法,而不会报错
animal = (Dog)animal;
((Dog) animal).eat();
}
}
instanceof的使用
Java中为了提高向下转型的安全性,引入了instanceof。如果表达式为 true,则可以安全转换。
class Animal {
public void display() {
System.out.println("Animal");
}
}
class Dog extends Animal {
public void display() {
System.out.println("dog");
}
public void eat() {
System.out.println("吃骨头");
}
}
public class Main {
public static void main(String[] args) {
//向上转型
Animal animal = new Dog();
//判断instanceof 是否为 true
if(animal instanceof Dog) {
//向下转型
animal = (Dog)animal;
((Dog) animal).eat();
} else {
System.out.println("Animal无法向下转型为Dog");
}
}
}
为什么需要向下转型?
-
访问子类特有功能:当你有一个基类引用,但实际上它指向的是一个子类对象,而你想调用子类中定义的额外方法或属性时,就需要向下转型。
-
提高代码可读性和明确性:尽管可以直接通过基类引用调用子类重写的方法,但向下转型可以使代码意图更清晰,表明你期望并知道引用的真实类型。
缺点:向下转型使用的比较少,而且不安全。如果转换失败,运行时就会抛异常。
强转:
在Java中,"强转"(类型转换的俗称)指的是显式地将一个数据类型转换为另一个兼容的数据类型,特别是当编译器无法自动完成类型转换时。Java提供了两种类型的转换:宽化转换(也称作隐式转换)和窄化转换(也称作显式转换或强转)。
宽化转换(Implicit Casting)
宽化转换不需要显式的转换操作符,它发生在从较小的数据类型转换到较大数据类型时,例如从int
转换到long
,从byte
转换到int
等。这种转换不会导致数据丢失。
窄化转换(Explicit Casting / Casting)
窄化转换,也就是强转,发生在需要将较大数据类型转换为较小数据类型时,比如把double
转换为int
,或者把父类对象转换为子类类型。因为这种转换可能导致数据丢失或不精确,所以Java要求程序员明确指示这一操作,使用括号语法来显式地强制类型转换。
1、基本类型之间的强转
double d = 10.5;
int i = (int) d; // 显式强转,可能会丢失小数部分
System.out.println(i); // 输出10,小数部分被截断
2、对象类型的转换
//创建一个String类型的对象,然后把这个String类型的对象,赋值给Object类型的对象,且其变量名为obj
Object obj = new String("Hello");
//这里进行了一个显式的类型转换(强转),将Object类型的引用obj转换为String类型的引用st
String str = (String) obj; // 显式强转,这里假设我们知道obj实际是指向String对象的
System.out.println(str);
3、继承关系中的强转
Animal animal = new Dog(); // Dog是Animal的子类
Dog doggy = (Dog) animal; // 向下转型,只有确定animal实际是Dog实例时才能这样做
doggy.bark(); // 假设Dog类有bark方法
在Java中,使用强制类型转换(强转)的情况主要包括以下几种:
-
基本类型之间的窄化转换: 当你需要将一个范围较大的数据类型转换为范围较小的数据类型时,如将
double
类型转换为int
类型,这时必须进行强制类型转换,因为这种转换可能会导致数据丢失(如小数部分被截断)。 -
对象类型的向下转型: 当你有一个父类或接口类型的引用指向一个子类对象,而你需要调用子类特有的方法或访问子类的成员变量时,需要将父类引用强制转换为子类类型。但在此之前,应该使用
instanceof
关键字检查确保转换是安全的,以避免ClassCastException
异常。 -
原始类型与包装类之间的转换: Java中的原始类型(如
int
,double
)与它们对应的包装类(如Integer
,Double
)之间也可以通过强转相互转换。例如,将Integer
对象转换为int
基本类型,反之亦然。 -
字符和整数间的转换: 字符类型(
char
)可以被转换为整数类型(如int
),反之亦然,因为字符本质上也是基于Unicode编码的整数。这类转换有时也需要显式强转。