implementación Java N-tupla
-
30-09-2019 - |
Pregunta
Me acaba de hacer una n-tupla de Java, que es de tipo seguro.
Estoy usando algunos métodos no convencionales para lograr la seguridad de tipos (que acabo de hacer por diversión).
Puede alguien puede dar alguna entrada en la mejora o algunos defectos posibles.
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,""),"");
}
}
Solución
Felicitaciones en el aprendizaje práctico. He aquí algunas sugerencias de "oportunidades" para la mejora:
-
puede nunca existe sólo un tipo de tupla (una vez Typelock está establecido). Esto perjudica a la reutilización y la escalabilidad de los programas que desean utilizar varios tipos de tuplas a menos que recurrir a la reutilización de corte-n-goma (BirthdayTuple, DimensionsTuple, StreetAddressTuple, ...). Considere una clase TupleFactory que acepta los tipos de destino y crea un objeto constructor tupla para generar tuplas.
-
La validez de "nulo" como un valor en una tupla no está documentado. Creo que antes se establece Typelock, se permite nula; pero después de Typelock se establece, código generará una NullPointerException - esto es inconsistente. Si no se les permite, el constructor debe cogerlo y no permitir que (independientemente de Typelock). Si se les permite, entonces el código general (constructor, iguales, código hash, etc.) necesita ser modificado para permitir por ello.
-
Las tuplas Decidir si tienen la intención de ser objetos de valor inmutable. Sobre la base de su falta de métodos setter, yo supongo que sí. Si es así, entonces tenga cuidado de "adoptar" la matriz de entrada -
lastTuple=this.arr
. A pesar de su constructor arg una var, el constructor podría ser llamada con una matriz directamente. La clase adopta el array (mantiene una referencia a él) y los valores de la matriz podría ser alterado fuera de la clase después. Me gustaría hacer una copia superficial de la matriz, sino también documentar el problema potencial con tuplas con valores que no son inmutables (que se podrían cambiar fuera de la tupla). -
Su método
equals
carece de la verificación null (if (obj == null) return false
) y la comprobación de clase (ya seaobj instanceof Tuple
othis.getClass().equals(object.getClass())
). El idioma es igual está bien documentada. -
No hay manera de ver los valores de una tupla, sino por
toString
. Esto protege los valores y la inmutabilidad general de, pero creo que limita la utilidad de la clase. -
Mientras me di cuenta es sólo un ejemplo, yo no esperaría a utilizar esta clase para algo como cumpleaños / fechas. En los dominios de soluciones con los tipos de objetos fijos, clases reales (como la fecha) son mucho mejores. Me imagino esta clase para ser útil en dominios específicos en los tuplas son objetos de primera clase.
Editar Estado pensando en esto. Esta es mi opinión sobre algo de código (en github + pruebas ):
===
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)
Otros consejos
¿Cómo es esto typesafe? Usted está lanzando excepciones de tiempo de ejecución en lugar de informar errores de tipo en tiempo de compilación.
Está intentando aridad más abstracta que es (hasta el momento) no es posible en lenguajes de tipos estáticos, sin perder typesafety.
Adición:
tuplas puede constar de elementos heterogéneos (elementos es decir, con diferentes tipos). Por lo tanto, incluso proporcionando "typesafety rutime" no es posible, 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 posterior 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);
}
}
Esta es la solución más simple y también es la mejor. Es similar a la forma en tuplas están representados en .NET. Se elude cuidadosamente borrado de Java. Está fuertemente tipado. No lanzar 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;
}
}
Usted debe mirar aplicación de .NET de Tupla . Son tiempo de compilación de tipo seguro.
¿Cuál es el propósito de typeLock
? Para permitir a alguien para evitar la construcción de más de estos objetos? Esta parte no tiene mucho sentido.
¿Por qué nunca quiere dejar alguien a evitar una mayor creación de instancias de los objetos? Si por alguna razón esto es algo que nunca necesita, en lugar de "bloqueo" una clase y lanzar excepciones, sólo asegúrese de que la ruta de código ... no crea más objetos del tipo.
¿Cuál es el propósito de la lastTuple
estática que se establece en una referencia de la última Tuple
instanciado? Es una mala práctica de mezclar referencias estáticas como este.
Francamente el código es bastante confuso, a pesar de que la necesidad de esta clase es confuso. Si de alguna manera se trataba de código que estaba revisando en un ambiente de trabajo, no lo permitiría.
vio 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);
}
}
Aquí hay una aplicación de n-tupla verdaderamente horrible que utiliza los genéricos para proporcionar controles de tipo en tiempo de compilación. El método principal (prevista demostración propósitos) demuestra 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
}
}
Si usted está realmente interesado en escribir contenedores tipo de fallos, 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);
}
}
Sería mejor utilizar genéricos para la seguridad de tipos en tiempo de compilación. Se puede definir una interfaz por aridad. A continuación, puede definir interfaces separadas que se puede 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 ) ; }