public 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; } }
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 [] = {{}}; }
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:
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
;
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 4) D
class Test
// Doesn't compile Test
execute((Serializable) (() -> {}));
execute((Runnable & Serializable) (() -> {}));
GO TO FULL VERSION