2014-06-11 20 views
20
interface I{} 
class A implements I{} 
class B{} 

pierwsze:Casting "z" Interfejs

I[] arr = new A[10]; 
arr[0] = (I) new B(); // will produce ClassCastException at runtime 

drugie: którym jeśli mogę użyć betonu klasy

I[] arr = new A[10]; 
arr[0] = (A) new B(); // will produce compile-time error 

co za różnica, czy w moim pierwszym przykładzie (I) new B(), kompilator Javy powinien również spowodować błąd kompilacji?

Czy to nie jest tak, że kompilator java powinien być w stanie odróżnić, że jest to również "typ niezapalny"? Zwłaszcza, gdy nowy operator przychodzi natychmiast?

Czy istnieje jakaś szansa/przypadek, że utworzenie nowej instancji B może spowodować przekształcenie typu I na ?

wiem, w pewnym momencie, że kompilator Javy nie powinien od razu powiedzieć, że jest to błąd kompilatora, jak, kiedy to zrobić:

I i = (I) getContent(); // wherein getContent() will return a type of Object 

Edit:

Pozwól mi wyjaśnić pytanie, dlaczego nie jest to możliwe duplikat tego: Cast reference of known type to an interface outside of type's hierarchy

Intencja tego pytania nie jest, ponieważ nie jestem świadomy, co będzie wynikiem lub co jest nie tak z somethi ng, itd.

Chciałbym tylko poznać "bardziej szczegółowe wyjaśnienie w sposób techniczny", dlaczego JVM zachowuje się w ten sposób lub dlaczego Java wymyśliła taką decyzję, aby nie tworzyć tego rodzaju scenariusza Błąd kompilacji.

Jak wszyscy wiemy, zawsze lepiej jest znaleźć "problematyczny kod" podczas kompilacji, a nie podczas pracy.

Inna sprawa, odpowiedź, której szukam, została znaleziona tutaj w tym wątku, a nie w tych "duplikatach?".

+2

Nie sądzę, że jest to ograniczone do tablic. To samo wydaje się być w przypadku 'Ix = (A) new B();' vs. 'I y = (I) new B();' Dla metod ma to sens, ponieważ mogą one zwrócić podklasę typ zwracany, który implementuje interfejs. Ale czy _konstruktor_ może zwrócić podklasę klasy, którą konstruuje? –

+1

16 upvotes już !!! :) .... możemy teraz mieć lepszy tytuł !! ;) – NoobEditor

Odpowiedz

9

Reguły dotyczące tego, które rzuty są zgodne z kompilacją, uwzględniają wyłącznie typy statyczne.

Gdy kompilator Java analizuje wyrażenie (I) new B(), widzi, że statyczny typ wyrażenia new B() to . Możemy stwierdzić, że new B() nie może być instancją I, ale reguły analizy czasu kompilacji nie mogą stwierdzić, że obiekt nie jest faktycznie instancją podklasy B, która implementuje I.

W związku z tym kompilator musi go przepuścić. W zależności od stopnia zaawansowania kompilatora może wykryć dziwność i wygenerować ostrzeżenie, ale w ten sam sposób, jak 1/0 nie jest błędem podczas kompilacji, nie może to być błąd podczas kompilacji.

+0

Dziękuję @ user2357112. Więc mówisz, że kompilator podczas analizy kompilacji widzi tylko "typ statyczny" każdej instancji, którą chcesz rzutować? Nawet jeśli użyjesz "nowego" operatora zaraz po obsadzie? – kev

+0

@KevObispo: Dosyć. – user2357112

+0

Dziękujemy @ user2357112. – kev

0

można rzucać żadnych przedmiotów na żądaną var, tylko jeśli oddanych do typu var:

interface I{} 
class A implements I{} 
class B{} 

I var = (I) object; // This is always possible in compile-time, no matter the object type, because object is casted to the var type, I 
I var = (A) object; // Not possible in compile-time because of the difference of types 

Wyjątek środowiska wykonawczego przychodzi, gdy obiekt nie może być odlewane, ale nie można wiedzieć, dopóki środowisko wykonawcze.

A object = new A(); 
I var = (I) object; 
B anotherObject = new B(); 
var = (I) anotherObject; 

Oba powyższe będzie pracować w czasie kompilacji, ale tylko pierwszy z nich zrobi to w czasie pracy, ze względu na implementacji interfejsu I.

+0

Czy próbowałeś tego? – weston

+0

Tak, zrobiłem to i działa w sposób, w jaki powiedziałem – Genzotto

+0

Nic z tego nie utworzyło dla mnie błędu czasu kompilacji. – weston

0

Dziedziczenie klas jest dziedziczeniem pojedynczym, żaden przyszły potomek nie może wprowadzić nowej klasy bazowej, np. w twoim przykładzie żaden potomek B nie może być kiedykolwiek przeniesiony do A. Ale może wprowadzić nowy interfejs, tj. Potomek B może obsługiwać I.

To błąd kompilatora, że ​​nie może rozwiązać prostego przypadku, ale nie jest to przypadek, który można zobaczyć na wolności. Utwórz i odrzuć w jednym wierszu, który jest.

Przykład dlaczego kompilator nie może wykryć to w przypadku bardziej złożonych przy użyciu klas

void method(B b){ 
    I i = (I) b; 
} 

class C extends B implements I{} // a descendent of B that introduces support for I 

method(new A()); //still compile time error 
method(new B()); //runtime exception 
method(new C()); //works 
+0

Dziękuję Weston, ale będzie to kolejna historia, gdy użyjesz metody, zanim zaczniesz rzucać. To jest dokładnie tak, jak mówiłem w ostatnim zdaniu mojego pytania. – kev

+0

W takim przypadku musisz rzucić od razu po stworzeniu? Twórcy kompilatorów języka Java nie zadbali o to, ponieważ nie jest to konieczne. – weston

+0

Ta metoda służyła tylko do pokazania, że ​​rzutuję na 'B' jako tymczasowy krok. I tak dla niektórych 'B's może działać w czasie wykonywania. – weston

0

próbowałem cztery przypadki:

  1. Casting klasy do innej klasy.
  2. Przesyłanie klasy do interfejsu.
  3. Przesyłanie interfejsu do klasy.
  4. Przesyłanie interfejsu do innego interfejsu.

Błąd podczas kompilacji występuje tylko w pierwszym przypadku.

static interface I {} 
static interface J {} 

static class A {} 
static class B {} 

Object o = (B) new A();  // compile-time error 
Object o = (I) new A();  // runtime error 
Object o = (B) ((I) new A()); // runtime error 
Object o = (J) ((I) new A()); // runtime error 

Domyślam się, że dzieje się tak dlatego ustalenie, czy obsada uda lub nie stosunkowo łatwiejsze w pierwszym przypadku, w porównaniu do pozostałych trzech przypadkach. Głównym powodem jest to, że klasa może rozszerzyć tylko jedną klasę, co pozwala kompilatorowi sprawdzić, czy rzutowanie powiedzie się, czy nie. Patrz następujące przykłady:

Załóżmy, oprócz powyższych klas i interfejsów, dodaję nową klasę:

static class C extends B implements I, J {} 

przykład kodu, który rzuca klasę do interfejsu (ostatnia linia):

C c = new C(); 
B b = (B) c; 
I i = (I) b; // This is ok. 

Przykład kodu, który rzuca interfejs do klasy (ostatniej linii)

C c = new C(); 
I i = (I) c; 
B b = (B) i; // This is ok. 

Przykład Kod, który rzuca interfejs do innego interfejsu (ostatni wiersz):

C c = new C(); 
I i = (I) c; 
J j = (J) i; // This is ok. 
5

Różnica w tej sytuacji jest oczywiste, że I to interfejs, który może być realizowany. Oznacza to, że nawet jeśli B nie ma nic wspólnego z I, może istnieć podklasa B, która implementuje interfejs I. Będzie to zilustrować na przykładzie:

interface I{} 
class A implements I{} 
class B{} 
class C extends B implements I{} 

I[] arr = new A[10]; // valid, cause A implements I 
B b = new C(); // Valid because C is a subclass of B 
arr[0] = (I) b; // This won't produce ClassCastException at runtime, because b 
       // contains an object at runtime, which implements I 
arr[0] = (I) new B(); // This will compile but will result in a ClassCastException 
         // at runtime, cause B does not implement I 

Ważne jest, aby odróżnić różnicę między static i dynamic typów.W tym przypadku typ statyczny zmiennej b jest określony jako typ dynamiczny (typ środowiska wykonawczego) C, gdzie new B() ma również statyczny typ B, ale typ dynamiczny jest również B. I tak jak w niektórych przypadkach rzutowanie z B do I nie spowoduje wyjątków (jak w tym scenariuszu), kompilator zezwala na takie odlewania, ale tylko na na typ interfejsu.

teraz przyjrzeć się następujący scenariusz:

I[] arr = new A[10]; 
B b = new C(); // Valid because C is a subclass of B 
A a1 = (A) b; // compile time error 
A a2 = (A) new B(); // compile time error 

to możliwe, że podklasą B kiedykolwiek rozszerzyć A i B w tym samym czasie? Odpowiedź brzmi: NO, ponieważ ograniczono się do rozszerzenia tylko jednej super klasy w Javie (gdzie w niektórych innych językach OO tak nie jest), dlatego kompilator zabrania tego, ponieważ nie ma możliwych scenariuszy, w których to nastąpi. praca.

+0

Dziękuję @Ivaylo. To może być również odpowiedź, ale ponieważ ktoś odpowiedział poprawnie wcześniej, niż ty, muszę mu przyznać kredyt. Odpowiedziałeś na to w bardzo szczegółowy sposób. Dziękuję za to, doceniam wyjaśnienie, w którym określiłeś różnicę między typem "statycznym" i "dynamicznym". Po prostu nie zdawałem sobie sprawy z tego, że dotyczy to również każdej obsady. – kev

+0

@KevObispo Nie, nie musisz oznaczać pierwszej poprawnej odpowiedzi jako zaakceptowanej - powinieneś użyć jej dla najlepszej odpowiedzi, niezależnie od tego, kiedy pojawi się odpowiedź. Często uważa się za dobry pomysł, aby nie oznaczyć * żadnej * odpowiedzi jako zaakceptowanej przez około 24 godziny, aby zobaczyć, co się pojawi. –