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,""),""); } }
感谢在实践中学习。 以下是改善“机会”的建议:
-
只有一种Tuple可以存在(一旦设置了Typelock)。 这会损害想要使用多种类型元组的程序的可重用性和可伸缩性,除非你采用cut-n-paste重用(BirthdayTuple,DimensionsTuple,StreetAddressTuple,…)。 考虑一个接受目标类型的TupleFactory类,并创建一个元组构建器对象来生成元组。
-
没有记录“null”作为元组中值的有效性。 我认为在设置Typelock之前,允许null; 但是在设置了Typelock之后,代码将生成NullPointerException – 这是不一致的。 如果不允许它们,构造函数应该捕获它并禁止它(不管Typelock)。 如果允许它们,则整个代码(构造函数,等号,哈希码等)需要修改以允许它。
-
确定元组是否是不可变的值对象。 基于其缺乏制定者方法,我猜是这样的。 如果是这样,那么小心“采用”传入的数组 –
lastTuple=this.arr
。 即使它是一个var arg构造函数,也可以直接使用数组调用构造函数。 该类采用数组(保持对它的引用),然后可以在类之外更改数组中的值。 我会做一个数组的浅表副本,但也记录了具有非不可变值的元组的潜在问题(可以在元组之外进行更改)。 -
你的
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 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
这是最简单的解决方案,也是最好的解决方案。 它类似于在.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
的引用? 混合这样的静态引用是一种不好的做法。
坦率地说,代码很混乱,即使这个类的需要令人困惑。 如果这是我在工作环境中审查的代码,我不会允许它。
在wave项目中看到了这段代码
public class Tuple { private final A[] elements; public static Tuple of(A ... elements) { return new Tuple(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 o2 = (Tuple) o; return Arrays.equals(elements, o2.elements); } @Override public int hashCode() { return Arrays.hashCode(elements); } @Override public String toString() { return Arrays.toString(elements); } }
这是一个非常糟糕的n元组实现,它使用generics来提供编译时类型检查。 主要方法(为演示目的而提供)显示了使用它的可怕程度:
interface ITuple { } /** * Typed immutable arbitrary-length tuples implemented as a linked list. * * @param Type of the first element of the tuple * @param Type of the rest of the tuple */ public class Tuple 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>> triple = new Tuple>>("one", new Tuple>(2, new Tuple("three", END))); System.out.println(triple.car + "/" + triple.cdr.car + "/" + triple.cdr.cdr.car); //: one/2/three } }
如果您真的对编写类型安全容器感兴趣,请查看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 ) ; }