Javaの例外

Java本格入門シリーズ。



今回は例外について読んだので、それのメモ。


例外は大きく3つ

1.検査例外(チェック例外) - Exception

2.実行時例外(非チェック例外) - RuntimeException

3.エラー(Error)




例外を表す3つのクラス

java.lang.Exceptionクラス

検査例外をあらわすクラス

このクラスを継承した例外は

・プログラム中で捕捉する(try~catch)

・発生するメソッド宣言でthrows節を記述する



throws宣言することで、どのメソッドでどんな例外が発生するのかがわかりやすくなったり、例外処理を書かないとコンパイルエラーになるので、例外の見逃しがなくなる。



一方で、tyr~catchブロックが必須になったりと、プログラムがやや重厚になりやすいので賛否両論ある。

java.lang.RuntimeExceptionクラス

実行時の例外をあらわすクラス。Exceptionクラスを継承している。



・プログラム中に捕捉する必要なし(try~catchが必須ではない)

・throws宣言も必須ではない。



捕捉しなかったら、Java VMに到達した時に「そのスレッドが終了する」


java.lang.Errorクラス

基本、アプリケーションでは捕捉すべきでない重大な問題

を示すクラス。



try~catchもthrowsも必須ではない。



だいたいアプリケーションに異常状態になっているので、捕捉せずにプログラムを終了させるべき。



例として、OutOfMemoryErrorクラス(メモリ不足)があるが、この状態でアプリケーションをそのままにしておくのはよろしくない。


try~catch=finally

try{

  //SomeException例外が発生する可能性のあるコードを含む処理

} catch(SomeException ex){

  //SomeException例外をcatchした時の処理

} finally {

  //try~catchブロックが終了したら必ず実行するべき処理

}

try~with-resources

try(InputStream is = File.newInputStream(Path)){

  //is.read(contens)

  //contensに対する処理。

} catch(IOException ex){

  //例外を捕捉した場合の処理。

}


マルチキャッチ

発生する例外ごとに処理をわけたい場合は複数のcatchを書いて処理できる。

try{

  //例外Aあるいは例外Bが発生する可能性のある処理。

} catch(例外A){

  //例外Aが発生した時の処理

} catch(例外B){

  //例外Bが発生した時の処理

}

複数例外をまとめることもできる。

try{

  //例外Aあるいは例外Bあるいは例外Cが発生する可能性のある処理。

} catch (例外A) {

  //例外Aが起きた時の処理

} catch (例外B |

         例外C) {

  //例外Bあるいは例外Cが起きた時の処理

}


例外処理のポイント

・エラーコードをreturnしない
javaは標準で例外機構を持っているので、エラーが発生したら例外を発生させること
正常に終了したらオブジェクトをreturnすればいい。
成功も失敗もreturnすると呼び出し元で条件分岐処理などが必要になってくる。
(もともとIsXxxなどの判定処理を意図したメソッドなら判定自体が成功したということでthrowしない方がいいケースもある)


・例外を揉み消さない。
catchブロックの中に何も書いていないケースがあったりするが、それはNG。
例外が起きたことすら気づかず、重大な問題を起こしかねない。

1.ログ出力をする
スタックトレースまで残すように

2.処理を継続するか判断する
処理を継続させた時に、次の処理でオブジェクトがnullになっていてぬるぽを発生させる可能性があったりするので、
基本的には例外が発生したら後続処理は中止させ、復旧or上位メソッドにthrowするべき。




thorws Exceptiionは悲劇の元

1.呼び出し元でExceptionを捕捉しないといけなくなる
メソッド呼び出しの階層が深くなると、上位の呼び出し元は
「なぜ例外を捕捉しなければいけないのかがわからない」状態になる

2.途中でIOExceptionなどの具体的な例外が発生しても Exceptionに巻き込まれしまう。
Exceptionは基底クラスなので、Exceptionでそのまま宣言できる。

そうなるとどんな例外が発生するのかは、コードを読んだだけではわからなくなる。



3.途中でRuntimeExceptionが発生しても、Exceptionに巻き込まれてしまう。

実行時例外(必ずしも捕捉する必要はない)でも捕捉しないといけなくなってしまう。
検査例外だと思って処理してしまう可能性がある。
重要な実行時例外を見逃してしまう可能性がある。



その他メモ

・例外が発生する(可能性がある)箇所。いわゆる末端箇所では「例外を発生させるだけ」に留めた方がいい

・処理の流れを判断する箇所で例外を捕捉するのが良い。

独自例外を作るポイントとして
・業務に特化した処理
フレームワークやシステムで共通的な例外処理をする場合
などになる。

ラムダ式で発生した例外はラムダ式の中で処理する(中にtry~catchを書く)

Stream API②

Stream API①の続き

Streamの終端操作

繰り返し処理

forEach

引数はConsumerインターフェース。

結果をまとめて取り出す終端操作

collect
要素を走査して結果を作成する。

        List<String> list = Arrays.asList("watanave", "hayashi", "tsuda", "seki", "sakai");

        //長さが5以上の要素に絞り、toListで戻り値をListにして返却。
        List<String> newList = list.stream()
            .filter(n -> n.length() > 5)
            .collect(Collectors.toList());

toList以外にも
toSet, joining, groupingByがある

・groupingBy
public class Main {
    
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();

        students.add(new Student("yamada", 100));
        students.add(new Student("harada", 50));
        students.add(new Student("gouda", 87));

        //キーに点数、値に対応する生徒のオブジェクトが入ったListでグルーピング
        Map<Integer, List<Student>> map = students.stream()
            .collect(Collectors.groupingBy(Student::getScore));

        //点数(キー)を元に任意の生徒を取り出す。
        List<Student> perfects = map.get(100);
        perfects.forEach(s -> System.out.println(s.getName()));
        //→ yamada

    }
}

toArray

全ての要素を配列にする。

        Stream<String> stream1 = Stream.of("a", "b", "c");
        Stream<Integer> stream2 = Stream.of(1,2,3,4);
        //streamからString型の配列に変換
        String[] strArr = stream1.toArray(String[]::new);
        //streamからInteger型の配列に変換
        Integer[] intArr = stream2.toArray(Integer[]::new);

reduce

値の集約処理をする。
引数はBInaryOperator
戻り値はOptional

public class Main {
    
    public static void main(String[] args) {
    // Integerのストリームを作成
    Stream<Integer>stream = Arrays.stream(new Integer[] { 1, 2, 3, 4});

    // reduceの戻り値はOptionalなので、Optionalでラッブする。
    Optional<Integer> total = stream.reduce((accum, value)->accum + value);
    
    System.out.println(total.get());    
    //→ 10
    }
}

まとめ

後半ちょっとだれて端折ってしまった部分があるが、大体わかった。
Qiitaで詳しく書かれた記事があったので、載せておく
qiita.com

Stream API①

Java本格入門のStreamAPIのところを読んだので、それのメモ。


StreamAPIは大量データを逐次処理する「ストリーム処理」を効率的に記述するための手段。

作成:コレクションや配列からStreamを作成

中間操作:StreamからStreamを作成

終端操作:Streamからコレクションや配列への変換、要素の処理や集計など

という流れで処理を行える。


for文やwhile文はStreamに置き換えていくのがよい(可読性が失われない範囲で)

Streamの作成

・ListやSetから
        //listを作成
        List<Integer> list = Arrays.asList(1,2,3,4);

        //作成したlistをstreamに変換し、メソッドチェーンで終端操作につなげて出力。
        list.stream().forEach(System.out::println);
        →1 2 3 4(実際には縦に表示される)
・配列から
        //配列を作成
        Integer[] array = {1,2,3,4,5,6};

        //ArraysクラスからStreamを作成し、メソッドチェーンで終端操作につなげて出力
        Arrays.stream(array).forEach(System.out::println);
        →1 2 3 4

配列の中身が決まっているなら、StreamのofメソッドでStreamを作れる。

        //ofメソッドの中にArrays.sreamがある
        Stream<Integer> stream = Stream.of(1,2,3,4);
        stream.forEach(System.out::println);
        →1 2 3 4
・Mapから
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "yamada");
        map.put(2, "tanaka");
        map.put(3, "sasaki");
        
        //entrySet()でSetを取得し、SetからStreamを作成、
        //そのままメソッドチェーンで終端操作につなげて出力。
        //キーと値の取得
        map.entrySet().stream()
        .forEach(e -> System.out.println(e.getKey() +  ":" + e.getValue()));
        // 1:yamada
        // 2:tanaka
        // 3:sasaki

        //キーの取得(上記のe.getKey()以外のやり方)
        map.keySet().stream()
        .forEach(System.out::println);
        // 1
        // 2
        // 3

        //値の取得(上記のe.getValue()以外のやり方)
        map.values().stream()
        .forEach(System.out::println);
        // yamada
        // tanaka
        // sasaki
・数値範囲から

開始と終了を指定して、範囲で数値を扱えるIntStreamというものがある。

        //rangeはfor(int i=1; i<5; i++)と同じ
        IntStream.range(1, 5)
        .forEach(System.out::println);
        // 1
        // 2
        // 3
        // 4

        //rangeClosedはfor(int i=1; i<=5; i++)と同じ
        IntStream.rangeClosed(1, 5)
        .forEach(System.out::println);
        // 1
        // 2
        // 3
        // 4
        // 5

       //「n回処理する」という場合はこう書ける。
       // for文より読みやすい!
        int count = 3;
        IntStream.range(1, count)
        .forEach(System.out::println);
        // 1
        // 2

Stremの中間操作

要素を置き換える中間操作

メソッドにmapという文字が入っている中間操作は要素を置き換えることを目的としている。

・map
要素を別の値に置き換える。
戻り値はStream<変換後の型>

・mapToInt
要素をint値に置き換える。
戻り値はintStream


他にもmapToDouble, mapToLong, flatMapなどがある。



Student.java

public class Student {
    
    private String name;
    private int score;

    public Student(String name, int score){
        this.name = name;
        this.score = score;
    }

    public String getName(){
        return name;
    }

    public int getScore(){
        return score;
    }

    public String toString(){
        return name + ":" + score;
    }
}

Main.java

public class Main {
    
    public static void main(String[] args) {
        
        List<Student> students = new ArrayList<>();
        students.add(new Student("yamada", 100));
        students.add(new Student("sasaki", 90));
        students.add(new Student("yoyogi", 67));

        //mapでスコアを取り出し(メソッド参照)、出力している。
        students.stream()
            .map(Student::getScore)
            .forEach(System.out::println);
    }
}
flatMap(複数StudentをGroupでまとめた時)

Goup.java

public class Group {
    public List<Student> students = new ArrayList<>();

    public void add(Student student){
        students.add(student);
    }

    public List<Student> getStudents(){
        return students;
    }
}

Main.java

public class Main {
    
    public static void main(String[] args) {
        List<Group> groups = new ArrayList<>();

        Group group1 = new Group();
        group1.add(new Student("yamada", 100));
        group1.add(new Student("gouda", 30));
        groups.add(group1);

        Group group2 = new Group();
        group2.add(new Student("takahashi", 89));
        group2.add(new Student("sigime", 24));
        groups.add(group2);

        //通常のmapで二つのグループをまとめる。
        //戻り値がListなので、次の処理単位はListになる
        Stream<List<Student>> mappedStream = groups.stream()
            .map(g -> g.getStudents());

        //flatMapで二つのグループをまとめる。
        //戻り値はStudent(group1とgroup2がまとまっている)で、次の処理単位はStudentになる
        Stream<Student> flatMappedStream = groups.stream()
            .flatMap(g -> g.getStudents().stream());


        //-------スコア順にstudentを並べる処理-----------

        //flatMapを使わない場合
        //group1とgroup2をまとめるために、Listを用意しておかないといけない
        List<Student> allStudents = new ArrayList<>();
        groups.stream()
            .forEach(g -> allStudents.addAll(g.getStudents()));

        allStudents.stream()
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .forEach(s -> System.out.println(s.getName() + ":" +s.getScore()));

        //flatMapを使う場合
        //group1とgroup2をまとめたList型の変数を用意しなくていい。
        groups.stream()
            .flatMap(g -> g.getStudents().stream())
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .forEach(s -> System.out.println(s.getName() + ":" +s.getScore()));
    }
}

要素を絞り込む中間操作

・filter - 条件に合致した要素のみに絞り込む
・limit - 指定した件数に絞り込む
・distinct - ユニークな要素のみに絞り込む

public class Main {
    
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();

        students.add(new Student("yamada", 100));
        students.add(new Student("harada", 50));
        students.add(new Student("gouda", 87));

        //filterを使って70以上に絞り込む
        students.stream()
            .filter(s -> s.getScore() > 80)
            .forEach(s -> System.out.println(s.getName()));
        // yamada
        // gouda

        //limitを使って2件だけに絞り込む
        students.stream()
            .limit(2)
            .forEach(System.out::println);
        // yamada:100
        // harada:50

        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(2);

        //distinctを使ってユニークな要素のみに絞り込む。(重複が除外される)
        numbers.stream()
            .distinct()
            .forEach(System.out::println);
        // 1
        // 2
        // 3
    }
}

要素を並べかえる中間操作

・sorted

Comparator(2つの引数を受けて、intを返すオブジェクト)を渡す。

public class Main {
    
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();

        students.add(new Student("yamada", 100));
        students.add(new Student("harada", 50));
        students.add(new Student("gouda", 87));

        //scoreが高い順に並べる
        students.stream()
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .forEach(s -> System.out.println(s.getName() + ":" + s.getScore()));
        // yamada:100
        // gouda:87
        // harada:50
    }
}

まとめ

Sreamは慣れれば簡単だし便利だと思うけど、書き方によっては可読性が下がりそう。
長くなってしまったので、後半は次に回す。

JavaのQueue

Java本格入門を読んでいたら、Queueを実現できるインターフェースがあった。

Queue(キュー)とは

先入れ先出しで値を出し入れするコレクションのこと。

スレッドセーフなので、マルチスレッドを考慮する必要がある。

public class Main {
    
    public static void main(String[] args) {
        //初期化
        Queue<Integer> q = new ArrayBlockingQueue<>(3);

        //値の追加
        q.offer(3);
        q.offer(1);
        q.offer(3);
        q.offer(5);
        System.out.println(q);
        //-> [1, 1, 3]
        //初期化の時にサイズを3にしたので、5は入らない。
        //値の重複はOK

        // 値の取得(キュー内からは削除)
        System.out.println(q.poll());
        //-> 3
        System.out.println(q);
        //-> [1, 3]

        //値の取得(キュー内から削除しない)
        System.out.println(q.peek());
        //-> 1
        System.out.println(q);
        //-> [1, 3]
    }
}
まとめ

はじめて見た。
使い所はわからないが、便利そう。
ただ、マルチスレッドの際にいろいろ考慮しないと事故りそうな気がする。

これからの仕事の中でどのようにQueueが使われているか見ていきたい。

JavaのSet

Java本格入門でSetについて読んだので、それのメモ。

Set

値の集合を扱うことができるインターフェース

List同様、要素を追加するaddメソッドがあるが、要素を取得するgetはない。

値を扱うインターフェースであるため、特定の要素の取得もできない

重複する要素を持たない

Setの内部にはMapが存在し、Setに追加された要素は内部のMapのキーとして保持される(Mapのキーは重複NG)

Setの基本的な使い方

・初期化

Set<Integer> Set = new HashSet<>();


・コレクションをSetに変換した時、

public class Main {
    
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();

        List<Integer> list = Arrays.asList(1,2,3,3,4,1,5);
        System.out.println(list);
        //-> [1, 2, 3, 3, 4, 1, 5]

        //ListをSetに変換
        Set<Integer> set2 = new HashSet<>(list);
        System.out.println(set2);
        //-> [1, 2, 3, 4, 5]
    }
}

Setに変換した際は重複は除外される。

配列から直接Setに変換はできないので、

配列→List→Set

という流れで変換しないといけない。


・基本的な処理

public class Main {
    
    public static void main(String[] args) {
        Set<Integer> numbers = new HashSet<>();

        //値の追加
        numbers.add(1);
        numbers.add(24);
        numbers.add(3);
        numbers.add(12);
        numbers.add(6);
        System.out.println(numbers);
        //-> [1, 3, 6, 24, 12]
        
        //値の上書き
        numbers.add(3);
        System.out.println(numbers);
        //-> [1, 3, 6, 24, 12]
        // 3が重複するのではなく、上書かれる

        numbers.remove(12);
        System.out.println(numbers);
        //-> [1, 3, 6, 24]

        int size = numbers.size();
        System.out.println(size);
        //-> 4

        boolean existValue = numbers.contains(24);
        System.out.println(existValue);
        //-> true
    }
}
Setの実装クラス

・HashSet(java.util.HashSet)
値からハッシュ値を計算して、内部のハッシュ テーブルという表に値を格納して要素を管理する。
追加した順に要素が保持されない。

スレッドセーフではないので、
synchronizedを使用して排他制御を行う
ConcurrentHashMapからSetを作成してスレッドセーフにする
などの対応が必要。


・LinkedHashSet(java.util.LinkedHashSet)
HashSetのサブクラス。
LinkedListのように、要素自信が前後情報を持つ。
追加した順番に要素が保持される。
要素の列挙はHashSetよりもこっちのほうが高速。


・TreeSet
キーの値を元に、2分探索木のアルゴリズムで要素をソートするクラス。
要素の追加時にソートされて保持される。

「〇〇以上〇〇以下のキーを持つ要素の取得」
「〇〇より大きな、もっとも近いキーを持つ要素を取得」
などのように、大小を意識した値の取得ができる。

まとめ

重複のない要素の集合を扱えることがわかったが、具体的な使い所に関してはいまいちわからず、、、

要素の集合から任意の値を使って何かをするというよりは、
要素の集合に対して、存在するかどうかだったり、サイズの確認などを使ってロジックに利用するのだろうか。。。

普段の仕事でSetの使われ方を意識して見て行こうと思う。

JavaのMap

Java本格入門のmapの部分を読んだので、それのメモ
(深い内容は書いてません)

Map・・・キーと値の組み合わせで複数要素を扱うことができる。

Mapの基本的な使い方
public class Main {
    
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();

        //値の追加
        scores.put("takahashi", 100);
        scores.put("yamada", 78);
        scores.put("sasaki", 50);
        System.out.println("Mapの中身" + scores.toString());
        //→ Mapの中身{sasaki=50, takahashi=100, yamada=78}

        //値の上書き
        scores.put("yamada", 90);
        System.out.println("Mapの中身" + scores.toString());
        ///→ Mapの中身{sasaki=50, takahashi=100, yamada=90}

        //値の取得。
        //キーが存在しない場合はnullが入る。
        //ただしだからと言ってキーの存在をチェックできるわけではない。
        //意図的に値をnullとしている場合、キーが存在するのかしないのかの区別がつかない。
        Integer score = scores.get("yamada");
        System.out.println(score);
        //→ 90

        //値の削除
        scores.remove("yamada");
        System.out.println("Mapの中身" + scores.toString());
        ///→ mapの中身{sasaki=50, takahashi=100}

        //サイズを取得
        int size = scores.size();
        System.out.println(size);
        //→ 2

        //キーの検索
        boolean existKey = scores.containsKey("yamada");
        System.out.println(existKey);
        //→ false

        //値の検索
        boolean existValue = scores.containsValue(100);
        System.out.println(existValue);
        //→ true

    }
}
Mapの3つの実装クラス

・HashMap

この実装はsynchronizedされません。複数のスレッドが並行してハッシュ・マップにアクセスし、それらのスレッドの少なくとも1つが構造的にマップを変更する場合は、外部でその同期をとる必要があります。

スレッドセーフじゃないらしい。
なので、Collections.synchronizedMapメソッドを使用してマップを「ラップ」するのがいいとのこと。

  Map m = Collections.synchronizedMap(new HashMap(...));


・LinkedHashMap

HashMapのサブクラス。
HashMapが順序に対して保証しないのに対し、LinkedHashMapはputした順番に格納される。

public class Main {
    
    public static void main(String[] args) {
        //・HashMapの場合
        Map<Integer, String> hmap = new HashMap<>();
        MapTest test = new MapTest();

        test.test(hmap, 4, "d");
        test.test(hmap, 45, "bbb");
        test.test(hmap, 1, "a");
        
        System.out.println(hmap);
        //→ {1=a, 4=d, 45=bbb}
        //挿入した順番になっていない

        //・LinkedHashMapの場合
        Map<Integer, String> lmap = new LinkedHashMap<>();
        MapTest test2 = new MapTest();

        test2.test(lmap, 3, "T");
        test2.test(lmap, 34, "FG");
        test2.test(lmap, 7, "W");

        System.out.println(lmap);
        //→ {3=T, 34=FG, 7=W}
        //挿入した順番になっている

    }
}


・TreeMap

キーの値を元に、2分探索木のアルゴリズムで要素をソートするクラス。
データが自動的にソートされるらしい。

「〇〇以上〇〇以下のキーを持つ要素の取得」
「〇〇より大きな、もっとも近いキーを持つ要素を取得」
などのように、大小を意識した値の取得ができる。

Mapの実装クラスの使い分け

キーの大小を意識した部分集合を扱う場合 → TreeMap

要素の順序を保持する必要がある場合 → LinkedHashMamp

複数スレッドから同時にアクセスする場合 → ConcurrentHashMap

それ以外 → HashMap

雑多メモ

・ConcurrentModificationException

あるスレッドがCollectionで反復処理を行っている間に、別のスレッドがそのCollectionを変更することは一般に許可されません。通常、そのような環境では、反復処理の結果は保証されません。いくつかのイテレータ(Iterator)の実装(JREが提供するすべての一般的な目的のコレクションの実装の、イテレータの実装を含む)は、その動作が検出された場合にこの例外をスローすることを選択できます。この例外をスローするイテレータは、フェイルファスト・イテレータと呼ばれます。

まとめ

mapについて調べてみたが、内部構造(ハッシュ テーブルがどうのこうの)などはいまいち理解できてないので、
どこかのタイミングで深くみてみたいところ。。。。

おわり

JavaのList

Java本格入門でListについて読んだので、それのメモ。

List(java.util.List)

配列のように複数要素を扱うことができ、インデックスを指定して値の取得や設定ができる。

初期化方法

・要素なしの初期化

List<Integer> list = new ArrayList<>();

Listインターフェースの実装クラスを引数なしでnew。
ジェネリクスにIntegerを指定し、型を制限。


・asListを使った初期化

List<Integer> list2 = Arrays.asList(1,2,3);

1行でかけるが、要素の追加・削除ができない(固定サイズになっている)

// java.lang.UnsupportedOperationExceptionが発生
list2.add(4);

list2.set(0, 4)
System.out.println(list2);
→[4,2,3]
要素の上書きはOK


・コンストラクタを使う初期化

List<Integer> list3 = new ArrayList<>(Arrays.asList(1,2,3));

追加も削除もできる。

Listのメソッド
 List<Integer> list = new ArrayList<>();

//要素の追加
list.add(1);
System.out.println(list);
->[1]


//要素の追加2。インデックス1に2を入れる。
list.add(1, 2);
System.out.println(list);
->[1, 2]


//要素の追加3。2が後ろにずれる
list.add(1, 3);
System.out.println(list);
->[1, 3, 2]


//要素の取得
Integer num = list.get(1);
System.out.println(num);
->3


//要素の上書き。インデックスが2(この場合2)の要素を5に上書き。
list.set(2,5);
System.out.println(list);
->[1, 3, 5]

        
//要素の削除。インデックスが1(この場合3)の要素を削除
list.remove(1);
System.out.println(list);
->[1, 5]


//要素の数を取得
int size = list.size();
System.out.println(size);
->2


//インデックス番号を取得。5がlistのどこにあるのか
int index = list.indexOf(5);
System.out.println(index);
->1


//インデックス番号を取得。要素がない場合
int index2 = list.indexOf(34);
System.out.println(index2);
->-1


//値があるかどうかの確認。
boolean exists = list.contains(5);
System.out.println(exists);
->true
Listをソートする

java.util.Collectionsを使ってソートする。
第1引数にソート対象を、
第2引数にComparatorインターフェースを指定、

public class Main {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(3,24,5,1,11,6,9));
        System.out.println(list);
        ->[3, 24, 5, 1, 11, 6, 9]

   //Comparatorインターフェース(関数型インターフェース)のcompareメソッドをラムダ式で実装。
        Comparator<Integer> c = (o1, o2) -> {return o2.compareTo(o1);};

        Collections.sort(list, c);
        System.out.println(list);
        ->[24, 11, 9, 6, 5, 3, 1]
    }
}

このように並び替えることができる。

Listの検索

binarySearch(リスト, 探す要素)で、要素のインデックスを取得できる

public class Main {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(3,24,5,1,11,6,9));
        int index = Collections.binarySearch(list, 6);
        System.out.println(index);
        -> 5 
    }
}


ArrayListについて

・内部に配列を持つ
・要素のインデックスを指定して何かする、などの処理に向いている。
・内部に持つ配列の長さはコンストラクはで指定できる(デフォルトは10)
・配列のサイズ以上の要素を追加する場合、
「よりサイズの大きい配列を新たに生成して、元の配列から全ての要素をコピーする」
という処理が行われる
・なので、格納する要素数の目安がついているなら、最初にサイズを指定した方がいい。
・リストの途中で追加や削除をするとその後の要素を後ろにずらす必要があるので、中間要素の追加・削除処理は高速ではない。


LinkedListについて
・要素自信が前後の要素情報(ポインタ的な?)を持つ
・「A, B, C, D」といった要素の場合、AはBでのリンクを、BはAとCのリンクを、CはBとDのリンクを、DはCのリンクをもつ。
・BとCの間にEを入れる際、AとDのリンク情報は変わらない
・なので中間要素の追加・削除処理が高速
・インデックス指定の処理は、一旦先頭から順番を辿らないといけないので、サイズが大きいと時間がかかる。


CopyOnWriteArrayListについて

ArrayListをスレッドセーフはないので、複数のスレッドでListを扱いたい時にこのCopyOnWriteArrayListを使う。
ループ処理を行う際に、リストをコピーしてそのコピーした物に対してループ処理を行うので、
他のスレッドがリストの操作をしてもループ側に影響はない。


Listの実装クラスの使いわけ

・配列の途中で要素の追加・削除等をする場合

  • > LinkedList

・for文などを使った全体的な繰り返し処理が多い場合

・複数スレッドから同時にアクセスする場合

  • >CopyOnWriteArrayList
まとめ

ArrayListはよく使うが、LinkedListとかCopyOnWriteArrayListを使う機会が少ない気がする。
(自分の現場がそうなだけ?)


これからはもうちょっと意識して使い分けて見ようと思う。