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 в то время как это простое решение позволяет звонить только до или после общей части...