Связанная ошибка несоответствия в аргументе универсального типа для универсального c класса, расширяющего другой общий c класс - PullRequest
2 голосов
/ 14 июля 2020

У меня проблема с дженериками и наследованием в java.

Я хотел бы создать экземпляр класса ObjectManager из метода createManager, который может создать экземпляр из заданного типа. У этого ObjectManager есть несколько служебных методов (например, getDescription).

Этот метод позволяет мне создать экземпляр ObjectManager из типа, а затем получить, например, его описание (ha sh здесь).

Я также хотел бы специализировать «ObjectManager» для «FooManager», если тип ввода можно присвоить типу «Foo», чтобы я мог переопределить метод «getDescription». Поэтому я проверяю тип ввода в методе createManager, чтобы узнать, создаю ли я «ObjectManager» или «FooManager».

Код работает, но я не знаю общего c тип для вывода, соответствующий «новому FooManager (тип)». На мой взгляд, и для Eclipse Quick fix тип должен быть , но это приводит к ошибке:

Связанное несоответствие: Тип T не является допустимой заменой ограниченного параметра типа FooManager

Мой код:

import java.lang.reflect.InvocationTargetException;
import java.util.Objects;

public class GenericBug {
    private GenericBug() {}
    
    public static void main(String[] args) {
        ObjectManager<Foo> objectManager1 = createManager(Foo.class);
        System.out.println(objectManager1.getDescription());
        
        ObjectManager<Object> objectManager2 = createManager(Object.class);
        System.out.println(objectManager2.getDescription());
    }
    
    private static <T> ObjectManager<T> createManager(Class<T> type) {
        if(Foo.class.isAssignableFrom(type))
            return new FooManager<T>(type);   //Error: Bound mismatch
        return new ObjectManager<T>(type);
    }
}

class ObjectManager<T>{
    public T val;
    public ObjectManager(Class<T> type) {
        try {
            val = type.getConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }
    
    public String getDescription() {
        return "Hash: " + Objects.hash(val);
    }
}

class FooManager<T extends Foo> extends ObjectManager<T>{

    public FooManager(Class<T> type) {
        super(type);
    }
    
    @Override
    public String getDescription() {
        return super.getDescription() + " value: " + val.getVal();
    }
}

class Foo{
    private double val = Math.random();
    public Foo() {}

    public double getVal() {
        return val;
    }
}

Итак, мой вопрос: почему эта ошибка? как исправить это без предупреждения? и, в качестве бонуса, почему Eclipse предоставляет решение, которое не компилируется? это ошибка Eclipse?

Заранее благодарим за ответ.

Ответы [ 3 ]

1 голос
/ 15 июля 2020

Эта ошибка возникает из-за отсутствия гарантии во время компиляции, что T является экземпляром Foo. У вас есть только гарантия времени выполнения, что Foo назначается из T, что компилятор не заботится. Вы можете буквально сделать эту компиляцию без предупреждений, подавив их:

@SuppressWarnings({ "unchecked", "rawtypes" })
private static <T> ObjectManager<T> createFooManager(Class<T> type) {
    if(Foo.class.isAssignableFrom(type))
        return new FooManager(type);   //Error: Bound mismatch
    return new ObjectManager<T>(type);
}

Вероятно, это не тот ответ, который вы искали, но я не думаю, что вы можете избавиться от предупреждений, не подавляя их. Из неизбежности этих предупреждений я бы сделал вывод, что первоначальный подход неверен. Почему вам нужно передать класс в качестве параметра? Если параметр класса известен во время компиляции, вы можете просто вызывать разные методы для разных классов. Если класс неизвестен во время компиляции, вы никогда не должны иметь возможность пользоваться этим параметром generi c вообще.

1 голос
/ 15 июля 2020

«… почему эта ошибка? …«

Ошибка, которую javac сообщает о вашем исходном коде, дает вам эту причину :

...GenericBug.java:14: error: type argument T#1 is not within bounds of type-variable T#2
            return new FooManager<T>(type);   //Error: Bound mismatch
                                  ^
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>createFooManager(Class<T#1>)
    T#2 extends Foo declared in class FooManager
1 error

T#1 is not within bounds of type-variable T#2 потому что:

  1. <T> ObjectManager<T> createFooManager(...) означает, что вы определили T ( aka T#1) как имеющий без границ.
  2. Параметр типа T ( aka T#2) из FooManager<T extends Foo> определяется с помощью границ extends Foo. А метод generi c T не расширяет Foo, как того требует класс generi c. Значит, это за пределами .

«… как это исправить …»

Ваш оригинал Foo.class.isAssignableFrom(type) подход кажется мне не очень объектно-ориентированным. Итак, в основном, Я исправил это следующим образом .

...
private static <T> ObjectManager<T> createObjectManager(Class<T> type) {
        return new ObjectManager<>(type);
} 

private static <S extends Foo> ObjectManager<S> createFooManager(Class<S> type) {
        return new FooManager<>(type);
}
...   

Не очевидно, каков ваш вариант использования IRL. Поэтому мне пришлось сделать некоторые предположения. Как я и предполагал, вы, скорее всего, будете использовать это с более специализированными реализациями Foo. Поэтому я ввел FooJr, чтобы продемонстрировать, как решение будет справляться с этим:

    ObjectManager<? extends Foo> objectManager1 = createFooManager(Foo.class);
    System.out.println(objectManager1.getDescription());
    
    ObjectManager<?> objectManager2 = createObjectManager(Object.class);
    System.out.println(objectManager2.getDescription());

    objectManager1 = createFooManager(FooJr.class);
    System.out.println(objectManager1.getDescription());        

    objectManager2 = createObjectManager(FooJr.class);
    System.out.println(objectManager2.getDescription());

Сказать, что одно решение - « лучше » или « хуже «чем другой - субъективно; возможно, дело личного вкуса. Но с объективностью ( каламбур ) можно сказать, что одно решение может быть более объектно-ориентированным , чем другое. Некоторые решения определенно более безопасны, чем другие.

«… без предупреждения? …»

Это решение является более безопасным по типу. Запустите его с -Xlint:unchecked. Компилятор не выдает непроверенных предупреждений. И это без необходимости в каких-либо @SuppressWarnings(...).

«… почему Eclipse предоставляет решение, которое не компилируется? …»

Eclipse только представил вам свое наилучшее предположение в том, что, по его мнению, можно исправить для проблемы. В природе IDE есть пропуск некоторых вещей, которые javac или JLS абсолютно не допускают.

1 голос
/ 15 июля 2020

Проблема в том, что вы смешиваете безопасность времени компиляции универсальных шаблонов и проверки времени выполнения через isAssignableFrom.

Я не знаю лучшего способа сделать это:

private static <T> ObjectManager<T> createFooManager(Class<T> type) {
    if (Foo.class.isAssignableFrom(type)) {
        return (ObjectManager<T>) getIt((Class<? extends Foo>) type);
    }
    return new ObjectManager<>(type);

}

private static <R extends Foo> ObjectManager<R> getIt(Class<R> cls) {
    return new FooManager<>(cls);
}

Это вызовет unchecked warning.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...