JavaRush /Blog Java /Random-PL /10 rzeczy, których nie wiedziałeś o Javie
minuteman
Poziom 32

10 rzeczy, których nie wiedziałeś o Javie

Opublikowano w grupie Random-PL
Czy niedawno zacząłeś pracować z Javą? Pamiętacie czasy, kiedy nazywało się to „Oak”, kiedy orientacja obiektowa była wciąż gorącym tematem, kiedy ludzie C++ myśleli, że Java nie ma szans i kiedy nikt nawet nie słyszał o apletach? Zakładam, że nie znasz nawet połowy z poniższych rzeczy. Zacznijmy tydzień od kilku fajnych niespodzianek na temat wewnętrznego działania Javy. 10 rzeczy, których nie wiedziałeś o Javie - 11. Nie ma czegoś takiego jak sprawdzony wyjątek. Zgadza się! JVM nie ma o tym pojęcia, ma to tylko język Java. Dziś wszyscy są zgodni, że sprawdzone wyjątki były błędem. Jak powiedział Bruce Eckel w swoim ostatnim przemówieniu na GeeCON w Pradze, żaden inny język, ponieważ Java nie używa sprawdzonych wyjątków, nawet Java 8 nie obejmuje ich już w nowym interfejsie Streams API (co może być nieco uciążliwe, gdy lambdy używają IO lub JDBC ). Chcesz dowodu, że JVM o czymś takim nie wie? Wypróbuj następujący kod: Nie tylko się to skompiluje, ale także wyrzuci wyjątek SQLException. Nie musisz nawet używać do tego @SneakyThrows Lomboka. 2. Możesz mieć przeciążone metody, które różnią się jedynie typem zwracanych wartościpublic class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test. doThrow0(e); } @SuppressWarnings("unchecked") static void doThrow0(Exception e) throws E { throw (E) e; } } To się nie skompiluje, prawda? class Test { Object x() { return "abc"; } String x() { return "123"; } } Prawidłowy. Język Java nie pozwala na równoważne zastępowanie dwóch metod w tej samej klasie w tym samym czasie, niezależnie od różnic w typach rzutów i zwrotów. Ale poczekaj chwilę. Sprawdź ponownie dokumentację dla Class.getMethod(String, Class…). Mówi: Zauważ, że możliwe jest, że w klasie istnieje więcej niż jedna odpowiadająca metoda, ponieważ podczas gdy język Java zabrania wielu metod z tą samą sygnaturą, ale różnymi typami zwracanych wartości, Wirtualna Maszyna Java tego nie robi. Tę elastyczność maszyny wirtualnej można wykorzystać do wdrożenia różnych funkcji językowych. Na przykład zwroty kowariantne można wykonać metodami pomostowymi; Metoda mostkowa i metoda zastąpiona miałyby ten sam podpis, ale różne typy zwracane. To ma sens. Właściwie sporo się dzieje, gdy piszesz co następuje: Spójrz na wygenerowany kod bajtowy: Zatem t jest w rzeczywistości obiektem w kodzie bajtowym. Jest to dobrze rozumiane. Metoda mostu syntetycznego jest w rzeczywistości generowana przez kompilator, ponieważ w niektórych częściach wywołań można spodziewać się zwrotu typu Parent.x(). Dodawanie typów generycznych bez takich metod mostowych nie będzie już możliwe w reprezentacji binarnej. Zatem zmiana JVM w celu umożliwienia takiej funkcji spowodowała mniej problemów (co pozwala również na nadpisanie metody kowariantnej jako efekt uboczny...) Inteligentne, prawda? 3. Wszystkie poniższe tablice są tablicami dwuwymiarowymi. To prawda. Nawet jeśli Twój analizator mentalny nie jest w stanie od razu zrozumieć typu zwrotu z metod opisanych powyżej, wszystkie są takie same! Podobnie jak następny fragment kodu. Czy uważasz, że to szaleństwo? Liczba możliwości pisania również po prostu eksploduje wyobraźnię! Wpisz adnotację. Urządzenie, którego tajemnica ustępuje jedynie mocy. Innymi słowy: kiedy wykonuję ostatnie zobowiązanie tuż przed moimi 4-tygodniowymi wakacjami. Wyrażam zgodę na wykorzystanie go w dowolny sposób. 4. Nie dostaniesz warunku. Myślałeś, że wiesz już wszystko o trybach warunkowych, kiedy zacząłeś ich używać? Pozwól, że cię rozczaruję – myliłeś się. Większość z Was pomyśli, że poniższe dwa przykłady są równoważne: równoważne temu? NIE. Zróbmy szybki test. Program wyświetli następujący komunikat: Tak! W razie potrzeby operator warunkowy wykona rzutowanie typu. Ponieważ w przeciwnym razie można by oczekiwać, że program zgłosi wyjątek NullPointerException? 5. Nie otrzymasz także złożonego operatora przypisania. Czy zaradność wystarczy? Przyjrzyjmy się następującym dwóm fragmentom kodu: abstract class Parent { abstract T x(); } class Child extends Parent { @Override String x() { return "abc"; } } // Method descriptor #15 ()Ljava/lang/String; // Stack: 1, Locals: 1 java.lang.String x(); 0 ldc [16] 2 areturn Line numbers: [pc: 0, line: 7] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: Child // Method descriptor #18 ()Ljava/lang/Object; // Stack: 1, Locals: 1 bridge synthetic java.lang.Object x(); 0 aload_0 [this] 1 invokevirtual Child.x() : java.lang.String [19] 4 areturn Line numbers: [pc: 0, line: 1] class Test { int[][] a() { return new int[0][]; } int[] b() [] { return new int[0][]; } int c() [][] { return new int[0][]; } } class Test { int[][] a = {{}}; int[] b[] = {{}}; int c[][] = {{}}; } @Target(ElementType.TYPE_USE) @interface Crazy {} class Test { @Crazy int[][] a1 = {{}}; int @Crazy [][] a2 = {{}}; int[] @Crazy [] a3 = {{}}; @Crazy int[] b1[] = {{}}; int @Crazy [] b2[] = {{}}; int[] b3 @Crazy [] = {{}}; @Crazy int c1[][] = {{}}; int c2 @Crazy [][] = {{}}; int c3[] @Crazy [] = {{}}; } 10 rzeczy, których nie wiedziałeś o Javie - 2 Object o1 = true ? new Integer(1) : new Double(2.0); Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0); System.out.println(o1); System.out.println(o2); 1.0 1 Integer i = new Integer(1); if (i.equals(1)) i = null; Double d = new Double(2.0); Object o = true ? i : d; // NullPointerException! System.out.println(o); i += j; i = i + j; Intuicyjnie powinny być równe, prawda? Ale wiesz co – są różne. Specyfikacja JLS mówi: Wyrażenie złożone typu E1 op = E2 jest równoważne E1 = (T) ((E1) op (E2)), gdzie T jest typem E1, z tą różnicą, że E1 jest oceniane tylko raz. Dobrym przykładem jest użycie *= lub /= : byte b = 10; b *= 5.7; System.out.println(b); // prints 57 lub: byte b = 100; b /= 2.5; System.out.println(b); // prints 40 lub: char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4' lub: char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a' Czy jest to nadal przydatne narzędzie? 6. Losowe liczby całkowite Teraz trudniejsze zadanie. Nie czytaj rozwiązania. Sprawdź, czy sam znajdziesz odpowiedź. Kiedy uruchamiam następujący program: for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } Czasami otrzymuję następujący wynik: 92 221 45 48 236 183 39 193 33 84 Ale jak to w ogóle możliwe? OK, odpowiedź polega na zastąpieniu pamięci podręcznej Integer JDK poprzez odbicie, a następnie użyciu automatycznego rozpakowywania i automatycznego rozpakowywania. Nie rób tego bez zgody osoby dorosłej! Inaczej mówiąc: 10 rzeczy, których nie wiedziałeś o Javie - 3 7. GOTO Jeden z moich ulubionych. Java ma GOTO! Napisz to: int goto = 1; i otrzymasz to: To dlatego, że goto jest nieużywanym słowem zastrzeżonym, na wszelki wypadek... Ale to nie jest ekscytująca część. Fajną rzeczą jest to, że możesz dołączyć goto w połączeniu z przerwami, kontynuacją i zaznaczonymi blokami: Skok do przodu W kodzie bajtowym: Skok do tyłu W kodzie bajtowym: 8. Java ma aliasy typów W innych językach (takich jak Cejlon) możemy zdefiniować typ aliasy są bardzo proste: Klasa People jest tutaj zbudowana w taki sposób, że można ją zamieniać z klasą Set Test.java:44: error: expected int goto = 1; ^ label: { // do stuff if (check) break label; // do more stuff } 2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 .. label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true); 2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 .. interface People => Set ; : W Javie nie możemy po prostu zdefiniować aliasu na najwyższym poziomie. Ale możemy to zrobić na potrzeby klasy lub metody. Załóżmy, że nie jesteśmy zadowoleni z nazw takich jak Integer, Long itp. i chcemy krótszych nazw: I i L. Łatwe: W powyższym przykładzie liczba Integer jest konwertowana na I dla widoczności klasy Test, natomiast Long jest konwertowana na L na potrzeby metody x(). Teraz możemy nazwać tę metodę w ten sposób: Oczywiście tej techniki nie należy traktować poważnie. W tym przypadku Integer i Long są typami końcowymi, co oznacza, że ​​I i L są efektywnymi konwersjami (prawie konwersja przebiega tylko w jedną stronę). Jeśli zdecydowalibyśmy się na użycie typów innych niż końcowe (na przykład Object), wówczas moglibyśmy skorzystać ze zwykłych typów generycznych. Pograliśmy trochę i wystarczy. Przejdźmy do czegoś naprawdę interesującego. 9. Niektóre relacje typu są nierozstrzygalne! OK, teraz będzie naprawdę ciekawie, więc weź filiżankę skoncentrowanej kawy i przyjrzyjmy się następującym dwóm rodzajom: People? p1 = null; Set ? p2 = p1; People? p3 = p2; class Test { void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } } new Test().x(1, 2L);// A helper type. You could also just use List interface Type {} class C implements Type > {} class D

implements Type >>> {} Co więc w ogóle oznaczają C i D? W pewnym sensie są rekursywne, podobnie jak rekurencja w java.lang.Enum. Rozważ: Biorąc pod uwagę powyższe specyfikacje, rzeczywista implementacja wyliczenia to po prostu cukier syntaktyczny. Mając to na uwadze, wróćmy do naszych dwóch typów. Czy poniższy kod się skompiluje? Trudne pytanie... i właściwie nie zostało rozwiązane? Czy C jest podtypem typu ? Spróbuj skompilować to w swoim Eclipse lub Idea, a oni powiedzą ci, co myślą. Spuść to do ścieku... Niektóre relacje typów w Javie są nierozstrzygalne! 10. Przecięcie typów Java posiada bardzo interesującą funkcję zwaną przecięciem typów. Można zadeklarować typ (ogólny), który w rzeczywistości jest przecięciem dwóch typów. Na przykład: Niestandardowy parametr typu T, który jest skojarzony z instancjami klasy Test, musi zawierać zarówno interfejsy Serializable, jak i Cloneable. Na przykład String nie może być ograniczony, ale Date może: Ta funkcja ma wiele zastosowań w Java8, gdzie można rzutować typy. Jak to pomaga? Prawie nic, ale jeśli chcesz rzucić wyrażenie lambda na typ, którego potrzebujesz, nie ma innego sposobu. Powiedzmy, że masz takie szalone ograniczenie w swojej metodzie: chcesz, aby metoda Runnable była możliwa do serializacji, tylko jeśli chcesz ją wykonać w innym miejscu i wysłać wynik przez sieć. Lambda i serializacja dodają odrobinę ironii. Możesz serializować swoje wyrażenie lambda, jeśli jego typ docelowy i argumenty można serializować. Ale nawet jeśli to prawda, nie włączają one automatycznie interfejsu Serializable. Musisz sam je sprowadzić do tego typu. Ale kiedy rzutujesz tylko na Serializable: wtedy lambda nie będzie już uruchamialna, więc rzuć ją na oba typy: I podsumowując: public abstract class Enum > { ... } // This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum { ... } class Test { Type c = new C(); Type> d = new D (); } Step 0) C Step 1) Type > >? Step 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java jest równie potężna, co tajemnicza.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION