【问题标题】:Java Object return type vs. Generic MethodsJava 对象返回类型与通用方法
【发布时间】:2016-04-07 13:18:29
【问题描述】:

我看到了几个关于泛型返回类型的问题,但没有一个回答我的问题。
如果没有任何参数的限制,例如JayWay中的以下方法:

public static <T> T read(String json, String jsonPath, Filter... filters) {
    return new JsonReader().parse(json).read(jsonPath, filters);
}

将其用作泛型有什么意义?
我告诉我团队的人,这种方法应该用作:

JsonPath.<Boolean>read(currentRule, "$.logged")

代替:

(boolean) JsonPath.read(currentRule, "$.logged")

但我真的分不出来...

【问题讨论】:

  • 类型擦除后,我认为没有区别。
  • 方法体中没有任何东西表面上使用类型变量TJsonPath.&lt;Boolean&gt;read(currentRule, "$.logged") 如何安全地返回 BooleanJsonPath.&lt;String&gt;read(currentRule, "$.logged") 安全地返回 String
  • 您愿意将演员表更改为Boolean 以避免将自动装箱拖入问题中吗?
  • 实际上,我在这里看不到泛型的优势。如前所述,从未使用过 T。

标签: java generics


【解决方案1】:

编译器在代码中插入不可见的强制转换来工作泛型。

例如,在将泛型添加到语言之前,您必须这样做。

List list = new ArrayList();
list.add("Foo");
list.add("Bar");
String str0 = (String) list.get(0);
String str1 = (String) list.get(1);

这很烦人。因为get() 返回了Object,所以每次你想从List 获得String 时都必须进行转换。

现在,List 是通用的,get() 返回T,所以你可以这样做。

List<String> list = new ArrayList<>();
list.add("Foo");
list.add("Bar");
String str0 = list.get(0);
String str1 = list.get(1);

这里发生的是编译器通过为您添加转换将新版本转换为旧版本,但它们仍然存在。

但是,泛型的全部意义在于,这些编译器生成的强制转换是保证安全的 - 即它们不可能在运行时抛出 ClassCastException

在我看来,如果你使用泛型来隐藏保证安全的强制转换,仅仅因为它们很烦人,那就是对该功能的滥用。

它是否是一个泛型方法并且你这样做

Boolean a = JsonPath.<Boolean>read(currentRule, "$.logged");

或者它返回 Object 而你这样做

Boolean a = (Boolean) JsonPath.read(currentRule, "$.logged");

两个版本都可能在运行时抛出ClassCastException,所以我认为最好强制转换,这样至少你知道你正在做的事情可能会失败。

我认为泛型方法的返回类型涉及类型参数T 是不好的做法,如果方法参数不涉及,除非返回的对象不能以妥协的方式使用类型安全。例如,

public static <T> List<T> emptyList()

Collections 中没问题(列表为空,因此它不能包含错误类型的元素)。

在您的情况下,我认为 read 方法不应该是通用的,而应该只返回 Object

【讨论】:

  • read 方法是我正在使用的第 3 方库。如果你在我的位置,你会如何使用它?自己投射还是让内部投射并捕捉ClassCastException
  • @Nati 我会使用Boolean a = JsonPath.&lt;Boolean&gt;readBoolean a = JsonPath.read,并在代码中留下注释//warning: this method involves an unsafe cast!
  • 该建议与 AndyTurner 的建议几乎相同(留下评论并使用 @SuppressWarnings("unchecked"),但实际上没有要禁止的警告。
  • 从我目前所见的情况来看 - 没有太大的区别,如果有的话。未经检查的铸造 invadable,所以这几乎是一个样式问题!
  • 没错——这纯粹是意见和风格的问题,看看这些答案显然存在分歧。不可避免的事实是,调用该方法并将其分配给Boolean(无论您使用(Boolean)&lt;Boolean&gt; 还是两者都不使用),您都可以获得ClassCastException
【解决方案2】:

我会远离的主要原因

JsonPath.<Boolean>read(currentRule, "$.logged")

是它在内部执行未经检查的强制转换,并隐藏了这个事实。例如,您可以在同一个地方调用此方法:

JsonPath.<String>read(currentRule, "$.logged")

在运行时实际发生之前,您不可能知道那里可能存在问题 - 它仍然可以编译,您甚至不会收到警告。

没有摆脱未经检查的演员表——我宁愿把它放在我面前的代码中,所以我知道存在潜在的危险;这让我可以采取合理的措施来缓解这个问题。

@SuppressWarnings("unchecked")  // I know something might go wrong here!
boolean value = (boolean) JsonPath.read(currentRule, "$.logged")

【讨论】:

  • 那么您实际上是在说该方法更好地返回 Object 而不是通用的,因为强制转换是可入侵的?
  • @Nati 是的。就像我上面评论的那样,你实际上并没有使用T,除了做未经检查的演员表。
  • 方法的声明方式,他可以这样做:String value = JsonPath.read(currentRule, "$.logged"); 无论您是否提供显式类型见证,它都会在内部执行未经检查的强制转换。
【解决方案3】:

拥有一个从未设置过的类型参数(当调用JsonPath.read(currentRule, "$.logged") 时),实际上会使编译器完全忽略方法中的所有泛型信息,并将所有类型参数替换为:

  • Object,如果类型参数没有上限。 (就像你的情况一样)
  • U,如果类型参数的边界类似于&lt;T extends U&gt;。例如,如果您有一个&lt;T extends Number&gt; 作为类型参数并通过调用JsonPath.read(...) 忽略它,那么编译器会将类型参数替换为Number

在使用强制转换 ((boolean) JsonPath.read(...)) 的情况下,类型参数将替换为 Object。然后,这种类型不安全地转换为boolean,首先返回一个Boolean(可能),然后将此包装器自动拆箱为boolean。这一点都不安全。实际上,每次转换都是不安全的 - 几乎你告诉编译器:“我知道这种类型在运行时会是什么,所以请相信我,让我将它转换为与之兼容的其他东西。”。你卑微的仆人,编译器,允许这样做,但如果你错了,那是不安全的。 :)

你的方法还有另一件事。类型参数从不在方法体或参数中使用——这使得它非常多余。因为通过对boolean 进行强制转换,您坚持要知道new JsonReader().parse(json).read(jsonPath, filters); 的返回类型,那么您应该将返回类型设为boolean(或Boolean):

public static Boolean read(String json, String jsonPath, Filter... filters) {
    return new JsonReader().parse(json).read(jsonPath, filters);
}

【讨论】:

  • 这不是我的代码,我只是想知道使用它的最佳方式是什么。
  • 无论是否属于您,我希望对您有所帮助。干杯:)
  • 我的意思是 - 这是我正在使用的依赖项,第 3 方代码。你会自己投射还是将 作为泛型传递?
  • 我会设置&lt;Boolean&gt;
【解决方案4】:

两者在功能上没有什么不同。字节码可能是相同的。

核心区别在于一个使用强制转换,而另一个使用泛型。

如果有任何替代机制,我通常会尽量避免强制转换,因为通用形式是一个非常有效的替代方案,我会这样做。

// The right way.
JsonPath.<Boolean>read(currentRule, "$.logged");

【讨论】:

  • 我的想法完全正确。我认为它看起来更干净,尽管 JayWay 内部确实转换为 (T)...
  • @Nati - 他们执行运行时转换,因此在功能上没有区别。
猜你喜欢
  • 2015-11-25
  • 2018-10-19
  • 1970-01-01
  • 1970-01-01
  • 2012-05-04
  • 2012-06-10
  • 2015-07-17
  • 2017-01-21
  • 2021-12-15
相关资源
最近更新 更多