Java N-Tuple实现

时间:2010-09-04 13:21:43

标签: java tuples type-safety

我刚刚创建了一个类型安全的Java n元组 我正在使用一些非传统的方法来实现类型安全(我只是为了好玩)。

有人可以就改进它或一些可能的缺陷提供一些意见。

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,""),"");
    }
}

9 个答案:

答案 0 :(得分:47)

赞扬在实践中学习。以下是改进的“机会”建议:

  1. 只能存在一种元组(一旦设置了Typelock)。这会损害想要使用多种类型元组的程序的可重用性和可伸缩性,除非你采用cut-n-paste重用(BirthdayTuple,DimensionsTuple,StreetAddressTuple,...)。考虑一个接受目标类型的TupleFactory类,并创建一个元组构建器对象来生成元组。

  2. 没有记录“null”作为元组中值的有效性。我认为在设置Typelock之前,允许null;但是在设置了Typelock之后,代码将生成NullPointerException - 这是不一致的。如果不允许它们,构造函数应该捕获它并禁止它(不管Typelock)。如果允许它们,那么整个代码(构造函数,等号,哈希码等)需要修改以允许它。

  3. 确定元组是否属于不可变值对象。基于缺乏制定方法,我猜是这样的。如果是这样,那么请小心“采用”传入的数组 - lastTuple=this.arr。即使它是一个var arg构造函数,也可以直接使用数组调用构造函数。该类采用数组(保持对它的引用),然后可以在类外部更改数组中的值。我会做一个数组的浅表副本,但也记录了具有非不可变值的元组的潜在问题(可以在元组之外进行更改)。

  4. 您的equals方法缺少空检查(if (obj == null) return false)和类检查(obj instanceof Tuplethis.getClass().equals(object.getClass()))。平等成语已有详细记载。

  5. 除了通过toString之外,无法查看元组的值。这保护了价值观和整体不变性,但我认为它限制了阶级的用处。

  6. 虽然我意识到它只是一个例子,但我不希望将这个类用于生日/日期之类的东西。在具有固定对象类型的解决方案域中,真正的类(如Date)要好得多。我认为这个类在元组是第一类对象的特定域中很有用。

  7. 修改的 一直想着这个。以下是我对某些代码的处理(github + tests):

    ===
    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)
    

答案 1 :(得分:10)

这种类型如何安全?您正在抛出运行时异常,而不是在编译时报告类型错误。

你正试图在静态抽象的情况下抽象,这在静态类型的语言中是不可能的,而不会丢失类型安全。

<强>附录:

元组可以由异构元素(即具有不同类型的元素)组成。因此,对于这个Tuple课程来说,提供甚至“rutime typesafety”是不可能的。该课程的客户负责制作适当的演员表。

这是您在Java中可以做的最好的事情:编辑:有关Tuple的更好实施,请参阅Brent's post。(它没有在客户端需要进行类型转换。))

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);
  }
}

答案 2 :(得分:4)

这是最简单的解决方案,也是最好的解决方案。它类似于在.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;
    }
}

答案 3 :(得分:3)

你应该看看.NET's Tuple's implementation。它们是编译时类型安全的。

答案 4 :(得分:1)

typeLock的目的是什么?允许某人阻止构建更多这些对象?这部分没有多大意义。

为什么你想让别人阻止你的对象进一步实例化?如果出于某种原因,这是你需要的东西,而不是“锁定”一个类并抛出异常,只需确保代码路径......不会创建更多类型的对象。

设置为最后一个实例化lastTuple的引用的静态Tuple的目的是什么?混合这样的静态引用是一种不好的做法。

坦率地说,代码很混乱,尽管这个类的需求令人困惑。如果这是我在工作环境中审查的代码,我不会允许它。

答案 5 :(得分:1)

在wave项目中看到了这段代码

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);
  }
}

答案 6 :(得分:1)

这是一个非常糟糕的n-tuple实现,它使用泛型来提供编译时类型检查。主要方法(为演示目的而提供)显示了使用它的可怕程度:

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
    }
}

答案 7 :(得分:0)

如果您真的对编写类型安全容器感兴趣,请查看泛型:

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);
  }
}

答案 8 :(得分:0)

将泛型用于编译时类型安全性会更好。您可以为每个arity定义一个接口。然后,您可以定义单独的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 ) ; }