Java Class.cast () vs. operator cast

107

Setelah diajari selama hari-hari C ++ saya tentang kejahatan operator cor gaya-C, saya senang pada awalnya menemukan bahwa di Java 5 java.lang.Classtelah memperoleh castmetode.

Saya pikir akhirnya kami memiliki cara OO untuk menangani casting.

Ternyata Class.casttidak sama dengan static_castdi C ++. Ini lebih seperti reinterpret_cast. Ini tidak akan menghasilkan kesalahan kompilasi seperti yang diharapkan dan sebagai gantinya akan menunda waktu proses. Berikut adalah kasus pengujian sederhana untuk menunjukkan perilaku yang berbeda.

package test;

import static org.junit.Assert.assertTrue;

import org.junit.Test;


public class TestCast
{
    static final class Foo
    {
    }

    static class Bar
    {
    }

    static final class BarSubclass
        extends Bar
    {
    }

    @Test
    public void test ( )
    {
        final Foo foo = new Foo( );
        final Bar bar = new Bar( );
        final BarSubclass bar_subclass = new BarSubclass( );

        {
            final Bar bar_ref = bar;
        }

        {
            // Compilation error
            final Bar bar_ref = foo;
        }
        {
            // Compilation error
            final Bar bar_ref = (Bar) foo;
        }

        try
        {
            // !!! Compiles fine, runtime exception
            Bar.class.cast( foo );
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }

        {
            final Bar bar_ref = bar_subclass;
        }

        try
        {
            // Compiles fine, runtime exception, equivalent of C++ dynamic_cast
            final BarSubclass bar_subclass_ref = (BarSubclass) bar;
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }
    }
}

Jadi, inilah pertanyaan saya.

  1. Haruskah Class.cast()dibuang ke tanah Generik? Di sana ia memiliki beberapa kegunaan yang sah.
  2. Haruskah kompiler menghasilkan kesalahan kompilasi saat Class.cast()digunakan dan kondisi ilegal dapat ditentukan pada waktu kompilasi?
  3. Haruskah Java menyediakan operator cast sebagai konstruksi bahasa yang mirip dengan C ++?
Alexander Pogrebnyak
sumber
4
Jawaban sederhana: (1) Dimanakah "tanah Generik"? Apa bedanya dengan cara operator cor digunakan sekarang? (2) Mungkin. Tetapi dalam 99% dari semua kode Java yang pernah ditulis, sangat tidak mungkin bagi siapa pun untuk menggunakan Class.cast()ketika kondisi ilegal dapat ditentukan pada waktu kompilasi. Dalam hal ini, semua orang kecuali Anda hanya menggunakan operator transmisi standar. (3) Java memang memiliki operator cast sebagai konstruksi bahasa. Ini tidak mirip dengan C ++. Itu karena banyak konstruksi bahasa Java tidak mirip dengan C ++. Terlepas dari kemiripan yang dangkal, Java dan C ++ sangat berbeda.
Daniel Pryden

Jawaban:

117

Saya hanya pernah Class.cast(Object)menghindari peringatan di "tanah generik". Saya sering melihat metode melakukan hal-hal seperti ini:

@SuppressWarnings("unchecked")
<T> T doSomething() {
    Object o;
    // snip
    return (T) o;
}

Seringkali yang terbaik adalah menggantinya dengan:

<T> T doSomething(Class<T> cls) {
    Object o;
    // snip
    return cls.cast(o);
}

Itulah satu-satunya kasus penggunaan yang Class.cast(Object)pernah saya temui.

Mengenai peringatan kompilator: Saya menduga itu Class.cast(Object)tidak khusus untuk kompilator. Ini dapat dioptimalkan ketika digunakan secara statis (yaitu Foo.class.cast(o)daripada cls.cast(o)) tetapi saya belum pernah melihat ada orang yang menggunakannya - yang membuat upaya membangun pengoptimalan ini ke dalam kompiler agak tidak berguna.

sfussenegger.dll
sumber
8
Saya pikir cara yang benar dalam kasus ini adalah melakukan contoh pemeriksaan terlebih dahulu seperti ini: if (cls.isInstance (o)) {return cls.cast (o); } Kecuali jika Anda yakin jenisnya akan benar, tentunya.
Puce
1
Mengapa varian kedua lebih baik? Bukankah varian pertama lebih efisien karena kode pemanggil akan melakukan transmisi dinamis?
pengguna1944408
1
@ user1944408 selama Anda secara tegas berbicara tentang kinerja, hal itu mungkin saja terjadi, meskipun cukup banyak dapat diabaikan. Saya tidak akan menyukai gagasan mendapatkan ClassCastExceptions di mana tidak ada kasus yang jelas. Selain itu, yang kedua bekerja lebih baik di mana kompilator tidak dapat menyimpulkan T, misalnya list.add (this. <String> doSomething ()) vs. list.add (doSomething (String.class))
sfussenegger
2
Tetapi Anda masih bisa mendapatkan ClassCastExceptions saat memanggil cls.cast (o) saat Anda tidak mendapatkan peringatan apa pun dalam waktu kompilasi. Saya lebih suka varian pertama tetapi saya akan menggunakan varian kedua ketika saya perlu misalnya untuk mengembalikan null jika saya tidak dapat melakukan casting. Dalam hal ini saya akan membungkus cls.cast (o) dalam blok coba tangkap. Saya juga setuju dengan Anda bahwa ini juga lebih baik ketika kompiler tidak dapat menyimpulkan T. Terima kasih atas jawabannya.
pengguna1944408
1
Saya juga menggunakan varian kedua ketika saya membutuhkan Kelas <T> cls menjadi dinamis, misalnya jika saya menggunakan refleksi tetapi dalam semua kasus lain saya lebih suka yang pertama. Saya rasa itu hanya selera pribadi.
pengguna1944408
20

Pertama, Anda sangat tidak disarankan untuk melakukan hampir semua pemeran, jadi Anda harus membatasinya sebanyak mungkin! Anda kehilangan keuntungan dari fitur waktu kompilasi Java yang sangat diketik.

Bagaimanapun, Class.cast()harus digunakan terutama saat Anda mengambil Classtoken melalui refleksi. Menulis itu lebih idiomatis

MyObject myObject = (MyObject) object

daripada

MyObject myObject = MyObject.class.cast(object)

EDIT: Kesalahan pada waktu kompilasi

Secara keseluruhan, Java melakukan pemeriksaan cast hanya pada waktu proses. Namun, kompilator dapat mengeluarkan kesalahan jika ia dapat membuktikan bahwa transmisi tersebut tidak akan pernah berhasil (mis. Mentransmisikan kelas ke kelas lain yang bukan supertipe dan mentransmisikan tipe kelas akhir ke kelas / antarmuka yang tidak ada dalam hierarki tipenya). Di sini karena Foodan Barmerupakan kelas yang tidak berada dalam hierarki satu sama lain, pemeran tidak akan pernah berhasil.

notnoop
sumber
Saya sangat setuju bahwa cast harus digunakan dengan hemat, itulah mengapa memiliki sesuatu yang dapat dengan mudah dicari akan sangat bermanfaat untuk pemfaktoran ulang / pemeliharaan kode. Tetapi masalahnya adalah bahwa meskipun pada pandangan pertama Class.casttampaknya sesuai dengan tagihan, hal itu menciptakan lebih banyak masalah daripada menyelesaikannya.
Alexander Pogrebnyak
16

Selalu bermasalah dan sering menyesatkan untuk mencoba menerjemahkan konstruksi dan konsep antar bahasa. Transmisi tidak terkecuali. Terutama karena Java adalah bahasa dinamis dan C ++ agak berbeda.

Semua casting di Java, tidak peduli bagaimana Anda melakukannya, dilakukan saat runtime. Jenis informasi disimpan saat runtime. C ++ lebih merupakan campuran. Anda dapat mentransmisikan struct di C ++ ke yang lain dan itu hanyalah interpretasi ulang dari byte yang mewakili struct tersebut. Java tidak bekerja seperti itu.

Juga generik di Java dan C ++ sangat berbeda. Jangan terlalu memikirkan cara Anda melakukan C ++ di Java. Anda perlu mempelajari cara melakukan berbagai hal dengan cara Java.

cletus
sumber
Tidak semua informasi disimpan saat runtime. Seperti yang Anda lihat dari contoh saya (Bar) foomemang menghasilkan kesalahan pada waktu kompilasi, tetapi Bar.class.cast(foo)tidak. Menurut saya jika digunakan dengan cara seperti ini seharusnya.
Alexander Pogrebnyak
5
@Alexander Pogrebnyak: Jangan lakukan itu! Bar.class.cast(foo)secara eksplisit memberi tahu compiler bahwa Anda ingin melakukan cast saat runtime. Jika Anda ingin waktu kompilasi memeriksa validitas cast, satu-satunya pilihan Anda adalah melakukan (Bar) foostyle cast.
Daniel Pryden
Menurut Anda apa cara melakukan hal yang sama? karena java tidak mendukung pewarisan kelas ganda.
Yamur
13

Class.cast()jarang digunakan dalam kode Java. Jika digunakan maka biasanya dengan tipe yang hanya diketahui saat runtime (mis. Melalui masing-masingClass objek dan oleh beberapa parameter tipe). Ini hanya sangat berguna dalam kode yang menggunakan obat generik (itu juga alasan mengapa tidak diperkenalkan sebelumnya).

Ini tidak mirip dengan reinterpret_cast, karena ini tidak akan memungkinkan Anda untuk merusak sistem tipe pada saat runtime lebih dari yang dilakukan cast normal (yaitu Anda dapat merusak parameter tipe generik, tetapi tidak dapat merusak tipe "nyata").

Kejahatan operator cor gaya-C umumnya tidak berlaku untuk Java. Kode Java yang terlihat seperti cor gaya-C paling mirip dengan filedynamic_cast<>() dengan tipe referensi di Java (ingat: Java memiliki informasi tipe runtime).

Umumnya membandingkan operator casting C ++ dengan casting Java cukup sulit karena di Java Anda hanya dapat memberikan referensi dan tidak ada konversi yang pernah terjadi pada objek (hanya nilai primitif yang dapat dikonversi menggunakan sintaks ini).

Joachim Sauer
sumber
dynamic_cast<>()dengan tipe referensi.
Tom Hawtin - tackline
@ Tom: apakah hasil edit itu benar? C ++ saya sangat berkarat, saya harus banyak melakukan re-google ;-)
Joachim Sauer
+1 untuk: "Kejahatan operator cor gaya-C umumnya tidak berlaku untuk Java." Benar sekali. Saya baru saja akan memposting kata-kata itu sebagai komentar atas pertanyaan itu.
Daniel Pryden
4

Umumnya operator cor lebih disukai daripada metode cor Kelas # karena lebih ringkas dan dapat dianalisis oleh kompiler untuk mengeluarkan masalah mencolok dengan kode.

Class # cast bertanggung jawab untuk pemeriksaan tipe pada saat run-time daripada selama kompilasi.

Tentu ada kasus penggunaan untuk cast Kelas #, terutama dalam hal operasi reflektif.

Sejak lambda datang ke java, saya pribadi suka menggunakan Class # cast dengan API koleksi / aliran jika saya bekerja dengan tipe abstrak, misalnya.

Dog findMyDog(String name, Breed breed) {
    return lostAnimals.stream()
                      .filter(Dog.class::isInstance)
                      .map(Dog.class::cast)
                      .filter(dog -> dog.getName().equalsIgnoreCase(name))
                      .filter(dog -> dog.getBreed() == breed)
                      .findFirst()
                      .orElse(null);
}
Caleb
sumber
3

C ++ dan Java adalah bahasa yang berbeda.

Operator cast gaya-C Java jauh lebih terbatas daripada versi C / C ++. Secara efektif cast Java seperti C ++ dynamic_cast jika objek yang Anda miliki tidak dapat ditransmisikan ke kelas baru, Anda akan mendapatkan pengecualian waktu proses (atau jika ada cukup informasi dalam kode untuk waktu kompilasi). Jadi ide C ++ untuk tidak menggunakan cast tipe C bukanlah ide yang bagus di Java

mmmmmm
sumber
0

Selain untuk menghapus peringatan cast jelek seperti yang paling banyak disebutkan, Class.cast adalah cast run-time yang sebagian besar digunakan dengan casting generik, karena info generik akan dihapus pada saat run time dan beberapa bagaimana setiap generik akan dianggap Object, ini mengarah ke tidak melempar ClassCastException awal.

misalnya serviceLoder menggunakan trik ini saat membuat objek, centang S p = service.cast (c.newInstance ()); ini akan memunculkan eksepsi cast kelas ketika SP = (S) c.newInstance (); tidak akan dan mungkin menampilkan peringatan 'Type safety: Unchecked cast from Object to S' . (sama seperti Object P = (Object) c.newInstance ();)

-sederhana itu memeriksa bahwa objek yang dicor adalah turunan dari kelas pengecoran kemudian itu akan menggunakan operator cor untuk melemparkan dan menyembunyikan peringatan dengan menekannya.

implementasi java untuk cast dinamis:

@SuppressWarnings("unchecked")
public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
        throw new ClassCastException(cannotCastMsg(obj));
    return (T) obj;
}




    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                 "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service,
                 "Provider " + cn  + " not a subtype");
        }
        try {
            S p = service.cast(c.newInstance());
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                 "Provider " + cn + " could not be instantiated",
                 x);
        }
        throw new Error();          // This cannot happen
    }
Amjad Abdul-Ghani
sumber
0

Secara pribadi, saya telah menggunakan ini sebelumnya untuk membangun konverter JSON ke POJO. Jika JSONObject diproses dengan fungsi berisi larik atau JSONObjects bersarang (menyiratkan bahwa data di sini bukan dari tipe primitif atau String), saya mencoba untuk memanggil metode penyetel menggunakan class.cast()cara ini:

public static Object convertResponse(Class<?> clazz, JSONObject readResultObject) {
    ...
    for(Method m : clazz.getMethods()) {
        if(!m.isAnnotationPresent(convertResultIgnore.class) && 
            m.getName().toLowerCase().startsWith("set")) {
        ...
        m.invoke(returnObject,  m.getParameters()[0].getClass().cast(convertResponse(m.getParameters()[0].getType(), readResultObject.getJSONObject(key))));
    }
    ...
}

Tidak yakin apakah ini sangat membantu, tetapi seperti yang dikatakan di sini sebelumnya, refleksi adalah salah satu dari sedikit kasus penggunaan sah yang class.cast()dapat saya pikirkan, setidaknya Anda memiliki contoh lain sekarang.

Wep0n
sumber