Достаточно часто на практике возникает необходимость воспользоваться такими конструкциями как «пара» (Pair<First, Second>
), менее часто «тринарными» объектами (Triplet<First, Second, Third>
) и более длинными цепочками ассоциированных объектов. В связи с чем всегда удивлялся, почему в JDK (в java.lang.*
или java.util.*
) до настоящего момента нет стандартной Pair<First, Second>
или более длинных конструкций. Думаю многие программисты практикующие на Java имеют свою реализацию «пары». Не исключением являюсь и я.
И вот в очередной раз наткнувшись на необходимость работать с большим количеством различных кортежей, решил взяться за эту проблему системно. Придумал название проекту, определил цели и после экспериментов, занявших некоторое время, выложил код ( git://github.com/telesik/rumba.git ).
Чего хотелось достичь:
- Необходима удобная конструкция (назовем ее кортеж) для ассоциации нескольких объектов, типы которых определялись бы из позиции в полученном кортеже;
- Кортеж должен быть неограниченным по длине и типу;
- Типы элементов должны быть относительно контролируемым (как минимум ограничен дженериками);
- Описание структуры кортежа должно быть простым;
Явление на свет кортежа
И вот на свет появился интерфейс Cortege
:
public interface Cortege<V, T extends Cortege> {
T setValue(V value);
T nextElement();
<Vi> Vi getValue(int index) throws ClassCastException; // be careful with types. Type is not bounded
int getDeep();
<Vi> void setValue(int index, Vi value); // be careful with types. Type is not bounded
Cortege<V, T> setValues(Object... values); // be careful with types. Type is not bounded!!!
V getValue();
static abstract class End<V, T extends Cortege<V, T>> implements Cortege<V, T> {
}
}
На рисунке ниже наглядно показан механизм описания кортежей произвольной длины и типов элементов
Рис. 1. механизм описания кортежей произвольной длины и типов элементов
Но что есть “голый” интерфейс, если нет ни одной реализации? Есть такая! Имя ей CortegeChain
. Наличие в названии слова Chain обусловлено способом организации хранения элементов в кортеже. Предполагаю и надеюсь, что в будущем появятся другие реализации, оптимизированные для различных типов использования.
В реализации CortegeChain
я не ставил особых целей оптимизации с точки зрения использования памяти или скорости работы. Основной задачей, которую я хотел решить — это попробовать саму идею, найти слабые места, обозначить очевидные и не очевидные возможности, открывающиеся перед разработчиком.
Примеры использования
Перейду сразу к наглядным примерам использования:
// создание инстанса кортежа:
Cortege<Long, Cortege<String, Cortege.End>> cortegeLS = CortegeChain.create(2);
Следует обратить внимание на аргумент метода create. В связи с тем, что в Java дженерики существуют лишь на этапе компиляции, и добраться к ним в runtime я «законных» методов не нашел :(, пришлось «заплатить» за это такой ценой. Суть этого параметра — декларация глубины создаваемого кортежа. Но в виду того, что разработчик как правило знает кол-во элементов, необходимость указания этого параметра не должно вызывать больших проблем.
примечание: Но все же, если кто-то может предложить свое решение это проблемки с параметром — я буду чрезвычайно благодарен.
Итак пример,
// заполнение элементов значениями
// 1-й вариант (заполняем первый элемент в кортеже, с контролем типа)
cortegeLS.setValue(4L);
cortegeLS.nextElement().setValue("str");
// 2-й вариант (заполняем подряд цепью, с контролем типа)
cortegeLS.setValue(4L).setValue("str");
// 3-й вариант (заполняем массивом, без контроля типа)
cortegeLS.setValues(4L, "str");
Теперь чтение,
// 1-й вариант (чтение первого элемента в кортеже, с контролем типа)
Long valueA = cortegeLS.getValue();
// 2-й вариант (чтение выбранного элемента в кортеже, с контролем типа)
String valueB = cortegeLS.nextElement().getValue();
// 3-й вариант (чтение выбранного элемента в кортеже, без контроля типа)
Long valueC = cortegeLS.getValue(1);
String valueD = cortegeLS.getValue(2);
Что еще можно сделать с кортежем:
1. Получить кортеж “справа”.
Cortege<String, Cortege.End> rightCortegeS = cortegeLS.nextElement();
// еще раз
Cortege.End rightCortegeEnd = cortegeLS.nextElement().nextElement();
2. Получить “глубину” (кол-во элементов в кортеже)
int deep = cortegeLS.getDeep();
Пожалуй все. Но не все! :)
Коллекции кортежей
Создавал я кортеж не просто так. Теперь имея такую конструкцию хорошо бы ее «прикрутить» к дружному семейству коллекций (java.util.Collection
). Сказано — сделано. Но банальное использование кортежа как элемента коллекции не интересно. Хочется не просто находить, удалять, модифицировать элементы коллекции вцелом, что дают традиционные реализации интерфейсов java.util.Set
и java.util.List
, но и искать, фильтровать по отдельным элементам кортежей, хранящимся в коллекции, модифицировать колонки целиком и т.п, что является естественным для реляционных таблиц.
Итак вновь поставив цели:
- Коллекция должна быть совместима с давно ставшими стандартом интерфейсами семейства
java.util.Collection
(такими какjava.util.Set
иjava.util.List
) - Имплементация должна расширять функционал своих аналогов в мире простых объектов (например таких как
java.util.HashSet<T>
); - В коллекции должно быть легко искать по абстрактному критерию;
- Необходимо реализовать получение подмножества;
- Очень хотелось бы реализовать возможность создания представления (аналог view в реляционных базах) над коллекцией.
- представление не “владеет” исходными данными, а лишь фильтрует оригинальную коллекцию согласно критерию
- фильтрация должна быть не одноразовой, а динамической. т.е. должна быть актуальной и отражать состояние оригинальной таблицы. все вставки, модификации и удаления из коллекции должны отражаться в ее представлении согласно предикату наложенному на view.
Примечание: Из всего, что я хотел, не получилось только реализовать последний пункт “представление” (аналог view в реляционных базах данных). Но работа идет и возможно в недалеком будущем на свет появится реализация и этой многообещающей идеи.
И вот что получилось:
public interface CortegeCollection<T extends Cortege> extends Collection<T>, Iterable<T> {
<T> boolean contains(int num, T obj);
CortegeCollection<T> extract(int num, Object key);
CortegeCollection<T> extract(Corteges.Predicate<T> predicate);
// <C> CortegeCollection<T> view(int num, Corteges.Predicate<C> predicate);
T findAny(int num, Object key);
T findAny(Corteges.Predicate<T> predicate);
<Vi> List<Vi> getColumnCopy(int num);
<Vi> void fill(int num, Vi value);
}
public interface CortegeSet<T extends Cortege> extends CortegeCollection<T>, Set<T> {
}
public interface CortegeList<T extends Cortege> extends CortegeCollection<T>, List<T> {
}
Рис. 2. Топология кортежных коллекций
Примеры использования коллекций кортежей
Теперь наверное стоит как и в случае с описанием Cortege
сразу перейти к наглядным примерам использования:
public class ExampleCollections {
public static void main(String[] args) {
// Создание экземпляра CortegeHashSet
CortegeSet<Cortege<Long, Cortege<String, Cortege.End>>> cortegeHashSetLS = Corteges.newCortegeHashSet(2);
for (long i = 0; i < 5; i++) {
Cortege<Long, Cortege<String, Cortege.End>> cortegeLS = CortegeChain.create(2);
cortegeLS.setValue(i).setValue("" + i);
cortegeHashSetLS.add(cortegeLS);
}
for (Cortege cortege : cortegeHashSetLS) {
System.out.println(cortege);
}
cortegeHashSetLS.add(CortegeChain.<Long, Cortege<String, Cortege.End>>create(2));
Cortege<Long, Cortege<String, Cortege.End>> cortegeIS = CortegeChain.create(2);
System.out.println(cortegeHashSetLS.contains(cortegeIS));
cortegeIS.setValue(null).setValue("3");
System.out.println(cortegeIS);
System.out.println(cortegeHashSetLS.contains(cortegeIS));
System.out.println(cortegeHashSetLS.contains(1, 3L));
Cortege<Long, Cortege<Long, Cortege<String, Cortege.End>>> cortegeLLS1 = CortegeChain.create(3);
Cortege<Long, Cortege<Long, Cortege<String, Cortege.End>>> cortegeLLS2 = CortegeChain.create(3);
Cortege<Long, Cortege<Long, Cortege<String, Cortege.End>>> cortegeLLS3 = CortegeChain.create(3);
CortegeChain<String, CortegeChain<Long, CortegeChain<String, Cortege.End>>> cortegeSLS = CortegeChain.create(3);
cortegeLLS1.setValue(1L);
cortegeLLS1.nextElement().setValue(11L);
cortegeLLS1.nextElement().nextElement().setValue("AAA");
cortegeLLS2.setValue(2L);
cortegeLLS2.nextElement().nextElement().setValue("BBB");
cortegeLLS3.setValue(3L);
cortegeLLS3.nextElement().setValue(33L);
cortegeLLS3.nextElement().nextElement().setValue("AAA");
CortegeHashSet<Cortege<Long, Cortege<Long, Cortege<String, Cortege.End>>>> cortegeSetLLS = Corteges.newCortegeHashSet(cortegeLLS1.getDeep());
System.out.println(cortegeSetLLS.contains(cortegeLLS1));
cortegeSetLLS.add(cortegeLLS1);
cortegeSetLLS.add(cortegeLLS2);
cortegeSetLLS.add(cortegeLLS3);
System.out.println(cortegeSetLLS.contains(cortegeLLS1));
for (Cortege<Long, Cortege<Long, Cortege<String, Cortege.End>>> cortege : cortegeSetLLS) {
System.out.println(cortege);
}
System.out.println(cortegeSetLLS.contains(3, "AAA"));
cortegeSetLLS.fill(1, 5L);
cortegeSetLLS.fill(2, 8L);
cortegeSetLLS.fill(3, "XXX");
for (Cortege<Long, Cortege<Long, Cortege<String, Cortege.End>>> cortege : cortegeSetLLS) {
System.out.println(cortege);
}
// Collection<Cortege> corteges = cortegeSetLLS.extract(2, "111");
}
}
Подведем итоги
Как видно из примера, получилась достаточно удобная библиотека для хранения и манипулирования с длинными кортежами объектов. К бенефитам библиотеки можно отнести:
- Создание кортежа произвольной длины достаточно просто и сводится лишь к перечислению типов элементов составляющих будущий экземпляр кортежа
- При последовательном обращении к элементам кортежа контролируется тип элемента
- Благодаря выбранной структуре декларации и хранения кортежа возможно получение под-кортежа «справа» (метод
nextElement()
)
Кроме самой структуры Cortege в библиотеке есть два типа коллекций, расширяющих всем известные java.util.Set
и java.util.List
) соответственно com.rumba.cortege.CortegeSet
и com.rumba.cortege.CortegeList
).
- Есть возможность найти кортеж или подмножество кортежей в коллекции по простому критерию эквивалентности элемента эталонному объекту (методы extract(int num, Object key) и findAny(int num, Object key))
- Есть возможность найти кортеж или подмножество кортежей в коллекции по предикату (методы extract(Corteges.Predicate<T> predicate) и findAny(Corteges.Predicate<T> predicate))
- Есть возмодность получить любую колонку с указанным номером образованную из элементов кортежей коллекции
- Есть возмодность заполнить целиком колонку элементов
Но и есть очевидные недостатки обусловленные в первую очередь невозможностью «добраться» к декларациям дженериков в Java и, как следствие, потеря контроля над типом при обращении по индексу к элементу кортежа. Справедливости ради я поискал библиотеки, решающие подобные задачи, и нашел достаточно интересную реализацию javatuples. В ней проблема декларации «длинных» цепочек ассоциациированных объектов решается простым перебором “разумного” с точки зрения разработчиков максимального размера кортежа.
- 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)
Заключение
Работая над библиотекой я старался покрыть наиболее типичные, и с моей точки зрения, полезные задачи, возникающие при работе с кортежами и их коллекциями. К сожалению не все планы реализовано, но надеюсь та толика работы, что была выполнена будет кому-нибудь полезна. Но работа продолжается, надеюсь не зря.
Было бы очень интересно получить конструктивный фитбэк на эту статью и библиотеку.
Полезные ссылки
javatuples
достаточно интересная попытка реализации
Автор: voilesik