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

这是因为 Truck 是 Car 的子类,简单来说,这意味着每辆 Truck 也是 Car。问题是,类型检查已完成,并且仅从 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中不起作用有什么原因吗?

4

4 回答 4

9

问题是,类型检查已完成,并且仅从 carList 中选择了卡车。

不,不是。如果您的列表包含除Trucks 之外的任何内容,则 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)的 Java 中引入的,因此不需要自动转换。

于 2012-04-11T07:31:48.323 回答
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
}

这样,枚举将被过滤,只有卡车才能进入循环。

于 2012-04-11T07:38:26.263 回答
2
List<Car> carList = new ArrayList<Car>();
//add some objects to the collection
for(Truck t : carList)
     //**PROBLEM**

Java 5 中引入的 Java 泛型与 .NET 泛型有点不同。

长话短说——你不能写这样的代码——这是一个编译时错误。你有一个汽车清单,你试图得到一个卡车清单——这听起来甚至是错误的/

正确的方法是通过其接口使用Truck该类。Car如果你不能(没有足够的方法或方法有不同的含义),那么你可以过滤列表(在迭代之前)或者..重新设计你的类

于 2012-04-11T07:45:22.293 回答
1

该程序将不会编译,因为您使用的是 Truck 类而不是 Car。

不需要这个

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
}
于 2012-04-11T07:50:28.207 回答