javaの配列とリスト

このページは

javascriptの配列とリストと比較するためのJava版です。

守備範囲がかなり違うこと、Javaの方が細かな規則が多いので難儀すると思いますが、javascript基準で進め、簡便な比較リファレンスを目指します。

配列の生成

要素の値をコンマ区切りで書いたものから配列を生成できます。

String[] names = {"mike","tama","kuro"};

型宣言と、括弧の種類が異なります。

ArrayTest01.java

import java.util.Arrays;

class ArrayTest01{
    public static void main( String[] args ) {
        String[] names = {"mike","tama","kuro"};
        System.out.println(names[2]);
        System.out.println(names[0]);
        System.out.println(names);
        System.out.println(Arrays.toString(names));
    }
}

System.out.println(names);だけでは要素を出してはくれません。Arrays.toString()を使えば可能ですが、このためだけに import java.util.Arrays; が必要となります。プログラム作成中の値の確認に使うぐらいでしょう。

実行結果です。

kuro
mike
[Ljava.lang.String;@4aa298b7
[mike, tama, kuro]

Javascriptと比べて特筆すべきは、要素の種類が混在できないということです。String[]と宣言しますから文字列の配列です。特殊ケースとして、Object[]と宣言すれば理屈上は混在が可能なはずです。また、Object[]を返すメソッドも見たことがありますが、要素一つ一つが異なるタイプというのは見たことがありません。できるかどうかやってみましょう。

import java.util.Arrays;

class ArrayTest02{
    public static void main( String[] args ) {
        int[] kazu1 = {2018,10,11};
        Integer[] kazu2 = {15,02,00};
        Object[] iroiros = {"mike",365,3.5,true,kazu1,kazu2};
        System.out.println(iroiros[2]);
        System.out.println(iroiros[0]);
        System.out.println(iroiros);
        System.out.println(Arrays.toString(iroiros));
        System.out.println(Arrays.deepToString(iroiros));
        System.out.println(iroiros[0].getClass());
        System.out.println(iroiros[1].getClass());
        System.out.println(iroiros[2].getClass());
        System.out.println(iroiros[3].getClass());
        System.out.println(iroiros[4].getClass());
        System.out.println(iroiros[5].getClass());
    }
}

できてしまいました。実行結果です。Arrays.deepToString()をはじめて使いました。

3.5
mike
[Ljava.lang.Object;@4aa298b7
[mike, 365, 3.5, true, [I@7d4991ad, [Ljava.lang.Integer;@28d93b30]
[mike, 365, 3.5, true, [2018, 10, 11], [15, 2, 0]]
class java.lang.String
class java.lang.Integer
class java.lang.Double
class java.lang.Boolean
class [I
class [Ljava.lang.Integer;

配列に対する.getClass()が変ですけど、配列はClassではありませんからね。

Javaでは演算などで型が厳しくチェックされます。これはプログラムのミスを防止する大切な機構です。いろいろな型の変数を入れることはできても、このJavaの恩恵を否定することになってしまいます。

空の配列をつくる

生成の時に要素が空であれば、要素数0の配列ができます。

String[] karas = {};

要素数は増えません。長さは0のままです。コンパイルは通りますが、...

class ArrayTest03{
    public static void main( String[] args ) {
        String[] karas = {};
        System.out.println(karas.length);
        karas[0] = "hoge";
        System.out.println(karas[0]);
    }
}

lengthは0というところまでは動きますが、代入しようとすると、ArrayIndexOutOfBoundsExceptionが発生して停止します。

0
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
	at ArrayTest03.main(ArrayTest03.java:6)

要素数を指定する配列

String[] arr = new String[4];

Javascriptと共通に要素数が4で、番号は0からになりますから、0,1,2,3の4つです。

class ArrayTest04{
    public static void main( String[] args ) {
        int[] shiteiA = new int[4];
        int[] shiteiB = new int[]{2018,10,11};
        int[] shiteiC = {4};
        System.out.println(shiteiA.length);
        System.out.println(shiteiB.length);
        System.out.println(shiteiC.length);
        System.out.println(shiteiB[shiteiB.length-1]);
    }
}

new int[]の後に{}で要素を初期化できます。必要になることはないでしょう。

{4}は要素数1で値が4の配列です。

最終要素は shiteiB.length-1 です。

4
3
1
11

要素数と初期値を指定する配列

たくさんのカウンタを配列に用意して、見つかった数を数えたい場合、最初に全部を0クリアして、+1していくことがあります。このような時配列の要素の初期値を設定する必要があります。

Javaでは基本データ型のうち数値は0に初期化されます。整数のbyte,short,int,longは0、浮動小数点数のfloat,doubleは0.0と表示されます。booleanはfalse、charは''になります。Stringは基本データ型ではなく参照型の変数と言われますが、nullと表示されます。なにも参照していないということです。

この値で満足できないときは、Arrays.fill(対象配列,値)が使えます。static(クラス)メソッドです。

import java.util.Arrays;

class ArrayTest05{
    public static void main( String[] args ) {
        int[] shiteiI = new int[256];
        System.out.println(shiteiI[255]);
        Arrays.fill(shiteiI,2);
        System.out.println(shiteiI[255]);
        double[] shiteiD = new double[256];
        System.out.println(shiteiD[255]);
        boolean[] shiteiB = new boolean[256];
        System.out.println(shiteiB[255]);
        char[] shiteiC = new char[256];
        System.out.println(shiteiC[255]);
        String[] shiteiS = new String[256];
        System.out.println(shiteiS[255]);
    }
}

実行結果です。

0
2
0.0
false

null

繰り返し機構

for文は多くの言語で使える王道です。

for(int i=0;names.length>i;i++)
for(int i=0;256>i;i++)

もう一つ、for-each と言われながら、予約語としてはやっぱり for を使う、もう一つのやり方もあります。リストやマップで活躍しますが、配列でも使えます。

class ArrayTest06{
    public static void main( String[] args ) {
        int[] shiteiI = new int[256];
        String[] names = {"mike","tama","kuro"};
        for(int i=0;names.length>i;i++){
            System.out.println(names[i]);
        }
        System.out.println("----");
        for(String name:names){
            System.out.println(name);
        }
    }
}

実行結果です。

mike
tama
kuro
----
mike
tama
kuro

for-eachループ

これはとても柔軟で便利です。たとえば、

for(int number: numbers)
for(double value: values)
for(char chr: chrs)
for(MyClass one: instances)

:より前に宣言された型の変数やクラスの配列やリストが後ろにあれば成り立ちます。つまり、int[] numbers だったりすればいいわけです。

import java.util.Arrays;

class ArrayTest07{
    public static void main( String[] args ) {
        double[] values = {1.2, 2.4, 3.6, 4.81};
        double sm = 0.0;
        for(double value:values){
            System.out.println(value);
            sm += value;
        }
        System.out.println("sum = "+sm);
        System.out.println("----");
        int[][] intss = {{1,2,3},{4,5},{6,7,8,9}};
        for(int[] ints:intss){
            System.out.println(Arrays.toString(ints));
        }
    }
}

実行結果です。

1.2
2.4
3.6
4.81
sum = 12.009999999999998
----
[1, 2, 3]
[4, 5]
[6, 7, 8, 9]

リスト

Javaでは配列とは別にListがあります。配列では要素を追加したり削除したりできません。

JavaのListは2つの選択肢があります。ArrayListとLinkedListです。ArrayListは配列の状の要素管理を行っていて要素番号による参照や更新を得意としますが、要素の挿入を多用する場合は効率的ではありません。LinkedListは要素が鎖状にリンクしている管理方法で要素の挿入がリンクを換えるだけで済むので効率的に処理できますが、逆に要素番号で指示されると端から数えるので時間がかかります。作業に合わせて選択します。

Javaではリストは配列とは別物ですから、たくさんの説明が必要です。とりあえずJavascriptのpush/popの例に合わせて作ってみました。

文字列の配列 saba,hokke,iwashi,shishamo からadd()でリストに取り込んでremove()で取り出して書き出しています。htmlの表の代わりに配列から読み込んでいます。

仕組みを理解するために、一回のadd()ごとに戻値と配列を書いていき、4つ読んだら一回のremove()ごとに戻値と配列を書いています。

import java.util.List;
import java.util.ArrayList;
class ArrayTest08{
    public static void main( String[] args ) {
        String[] fishes = {"saba","hokke","iwashi","shishamo"};
        List<String> fishlist = new ArrayList<>();
        for(String fish:fishes){ 
            boolean tmp = fishlist.add(fish);
            System.out.print(tmp+"|");
            System.out.println(fishlist.toString());
        }
        while(fishlist.size()>0){
            String tmp = fishlist.remove(fishlist.size()-1);
            System.out.print(tmp+"|");
            System.out.println(fishlist.toString());
        }
    }
}

実行結果です。

true|[saba]
true|[saba, hokke]
true|[saba, hokke, iwashi]
true|[saba, hokke, iwashi, shishamo]
shishamo|[saba, hokke, iwashi]
iwashi|[saba, hokke]
hokke|[saba]
saba|[]

ArrayListを使っていますがLinkedListでも同様にできます。

ArrayListのインスタンスを作っていますが、宣言は単にListとしています。Array,Linkedが共通に持っている機能しか使っていないので、汎用性を持たせるため通常はこのようにします。<String>は入れる変数のクラスを宣言します。後ろの<>はこのように省略できます。

intやdoubleなどの基本変数は、<Integer>, <Double> と相当するラッパークラスで宣言します。

add()の戻値はjavascriptのpopに合わせて書きましたが、普通使うことはありません。

popにあたるものはなくて、remove()は要素番号を指定しなければなりません。

Listの主要なメソッド

配列の様に要素番号を指定して値を参照したり、書き換えたりもできます。角括弧ではなく、メソッドを使います。

戻値メソッド説明
booleanadd(E e)リストの最後にeを追加する
voidadd(int index, E e)indexの位置にeを挿入する
Eget(int index)indexの位置の要素を返す
Eset(int index, E e)indexの位置の要素を指定要素で置き換える
Eremove(int index)indexの位置の要素を削除する
booleanremove(Object o)指定された要素があれば、最初のものを削除する
intindexOf(Object o)指定された要素が最初に検出された位置を返す。ない場合は -1を返す。
booleanisEmpty()要素がなければtrue
voidclear()すべての要素を削除
intsize()要素の数を返す

値の出し入れについて、以下にまとめておきます。色付き文字はLinkedListにのみ使えるメソッドです。

★要素数が変化しないメソッド(戻値はすべてE)
↑get(int index)
getFirst()← List (LinkedList) →getLast()
↑set(int index, E element)
★要素数の増減をともなうメソッド(戻値はbがboolean, vはvoid, 他はE)
⇩v add(int index, E element)
v addFirst(E element)⇨ List (LinkedList) ⇦v addLast(E element)
remove()⇦ ⇦b add(E element)
removeFirst()⇦ ⇨removeLast()
⇩remove(int index)
⇩b remove(Object o)

LinkedListであればpush(E e)pop()も使えますが、Listでも使えるということで、add(E e)とremove()を基本に記憶するようにと省きました。

offer, peek, pollなども、使えます。語感や他言語との互換を気にしなければ覚えておく必要はないでしょう。

LinkedListに追加されているメソッド

前項にあるように普通は、

List<String> list = new LinkedList<>();

としますが、popにあたるremoveLastなど、LinkedList特有のメソッドを使うときには次のように宣言します。

LinkedList<String> list = new LinkedList<>();

addFirst()の戻値はvoid(つまり無し)なので、取得せず、voidと書いています。

import java.util.LinkedList;

class ArrayTest09{
    public static void main( String[] args ) {
        String[] fishes = {"saba","hokke","iwashi","shishamo"};
        LinkedList<String> fishlist = new LinkedList<>();
        for(String fish:fishes){ 
            fishlist.addFirst(fish);
            System.out.print("void |");
            System.out.println(fishlist.toString());
        }
        while(fishlist.size()>0){
            String tmp = fishlist.remove();
            System.out.print(tmp+"|");
            System.out.println(fishlist.toString());
        }
    }
}

実行結果です。

void |[saba]
void |[hokke, saba]
void |[iwashi, hokke, saba]
void |[shishamo, iwashi, hokke, saba]
shishamo|[iwashi, hokke, saba]
iwashi|[hokke, saba]
hokke|[saba]
saba|[]

リストに初期値を設定する

Javaのリストに初期値を設定したり、複数の要素をまとめて追加する方法です。

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.LinkedList;

class ArrayTest10{
    public static void main( String[] args ) {
        List<String> namelist1 = Arrays.asList("mike","tama","kuro");  //追加不可
        System.out.println(namelist1.get(1));
        namelist1.set(2,"tora");
        System.out.println(namelist1.toString());

        List<String> namelist2 = new LinkedList<>(Arrays.asList("mike","tama","kuro"));
        System.out.println(namelist2.get(1));
        namelist2.set(2,"tora");
        namelist2.add(2,"akame");
        System.out.println(namelist2.toString());

        List<String> namelist3 = new LinkedList<>();
        Collections.addAll(namelist3, "mike","tama","kuro");
        String[] fishes = {"saba","hokke","iwashi","shishamo"};
        Collections.addAll(namelist3, fishes);
        System.out.println(namelist3.toString());
    }
}

実行結果です。

tama
[mike, tama, tora]
tama
[mike, tama, akame, tora]
[mike, tama, kuro, saba, hokke, iwashi, shishamo]

(namelist1)はArrays.asList()で配列からリストを作ることができますから、これをListのインスタンスに代入できます。ただし、この方法では要素数が固定になりますから、要素の増減はできません。各要素への参照と値の変更のみならこれで済ませられます。

(namelist2)はArrays.asList()で作ったリストを元に新たなLinkedListを作っています。ArrayListでもかまいません。リストを初期値に新たなリストを作ることができ、このリストは要素の増減が可能です。

(namelist3)は複数の要素をまとめて追加する方法です。要素の列記からでも、配列からでも追加できます。

ここで、三項演算子(条件?trueの時の式:falseの時の式)も使いました。javascriptでは条件演算子と呼ぶようです。ifの中で変数を定義すると外ではアクセスできませんから、これは便利です。

配列中の値を探して削除

JavaでもindexOf()が使えますが、remove()で値を直接指定することもできます。両方やってみます。

方法1

indexOf("検索値")は配列中の要素で"検索値"と一致するものを探して位置(index)を返します。見つからない時は-1です。indexOf("検索値",開始位置)はありません。一つ見つかったらそれ以上は探しません。

remove(index)は、index位置の要素をひとつ削除して、削除した要素の値を返します。元のリストは書き換えられて要素数が減ります。ここのプログラムでは3項演算子の中に書かれているのでわかりにくいかもしれません。

方法2

remove(削除値)で、最初に見つかった要素をひとつ削除します。見つかったらtrue, そうでなければfalseを返します。削除されたリストは要素数が減ります。

split("区切り文字")は文字列を区切り文字で区切って、分けたものを要素にした配列を生成して返します。Javascriptの例とできるだけ同じにするために使用しました。

import java.util.Arrays;
import java.util.List;
import java.util.Collections;
import java.util.LinkedList;

class ArrayTest11{
    public static void main( String[] args ) {
        String[] names = "mike,tama,kuro,tora,tama".split(",");
        List<String> rmlist = Arrays.asList("tama","momo","tora");

        System.out.println("方法1---------------");
        List<String> namelist = new LinkedList<>();
        Collections.addAll(namelist, names);
        System.out.printf("%-18s|%s\n","original list",namelist.toString());
        for(String rmname :rmlist){
            int index = namelist.indexOf(rmname);
            String removed = 0>index ?rmname+":not found" :namelist.remove(index);
            System.out.printf("%2d|%-15s|%s\n",index,removed,namelist.toString());
         }

        System.out.println("方法2---------------");
        List<String> namelist2 = new LinkedList<>();
        Collections.addAll(namelist2, names);
        System.out.printf("%-16s|%s\n","original list",namelist2.toString());
        for(String rmname :rmlist){
            boolean rmsuccess = namelist2.remove(rmname);
            String removed = rmsuccess? "removed" : "not found";
            System.out.printf("%-6s%-10s|%s\n",rmname,removed,namelist2.toString());
        }
    }
}
方法1---------------
original list     |[mike, tama, kuro, tora, tama]
 1|tama           |[mike, kuro, tora, tama]
-1|momo:not found |[mike, kuro, tora, tama]
 2|tora           |[mike, kuro, tama]
方法2---------------
original list   |[mike, tama, kuro, tora, tama]
tama  removed   |[mike, kuro, tora, tama]
momo  not found |[mike, kuro, tora, tama]
tora  removed   |[mike, kuro, tama]

リストのリスト

リストは関数を呼び出す時の引数や戻値にすると便利なことが多いですが、リストのリストを必要とする場面がありました。Javaでもできます。

個数が一定しない数の列があります。リストにしましょう。このリストが3つあって、関数に引数として渡します。現在は3つですが将来的に増える可能性もあるので、こちらもリストにします。リストのリストです。

<List<String>>.....<>の部分が、ポイントです。

List<String> baselist = new LinkedList<>();
Collections.addAll(baselist,"アイス","ヨーグルト");
List<String> saucelist = new LinkedList<>();
Collections.addAll(saucelist,"ブルーベリー","黒蜜","練乳","チョコ");
List<String> toppinglist = new LinkedList<>();
Collections.addAll(toppinglist,"イチゴ","バナナ","小倉餡");
List<List<String>> dessertlist = new LinkedList<>();
Collections.addAll(dessertlist,baselist,saucelist,toppinglist);

baselist,saucelist,toppinglistという分類名は配列やリストでは失われて番号になりますが、実際に作成したのは数学関係のプログラムでしたので、0,1,2という連番で構いませんでした。必要ならListの代わりにMapを使えば解決します。

dessertlist.get(2).get(0)+dessertlist.get(1).get(2)+dessertlist.get(0).get(0);
// イチゴ練乳アイス

baselistに"かき氷"を加えたければ、

dessertlist.get(0).add("かき氷");

drinkメニューを加えたければ、先にdrinklistを作っておいて、

dessertlist.add(drinklist);

で可能です。

import java.util.Arrays;
import java.util.List;
import java.util.Collections;
import java.util.LinkedList;

class ArrayTest12{
    public static void main( String[] args ) {
        List<String> baselist = new LinkedList<>();
        Collections.addAll(baselist,"アイス","ヨーグルト");
        List<String> saucelist = new LinkedList<>();
        Collections.addAll(saucelist,"ブルーベリー","黒蜜","練乳","チョコ");
        List<String> toppinglist = new LinkedList<>();
        Collections.addAll(toppinglist,"イチゴ","バナナ","小倉餡");
        List<List<String>> dessertlist = new LinkedList<>();
        Collections.addAll(dessertlist,baselist,saucelist,toppinglist);

        System.out.println(dessertlist.get(2).get(0)+dessertlist.get(1).get(2)+dessertlist.get(0).get(0));
        System.out.printf("%-10s|%s\n","base追加前",dessertlist.get(0).toString());
        dessertlist.get(0).add("かき氷");
        System.out.printf("%-10s|%s\n","base追加後",dessertlist.get(0).toString());

        System.out.printf("%-10s|%s\n","飲物追加前",dessertlist.toString());
        List<String> drinklist = new LinkedList<>();
        Collections.addAll(drinklist,"コーヒー","紅茶","ソーダ");
        dessertlist.add(drinklist);
        System.out.printf("%-10s|%s\n","飲物追加後",dessertlist.toString());
        baselist.add("パスタ");
        System.out.printf("%-10s|%s\n","!!",dessertlist.toString());
    }
}
イチゴ練乳アイス
base追加前   |[アイス, ヨーグルト]
base追加後   |[アイス, ヨーグルト, かき氷]
飲物追加前     |[[アイス, ヨーグルト, かき氷], [ブルーベリー, 黒蜜, 練乳, チョコ], [イチゴ, バナナ, 小倉餡]]
飲物追加後     |[[アイス, ヨーグルト, かき氷], [ブルーベリー, 黒蜜, 練乳, チョコ], [イチゴ, バナナ, 小倉餡], [コーヒー, 紅茶, ソーダ]]
!!        |[[アイス, ヨーグルト, パスタ, かき氷], [ブルーベリー, 黒蜜, 練乳, チョコ], [イチゴ, バナナ, 小倉餡], [コーヒー, 紅茶, ソーダ]]

最後の例(!!)で、リストに入るのは参照であることがわかります。順番が異なりますね。理解していない仕組みがあるのかもしれません。