Dlaczego Java nie pozwala na zgłoszenie sprawdzonego wyjątku z bloku statycznej inicjalizacji? Jaki był powód tej decyzji projektowej?
Dlaczego Java nie pozwala na zgłoszenie sprawdzonego wyjątku z bloku statycznej inicjalizacji? Jaki był powód tej decyzji projektowej?
Odpowiedzi:
Ponieważ nie jest możliwe obsłużenie tych sprawdzonych wyjątków w twoim źródle. Nie masz żadnej kontroli nad procesem inicjalizacji, a statyczne bloki {} nie mogą być wywoływane ze źródła, aby móc je otoczyć try-catch.
Ponieważ nie możesz obsłużyć żadnego błędu wskazywanego przez sprawdzony wyjątek, zdecydowano się zabronić wyrzucania statycznych bloków sprawdzonych wyjątków.
Blok statyczny nie może zgłaszać sprawdzonych wyjątków, ale nadal umożliwia zgłaszanie wyjątków niezaznaczonych / środowiska uruchomieniowego. Ale z powyższych powodów nie byłbyś w stanie sobie z nimi poradzić.
Podsumowując, ograniczenie to uniemożliwia (lub przynajmniej utrudnia) deweloperowi zbudowanie czegoś, co może skutkować błędami, z których aplikacja nie byłaby w stanie odzyskać.
static { if(1 < 10) { throw new NullPointerException(); } }
Możesz obejść ten problem, przechwytując każdy zaznaczony wyjątek i zgłaszając go ponownie jako niezaznaczony wyjątek. Ten niekontrolowany klasa wyjątek działa również owijki: java.lang.ExceptionInInitializerError
.
Przykładowy kod:
protected static class _YieldCurveConfigHelperSingleton {
public static YieldCurveConfigHelper _staticInstance;
static {
try {
_staticInstance = new YieldCurveConfigHelper();
}
catch (IOException | SAXException | JAXBException e) {
throw new ExceptionInInitializerError(e);
}
}
}
catch (Exception e) {
zamiast tego.
System.exit(...)
(lub równoważne) jest jedyną opcją,
Musiałoby to wyglądać tak (to nie jest poprawny kod Java)
// Not a valid Java Code
static throws SomeCheckedException {
throw new SomeCheckedException();
}
ale jak by reklama, gdzie ją złapałeś? Zaznaczone wyjątki wymagają wyłapania. Wyobraź sobie kilka przykładów, które mogą zainicjować klasę (lub nie, ponieważ jest już zainicjowana) i aby zwrócić uwagę na złożoność, którą wprowadziłaby, umieściłem przykłady w innym statycznym inicjatorze:
static {
try {
ClassA a = new ClassA();
Class<ClassB> clazz = Class.forName(ClassB.class);
String something = ClassC.SOME_STATIC_FIELD;
} catch (Exception oops) {
// anybody knows which type might occur?
}
}
I kolejna paskudna rzecz -
interface MyInterface {
final static ClassA a = new ClassA();
}
Wyobraź sobie, że klasa A miała statyczny inicjator rzucający sprawdzony wyjątek: w tym przypadku MyInterface (który jest interfejsem z „ukrytym” statycznym inicjatorem) musiałby zgłosić wyjątek lub obsłużyć go - obsługa wyjątków w interfejsie? Lepiej zostaw to tak, jak jest.
main
może zgłaszać zaznaczone wyjątki. Oczywiście nie można sobie z nimi poradzić.
main()
który drukuje wyjątek ze śladem stosu do System.err
, a następnie wywołuje System.exit()
. Ostatecznie odpowiedź na to pytanie brzmi prawdopodobnie: „ponieważ tak powiedzieli projektanci Javy”.
Dlaczego Java nie pozwala na zgłoszenie sprawdzonego wyjątku z bloku statycznej inicjalizacji?
Technicznie możesz to zrobić. Jednak zaznaczony wyjątek musi zostać przechwycony w bloku. Zaznaczony wyjątek nie może być propagowany poza blok.
Z technicznego punktu widzenia możliwe jest również zezwolenie niezaznaczonemu wyjątkowi na propagację z bloku 1 statycznego inicjatora . Ale robienie tego celowo jest naprawdę złym pomysłem! Problem polega na tym, że sama maszyna JVM przechwytuje niezaznaczony wyjątek, opakowuje go i ponownie zgłasza jako plik ExceptionInInitializerError
.
Uwaga: to Error
nie jest zwykły wyjątek. Nie powinieneś próbować go odzyskać.
W większości przypadków wyjątku nie można złapać:
public class Test {
static {
int i = 1;
if (i == 1) {
throw new RuntimeException("Bang!");
}
}
public static void main(String[] args) {
try {
// stuff
} catch (Throwable ex) {
// This won't be executed.
System.out.println("Caught " + ex);
}
}
}
$ java Test
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Bang!
at Test.<clinit>(Test.java:5)
Nigdzie nie możesz umieścić try ... catch
w powyższym miejscu, aby złapać ExceptionInInitializerError
2 .
W niektórych przypadkach możesz to złapać. Na przykład, jeśli wyzwoliłeś inicjalizację klasy przez wywołanie Class.forName(...)
, możesz zawrzeć wywołanie w a try
i złapać albo ExceptionInInitializerError
następny NoClassDefFoundError
.
Jeśli jednak spróbujesz odzyskać siły , ExceptionInInitializerError
możesz napotkać blokadę na drogach. Problem polega na tym, że przed zgłoszeniem błędu maszyna JVM oznacza klasę, która spowodowała problem, jako „nie powiodła się”. Po prostu nie będziesz w stanie go używać. Ponadto wszelkie inne klasy, które są zależne od klasy, która zakończyła się niepowodzeniem, również przejdą w stan niepowodzenia, jeśli spróbują zainicjować. Jedynym wyjściem jest zwolnienie wszystkich klas, które zakończyły się niepowodzeniem. Że może być wykonalne dla kodu dynamicznie załadowanego 3 , ale w ogóle nie jest.
1 - jest to błąd kompilacji, jeśli blok statyczny bezwarunkowo zgłasza niesprawdzony wyjątek.
2 - Ty może być w stanie przechwycić go rejestrując domyślnej obsługi przechwycony wyjątek, ale to nie będzie można odzyskać, ponieważ „głównym” nić nie można uruchomić.
3 - Jeśli chcesz odzyskać klasy, które się nie powiodły, musisz pozbyć się modułu ładującego klasy, który je ładował.
Jaki był powód tej decyzji projektowej?
Ma to na celu ochronę programisty przed pisaniem kodu, który rzuca wyjątki, których nie można obsłużyć!
Jak widzieliśmy, wyjątek w inicjatorze statycznym zamienia typową aplikację w cegłę. Najlepszą rzeczą, jaką mogliby zrobić projektanci języka, jest potraktowanie zaznaczonego przypadku jako błędu kompilacji. (Niestety nie jest to praktyczne również w przypadku niezaznaczonych wyjątków).
OK, więc co należy zrobić, jeśli Twój kod „musi” zgłaszać wyjątki w statycznym inicjatorze. Zasadniczo istnieją dwie alternatywy:
Jeśli (pełne!) Odzyskanie z wyjątku w bloku jest możliwe, zrób to.
W przeciwnym razie zrestrukturyzuj swój kod, aby inicjalizacja nie odbywała się w statycznym bloku inicjalizacji (lub w inicjatorach zmiennych statycznych).
Spójrz na specyfikacje języka Java : stwierdzono, że jest to błąd czasu kompilacji, jeśli inicjator statyczny nie powiedzie się i może zakończyć się nagle z zaznaczonym wyjątkiem.
public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }
Wyjście:Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: whathappenswhenastaticblockthrowsanexception at Main.<clinit>(Main.java:6) Caused by: java.lang.ClassNotFoundException: whathappen...
Ponieważ żaden kod, który piszesz, nie może wywołać statycznego bloku inicjalizacji, nie jest użyteczne rzucanie check exceptions
. Gdyby to było możliwe, co zrobiłaby jvm, gdy zostaną wyrzucone sprawdzone wyjątki? Runtimeexceptions
są rozmnażane.
Na przykład: Spring's DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) obsługuje scenariusz, który wyłapuje sprawdzony wyjątek i wyrzuca inny niezaznaczony wyjątek.
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
Jestem w stanie skompilować, zgłaszając również zaznaczony wyjątek ....
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}