Java Class.cast () ile cast operatörü karşılaştırması


107

Ben Java 5 bulmak için ilk başta memnun etti C tarzı döküm operatörü kötülükleri hakkında benim C ++ gün boyunca öğretilen olması java.lang.Classbir edinmişti castyöntemi.

Sonunda oyuncu kadrosuyla başa çıkmak için bir OO yöntemimiz olduğunu düşündüm.

C ++ 'daki ile Class.castaynı olmadığı ortaya çıktı static_cast. Daha çok benziyor reinterpret_cast. Beklendiği yerde bir derleme hatası oluşturmayacak ve bunun yerine çalışma zamanını erteleyecektir. İşte farklı davranışları göstermek için basit bir test durumu.

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 );
        }
    }
}

İşte sorularım bunlar.

  1. Meli Class.cast()Jenerik arazi sürülecek? Orada epeyce meşru kullanımları vardır.
  2. Derleyiciler Class.cast()kullanıldığında derleme hataları üretmeli mi ve derleme sırasında yasadışı koşullar belirlenebilir mi?
  3. Java, C ++ 'ya benzer bir dil yapısı olarak bir döküm operatörü sağlamalı mı?

4
Basit cevap: (1) "Jenerikler ülkesi" nerede? Bu, döküm operatörünün şu anda kullanıldığından ne kadar farklı? (2) Muhtemelen. Ancak şimdiye kadar yazılan tüm Java kodlarının% 99'unda , derleme sırasında yasa dışı koşullar belirlendiğinde kimsenin kullanması son derece düşük bir ihtimaldir Class.cast(). Bu durumda, siz hariç herkes standart döküm operatörünü kullanır. (3) Java'nın dil yapısı olarak bir çevirme operatörü vardır. C ++ 'ya benzemez. Bunun nedeni, Java'nın dil yapılarının çoğunun C ++ 'ya benzememesidir. Yüzeysel benzerliklere rağmen, Java ve C ++ oldukça farklıdır.
Daniel Pryden

Yanıtlar:


117

Ben sadece Class.cast(Object)"jenerik ilaç ülkesinde" uyarılardan kaçınırdım. Sıklıkla böyle şeyler yapan yöntemler görüyorum:

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

Bunu şu şekilde değiştirmek genellikle en iyisidir:

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

Class.cast(Object)Şimdiye kadar karşılaştığım tek kullanım durumu bu .

Derleyici uyarılarıyla ilgili olarak: Derleyiciye Class.cast(Object)özel olmadığından şüpheleniyorum . Statik olarak kullanıldığında optimize edilebilir (yani Foo.class.cast(o)yerine cls.cast(o)), ancak onu kullanan hiç kimseyi görmedim - bu da bu optimizasyonu derleyicide oluşturma çabasını biraz değersiz kılıyor.


8
Sanırım bu durumda doğru yol, ilk önce şu şekilde bir kontrol örneği yapmak olacaktır: if (cls.isInstance (o)) {return cls.cast (o); } Tabi ki türün doğru olacağından eminseniz hariç.
Puce

1
İkinci değişken neden daha iyi? Arayanın kodu yine de dinamik yayın yapacağı için ilk değişken daha verimli değil mi?
user1944408

1
@ user1944408, performans hakkında kesinlikle konuştuğunuz sürece, oldukça önemsiz de olsa, olabilir. Yine de bariz bir durumun olmadığı durumlarda ClassCastExceptions alma fikrinden hoşlanmam. Ek olarak, ikincisi, derleyicinin T'yi çıkaramadığı durumlarda daha iyi çalışır, örneğin list.add (this. <String> doSomething ()) vs. list.add (doSomething (String.class))
sfussenegger

2
Ancak, derleme zamanında herhangi bir uyarı almadığınız halde cls.cast (o) 'i çağırdığınızda ClassCastExceptions alabilirsiniz. İlk varyantı tercih ederim, ancak örneğin döküm yapamıyorsam null döndürmem gerektiğinde ikinci varyantı kullanırım. Bu durumda cls.cast (o) 'yi bir try catch bloğuna sarardım. Ayrıca, derleyici T'yi çıkaramadığında bunun daha iyi olacağı konusunda size katılıyorum. Cevabınız için teşekkürler.
user1944408

1
Sınıf <T> cls'nin dinamik olmasına ihtiyaç duyduğumda ikinci değişkeni de kullanıyorum, örneğin yansıma kullanıyorsam ama diğer tüm durumlarda ilkini tercih ediyorum. Sanırım bu sadece kişisel bir zevk.
user1944408

20

Birincisi, hemen hemen her oyuncu kadrosunu yapmaktan kesinlikle çekiniyorsunuz, bu yüzden mümkün olduğunca sınırlandırmalısınız! Java'nın derleme zamanı kesin yazılmış özelliklerinin avantajlarını kaybedersiniz.

Her durumda, Class.cast()esas olarak Classbelirteci yansıtma yoluyla aldığınızda kullanılmalıdır . Yazmak daha deyimsel

MyObject myObject = (MyObject) object

ziyade

MyObject myObject = MyObject.class.cast(object)

DÜZENLEME: Derleme zamanında hatalar

Her şeyden öte, Java yalnızca çalışma zamanında yayın denetimleri gerçekleştirir. Bununla birlikte, derleyici, bu tür yayınların asla başarılı olamayacağını kanıtlayabilirse bir hata verebilir (örneğin, bir sınıfı süper tip olmayan başka bir sınıfa atar ve son sınıf türünü, kendi tür hiyerarşisinde olmayan sınıfa / arabirime atar). O zamandan beri Foove Barbirbiri hiyerarşisinde olmayan sınıflar, oyuncu kadrosu asla başarılı olamaz.


Yayınların idareli kullanılması gerektiğine tamamen katılıyorum, bu yüzden kolayca aranabilen bir şeye sahip olmak, kod yeniden düzenleme / bakım için büyük fayda sağlayacaktır. Ancak sorun şu ki, ilk bakışta Class.castfaturaya uyuyor gibi görünse de çözdüğünden daha fazla sorun yaratıyor.
Alexander Pogrebnyak

16

Yapıları ve kavramları diller arasında çevirmeye çalışmak her zaman sorunlu ve çoğu zaman yanıltıcıdır. Oyuncu seçimi bir istisna değildir. Özellikle Java dinamik bir dil olduğu ve C ++ biraz farklı olduğu için.

Java'da tüm döküm, nasıl yaparsanız yapın, çalışma zamanında yapılır. Tür bilgileri çalışma zamanında tutulur. C ++ biraz daha bir karışımdır. C ++ 'da bir yapıyı diğerine çevirebilirsiniz ve bu yalnızca bu yapıları temsil eden baytların yeniden yorumlanmasıdır. Java bu şekilde çalışmaz.

Ayrıca Java ve C ++ 'daki jenerikler büyük ölçüde farklıdır. Java'da C ++ işlerini nasıl yaptığınızla fazla ilgilenmeyin. İşleri Java yöntemiyle nasıl yapacağınızı öğrenmelisiniz.


Tüm bilgiler çalışma zamanında tutulmaz. Örneğimden görebileceğiniz gibi (Bar) foo, derleme zamanında bir hata oluşturuyor, ancak Bar.class.cast(foo)yapmıyor. Bence bu şekilde kullanılması gerekir.
Alexander Pogrebnyak

5
@Alexander Pogrebnyak: Bunu yapma o zaman! Bar.class.cast(foo)Derleyiciye, yayınlamayı çalışma zamanında yapmak istediğinizi açıkça söyler. Oyuncu kadrosunun geçerliliğini derleme sırasında kontrol etmek istiyorsanız, tek seçeneğiniz (Bar) foostil atamasını yapmaktır .
Daniel Pryden

Aynı şekilde yapmanın yolu nedir sizce? Java birden çok sınıf mirasını desteklemediğinden.
Yamur

13

Class.cast()Java kodunda nadiren kullanılır. Kullanılıyorsa, genellikle yalnızca çalışma zamanında bilinen türlerle (yani ilgili Classnesneleri ve bazı tür parametreleri aracılığıyla) kullanılır. Yalnızca jenerik kullanan kodlarda gerçekten yararlıdır (daha önce tanıtılmamasının nedeni de budur).

Öyle değil benzer reinterpret_casto, çünkü değil Artık gelmez normal bir döküm daha zamanında tip sistemini kırmak için izin (yani olabilir kırmak genel tür parametreleri, ancak edemez kırmak "gerçek" türleri).

C tarzı döküm operatörünün kötülükleri genellikle Java için geçerli değildir. C-stili döküm gibi görünen Java kodu, Java'daki dynamic_cast<>()bir referans türü ile en çok benzerdir (unutmayın: Java, çalışma zamanı tür bilgisine sahiptir).

Genel olarak C ++ döküm işleçlerini Java dökümüyle karşılaştırmak oldukça zordur, çünkü Java'da yalnızca referans atabilirsiniz ve nesnelere hiç dönüştürme olmaz (yalnızca ilkel değerler bu sözdizimi kullanılarak dönüştürülebilir).


dynamic_cast<>()bir referans türü ile.
Tom Hawtin - tackline

@Tom: Bu düzenleme doğru mu? Benim C ++ programım çok paslı, çoğunu yeniden google'da yeniden aramak zorunda kaldım ;-)
Joachim Sauer

+1: "C-stili cast operatörünün kötülükleri genellikle Java için geçerli değildir." Sessiz doğru. Tam da bu kelimeleri soruya yorum olarak göndermek üzereydim.
Daniel Pryden

4

Genel olarak, döküm operatörü, daha kısa olduğundan ve kodla ilgili bariz sorunları ortaya çıkarmak için derleyici tarafından analiz edilebildiğinden Class # cast yöntemine tercih edilir.

Class # cast, derleme sırasında değil, çalışma zamanında tür denetleme sorumluluğunu alır.

Sınıf # döküm için, özellikle de yansıtıcı işlemler söz konusu olduğunda kesinlikle kullanım durumları vardır.

Lambda'lar java'ya geldiğinden kişisel olarak, örneğin soyut türlerle çalışıyorsam, koleksiyonlar / akış API'si ile Class # cast kullanmayı seviyorum.

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);
}

3

C ++ ve Java farklı dillerdir.

Java C-style cast operatörü, C / C ++ sürümünden çok daha kısıtlıdır. Etkili bir şekilde Java dökümü, C ++ dynamic_cast'e benzer, eğer sahip olduğunuz nesne yeni sınıfa dönüştürülemezse, bir çalışma süresi (veya kodda yeterli bilgi varsa, bir derleme zamanı) istisnası alırsınız. Bu nedenle C ++ 'ın C tipi döküm kullanmama fikri Java'da iyi bir fikir değildir


0

En çok bahsedildiği gibi çirkin atama uyarılarını kaldırmanın yanı sıra, Class.cast çoğunlukla jenerik çevrimle kullanılan çalışma zamanı dökümdür, çünkü genel bilgi çalışma zamanında silinecektir ve her bir jenerik nasıl Nesne olarak kabul edilecektir, bu da erken bir ClassCastException oluşturun.

örneğin serviceLoder nesneleri oluştururken bu numarayı kullanır, S p = service.cast (c.newInstance ()); bu SP = (S) c.newInstance (); 'Tür güvenliği: Nesneden S'ye denetlenmemiş çevirme ' uyarısı göstermez ve göstermeyebilir . (Nesne P = (Nesne) c.newInstance (); ile aynı)

- basitçe, dönüştürülen nesnenin döküm sınıfının bir örneği olup olmadığını kontrol eder, ardından uyarıyı bastırarak çevirmek ve gizlemek için cast operatörünü kullanır.

dinamik yayın için java uygulaması:

@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
    }

0

Şahsen, bunu daha önce JSON'dan POJO'ya dönüştürücü oluşturmak için kullandım. İşlevle işlenen JSONObject öğesinin bir dizi veya iç içe geçmiş JSONObjects içermesi durumunda (buradaki verilerin ilkel bir türde olmadığı anlamına gelir String), class.cast()bu şekilde kullanarak ayarlayıcı yöntemini çağırmaya çalışıyorum :

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))));
    }
    ...
}

Bunun son derece yararlı olup olmadığından emin değilim, ancak daha önce de söylediğim gibi, düşünme, class.cast()aklıma gelen çok az meşru kullanım durumundan biri, en azından şimdi başka bir örneğiniz var.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.