【问题标题】:Difference between C# and Java's ternary operator (? :)C# 和 Java 的三元运算符 (? :) 之间的区别
【发布时间】:2016-05-15 19:48:42
【问题描述】:

我是 C# 新手,我只是遇到了一个问题。在处理三元运算符(? :)时,C# 和 Java 是有区别的。

在下面的代码段中,为什么第 4 行不起作用?编译器显示错误消息there is no implicit conversion between 'int' and 'string'。第 5 行也不行。 Lists 都是对象,不是吗?

int two = 2;
double six = 6.0;
Write(two > six ? two : six); //param: double
Write(two > six ? two : "6"); //param: not object
Write(two > six ? new List<int>() : new List<string>()); //param: not object

但是,相同的代码在 Java 中也可以工作:

int two = 2;
double six = 6.0;
System.out.println(two > six ? two : six); //param: double
System.out.println(two > six ? two : "6"); //param: Object
System.out.println(two > six ? new ArrayList<Integer>()
                   : new ArrayList<String>()); //param: Object

C# 中缺少哪些语言功能?如果有,为什么不添加?

【问题讨论】:

  • 根据msdn.microsoft.com/en-us/library/ty67wk28.aspx“first_expression 和 second_expression 的类型必须相同,或者必须存在从一种类型到另一种类型的隐式转换。”
  • 我有段时间没做C#了,但是你引用的消息不是说的吗?三元的两个分支必须返回相同的数据类型。你给它一个int和一个String。 C# 不知道如何自动将 int 转换为 String。您需要对其进行显式类型转换。
  • @blackr1234 因为int 不是对象。这是一个原始的。
  • @Code-Apprentice 是的,他们是very different indeed - C# 具有运行时具体化,因此列表是不相关的类型。哦,如果你愿意,你也可以将通用接口协变/逆变加入混合,以引入某种程度的关系;)
  • 为了 OP 的好处:Java 中的类型擦除意味着 ArrayList&lt;String&gt;ArrayList&lt;Integer&gt; 在字节码中变为 ArrayList。这意味着它们在运行时看起来是完全相同的类型。显然在 C# 中它们是不同的类型。

标签: java c# ternary-operator conditional-operator


【解决方案1】:

关于泛型部分:

two > six ? new List<int>() : new List<string>()

在 C# 中,编译器尝试将右侧的表达式部分转换为一些通用类型;因为List&lt;int&gt;List&lt;string&gt; 是两种不同的构造类型,一种不能转换为另一种。

在Java中,编译器试图找到一个共同的超类型而不是转换,所以代码的编译涉及到wildcardstype erasure的隐式使用;

two > six ? new ArrayList<Integer>() : new ArrayList<String>()

具有ArrayList&lt;?&gt; 的编译类型(实际上,它也可以是ArrayList&lt;? extends Serializable&gt;ArrayList&lt;? extends Comparable&lt;?&gt;&gt;,取决于使用上下文,因为它们都是常见的泛型超类型)和运行时类型的原始ArrayList(因为它是常见的原始超类型)。

例如(test it yourself)

void test( List<?> list ) {
    System.out.println("foo");
}

void test( ArrayList<Integer> list ) { // note: can't use List<Integer> here
                                 // since both test() methods would clash after the erasure
    System.out.println("bar");
}

void test() {
    test( true ? new ArrayList<Object>() : new ArrayList<Object>() ); // foo
    test( true ? new ArrayList<Integer>() : new ArrayList<Object>() ); // foo 
    test( true ? new ArrayList<Integer>() : new ArrayList<Integer>() ); // bar
} // compiler automagically binds the correct generic QED

【讨论】:

  • 其实Write(two &gt; six ? new List&lt;object&gt;() : new List&lt;string&gt;());也不起作用。
  • @blackr1234 确切地说:我不想重复其他答案已经说过的内容,但是三元表达式需要评估为一种类型,编译器不会尝试找到“最低公分母” ”的两个。
  • 其实这不是类型擦除,而是通配符类型。 ArrayList&lt;String&gt;ArrayList&lt;Integer&gt; 的擦除将是 ArrayList(原始类型),但此三元运算符的推断类型是 ArrayList&lt;?&gt;(通配符类型)。更一般地说,Java runtime 通过类型擦除实现泛型对表达式的 compile time 类型没有影响。
  • @vaxquis 谢谢!我是 C# 人,Java 泛型让我很困惑 ;-)
  • 实际上,two &gt; six ? new ArrayList&lt;Integer&gt;() : new ArrayList&lt;String&gt;() 的类型是ArrayList&lt;? extends Serializable&amp;Comparable&lt;?&gt;&gt;,这意味着你可以将它分配给ArrayList&lt;? extends Serializable&gt; 类型的变量以及ArrayList&lt;? extends Comparable&lt;?&gt;&gt; 类型的变量,当然@987654342 @,相当于ArrayList&lt;? extends Object&gt;,同样有效。
【解决方案2】:

给出的答案很好;我要向他们补充一点,这条 C# 规则是更通用的设计准则的结果。当被要求从几个选项之一推断表达式的类型时,C# 会选择其中最好的一个。也就是说,如果你给 C# 一些选项,比如“Giraffe, Mammal, Animal”,那么它可能会选择最一般的——动物——或者它可能会选择最具体的——长颈鹿——这取决于具体情况。但它必须选择它实际给出的选择之一。 C# 从来没有说“我的选择是在猫和狗之间,因此我会推断动物是最好的选择”。这不是给定的选择,因此 C# 无法选择它。

在三元运算符的情况下,C# 尝试选择更通用的 int 和 string 类型,但也不是更通用的类型。 C# 没有选择一开始就没有选择的类型,比如 object,而是决定不能推断出任何类型。

我还注意到,这符合 C# 的另一个设计原则:如果看起来有问题,请告诉开发人员。语言并没有说“我会猜测你的意思,如果可以的话,我会蒙混过关”。语言说“我认为你在这里写了一些令人困惑的东西,我要告诉你。”

另外,我注意到 C# 不会从 变量 推理到 赋值,而是从另一个方向推理。 C# 没有说“您正在分配给一个对象变量,因此表达式必须可转换为对象,因此我会确保它是”。相反,C# 说“这个表达式必须有一个类型,并且我必须能够推断出该类型与对象兼容”。由于表达式没有类型,所以会产生错误。

【讨论】:

  • 我过了第一段,想知道为什么突然对协变/逆变感兴趣,然后我开始在第二段上同意更多,然后我看到谁写了答案......应该早点猜到。你有没有注意到exactly how much you use 'Giraffe' as an example?你一定很喜欢长颈鹿。
  • 长颈鹿真棒!
  • @Pharap:说真的,我这么多使用长颈鹿是有教学原因的。首先,长颈鹿在全世界都很有名。其次,这是一个有趣的词,它们看起来很奇怪,这是一个令人难忘的形象。第三,在同一个类比中使用“长颈鹿”和“乌龟”,强烈强调“这两个类虽然可能共享一个基类,但它们是非常不同的动物,具有非常不同的特征”。关于类型系统的问题通常有类型在逻辑上非常相似的示例,这会导致您的直觉错误。
  • @Pharap:也就是说,问题通常类似于“为什么不能将客户发票供应商工厂列表用作发票供应商工厂列表?”你的直觉说“是的,这些基本上是一样的”,但是当你说“为什么一个满是长颈鹿的房间不能用作一个满是哺乳动物的房间呢?”那么,“因为一个满是哺乳动物的房间可以容纳老虎,一个满是长颈鹿的房间却不能”,这更像是一个直观的类比。
  • @Eric 我最初在int? b = (a != 0 ? a : (int?) null) 遇到了这个问题。还有this question,以及旁边的所有链接问题。如果您继续关注这些链接,将会有很多。相比之下,我从未听说有人在使用 Java 的方式时遇到实际问题。
【解决方案3】:

这很简单。 string 和 int 之间没有隐式转换。三元运算符需要最后两个操作数具有相同的类型。

试试:

Write(two > six ? two.ToString() : "6");

【讨论】:

  • 问题是是什么导致了 Java 和 C# 之间的差异。这没有回答问题。
【解决方案4】:

查看C# 5 语言规范第 7.14 节:条件运算符,我们可以看到以下内容:

  • 如果 x 是 X 类型,y 是 Y 类型,那么

    • 如果存在从 X 到 Y,但不存在从 Y 到 X 的隐式转换(第 6.1 节),则 Y 是 条件表达式。

    • 如果存在从 Y 到 X,但不存在从 X 到 Y 的隐式转换(第 6.1 节),则 X 是 条件表达式。

    • 否则无法确定表达式类型,出现编译时错误

换句话说:它尝试查找 x 和 y 是否可以转换为 彼此,如果不能,则发生编译错误。在我们的例子中,intstring 没有显式或隐式转换,因此无法编译。

将此与Java 7 Language Specification section 15.25: Conditional Operator 进行对比:

  • 如果第二个和第三个操作数的类型相同(可能是 null 类型),那么这就是条件表达式的类型。 (
  • 如果第二个和第三个操作数之一是原始类型 T,而另一个的类型是对 T 应用装箱转换(第 5.1.7 节)的结果,则条件表达式的类型为 T。(
  • 如果第二个和第三个操作数之一是空类型,另一个是引用类型,那么条件表达式的类型就是那个引用类型。 (
  • 否则,如果第二个和第三个操作数具有可转换(第 5.1.8 节)为数值类型的类型,则有几种情况:(NO
  • 否则,第二个和第三个操作数分别为 S1 和 S2 类型。令 T1 为将装箱转换应用于 S1 的类型,令 T2 为将装箱转换应用于 S2 的类型。
    条件表达式的类型是将捕获转换 (§5.1.10) 应用于 lub(T1, T2) (§15.12.2.7) 的结果。 (

并且,查看section 15.12.2.7. Inferring Type Arguments Based on Actual Arguments,我们可以看到它试图找到一个共同的祖先,该祖先将用作用于以Object 登陆它的调用的类型。 Object 一个可接受的参数,所以调用会起作用。

【讨论】:

  • 我读到 C# 规范说必须至少在一个方向上进行隐式转换。只有在任一方向没有隐式转换时才会出现编译器错误。
  • 当然,这仍然是最完整的答案,因为您直接引用规范。
  • 这实际上只在 Java 5 中发生了变化(我认为可能是 6)。在此之前,Java 的行为与 C# 完全相同。由于枚举的实现方式,这可能在 Java 中引起比 C# 更多的惊喜,尽管这一直是一个很好的变化。
  • @Voo:仅供参考,Java 5 和 Java 6 具有相同的规范版本(Java 语言规范,第三版),所以它在 J​​ava 6 中肯定没有改变.
【解决方案5】:

在 Java 和 C#(以及大多数其他语言)中,表达式的结果都有一个类型。在三元运算符的情况下,有两个可能的子表达式为结果求值,并且两者必须具有相同的类型。对于 Java,int 变量可以通过自动装箱转换为 Integer。现在由于IntegerString 都继承自Object,因此可以通过简单的缩小转换将它们转换为相同的类型。

另一方面,在 C# 中,int 是原语,不会隐式转换为 string 或任何其他 object

【讨论】:

  • 感谢您的回答。自动装箱是我目前正在考虑的。 C# 不允许自动装箱吗?
  • 我认为这是不正确的,因为在 C# 中将 int 装箱到对象是完全可能的。我相信这里的不同之处在于,C# 只是采取了一种非常宽松的方法来确定它是否被允许。
  • 这与自动装箱无关,在 C# 中也会发生这种情况。不同之处在于 C# 不会尝试找到通用的超类型,而 Java(因为只有 Java 5!)会这样做。您可以通过尝试查看如果使用 2 个自定义类(显然都从对象继承)来尝试会发生什么来轻松地测试它。还有一个从intobject 的隐式转换。
  • 再吹毛求疵:从Integer/StringObject 的转换不是缩小转换,而是完全相反:-)
  • IntegerString 也实现了 SerializableComparable 因此对它们中的任何一个的分配都可以工作,例如Comparable&lt;?&gt; c=condition? 6: "6";List&lt;? extends Serializable&gt; l = condition? new ArrayList&lt;Integer&gt;(): new ArrayList&lt;String&gt;(); 是合法的 Java 代码。
猜你喜欢
  • 2015-07-16
  • 2019-07-09
  • 1970-01-01
  • 2011-04-22
  • 2020-12-22
  • 2010-11-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多