Убедитесь, что разные типы дженериков

Я пытаюсь создать общий интерфейс конвертера, который будет конвертировать T возражает против U объекты с использованием того же имени метода, т.е. convert:

public interface GenericConverter<T, U> {
  T convert(U fromObject);
  U convert(T fromObject);
}

Конечно, стирание дженериков преобразует оба метода в следующие во время компиляции:

convert(object fromObject);

Таким образом, оба метода имеют одинаковое удаление, что приводит к ошибке во время компиляции.

В моем примере логично, что я всегда буду использовать разные типы объектов для T а также U, Есть ли способ сохранить то же имя метода (конвертировать), чтобы иметь возможность инкапсулировать тот факт, что T а также U разные типы и гарантируют, что правильный метод будет вызываться в каждом случае?

3 ответа

Решение

Если только два типа T а также U основаны на двух отдельных иерархиях типов (т.е. каждая из них всегда будет иметь какой-то отдельный суперкласс), нет способа иметь два метода с одинаковым именем. Это даже не имеет смысла семантически в этом случае - какова должна быть семантическая разница между этими двумя методами, если вы не можете различить два типа в любом разумном вопросе?

Помимо предложенного переименования методов, рассмотрим также наличие только одного такого метода в интерфейсе и вместо этого использование GenericConverter<T, U> а также GenericConverter<U, T> везде, где вам нужно преобразовать оба пути.

Это невозможно напрямую из-за стирания типа. Несколько вариантов уже были перечислены в других ответах. Один из них неявно направлен на разделение конверсий. Таким образом, вместо одного конвертера, вы можете иметь два разных:

public interface GenericConverter<T, U> {
  U convert(T fromObject);
}

GenericConverter<Integer, String> forward = Converters.integerString();
GenericConverter<String, Integer> backward = Converters.stringInteger();

Но обратите внимание, что GenericConverter интерфейс в этом случае структурно равен Function интерфейс - так что, вероятно, нет причин создавать новый.

Вместо этого, если вы хотите использовать этот "прямой и обратный преобразователь" в качестве некоего именованного объекта (обе функции преобразования неразрывно связаны друг с другом), вы можете определить интерфейс для этого:

public interface GenericConverter<T, U> {
  Function<T, U> forward();
  Function<U, T> backward();
}

Это может быть использовано следующим образом:

GenericConverter<Integer, String> converter = Converters.integerString();

String string = converter.forward().apply(someInteger);
Integer integer = converter.backward().apply(someString);

Является ли это "лучшим" решением или нет, зависит от предполагаемых моделей использования. Одним из преимуществ может быть то, что с общей (!) Утилитой, подобной этой...

private static GenericConverter<T, U> create(
    Function<T, U> forward, Function<U, T> backward) {
    return new GenericConverter() {
        @Override
        public Function<T, U> forward() {
            return forward;
        }
        @Override
        public Function<U, T> backward() {
            return backward;
        }
    }
}

создать новый конвертер было бы просто, как пирог:

public static GenericConverter<Integer, String> integerString() {
    return create(
        integer -> String.valueOf(integer), 
        string -> Integer.parseInt(string)
    );
}

проблема

Когда ты сказал,

логично, что я всегда буду использовать разные типы объектов для T а также U

Компилятор не знает. Типы могут быть одинаковыми, но не разными (без ограничений).


Подход 1

class ConversionSource {}

class ConversionTarget {}

interface GenericConverter<T extends ConversionSource, U extends ConversionTarget> {
    T convert(U obj);
    U convert(T obj);
}

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


Подход 2

interface InvertibleConverter<T, U> {
    U convert(T obj);
    InvertibleConverter<U, T> inverse();
}

class Tokenizer implements InvertibleConverter<String, Stream<String>> {

    @Override
    Stream<String> convert(String obj) {
        return Arrays.stream(obj.split(" "));
    }

    @Override
    InvertibleConverter<Stream<String>, String> inverse() {
        return new InvertibleConverter<Stream<String>, String>() {
            @Override
            public String convert(Stream<String> obj) {
                return obj.collect(Collectors.joining(" "));
            }

            @Override
            public InvertibleConverter<String, Stream<String>> inverse() {
                return Tokenizer.this;
            }
        };
    }
}

Использование может быть следующим

InvertibleConverter<String, Stream<String>> splitter = new Tokenizer();
String sentence = "This is a sentence";
Stream<String> words = splitter.convert(sentence);
String sameSentence = splitter.inverse().convert(words);

Этот подход работает даже тогда, когда T а также U идентичны

Надеюсь это поможет.
Удачи

Другие вопросы по тегам