Java generics: фактический аргумент T не может быть преобразован в int путем преобразования вызова метода
У меня есть такой код:
// This class cannot be changed
class VendorApi {
static void func1(char x) {}
static void func1(int x) {}
static void func1(float x) {}
static void func1(double x) {}
}
class Main {
static <T> void my_func(T arg) {
// much of code, which uses T
// ...
VendorApi.<T>func1(arg);
}
public static void main(String args[]) {
// call my_func for each type (char, int, float, double)
// ...
int i = 1;
my_func(i);
char c = 1;
my_func(c);
}
}
Что мне нужно сделать, так это вызвать каждую функцию VendorApi.func() для каждого типа аргументов из my_func(). Выложенный код не компилируется, он показывает идею. Как это сделать, кроме копирования в my_func() для каждого типа?
Ответы
Ответ 1
Вы можете передать func1
в метод как Consumer<T>
:
class VendorApi {
static void func1(char x) {}
static void func1(int x) {}
static void func1(float x) {}
static void func1(double x) {}
}
class Main {
static void my_func(char arg) { my_func(arg, VendorApi::func1); }
static void my_func(int arg) { my_func(arg, VendorApi::func1); }
static void my_func(float arg) { my_func(arg, VendorApi::func1); }
static void my_func(double arg) { my_func(arg, VendorApi::func1); }
private static <T> void my_func(T arg, Consumer<T> func1) {
// much of code, which uses T
// ...
func1.accept(arg);
}
public static void main(String args[]) {
// call my_func for each type (char, int, float, double)
// ...
int i = 1;
my_func(i, VendorApi::func1);
char c = 1;
my_func(c);
}
}
Это дает вам возможность скомпилировать тип времени (вы можете вызывать только my_func
с char
, int
, float
и double
вне класса, так как общая версия является частной) и позволяет избежать отражения.
Также my_func
должен быть myFunc
, если вы хотите следовать соглашениям об именах методов Java.
Ответ 2
Не самый чистый ответ, но он будет делать то, что вы просите.
Вы можете проверить, соответствует ли ваш общий класс аргументов любому типу, который предоставляет ваш VenderApi
, а затем выполняется.
код
public class Main {
static <T> void my_func(T arg) {
if (arg.getClass().equals(Integer.class))
VendorApi.func1((Integer) arg);
else if (arg.getClass().equals(Character.class))
VendorApi.func1((Character) arg);
else
throw new IllegalStateException(
"cannot perform my_func on object of class "
+ arg.getClass());
}
public static void main(String args[]) {
// call my_func for each type (char, int, float, double)
// ...
int i = 1;
my_func(i);
char c = 1;
my_func(c);
String str = "bla";
my_func(str);
}
}
API вашего поставщика
//This class cannot be changed
public class VendorApi {
public static void func1(char x) {
System.out.println("i am a char "+x);
}
public static void func1(int x) {
System.out.println("i am a int "+x);
}
public static void func1(float x) {
}
public static void func1(double x) {
}
}
Выход
i am a int 1
i am a char
Exception in thread "main" java.lang.IllegalStateException: cannot perform my_func on object of class class java.lang.String
at core.Main.my_func(Main.java:10)
at core.Main.main(Main.java:23)
Ответ 3
Другой вариант - использовать отражение и отображение:
Map<Class<?>, Method> mapping = new HashMap<>();
mapping.put(Integer.class, VendorApi.class.getMethod("func1", int.class));
// more mappings here
Хотя вам нужен такой же код, как и в конструкции if/else, это сопоставление также может быть заполнено программно (вы можете запустить цикл вокруг VendorApi.class.getMethods()
), или вы можете прочитать конфигурацию из файла. В общем, такое отображение является более гибким.
Теперь вы можете использовать его для вызова API:
static void callVendorFunc(Object arg) { // no need for generics here
mapping.get(arg.getClass()).invoke(null, arg);
}
И ваш метод станет следующим:
static <T> void my_func(T arg) {
// much of code, which uses T
// ...
callVendorFunc(arg);
}
Я не делал никаких исключений. И, конечно же, рефлексивный подход несколько менее эффективен.
Ответ 4
То, что вы ищете, не может быть выполнено в java. Это обходное решение.
// This class cannot be changed
class VendorApi {
static void func1(char x) {}
static void func1(int x) {}
static void func1(float x) {}
static void func1(double x) {}
}
class Main {
static <T> void my_func(T arg) {
// much of code, which uses T
// ...
if(arg instanceof Character) {
VendorApi.func1((Character)arg);
}
else if (arg instanceof Integer) {
VendorApi.func1((Integer)arg);
}
//And so on...
}
public static void main(String args[]) {
// call my_func for each type (char, int, float, double)
// ...
int i = 1;
my_func(i);
char c = 1;
my_func(c);
}
}
Но я бы советовал переосмыслить ваш дизайн.
Ответ 5
Если вы не можете изменить свой VendorApi
, ваш лучший выбор, кажется, обертывает его в общий вызов. Это похоже на то, что предлагалось другими, но с меньшим дублированием, а также безопасным типом (без исключений во время выполнения, если аргумент неправильного типа):
class VendorAPIWrapper {
static <T extends Number> void func1(T arg) {
if(arg instanceof Double) VendorAPI.func1(arg.doubleValue());
else if(arg instanceof Float) VendorAPI.func1(arg.floatValue());
else VendorAPI.func1(arg.intValue());
}
static void func1(char arg) { VendorAPI.func1(arg); }
}
Вам нужно будет изменить определение my_func
, чтобы ограничить параметр типа, а затем внутри него вы можете просто сделать VendorAPIWrapper.func1(arg)
. Проблема в том, что Character
не является Number
, поэтому, чтобы быть безопасным по типу, вам все равно понадобятся две версии функции: одна для чисел и другая для символов, если вы не хотите конвертировать символы в ints или байты перед вызовом.
Ответ 6
Я думаю, вам нужно что-то вроде этого:
static <T> void my_func(T arg) {
// much of code, which uses T
// ...
if(arg instanceof Integer) {
VendorApi.func1((Integer) arg)
} else if(arg instanceof Double) {
...
} else {
throw new IllegalArgumentException("...");
}
Ответ 7
Просто разделите свой метод на общую часть и часть invokation:
public class Main {
static void my_func(char arg) {
my_funcGenericPart(arg);
VendorApi.func1(arg);
}
static void my_func(int arg) {
my_funcGenericPart(arg);
VendorApi.func1(arg);
}
static void my_func(float arg) {
my_funcGenericPart(arg);
VendorApi.func1(arg);
}
static void my_func(double arg) {
my_funcGenericPart(arg);
VendorApi.func1(arg);
}
private static <T> void my_funcGenericPart(T arg) {
// much of code, which uses T
// ...
// the caller will invoke the right VendorApi.func1(arg);
}
public static void main(String args[]) {
// call my_func for each type (char, int, float, double)
// ...
int i = 1;
my_func(i);
char c = 1;
my_func(c);
}
}
Обратите внимание, что ваш параметр типа T
здесь бесполезен, вы можете просто объявить аргумент своего общего метода как Object
(или Number
) без разницы.
Это концептуально близко к ответу Alex, но не требует Java 8. Но его ответ имеет то преимущество, что общий код может выбрать, в какой момент вызывать VendorApi.func1
в то время как это простое решение позволяет звонить только до или после общей части...