Java neden Throwable'ın jenerik alt sınıflarına izin vermiyor?


147

Göre Java Dil Sepecification , 3rd edition:

Genel bir sınıfın doğrudan veya dolaylı bir alt sınıfı olması, derleme zamanı hatasıdır Throwable.

Bu kararın neden verildiğini anlamak isterim. Genel istisnaların nesi yanlış?

(Bildiğim kadarıyla, jenerikler basitçe derleme zamanı sözdizimsel şekerlerdir ve dosyalarda Objectyine de tercüme edilecekler .class, o kadar etkili bir şekilde genel bir sınıfı ilan etmek, sanki içindeki her şey bir Objectsanki. .)


1
Genel tür bağımsız değişkenleri, varsayılan olarak Object olan üst sınırla değiştirilir. List <? A> 'yı genişletir, ardından sınıf dosyalarında A kullanılır.
Torsten Marek

Teşekkür ederim @Torsten. Bu davayı daha önce düşünmemiştim.
Hosam Aly

2
Bu iyi bir röportaj sorusu.
skaffman

@TorstenMarek: Biri ararsa myList.get(i), belli ki gethala bir Object. Derleyici A, çalışma zamanında kısıtlamaların bir kısmını yakalamak için bir çevirme ekliyor mu? Değilse, OP, sonunda Objectçalışma zamanında s'ye indiği konusunda haklıdır . (Sınıf dosyası kesinlikle ilgili meta verileri içerir A, ancak yalnızca meta verilerdir. AFAIK.)
Mihai Danila

Yanıtlar:


155

Mark'ın dediği gibi, türler yeniden değiştirilemez, bu da aşağıdaki durumda bir sorundur:

try {
   doSomeStuff();
} catch (SomeException<Integer> e) {
   // ignore that
} catch (SomeException<String> e) {
   crashAndBurn()
}

Her ikisi de SomeException<Integer>ve SomeException<String>aynı türde silindiğinde, JVM'nin istisna durumlarını ayırt etmesi mümkün değildir ve bu nedenle hangi catchbloğun yürütülmesi gerektiğini söylemenin bir yolu yoktur .


4
ama "yeniden yapılandırılabilir" ne anlama geliyor?
aberrant80

64
Bu nedenle kural, "genel türler Throwable alt sınıfı olamaz" değil, bunun yerine "catch cümleleri her zaman ham türleri kullanmalıdır" olmalıdır.
Archie

3
Aynı türden iki yakalama bloğunun birlikte kullanılmasına izin vermeyebilirler. Dolayısıyla, yalnızca SomeExc <Integer> kullanımı yasal olur, yalnızca SomeExc <Integer> ve SomeExc <String> birlikte kullanılması yasadışı olur. Bu hiç sorun yaratmaz mı yoksa olmaz mı?
Viliam Búr

3
Oh, şimdi anladım. Çözümüm, bildirilmesi gerekmeyen RuntimeExceptions sorunlarına neden olacak. Bu nedenle, Eğer SomeExc, RuntimeException'ın bir alt sınıfı ise, SomeExc <Integer> 'ı atabilir ve açıkça yakalayabilirim, ancak belki başka bir işlev Sessizce SomeExc <String> atıyor ve SomeExc <Integer> için catch bloğum yanlışlıkla bunu da yakalayabilir.
Viliam Búr

4
@ SuperJedi224 - Hayır . Jeneriklerin geriye dönük olarak uyumlu olması gerektiği kısıtlaması göz önüne alındığında doğru yapıyor.
Stephen C

15

İstisnanın nasıl kullanılacağına dair basit bir örnek:

class IntegerExceptionTest {
  public static void main(String[] args) {
    try {
      throw new IntegerException(42);
    } catch (IntegerException e) {
      assert e.getValue() == 42;
    }
  }
}

TRy ifadesinin gövdesi, catch cümlesi tarafından yakalanan belirli bir değerdeki istisnayı fırlatır.

Buna karşılık, yeni bir istisnanın aşağıdaki tanımı, parametreli bir tür oluşturduğundan yasaklanmıştır:

class ParametricException<T> extends Exception {  // compile-time error
  private final T value;
  public ParametricException(T value) { this.value = value; }
  public T getValue() { return value; }
}

Yukarıdakileri derleme girişimi bir hata bildirir:

% javac ParametricException.java
ParametricException.java:1: a generic class may not extend
java.lang.Throwable
class ParametricException<T> extends Exception {  // compile-time error
                                     ^
1 error

Bu kısıtlama mantıklıdır, çünkü bu tür bir istisnayı yakalamaya yönelik hemen hemen her girişimin başarısız olması gerekir, çünkü tür yeniden yapılandırılabilir değildir. İstisnanın tipik kullanımının aşağıdaki gibi olması beklenebilir:

class ParametricExceptionTest {
  public static void main(String[] args) {
    try {
      throw new ParametricException<Integer>(42);
    } catch (ParametricException<Integer> e) {  // compile-time error
      assert e.getValue()==42;
    }
  }
}

Catch cümlesindeki tür yeniden belirtilebilir olmadığından buna izin verilmez. Bu yazının yazıldığı sırada, Sun derleyicisi böyle bir durumda bir dizi sözdizimi hatası rapor eder:

% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
    } catch (ParametricException<Integer> e) {
                                ^
ParametricExceptionTest.java:8: ')' expected
  }
  ^
ParametricExceptionTest.java:9: '}' expected
}
 ^
3 errors

İstisnalar parametrik olamayacağından, sözdizimi sınırlandırılmıştır, böylece tip, aşağıdaki parametre olmadan bir tanımlayıcı olarak yazılmalıdır.


2
Yeniden temin edilebilir derken ne demek istiyorsun? "Yeniden yapılandırılabilir" bir kelime değildir.
ForYourOwnGood

1
Kelimeyi kendim bilmiyordum, ancak google'da hızlı bir arama bana şunu sağladı : java.sun.com/docs/books/jls/third_edition/html/…
Hosam Aly


13

Esasen kötü bir şekilde tasarlandığı için.

Bu sorun temiz soyut tasarımı engeller, örn.

public interface Repository<ID, E extends Entity<ID>> {

    E getById(ID id) throws EntityNotFoundException<E, ID>;
}

Bir yakalama cümlesinin jenerikler için başarısız olması gerçeği somutlaştırılmaması bunun için bir mazeret değildir. Derleyici, Throwable'ı genişleten veya catch cümlelerinin içindeki jeneriklere izin vermeyen somut jenerik türlere izin vermeyebilir.



1
Daha iyi tasarlayabilmelerinin tek yolu, ~ 10 yıllık müşteri kodunu uyumsuz hale getirmekti. Bu uygulanabilir bir iş kararıydı. Bağlam göz önüne alındığında ... tasarım doğruydu .
Stephen C

1
Peki bu istisnayı nasıl yakalayacaksınız? Çalışmanın tek yolu ham türü yakalamaktır EntityNotFoundException. Ancak bu jenerikleri işe yaramaz hale getirir.
Frans

4

Tür doğruluğu için jenerikler derleme zamanında kontrol edilir. Genel tür bilgileri daha sonra tür silme adı verilen bir işlemle kaldırılır . Örneğin, List<Integer>genel olmayan türe dönüştürülecektir List.

Yüzünden Tür silme , tür parametreleri çalışma zamanında belirlenemez.

ThrowableBu şekilde genişletmenize izin verildiğini varsayalım :

public class GenericException<T> extends Throwable

Şimdi şu kodu ele alalım:

try {
    throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
    System.err.println("Integer");
}
catch(GenericException<String> e) {
    System.err.println("String");
}

Nedeniyle Tür silme , çalışma zamanı hangi catch bloğunun çalıştırılacağını bilemeyecektir.

Bu nedenle, genel bir sınıf Throwable'ın doğrudan veya dolaylı bir alt sınıfı ise bu bir derleme zamanı hatasıdır.

Kaynak: Tür silmeyle ilgili sorunlar


Teşekkürler. Bu, Torsten tarafından verilen cevapla aynıdır .
Hosam Aly

Hayır değil. Torsten'in cevabı bana yardımcı olmadı, çünkü silme / şeyleştirmenin ne tür olduğunu açıklamadı.
Good Night Nerd Pride

2

Bunun parametreleştirmeyi garanti etmenin bir yolu olmadığı için olmasını beklerdim. Aşağıdaki kodu göz önünde bulundurun:

try
{
    doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
    // handle it
}

Sizin de belirttiğiniz gibi, parametreleştirme sadece sözdizimsel şekerdir. Ancak derleyici, derleme kapsamındaki bir nesneye yapılan tüm başvurularda parametreleştirmenin tutarlı kalmasını sağlamaya çalışır. Bir istisna durumunda, derleyicinin MyException'ın yalnızca işlediği kapsamdan atılacağını garanti etmenin bir yolu yoktur.


Evet, ama o zaman neden örneğin yayınlarda olduğu gibi "güvenli değil" olarak işaretlenmiyor?
eljenso

Çünkü bir atamayla derleyiciye "Bu yürütme yolunun beklenen sonucu ürettiğini biliyorum" diyorsunuz. Bir istisna dışında, (tüm olası istisnalar için) "Bunun nereye atıldığını biliyorum" diyemezsiniz. Ama yukarıda da söylediğim gibi, bu bir tahmin; Ben orada değildim.
kdgregory

"Bu yürütme yolunun beklenen sonucu verdiğini biliyorum." Bilmiyorsun, öyle olmasını umuyorsun. Bu nedenle jenerik ve aşağı yayınlar statik olarak güvenli değildir, ancak yine de izin verilir. Torsten'in cevabına olumlu oy verdim çünkü orada problemi görüyorum. Burada yok.
eljenso

Bir nesnenin belirli bir türden olduğunu bilmiyorsanız, onu yayınlamamalısınız. Bir oyuncu kadrosunun tüm fikri, derleyiciden daha fazla bilgiye sahip olmanız ve bu bilgiyi açıkça kodun bir parçası haline getirmenizdir.
kdgregory

Evet ve burada MyException'dan MyException <Foo> 'ya denetlenmemiş bir dönüştürme yapmak istediğiniz için derleyiciden daha fazla bilgiye sahip olabilirsiniz. Belki bunun bir MyException <Foo> olacağını "biliyorsunuz".
eljenso
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.