質問
タイプセーフの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<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種類のタプルのみが存在することができます(タイプロックが設定されると)。これは、カット-N-Pasteの再利用に頼らない限り、複数のタイプのタプルを使用したいプログラムの再利用性とスケーラビリティを傷つけます(BirthndTuple、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
. 。これにより、値と全体的な不変性が保護されますが、クラスの有用性を制限すると思います。私はそれが単なる例であることに気づきましたが、私はこのクラスを誕生日や日付のようなものに使用することを期待していません。固定オブジェクトタイプのソリューションドメインでは、実際のクラス(日付など)が非常に優れています。このクラスは、タプルがファーストクラスのオブジェクトである特定のドメインで役立つと思います。
編集これについて考えていました。これがいくつかのコードについての私の見解です(オン 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)
他のヒント
これはどうですか?コンパイル時にタイプエラーを報告する代わりに、ランタイムの例外をスローしています。
あなたは、タイプフェティを失うことなく、静的にタイプされた言語では(まだ)不可能なアリティを抽象化しようとしています。
補遺:
タプルは、不均一な要素(つまり、タイプが異なる要素)で構成できます。したがって、「ルティタイプタイプフェティ」さえも提供することは不可能です。 Tuple
クラス。クラスのクライアントは、適切なキャストを作成する責任があります。
これはあなたがJavaでできる最善のことです: (編集: 見る ブレントの投稿 より良い実装のために 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);
}
}
これは最も単純なソリューションであり、最高のソリューションでもあります。これは、タプルが.NETでどのように表現されるかに似ています。 Java Erasureを注意深く回避します。強く入力されます。例外を投げません。とても使いやすいです。
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;
}
}
あなたは見るべきです .NETのTupleの実装. 。それらはタイムタイプセーフをコンパイルしています。
の目的は何ですか typeLock
?誰かがこれらのオブジェクトの構築を防ぐことができるようにするために?この部分はあまり意味がありません。
なぜ誰かがあなたのオブジェクトのさらなるインスタンス化を防ぎたいのですか?何らかの理由でこれが必要なものである場合、クラスを「ロック」して例外を投げる代わりに、コードパスがタイプのオブジェクトをさらに作成しないようにしてください。
静的の目的は何ですか lastTuple
最後のインスタンス化の参照に設定されています Tuple
?このような静的参照を混ぜるのは貧弱な習慣です。
率直に言って、このクラスの必要性は混乱していても、コードは非常に混乱しています。どういうわけかこれが私が職場環境でレビューしていたコードであれば、私はそれを許可しません。
このコードを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);
}
}
ジェネリックを使用してコンパイルタイムタイプチェックを提供する本当にひどい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
}
}
タイプセーフコンテナを書くことに本当に興味がある場合は、ジェネリックを調べてください。
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);
}
}
Compile Time Type Safetyにジェネリックを使用する方が良いでしょう。 Arityごとに1つのインターフェイスを定義できます。次に、個別の呼び出し可能なインターフェイスを定義して、タプルの値にアクセスできます。
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 ) ; }