Передача вложенного класса <MyInterface <T>> в качестве параметра в Android

Я пытаюсь создать обертку поверх Retrofit, чтобы абстрагировать реализацию моего сервиса. Я до сих пор успешно компилировал компилятор:

package com.example.spark.testapp.services;

import com.example.spark.testapp.services.apis.Get;
import com.example.spark.testapp.services.apis.Post;
import com.example.spark.testapp.services.utils.*;
import com.example.spark.testapp.services.utils.Error;

import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;




public class ServiceLayer {
    public <T> void performGet(String url, final Class<Get<T>> clazz, com.example.spark.testapp.services.utils.Callback<T> callback) {
        Retrofit retrofit = new Retrofit.Builder().baseUrl("").build();
        Get<T> service = retrofit.create(clazz);
        //Pass authentication token here
        Call<T> t = service.get(url, "");
        executeCallback(callback,t);
    }

    public <T> void performPost(String url, final Class<Post<T>> clazz,com.example.spark.testapp.services.utils.Callback<T> callback) {
        Retrofit retrofit = new Retrofit.Builder().baseUrl("").build();
        Post<T> service = retrofit.create(clazz);

        //Pass authentication token here
        Call<T> t = service.post(url, "");
        executeCallback(callback,t);
    }

    public <T> void executeCallback( final com.example.spark.testapp.services.utils.Callback<T> callback , Call<T> call) {
        call.enqueue(new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, Response<T> response) {
                callback.onSuccess(response.body());
            }


            @Override
            public void onFailure(Call<T> call, Throwable t) {
                ///Find out what exactly went wrong. Populate Error. and then...
                com.example.spark.testapp.services.utils.Error e = new Error();
                callback.onFailure(e);
            }
        });
    }
}

Пока это компилируется, проблема заключается в вызове метода:

private void getString() {

        ServiceLayer s = new ServiceLayer();
        s.performGet("",Get<String>.class,this); //Cannot select from parameterised type

    }

Я немного разобрался с этим и узнал, что это невозможно из-за стирания типа. Хорошо.

Но мой вопрос заключается в том, не должен ли компилятор вызывать ошибку здесь? На этой линии?

public <T> void performGet(String url, final Class<Get<T>> clazz, com.example.spark.testapp.services.utils.Callback<T> callback) 

Как скомпилирован мой сервисный уровень?

ИЗМЕНИТЬ

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

Ответы

Ответ 1

Но мой вопрос заключается в том, не должен ли компилятор вызывать ошибку здесь?

Подпись метода совершенно корректна в Java. И общие сигнатуры методов контролируются теми же правилами, что и обычные методы.

В общем методе действия, которые вы можете выполнять в коде, зависят от типов параметров. Например, если у вас есть этот метод:

public static <T> void test(List<T> list, Class<T> clazz) 
        throws InstantiationException, IllegalAccessException 
{
    list.add(clazz.newInstance());
}

Он компилируется, но если мы добавим следующую строку:

list.add(new Integer(1));

это произойдет не потому, что во время компиляции list принимает только экземпляры T. Поэтому общий метод хорошо определен.

При попытке вызвать общий метод компилятор не может вывести T из параметров. Основная проблема - это, очевидно, конструкция Class<Get<T>>, которая действительна в сигнатуре метода, но не рекомендуется. Хотя вы можете сделать небезопасный и странный бросок, чтобы заставить метод call компилироваться и работать:

s.performGet("",(Class<Get<String>>)(Class) Get.class,this);

Выполняя эту цепочку отливок, компилятор теперь может вывести T, потому что общие типы проверяются только во время компиляции. Во время выполнения Class<Get<T>> всегда будет Get.class.

Некоторые связанные вопросы по этой теме:

Передача класса <T> в java общего списка?

Общий тип как параметр в Java-методе

Ответ 2

Во-первых, ваш синтаксис действителен, поэтому компилятор не задает ошибку в заданной строке. Ошибка, которая у вас есть, вероятно, связана с тем, что Class является специальным классом, который создается VM, а компилятор во время проверки на ошибку рассматривает его как обычный класс.

Взгляните на этот пример:

 class Get<T> {
    }

class Callback<T> {
    }
class Clazz<T> {
    }

static class ServiceLayer  {

        public <T> void performGet(String url, final Clazz<Get<T>> clazz,
                Callback<T> callback) {
        }
    }

теперь, если вы попытаетесь позвонить

    ServiceLayer a = new ServiceLayer();
    Clazz<Get<String>> clazz = new Clazz<Hello.Get<String>>(); 
    Callback<String> callback = new Callback<String>();
    a.performGet("someurl", clazz, callback);

у вас есть проблемы. Итак, вы видите, что с этим синтаксисом нет проблем, но со специальным объектом.

Ответ 3

Сделал некоторые тесты и нашел какое-то решение, надеюсь, это поможет.

Так как этот код возвращает true (где A - некоторый общий класс, который ничего не делает):

A<String> a = new A<String>();
System.out.println(A.class == a.getClass());

Это означает, что вам не нужно получать класс 'Get <T> ', но просто "Get" для вашей модернизации.

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

public <T> void performGet(String url, final Class<Get> clazz, final Class<T> t, com.example.spark.testapp.services.utils.Callback<T> callback) {
    Retrofit retrofit = new Retrofit.Builder().baseUrl("").build();
    Get<T> service = retrofit.create(clazz);
    ...

Ответ 4

Если вы хотите сохранить доступный общий параметр параметра параметра (T) в методе, вы должны предоставить информацию о классе. Пример:

public class SomeClass<T, X, C> {
   Class<X> xClass;
   Class<T> tClass;
   Class<C> cClass;

   public SomeClass<T, X, C>(Class<T> a, Class<X> b, Class<C> c){
      tClass = a;
      xClass = b;
      cClass = c;
   }
}

Таким образом, вы можете использовать информацию о классе внутри класса "потребитель", либо для доступа к общим сигнатурам, процессам сериализации/десериализации, создания объектов, вызова общих методов и т.д.

Теперь, если вы просто хотите использовать общую информацию о параметрах класса внутри метода, вы должны:

public <T> void performGet(String url, final Class<T> clazz, com.example.spark.testapp.services.utils.Callback<T> callback) {
    Retrofit retrofit = new Retrofit.Builder().baseUrl("").build();
    Get<T> service = retrofit.create(clazz);
    //Pass authentication token here
    Call<T> t = service.get(url, "");
    executeCallback(callback,t);
}

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

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

https://github.com/fcopardo/EasyRest/blob/master/src/main/java/com/grizzly/rest/GenericRestCall.java