Убедитесь, что разные типы дженериков
Я пытаюсь создать общий интерфейс конвертера, который будет конвертировать 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
идентичны
Надеюсь это поможет.
Удачи