ClassCastExceptionsを回避するためにJavaジェネリックを使用する方法

Java 5は、ジェネリックスをJava言語にもたらしました。この記事では、ジェネリックスを紹介し、ジェネリック型、ジェネリックメソッド、ジェネリックと型推論、ジェネリック論争、ジェネリックとヒープ汚染について説明します。

ダウンロードコードを入手するこのJava101チュートリアルの例のソースコードをダウンロードします。JavaWorld用にJeffFriesenによって作成されました。

ジェネリックとは何ですか?

ジェネリックスは、コンパイル時の型の安全性を提供しながら、型またはメソッドがさまざまな型のオブジェクトを操作できるようにする関連言語機能のコレクションです。ジェネリックス機能java.lang.ClassCastExceptionは、実行時にスローされる問題に対処します。これは、タイプセーフではないコードの結果です(つまり、オブジェクトを現在のタイプから互換性のないタイプにキャストします)。

ジェネリックスとJavaコレクションフレームワーク

ジェネリックスは、Javaコレクションフレームワーク(将来のJava 101の記事で正式に紹介されます)で広く使用されていますが、それだけではありません。ジェネリック医薬品も含め、Javaの標準クラスライブラリの他の部分で使用されているjava.lang.Classjava.lang.Comparablejava.lang.ThreadLocal、とjava.lang.ref.WeakReference

java.util.LinkedListジェネリックスが導入される前のJavaコードで一般的だった(Javaコレクションフレームワークのクラスのコンテキストでの)型安全性の欠如を示す次のコードフラグメントについて考えてみます。

リストdoubleList = new LinkedList(); doubleList.add(new Double(3.5)); Double d =(Double)doubleList.iterator()。next();

上記のプログラムのjava.lang.Double目的はリスト内のオブジェクトのみを格納することですが、他の種類のオブジェクトの格納を妨げるものは何もありません。たとえばdoubleList.add("Hello");java.lang.Stringオブジェクトを追加するように指定できます。ただし、別の種類のオブジェクトを格納する場合、非オブジェクトに直面すると、最終行の(Double)キャスト演算子ClassCastExceptionがスローされDoubleます。

この型安全性の欠如は実行時まで検出されないため、開発者は問題に気付かず、(コンパイラではなく)クライアントに問題を発見させる可能性があります。ジェネリックスDoubleは、開発者がリストにDoubleオブジェクトのみを含むものとしてマークを付けることを許可することにより、コンパイラーが非型のオブジェクトをリストに格納する問題について開発者に警告するのに役立ちます。この支援を以下に示します。

リストdoubleList = new LinkedList(); doubleList.add(new Double(3.5)); Double d = doubleList.iterator()。next();

ListListof 」と表示されDoubleます。 Listは、として表される汎用インターフェイスでありListDouble型引数を取ります。これは、実際のオブジェクトを作成するときにも指定されます。コンパイラは、オブジェクトをリストに追加するときに型の正確性を強制できるようになりました。たとえば、リストにはDouble 値のみを格納できます。この施行により、(Double)キャストの必要がなくなります。

ジェネリック型の発見

ジェネリック型は、を介してパラメータ化されたタイプのセットを導入するクラスまたはインタフェースである仮型パラメータリスト角括弧の対の間の型パラメータ名のカンマ区切りのリストです。ジェネリック型は次の構文に従います。

クラス識別子< formalTypeParameterList > {//クラス本体}インターフェイス識別子< formalTypeParameterList > {//インターフェイス本体}

Javaコレクションフレームワークは、ジェネリック型とそのパラメーターリストの多くの例を提供します(そして、この記事全体でそれらを参照します)。たとえば、java.util.Setはジェネリック型、  はその正式な型パラメータリスト、E はリストの単独型パラメータです。別の例は java.util.Mapです。

Javaタイプパラメータの命名規則

Javaプログラミング規則では、型パラメーター名はE、要素、Kキー、V値、T型などの単一の大文字である必要があります。可能であれば、次のような意味のない名前の使用は避けてくださいPjava.util.Listは要素のリストを意味しますが、どういう意味でしょうかList

A parameterized type is a generic type instance where the generic type’s type parameters are replaced with actual type arguments (type names). For example, Set is a parameterized type where String is the actual type argument replacing type parameter E.

The Java language supports the following kinds of actual type arguments:

  • Concrete type: A class or other reference type name is passed to the type parameter. For example, in List, Animal is passed to E.
  • Concrete parameterized type: A parameterized type name is passed to the type parameter. For example, in Set , List is passed to E.
  • Array type: An array is passed to the type parameter. For example, in Map, String is passed to K and String[] is passed to V.
  • Type parameter: A type parameter is passed to the type parameter. For example, in class Container { Set elements; }, E is passed to E.
  • Wildcard: The question mark (?) is passed to the type parameter. For example, in Class, ? is passed to T.

Each generic type implies the existence of a raw type, which is a generic type without a formal type parameter list. For example, Class is the raw type for Class. Unlike generic types, raw types can be used with any kind of object.

Declaring and using generic types in Java

Declaring a generic type involves specifying a formal type parameter list and accessing these type parameters throughout its implementation. Using the generic type involves passing actual type arguments to its type parameters when instantiating the generic type. See Listing 1.

Listing 1:GenDemo.java (version 1)

class Container { private E[] elements; private int index; Container(int size) { elements = (E[]) new Object[size]; index = 0; } void add(E element) { elements[index++] = element; } E get(int index) { return elements[index]; } int size() { return index; } } public class GenDemo { public static void main(String[] args) { Container con = new Container(5); con.add("North"); con.add("South"); con.add("East"); con.add("West"); for (int i = 0; i < con.size(); i++) System.out.println(con.get(i)); } }

Listing 1 demonstrates generic type declaration and usage in the context of a simple container type that stores objects of the appropriate argument type. To keep the code simple, I’ve omitted error checking.

The Container class declares itself to be a generic type by specifying the formal type parameter list. Type parameter E is used to identify the type of stored elements, the element to be added to the internal array, and the return type when retrieving an element.

The Container(int size) constructor creates the array via elements = (E[]) new Object[size];. If you’re wondering why I didn’t specify elements = new E[size];, the reason is that it isn’t possible. Doing so could lead to a ClassCastException.

Compile Listing 1 (javac GenDemo.java). The (E[]) cast causes the compiler to output a warning about the cast being unchecked. It flags the possibility that downcasting from Object[] to E[] might violate type safety because Object[] can store any type of object.

Note, however, that there is no way to violate type safety in this example. It’s simply not possible to store a non-E object in the internal array. Prefixing the Container(int size) constructor with @SuppressWarnings("unchecked") would suppress this warning message.

Execute java GenDemo to run this application. You should observe the following output:

North South East West

Bounding type parameters in Java

The E in Set is an example of an unbounded type parameter because you can pass any actual type argument to E. For example, you can specify Set, Set, or Set.

Sometimes you’ll want to restrict the types of actual type arguments that can be passed to a type parameter. For example, perhaps you want to restrict a type parameter to accept only Employee and its subclasses.

You can limit a type parameter by specifying an upper bound, which is a type that serves as the upper limit on the types that can be passed as actual type arguments. Specify the upper bound by using the reserved word extends followed by the upper bound’s type name.

For example, class Employees restricts the types that can be passed to Employees to Employee or a subclass (e.g., Accountant). Specifying new Employees would be legal, whereas new Employees would be illegal.

You can assign more than one upper bound to a type parameter. However, the first bound must always be a class, and the additional bounds must always be interfaces. Each bound is separated from its predecessor by an ampersand (&). Check out Listing 2.

Listing 2: GenDemo.java (version 2)

import java.math.BigDecimal; import java.util.Arrays; abstract class Employee { private BigDecimal hourlySalary; private String name; Employee(String name, BigDecimal hourlySalary) { this.name = name; this.hourlySalary = hourlySalary; } public BigDecimal getHourlySalary() { return hourlySalary; } public String getName() { return name; } public String toString() { return name + ": " + hourlySalary.toString(); } } class Accountant extends Employee implements Comparable { Accountant(String name, BigDecimal hourlySalary) { super(name, hourlySalary); } public int compareTo(Accountant acct) { return getHourlySalary().compareTo(acct.getHourlySalary()); } } class SortedEmployees
    
      { private E[] employees; private int index; @SuppressWarnings("unchecked") SortedEmployees(int size) { employees = (E[]) new Employee[size]; int index = 0; } void add(E emp) { employees[index++] = emp; Arrays.sort(employees, 0, index); } E get(int index) { return employees[index]; } int size() { return index; } } public class GenDemo { public static void main(String[] args) { SortedEmployees se = new SortedEmployees(10); se.add(new Accountant("John Doe", new BigDecimal("35.40"))); se.add(new Accountant("George Smith", new BigDecimal("15.20"))); se.add(new Accountant("Jane Jones", new BigDecimal("25.60"))); for (int i = 0; i < se.size(); i++) System.out.println(se.get(i)); } }
    

Listing 2’s Employee class abstracts the concept of an employee that receives an hourly wage. This class is subclassed by Accountant, which also implements Comparable to indicate that Accountants can be compared according to their natural order, which happens to be hourly wage in this example.

The java.lang.Comparable interface is declared as a generic type with a single type parameter named T. This interface provides an int compareTo(T o) method that compares the current object with the argument (of type T), returning a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.

The SortedEmployees class lets you store Employee subclass instances that implement Comparable in an internal array. This array is sorted (via the java.util.Arrays class’s void sort(Object[] a, int fromIndex, int toIndex) class method) in ascending order of the hourly wage after an Employee subclass instance is added.

Compile Listing 2 (javac GenDemo.java) and run the application (java GenDemo). You should observe the following output:

George Smith: 15.20 Jane Jones: 25.60 John Doe: 35.40

Lower bounds and generic type parameters

You cannot specify a lower bound for a generic type parameter. To understand why I recommend reading Angelika Langer’s Java Generics FAQs on the topic of lower bounds, which she says “would be confusing and not particularly helpful.”

Considering wildcards

Let’s say you want to print out a list of objects, regardless of whether these objects are strings, employees, shapes, or some other type. Your first attempt might look like what’s shown in Listing 3.

Listing 3: GenDemo.java (version 3)

import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class GenDemo { public static void main(String[] args) { List directions = new ArrayList(); directions.add("north"); directions.add("south"); directions.add("east"); directions.add("west"); printList(directions); List grades = new ArrayList(); grades.add(new Integer(98)); grades.add(new Integer(63)); grades.add(new Integer(87)); printList(grades); } static void printList(List list) { Iterator iter = list.iterator(); while (iter.hasNext()) System.out.println(iter.next()); } }

It seems logical that a list of strings or a list of integers is a subtype of a list of objects, yet the compiler complains when you attempt to compile this listing. Specifically, it tells you that a list-of-string cannot be converted to a list-of-object, and similarly for a list-of-integer.

The error message you've received is related to the fundamental rule of generics: