Pengecoran Jenis Objek di Jawa

1. Ikhtisar

Sistem tipe Java terdiri dari dua jenis tipe: primitif dan referensi.

Kami membahas konversi primitif dalam artikel ini, dan kami akan fokus pada transmisi referensi di sini, untuk mendapatkan pemahaman yang baik tentang bagaimana Java menangani tipe.

2. Primitif vs. Referensi

Meskipun konversi primitif dan transmisi variabel referensi mungkin terlihat serupa, keduanya merupakan konsep yang sangat berbeda.

Dalam kedua kasus tersebut, kami "mengubah" satu jenis menjadi jenis lainnya. Namun, dengan cara yang disederhanakan, variabel primitif berisi nilainya, dan konversi variabel primitif berarti perubahan yang tidak dapat diubah nilainya:

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

Setelah konversi pada contoh di atas, variabel myInt adalah 1 , dan kami tidak dapat mengembalikan nilai sebelumnya 1,1 darinya.

Variabel referensi berbeda ; variabel referensi hanya mengacu pada sebuah objek tetapi tidak berisi objek itu sendiri.

Dan mentransmisikan variabel referensi tidak menyentuh objek yang dirujuknya, tetapi hanya memberi label pada objek ini dengan cara lain, memperluas atau mempersempit peluang untuk bekerja dengannya. Upcasting mempersempit daftar metode dan properti yang tersedia untuk objek ini, dan downcasting dapat memperluasnya.

Referensi seperti remote control ke suatu objek. Remote control memiliki lebih banyak atau lebih sedikit tombol tergantung pada tipenya, dan objek itu sendiri disimpan dalam heap. Saat kami melakukan transmisi, kami mengubah jenis remote control tetapi tidak mengubah objeknya sendiri.

3. Upcasting

Mentransmisikan dari subclass ke superclass disebut upcasting . Biasanya, upcasting dilakukan secara implisit oleh kompilator.

Upcasting terkait erat dengan pewarisan - konsep inti lain di Jawa. Variabel referensi biasanya digunakan untuk merujuk ke tipe yang lebih spesifik. Dan setiap kali kita melakukan ini, upcasting implisit terjadi.

Untuk mendemonstrasikan upcasting, mari kita definisikan kelas Hewan :

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

Sekarang mari kita kembangkan Hewan :

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

Sekarang kita dapat membuat objek kelas Cat dan menugaskannya ke variabel referensi tipe Cat :

Cat cat = new Cat();

Dan kita juga bisa menugaskannya ke variabel referensi tipe Animal :

Animal animal = cat;

Dalam tugas di atas, upcasting implisit terjadi. Kami dapat melakukannya secara eksplisit:

animal = (Animal) cat;

Tapi tidak perlu melakukan cast up secara eksplisit pada pohon warisan. Kompilator mengetahui bahwa kucing adalah Hewan dan tidak menampilkan kesalahan apa pun.

Perhatikan, referensi itu bisa merujuk ke subtipe apa pun dari tipe yang dideklarasikan.

Dengan menggunakan upcasting, kami telah membatasi jumlah metode yang tersedia untuk instans Cat tetapi belum mengubah instans itu sendiri. Sekarang kita tidak bisa melakukan apa pun yang khusus untuk Cat - kita tidak bisa memanggil meong () pada variabel hewan .

Meskipun objek Cat tetap menjadi objek Cat , memanggil meow () akan menyebabkan kesalahan compiler:

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

Untuk memohon meong () kita perlu merendahkan hewan , dan kita akan melakukannya nanti.

Tapi sekarang kami akan menjelaskan apa yang memberi kami upcasting. Berkat upcasting, kita bisa memanfaatkan polimorfisme.

3.1. Polimorfisme

Mari kita definisikan subkelas Hewan lainnya , kelas Anjing :

public class Dog extends Animal { public void eat() { // ... } }

Sekarang kita bisa mendefinisikan metode feed () yang memperlakukan semua kucing dan anjing seperti hewan :

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

Kami tidak ingin AnimalFeeder peduli tentang hewan mana yang ada dalam daftar - Kucing atau Anjing . Dalam metode feed () mereka semua adalah hewan .

Upcasting implisit terjadi saat kita menambahkan objek dari tipe tertentu ke daftar hewan :

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

Kami menambahkan kucing dan anjing dan mereka terserap ke jenis Hewan secara implisit. Setiap Kucing adalah Hewan dan setiap Anjing adalah Hewan . Mereka polimorfik.

Omong-omong, semua objek Java bersifat polimorfik karena setiap objek setidaknya adalah Objek . Kita dapat menetapkan sebuah instance Animal ke variabel referensi tipe Object dan compiler tidak akan mengeluh:

Object object = new Animal();

Itu sebabnya semua objek Java yang kita buat sudah memiliki metode spesifik Objek , misalnya, toString () .

Upcasting ke sebuah antarmuka juga umum.

Kita dapat membuat antarmuka Mew dan membuat Cat mengimplementasikannya:

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

Sekarang objek Cat juga bisa dikirim ke Mew :

Mew mew = new Cat();

Kucing adalah Mew , upcasting adalah legal dan dilakukan secara implisit.

Jadi, Kucing adalah Mew , Hewan , Objek , dan Kucing . Itu dapat ditugaskan ke variabel referensi dari keempat jenis dalam contoh kita.

3.2. Utama

Dalam contoh di atas, metode eat () diganti. Ini berarti bahwa meskipun eat () dipanggil pada variabel tipe Hewan , pekerjaan dilakukan dengan metode yang dipanggil 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 dasar ini, kita telah menjelajahi apa itu upcasting, downcasting, bagaimana menggunakannya dan bagaimana konsep-konsep ini dapat membantu Anda memanfaatkan polimorfisme.

Seperti biasa, kode untuk artikel ini tersedia di GitHub.