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で動作しない理由はありますか?

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**

Java5で導入されたJavaGenericsは、.NETGenericsとは少し異なります。

簡単に言えば、このようなコードを書くことはできません。これはコンパイル時のエラーです。あなたは車のリストを持っていて、トラックのリストを取得しようとしています-それは間違っているようにさえ聞こえます/

正しいアプローチは、そのインターフェースTruckを介してクラスを使用することです。Carできない場合(十分なメソッドまたはメソッドの意味が異なる)は、リストを(反復する前に)フィルタリングするか、クラスを再設計することができます。

于 2012-04-11T07:45:22.293 に答える
1

Carの代わりにTruckクラスを使用しているため、プログラムはコンパイルされません。

これは必要ありません

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 に答える