Java 的增强型 for 循环和 .NET 的 foreach 循环的区别

6

我有一个问题,但是我没有找到答案。假设我们在Java或C#中有以下代码:

class Car {
   /* car stuff */
}

接下来,就是Java方面的内容了

class Truck extends Car {
   /* truck stuff */
}

以及 C#

class Truck : Car {
   /* truck stuff again */
}

在C#中,以下代码可以正常运行:

List<Car> carList = new List<Car>();
//add some objects to the collection
foreach(Truck t in carList)
     //do stuff with only the Truck objects in the carList collection

这是因为卡车是汽车的子类,简单来说每辆卡车也都是一辆汽车。然而,类型检查会进行,只有卡车才会从carList中被选中。
如果我们在Java中尝试同样的操作:
List<Car> carList = new ArrayList<Car>();
//add some objects to the collection
for(Truck t : carList)
     //**PROBLEM**

由于增强循环内部的代码,代码甚至无法编译。为了达到相同的效果,我们必须像这样做:

for(Car t : carList)
   if(t instanceof Car)
      //cast t to Truck and do truck stuff with it

在C#中可以轻松实现的同样的想法,在Java中需要额外的代码。即使语法几乎相同!有没有什么原因导致它在Java中无法工作?


1
在C#中,这也不是很干净的,因为每辆卡车都是一辆汽车,但并非每辆汽车都是一辆卡车... 在C#中,转换是在运行时尝试的,但您应该非常谨慎。Java似乎更加谨慎,并希望程序员首先进行检查(当然,如果您知道自己在做什么,C#版本确实会增加一些好处)。 - Florian Rappl
在C#中,每个对象的类型都会被检查。如果它是卡车,则进入foreach循环的主体,否则不会。这有什么不干净的地方?无论是我进行检查(Java)还是运行时进行检查(C#),最终结果都是相同的。 - user520288
Java泛型允许清晰地指定对象,但如果您想要一个应该包含任何汽车的列表,则可以编写类似以下内容的代码。 List<? extends Car> carList = new ArrayList<Truck>(); - Phani
@Florian Rappl,大多数程序员不知道他们在做什么,所以我怀疑从长远来看这是一个好处。Java 的设计者有一个简单的原则:尽快找到错误。在这种情况下,异常可能发生在运行时而不是编译时。Java 在编译时检查异常。 - Damian Leszczyński - Vash
1
foreach循环仅适用于卡车。如果例如在该列表中有一辆公共汽车,运行时将抛出异常。 - Damian Leszczyński - Vash
4个回答

9

事实上,只有卡车在carList中被选择,进行了类型检查。

不是这样的。如果您的列表中除了Truck之外还包含其他东西,C#会发生运行时异常。基本上,在C#中,以下内容:

foreach(Truck t in carList) {
    ...
}

表现得像
foreach(object _t in carList) {
    Truck t = (Truck)_t;        // throws an InvalidCastException if _t is not a Truck
    ...
}

相比之下,Java变体是类型安全的:您必须自己进行强制转换和类型检查。


那么,为什么Java和C#的行为不同呢?这是我的猜测:

C#在拥有泛型之前就有了foreach关键字。因此,没有可能有一个List<Car>。如果C#选择了Java的foreach方式,您将不得不编写:

foreach(object _c in myArraylistContainingOnlyCars) {
    Car c = (Car)_c;
    // do something with c
}

这很让人烦恼。另一方面,扩展的for循环和泛型在同一个版本(Java 5)中被引入,因此不需要自动转换。


那么您的意思是,如果列表中没有卡车对象,会抛出异常? - user520288
1
不,如果其中有任何非卡车对象,则会抛出异常。 - Boosty
尝试了一下,你是对的...应该好好复习一下C#,感谢你的回答! - user520288

5
在C#中,当你编写以下代码:
foreach(Truck t in carList)

编译器理解的大致是:
var enumerator = carList.GetEnumerator();

while (enumerator.MoveNext())
{
    Truck t = (Truck)enumerator.Current;
    // do stuff
}

如果carList包含非卡车汽车,则该任务将在运行时失败。

您可以尝试以下控制台应用程序以说明:

namespace ConsoleApplication1
{
    class Car
    {
    }

    class Truck: Car
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            Car[] cars = new[] { new Car(), new Truck() };

            foreach (Truck t in cars)
            {
                Console.WriteLine("1 truck");
            }

            Console.Read();
        }
    }
}

在设计语言时,C# 和 Java 编译器有着不同的选择,因此它们的行为也有所不同。有趣的是,在 C# 规范中,“尽早失败,如果可能的话,就在编译时失败”是一个常见的指导方针。但这里是一个例外情况,与我喜欢的 C# 语言相比,我更喜欢 Java 的行为。

然而,在 C# 中,linq 提供了一种简单的方法来实现您原本以为它具备的功能:

foreach (Truck t in carList.OfType<Truck>())
{
    //do stuff
}

那样,可枚举对象将被过滤,并且只有卡车才能通过循环。


2
List<Car> carList = new ArrayList<Car>();
//add some objects to the collection
for(Truck t : carList)
     //**PROBLEM**

Java泛型是在Java 5中引入的,与.NET泛型有些不同。

简而言之 - 您不能编写这样的代码 - 这是一个编译时错误。您有一组汽车列表,您尝试获取卡车列表 - 这甚至听起来就不对/

正确的方法是通过其Car接口使用Truck类。如果您无法这样做(方法不足或方法具有不同的含义),则可以在迭代之前过滤列表或重新设计您的类。


1
程序无法编译,因为您使用了卡车类而不是汽车类。
这一个不需要。
for(Car t : carList) {
    if(t instanceof Car)
        // Do stuff here
}

你可以这样做:
for(Car t : carList) {
    // Do stuff here
}

如果你这样做,它就会起作用:

List<Truck> truckList = new ArrayList<Truck>();

for(Car car : truckList) {
    // Do stuff here
}

或者这样:

List<Car> carList = new ArrayList<Car>();

for (Iterator<Car> it = carList.iterator(); it.hasNext();) {
    Truck car = (Truck) it.next();
    // Do stuff here
}

不要这样写:

List<Car> carList = new ArrayList<Car>();

for(Truck truck : carList) {
    // Do stuff here
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接