【问题标题】:Passing nested Class<MyInterface<T>> as a parameter in Android在 Android 中将嵌套的 Class<MyInterface<T>> 作为参数传递
【发布时间】:2016-04-06 06:23:12
【问题描述】:

我正在尝试在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

    }

我在 Google 上搜索了一下,发现由于类型擦除,这是不可能的。美好的。

但我的问题是,编译器不应该在这里引发错误吗?在这条线上? :

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

我的服务层是如何编译的?

编辑

这个问题似乎被误解了。我不是在寻找一种方法来让这个设计工作。我理解其中的缺陷,我们找到了一种更好的方法来分层我们的服务。问题是关于语言本身的有趣/奇怪的行为。

【问题讨论】:

  • 我假设 this 实现 Callback&lt;String&gt;?
  • @njk2 -- 是的。你说的对。 this 实现回调。而且根本没有关系。问题是,如果你不能直接调用它,我的服务层如何编译?
  • 我不确定你是否有最好的方法,或者甚至改造是在这里使用的正确工具。您显然有一个 GET 端点,它根据各种事物返回不同的事物。如果您知道的话,您可能可以定义端点返回的所有可能类型。或者完全使用其他东西,更底层一点。
  • 是的,我们没有根据我们的最佳判断使用 Retrofit。但是这种语言怪癖仍然让我很感兴趣,因此我重申的问题是关于编译器让我做一些不可撤销的事情。我坚信这不是方法。

标签: java android generics


【解决方案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&lt;Get&lt;T&gt;&gt; 构造,它在方法签名中有效但不推荐。虽然,你可以做一个不安全和奇怪的转换来使方法调用编译并工作:

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

编译器现在可以通过这个转换链推断T,因为泛型类型只在编译时检查。在运行时,Class&lt;Get&lt;T&gt;&gt; 将始终为 Get.class

关于这个话题的一些相关问题:

Passing the Class<T> in java of a generic list?

Generic type as parameter in Java Method

【讨论】:

    【解决方案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”类,而只需要“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);
          ...
      

      【讨论】:

      • 我们之前尝试过这个解决方案,但我们不希望T 的类型信息在retrofit.create(clazz) 中丢失。它返回的是 Get 而不是 Get&lt;T&gt; 类型的东西。
      • 按类型是指班级?如果是,第一个示例显示 Get&lt;T&gt;Get 属于同一类。如果不是,你能告诉你在Get&lt;T&gt; = ...设置后会出现什么样的错误吗?
      • 按类型,我的意思是类型。我没有收到错误,但它自然而然地给了我一个警告。 Get&lt;T&gt;Get 的类型不相同。即使我们按照您建议的方式执行此操作,它也会编译,但它肯定无法完成我想要它做的事情。 :) 另外,这不是我问题的答案。
      【解决方案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

      【讨论】:

      • 请看一下编辑。我不是在寻找解决方案来让它发挥作用。我在问为什么编译器让这个代码编译——一个方法签名是不可调用的代码”——当它不应该的时候。这个答案,尽管信息量很大,但我并不是真的寻找
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-03
      • 1970-01-01
      • 2017-03-21
      • 1970-01-01
      相关资源
      最近更新 更多