Прежде чем перейти к самой статье, я хотел бы поблагодарить Даниэля Дитриха , автора потрясающей библиотеки Javaslang , у которого была идея до меня:
Контравариантные родовые границы
Все началось с твита:
Я хотел сделать что-то вроде сопоставления с шаблоном общего супертипа набора типов, например:
1
|
<T super T1 | T2 | ... | TN> |
Обратите внимание, что мне действительно нужна поддержка типов объединения, а не типов пересечений, как я изначально заявлял.
Почему я хотел это сделать? Потому что это было бы хорошим дополнением к библиотеке jOOλ , которая содержит типизированные кортежи для Java :
1
2
3
4
5
6
7
|
class Tuple3<T1, T2, T3> { final T1 v1; final T2 v2; final T3 v3; // Lots of useful stuff here } |
В кортеже было бы неплохо использовать метод forEach()
который перебирает все атрибуты:
1
|
tuple( 1 , "a" , null ).forEach(System.out::println); |
Вышесказанное просто даст:
1
2
3
|
1 "a" null |
Теперь, каким будет тип аргумента этого метода forEach()
? Это будет выглядеть так:
1
2
3
|
class Tuple3<T1, T2, T3> { void forEach(Consumer<? super T1 | T2 | T3> c) {} } |
Потребитель получит объект типа T1, T2 или T3. Но потребитель, который принимает общий супертип предыдущих трех типов, тоже в порядке. Например, если у нас есть:
1
2
|
Tuple2<Integer, Long> tuple = tuple( 1 , 2L); tuple.forEach(v->System.out.println(v.doubleValue())); |
Вышеприведенное скомпилирует, потому что Number
— это общий супер тип Integer
и Long
, и он содержит метод doubleValue()
.
К сожалению, это невозможно в Java
Java в настоящее время поддерживает типы объединения / суммы ( см. Также алгебраические типы данных ) только для блоков перехвата исключений, где вы можете писать такие вещи, как:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
class X extends RuntimeException { void print() {} } class X1 extends X {} class X2 extends X {} // With the above try { ... } catch (X1 | X2 e) { // This compiles for the same reasons! e.print(); } |
Но, к сожалению, блоки catch — это единственное место в Java, которое позволяет использовать типы сумм.
Именно здесь вступает в игру хитрый и хитрый обходной путь Дэниела Мы можем написать статический метод, который выполняет некоторое «сопоставление с образцом» (если вы щуритесь), используя обобщения, наоборот:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
static < T, T1 extends T, T2 extends T, T3 extends T > void forEach( Tuple3<T1, T2, T3> tuple, Consumer<? super T> consumer ) { consumer.accept(tuple.v1); consumer.accept(tuple.v2); consumer.accept(tuple.v3); } |
Вышеизложенное теперь можно безопасно использовать для определения общего супертипа (ов) T1, T2 и T3:
1
2
3
4
|
Tuple2<Integer, Long> t = tuple( 1 , 2L); forEach(t, c -> { System.out.println(c.doubleValue()); }); |
уступая, как и ожидалось:
1
2
|
1.0 2.0 |
Это имеет смысл, потому что ограничения общего типа просто задаются «наоборот», то есть когда T1 extends T
принудительно T1 extends T
, T super T1
…
Если ты очень сильно щуришься 😉
Этот метод предположительно используется Дэниелом в предстоящем API соответствия шаблонов Javaslang . Мы с нетерпением ожидаем увидеть это в действии!
Ссылка: | Гениальный обходной путь для эмуляции типов сумм в Java от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и AND JOOQ . |