
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;
}
}
Это не только скомпилируется, это также бросит SQLException, вам даже не нужно
использовать Lombok's @SneakyThrows для этого.
2. Вы можете иметь перегруженные методы отличающиеся только возвращаемыми
типамиclass Test {
Object x() { return "abc"; }
String x() { return "123"; }
}
Верно. Язык Java не позволяет одновременно двум методам быть эквивалентно
переопределенными в пределах одного класса, не обращая внимания на их отличая в
throws либо return типах.
Но подождите минутку. Проверьте еще раз документацию по Class.getMethod(String,
Class…). Там написано:
Отметьте, что, возможно, есть более одного соответствующего метода в классе,
потому что, пока язык Java запрещает множество методов с одинаковой сигнатурой
но разными возвращаемыми типами, виртуальная машина Java этого не делает. Эта
гибкость в виртуальной машине может использоваться для реализации различных
функций языка. Например, ковариантные возвраты могут осуществляться с bridge
методами; bridge метод и переопределенный метод, имели бы одинаковую сигнатуру
но разные возвращаемые типы.
Ничего себе, да это имеет смысл. На самом деле это довольно много что происходит,
когда вы пишете следующее:
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]
Итак, t на самом деле объект в байт коде. Это хорошо понимается.
Синтетический bridge метод на самом деле генерируется компилятором потому что тип
возвращаемого значения Parent.x() можно ожидать на определенных участках вызовов.
Добавление generics без таких bridge методов перестанет быть возможным в двоичном
представлении. Итак, изменения в JVM чтобы позволить такую функцию произвело
меньше боли (которая также позволяет ковариантное переопределение метода в качестве
побочного эффекта…) По умному правда?
3. Все следующее – двумерные массивы.
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 [] = {{}};
}
Type annotation. Устройство загадочность которого уступает только его мощи.
Или другими словами:
Когда я делаю последний коммит как раз перед моим 4-х недельным отпуском.

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
Да! Условный оператор будет осуществлять приведение типов, если понадобится.
Поскольку в ином случае вы ожидали бы что программа бросит NullPointerException?
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);
5. Вы также не получите составной оператор назначения.
Изворотливости достаточно? Давайте рассмотрим следующие два фрагмента кода:
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. Не делайте этого без разрешения взрослых!
Или другими словами:

int goto = 1;
и вы получите это:
Test.java:44: error: expected
int goto = 1;
^
Это потому что goto это неиспользуемое зарезервированное слово, просто на всякий
случай…
Но это не самая захватывающая часть. Самое интересное то что вы можете включить
goto в паре с break, continue и помеченных блоков:
Прыжки вперед
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 ..
8. У Java есть псевдонимы типов
В других языках (например Ceylon), мы можем определять псевдонимы типов очень
легко:
interface People => Set;
Класс People здесь построен таким образом, что может взаимозаменяться множеством
SetPeople? p1 = null;
Set? p2 = p1;
People? p3 = p2;
В Java мы не можем просто так определить псевдоним на верхнем уровне. Но мы можем
сделать так для потребностей класса либо метода. Давайте предположим что нас не
устраивают такие имена как Integer, Long и т.д. и мы хотим имена по короче: I и L. Да
легко:
class Test {
void x(I i, L l) {
System.out.println(
i.intValue() + ", " +
l.longValue()
);
}
}
В примере выше, Integer преобразован в I для видимости класса Test в то время как Long
преобразован в L для нужд метода х(). Теперь мы можем вызвать этот метод следующим
образом:
new Test().x(1, 2L);
Конечно эту технику не следует воспринимать всерьез. В данном случае Integer и Long
final типы, что означает что I и L – эффективные преобразования (почти, преобразование
идет только в одну сторону). Если бы мы решили использовать non-final типы (к примеру
Object), тогда мы могли бы обойтись обычными дженериками.
Поигрались немного и хватит. Давай перейдем к чему-то по настоящему интересному.
9. Некоторые отношения типов неразрешимы!
Хорошо, сейчас будет действительно интересно, так что возьмите чашку
концентрированного кофе и давайте рассмотрим следующие два типа:
// A helper type. You could also just use List
interface Type {}
class C implements Type> {}
class D
implements Type
Так что же С и D вообще означают?
В каком-то смысле они рекурсивны, похоже на рекурсию в java.lang.Enum. Подумайте:
public abstract class Enum
С учетом указанных выше спецификаций, фактическая реализация enum – это всего
лишь синтаксический сахар:
// This
enum MyEnum {}
// Is really just sugar for this
class MyEnum extends Enum
Думая об этом давайте вернемся в нашим двум типам. Скомпилируется ли следующий
код?
class Test {
Type c = new C();
Type> d = new D
Сложный вопрос… и он фактически не решается?
Является ли С подтипом Type?
Step 0) C
Step 1) Type
Попробуйте скомпилировать это в вашем Eclipse или Idea и они расскажут все что о вас
думают.
Спустите это в сточную трубу…
Некоторые отношения типов в Java неразрешимы!
10. Пересечение типов
В Java есть очень интересная особенность под названием пересечение типов (type
intersection). Вы можете объявить (generic) тип, который является на самом деле
пересечением двух типов. Например:
>> >
Step 4) D
class Test
Параметр настраиваемого типа Т, который вы связываете с образцами класса Test
должен включать в себя как Serializable так и Cloneable интерфейсы. Например, String не
возможно ограничить, но Date – пожалуйста:
// Doesn't compile
Test
Эта особенность находит многократное использование в Java8, где вы можете
приводить типы. Как это помогает? Практически никак, но если вы хотите привести свое
лямбда-выражение в нужный вам тип, то другого пути нет. Допустим у вас есть такое
сумасшедшее ограничение в вашем методе:
Вы хотите Runnable который в тоже время Serializable только в том случае если хотите
выполнить это в другом месте и отправить результат по сети. Лямбда и сериализация
привносят немного иронии.
Вы можете сериализировать ваше лямбда-выражение если его целевой тип и
аргументы сериализуемы.
Но даже если это верно, они автоматически не включают интерфейс Serializable. Вы
должны самостоятельно приводить их к этому типу. Но когда вы приводите только к
Serializable:
execute((Serializable) (() -> {}));
тогда лямбда больше не будет Runnable, поэтому приводите их к обоим типам:
execute((Runnable & Serializable) (() -> {}));
И в заключение:
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
выдаёт от 0 до 9
может в многопоточном режиме запускать надо?
вот например в этих местах:
«Вы можете иметь перегруженные методы отличающиеся только возвращаемыми
типами»
«На самом деле это довольно много что происходит»
«умственный анализатор»
«используем быстрый тест»
«они должны быть равняться»
это, разумеется, далеко не всё.
и я уж не говорю про знаки препинания.
П.С. но ваще за сам факт работы и в принципе хоть и кривого, но таки понятного перевода — похлава)