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"; } }
Правильно. Мова Java не дозволяє одночасно двом методам бути еквівалентно перевизначеними в межах одного класу, не звертаючи уваги на їх розрізняючи у throws чи return типах. Але зачекайте на хвабонку. Перевірте ще раз документацію щодо Class.getMethod(String, Class…). Там написано:
Відзначте, що, можливо, є більше одного відповідного методу в класі, тому що поки мова Java забороняє безліч методів з однаковою сигнатурою, але різними типами, що повертаються, віртуальна машина Java цього не робить. Ця гнучкість у віртуальній машині можна використовувати реалізації різних функцій мови. Наприклад, підступні повернення можуть здійснюватися з методами bridge; bridge метод і перевизначений метод, мали б однакову сигнатуру та різні типи, що повертаються. Нічого собі, та це має сенс. Насправді це досить багато що відбувається, коли ви пишете наступне: Подивіться на згенерований байт код: Отже, t насправді об'єкт у байт коді. Це добре розуміється. Синтетичний bridge метод насправді генерується компілятором тому що тип значення Parent.x(), що повертається, можна очікувати на певних ділянках викликів. Додавання generics без таких методів bridge перестане бути можливим у двійковому поданні. Отже, зміни в JVM щоб дозволити таку функцію зробило менше болю (який також дозволяє коваріантне перевизначення методу як побічний ефект…) По розумному правда?
3. Все наступне – двомірні масиви. Це насправді так. Навіть якщо ваш розумовий аналізатор, не може відразу зрозуміти тип, що повертається з описаних вище способів, всі вони однакові! Як і наступний шматок коду. Ви думаєте, що це божевілля? Кількість можливостей написати теж просто підриває уяву!
Тип annotation.
Пристрій загадковість якого поступається лише його мощі. Або іншими словами: Коли я роблю останній коміт якраз перед моєю 4-тижневою відпусткою. Я дозволяю вам користуватися будь-яким способом, що вам сподобався.
4. Ви не отримаєте умовного виразу Отже, ви думали, що вже знаєте все про умовні вирази, коли почали їх використовувати? Дозвольте засмутити вас – ви помилялися. Більшість із вас подумає, що наступні два приклади еквівалентні: еквівалентно цьому? Ні. Давайте використовуємо швидкий тест Програма виведе наступне: Так! Умовний оператор здійснюватиме наведення типів, якщо знадобиться. Оскільки в іншому випадку ви очікували, що програма кине NullPointerException?
5. Ви також не отримаєте складовий оператор призначення. Викрутливості достатньо? Давайте розглянемо наступні два фрагменти коду:
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;
Інтуїтивно, вони мають бути рівними правда? Але знаєте що вони різні. Специфікація JLS каже:
Складове вираз типу Е1 ор = Е2 еквівалентно Е1 = (Т) ((Е1) ор (Е2)), де Т це тип Е1, за винятком що Е1 обчислюється лише один раз. Хороший приклад це використовувати *= або /= :
byte b = 10; b *= 5.7; System.out.println(b); // prints 57
або:
byte b = 100; b /= 2.5; System.out.println(b); // prints 40
або:
char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4'
або:
char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a'
Отже, це досі корисний інструмент?
6. Випадкові цілочисельні числа Тепер важче завдання. Чи не читайте рішення. Подивіться, чи зможете ви знайти відповідь самостійно. Коли я запущу наступну програму:
for (int i = 0; i < 10; i++) { System.out.println((Integer) i); }
іноді я отримую наступний висновок:
92 221 45 48 236 183 39 193 33 84
Але як таке взагалі можливе? Ок, відповідь у криється в перевизначенні JDK кеша Integer через рефлексію, а потім у використанні auto-boxing і auto-unboxing. Не робіть цього без дозволу дорослих! Або іншими словами:
7. GOTO Одне з моїх найулюбленіших. Java має GOTO! Напишіть це:
int goto = 1;
і ви отримаєте це: Це тому, що goto це невикористовуване зарезервоване слово, просто про всяк випадок… Але це не найбільш захоплююча частина. Найцікавіше те, що ви можете включити goto в парі з break, continue і помічених блоків: Стрибки вперед У байт коді: Стрибки назад У байт коді:
8. У Java є псевдоніми типів В інших мовах (наприклад Ceylon), ми можемо визначати псевдоніми типів дуже легко: Клас People тут побудований таким чином, що може взаємозамінюватися безліччю 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
То що С і D взагалі означають? В певному сенсі вони рекурсивні, схоже на рекурсію в java.lang.Enum. Подумайте: З урахуванням зазначених вище специфікацій, фактична реалізація enum – це лише синтаксичний цукор: Думаючи про це давайте повернемося до наших двох типів. Чи компілюється наступний код? Складне питання… і воно фактично не вирішується? Чи є З підтипом Type ? Спробуйте скомпілювати це у вашому Eclipse або Idea і вони розкажуть все, що про вас думають. Спустіть це в стічні труби ... Деякі відносини типів в Java нерозв'язні! 10. Перетин типів У Java є дуже цікава особливість під назвою перетин типів (type intersection). Ви можете оголосити (generic) тип, який є насправді перетином двох типів. Наприклад: Параметр типу Т, який ви зв'язуєте зі зразками класу Test повинен включати в себе як Serializable так і Cloneable інтерфейси. Наприклад, String неможливо обмежити, але Date – будь ласка: Ця особливість знаходить багаторазове використання в Java8, де ви можете наводити типи. Як це допомагає? Практично ніяк, але якщо ви хочете привести своє лямбда-вираз у потрібний вам тип, іншого шляху немає. Допустимо у вас є таке божевільне обмеження у вашому методі: Ви хочете Runnable який в той же час Serializable тільки в тому випадку, якщо хочете виконати це в іншому місці і відправити результат по мережі. Лямбда та серіалізація привносять трохи іронії. Ви можете серіалізувати ваше лямбда-вираз якщо його цільовий тип і аргументи серіалізуються. Але навіть якщо це правильно, вони автоматично не включають Serializable інтерфейс. Ви повинні самостійно наводити їх до цього типу. Але коли ви приводите тільки до Serializable: тоді лямбда більше не буде Runnable, тому приводьте їх до обох типів: І на закінчення: 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) (() -> {}));