Как использовать универсальные символы подстановки Java с методами, принимающими более одного универсального параметра? - PullRequest
2 голосов
/ 20 января 2012

Итак, у нас есть общий метод, подобный этому, который является частью инициализации внедрения зависимости:

public static <TS, TI extends TS> void registerTransient(
    Class<TS> serviceClass, Class<TI> implementationClass)
{
    //
}

В какой-то момент мы обнаружили случай, когда класс не обязательно должен присутствовать. И это класс реализации, который мы будем вводить множественно (таким образом, класс обслуживания такой же, как и класс реализации). Естественно, вы бы написали это так:

Class<?> clazz = Class.forName("com.acme.components.MyPersonalImplementation");
registerTransient(clazz, clazz);

У IDEA нет проблем с этим, но javac жалуется:

error: method registerTransient in class TestTrash cannot be applied to given types;
required: Class<TS>,Class<TI>
found: Class<CAP#1>,Class<CAP#2>
reason: inferred type does not conform to declared bound(s)
inferred: CAP#2
bound(s): CAP#1
where TS,TI are type-variables:
TS extends Object declared in method <TS,TI>registerTransient(Class<TS>,Class<TI>)
TI extends TS declared in method <TS,TI>registerTransient(Class<TS>,Class<TI>)
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?

Что дает? Метод требует, чтобы второй параметр был подклассом первого. Независимо от того, к какому классу относится ?, это один и тот же объект класса для обоих параметров, и я думал, что класс всегда можно назначить из самого себя. Это похоже на то, как если бы javac излишне придумывал второй подстановочный тип, чтобы использовать его для второго параметра, а затем сказал: «О, дорогой, у вас здесь есть два подстановочных знака, поэтому я не могу сказать, назначается ли один из другого».

Ответы [ 2 ]

2 голосов
/ 20 января 2012

Проблема в том, что Class<?> не может быть приведен к какому-либо другому типу, кроме как через явное приведение (оно фактически становится Class<#capture-... of ?> во время преобразования захвата).Таким образом, компилятор не может (статически) сопоставить границы типов этих захватов с параметризованными типами определения метода.

Попробуйте сначала привести его явно к классу, как в:

registerTransient((Class<Object>)clazz, clazz); 

Таким образом, компилятор может связать TS с Object и TI с чем-то, что расширяет объект (хотя он все равно выдаст предупреждение).

Тот факт, что компилятор IntelliJ не жалуется на это, может быть из-за какой-то оптимизации или даже из-за ошибки компилятора.Вы должны опубликовать его как таковой и дождаться ответа.

Если вы хотите проверить его с помощью немного другого подхода, следующее все равно не будет компилироваться, даже если оно "выглядит" нормально:

public class A {
    static class B {}

    static class C extends B {}

    static <T, R extends T> void method(final Class<T> t, final Class<R> r) {}

    public static final void main(String... args) {
        B b = new B();
        C c = new C();
        Class<?> cb = b.getClass();
        Class<?> cc = c.getClass();
        method(cb, cc);
    }
}

Загляните сюда .Он представляет необычайный взгляд на систему типов Java (хотя и довольно плотный).

0 голосов
/ 03 июля 2013

Проблема, с которой вы столкнулись, заключается в том, что преобразование захвата происходит индивидуально для каждого аргумента метода в соответствии с JLS 7 §6.5.6.1 Имена простых выражений :

Если имя выражения появляется в контексте, в котором оно подвергается преобразованию присваивания, преобразованию вызова метода или преобразованию приведения, то тип имени выражения является объявленным типом поля, локальной переменной или параметра после захват преобразования ( §5.1.10 ).

В вашем случае «имя выражения» - это идентификатор clazz. Как показывает вывод компилятора, он захватывается дважды, как того требует JLS.

Общая методика для решения этой проблемы заключается во введении вспомогательного метода, связывающего шаблон с переменной типа:

private static <T> void registerTransient(Class<T> serviceAndImplClass)
{
    registerTransient(serviceAndImplClass, serviceAndImplClass);
}

Вызов этого нового метода с подстановочным знаком будет работать:

Class<?> clazz = Class.forName("com.acme.components.MyPersonalImplementation");
registerTransient(clazz);

Я вижу, что вы упомянули этот обходной путь в комментарии. Это может показаться странным, но на самом деле это подход, разработанный языковыми дизайнерами.

...