2011-06-30 11 views
9

Jeśli rozumiem to poprawnie, Integer[] jest podtypem Object[]. Można na przykład zrobićDlaczego typ argumentu var-arg "jest w przybliżeniu"?

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

Podczas zabawy z var-args zdałem sobie sprawę, że wydaje się, że kompilator „nad approixmates” typu macierzy bez wyraźnego powodu.

Poniższy program, na przykład, drukuje 123123. Czy nie miałoby to sensu/być bardziej precyzyjne, gdyby wydrukowano 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 
    } 
} 

Chyba moje pytanie można podsumować jako: Czy jakakolwiek sprzeczność/Problem powstaje, jeśli została zdefiniowana JLS przekazać T[] jako argumentu, gdzie T był kresem górnym typów wszystkich argumentów podanych?


Edit: Zdaję sobie sprawę, że w tym konkretnym przypadku mogłoby przeciążyć metodę, combine podjęcia Integer[] jak również (ideone demo). Pozostaje jednak pytanie, dlaczego ten projekt został wybrany.

+0

możliwy duplikat [Java: przeciążona metoda rozdzielczości i varargs - mylący przykład] (http://stackoverflow.com/questions/6032901/java-overloaded-method-resolution-and-varargs -confusing-example) –

+0

Widziałem to pytanie (i przegłosowałem!) Pytanie to dotyczy przeładowania, co jest zupełnie innym problemem, jak to widzę? – aioobe

+1

Uzgodnione, to inna sprawa. To pytanie dotyczy pakowania tablicy varargs, a nie rozdzielczości metody. –

Odpowiedz

6

Co do tego konkretnego pytania:

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?

Tak, ponieważ tablica nie jest tylko do odczytu; to zapisu:

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

która drukuje

[1, 2, Pow!] 

Jeśli JLS został zdefiniowany sposób Pytasz (np foo(T... args), jeśli zadzwonisz foo(a,b,c) następnie kompilator tworzy tablicę kres górny typy a, b, c), wtedy ten przypadek pozwoliłby na błąd runtime: wywołanie changeLastArgument(1,2,3) utworzyłoby tablicę typu Integer[], ale metoda changeLastArgument() spróbowałaby przypisać "Pow!" do ostatniego elementu, a otrzymasz środowisko uruchomieniowe błąd.

Deklaracja changeLastArgument() jest określenie jego rodzaju wejściowe, a zatem powinno być w stanie przyjąć argumentu wejściowego jest naprawdę Object[] a nie podtyp Object[], tak, że można go bezpiecznie modyfikować argumenty wejściowe. (Jest to podobne do PECS principle - aby List<T> był zarówno czytelny, jak i zapisywalny, nie można używać symboli wieloznacznych, takich jak List<? extends T> - który jest bezpiecznie czytelny, ale nie można go bezpiecznie zapisywać - lub List<? super T> - który jest bezpieczny zapisywalne, ale niezbyt czytelne.)

+0

Hah! Myślę, że go przybiłeś! Przekazanie "nowej liczby całkowitej [] {1, 2, 3}" powoduje zauważenie wyjątku "ArrayStoreException". Zasługujesz na miłą odznakę dla tego IMO :-) – aioobe

+0

@aioobe to nie jest argument mordercy. możesz samemu przekazać: 'Arrays.toString (changeLastArgument (new Integer [] {1,2,3})) i uzyskać wyjątek ArrayStoreException. – irreputable

+0

Jasne, ale to przynajmniej "moja wina". Byłby * znacznie gorszy *, gdyby było to domyślne zachowanie, gdy po prostu robisz 'Combine (1, 2, 3)'. – aioobe

1

Wygląda na to, że combine(1, 2, 3) przyniesie int[] zamiast Integer[]. Ponieważ tablica int[] nie jest instancją macierzy Integer[], pierwsze sprawdzenie kończy się niepowodzeniem, a ty powracasz do bloku konkat.

+1

Uhm, nie, ponieważ 'int []' nie jest podtypem 'Object []'. Nie możesz wykonać 'Object [] o = new int [] {1, 2, 3};'. Innymi słowy, nie ma sposobu na przekazanie 'int []' do metody akceptującej 'Object []'. – aioobe

1

JLS określa to zachowanie (utworzenie tablicy elementów typu, która jest typem parametru arytmetycznego, tj. Jeśli metoda vararg to foo(Bar bar, Baz baz, T...), wówczas tablica utworzona podczas wywołania metody jest typu T[]), jeśli znajdziesz prawo spotu:

Od JLS 8.4.1 (site Oracle mającym kłopoty w tej chwili, musiałem użyć Internet Archive):

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).

Od 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.

Więc podtrzymuję moją oryginalną odpowiedź (patrz poniżej).


Uważam, że odpowiedź jest w deklaracji:

public static Object combine(Object... objs) 

Kompilator pasuje do tej metody, a więc dla varargs ustalało Object[]. Nie ma powodu, aby przydzielać Integer[].


testowych trial:

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

która drukuje:

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

Wreszcie, jeśli chcą kompilator być bardziej szczegółowe, należy użyć metody rodzajowe:

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

która drukuje:

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

Tak, wiem. Sam wykonałem test 'getClass'. Moje pytanie brzmi: * dlaczego * nie kompilator/JLS przydziela zamiast tego 'Integer []'? Byłoby to bardziej precyzyjne, nie? – aioobe

+0

JLS mówi dokładnie, co mówi JLS, niezależnie od tego, czy coś innego byłoby lepsze. Chociaż istnieje pewna symetria: ze strony rozdzielczości metody varargs znajduje najbardziej specyficzną metodę; od strony konsumenta (wywołującej) varargs, znajduje * najmniejszy * określony typ (który jest typem parametru arytmetycznego zmiennej deklarowanej metody) –

+0

Moje pytanie nie dotyczy tego, jak to działa, ani co mówi JLS. Moje pytanie brzmi, jak stwierdzono od początku, * Czy pojawiłaby się jakakolwiek sprzeczność/problem, gdyby JLS zdefiniowano jako przekazanie T [] jako argumentu, gdzie T było najmniejszą górną granicą typów wszystkich argumentów? * – aioobe

0

Dobrze, że nie wysyłasz [] do łączenia funkcji Integer. Dlatego nie działa tak, jak oczekujesz.

Zastosowanie

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

aby to działało.

+0

Ah, dobry punkt. Dlaczego jednak kompilator/JLS nie zrobi tego dla mnie, jeśli podam mu argumenty "1, 2, 3"? – aioobe

3

W rezultacie, aby wydrukować 6, kompilator musiałby być na tyle sprytny, aby zdawać sobie sprawę, że wszystkie argumenty mogą być zapakowane w podobną klasę opakowania.

Sądzę, że jest to po prostu zbyt dużo wysiłku lub zbyt trudne do określenia poprawnie w niektórych bardzo rzadkich przypadkach.


Poza tym pytanie, cóż, wygląda na to, prosta zasada: tablica jest zawsze typu Object[] (jeśli typ varargs jest Object), oto niektóre kod demonstracji:

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

wyjściowa:

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

Aha, to dobra teoria, +1. Identyczna specyfikacja istnieje jednak w przypadku przeciążonych metod, gdzie wywoływana jest "najbardziej precyzyjna" metoda. – aioobe

+0

Odnośnie twojej edycji: Tak, wiem. Typ parametru określa sposób kompilacji argumentu. Moje pytanie brzmi, ponieważ 'Integer []' jest podtypem 'Object []' dlaczego kompilator nie kompiluje typu argumentu do najbardziej konkretnego typu, tj. 'Integer []' zamiast 'Object []'? – aioobe

+0

Ale dlaczego miałoby to być najbardziej precyzyjne w tym przypadku?Nie ma wymogu; musi pasować do metody, a kiedy to robi, nie ma wymogu, aby próbować być sprytnym i przydzielić Integer [] zamiast Object []. –

0

Mój najlepszy przypuszczenie to, że nie określono typ że varargs powinna ponieść.

Poniżej przedstawiono, co mam na myśli:

/** 
* @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)); 
    } 
} 

wyjściowa:

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 

To jasne, że nie przekazując "bez typu" tablicę, JVM konwertuje go do Object[], który jest przekazywany do metoda combine().

PS, nie mogłem znaleźć JLS, gdy serwer Oracle jest wyłączony.

+0

Nie rozumiem twojego zdania na temat "generics". Możesz rozwinąć temat? – aioobe

+0

@ aioobe, oops, miałem na myśli varargs. Zaktualizowałem. –

+0

Dobrze. Mimo to nie było to moje pytanie: P Moje pytanie brzmiało: dlaczego JLS został zaprojektowany, aby "przewyższać" typ i dlaczego nie jest zdefiniowany, aby wybrać najbardziej precyzyjny typ. – aioobe

Powiązane problemy