В Java 8 есть лямбды и потоки, но нет кортежей, это позор . Вот почему мы реализовали кортежи в недостающих частях jOOλ — Java 8 . Кортежи — это действительно скучные контейнеры типа значения. По сути, это просто перечисление таких типов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class Tuple2<T1, T2> { public final T1 v1; public final T2 v2; public Tuple2(T1 v1, T2 v2) { this .v1 = v1; this .v2 = v2; } // [...] } public class Tuple3<T1, T2, T3> { public final T1 v1; public final T2 v2; public final T3 v3; public Tuple3(T1 v1, T2 v2, T3 v3) { this .v1 = v1; this .v2 = v2; this .v3 = v3; } // [...] } |
Написание классов кортежей — очень скучная задача, и ее лучше всего выполнять с помощью генератора исходного кода.
Кортежи на других языках и API
Текущая версия jOOλ показывает кортежи степеней 0 — 16. C # и другие языки .NET имеют типы кортежей от 1 до 8. Существует специальная библиотека для кортежей под названием Javatuples с кортежами от 1 до 10 степеней, и авторы пошли на дополнительные милю и дал кортежам отдельные английские имена:
01
02
03
04
05
06
07
08
09
10
|
Unit<A> // (1 element) Pair<A,B> // (2 elements) Triplet<A,B,C> // (3 elements) Quartet<A,B,C,D> // (4 elements) Quintet<A,B,C,D,E> // (5 elements) Sextet<A,B,C,D,E,F> // (6 elements) Septet<A,B,C,D,E,F,G> // (7 elements) Octet<A,B,C,D,E,F,G,H> // (8 elements) Ennead<A,B,C,D,E,F,G,H,I> // (9 elements) Decade<A,B,C,D,E,F,G,H,I,J> // (10 elements) |
Почему?
потому что Ennead действительно звонит этот сладкий звонок, когда я вижу его
Наконец, что не менее важно, jOOQ также имеет встроенный org.jooq.Record
тип org.jooq.Record
, который служит базовым типом для хороших подтипов, таких как Record7<T1, T2, T3, T4, T5, T6, T7>
jOOQ следует за Scala и определяет записи до степени 22.
Будьте осторожны при определении иерархий типов кортежей
Как мы видели в предыдущем примере, Tuple3
имеет много общего с Tuple2
.
Поскольку мы все сильно повреждены мозгом десятилетиями ориентации объектов и антипаттеров полиморфного дизайна, мы можем подумать, что было бы неплохо позволить Tuple3<T1, T2, T3>
расширять Tuple2<T1, T2>
, как Tuple3
просто добавляет еще один атрибут справа от Tuple2
, верно? Так…
01
02
03
04
05
06
07
08
09
10
|
public class Tuple3<T1, T2, T3> extends Tuple2<T1, T2> { public final T3 v3; public Tuple3(T1 v1, T2 v2, T3 v3) { super (v1, v2); this .v3 = v3; } // [...] } |
Правда в том, что это худшее, что вы можете сделать по нескольким причинам. Во-первых, да. И Tuple2
и Tuple3
являются кортежами, поэтому у них есть некоторые общие черты. Неплохая идея сгруппировать эти функции в общий супертип, такой как:
1
2
3
|
public class Tuple2<T1, T2> implements Tuple { // [...] } |
Но степень не одна из тех вещей. Вот почему:
Перестановки
Подумайте обо всех возможных кортежах, которые вы можете сформировать. Если вы позволите кортежам расширять друг друга, то Tuple5
также будет совместим, например, с Tuple2
. Следующее будет идеально скомпилировано:
1
|
Tuple2<String, Integer> t2 = tuple( "A" , 1 , 2 , 3 , "B" ); |
Tuple3
расширять Tuple2
, может показаться хорошим выбором по умолчанию просто удалить самый правый атрибут из кортежа в цепочке расширений.
Но в приведенном выше примере, почему я не хочу переназначить (v2, v4)
так, чтобы результат был (1, 3)
, или, возможно, (v1, v3)
, чтобы результат был ("A", 2)
Существует огромное количество комбинаций возможных атрибутов, которые могут представлять интерес при «уменьшении» кортежа более высокой степени до единицы более низкой степени. Ни при каких условиях по умолчанию удаление самого правого атрибута не будет достаточно общим для всех случаев использования.
Тип системы
Гораздо хуже, чем выше, будут серьезные последствия для системы типов, если Tuple3
расширит Tuple2
. Посмотрите API jOOQ, например. В jOOQ вы можете смело предполагать следующее :
1
2
3
4
5
|
// Compiles: TABLE1.COL1. in ( select (TABLE2.COL1). from (TABLE2)) // Must not compile: TABLE1.COL1. in ( select (TABLE2.COL1, TABLE2.COL2). from (TABLE2)) |
Первый предикат IN
верен. Левая часть предиката имеет один столбец ( в отличие от выражения значения строки ). Это означает, что правая часть предиката также должна работать с выражениями из одного столбца, например подзапросом SELECT
который выбирает один столбец (того же типа).
Во втором примере выбрано слишком много столбцов, и jOOQ API сообщит компилятору Java, что это неправильно.
Это гарантируется jOOQ через метод Field.in(Select)
, чья подпись гласит:
1
2
3
4
5
|
public interface Field<T> { ... Condition in(Select<? extends Record1<T>> select); ... } |
Таким образом, вы можете предоставить SELECT
которая создает любой подтип типа Record1<T>
.
К счастью, Record2
не расширяет Record1
Если бы сейчас Record2
расширил Record1
, что на первый Record1
могло показаться хорошей идеей, второй запрос неожиданно скомпилировался бы:
1
2
|
// This would now compile TABLE1.COL1. in ( select (TABLE2.COL1, TABLE2.COL2). from (TABLE2)) |
… даже если он формирует недопустимый оператор SQL. Он будет скомпилирован, потому что он сгенерирует тип Select<Record2<Type1, Type2>>
, который будет подтипом ожидаемого Select<Record1<Type1>>
из Field.in(Select)
.
Вывод
Tuple2
и Tuple5
являются принципиально несовместимыми типами. В системах с сильными типами не следует думать, что похожие типы или связанные типы также должны быть совместимыми типами.
Иерархии типов являются чем-то очень объектно-ориентированным, и под объектно-ориентированным я имею в виду ошибочное и чрезмерно спроектированное представление об объектной ориентации, от которого мы все еще страдаем с 90-х годов. Даже в «Предприятии» большинство людей научились отдавать предпочтение композиции, а не наследованию . Композиция в случае кортежей означает, что вы вполне можете преобразовать Tuple5
в Tuple2
. Но вы не можете назначить это.
В jOOλ такое преобразование может быть сделано очень легко следующим образом:
1
2
3
4
5
6
7
8
9
|
// Produces (1, 3) Tuple2<String, Integer> t2_4 = tuple( "A" , 1 , 2 , 3 , "B" ) .map((v1, v2, v3, v4, v5) -> tuple(v2, v4)); // Produces ("A", 2) Tuple2<String, Integer> t1_3 = tuple( "A" , 1 , 2 , 3 , "B" ) .map((v1, v2, v3, v4, v5) -> tuple(v1, v3)); |
Идея состоит в том, что вы оперируете неизменными значениями и можете легко извлечь части этих значений и сопоставить / перекомбинировать их с новыми значениями.
Ссылка: | Опасность полиморфизма подтипа, примененного к кортежам от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и AND JOOQ . |