你好!在之前的一讲中,我们讨论了原始类型的转换。让我们简单回忆一下我们正在谈论的内容。 我们根据原始类型(在本例中为数字)占用的内存量将它们表示为嵌套娃娃。您还记得,无论是在现实生活中还是在 Java 编程中,将较小的嵌套娃娃放入较大的嵌套娃娃中都是很简单的。
public class Main {
public static void main(String[] args) {
short smallNumber = 100;
int bigNumber = smallNumber;
System.out.println(bigNumber);
}
}
这是自动转换或扩展 的示例。它会自行发生,因此无需编写额外的代码。最后,我们并没有做任何不寻常的事情:我们只是将一个较小的嵌套娃娃放入一个较大的嵌套娃娃中。如果我们反其道而行之,将一个大的俄罗斯套娃放入一个较小的俄罗斯套娃中,那就是另一回事了。这在生活中是做不到的,但是在编程中却可以做到。但有一点需要注意。如果我们尝试将一个值int
放入变量中short
,则不会那么容易。毕竟,变量只能容纳 16 位信息short
,但值却int
需要 32 位!结果,传输的值将会失真。编译器会给我们一个错误(“伙计,你正在做一些可疑的事情! ”),但是如果我们明确指定我们将值转换为什么类型,它仍然会执行这样的操作。
public class Main {
public static void main(String[] args) {
int bigNumber = 10000000;
bigNumber = (short) bigNumber;
System.out.println(bigNumber);
}
}
在上面的例子中,我们就是这样做的。操作已完成,但由于short
32 位中只有 16 位适合该变量,因此最终值被扭曲,结果我们收到了数字-27008。此操作称为显式转换或缩小。
引用类型的扩展和收缩示例
现在我们将讨论相同的操作,但不适用于基本类型,而是适用于对象和引用变量!这在 Java 中是如何工作的?其实很简单。有些对象彼此不相关。假设它们不能显式或自动地相互转换是合乎逻辑的:public class Cat {
}
public class Dog {
}
public class Main {
public static void main(String[] args) {
Cat cat = new Dog();//error!
}
}
当然,我们会得到一个错误。这些类彼此不相关,我们也没有编写一个从一个类到另一个类的“转换器” Cat
。Dog
从逻辑上讲,我们无法做到这一点:编译器不知道如何在这些对象之间进行转换。如果物体相互连接那就是另一回事了!如何?首先,使用继承。让我们尝试创建一个带有继承的小型类系统。我们将有一个代表动物的通用类:
public class Animal {
public void introduce() {
System.out.println("i'm Animal");
}
}
如您所知,动物有家养动物和野生动物:
public class WildAnimal extends Animal {
public void introduce() {
System.out.println("i'm WildAnimal");
}
}
public class Pet extends Animal {
public void introduce() {
System.out.println("i'm Pet");
}
}
以狗为例——一只家狗和一只土狼:
public class Dog extends Pet {
public void introduce() {
System.out.println("i'm Dog");
}
}
public class Coyote extends WildAnimal {
public void introduce() {
System.out.println("i'm Coyote");
}
}
我们的类故意是最原始的,以使它们更容易被感知。我们这里并不真正需要字段,一种方法就足够了。让我们尝试运行以下代码:
public class Main {
public static void main(String[] args) {
Animal animal = new Pet();
animal.introduce();
}
}
你认为控制台会输出什么?introduce
类Pet
或类方法会起作用吗Animal
?在继续阅读之前尝试证明你的答案是正确的。这就是结果! 我是 Pet 为什么答案是这样的?这很简单。我们有一个父变量和一个子对象。通过写:
Animal animal = new Pet();
我们扩展了引用类型Pet
并将其对象存储在变量中Animal
。与原始类型一样,Java 中引用类型的扩展是自动完成的。无需为此编写额外的代码。现在我们有一个子对象附加到父引用,结果我们看到该方法在子类上被调用。如果您仍然不完全理解这段代码的工作原理,请用简单的语言重写它:
Животное животное = new ДомашнееЖивотное();
这没有问题,对吧?想象一下这是现实生活,本例中的链接是一个简单的纸质标签,上面写着“动物”。如果你把这样一张纸贴在任何宠物的项圈上,一切都会好起来的。任何宠物仍然是动物!相反的过程,即沿着继承树向下移动到继承人,是一个缩小范围:
public class Main {
public static void main(String[] args) {
WildAnimal wildAnimal = new Coyote();
Coyote coyote = (Coyote) wildAnimal;
coyote.introduce();
}
}
正如您所看到的,这里我们明确指出我们要将对象转换为哪个类。以前我们有一个变量WildAnimal
,现在有Coyote
,它沿着继承树向下移动。从逻辑上讲,如果没有明确指示,编译器不会跳过此类操作,但如果您在括号中指定类型,则一切都会起作用。 让我们看另一个更有趣的例子:
public class Main {
public static void main(String[] args) {
Pet pet = new Animal();//error!
}
}
编译器会抛出错误!是什么原因? 事实上,您正在尝试将父对象分配给子变量。 换句话说,你想要这样做:
ДомашнееЖивотное домашнееЖивотное = new Животное();
但如果我们明确指出我们试图转换的类型,我们就会成功吗?数字看起来很有效,让我们试试吧!:)
public class Main {
public static void main(String[] args) {
Pet pet = (Pet) new Animal();
}
}
线程“main”中出现异常 java.lang.ClassCastException:动物无法转换为 Pet 错误!这次编译器没有抱怨,但结果我们收到了一个异常。我们已经知道原因:我们正在尝试将父对象分配给子变量。事实上,为什么不能这样做呢? 因为并非所有动物都是宠物。 您创建了一个对象Animal
并尝试将其分配给一个变量Pet
。但是,例如,土狼也是家畜Animal
,但它不是。Pet
换句话说,当你写:
Pet pet = (Pet) new Animal();
new Animal()
任何动物都可以 在那里,而且不一定是家养的!当然,你的变量Pet pet
只适合存储宠物(及其后代),而不适合所有人。因此,对于这种情况,Java 中创建了一个特殊的异常——ClassCastException
转换类时出错。让我们再说一遍,以便更清楚。父变量(引用)可以指向后代类的对象:
public class Main {
public static void main(String[] args) {
Pet pet = new Pet();
Animal animal = pet;
Pet pet2 = (Pet) animal;
pet2.introduce();
}
}
例如,我们在这里不会有任何问题。我们有一个Pet
由链接指向的对象Pet
。然后一个新的链接开始指向同一个对象Animal
。之后我们将其转换animal
为Pet
. 顺便问一下,我们为什么要这样做?上次我们有一个例外!因为这次我们的原始对象是Pet pet
!
Pet pet = new Pet();
在前面的示例中,它是一个对象Animal
:
Pet pet = (Pet) new Animal();
后代变量不能分配给祖先对象。相反,你可以做到。
GO TO FULL VERSION