Ошибка с varargs и перегрузка?

Кажется, что ошибка в реализации Java varargs. Java не может различать соответствующий тип, когда метод перегружен различными типами параметров vararg.

Это дает мне ошибку The method ... is ambiguous for the type ...

Рассмотрим следующий код:

public class Test
{
    public static void main(String[] args) throws Throwable
    {
        doit(new int[]{1, 2}); // <- no problem
        doit(new double[]{1.2, 2.2}); // <- no problem
        doit(1.2f, 2.2f); // <- no problem
        doit(1.2d, 2.2d); // <- no problem
        doit(1, 2); // <- The method doit(double[]) is ambiguous for the type Test
    }

    public static void doit(double... ds)
    {
        System.out.println("doubles");
    }

    public static void doit(int... is)
    {
        System.out.println("ints");
    }
}

docs говорят: "Вообще говоря, вы не должны перегружать метод varargs, или программистам будет сложно понять которая вызывает перегрузку".

однако они не упоминают эту ошибку, и это не программисты, которым это сложно, это компилятор.

мысли?

EDIT - Компилятор: Sun jdk 1.6.0 u18

Ответ 1

Об этом обсуждается на форумах Sun.

Нет реального разрешения там, просто отставка.

Varargs (и автоматическое боксирование, которое также приводит к сложному поведению, особенно в сочетании с varargs), были заперты позже в жизни Java, и это одна из областей, где это видно. Так что это скорее ошибка в спецификации, чем в компиляторе.

По крайней мере, это создает хорошие (?) вопросы трюков SCJP.

Ответ 2

Проблема заключается в том, что она неоднозначна.

doIt(1, 2);

может быть вызовом doIt(int ...) или doIt(double ...). В последнем случае целые литералы будут продвигаться до значений double.

Я уверен, что спецификация Java говорит, что это неоднозначная конструкция, а компилятор просто соблюдает правила, установленные спецификацией. (Я должен был бы исследовать это дальше, чтобы быть уверенным.)

EDIT - соответствующая часть JLS - " 15.12.2.5 Выбор наиболее конкретного метода", но у меня болит голова.

Я думаю, что рассуждение состояло бы в том, что void doIt(int[]) не является более конкретным (или наоборот), чем void doIt(double[]), потому что int[] не является подтипом double[] (и наоборот). Поскольку две перегрузки одинаково специфичны, вызов неоднозначен.

В отличие от этого, void doItAgain(int) более специфичен, чем void doItAgain(double), потому что int является подтипом double в соответствии с JLS. Следовательно, вызов doItAgain(42) не является двусмысленным. Забастовкa >

EDIT 2 - @finnw прав, это ошибка. Рассмотрим эту часть 15.12.2.5 (отредактировано для удаления неприменимых случаев):

Один элемент члена переменной arty с именем m более специфичен, чем другой метод члена переменной arty с тем же именем, если:

Один метод-член имеет n параметров, а другой имеет k параметров, где n ≥ k. Типы параметров метода первого элемента - T1,.,, Tn-1, Tn [], типы параметров другого метода - U1,.,, Uk-1, Uk []. Пусть Si = Ui, 1 <= я <= k. Тогда:

  • для всех j от 1 до k-1, Tj <: Sj и,
  • для всех j от k до n, Tj <: Sk

Примените это к случаю, когда n = k = 1, и мы видим, что doIt(int[]) более специфично, чем doIt(double[]).


Фактически для этого есть отчет отчет об ошибке, и Sun признает, что это действительно ошибка, хотя они имеют приоритет это "очень низкий" . Ошибка теперь отмечена как Исправлена ​​в Java 7 (b123).

Ответ 3

Интересно. К счастью, есть несколько способов избежать этой проблемы:

Вместо меток-сигнатур вы можете использовать типы-оболочки:

   public static void doit(Double... ds) {
       for(Double currD : ds) {
          System.out.println(currD);
       }
    }

    public static void doit(Integer... is) {
       for(Integer currI : is) {
          System.out.println(currI);
       }
    }

Или вы можете использовать generics:

   public static <T> void doit(T... ts) {
      for(T currT : ts) {
         System.out.println(currT);
      }
   }