Java: разрешение времени компиляции и наиболее конкретный метод применительно к переменной арности

Может ли кто-нибудь помочь мне понять раздел 15.12.2.5 JLS re: самый конкретный метод?

(далее следует дубленый вырезать и вставить из JLS)

Кроме того, один метод члена переменной arity с именем m является более конкретным, чем другой метод члена переменной arity с тем же именем, если:

  • Один метод-член имеет n параметров, а другой - k параметров, где n> = k. Типы параметров первого метода-члена - T1,. . . , Tn-1, Tn [], типы параметров другого метода - U1,. . . , Ук-1, Ук []. Если второй метод является универсальным, пусть R1 ... Rp p1 - его параметры формального типа, пусть Bl - объявленная граница Rl, 1lp, пусть A1 ... Ap - фактические выведенные аргументы типа (§15.12.2.7) для этого вызова при начальных ограничениях Ti ‹---------------- Ui, 1ik-1, Ti ‹* Uk, kin и пусть Si = Ui [R1 = A1, ..., Rp = Ap] 1ik; в противном случае пусть Si = Ui, 1ik. Затем: для всех j от 1 до k-1, Tj ‹: Sj, и для всех j от k до n, Tj‹: Sk, и, если второй метод является универсальным, как описано выше, то Al ‹: Bl [R1 = A1, ..., Rp = Ap], 1lp.
  • Один метод-член имеет k параметров, а другой - n параметров, где n> = k. Типы параметров первого метода - U1,. . . , Uk-1, Uk [], типы параметров другого метода - T1,. . ., Тн-1, Тн []. Если второй метод является универсальным, пусть R1 ... Rp p1 - его параметры формального типа, пусть Bl - объявленная граница Rl, 1lp, пусть A1 ... Ap - фактические выведенные аргументы типа (§15.12.2.7) для этого вызова при начальных ограничениях Ui ‹* Ti, 1ik-1, Uk ‹* Ti, kin и пусть Si = Ti [R1 = A1, ..., Rp = Ap] 1in; в противном случае пусть Si = Ti, 1in. Затем: для всех j от 1 до k-1, Uj ‹: Sj, и для всех j от k до n, Uk‹: Sj, и, если второй метод является универсальным, как описано выше, то Al ‹: Bl [R1 = A1, ..., Rp = Ap], 1lp.

Игнорируя обобщенные задачи, означает ли это, что varargs важнее подтипов, или подтипов важнее, чем varargs, при принятии решения о том, является ли один метод более конкретным, чем другой? Я не могу этого понять.

Конкретный пример: какой из следующих compute() методов является «более конкретным» согласно JLS?

package com.example.test.reflect;

class JLS15Test
{
    int compute(Object o1, Object o2, Object... others) { return 1; }
    int compute(String s1, Object... others)            { return 2; }

    public static void main(String[] args) 
    {
        JLS15Test y = new JLS15Test();
        System.out.println(y.compute(y,y,y));
        System.out.println(y.compute("hi",y,y));
    }
}

Я не могу понять, что более конкретно; вывод распечатывает

1
2

Я не понимаю, как интерпретировать результаты. Когда первым аргументом была строка, компилятор выбрал метод с более конкретным подтипом. Когда первым аргументом был объект, компилятор выбирал метод с меньшим количеством необязательных переменных.


ПРИМЕЧАНИЕ. Если вы не читаете этот раздел JLS и даете ответ, зависящий от типов аргументов, вы мне не помогаете. Если вы внимательно прочитаете JLS, за исключением частей, относящихся к универсальным шаблонам, определение «более конкретного» зависит от заявленных аргументов, а не от фактических аргументов - это вступает в игру в других частях JLS (пока не могу найти).

например для методов фиксированной арности compute(String s) будет более конкретным, чем compute(Object o). Но я пытаюсь понять соответствующий раздел методов JLS re: variable arity.


person Jason S    schedule 16.05.2011    source источник
comment
@David @Jason - Насколько я знаю, вы не можете изменить тип первого параметра на String в первом методе. Поскольку это соответствует последней строке указанного раздела - we say that the method invocation is ambiguous, and a compile-time error occurs.   -  person MByD    schedule 17.05.2011
comment
Я думаю, Джон Скит сейчас спит ...   -  person Martijn Courteaux    schedule 17.05.2011
comment
ах. Я только хотел поместить конкретный пример в свой исходный пост, чтобы убрать абстрактность из формулировки JLS, и вместо этого все, что он сделал, это сосредоточил внимание на конкретном примере, а не на JLS.   -  person Jason S    schedule 17.05.2011
comment
см. отредактированный ответ, я думаю, что нашел случай, соответствующий вашему вопросу.   -  person MByD    schedule 17.05.2011


Ответы (3)


  1. int compute(String s1, Object... others) более конкретен, когда вы вызываете compute("hi",y,y), поскольку String является подклассом Object.

  2. int compute(Object o1, Object o2, Object... others) является единственным совпадением для compute(y,y,y), потому что второй метод получает String в качестве первого параметра, а JLS15Test не является подклассом String

ИЗМЕНИТЬ

Мой ответ основан на основах конкретных методов, но ваш код компилируется только из-за способности компилятора различать методы описанным выше способом.

Следующие примеры даже не будут компилироваться из-за их неоднозначности:

Дело 1:

int compute(Object o1, Object o2, Object... others) { return 1; }
int compute(Object s1, Object... others)            { return 2; }

public static void main(String[] args) 
{
    JLS15Test y = new JLS15Test();
    System.out.println(y.compute(y,y,y));
    System.out.println(y.compute("hi",y,y));
}

случай 2:

int compute(String o1, Object o2, Object... others) { return 1; }
int compute(Object s1, String... others)            { return 2; }

public static void main(String[] args) 
{
    JLS15Test y = new JLS15Test();
    System.out.println(y.compute("hi","hi","hi"));
}

Еще редактировать

Первые два раза я плохо понял ваш вопрос (и надеюсь, что ответит на этот раз :)).

Фактический случай, о котором вы говорите, будет выглядеть так:

public class Test {
    public static void main(String[] args)
    {
        Test t = new Test();
        int a = t.compute("t", new Test());
        System.out.println(a);
    }

    int compute(String s, Object... others) { return 1; }
    int compute(Object s1, Object others)   { return 2; }
}

В этом случае compute(Object s1, Object others) действительно более конкретен, чем compute(String s, Object... others) (имеет меньше параметров), поэтому результат действительно будет 2.

person MByD    schedule 16.05.2011
comment
Вы правы в отношении второго примера, но, пожалуйста, перечитайте мой отредактированный вопрос. Я конкретно спрашиваю, как varargs влияет на JLS. - person Jason S; 17.05.2011
comment
+1 и LOL для очевидного ответа, который я полностью пропустил, потому что я предполагал, что OP напишет тестовый код для передачи аргументов, удовлетворяющих обеим сигнатурам метода. - person David Harkness; 17.05.2011
comment
+1 за детали. Ваш последний случай, однако, для более конкретного наиболее применимого метода по другой причине - это не потому, что он более конкретен или потому, что у него меньше параметров, а потому, что compute (Object, Object) - это метод с фиксированной арностью, а другой - это переменная арность, и сначала рассматриваются методы фиксированной арности (JLS диктует 3 фазы, причем фаза 1 является точным совпадением, фаза 2 добавляет автобокс / распаковку, а фаза 3 добавляет varargs). - person Jason S; 17.05.2011
comment
@Jason - Надеюсь, вы поделитесь с нами реальным ответом, когда найдете его :) - person MByD; 17.05.2011

Прочитав JLS несколько раз, я, наконец, думаю, что понял этот раздел.

Они говорят, что если есть два метода переменной арности, с целью решить, какой из них «более конкретный», вы можете рассмотреть тот, у которого более короткий список аргументов, как расширенный, чтобы он имел длину равную длине более длинному. . например

int compute(Object o1, Object o2, Object... others)
int compute(String s1, Object... others)

может рассматриваться (только в целях «более конкретного») как эквивалент

int compute(Object o1, Object o2, Object... others)
int compute(String s1, Object,    Object... others)

а затем типы аргументов сравниваются один за другим, причем последний метод является более конкретным.

(более строго, первый имеет n = 3, k = 2, n> = k, с String ‹: Object [String является подтипом Object], а JLS требует непосредственного сравнения типов каждого параметра для j между 1 и k-1 [на единицу меньше более короткой длины], сравнивая тип vararg более короткой сигнатуры метода с остальными параметрами более длинного метода.)

В следующем случае:

int compute(Object o1, Object o2, String... strings)
int compute(Object o1, String... strings)

они были бы эквивалентны (только в целях «более конкретного»)

int compute(Object o1, Object o2, String... strings)
int compute(Object o1, String,    String... strings)

и последнее более конкретное.

Таким образом, переменная арность никогда не превосходит подтипирование в целях сравнения «более конкретных» методов, которые имеют переменную арность.

Однако методы фиксированной арности всегда рассматриваются в первую очередь (JLS 15.12.2.2 и 15.12.2.3), а не методы переменной арности.

person Jason S    schedule 16.05.2011

Второй вызов compute печатает 2, потому что во время компиляции известно, что литерал «hi» является String, поэтому компилятор выбирает вторую сигнатуру метода, потому что String более конкретен, чем Object.

person Dev    schedule 16.05.2011