Кортежи объектов в Java и их коллекции

в 9:04, , рубрики: java, tuple, Песочница, метки:

Достаточно часто на практике возникает необходимость воспользоваться такими конструкциями как «пара» (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> {
    }
}

На рисунке ниже наглядно показан механизм описания кортежей произвольной длины и типов элементов
image
Рис. 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> {
}

image
Рис. 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


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js