Java N-Tuple实现

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

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

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 birthDay = new HashMap(); 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. 只有一种Tuple可以存在(一旦设置了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)要好得多。 我想这个类在元组是第一类对象的特定域中是有用的。

编辑一直在想这个。 这是我对一些代码的看法(在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 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 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  Class 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) 

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

你试图在静态中抽象,这是(现在)在静态类型语言中不可能的,而不会丢失类型安全。

附录:

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

这是你在Java中可以做的最好的事情:( 编辑:请参阅布伦特的文章 ,以更好地实现Tuple 。(它不需要在客户端进行类型转换。))

 final class Tuple { private final List 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); } } 

这是最简单的解决方案,也是最好的解决方案。 它类似于在.NET中表示元组的方式。 它仔细回避了java擦除。 它是强类型的。 它不会抛出exception。 这是非常容易使用。

 public interface Tuple { int size(); } public class Tuple2 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 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; } } 

你应该看看.NET的Tuple的实现 。 它们是编译时类型安全的。

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

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

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

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

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

 public class Tuple { private final T[] arr; public Tuple (T... contents) { arr = contents; //not sure if this compiles?? } // etc public static final void main(String[] args) { Tuple stringTuple = new Tuple("Hello", "World!"); Tuple intTuple = new Tuple(2010,9,4); } } 

将generics用于编译时类型安全性会更好。 您可以为每个arity定义一个接口。 然后,您可以定义单独的Callable接口以访问元组的值。

 interface Tuple1  {  R accept ( Callable1 callable ) ; } interface Tuple2  {  R accept ( Callable2 callable ) ; } ... interface Tuplek  {  R accept ( Callablek callable ) ; } interface Callable1 { R call ( T0 t0 ) ; } interface Callable2 { R call ( T0 t0 , T1 t1 ) ; } .... interface Callablek { R call ( T0 t0 , T1 t1 , T2 t2 , ... , Tk tk ) ; }