2010-07-07 13 views
9

Estoy intentando escribir una clase que tiene una variable de miembro genérica pero que no es, en sí misma, genérica. Específicamente, quiero decir que tengo una Lista de valores de "algún tipo que implementa de forma comparable a sí mismo", por lo que puedo ordenar ordenar en esa lista ... Espero que tenga sentido.Variable de instancia genérica en la clase no genérica

El resultado final de lo que estoy tratando de hacer es crear una clase de modo que pueda crear una instancia de dicha clase con una matriz de (cualquier tipo) y hacer que genere una representación de cadena para esa lista. En el código real, yo también pase en la clase de los tipos que estoy pasando en:

String s = new MyClass(Integer.class, 1,2,3).asString(); 
assertEquals("1 or 2 or 3", s); 
String s = new MyClass(String.class, "c", "b", "a").asString(); 
assertEquals("\"a\" or \"b\" or \"c\"", s); 

principio yo ni siquiera quería pasar en la clase, sólo quería pasar en los valores y tienen el código examina la matriz resultante para elegir la clase de los valores ... pero eso también me daba problemas.

El siguiente es el código que tengo, pero no puedo dar con el mojo correcto para poner para el tipo de variable.

public class MyClass { 
    // This doesn't work as T isn't defined 
    final List<T extends Comparable<? super T>> values; 

    public <T extends Comparable<? super T>> MyClass (T... values) { 
     this.values = new ArrayList<T>(); 
     for(T item : values) { 
      this.values.add(item); 
     } 
    } 

    public <T extends Comparable<? super T>> List<T> getSortedLst() { 
     Collections.sort(this.values); 
     return this.values; 
    } 
} 

error en la línea de declaración de variables:

Syntax error on token "extends", , expected 

sería muy apreciado Cualquier ayuda.

Edit: código actualizado para usar List en lugar de array, porque no estoy seguro de que se pueda hacer con matrices.

@ Marcos: De todo lo que he leído, lo que realmente quiero decir "T es un tipo que es comparable a sí mismo", no sólo "T es un tipo que es comparable". Dicho esto, el siguiente código no funciona bien:

public class MyClass { 
    // This doesn't work 
    final List<? extends Comparable> values; 

    public <T extends Comparable> MyClass (T... values) { 
     this.values = new ArrayList<T>(); 
     for(T item : values) { 
      this.values.add(item); 
     } 
    } 

    public <T extends Comparable> List<T> getSortedLst() { 
     Collections.sort(this.values); 
     return this.values; 
    } 
} 

error en la línea add:

The method add(capture#2-of ? extends Comparable) in the type List<capture#2-of ? extends Comparable> is not applicable for the arguments (T) 

error en la línea para ordenar:

Type mismatch: cannot convert from List<capture#4-of ? extends Comparable> to List<T> 

Conclusión:

De lo que se trata, parece, es que Java no puede manejar wh en lo que quiero hacer El problema se debe a que lo que estoy tratando de decir es:

Quiero una lista de elementos que son comparables contra sí mismos, y yo crear toda la lista a la vez de los datos pasados ​​en en la creación.

Sin embargo, Java ve que tengo esa lista y no puedo asegurar que toda la información para mi situación esté disponible en tiempo de compilación, ya que podría intentar agregar cosas a la lista más tarde y, por tipo borrado, no puede garantizar esa seguridad. No es posible comunicar a Java las condiciones involucradas en mi situación sin aplicar el tipo genérico a la clase.

+3

¿Por qué no es suficiente tener una Lista de cosas que implementen Comparable? –

+0

No es que resuelva el problema, pero si algo es comparable no significa que sea comparable. La simple implementación de Comparable no dice "es comparable contra sí misma". – RHSeeger

+0

Puede hacerlo con métodos estáticos. Ver mi segunda respuesta. – TofuBeer

Respuesta

7

Creo que la respuesta simple es que no se puede hacer eso. Si el tipo de uno de los atributos de una clase depende de un parámetro de tipo, ese parámetro debe declararse en el nivel de clase.Y no creo que "tenga sentido" de ninguna otra manera.

Si T en su ejemplo no es un parámetro de tipo de la clase, ¿qué es? No puede ser el parámetro de tipo del método, porque ese tipo está determinado por cómo se llama el método. (Si se llama al método en diferentes contextos estáticos con diferentes tipos inferidos para T, ¿cuál es el tipo nocional de T en el contexto de la declaración de atributo?)

Entonces, devuelva esto a lo que está tratando de hacer aquí , una instancia de MyClass contendrá elementos de algún tipo y desea poder insertar y eliminar elementos de una manera estática segura. Pero al mismo tiempo, no quiere poder decir qué es ese tipo. Entonces, ¿cómo se supone que el compilador estáticamente distingue entre una instancia MyClass que contiene (digamos) Integer objetos y una que contiene String objetos?

Ni siquiera creo que pueda implementar esto con comprobaciones de tipo dinámicas explícitas. (Creo que ese tipo de borrado significa que la implementación del método getSortedList() no puede averiguar qué tipo real está obligado a su tipo de retorno.)

No. La verdadera solución es hacer MyClass una clase genérica que declara el parámetro de tipo T ; p.ej.

public class MyClass <T extends Comparable<T>> { 

y retire la declaración del parámetro de tipo de nivel de método T de los dos métodos.

+0

Acepto que T no tiene ningún contexto en ese fragmento de código, pero ese es el problema. Todo lo que quiero hacer es tener "una lista de un tipo que pueda compararse contra sí mismo, para que pueda ordenarlo". Es decir, aparentemente, más difícil de lo que parece en una clase que no es genérica. – RHSeeger

+0

Es solo difícil (realmente imposible) porque estás tratando de evitar que la clase sea genérica. –

+0

Eso es todo, sin embargo. Conceptualmente, la clase no es genérica. Claro, puedo hacer que la clase sea genérica para resolver el problema, pero esperaba que hubiera una forma de evitarlo porque es un olor a código. Posiblemente uno requerido por el lenguaje, pero un código huele no obstante. – RHSeeger

1

considerarlo como esto (lo que voy a decir no es la realidad, pero que ilustra por qué tiene que hacer lo que tiene que hacer.):

class Foo<T> 
{ 
    private T value; 

    T getValue() { return value; } 
    void setValue(T val) {value = val; } 
} 

// some code that uses the above class 

Foo<Integer> iFoo = new Foo<Integer>(); 
Foo<String> sFoo = new Foo<String>(); 
iFoo.setValue(5); 
sFoo.setValue("Hello"); 

Cuando esto sucede, el compilador (no ! REALMENTE hacer lo que estoy a punto de decir) genera el siguiente código:

class IntegerFoo 
{ 
    private Integer value; 

    Integer getValue() { return value; } 
    void setValue(Integer val) {value = val; } 
} 

class StringFoo 
{ 
    private String value; 

    String getValue() { return value; } 
    void setValue(String val) {value = val; } 
} 

// some code that uses the above class 

IntegerFoo iFoo = new IntegerFoo(); 
StringFoo< sFoo = new StringFoo(); 
iFoo.setValue(5); 
sFoo.setValue("Hello"); 

Si fueron capaces de tener las variables de instancia/métodos parametrizar sin parametrización de la clase lo anterior (que no es la realidad) wouldn' t trabajo.

Lo que está tratando de hacer debe ser posible con métodos estáticos, pero no creo que eso sea lo que desea.

¿Puede explicar por qué quiere hacer el código que está tratando de hacer? Quizás podamos encontrar una forma mejor de hacer lo que quiere hacer que funcione dentro del lenguaje.

+0

Agregué un poco más de detalles sobre lo que estoy tratando de hacer. A falta de eso, tengo una variable miembro que es una lista que quiero ordenar, pero puede ser una lista de cualquier tipo (ordenable). La clase en sí misma no es una clase "de ese tipo", per se ... simplemente esa variable es. – RHSeeger

+0

Algo así como la nueva MyClass (Integer.class, .........); no es una práctica poco común. – TofuBeer

1

Creo que lo siguiente logrará lo que quiere (mecanografía más fuerte de Comparable). Esto evitará que las personas agreguen objetos Comparables que no sean de su interfaz a la lista y permitan múltiples implementaciones.

public class test<T extends ComparableType> { 


final List<T> values = new ArrayList<T>(); 
    public test (T... values) { 
     for(T item : values) { 
      this.values.add(item); 
     } 
    } 

    public List<T> getSortedLst() { 
     Collections.sort(this.values); 
     return Collections.unmodifiableList(this.values); 
    } 
} 

public interface ComparableType extends Comparable<ComparableType> {} 

public class ConcreteComparableA implements ComparableType { 
    @Override 
    public int compareTo(ComparableType o) { 
    return 0; 
    } 
} 

public class ConcreteComparableB implements ComparableType { 
    @Override 
    public int compareTo(ComparableType o) { 
    return 0; 
    } 
} 

edición:

Sé que esto puede ser obvio; pero si usted no desea la clase de ser genérico esta solución también trabajará con:

public class test { 
    final List<ComparableType> values = new ArrayList<ComparableType>(); 

    public test (ComparableType... values) { 
     for(ComparableType item : values) { 
      this.values.add(item); 
     } 
    } 

    public List<ComparableType> getSortedLst() { 
     Collections.sort(this.values); 
     return Collections.unmodifiableList(this.values); 
    } 
} 
+0

Todavía terminas con una clase genérica allí, además de que eliminas la posibilidad de crear una instancia con clases de sistema como String. – RHSeeger

+0

Editado para indicar que funciona fácilmente sin que la clase primaria sea Genérica. Creo que el objetivo es que solo se puedan usar los Comparables aprobados (es decir, que la cadena no se puede utilizar por diseño). – Syntax

2

Hay un montón de advertencias sin marcar en esto, pero en principio no es necesario para mantener el List como algo más que algo que contenga usted cosas saber son Comparable.Usted aplica las reglas que necesita en el constructor, y todo lo demás debería estar bien. ¿Qué tal algo como esto:

public class MyClass { 

    final private List<Comparable> values; 

    public <T extends Comparable<? super T>>MyClass(T... values){ 
     this.values = new ArrayList<Comparable>(); 
     for(T item : values) { 
      this.values.add(item); 
     } 
    } 

    public <T extends Comparable<? super T>> List<T> getSortedLst() { 
     Collections.sort(this.values); 
     return (List<T>)this.values; 
    } 

} 

Una prueba rápida usando la siguiente muestra que para las clases que implementan Comparable (como Número entero y Cadena) MyClass se comporta como se esperaba, pero lanzará un error de compilación para las clases que no implementan Comparable :

class Junk { } 

    public static void main(String[] args){ 
     MyClass s = new MyClass(1,2,3); 
     System.out.println(s.getSortedLst()); 

     MyClass a = new MyClass("c", "a", "b"); 
     System.out.println(a.getSortedLst()); 

     MyClass c = new MyClass(new Junk()); 
    } 
+0

Esto viene bastante cerca de lo que estoy buscando.Dicho esto, hay otra pieza del rompecabezas (algo relacionado pero que lo hace más complejo de lo que quiero manejar en la misión) que me está mordiendo cuando voy por este camino. Aunque aprecio la respuesta. – RHSeeger

+0

@RHSeeger, ya que esto básicamente responde a su pregunta agradecería un poco más de información sobre por qué no es suficiente ... –

+0

el principal problema fue que realmente quería variables miembro que dijeran "algún tipo que implemente de forma comparable a sí mismo", en lugar que simplemente "algún tipo que implementa código comparable", tanto para el código de auto documentación como para otras piezas de código que desean tal valor como entrada. Habiendo dicho eso, también odio ver advertencias sin control, una de mis preocupaciones personales es más con el pobre diseño de Java que con cualquier otra cosa. – RHSeeger

-1
public class MyClass<T extends Comparable<? super T>> { 
    // This doesn't work as T isn't defined 
    final List<T> values; 

    public MyClass (T... values) { 
     this.values = new ArrayList<T>(Arrays.asList(values)); 
    } 

    public List<T> getSortedLst() { 
     Collections.sort(this.values); 
     return this.values; 
    } 
} 
1

lo haría de esta manera (lo hice como una lista o como una matriz), a menos que realmente necesita la variable de instancia/métodos:

import java.lang.reflect.Array; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.Collections; 
import java.util.List; 


public class MyClass 
{ 
    public static <T extends Comparable<T>> List<T> asSortedList(final T ... vals) 
    { 
     final List<T> temp; 

     temp = new ArrayList<T>(vals.length); 
     temp.addAll(Arrays.asList(vals)); 
     Collections.sort(temp); 

     return (Collections.unmodifiableList(temp)); 
    } 

    public static <T extends Comparable<T>> T[] asSortedArray(final Class<?> clazz, 
                   final T ... vals) 
    { 
     final T[] temp; 

     temp = (T[])Array.newInstance(clazz, 
           vals.length); 
     System.arraycopy(vals, 
         0, 
         temp, 
         0, 
         vals.length); 
     Arrays.sort(temp); 

     return (temp); 
    } 

    public static void main(final String[] argv) 
    { 
     final List<String> list; 
     final String[]  array; 

     list = MyClass2.asSortedList("c", "a", "b"); 
     System.out.println(list); 

     array = MyClass2.asSortedArray(String.class, "z", "y", "x"); 
     System.out.println(Arrays.deepToString(array)); 
    } 
} 
0

la restricción de tipo que desea en la variable no se puede expresar directamente. puede introducir un nuevo tipo para resolver el problema.

static class MyList<T extends Comparable<? super T>> extends ArrayList<T>{} 

final MyList<?> values; 

Sin embargo, no tiene sentido ser extremadamente seguro en una pieza privada de código. Generic está ahí para ayudarlo a aclarar sus tipos, no para ofuscarlos.

Cuestiones relacionadas