JavaRush /Java Blog /Random-JA /猫用のジェネリック医薬品
Viacheslav
レベル 3

猫用のジェネリック医薬品

Random-JA グループに公開済み
猫用ジェネリック - 1

導入

今日は、Java についての知識を思い出すのに最適な日です。最も重要な文書によると、つまり Java 言語仕様 (JLS - Java 言語仕様) の「第 4 章 型、値、および変数」の章で説明されているように、Java は厳密に型指定された言語です。これはどういう意味ですか?main メソッドがあるとします。
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
強力な型付けにより、このコードがコンパイルされるときに、コンパイラーは、テキスト変数の型を String として指定した場合に、それを別の型の変数 (たとえば、整数) として使用しようとしていないことを確認します。 。たとえば、テキストの代わりに値を保存しようとすると2L(つまり、文字列の代わりに Long を)、コンパイル時にエラーが発生します。

Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
それらの。強い型付けを使用すると、オブジェクトに対する操作がそのオブジェクトに対して正当な場合にのみ実行されるようにすることができます。これはタイプ セーフティとも呼ばれます。JLS に記載されているように、Java にはプリミティブ型と参照型という 2 つの型のカテゴリがあります。プリミティブ型については、レビュー記事「Java のプリミティブ型: それほどプリミティブではない」を思い出してください。参照型は、クラス、インターフェイス、または配列で表すことができます。そして今日は参照型に興味を持ちます。そして配列から始めましょう:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
このコードはエラーなしで実行されます。ご存知のとおり (たとえば、「Oracle Java チュートリアル: 配列」)、配列は 1 つのタイプのみのデータを格納するコンテナです。この場合は行のみです。String の代わりに Long を配列​​に追加してみましょう。
text[1] = 4L;
このコードを (たとえば、Repl.it Online Java Compilerで) 実行すると、エラーが発生します。
error: incompatible types: long cannot be converted to String
言語の配列と型安全性により、型に適合しないものを配列に保存することはできませんでした。これは型安全性の現れです。「エラーを修正してください。ただし、それまではコードをコンパイルしません。」と言われました。そして、これについて最も重要なことは、これはプログラムの起動時ではなく、コンパイル時に行われるということです。つまり、エラーは「いつか」ではなく、すぐにわかります。配列について思い出したので、Java Collections Frameworkについても思い出してみましょう。そこには異なる構造がありました。たとえば、リスト。例を書き直してみましょう。
import java.util.*;
class Main {
  public static void main(String[] args) {
    List text = new ArrayList(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(0);
  }
}
testコンパイルすると、変数初期化行でエラー が発生します。
incompatible types: Object cannot be converted to String
私たちの場合、List は任意のオブジェクト (つまり、Object 型のオブジェクト) を格納できます。したがって、編纂者は、そのような責任を負うことはできないと言っています。したがって、リストから取得する型を明示的に指定する必要があります。
String test = (String) text.get(0);
この指示は、型変換または型キャストと呼ばれます。そして、インデックス 1 の要素を取得しようとするまでは、すべてが正常に動作します。ロングタイプです。そして、かなりのエラーが発生しますが、プログラムの実行中(実行時)にすでに次のようになります。

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
ご覧のとおり、ここにはいくつかの重要な欠点があります。まず、リストから取得した値を String クラスに「キャスト」する必要があります。同意します、これは醜いです。次に、エラーが発生した場合、プログラムの実行時にのみエラーが表示されます。コードがより複雑であれば、そのようなエラーをすぐに検出できない可能性があります。そして開発者は、そのような状況での作業を容易にし、コードをより明確にする方法を考え始めました。そして彼らはジェネリック医薬品として誕生しました。
猫用ジェネリック医薬品 - 2

ジェネリック

ということで、ジェネリック。それは何ですか?ジェネリックは、使用される型を記述する特別な方法であり、コード コンパイラーが型の安全性を確保するためにその作業で使用できます。次のようになります。
猫用ジェネリック - 3
以下に短い例と説明を示します。
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> text = new ArrayList<String>(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(1);
  }
}
この例では、 だけではなく もあると言いますListList、これは String 型のオブジェクトでのみ機能します。他にはありません。括弧内に示されているものは、保存できます。このような「括弧」は「山括弧」と呼ばれます。角括弧。コンパイラは、文字列のリスト (リストの名前は text) を操作するときに、間違いがないかどうかを親切にチェックしてくれます。コンパイラは、私たちが厚かましくも Long を String リストに入れようとしていると認識します。そしてコンパイル時にエラーが発生します。
error: no suitable method found for add(long)
String が CharSequence の子孫であることを覚えているかもしれません。そして、次のようなことを行うことに決めます。
public static void main(String[] args) {
	ArrayList<CharSequence> text = new ArrayList<String>(5);
	text.add("Hello");
	String test = text.get(0);
}
しかし、これは不可能であり、 error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence> 次のエラーが表示されます。行にCharSequence sec = "test";エラーは含まれていません。それを理解しましょう。彼らはこの動作について「ジェネリックは不変である」と言います。「不変条件」とは何ですか? これについては、Wikipedia の「共分散と反分散」という記事で次のように述べられているのが気に入っています。
猫用ジェネリック医薬品 - 4
したがって、不変性とは、派生型間に継承が存在しないことを意味します。Cat が Animals のサブタイプである場合、Set<Cats> は Set<Animals> のサブタイプではなく、Set<Animals> は Set<Cats> のサブタイプではありません。ところで、Java SE 7から、いわゆる「ダイヤモンドオペレーター」が登場したことは言う価値があります。2 つの山括弧 <> がダイヤモンドに似ているためです。これにより、次のようにジェネリックを使用できるようになります。
public static void main(String[] args) {
  List<String> lines = new ArrayList<>();
  lines.add("Hello world!");
  System.out.println(lines);
}
このコードに基づいて、コンパイラは、左側でListString 型のオブジェクトが含まれることを示した場合、右側では、lines新しい ArrayList を変数に保存し、その変数にはオブジェクトも格納することを意味すると理解します。左側で指定されたタイプの。したがって、左側のコンパイラは右側の型を理解または推測します。このため、この動作は型推論、または英語で「Type Inference」と呼ばれます。注目に値するもう 1 つの興味深い点は、RAW タイプまたは「生のタイプ」です。なぜなら ジェネリックスは常に存在していたわけではなく、Java は可能な限り下位互換性を維持しようとしますが、ジェネリックスは、ジェネリックスが指定されていないコードでも何らかの形で動作することを余儀なくされます。例を見てみましょう:
List<CharSequence> lines = new ArrayList<String>();
覚えているとおり、ジェネリックの不変性のため、そのような行はコンパイルされません。
List<Object> lines = new ArrayList<String>();
同じ理由で、これもコンパイルできません。
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
このような行はコンパイルされ、機能します。Raw タイプが使用されるのはそれらの中でです。不特定のタイプ。繰り返しになりますが、Raw 型は最新のコードでは使用すべきではないことを指摘する価値があります。
猫用ジェネリック - 5

型付きクラス

つまり、型付きクラスです。独自の型付きクラスを作成する方法を見てみましょう。たとえば、次のようなクラス階層があります。
public static abstract class Animal {
  public abstract void voice();
}

public static class Cat extends Animal {
  public void voice(){
    System.out.println("Meow meow");
  }
}

public static class Dog extends Animal {
  public void voice(){
    System.out.println("Woof woof");
  }
}
動物コンテナを実装するクラスを作成したいと思います。任意の を含むクラスを作成することは可能ですAnimal。これは単純で理解できますが、犬と猫を混ぜるのはよくありません。犬と猫はお互いに友達ではありません。さらに、そのような容器を受け取った人は、誤って猫を容器から犬の群れの中に投げ込む可能性があります...これは何の良いこともありません。そしてここでジェネリック医薬品が役に立ちます。たとえば、次のように実装を書いてみましょう。
public static class Box<T> {
  List<T> slots = new ArrayList<>();
  public List<T> getSlots() {
    return slots;
  }
}
私たちのクラスは、T という名前のジェネリックで指定された型のオブジェクトを処理します。これは一種のエイリアスです。なぜなら ジェネリックはクラス名で指定されており、クラスを宣言するときにそれを受け取ります。
public static void main(String[] args) {
  Box<Cat> catBox = new Box<>();
  Cat murzik = new Cat();
  catBox.getSlots().add(murzik);
}
ご覧のとおり、 があることを示しましたがBox、これは でのみ機能しますCat。コンパイラーは、ジェネリックの名前が指定されているcatBox場合は、ジェネリックの代わりにT型を置き換える必要があることを認識しました。 CatT
猫用ジェネリック - 6
それらの。実際にどうあるべきBox<Cat>かをコンパイラが理解できるのは、コンパイラのおかげです。内部には があり、 が含まれます。型宣言には複数のジェネリックスを含めることができます。次に例を示します。 slotsList<Cat>Box<Dog>slotsList<Dog>
public static class Box<T, V> {
ジェネリックの名前は何でも構いませんが、いくつかの暗黙のルールに従うことをお勧めします - 「型パラメータの命名規則」: 要素型 - E、キー型 - K、数値型 - N、T - 型、V - 型値のタイプ。ところで、ジェネリックは不変であると述べたことを思い出してください。継承階層を保持しません。実際、私たちはこれに影響を与えることができます。つまり、ジェネリックを COvariant にする機会があります。継承を同じ順序で維持します。この動作は「境界型」と呼ばれます。限られた種類。たとえば、クラスにBoxすべての動物を含めることができる場合、次のようにジェネリックを宣言します。
public static class Box<T extends Animal> {
つまり、クラスに上限を設定しますAnimal。キーワードの後に​​複数のタイプを指定することもできますextends。これは、これから扱う型が何らかのクラスの子孫であると同時に、何らかのインターフェイスを実装している必要があることを意味します。例えば:
public static class Box<T extends Animal & Comparable> {
Boxこの場合、継承子ではなくAnimal、 を実装していないものを such に 入れようとするとComparable、コンパイル中にエラーが発生します。
error: type argument Cat is not within bounds of type-variable T
猫用ジェネリック - 7

入力方法

ジェネリックは型だけでなく、個々のメソッドでも使用されます。メソッドの適用については、公式チュートリアル「Generics Methods」でご覧いただけます。

背景:

猫用ジェネリック - 8
この写真を見てみましょう。ご覧のとおり、コンパイラはメソッド シグネチャを調べ、未定義のクラスを入力として取得していることを確認します。署名によっては、ある種のオブジェクトを返すかどうかは判断されません。物体。したがって、たとえば ArrayList を作成したい場合は、次のようにする必要があります。
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
出力が ArrayList になることを明示的に記述する必要がありますが、これは見苦しく、間違いを犯す可能性が高くなります。たとえば、このようなナンセンスな内容を書くと、コンパイルされます。
ArrayList object = (ArrayList) createObject(LinkedList.class);
コンパイラを手伝ってもらえますか? はい、ジェネリックを使用するとこれが可能になります。同じ例を見てみましょう。
猫用ジェネリック - 9
次に、次のようにしてオブジェクトを簡単に作成できます。
ArrayList<String> object = createObject(ArrayList.class);
猫用ジェネリック - 10

ワイルドカード

Oracle のジェネリックに関するチュートリアル、特に「ワイルドカード」セクションによると、疑問符と呼ばれる疑問符を使用して「不明な型」を記述することができます。ワイルドカードは、ジェネリックの制限の一部を軽減する便利なツールです。たとえば、前に説明したように、ジェネリックは不変です。これは、すべてのクラスは Object 型の子孫 (サブタイプ) ですが、List<любой тип>サブタイプではないことを意味しますList<Object>。ただし、List<любой тип>それはサブタイプですList<?>。したがって、次のコードを書くことができます。
public static void printList(List<?> list) {
  for (Object elem: list) {
    System.out.print(elem + " ");
  }
  System.out.println();
}
通常のジェネリック (つまり、ワイルドカードを使用しない) と同様に、ワイルドカードを使用したジェネリックも制限できます。上限付きワイルドカードは見覚えのあるものです。
public static void printCatList(List<? extends Cat> list) {
  for (Cat cat: list) {
    System.out.print(cat + " ");
  }
  System.out.println();
}
ただし、下限ワイルドカードによって制限することもできます。
public static void printCatList(List<? super Cat> list) {
したがって、メソッドはすべての猫だけでなく、より上位の階層 (オブジェクトまで) も受け入れるようになります。
猫用ジェネリック - 11

タイプ消去

ジェネリックについて言えば、「型消去」について知っておく価値があります。実際、型の消去は、ジェネリックがコンパイラのための情報であるという事実に関係しています。プログラムの実行中、ジェネリックに関する情報はなくなります。これは「消去」と呼ばれます。この消去により、ジェネリック型が特定の型に置き換えられるという効果があります。ジェネリックに境界がない場合は、オブジェクト型が置き換えられます。境界線が指定されている場合 (たとえば<T extends Comparable>、 )、それが置き換えられます。Oracle のチュートリアル「ジェネリック型の消去」の例を次に示します。
猫用ジェネリック - 12
上で述べたように、この例ではジェネリックはTその境界線まで消去されます。前にComparable
猫用ジェネリック - 13

結論

ジェネリックは非常に興味深いテーマです。このトピックに興味を持っていただければ幸いです。要約すると、ジェネリックスは、開発者がコンパイラーに追加情報を要求して、一方では型の安全性を確保し、他方では柔軟性を確保するための優れたツールであると言えます。興味があれば、私が気に入ったリソースをチェックすることをお勧めします。 #ヴィアチェスラフ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION