for文は、値の範囲または配列やコレクションの要素に対して反復処理を行うためのループ処理です。特定のタスクを複数回実行するための方法であり、同じコードを何度も書く手間を省くことができます。
Javaにおけるfor文の基本的な構文は以下の通りです。
for (initialization; condition; increment/decrement) {
statement(s)
}
for文の各パーツを分解してみましょう。
initialization: ループの最初に一度だけ実行されるステートメントです。通常、ループ変数の初期化に使用されます。ループ変数とは、ループの反復ごとに値が変化する変数のことを指します。
condition: ループの各反復の前に評価されるブーリアン式です。条件が真であれば、ループの実行は継続されます。条件が偽の場合、ループは終了し、制御はループの次の文に移されます。
increment/decrement: ループの各反復の後に実行されるステートメントです。通常、ループ変数の値を更新するために使用されます。
statement(s): 条件が真である限り、繰り返し実行されるコードのブロックです。1つの文でも、中括弧で囲まれた文のグループでも問題ありません。
以下は、1から10までを出力するfor文の例です。
for (int i = 1; i <= 10; i++) {
System.out.println(i);
}
この例では、ループ変数iは1で初期化され、繰り返し実行されるたびに1ずつ増加します。ループはiが10以下である限り実行し続けます。
以下は、10から1までを出力するfor文の例です。
for (int i = 10; i >= 1; i--) {
System.out.println(i);
}
この例では、ループ変数iは10で初期化され、繰り返し実行されるたびに1ずつ減算されます。ループはiが1以上である限り実行し続けます。
1から10までを数えて、すべての数の合計を計算するfor文を見てみましょう。
class Main {
public static void main(String[] args){
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
System.out.println("The sum is: " + sum);
}
}
最後にその合計が表示されます。
拡張for文とは、Java 5から導入された配列やコレクションを繰り返すための構文で、次のような形式で書かれます。
for (データ型 変数名 : 配列やコレクション) {
statement(s)
}
通常のfor文と違い、ループ変数や条件式を使用せず一時変数に要素を格納し、取り出すべき要素がなくなるまで処理を繰り返します。拡張for文が使えるのは、配列および、java.lang.Iterableを実装しているクラスです。例えば以下のようなクラスです。
配列 int[] numbersの各要素を拡張for文を使って表示する例を見てみましょう。
class Main {
public static void main(String[] args){
int[] numbers = {1, 2, 3, 4, 5};
// 通常のfor文
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// 拡張for文
for (int number : numbers) {
System.out.println(number);
}
}
}
この例では、numbers配列の各要素を 一時変数のnumberに代入し、その要素を表示する処理を繰り返します。拡張for文は、配列やコレクションの要素を順番に処理する場合に便利です。配列やコレクションを反復処理する場合には、通常のfor文よりも簡潔で可読性が良くなります。
では、fruitsリストの要素を繰り返し、各要素を別々の行に表示してみましょう。
import java.util.List;
import java.util.Arrays;
class Main {
public static void main(String[] args){
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
for (int i = 0; i < fruits.size(); i++) {
System.out.println(fruits.get(i));
}
for (String fruit : fruits) {
System.out.println(fruit);
}
}
}
fruitと価格をキーと値のペアとして格納し、各ペアを特定のフォーマットで表示してみます。Mapクラスの反復処理では、Mapのキーと値をセットにして保存するMap.Entryを使います。Entryインターフェースには、getKey()メソッドや、getValue()メソッドなど、キーと値を参照したりセットしたりできるメソッドが用意されています。
import java.util.Map;
import java.util.HashMap;
class Main {
public static void main(String[] args){
Map<String, Integer> prices = new HashMap<>();
prices.put("apple", 100);
prices.put("banana", 50);
prices.put("cherry", 75);
for (Map.Entry<String, Integer> entry : prices.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue() + " yen");
}
}
}
colorの集合の要素を繰り返し、各要素を別々の行に表示します。HashSetは重複を許容せず、順序も補償されません。通常のfor文で要素を取り出すには一度配列にする必要がありますが、拡張for文を使えば順不同で全ての要素を参照できます。
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
class Main {
public static void main(String[] args){
Set<String> colors = new HashSet<>(Arrays.asList("red", "green", "blue"));
for (int i = 0; i < colors.size(); i++) {
String color = (String) colors.toArray()[i];
System.out.println(color);
}
for (String color : colors) {
System.out.println(color);
}
}
連結リストの要素を反復処理してみましょう。連結リストは、一連のノードからなるデータ構造で、各ノードにはリスト内の次のノードへの参照が含まれています。
import java.util.LinkedList;
class Main {
public static void main(String[] args){
LinkedList<String> names = new LinkedList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
System.out.println(name);
}
for (String name : names) {
System.out.println(name);
}
}
}
以下のコードを実行すると、ConcurrentModificationExceptionがスローされます。
import java.util.List;
import java.util.ArrayList;
class Main {
public static void main(String[] args){
List<String> list = new ArrayList<String>();
list.add("apple");
list.add("orange");
list.add("banana");
list.add("cherry");
list.add("pear");
for (String s : list) {
if ("banana".equals(s)) {
list.remove(s);
}
else {
System.out.println(s);
}
}
}
}
Java5で拡張for文が導入される以前は、反復処理にiteratorメソッドを使っていました。拡張for文はコンパイル時に、そのiteratorを使う処理へ暗黙的に変換されています。以下のコードで拡張for文とiteratorメソッドでの通常のfor文は等価です。
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
class Main {
public static void main(String[] args){
List<String> fruits = new ArrayList<String>();
fruits.add("apple");
fruits.add("orange");
fruits.add("banana");
// 拡張for文
for (String item : fruits) {
System.out.println(item);
}
// iterator()を使用し、通常のfor文
for (
Iterator<String> it = fruits.iterator(); // 初期化式
it.hasNext(); // 条件式
// 変化式はない
) {
String item = it.next(); // 次の要素を取得
System.out.println(item);
}
}
}
次の要素を取得するnext()メソッドは、取得する前に毎回checkForComodificationメソッドを呼び、expectedModCountとmodCountが比較されます。
expectedModCountは、iteratorやlist iteratorが作成されたときに、そのコレクションのmodCountの値が記録されます。modCountは、コレクションが変更されるたびにインクリメントされるカウンターです。expectedModCountとmodCountの値が異なる場合、そのiteratorやlist iteratorが作成された後にコレクションが変更されたことを意味し、ConcurrentModificationExceptionがスローされます。
for loop中に要素の変更をしなければいけない場合は、iteratorのremove()メソッドを使うことでエラーは発生しませんが、コレクションの反復処理中にリスト要素を直接修正することは予期せぬ副作用を発生させる可能性があるため避けるべきです。
continue文は、forループの中で、現在の反復処理の残りの文をスキップして、次の反復処理に移るために使用することができます。以下の例では、continue文を使って偶数番号を飛ばし、奇数番号だけを出力します。
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue;
}
System.out.println(i);
}
break文は、forループの中で使用すると、ループを途中で終了させることができます。以下の例では、iが5になった時点でループが終了し、1から4までの数字だけが出力されます。
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
System.out.println(i);
}
forループにラベルを付けたものでこのラベルは、break文やcontinue文と一緒に使うことで、ループの流れを制御することができます。
class Main {
public static void main(String[] args){
outer: for (int i = 1; i <= 3; i++) {
inner: for (int j = 1; j <= 3; j++) {
if (i == 2 && j == 2) {
break outer;
}
System.out.println(i + "," + j);
}
}
}
}
この例では、iが2、jが2のとき、break outerで内側ループと外側ループの両方を終了させています。continue文を使って特定のループの残りの文をスキップしたい場合にも、ラベル付きforループを使用することがあります。
outer: for (int i = 1; i <= 3; i++) {
inner: for (int j = 1; j <= 3; j++) {
if (i == 2 && j == 2) {
continue outer;
}
System.out.println(i + "," + j);
}
}
この例では、iが2、jが2のとき、continue outerで内側ループの残りの文を飛ばし、次外側ループの繰り返しに移っています。
反復処理を行う場合、無限ループに注意することが重要です。無限ループとは、決して終了しないループのことで、プログラムが無限に実行され、メモリやCPU時間などのリソースを消費してしまう可能性があります。
無限ループが発生する方法はいくつかあります。
for (int i = 1; i > 0; i++) {
// infinite loop
}
この例では、ループ条件i > 0が常に真であるため、ループは無限に実行されます。
for (;;) {
// infinite loop
}
この例では、ループ条件が省略されているため、ループは無限に実行されます。ループ条件がチェックされない場合、ループは永久に実行されます。
for (int i = 1; i <= 10;) {
// infinite loop
}
この例では、ループ変数iはループ本体で更新されないので、ループ条件i <= 10は常に真となり、ループは無限に実行されます。
無限ループを避けるには、ループ条件を確認し、ループ変数を正しく更新することが重要です。また、ループ条件が期待通りに満たされない場合に備えて、ループの反復回数を制限するためのカウンタやタイマーを入れておくとよいでしょう。
for文の練習については、CS基礎/中級/制御フローで詳しく学習できます。