2011-06-30 10 views
9

Si lo entiendo correctamente, Integer[] es un subtipo de Object[]. Por ejemplo, puede hacer¿Por qué el tipo del argumento var-arg es "aproximado"?

Object[] objs = new Integer[] { 1, 2, 3 }; 

Si bien jugar con var-args me di cuenta, que parece que el compilador "sobre approixmates" el tipo de matriz sin motivo aparente.

El siguiente programa, por ejemplo, imprime 123123. ¿No tendría sentido/sería más preciso si imprimiera 1236?

class Test { 

    public static Object combine(Object... objs) { 

     if (objs instanceof Integer[]) { 

      int sum = 0; 
      for (Integer i : (Integer[]) objs) 
       sum += i; 
      return sum; 

     } else { 

      String concat = ""; 
      for (Object o : objs) 
       concat += o; 
      return concat; 

     } 
    } 

    public static void main(String[] args) { 
     System.out.println(combine("1", "2", "3")); // prints 123 
     System.out.println(combine(1, 2, 3));  // prints 123 
    } 
} 

Creo que mi pregunta podría resumirse como: surgiría ninguna contradicción/problema si el JLS se definió para pasar T[] como argumento, donde T fue el extremo superior de los tipos de los argumentos dados?


Editar: me doy cuenta que, en este caso particular, podría sobrecargar el método de la combine tomar Integer[] así (ideone demo). Aún así, queda la pregunta de por qué se eligió este diseño.

+0

posible duplicado de [Java: resolución de método sobrecargado y varargs - ejemplo confuso] (http://stackoverflow.com/questions/6032901/java-overloaded-method-resolution-and-varargs -confusing-example) –

+0

He visto esa pregunta (¡y la he votado a favor!) Sin embargo, esa pregunta se refiere a la sobrecarga, ¿cuál es un problema completamente diferente según lo veo? – aioobe

+1

De acuerdo, es un problema diferente. Esta pregunta es sobre el paquete del compilador de la matriz varargs, no sobre la resolución del método. –

Respuesta

6

En cuanto a esta pregunta específica:

Would any contradiction/problem arise if the JLS was defined to pass T[] as argument, where T was the least upper bound of the types of all arguments given?

Sí, porque la matriz no es de sólo lectura; es modificable:

package com.example.test; 

import java.util.Arrays; 

public class Varargs3 { 
    public static Object[] changeLastArgument(Object... objs) { 
     if (objs.length > 0) 
      objs[objs.length-1] = "Pow!"; 
     return objs; 
    } 

    public static void main(String[] args) { 
     System.out.println(
      Arrays.toString(changeLastArgument(1,2,3)) 
     ); 
    } 
} 

que imprime

[1, 2, Pow!] 

Si el JLS se definió la forma en que están pidiendo (por ejemplo, para foo(T... args), si se llama a foo(a,b,c) entonces el compilador construye una matriz del extremo superior de los tipos a, b, c), entonces este caso permitiría un error de tiempo de ejecución: la invocación de changeLastArgument(1,2,3) crearía una matriz de tipo Integer[], pero el método changeLastArgument() intentaría asignar "Pow!" al último elemento y obtendría un tiempo de ejecución error.

La declaración de changeLastArgument() está especificando sus tipos de entrada, y por lo tanto debe ser capaz de asumir su argumento de entrada es realmente un Object[] y no un subtipo de Object[], para que se pueda modificar de forma segura los argumentos de entrada. (Esto es similar al PECS principle - para que un List<T> sea legible y escribible de manera segura, no puede usar ningún comodín como List<? extends T> - que sea de lectura segura pero no de escritura segura - o List<? super T> - que es seguro escribible pero no legible de manera segura.)

+0

¡Ja! ¡Creo que lo has clavado! Pasar un 'nuevo entero [] {1, 2, 3}' da como resultado una 'ArrayStoreException' como usted señala. Usted merece una insignia de respuesta agradable para esta IMO :-) – aioobe

+0

@aioobe este no es un argumento decisivo. puede pasar uno usted mismo: 'Arrays.toString (changeLastArgument (new Integer [] {1,2,3}))' y obtener ArrayStoreException. – irreputable

+0

Claro, pero al menos es "mi culpa". Sería * mucho peor * si este fuera el comportamiento predeterminado al simplemente hacer 'combine (1, 2, 3)'. – aioobe

1

Me parece que combine(1, 2, 3) dará un int[] en lugar de Integer[]. Como una matriz int[] no es una instancia de una matriz Integer[], falla la primera comprobación y usted recurre al bloque concat.

+1

Uhm, no, ya que 'int []' no es un subtipo de 'Objeto []'. No puede hacer 'Object [] o = new int [] {1, 2, 3};'. Dicho de otra manera, no hay forma de pasar un 'int []' a un método que acepta un 'Object []'. – aioobe

1

El JLS especifica este comportamiento (creación de un conjunto de elementos del tipo que es la aridad tipo de parámetro variable, es decir, si el método vararg es foo(Bar bar, Baz baz, T...) entonces la matriz creada en la invocación del método es de tipo T[]), si encuentra el lugar correcto:

de JLS 8.4.1 (Oracle sitio tiene problemas en este momento, tuve que usar el Archivo de Internet):

If the last formal parameter is a variable arity parameter of type T, it is considered to define a formal parameter of type T[]. The method is then a variable arity method. Otherwise, it is a fixed arity method. Invocations of a variable arity method may contain more actual argument expressions than formal parameters. All the actual argument expressions that do not correspond to the formal parameters preceding the variable arity parameter will be evaluated and the results stored into an array that will be passed to the method invocation (§15.12.4.2).

de JLS 15.12.4.2:

15.12.4.2 Evaluate Arguments The process of evaluating of the argument list differs, depending on whether the method being invoked is a fixed arity method or a variable arity method (§8.4.1).

If the method being invoked is a variable arity method (§8.4.1) m, it necessarily has n>0 formal parameters. The final formal parameter of m necessarily has type T[] for some T, and m is necessarily being invoked with k >= 0 actual argument expressions.

If m is being invoked with k != n actual argument expressions, or, if m is being invoked with k=n actual argument expressions and the type of the kth argument expression is not assignment compatible with T[], then the argument list (e1, ... , en-1, en, ...ek) is evaluated as if it were written as (e1, ..., en-1, new T[]{en, ..., ek}).

The argument expressions (possibly rewritten as described above) are now evaluated to yield argument values. Each argument value corresponds to exactly one of the method's n formal parameters.

The argument expressions, if any, are evaluated in order, from left to right. If the evaluation of any argument expression completes abruptly, then no part of any argument expression to its right appears to have been evaluated, and the method invocation completes abruptly for the same reason.The result of evaluating the jth argument expression is the jth argument value, for 1 <= j <= n. Evaluation then continues, using the argument values, as described below.

Por lo tanto, mantengo mi respuesta original (ver a continuación).


Creo que la respuesta está en la declaración:

public static Object combine(Object... objs) 

El compilador coincide con este método, y por lo tanto para varargs se asigna una Object[]. No hay ninguna razón para asignar un Integer[].


prueba ensayo:

package com.example.test; 
public class Varargs1 { 
    public static void varargs(Object... objs) { 
     System.out.println(objs.getClass()); 
    } 

    public static void main(String[] args) { 
     varargs("1", "2", "3"); 
     varargs(1, 2, 3); 

     Integer[] ints = {1,2,3}; 
     varargs(ints); // Eclipse yields the following warning: 
     /* 
     * The argument of type Integer[] should explicitly be 
     * cast to Object[] for the invocation of the varargs 
     * method varargs(Object...) from type Varargs1. 
     * It could alternatively be cast to Object for a 
     * varargs invocation 
     */ 
    } 
} 

que imprime:

class [Ljava.lang.Object; 
class [Ljava.lang.Object; 
class [Ljava.lang.Integer; 

Por último, si desea el compilador para ser más específicos, utilizar métodos genéricos:

package com.example.test; 

public class Varargs2 { 
    public static <T> void varargs(T... objs) { 
     System.out.println(objs.getClass()); 
    } 

    public static void main(String[] args) { 
     varargs("1", "2", "3"); 
     varargs(1, 2, 3); 
     varargs(1, "2", 3); // warning from Eclipse: 
     /* 
     * Type safety : A generic array of 
     * Object&Comparable<?>&Serializable 
     * is created for a varargs parameter 
     */ 
    } 
} 

que imprime:

class [Ljava.lang.String; 
class [Ljava.lang.Integer; 
class [Ljava.lang.Comparable; 
+0

Sí, lo sé. Hice la comprobación 'getClass' por mi cuenta. Mi pregunta es * why * ¿el compilador/JLS no asigna un 'Integer []' en su lugar? Sería más preciso, ¿no? – aioobe

+0

El JLS dice exactamente lo que dice el JLS, independientemente de si algo más sería mejor. Aunque probablemente haya una simetría aquí: desde el lado de la resolución del método de varargs, encuentra el método más específico; desde el lado consumidor (llamante) de varargs, encuentra el tipo * menos * específico (que es el tipo del parámetro arity variable del método de declaración) –

+0

Mi pregunta no es sobre cómo funciona, o qué dice el JLS. Mi pregunta es, como se dijo desde el principio, * ¿Surgiría alguna contradicción/problema si el JLS se definiera para pasar T [] como argumento, donde T era el límite superior mínimo de los tipos de todos los argumentos dados? * – aioobe

0

Así no se está enviando un número entero [] en combinar la función. Es por eso que no está funcionando como esperabas.

Uso

System.out.println(combine(new Integer[] {1, 2, 3})); 

a hacer que funcione.

+0

Ah, buen punto. Sin embargo, ¿por qué el compilador/JLS no hace exactamente esto por mí si le proporciono los argumentos '1, 2, 3'? – aioobe

3

Para imprimir 6 como resultado, el compilador tendría que ser lo suficientemente inteligente como para darse cuenta de que todos los argumentos pueden encajonarse en una clase de contenedor similar.

Supongo que esto es demasiado esfuerzo o demasiado difícil de especificar correctamente para algunos casos muy raros.


Además de la cuestión, así, como se ve, la regla es simple: la matriz es siempre de tipo Object[] (si el tipo de varargs es Object), aquí hay algo de código de demostración:

public static void main (String[] args) { 
    temp("1", "2", "3"); 
    temp(1,2,3); 
    temp(String.class, Integer.class); 
} 

public static void temp(Object... objs) { 
    System.out.println(objs.getClass()); 
} 

salida:

class [Ljava.lang.Object; 
class [Ljava.lang.Object; 
class [Ljava.lang.Object; 
+0

Ajá, esa es una buena teoría, +1. Sin embargo, ya existe una especificación idéntica para los métodos sobrecargados, donde se llama al método "más preciso". – aioobe

+0

En cuanto a su edición: Sí, lo sé. El tipo de parámetro define cómo se compila el argumento. Mi pregunta es, ya que 'Integer []' es un subtipo de 'Object []' ¿por qué el compilador no compila el tipo de argumento al tipo más específico, es decir, 'Integer []' en lugar de 'Object []'? – aioobe

+0

Pero, ¿por qué debería ser el más preciso en este caso?No hay ningún requisito; necesita coincidir con un método, y una vez que lo hace, no es necesario intentar ser inteligente y asignar entero [] en lugar de objeto []. –

0

Mi mejor conjetura es que no se ha especificado un tipo que los varargs deben incurrir.

A continuación se muestra lo que quiero decir:

/** 
* @author The Elite Gentleman. 
* 
*/ 
public class Test { 

    public static Object combine(Object... objs) { 
     System.out.println("combine()"); 
     System.out.println(objs.getClass().getName()); 

     if (objs instanceof Integer[]) { 

      int sum = 0; 
      for (Integer i : (Integer[]) objs) 
      sum += i; 
      return sum; 

     } else { 

      String concat = ""; 
      for (Object o : objs) { 
      System.out.println(o.getClass().getName()); 
      concat += o; 
      } 
      return concat; 

     } 
    } 


    public static void main(String[] args) { 
     System.out.println("1"); 
     System.out.println(combine(new String[] {"1", "2", "3"})); 
     System.out.println(combine(new Integer[] {1, 2, 3})); 

     System.out.println("2"); 
     System.out.println(combine("1", "2", "3")); 
     System.out.println(combine(1, 2, 3)); 
    } 
} 

de salida:

1 
combine() 
[Ljava.lang.String; 
java.lang.String 
java.lang.String 
java.lang.String 
123 
combine() 
[Ljava.lang.Integer; 
6 
2 
combine() 
[Ljava.lang.Object; 
java.lang.String 
java.lang.String 
java.lang.String 
123 
combine() 
[Ljava.lang.Object; 
java.lang.Integer 
java.lang.Integer 
java.lang.Integer 
123 

Está claro que, al no pasar una matriz "sin tipo", la JVM convierte en una Object[] que se pasa a el método combine().

PD, no pude encontrar el JLS ya que el servidor de Oracle no funciona.

+0

No entiendo tu oración sobre" genéricos ". ¿Cuidado para elaborar? – aioobe

+0

@aioobe, oops, quise decir varargs. He actualizado –

+0

Derecha. Aún así, esa no era mi pregunta: P Mi pregunta era por qué JLS está diseñado para "sobreestimar" el tipo, y por qué no está definido para el tipo más preciso. – aioobe

Cuestiones relacionadas