Mise en œuvre de Java N-Tuple
je viens de faire un Java n-tuple qui est sans danger.
J'utilise des méthodes non conventionnelles pour atteindre la sécurité de type (je l'ai juste fait pour le plaisir).
est-ce que quelqu'un peut donner quelques idées sur l'amélioration ou certains défauts possibles.
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 réponses
Félicitations sur l'apprentissage par la pratique. Voici des suggestions de "possibilités" d'amélioration:
-
un seul type de Tuple peut jamais exister (une fois que la touche est définie). Cela nuit à la réutilisabilité et à l'évolutivité dans les programmes qui veulent utiliser plusieurs types de Tuples, à moins que vous ne recouriez à la réutilisation de cut-n-paste (BirthdayTuple, Dimensionsuple, Streetadresstuple,...). Considérons une classe TupleFactory qui accepte les types de cibles et crée un constructeur de tuples objet pour générer des n-uplets.
-
la validité de "null" comme valeur dans un Tuple n'est pas documentée. Je pense qu'avant que Typelock soit défini, null est autorisé; mais après que Typelock soit défini, le code générera une NullPointerException - ceci est incohérent. S'ils ne sont pas autorisés, le constructeur devrait les attraper et les désactiver (sans tenir compte de la typographie). S'ils sont autorisés, alors le code dans son ensemble (constructor, equals, hashcode, etc) doit être modifié pour permettre il.
-
décider si les Tuples sont destinés à être des objets de valeur immuables. Basé sur son manque de méthodes de définition, je suppose. Si c'est le cas, faites attention à "adopter" le tableau entrant -
lastTuple=this.arr
. Même si c'est un constructeur arg var, le constructeur peut être appelé directement avec un tableau. La classe adopte le tableau (garde une référence à lui) et les valeurs dans le tableau peuvent être modifiées en dehors de la classe après. Je ferais une copie du tableau, mais aussi documenter le problème potentiel avec les Tuples avec des valeurs non immuables (qui pourraient être changées en dehors du Tuple). -
votre méthode
equals
est dépourvue du contrôle nul (if (obj == null) return false
) et du contrôle de classe (soitobj instanceof Tuple
outhis.getClass().equals(object.getClass())
). L'idiome des égaux est bien documenté. -
il n'y a aucun moyen de voir les valeurs d'un Tuple sauf par
toString
. Cela protège l' les valeurs et l'immutabilité globale de, mais je pense qu'il limite l'utilité de la classe. -
bien que je réalise que ce n'est qu'un exemple, Je ne m'attendrais pas à utiliser cette classe pour quelque chose comme les anniversaires/dates. Dans les domaines de solution avec des types d'objets fixes, les classes réelles (comme la Date) sont tellement mieux. J'imagine que cette classe pour être utile dans des domaines spécifiques où les tuples sont des objets de première classe.
Modifier Été de penser à ce sujet. Voici mon point de vue sur un code (sur 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)
Comment est ce typesafe? Vous lancez des exceptions d'exécution au lieu de signaler des erreurs de type au moment de la compilation.
Vous essayez de résumé sur arité qui est (encore) possible dans les langages statiquement typés, sans perdre typesafety.
Addendum:
Les Tuples peuvent être constitués d'éléments hétérogènes (c'est-à-dire d'éléments de types différents). Donc même "rutime typesafety" n'est pas possible, pour cette classe Tuple
. Les Clients de la classe sont responsables pour le jette.
C'est le meilleur que vous pouvez faire en Java : ( Edit: Voir Brent post pour une meilleure mise en œuvre de Tuple
. (Il ne nécessite pas de typographie du côté du client.))
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);
}
}
C'est la solution la plus simple et c'est aussi le meilleur. C'est similaire à la façon dont les Tuples sont représentés .NET. Il évite soigneusement l'effacement java. Il est fortement tapé. Il ne jette pas des exceptions. Il est très facile à utiliser.
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;
}
}
vous devriez regarder . l'implémentation de Tuple . Ils sont compilés par type de temps.
Quel est le but de typeLock
? Pour permettre à quelqu'un d'empêcher la construction d'autres de ces objets? Cette partie n'a pas beaucoup de sens.
pourquoi voudriez-vous jamais laisser quelqu'un empêcher une autre instanciation de vos objets? Si, pour une raison quelconque, c'est quelque chose dont vous avez besoin, au lieu de "verrouiller" une classe et de lancer des exceptions, assurez-vous simplement du chemin de code ... ne crée pas plus d'objets du type.
Quel est le but de le statique lastTuple
qui est une référence de la dernière instancié Tuple
? C'est une mauvaise pratique de mélanger les références statiques comme ça.
franchement, le code est assez déroutant, même si la nécessité de cette classe est déroutante. Si, d'une façon ou d'une autre, il s'agissait d'un code que j'examinais dans un milieu de travail, je ne le permettrais pas.
vu ce code dans wave project
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);
}
}
Voici une implémentation n-tuple vraiment horrible qui utilise des génériques pour fournir des vérifications de type de compilation. La méthode principale (fournie à des fins de démonstration) montre à quel point cela serait horrible à utiliser:
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
}
}
si vous êtes vraiment intéressé par l'écriture de type-conteneurs sûrs, regarder dans génériques:
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);
}
}
il serait préférable d'utiliser des génériques pour la sécurité de type de compilation. Vous pouvez définir une possibilité d'interface. Ensuite, vous pouvez définir des interfaces Callables séparées pour accéder aux valeurs du tuple.
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 ) ; }