1. Gambaran keseluruhan
Sistem jenis Java terdiri dari dua jenis jenis: primitif dan rujukan.
Kami merangkumi penukaran primitif dalam artikel ini, dan kami akan memfokuskan pada penutupan rujukan di sini, untuk mendapatkan pemahaman yang baik tentang bagaimana Java menangani jenis.
2. Primitif vs Rujukan
Walaupun penukaran primitif dan pemutus pemboleh ubah rujukan mungkin kelihatan serupa, konsepnya sangat berbeza.
Dalam kedua kes tersebut, kita "mengubah" satu jenis menjadi jenis yang lain. Tetapi, dengan cara yang disederhanakan, pemboleh ubah primitif mengandungi nilainya, dan penukaran pemboleh ubah primitif bermaksud perubahan nilai yang tidak dapat dipulihkan:
double myDouble = 1.1; int myInt = (int) myDouble; assertNotEquals(myDouble, myInt);
Selepas penukaran dalam contoh di atas, pemboleh ubah myInt adalah 1 , dan kami tidak dapat memulihkan nilai sebelumnya 1.1 darinya.
Pemboleh ubah rujukan berbeza ; pemboleh ubah rujukan hanya merujuk kepada objek tetapi tidak mengandungi objek itu sendiri.
Dan menghantar pemboleh ubah rujukan tidak menyentuh objek yang dimaksudkannya, tetapi hanya melabel objek ini dengan cara lain, memperluas atau menyempitkan peluang untuk bekerja dengannya. Upcasting menyempitkan senarai kaedah dan sifat yang tersedia untuk objek ini, dan downcasting dapat memperluasnya.
Rujukan adalah seperti alat kawalan jauh ke objek. Alat kawalan jauh mempunyai butang lebih kurang bergantung pada jenisnya, dan objek itu sendiri disimpan dalam timbunan. Semasa melakukan casting, kita mengubah jenis alat kawalan jauh tetapi tidak mengubah objek itu sendiri.
3. Penyiaran
Transmisi dari subkelas ke superclass disebut upcasting . Biasanya, penyiaran dilakukan secara tidak langsung oleh penyusun.
Penyiaran berkait rapat dengan warisan - konsep teras lain di Jawa. Adalah biasa menggunakan pemboleh ubah rujukan untuk merujuk kepada jenis yang lebih spesifik. Dan setiap kali kita melakukan ini, penyiaran tersirat berlaku.
Untuk menunjukkan peningkatan, mari tentukan kelas Haiwan :
public class Animal { public void eat() { // ... } }
Sekarang mari kita memanjangkan Haiwan :
public class Cat extends Animal { public void eat() { // ... } public void meow() { // ... } }
Sekarang kita boleh membuat objek kelas Cat dan memberikannya kepada pemboleh ubah rujukan jenis Cat :
Cat cat = new Cat();
Dan kita juga dapat menetapkannya ke pemboleh ubah rujukan jenis Haiwan :
Animal animal = cat;
Dalam tugasan di atas, penambahan secara tersirat berlaku. Kami dapat melakukannya dengan jelas:
animal = (Animal) cat;
Tetapi tidak perlu dilakukan secara eksplisit membuang harta pusaka. Penyusun tahu bahawa kucing adalah Haiwan dan tidak menunjukkan kesalahan.
Perhatikan, rujukan itu boleh merujuk kepada subtipe dari jenis yang dinyatakan.
Dengan menggunakan siaran, kami telah membatasi jumlah kaedah yang tersedia untuk instance Cat tetapi tidak mengubah instance itu sendiri. Sekarang kita tidak dapat melakukan sesuatu yang khusus untuk Cat - kita tidak boleh meminta meow () pada pemboleh ubah haiwan .
Walaupun objek Cat tetap menjadi objek Cat , memanggil meow () akan menyebabkan ralat penyusun:
// animal.meow(); The method meow() is undefined for the type Animal
Untuk memanggil meow () kita perlu menurunkan haiwan , dan kita akan melakukannya kemudian.
Tetapi sekarang kita akan menerangkan apa yang memberi kita peningkatan. Terima kasih kepada penyiaran, kami dapat memanfaatkan polimorfisme.
3.1. Polimorfisme
Mari kita tentukan subkelas Haiwan lain , kelas Anjing :
public class Dog extends Animal { public void eat() { // ... } }
Sekarang kita dapat menentukan kaedah feed () yang memperlakukan semua kucing dan anjing seperti haiwan :
public class AnimalFeeder { public void feed(List animals) { animals.forEach(animal -> { animal.eat(); }); } }
Kami tidak mahu AnimalFeeder peduli dengan haiwan mana yang ada dalam senarai - Kucing atau Anjing . Dalam kaedah feed () mereka semua binatang .
Upcasting tersirat berlaku apabila kita menambahkan objek dari jenis tertentu ke senarai haiwan :
List animals = new ArrayList(); animals.add(new Cat()); animals.add(new Dog()); new AnimalFeeder().feed(animals);
Kami menambah kucing dan anjing dan mereka ditingkatkan ke jenis Haiwan secara tidak langsung. Setiap Kucing adalah Haiwan dan setiap Anjing adalah Haiwan . Mereka polimorfik.
By the way, semua objek Java adalah polimorfik kerana setiap objek adalah Objek sekurang-kurangnya. Kami dapat memberikan contoh Animal ke pemboleh ubah rujukan jenis Objek dan penyusun tidak akan mengeluh:
Object object = new Animal();
Itulah sebabnya semua objek Java yang kita buat sudah mempunyai kaedah khusus Objek , misalnya, toString () .
Penyiaran ke antara muka juga biasa.
Kami boleh membuat antara muka Mew dan membuat Cat melaksanakannya:
public interface Mew { public void meow(); } public class Cat extends Animal implements Mew { public void eat() { // ... } public void meow() { // ... } }
Sekarang mana-mana objek Cat juga boleh dinaikkan ke Mew :
Mew mew = new Cat();
Cat adalah Mew , penyiaran adalah sah dan dilakukan secara tidak langsung.
Oleh itu, Kucing adalah Mew , Haiwan , Objek , dan Kucing . Ia boleh ditugaskan untuk merujuk pemboleh ubah keempat-empat jenis dalam contoh kita.
3.2. Menimpa
Dalam contoh di atas, kaedah eat () diganti. Ini bermaksud bahawa walaupun makan () dipanggil pada pemboleh ubah jenis Haiwan , pekerjaan itu dilakukan dengan kaedah yang digunakan pada objek nyata - kucing dan anjing:
public void feed(List animals) { animals.forEach(animal -> { animal.eat(); }); }
If we add some logging to our classes, we'll see that Cat’s and Dog’s methods are called:
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
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.
Let’s take 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.
To call meow() we should downcast animal to 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.
Let’s rewrite the previous AnimalFeeder example with meow() method:
public class AnimalFeeder { public void feed(List 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:
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 which are really instances of Cat. To do this, we use the operator instanceof.
4.1. instanceof Operator
We often use instanceof operator before downcasting to check if the object belongs to the specific type:
if (animal instanceof Cat) { ((Cat) animal).meow(); }
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.
To demonstrate this let’s remove the instanceof operator from the above code:
public void uncheckedFeed(List 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
This means that we are trying to convert an object which is an instance of Dog into a Cat instance.
ClassCastException's always thrown at runtime if the type we downcast to doesn't match the type of the real object.
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
There's another way to cast objects using the methods of 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.
It's common to use cast() and isInstance() methods with generic types.
Let's create AnimalFeederGeneric class with feed() method which “feeds” only one type of animals – cats or dogs, depending on the value of the type parameter:
public class AnimalFeederGeneric { private Class type; public AnimalFeederGeneric(Class type) { this.type = type; } public List feed(List animals) { List list = new ArrayList(); 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 which are instances of 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.
Let's make T equal to Cat and make sure that the method returns only cats:
@Test public void whenParameterCat_thenOnlyCatsFed() { List animals = new ArrayList(); animals.add(new Cat()); animals.add(new Dog()); AnimalFeederGeneric catFeeder = new AnimalFeederGeneric(Cat.class); List fedAnimals = catFeeder.feed(animals); assertTrue(fedAnimals.size() == 1); assertTrue(fedAnimals.get(0) instanceof Cat); }
6. Conclusion
Dalam tutorial asas ini, kami telah meneroka apa itu upcasting, downcasting, bagaimana menggunakannya dan bagaimana konsep-konsep ini dapat membantu anda memanfaatkan polimorfisme.
Seperti biasa, kod untuk artikel ini terdapat di GitHub.