Object Type Casting in Java – Java中的对象类型投递

最后修改: 2018年 2月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The Java type system is made up of two kinds of types: primitives and references.

Java类型系统是由两种类型组成的:基元和引用。

We covered primitive conversions in this article, and we’ll focus on references casting here to get a good understanding of how Java handles types.

我们在本文中介绍了原始转换,在此我们将重点介绍引用投递,以充分了解Java如何处理类型。

2. Primitive vs Reference

2.原始与参考

Although primitive conversions and reference variable casting may look similar, they’re quite different concepts.

虽然原始转换和引用变量铸造看起来很相似,但它们是相当不同的概念

In both cases, we’re “turning” one type into another. But, in a simplified way, a primitive variable contains its value, and conversion of a primitive variable means irreversible changes in its value:

在这两种情况下,我们都是将一种类型 “转化 “为另一种类型。但是,以一种简化的方式,一个原始变量包含了它的值,而原始变量的转换意味着其值的不可逆转的变化。

double myDouble = 1.1;
int myInt = (int) myDouble;
        
assertNotEquals(myDouble, myInt);

After the conversion in the above example, myInt variable is 1, and we can’t restore the previous value 1.1 from it.

在上面的例子中,经过转换后,myInt变量是1,我们不能从它那里恢复以前的值1.1

Reference variables are different; the reference variable only refers to an object but doesn’t contain the object itself.

引用变量是不同的;引用变量只引用一个对象,但不包含对象本身。

And casting a reference variable doesn’t touch the object it refers to but only labels this object in another way, expanding or narrowing opportunities to work with it. Upcasting narrows the list of methods and properties available to this object, and downcasting can extend it.

而铸造一个引用变量并不触及它所引用的对象,而只是以另一种方式给这个对象打上标签,扩大或缩小与它合作的机会。上投缩小了这个对象可用的方法和属性列表,下投可以扩展它。

A reference is like a remote control to an object. The remote control has more or fewer buttons depending on its type, and the object itself is stored in a heap. When we do casting, we change the type of the remote control but don’t change the object itself.

一个引用就像一个对象的遥控器。遥控器有更多或更少的按钮,这取决于它的类型,而对象本身则存储在一个堆中。当我们做铸造时,我们改变了遥控器的类型,但没有改变对象本身。

3. Upcasting

3.上映

Casting from a subclass to a superclass is called upcasting. Typically, the upcasting is implicitly performed by the compiler.

从子类到超类的转换被称为上转换。通常情况下,上转换是由编译器隐含地执行的。

Upcasting is closely related to inheritance — another core concept in Java. It’s common to use reference variables to refer to a more specific type. And every time we do this, implicit upcasting takes place.

上推法与继承密切相关–这是Java的另一个核心概念。使用引用变量来引用一个更具体的类型是很常见的。每当我们这样做的时候,就会发生隐式上推。

To demonstrate upcasting, let’s define an Animal class:

为了演示上投,让我们定义一个Animal类。

public class Animal {

    public void eat() {
        // ... 
    }
}

Now let’s extend Animal:

现在我们来扩展Animal

public class Cat extends Animal {

    public void eat() {
         // ... 
    }

    public void meow() {
         // ... 
    }
}

Now we can create an object of Cat class and assign it to the reference variable of type Cat:

现在我们可以创建一个Cat类的对象,并将其分配给Cat类型的引用变量。

Cat cat = new Cat();

And we can also assign it to the reference variable of type Animal:

我们也可以把它赋给Animal类型的引用变量。

Animal animal = cat;

In the above assignment, implicit upcasting takes place.

在上述分配中,发生了隐性上报。

We could do it explicitly:

我们可以明确地做到这一点。

animal = (Animal) cat;

But there is no need to do explicit cast up the inheritance tree. The compiler knows that cat is an Animal and doesn’t display any errors.

但是没有必要在继承树上做明确的投递。编译器知道cat是一个Animal,不会显示任何错误。

Note that reference can refer to any subtype of the declared type.

注意,引用可以指代声明类型的任何子类型。

Using upcasting, we’ve restricted the number of methods available to Cat instance but haven’t changed the instance itself. Now we can’t do anything that is specific to Cat — we can’t invoke meow() on the animal variable.

使用上传法,我们限制了Cat实例可用的方法的数量,但没有改变实例本身。现在我们不能做任何专门针对Cat的事情–我们不能在animal变量上调用meow()

Although Cat object remains Cat object, calling meow() would cause the compiler error:

尽管Cat对象仍然是Cat对象,但调用meow()将导致编译器错误。

// animal.meow(); The method meow() is undefined for the type Animal

To invoke meow() we need to downcast animal, and we’ll do this later.

为了调用meow(),我们需要将animal下移,我们将在后面做这个。

But now we’ll describe what gives us the upcasting. Thanks to upcasting, we can take advantage of polymorphism.

但现在我们要描述一下是什么给了我们上纲上线的机会。由于上移,我们可以利用多态性的优势。

3.1. Polymorphism

3.1.多态性

Let’s define another subclass of Animal, a Dog class:

让我们定义Animal的另一个子类,一个Dog类。

public class Dog extends Animal {

    public void eat() {
         // ... 
    }
}

Now we can define the feed() method, which treats all cats and dogs like animals:

现在我们可以定义feed()方法,该方法将所有的猫和狗当作动物

public class AnimalFeeder {

    public void feed(List<Animal> animals) {
        animals.forEach(animal -> {
            animal.eat();
        });
    }
}

We don’t want AnimalFeeder to care about which animal is on the list — a Cat or a Dog. In the feed() method they are all animals.

我们不希望AnimalFeeder关心哪个动物在列表中–一只或一只。在feed()/em>方法中,它们都是动物

Implicit upcasting occurs when we add objects of a specific type to the animals list:

当我们将特定类型的对象添加到animals列表中时,就会发生隐式上推。

List<Animal> animals = new ArrayList<>();
animals.add(new Cat());
animals.add(new Dog());
new AnimalFeeder().feed(animals);

We add cats and dogs, and they are upcast to Animal type implicitly. Each Cat is an Animal and each Dog is an Animal. They’re polymorphic.

我们添加猫和狗,它们被隐含地上传到Animal类型。每个是一个动物,每个是一个动物。它们是多态的。

By the way, all Java objects are polymorphic because each object is an Object at least. We can assign an instance of Animal to the reference variable of Object type and the compiler won’t complain:

顺便说一下,所有的Java对象都是多态的,因为每个对象至少是一个Object。我们可以将一个Animal的实例分配给Object类型的引用变量,编译器不会抱怨。

Object object = new Animal();

That’s why all Java objects we create already have Object-specific methods, for example toString().

这就是为什么我们创建的所有Java对象已经有Object特定的方法,例如toString()

Upcasting to an interface is also common.

上播到一个接口也很常见。

We can create Mew interface and make Cat implement it:

我们可以创建Mew接口并让Cat实现它。

public interface Mew {
    public void meow();
}

public class Cat extends Animal implements Mew {
    
    public void eat() {
         // ... 
    }

    public void meow() {
         // ... 
    }
}

Now any Cat object can also be upcast to Mew:

现在,任何Cat对象也可以被上传到Mew

Mew mew = new Cat();

Cat is a Mew; upcasting is legal and done implicitly.

CatMew;上传是合法的,并且是隐含的。

Therefore, Cat is a Mew, Animal, Object and Cat. It can be assigned to reference variables of all four types in our example.

因此,Cat是一个MewAnimalObjectCat。在我们的例子中,它可以被分配给所有四种类型的引用变量。

3.2. Overriding

3.2.覆盖

In the example above, the eat() method is overridden. This means that although eat() is called on the variable of the Animal type, the work is done by methods invoked on real objects — cats and dogs:

在上面的例子中,eat()方法被重写。这意味着尽管eat()是在Animal类型的变量上调用的,但工作是由真实对象–猫和狗–上调用的方法完成的。

public void feed(List<Animal> animals) {
    animals.forEach(animal -> {
        animal.eat();
    });
}

If we add some logging to our classes, we’ll see that Cat and Dog methods are called:

如果我们给我们的类添加一些日志,我们会看到CatDog方法被调用。

web - 2018-02-15 22:48:49,354 [main] INFO com.baeldung.casting.Cat - cat is eating
web - 2018-02-15 22:48:49,363 [main] INFO com.baeldung.casting.Dog - dog is eating

To sum up:

总结起来:

  • A reference variable can refer to an object if the object is of the same type as a variable or if it is a subtype.
  • Upcasting happens implicitly.
  • All Java objects are polymorphic and can be treated as objects of supertype due to upcasting.

4. Downcasting

4.降调

What if we want to use the variable of type Animal to invoke a method available only to Cat class? Here comes the downcasting. It’s the casting from a superclass to a subclass.

如果我们想用Animal类型的变量来调用一个只有Cat类才能使用的方法,该怎么办?这时就需要降级了。这是从超类到子类的降级。

Let’s look at an example:

我们来看看一个例子。

Animal animal = new Cat();

We know that animal variable refers to the instance of Cat. And we want to invoke Cat’s meow() method on the animal. But the compiler complains that meow() method doesn’t exist for the type Animal.

我们知道animal变量是指Cat的实例。我们想在animal上调用Catmeow()方法。但是编译器抱怨说meow()方法对于Animal类型不存在。

To call meow() we should downcast animal to Cat:

为了调用meow(),我们应该将animal下移为Cat

((Cat) animal).meow();

The inner parentheses and the type they contain are sometimes called the cast operator. Note that external parentheses are also needed to compile the code.

内部小括号和它们所包含的类型有时被称为cast操作符。请注意,外部小括号也需要用来编译代码。

Let’s rewrite the previous AnimalFeeder example with meow() method:

让我们用meow()方法重写之前的AnimalFeeder例子。

public class AnimalFeeder {

    public void feed(List<Animal> animals) {
        animals.forEach(animal -> {
            animal.eat();
            if (animal instanceof Cat) {
                ((Cat) animal).meow();
            }
        });
    }
}

Now we gain access to all methods available to Cat class. Look at the log to make sure that meow() is actually called:

现在我们获得了对Cat类所有可用方法的访问权。看一下日志,确保meow()确实被调用。

web - 2018-02-16 18:13:45,445 [main] INFO com.baeldung.casting.Cat - cat is eating
web - 2018-02-16 18:13:45,454 [main] INFO com.baeldung.casting.Cat - meow
web - 2018-02-16 18:13:45,455 [main] INFO com.baeldung.casting.Dog - dog is eating

Note that in the above example we’re trying to downcast only those objects that are really instances of Cat. To do this, we use the operator instanceof.

请注意,在上面的例子中,我们试图只对那些真正属于Cat实例的对象进行降级。要做到这一点,我们使用操作符instanceof

4.1. instanceof Operator

4.1.instanceof操作符

We often use instanceof operator before downcasting to check if the object belongs to the specific type:

我们经常在下转换之前使用instanceof操作符来检查对象是否属于特定类型。

if (animal instanceof Cat) {
    ((Cat) animal).meow();
}

4.2. ClassCastException

4.2.ClassCastException

If we hadn’t checked the type with the instanceof operator, the compiler wouldn’t have complained. But at runtime, there would be an exception.

如果我们没有用instanceof操作符检查类型,编译器就不会抱怨了。但在运行时,就会出现异常。

To demonstrate this, let’s remove the instanceof operator from the above code:

为了证明这一点,让我们从上面的代码中删除instanceof操作符。

public void uncheckedFeed(List<Animal> animals) {
    animals.forEach(animal -> {
        animal.eat();
        ((Cat) animal).meow();
    });
}

This code compiles without issues. But if we try to run it, we’ll see an exception:

这段代码的编译没有问题。但如果我们试图运行它,我们会看到一个异常。

java.lang.ClassCastException: com.baeldung.casting.Dog cannot be cast to com.baeldung.casting.Cat

java.lang.ClassCastException: com.baeldung.casting.Dog不能被投到com.baeldung.casting.Cat

This means that we are trying to convert an object that is an instance of Dog into a Cat instance.

这意味着我们正试图将一个Dog实例的对象转换成Cat实例。

ClassCastException is always thrown at runtime if the type we downcast to doesn’t match the type of the real object.

ClassCastException总是在运行时被抛出,如果我们下转换的类型与真实对象的类型不匹配。

Note that if we try to downcast to an unrelated type, the compiler won’t allow this:

请注意,如果我们试图下移到一个不相关的类型,编译器将不允许这样做。

Animal animal;
String s = (String) animal;

The compiler says “Cannot cast from Animal to String.”

编译器说:”不能从动物到字符串的转换”。

For the code to compile, both types should be in the same inheritance tree.

为了使代码能够被编译,这两种类型都应该在同一个继承树中。

Let’s sum up:

让我们来总结一下。

  • Downcasting is necessary to gain access to members specific to subclass.
  • Downcasting is done using cast operator.
  • To downcast an object safely, we need instanceof operator.
  • If the real object doesn’t match the type we downcast to, then ClassCastException will be thrown at runtime.

5. cast() Method

5.cast()方法

There’s another way to cast objects using the methods of Class:

还有一种方法可以使用Class的方法来铸造对象。

public void whenDowncastToCatWithCastMethod_thenMeowIsCalled() {
    Animal animal = new Cat();
    if (Cat.class.isInstance(animal)) {
        Cat cat = Cat.class.cast(animal);
        cat.meow();
    }
}

In the above example, cast() and isInstance() methods are used instead of cast and instanceof operators correspondingly.

在上面的例子中,cast()和isInstance()方法被用来代替相应的cast和instanceof操作符。

It’s common to use cast() and isInstance() methods with generic types.

使用cast()isInstance()方法来处理通用类型很常见。

Let’s create AnimalFeederGeneric<T> class with feed() method that “feeds” only one type of animal, cats or dogs, depending on the value of the type parameter:

让我们创建AnimalFeederGeneric<T>类,其feed()方法只 “喂 “一种动物,猫或狗,取决于类型参数的值。

public class AnimalFeederGeneric<T> {
    private Class<T> type;

    public AnimalFeederGeneric(Class<T> type) {
        this.type = type;
    }

    public List<T> feed(List<Animal> animals) {
        List<T> list = new ArrayList<T>();
        animals.forEach(animal -> {
            if (type.isInstance(animal)) {
                T objAsType = type.cast(animal);
                list.add(objAsType);
            }
        });
        return list;
    }

}

The feed() method checks each animal and returns only those that are instances of T.

feed()方法检查每个动物,只返回那些属于T的实例。

Note that the Class instance should also be passed to the generic class, as we can’t get it from the type parameter T. In our example, we pass it in the constructor.

注意,Class实例也应该被传递给泛型类,因为我们不能从类型参数T中得到它。在我们的例子中,我们在构造函数中传递它。

Let’s make T equal to Cat and make sure that the method returns only cats:

让我们让T等于Cat,并确保该方法只返回猫。

@Test
public void whenParameterCat_thenOnlyCatsFed() {
    List<Animal> animals = new ArrayList<>();
    animals.add(new Cat());
    animals.add(new Dog());
    AnimalFeederGeneric<Cat> catFeeder
      = new AnimalFeederGeneric<Cat>(Cat.class);
    List<Cat> fedAnimals = catFeeder.feed(animals);

    assertTrue(fedAnimals.size() == 1);
    assertTrue(fedAnimals.get(0) instanceof Cat);
}

6. Conclusion

6.结论

In this foundational tutorial, we’ve explored upcasting, downcasting, how to use them and how these concepts can help you take advantage of polymorphism.

在这个基础教程中,我们已经探讨了上投、下投、如何使用它们以及这些概念如何帮助你利用多态性。

As always, the code for this article is available over on GitHub.

一如既往,本文的代码可在GitHub上获得over