2010-09-04 14 views
21

Acabo de hacer una n-tupla de Java que es segura.
Estoy usando algunos métodos poco convencionales para lograr la seguridad de tipo (solo lo hice por diversión).Implementación de Java N-Tuple

¿Alguien puede dar algo de información sobre la mejora o algunos posibles defectos.

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

¿Cómo se puede incluso recuperar los datos desde el par de valores? –

+3

Las tuplas pueden tener elementos con tipos heterogéneos. Ese 'TypeLock' y todas esas cosas' getClass' no tienen sentido. – missingfaktor

+2

Estoy votando para cerrar esta pregunta como fuera de tema porque la pregunta es sobre la revisión del código de trabajo, código utilizable; no hay una declaración clara del problema y no hay una solución clara posible. – vaxquis

Respuesta

45

Felicitaciones por aprender haciendo. Aquí hay sugerencias de "oportunidades" de mejora:

  1. Solo existe un tipo de Tuple (una vez que se establece Typelock). Esto perjudica la reutilización y la escalabilidad en los programas que desean utilizar varios tipos de tuplas a menos que recurra a la reutilización de cortar y pegar (BirthdayTuple, DimensionsTuple, StreetAddressTuple, ...). Considere una clase TupleFactory que acepte los tipos de destino y cree un objeto constructor de tuplas para generar tuplas.

  2. La validez de "nulo" como valor en un Tuple no está documentada. Creo que antes de que Typelock esté configurado, se permite null; pero después de establecer Typelock, el código generará una NullPointerException; esto es inconsistente. Si no están permitidos, el constructor debería atraparlo y no permitirlo (independientemente del Typelock). Si están permitidos, entonces el código general (constructor, equal, hashcode, etc.) necesita modificaciones para permitirlo.

  3. Decida si los Tuples están destinados a ser objetos de valor inmutables. Basado en su falta de métodos setter, supongo que sí. Si es así, tenga cuidado de "adoptar" la matriz entrante - lastTuple=this.arr. Aunque es un constructor var arg, el constructor podría ser llamado directamente con una matriz. La clase adopta la matriz (mantiene una referencia) y los valores en la matriz podrían alterarse fuera de la clase. Me gustaría hacer una copia superficial de la matriz, pero también documentar el posible problema con Tuples con valores no inmutables (que podrían cambiarse fuera de la Tupla).

  4. Su método equals carece de la verificación null (if (obj == null) return false) y la comprobación de clase (ya sea obj instanceof Tuple o this.getClass().equals(object.getClass())). La expresión igual está bien documentada.

  5. No hay forma de ver los valores de una Tupla excepto a través de toString. Esto protege los valores y la inmutabilidad general de, pero creo que limita la utilidad de la clase.

  6. Aunque me doy cuenta de que es solo un ejemplo, no esperaría usar esta clase para algo como cumpleaños/fechas. En los dominios de solución con tipos de objetos fijos, las clases reales (como Date) son mucho mejores. Me imagino que esta clase es útil en dominios específicos donde las tuplas son objetos de primera clase.

Edición estado pensando en esto. Esta es mi opinión sobre algo de código (en 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

Esta implementación es realmente genial ... Hubiera dado más de un voto por esto si se me permitiera. – Emil

+1

@ Brent: Muy buena implementación, libera al cliente de la molestia de agregar typecasts a su código. +1 de mi parte :-) – missingfaktor

+1

@Bert: Ayer vi que el código no podía ejecutarse. Hoy cuando pegué el código para eclipsar. Muestra un error para TupleTypeImpl para la función getNthType. Luego hice la firma del método en la interfaz igual que en la función junto con un tipo emitido a la devolución y funciona. ¿Es por mi versión jdk? Estoy usando 1.6. – Emil

10

¿Cómo es esto tipo seguro? Está lanzando excepciones de tiempo de ejecución en lugar de informar errores de tipo en tiempo de compilación.

Está tratando de abstraer sobre arity que es (hasta el momento) imposible en lenguajes tipados estáticamente, sin perder typesafety.

Adición:

tuplas puede constar de elementos heterogéneos (elementos es decir, con diferentes tipos). Por lo tanto, no es posible proporcionar incluso "rutware typesafety", para esta clase Tuple. Los clientes de la clase son responsables de hacer los moldes apropiados.

Este es el mejor que puede hacer en Java: (Editar:.. Ver Brent's post para una mejor aplicación de Tuple (No requiere typecasts en el lado del cliente))

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

ok, voy a editar la pregunta y decir seguridad en tiempo de ejecución. ¿Sería correcto? – Emil

+1

Bueno, la seguridad del tipo no tiene que suceder en tiempo de compilación. Pero en un lenguaje estático, debería esperarse la verificación de tipos en tiempo de compilación ... bueno, eso lo elude. Pero el segundo párrafo es correcto. – delnan

+1

@Emil: Runtime typesafety ?! Suena como un mal oxímoron para mí. – missingfaktor

1

¿Cuál es el propósito de typeLock? ¿Permitir que alguien evite construir más de estos objetos? Esta parte no tiene mucho sentido.

¿Por qué querrías dejar que alguien evite la creación de instancias adicionales de tus objetos? Si por alguna razón esto es algo que siempre necesitas, en lugar de "bloquear" una clase y lanzar excepciones, solo asegúrate de que la ruta del código ... no cree más objetos del tipo.

¿Cuál es el propósito de la estática lastTuple que se establece en una referencia de la última instancia de Tuple? No es una buena práctica mezclar referencias estáticas como esta.

Francamente, el código es bastante confuso, aunque la necesidad de esta clase es confusa. Si de alguna manera este fuera el código que estaba revisando en un entorno de trabajo, no lo permitiría.

+0

No, es para evitar la creación de instancias con diferentes tipos. Solo el tipo de la última instancia está bloqueado. Lo tiene.Una vez que haya terminado con ese tipo, puede reiniciar el bloqueo de tipo y agregar una tupla con el nuevo tipo. – Emil

+0

Parece que solo puede tener una forma de tupla en una sola JVM, y la primera tupla que se construirá establece esa forma. Este código es inútil y tonto. –

+1

@Emil, creo que está confundido acerca de "seguridad tipo". Este bloqueo parece muy extraño, no puedo pensar en ninguna razón por la que alguien quiera tomar una clase de contenedor (destinada a contener cualquier tipo) y luego restringir * cualquier * más instancias de la clase para que se creen con diferentes parámetros de tipo para cualquier período de tiempo. Por qué querrías hacer esto? –

3

Debería mirar .NET's Tuple's implementation. Son compilables en tiempo seguro.

+1

Supongo que no es posible en Java debido al borrado de tipo en el tiempo de ejecución. –

+2

@ abhin4v: Aunque todos los tipos de tupla en .NET comparten el mismo nombre (es decir, 'Tuple'), todos ellos todavía se definen como tipos separados (como en Scala). El único problema que el borrado plantea es que no le permite tener clases con el mismo nombre pero diferente número de parámetros de tipo (los tipos Reified en .NET le permiten hacerlo). – missingfaktor

+0

He imitado la implementación de la tupla .NET en Java, aquí: http://intrepidis.blogspot.co.uk/2013/07/a-tuple-implementation-in-java.html –

0

Si usted está realmente interesado en recipientes con seguridad de tipos de escritura, mira en los genéricos:

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

Tuplas puede comprender elementos con diferentes tipos. La noción de OP de tuplas es incorrecta. – missingfaktor

+0

@missingfaktor: Tuples _puede tener diferentes tipos, ya que no se especifica explícitamente ... Pero se implementan comúnmente con seguridad tipo para un uso más fácil y seguro. Personalmente, estoy en el campamento para hacer que mecanografía seguro, por lo que el consumidor tiene que elegir explícitamente un tipo más genérico como Object si quieren mezclar tipos. –

+0

@JonAdams, ¿estás seguro de que entiendes las tuplas? Básicamente son una estructura compuesta de longitud fija de tipos heterogéneos. [Aquí tienes] (http://learnyouahaskell.com/starting-out). – missingfaktor

0

Sería mejor utilizar genéricos para la seguridad del tipo de tiempo de compilación. Puede definir una interfaz por aridad. Luego puede definir interfaces diferentes que se pueden llamar para acceder a los valores de la tupla.

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

vieron este código en proyectos de onda

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

Ésta es la solución más simple y también es la mejor. Es similar a cómo se representan las tuplas en .NET. Evita cuidadosamente el borrado de java. Está fuertemente tipado. No arroja excepciones. Es muy fácil de usar.

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; 
    } 
} 
+0

La única limitación es que debe crear más tipos a medida que aumenta su valor N. Esta es una limitación del lenguaje. Las tuplas deben incorporarse a un idioma para sentirse más natural. –

+0

De todos modos, si comienza a necesitar más de varios elementos en una Tuple, probablemente debería hacer un tipo concreto específicamente para la implementación, o simplemente refactorizar su código. –

+0

Sí, esta es, de hecho, la mejor manera de hacer tuplas, porque si necesita más elementos, debe ir con una lista o matriz. –

1

He aquí una implementación de n-tuple realmente horrible que usa genéricos para proporcionar comprobaciones de tipo de tiempo de compilación. El método principal (proporcionado con fines de demostración) muestra lo terrible que esto sería utilizar:

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