配列 (2)

2次元の配列(配列の配列)

配列の各要素がまた配列であってもよい。その場合は2次元配列になる (配列の各要素が2次元配列であれば3次元配列になる)。たとえば aa という配列の各要素がまた配列である場合は次の図のようになる。


この場合は、

int[][] aa;

のように宣言すればよい。

ただし、配列は宣言しただけでは要素を格納する箱が作成されない。そのため、要素を格納する箱を先週までの配列と同様に作成してやる必要がある。

int[][] aa = new int[3][2];

aa は、第0,1,2要素が長さ 2 の整数型の配列である。第 i 要素の配列の中の第 j 要素は aa[i][j] と表される。new int[3][2] によって、aa[0][0], aa[0][1], aa[1][0], aa[1][1], aa[2][0], aa[2][1] といった、6つのデータを格納する配列が作成できた。

初期値も与えるとすると

int[][] aa = {{1, 2}, {3, 4}, {5, 6}};

のように書けばよい。その結果、配列の各要素は次のような値になる。

要素
aa[0][0] 1
aa[0][1] 2
aa[1][0] 3
aa[1][1] 4
aa[2][0] 5
aa[2][1] 6

また、aa は「配列の配列」であったので、aa が直接持つ箱は int[] 型のデータを保持している。

要素
aa[0] {1, 2}
aa[1] {3, 4}
aa[2] {5, 6}

つまり、次のように2次元配列から1次元配列を取り出すこともできる。

int[][] aa = {{1, 2}, {3, 4}, {5, 6}};
int[] a = aa[0];
int[] b = aa[1];
int[] c = aa[2];

ここで、b[0] に何らかの値を代入すると、aa[1][0] の値も変化する (紙に図を書いて考えてみよ)。

配列の要素がまた配列である場合、それぞれの配列の長さは一定でなくても良い。たとえば

int[][] aa = {{10, 20}, {30}, {} };

とすると次の図のようになる。


二次元配列は、主に2次元の画像処理を行う場合や、行列を用いた演算をするような場面でよく利用される。

2次元配列の走査

1次元配列 (先週の配列) では、次のように for 文を用いることで配列の各要素に順番にアクセスできた。

int[] array1d = new int[100];
for (int i = 0; i < array1d.length; i++) {
    System.out.println("array1d[" + i + "] = " + array1d[i]);
}

しかし、2次元以上の配列は以下のように for 文を二重にしてアクセスする必要がある。

int[][] array2d = {{1,2}, {3,4}, {5,6}};
for (int i = 0; i < array2d.length; i++) {
  for (int j = 0; j < array2d[i].length; j++) {
    System.out.println("array2d[" + i + "][" + j + "] = " + array2d[i][j]);
  }
}

様々な型

これまでに、整数を表現する int 型と実数を表現する double 型を紹介してきた。

他によく使用される型として、char 型と boolean 型を紹介する。char は character (文字列)の略で、その名の通り文字一文字を現すための型である。boolean 型は true (真)false (偽) の2値を扱うための型で、条件式などに使用される。

文字型 (char)

文字型とはその名の通り文字一文字を表す型である。文字には例えば "a", "A", "z" などのアルファベットや、"1", "2", "3" などの数字一文字、"あ", "亜"などの日本語や、スペース、改行などが含まれる。

文字定数 (character literal)

文字を表現するには、表現したい文字を「'」と「'」でくくってやればよい。

char c = 'C';
char space = ' ';
char a = 'あ';

文字列が「"」でくくるのに対し、文字は「'」でくくる必要があるので注意すること。

文字の表示

文字を表示するには、今までどおり System.out.print や System.out.println 命令が使用できる。

char a = 'A';
System.out.println(a);

文字の比較

文字を比較するには、整数と同じように == などの演算子が使用できる。

char a = 'A';
if (a == 'A') {
  ...
}

また、アルファベットは A から Z まで順に並んでいるという決まりごとがあり、次のように大小比較をすることもできる。

char c = 'C';
if ('A' <= c && c <= 'Z') {
  // 大文字のときの処理
}
else if ('a' <= c && c <= 'z') {
  // 小文字のときの処理
}

これは次のように書いてもよい。

char c = 'C';
if (Character.isUpperCase(c)) {
  // 大文字のときの処理
}
else if (Character.isLowerCase(c)) {
  // 小文字のときの処理
}

ブール値型 (boolean)

boolean 型は true (真)false (偽) の2値を扱うための型で、実は今までにもたくさん使用してきている。

if (a < 0) ...

とあったとき、 a < 0 を実行した結果は boolean 型になる (つまり、true か false になる)。boolean 型を使用すると、この条件式を実行した結果を変数に格納することができる。

boolean negative = a < 0;

上記の文を実行すると、a が 0 未満であった場合は negative に true が代入される。

値が代入された変数は、条件式を扱う構文で使用することができる。

boolean branchCondition = ...;
boolean loopCondition = ...;
if (brabchCondition) {
  ...;
}
while (loopCondition) {
  ...;
}

boolean を意識して使用すると、非常に読みやすいプログラムが書けるようになる。

例えば、課題0501(TriangleCheck)を書く場合、boolean を意識して使用しない場合は次のようなコードになる。

public static void main(String[] args) {
  ...
  if (a == b && b == c)
    System.out.println("入力された三角形は正三角形");
  else if (a == b || b == c || c == a)
    System.out.println("入力された三角形は二等辺三角形");
  else
    System.out.println("入力された三角形は通常の三角形");
}

上記の条件式でも十分読みやすいが、これを boolean を意識して書き直すと、次のようなコードが書ける。

public static void main(String[] args) {
  ...
  if (isEquilateral(a, b, c))
    System.out.println("入力された三角形は正三角形");
  else if (isIsosceles(a, b, c))
    System.out.println("入力された三角形は二等辺三角形");
  else
    System.out.println("入力された三角形は通常の三角形");
}

public static boolean isEquilateral(double a, double b, double c) {
  return (a == b && b == c);
}

public static boolean isIsosceles(double a, double b, double c) {
  return (a == b || b == c || c == a);
}

上記のように、main メソッドの中身を英語のような文章に書き換えることができる。このようにすればmainメソッドが読みやすくなるだけでなく、isEquilateral, isIsosceles の両メソッドを単体テストしやすくなる。

単体テストを行う必要がないなら、以下のように boolean 型の変数に代入するだけでもわかりやすくなる。

public static void main(String[] args) {
  ...
  boolean equilateral = (a == b && b == c);
  boolean isosceles = (a == b || b == c || c == a);
  if (equilateral)
    System.out.println("入力された三角形は正三角形");
  else if (isosceles)
    System.out.println("入力された三角形は二等辺三角形");
  else
    System.out.println("入力された三角形は通常の三角形");
}

この程度の複雑さならばわざわざメソッドや変数を作成するまでもないかもしれないが、一目でわかりにくいと思うような条件式を書いてしまった場合は一考してみるとよい。

ブール値の定数 (boolean literal)

boolean 型の定数は true と false の二種類しかない。

boolean b = true;
boolean z = false;

定数を使う場面のうち、よく見かける場面は次のような場面である。

// 無限ループ
while (true) {
  ...
}
// 探索
boolean found = false;
for (int i = 0; i < array.length; i++) {
  if (array[i] == searching) {
    found = true;
  }
}
if (found) {
  // 見つかった場合の処理
}

ブール型の演算

複雑な条件式 のところで紹介した「&& (~かつ~)」「|| (~または~)」「! (~ではない)」は、boolean 型の値に対する演算を行う。そのため、 a < 0 や a == b のような条件式だけでなく、次のような式にも使用できる。

boolean cond1 = ...;
boolean cond2 = ...;
boolean cond3 = ...;
if (cond1 && cond2 && cond3) {
  ...

String 型

char型は文字一文字を表す型であるが、String型は文字列を表す型である。文字列は今までの演習に幾度となく出てきていたが、あまり意識したことはなかったかもしれない。

String hello = "Hello, world!";

上記のように、「"」から始まり「"」で終わる文字列は String 型の定数を表し、String 型の変数にこのような文字列を格納できる。

文字列の長さを取得する

文字列の長さを取得するには、「文字列.length()」という命令を使用する。この命令を実行すると、文字列の長さが int 型の値として返ってくる。

String line = "Hello, world!";
int length = line.length();

ここで注意すべき点は、配列の長さを取得する場合は「配列.length」であったのに対し、文字列の場合は「文字列.length()」であるという点である。

文字列内の各文字を取得する

文字列内の各文字を取得するには、「文字列.charAt(文字の位置)」という命令を使用する。この命令を実行すると、「文字列内」の「文字の位置」で指定した位置の文字が、char 型の値 (文字一文字) として返ってくる。文字の位置は、先頭の文字が 0 番目で、末尾の文字が (文字列の長さ - 1) である。これは配列と同じルールである。

String line = "Hello, world!";
// 先頭の文字を取得
char head = line.charAt(0);
// 末尾の文字を取得
char tail = line.charAt(line.length() - 1);

配列と同様に、文字列内の各文字を順番に調べていくには for を使うと書きやすい。

for (int i = 0; i < line.length(); i++) {
  // 2回表示する
  System.out.print(line.charAt(i));
  System.out.print(line.charAt(i));
}

ところで、「.」という文字をよく目にするようになったと思うが、これは日本語で . の左右をつなげて「~の~」と解釈すると読みやすい。

例えば、以下のように読む。

String line = "Hello, world!";
// head = line の char at 0 => lineの0文字目
char head = line.charAt(0);

コンソールに入力された文字列

今まで何気なく以下の2つの命令を使用していた。

... = Integer.parseInt(reader.readLine());
... = Double.parseDouble(reader.readLine());

reader.readLine() という命令は、実は「コンソールから1行読んだ結果を文字列として返す」という命令である。つまり、次のように書くこともできる。

String line = reader.readLine();
int input = Integer.parseInt(line);

上記の例では、入力された文字列を一度 line に格納して、その後 Integer.parseInt に その line を渡している。Integer.parseInt では、渡された文字列に書かれている整数値 ("100", "350" など) を、int 型の値に変換して返している。Double.parseDouble は Integer.parseInt の実数版である。

下記のように、Integer.parseInt や Double.parseDouble の引数に文字列定数を渡すこともできる。つまり、これらの命令に渡す値は String 型であればどのような値を渡すこともできる (ただし、"a" や "b" といった数値以外を渡すとエラーになる)。

int a = Integer.parseInt("100");
double b = Double.parseDouble("3.14");
System.out.println("a = " + a + ", b = " + b);

以下のプログラムは、コンソールに入力された文字列と同じ文字列を 2 回連続して表示するだけの簡単なプログラムである。

package j1.lesson12;

import java.io.*;

public class DoubleEchoString {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("input> ");
        String line = reader.readLine();
        System.out.println(line);
        System.out.println(line);
    }
}

char[] との相互変換

文字列を表すもう一つの方法として、「文字の配列」を使用するという方法がある。Java で文字を表すには char 型を使用すればよく、その配列は char[] 型となる。

少し古いプログラミング言語では、文字列を表現する際に文字の配列を使っていた。このようなプログラミング言語は今でも多くの人に使用されているため、慣れておくとよい。

int 配列や double 配列と同じように、char 配列を作成することができる。

char[] array = new char[10];

宣言と同時に初期化することもできる。

char[] array = {'H', 'e', 'l', 'l', 'o'};

ここでは紹介しないが、boolean 型も同様の手順で配列を生成することができる。

String を char[] に変換するには、「文字列.toCharArray()」という命令を使用する。

String hello = "Hello";
char[] array = hello.toCharArray();

逆に、char[] を String に変換するには「String.valueOf(変換する値)」という命令を使用する。

char[] array = {'H', 'e', 'l', 'l', 'o'};
String hello = String.valueOf(array); // "Hello"

この String.valueOf というメソッドはさまざまな型で使用することができ、int, double, char, boolean などの値を String 型に変換してくれる。

String s100 = String.valueOf(100);   // "100"
String pi = String.valueOf(Math.PI); // "3.141592..."
String a = String.valueOf('a');      // "a"
String sTrue = String.valueOf(true); // "true"

ただし、int[] 型の値や double[] 型の値は ID を表す文字列に変換してしまうので注意すること。

例えば、文字列の先頭と末尾の文字を入れ替えた文字列を作成する場合、以下のように書ける。

String input = "hello";

// String -> char[]
char[] array = input.toCharArray();

// 文字の入れ替え
char head = array[0];
array[0] = array[array.length - 1];
array[array.length - 1] = head;

// char[] -> String
String replaced = String.valueOf(array);