Внедрение Java N-Tuple

Я только что создал Java-код, который безопасен по типу.
Я использую некоторые нетрадиционные методы для обеспечения безопасности типов (я просто сделал это для удовольствия).

Может кто-то может внести свой вклад в улучшение его или некоторые возможные недостатки.

public class Tuple {
    private Object[] arr;
    private int size;
    private static boolean TypeLock = false;
    private static Object[] lastTuple = {1,1,1}; //default tuple type

    private Tuple(Object ... c) {
        // TODO Auto-generated constructor stub
        size=c.length;
        arr=c;
        if(TypeLock)
        {
            if(c.length == lastTuple.length)
                for(int i = 0; i<c.length; i++)
                {
                    if(c[i].getClass() == lastTuple[i].getClass())
                        continue;
                    else
                        throw new RuntimeException("Type Locked");
                }
            else
                throw new RuntimeException("Type Locked");
        }

        lastTuple = this.arr;
    }

    public static void setTypeLock(boolean typeLock) {
        TypeLock = typeLock;
    }

    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        if (this == obj)
            return true;

        Tuple p = (Tuple)obj;

        for (int i = 0; i < size; i++)
        {
            if (p.arr[i].getClass() == this.arr[i].getClass())
            {
                if (!this.arr[i].equals(p.arr[i]))
                    return false;
            }
            else
                return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        // TODO Auto-generated method stub
        int res = 17;
        for(int i = 0; i < size; i++)
            res = res*37+arr[i].hashCode();

        return res;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return Arrays.toString(arr);
    }

    public static void main(String[] args) {
        HashMap<Tuple,String> birthDay = new HashMap<Tuple,String>();
        Tuple p = new Tuple(1,2,1986);
        Tuple.setTypeLock(true);
        Tuple p2 = new Tuple(2,10,2009);
        Tuple p3 = new Tuple(1,2,2010);
        Tuple p4 = new Tuple(1,2,2010);
        birthDay.put(p,"Kevin");
        birthDay.put(p2,"Smith");
        birthDay.put(p3,"Sam");
        birthDay.put(p4, "Jack");
        System.out.println(birthDay);
        System.out.println(birthDay.get(new Tuple(1,2,1986)));
        birthDay.put(new Tuple(1,2,""),"");
    }
}

Ответ 1

Престижность в обучении. Вот предложения "возможностей" для улучшения:

  • Только один тип кортежа может когда-либо существовать (после установки Typelock). Это вредит повторному использованию и масштабируемости в программах, которые хотят использовать несколько типов кортежей, если вы не прибегаете к повторному использованию cut-n-paste (BirthdayTuple, DimensionsTuple, StreetAddressTuple,...). Рассмотрим класс TupleFactory, который принимает целевые типы и создает объект построителя кортежей для генерации кортежей.

  • Действительность "null" как значения в кортеже не документирована. Я думаю, что до установки Typelock допускается null; но после установки Typelock код будет генерировать исключение NullPointerException - это противоречиво. Если они не разрешены, конструктор должен поймать его и запретить (независимо от Typelock). Если они разрешены, то общий код (конструктор, equals, hashcode и т.д.) Нуждается в модификации, чтобы разрешить его.

  • Определите, предназначены ли Tuples для объектов неизменяемых значений. Основываясь на отсутствии методов setter, я бы предположил. Если да, тогда будьте осторожны с "принятием" входящего массива - lastTuple=this.arr. Хотя конструктор var arg конструктор может быть вызван непосредственно с массивом. Класс принимает массив (сохраняет ссылку на него), и после этого значения в массиве могут быть изменены вне класса. Я сделал бы мелкую копию массива, но также документировал потенциальную проблему с Tuples с неизменяемыми значениями (которые могут быть изменены за пределами Tuple).

  • В вашем методе equals отсутствует нулевая проверка (if (obj == null) return false) и проверка класса (либо obj instanceof Tuple, либо this.getClass().equals(object.getClass())). Идиома равенства хорошо документирована.

  • Невозможно просмотреть значения кортежа, кроме toString. Это защищает ценности и общую неизменность, но я считаю, что это ограничивает полезность класса.

  • Пока я понимаю его пример, я бы не стал использовать этот класс для чего-то вроде дней рождения/дат. В областях решений с фиксированными типами объектов реальные классы (например, Date) намного лучше. Я бы предположил, что этот класс полезен в определенных областях, где кортежи являются объектами первого класса.

Изменить Думал об этом. Здесь я беру код (github + тесты):

===
Tuple.java
===
package com.stackoverflow.tuple;

/**
 * Tuple are immutable objects.  Tuples should contain only immutable objects or
 * objects that won't be modified while part of a tuple.
 */
public interface Tuple {

    public TupleType getType();
    public int size();
    public <T> T getNthValue(int i);

}


===
TupleType.java
===
package com.stackoverflow.tuple;

/**
 * Represents a type of tuple.  Used to define a type of tuple and then
 * create tuples of that type.
 */
public interface TupleType {

    public int size();

    public Class<?> getNthType(int i);

    /**
     * Tuple are immutable objects.  Tuples should contain only immutable objects or
     * objects that won't be modified while part of a tuple.
     *
     * @param values
     * @return Tuple with the given values
     * @throws IllegalArgumentException if the wrong # of arguments or incompatible tuple values are provided
     */
    public Tuple createTuple(Object... values);

    public class DefaultFactory {
        public static TupleType create(final Class<?>... types) {
            return new TupleTypeImpl(types);
        }
    }

}


===
TupleImpl.java (not visible outside package)
===
package com.stackoverflow.tuple;

import java.util.Arrays;

class TupleImpl implements Tuple {

    private final TupleType type;
    private final Object[] values;

    TupleImpl(TupleType type, Object[] values) {
        this.type = type;
        if (values == null || values.length == 0) {
            this.values = new Object[0];
        } else {
            this.values = new Object[values.length];
            System.arraycopy(values, 0, this.values, 0, values.length);
        }
    }

    @Override
    public TupleType getType() {
        return type;
    }

    @Override
    public int size() {
        return values.length;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getNthValue(int i) {
        return (T) values[i];
    }

    @Override
    public boolean equals(Object object) {
        if (object == null)   return false;
        if (this == object)   return true;

        if (! (object instanceof Tuple))   return false;

        final Tuple other = (Tuple) object;
        if (other.size() != size())   return false;

        final int size = size();
        for (int i = 0; i < size; i++) {
            final Object thisNthValue = getNthValue(i);
            final Object otherNthValue = other.getNthValue(i);
            if ((thisNthValue == null && otherNthValue != null) ||
                    (thisNthValue != null && ! thisNthValue.equals(otherNthValue))) {
                return false;
            }
        }

        return true;
    }

    @Override
    public int hashCode() {
        int hash = 17;
        for (Object value : values) {
            if (value != null) {
                hash = hash * 37 + value.hashCode();
            }
        }
        return hash;
    }

    @Override
    public String toString() {
        return Arrays.toString(values);
    }
}


===
TupleTypeImpl.java (not visible outside package)
===
package com.stackoverflow.tuple;

class TupleTypeImpl implements TupleType {

    final Class<?>[] types;

    TupleTypeImpl(Class<?>[] types) {
        this.types = (types != null ? types : new Class<?>[0]);
    }

    public int size() {
        return types.length;
    }

    //WRONG
    //public <T> Class<T> getNthType(int i)

    //RIGHT - thanks Emil
    public Class<?> getNthType(int i) {
        return types[i];
    }

    public Tuple createTuple(Object... values) {
        if ((values == null && types.length == 0) ||
                (values != null && values.length != types.length)) {
            throw new IllegalArgumentException(
                    "Expected "+types.length+" values, not "+
                    (values == null ? "(null)" : values.length) + " values");
        }

        if (values != null) {
            for (int i = 0; i < types.length; i++) {
                final Class<?> nthType = types[i];
                final Object nthValue = values[i];
                if (nthValue != null && ! nthType.isAssignableFrom(nthValue.getClass())) {
                    throw new IllegalArgumentException(
                            "Expected value #"+i+" ('"+
                            nthValue+"') of new Tuple to be "+
                            nthType+", not " +
                            (nthValue != null ? nthValue.getClass() : "(null type)"));
                }
            }
        }

        return new TupleImpl(this, values);
    }
}


===
TupleExample.java
===
package com.stackoverflow.tupleexample;

import com.stackoverflow.tuple.Tuple;
import com.stackoverflow.tuple.TupleType;

public class TupleExample {

    public static void main(String[] args) {

        // This code probably should be part of a suite of unit tests
        // instead of part of this a sample program

        final TupleType tripletTupleType =
            TupleType.DefaultFactory.create(
                    Number.class,
                    String.class,
                    Character.class);

        final Tuple t1 = tripletTupleType.createTuple(1, "one", 'a');
        final Tuple t2 = tripletTupleType.createTuple(2l, "two", 'b');
        final Tuple t3 = tripletTupleType.createTuple(3f, "three", 'c');
        final Tuple tnull = tripletTupleType.createTuple(null, "(null)", null);
        System.out.println("t1 = " + t1);
        System.out.println("t2 = " + t2);
        System.out.println("t3 = " + t3);
        System.out.println("tnull = " + tnull);

        final TupleType emptyTupleType =
            TupleType.DefaultFactory.create();

        final Tuple tempty = emptyTupleType.createTuple();
        System.out.println("\ntempty = " + tempty);

        // Should cause an error
        System.out.println("\nCreating tuple with wrong types: ");
        try {
            final Tuple terror = tripletTupleType.createTuple(1, 2, 3);
            System.out.println("Creating this tuple should have failed: "+terror);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace(System.out);
        }

        // Should cause an error
        System.out.println("\nCreating tuple with wrong # of arguments: ");
        try {
            final Tuple terror = emptyTupleType.createTuple(1);
            System.out.println("Creating this tuple should have failed: "+terror);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace(System.out);
        }

        // Should cause an error
        System.out.println("\nGetting value as wrong type: ");
        try {
            final Tuple t9 = tripletTupleType.createTuple(9, "nine", 'i');
            final String verror = t9.getNthValue(0);
            System.out.println("Getting this value should have failed: "+verror);
        } catch (ClassCastException ex) {
            ex.printStackTrace(System.out);
        }

    }

}

===
Sample Run
===
t1 = [1, one, a]
t2 = [2, two, b]
t3 = [3.0, three, c]
tnull = [null, (null), null]

tempty = []

Creating tuple with wrong types: 
java.lang.IllegalArgumentException: Expected value #1 ('2') of new Tuple to be class java.lang.String, not class java.lang.Integer
    at com.stackoverflow.tuple.TupleTypeImpl.createTuple(TupleTypeImpl.java:32)
    at com.stackoverflow.tupleexample.TupleExample.main(TupleExample.java:37)

Creating tuple with wrong # of arguments: 
java.lang.IllegalArgumentException: Expected 0 values, not 1 values
    at com.stackoverflow.tuple.TupleTypeImpl.createTuple(TupleTypeImpl.java:22)
    at com.stackoverflow.tupleexample.TupleExample.main(TupleExample.java:46)

Getting value as wrong type: 
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at com.stackoverflow.tupleexample.TupleExample.main(TupleExample.java:58)

Ответ 2

Как это называется? Вы бросаете исключения во время выполнения вместо того, чтобы сообщать о ошибках типа во время компиляции.

Вы пытаетесь абстрагироваться от арности, которая (пока) невозможна в статически типизированных языках, не теряя при этом типов безопасности.

Добавление:

Кортежи могут состоять из гетерогенных элементов (т.е. элементов с разными типами). Поэтому для этого класса Tuple невозможно обеспечить даже "мелочи типа rutime". Клиенты класса несут ответственность за выполнение соответствующих бросков.

Это лучшее, что вы можете сделать в Java: ( Изменить: Смотрите сообщение Brent для лучшего реализация Tuple. (Это не требует машинных приемов на стороне клиента.))

final class Tuple {
  private final List<Object> elements;

  public Tuple(final Object ... elements) {
    this.elements = Arrays.asList(elements);
  }

  @Override
  public String toString() {
    return elements.toString();
  }

  //
  // Override 'equals' and 'hashcode' here
  //

  public Object at(final int index) {
    return elements.get(index);
  }
}

Ответ 3

Это самое простое решение, и это тоже самое лучшее. Это похоже на то, как Tuples представлены в .NET. Он тщательно оборачивает стирание Java. Он строго типизирован. Он не бросает исключений. Он очень прост в использовании.

public interface Tuple
{
    int size();
}

public class Tuple2<T1,T2> implements Tuple
{
    public final T1 item1;
    public final T2 item2;

    public Tuple2(
        final T1 item_1,
        final T2 item_2)
    {
        item1 = item_1;
        item2 = item_2;
    }

    @Override
    public int size()
    {
        return 2;
    }
}

public class Tuple3<T1,T2,T3> implements Tuple
{
    public final T1 item1;
    public final T2 item2;
    public final T3 item3;

    public Tuple3(
        final T1 item_1,
        final T2 item_2,
        final T3 item_3)
    {
        item1 = item_1;
        item2 = item_2;
        item3 = item_3;
    }

    @Override
    public int size()
    {
        return 3;
    }
}

Ответ 4

Вы должны посмотреть . Реализация NET Tuple. Они являются безопасными для времени компиляции.

Ответ 5

Какова цель typeLock? Чтобы позволить кому-то предотвратить создание каких-либо из этих объектов? Эта часть не имеет большого смысла.

Почему вы хотите, чтобы кто-то препятствовал дальнейшему созданию объектов? Если по какой-то причине это то, что вам когда-либо понадобится, вместо "блокировки" класса и исключения исключений, просто убедитесь, что путь к коду... не создает больше объектов типа.

Какова цель статического lastTuple, который задан ссылкой на последний экземпляр Tuple? Это плохая практика для смешивания статических ссылок, подобных этому.

Честно говоря, код довольно запутанный, хотя потребность в этом классе запутанна. Если каким-то образом это был код, который я просматривал в рабочей среде, я бы этого не допустил.

Ответ 6

увидел этот код в волновом проекте

public class Tuple<A> {

  private final A[] elements;

  public static <A> Tuple<A> of(A ... elements) {
    return new Tuple<A>(elements);
  }

  public Tuple(A ... elements) {
    this.elements = elements;
  }

  public A get(int index) {
    return elements[index];
  }

  public int size() {
    return elements.length;
  }

  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }

    if (o == null || o.getClass() != this.getClass()) {
      return false;
    }

    Tuple<A> o2 = (Tuple<A>) o;
    return Arrays.equals(elements, o2.elements);
  }

  @Override
  public int hashCode() {
    return Arrays.hashCode(elements);
  }

  @Override
  public String toString() {
    return Arrays.toString(elements);
  }
}

Ответ 7

Здесь действительно ужасная реализация n-кортежа, которая использует generics для проверки типа времени компиляции. Основной метод (предусмотренный для демонстрационных целей) показывает, насколько ужасно это будет использовать:

interface ITuple { }

/**
 * Typed immutable arbitrary-length tuples implemented as a linked list.
 *
 * @param <A> Type of the first element of the tuple
 * @param <D> Type of the rest of the tuple
 */
public class Tuple<A, D extends ITuple> implements ITuple {

    /** Final element of a tuple, or the single no-element tuple. */
    public static final TupleVoid END = new TupleVoid();

    /** First element of tuple. */
    public final A car;
    /** Remainder of tuple. */
    public final D cdr;

    public Tuple(A car, D cdr) {
        this.car = car;
        this.cdr = cdr;
    }

    private static class TupleVoid implements ITuple { private TupleVoid() {} }

    // Demo time!
    public static void main(String[] args) {
        Tuple<String, Tuple<Integer, Tuple<String, TupleVoid>>> triple =
                new Tuple<String, Tuple<Integer, Tuple<String, TupleVoid>>>("one",
                        new Tuple<Integer, Tuple<String, TupleVoid>>(2,
                                new Tuple<String, TupleVoid>("three",
                                        END)));
        System.out.println(triple.car + "/" + triple.cdr.car + "/" + triple.cdr.cdr.car);
        //: one/2/three
    }
}

Ответ 8

Если вы действительно заинтересованы в написании контейнеров типа безопасного типа, посмотрите на generics:

public class Tuple<T> {
  private final T[] arr;
  public Tuple (T... contents) {
    arr = contents;  //not sure if this compiles??
  }

  // etc

  public static final void main(String[] args) {
    Tuple<String> stringTuple = new Tuple<String>("Hello", "World!");
    Tuple<Integer> intTuple = new Tuple<Integer>(2010,9,4);
  }
}

Ответ 9

Было бы лучше использовать generics для безопасности типа времени компиляции. Вы можете определить один интерфейс для каждой цели. Затем вы можете определить отдельные интерфейсы Callable для доступа к значениям кортежа.

interface Tuple1 <T0> { <R> R accept ( Callable1<R,T0> callable ) ; }

interface Tuple2 <T0,T1> { <R> R accept ( Callable2<R,T0,T1> callable ) ; }

...

interface Tuplek <T0,T1,T2,...,Tk> { <R> R accept ( Callablek<R,T0,T1,T2,...,Tk> callable ) ; }

interface Callable1<R,T0> { R call ( T0 t0 ) ; }

interface Callable2<R,T0> { R call ( T0 t0 , T1 t1 ) ; }

....

interface Callablek<R,T0,T1,T2,...,Tk> { R call ( T0 t0 , T1 t1 , T2 t2 , ... , Tk tk ) ; }