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

У меня вопрос, есть ли способ иметь несколько возможных расширений для дженериков. Например:

public class Foo<? extends String>{}
public class Bar<? extends StringBuilder>{}
//combined
public class FooBar<? extends String, StringBuilder>{}
//or perhaps
public class FooBar<? extends String || StringBuilder>{}

Я знаю, что класс FooBar не будет компилироваться, но я надеюсь, что это поможет объяснить мой вопрос и указанное намерение. В заключение еще раз повторю мой вопрос: возможно ли расширение двух классов в общем предложении или способ, который косвенно имитировал бы такое действие?

Заметьте, я не спрашиваю, как работают дженерики, ни работают расширения, ни что-либо касается того, как и когда использовать дженерики, поскольку я уже знаю этот. Если вопрос требует уточнения, я отредактирую его, чтобы лучше понять мои способности.

1
George Xavier 29 Май 2019 в 21:06

2 ответа

Лучший ответ

Класс не может расширять оба String и StringBuilder, так как у классов есть только один родительский класс. Однако они могут реализовывать несколько интерфейсов, которые вы можете указать с помощью &.

class FooBar<T extends String & Runnable & Collection<Integer>> {}

(Обратите внимание, что String является окончательным, поэтому на самом деле невозможно выполнить вышеуказанное ограничение.)

3
John Kugelman 29 Май 2019 в 18:13

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

Вы можете легко найти полностью реализованный класс Either с помощью простого поиска Google, например, на github

Для фрагмента кода ниже давайте рассмотрим эту упрощенную версию:

public class Either<L, R> {
    private final L left;
    private final R right;
    private final boolean isRight;

    private Either(L left, R right, boolean isRight) {
        this.left = left;
        this.right = right;
        this.isRight = isRight;
    }

    public static <L, R> Either<L, R> left(L left){
        return new Either<>(left, null, false);
    }

    public static <L, R> Either<L, R> right(R right){
        return new Either<>(null, right, true);
    }

    public <T>  T fold(Function<L,T> foldLeft, Function<R, T> foldRight){
        return isRight ? foldRight.apply(right) : foldLeft.apply(left);
    }
}

Теперь предположим, что у вас есть интерфейс с каким-то методом, который должен принимать String или StringBuilder:

public interface IFooBar <T extends Either<? extends String, ? extends StringBuilder>>{
    String  doSomething(T t);
}

И реализация:

class FooBar implements IFooBar<Either<String, StringBuilder>> {
    @Override
    public String doSomething(Either<String, StringBuilder> either) {
        return either.fold(s -> "String: " + s, sb -> "StringBuilder:" + sb);
    }
}

Тогда вы можете просто использовать это так:

public static void main(String[] args) {
    IFooBar<Either<String, StringBuilder>> fooBar = new FooBar();
    // Since in this case it is single method interface you can even use lambda expression
    // IFooBar<Either<String, StringBuilder>> fooBar = either -> either.fold(s -> "String: " + s, sb -> "StringBuilder:" + sb);
    System.out.println(fooBar.doSomething(Either.left("Foo")));
    System.out.println(fooBar.doSomething(Either.right(new StringBuilder("Bar"))));
}

Я надеюсь, что это поможет вам.

1
FilipRistic 29 Май 2019 в 19:36