カテゴリー
Java

Java(Step7-2)

メソッド

整数の内部

値はビットとして表現されます。ここでは、整数の内部のビットを扱う演算子を説明していきます。

ビット単位の論理演算

論理演算は、論理型でなく、整数型に対しても適用できます。整数型オペランドに論理演算子を適用すると、ビット単位の論理演算が行われます。整数型に対して適用できるビット単位の論理演算子をまとめたのが以下の表です。

x & yx と y のビット単位の論理積を生成。
x | yx と y のビット単位の論理和を生成。
x ^ yx と y のビット単位の排他的論理和を生成。
~xx のビット単位の補数を生成。
ビット単位の論理演算子

&ビット論理積演算子(bitwise and operator)、|ビット論理和演算子(bitwise inclusive or operator)、^ビット排他的論理和演算子(bitwise exclusive or operator)、~ビット単位の補数演算子(bitwise complement operator)と呼ばれます。これらの演算子で行われる論理演算の真理値表が以下になります。

ビット単位の論理演算の真理値表

2つの整数を読み込んで、ビット単位の論理演算を行った結果を表示するプログラムを以下に示します。

//BitwiseOperation.java
//int型整数のビット単位の論理積・論理和・排他的論理和・補数を表示
import java.util.Scanner;

public class BitwiseOperation {
    //int型のビット構成を表示
    static void printBits(int x){
        for(int i = 31; i >= 0; i--)
            System.out.print(((x >>> i & 1) == 1) ? '1' : '0');
    }
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("2つの整数を入力してください。");
        System.out.print("a:"); int a = stdIn.nextInt();
        System.out.print("b:"); int b = stdIn.nextInt();

        System.out.print("a     = "); printBits(a);
        System.out.print("\nb     = "); printBits(b);
        System.out.print("\na & b = "); printBits(a & b);
        System.out.print("\na | b = "); printBits(a | b);
        System.out.print("\na ^ b = "); printBits(a ^ b);
        System.out.print("\n~a    = "); printBits(~a);
        System.out.print("\n~b    = "); printBits(~b);
        stdIn.close();
    }

}
BitwiseOperation.java実行結果

シフト演算

メソッド printBits に初めて登場する演算子があります。>>>演算子(>>> operator)です。この演算子と<<演算子(<< 演算子)と>>演算子(>> 演算子)は、整数中のビットを左または右にシフトした値を生成する演算になります。これらの演算子をまとめて、シフト演算子(shift operator)と呼びます。その働きを以下のプログラムで示します。

//ShiftOperation.java
//int型の値を左右にシフトした値を表示
import java.util.Scanner;

public class ShiftOperation {
    //int型のビット構成を表示
    static void printBits(int x){
        for(int i = 31; i >= 0; i--)
            System.out.print(((x >>> i & 1) == 1) ? '1' : '0');
    }

    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.print("整数:"); int x = stdIn.nextInt();
        System.out.print("シフトするビット数:"); int n = stdIn.nextInt();

        System.out.print("整数    = "); printBits(x);
        System.out.print("\nx <<  n = "); printBits(x << n);
        System.out.print("\nx >>  n = "); printBits(x >> n);
        System.out.print("\nx >>> n = "); printBits(x >>> n);
        stdIn.close();
    }
}
ShiftOperation.java実行結果
  • x << n …左シフト

xをnビット左にシフトして、空いたビットに0を詰めた値を生成します。シフトの結果は x × 2のn乗となります。

  • x >> n …右シフト

右方向への算術シフト(arithmetic shift)を行います。最上位の符号ビット以外のビットをシフトし、空いたビットをシフト前の符号ビットで埋め尽くした値を生成します。

また、1ビット左にシフトすると値が2倍になり、1ビット右にシフトすると値が2分の1になる関係性が保たれます。xが非負の値を持つと、x + 2のn乗の商の整数部がシフト結果になります。

  • x >>> n …右シフト

右方向への論理シフト(logical shift)を行います。符号ビットを特別に考慮することなく、まるごとnビットシフトした値を生成します。xが負の値であれば、符号ビットが1から0に変わるため、演算によって得られる結果は正の値になります。

ビット数のカウント

int型の整数を構成する32個のビットの中に”1″であるビットが何個あるかをカウントするプログラムを作りましょう。

//CountBits.java
//int型整数中の1であるビット数をカウント
import java.util.Scanner;

public class CountBits {
    //int型のビット構成を表示
    static void printBits(int x){
        for(int i = 31; i >= 0; i--)
            System.out.print(((x >>> i & 1) == 1) ? '1' : '0');
    }
    //int型整数中の1であるビット数をカウント
    static int countBits(int x){
        int bits = 0;
        while (x != 0){
            if((x & 1) == 1) bits++;
            x >>>= 1;
        }
        return bits;
    }

    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.print("整数:");
        int x = stdIn.nextInt();

        System.out.print("ビット構成 = ");
        printBits(x);
        System.out.println("\n1であるビット数 = " + countBits(x));
    }
}
CountBits.java実行結果

メソッド countBits は、仮引数xに受け取った整数中に存在する”1″であるビットの個数をカウントして、その値を返すメソッドです。

  1. xと1の論理積が1となるかどうかによって、xの最下位ビットの値を調べます。その結果が1であれば、xの最下位ビットは1になるので、bitsをインクリメントします。
  2. 全ビットを右に1ビット論理シフトします。その結果、調べ終わった最下位ビットがはじき出されます。
カテゴリー
Java

Java(Step7-1-2)

メソッド

メソッド②

voidメソッド

以前、記号文字 ‘*’ を並べて直角三角形を表示するプログラムを作成したのを覚えているでしょうか?これを、メソッドを用いてプログラムを改良しましょう。

//IsoscelesTriangleLB.java
//左下が直角の直角三角形を表示
import java.util.Scanner;

public class IsoscelesTriangleLB {
    
    static void putStars(int n){
        while(n-- > 0)
            System.out.print('*');
    }

    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("直角三角形を表示");
        System.out.print("段数は:");
        int n = stdIn.nextInt();

        for(int i = 1; i <= n; i++){
            putStars(i);
            System.out.println();
        }
        stdIn.close();
    }
}
IsoscelesTriangleLB.java実行結果

メソッドputStarsは、n個の ‘*’ を連続して表示します。表示をするだけで返却する値はありません。そのため、このようなメソッドの返却型を void と宣言します。voidメソッドでは、値を返さないため、return文は必要ではありません。

メソッドの汎用性

次に、右下側が直角である直角三角形を表示するプログラムを作りましょう。

//IsoscelesTriangleRB.java
//右下が直角の直角三角形を表示
import java.util.Scanner;
public class IsoscelesTriangleRB {

    static void putChars(char c, int n){
        while(n-- > 0)
            System.out.print(c);
    }

    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("直角三角形を表示");
        System.out.print("段数は:");
        int n = stdIn.nextInt();

        for(int i = 1; i <= n; i++){
            putChars(' ', n - i);
            putChars('+', i);
            System.out.println();
        }
        stdIn.close();
    }
}
IsoscelesTriangleRB.java実行結果

今回のプログラムで新しく定義したのが、putCharsというメソッドです。このメソッドは、仮引数cに与えられた文字を連続してn個表示します。任意の文字を表示できるという点でより汎用性が高くなっています。

他のメソッドの呼び出し

ここまでのプログラムでは、mainメソッドの中から標準ライブラリメソッドや自作のメソッドを呼び出していました。自作のメソッド内でもメソッドを呼び出せます。そのような例を以下に示します。

//SquareRectangle.java
//長方形と正方形を表示
import java.util.Scanner;

public class SquareRectangle {
    
    //文字cをn個表示
    static void putChars(char c, int n){
        while(n-- > 0)
        System.out.print(c);
    }

    //文字'+'を並べ正方形を表示
    static void putSquare(int n){
        for(int i = 1; i <= n; i++){
            putChars('+', n);
            System.out.println();
        }
    }

    //文字'*'を並べ高さhで幅wの長方形を表示
    static void putRectangle(int h, int w){
        for(int i = 1; i <= h; i++){
            putChars('*', w);
            System.out.println();
        }
    }

    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.print("正方形を表示");
        System.out.print("一辺は:"); int n = stdIn.nextInt();
        putSquare(n);

        System.out.print("長方形を表示");
        System.out.print("高さは:"); int h = stdIn.nextInt();
        System.out.print("横幅は:"); int w = stdIn.nextInt();
        putRectangle(h, w);
        stdIn.close();
    }
}
SquareRectangle.java実行結果

メソッドputCharsは、前のプログラムと同じですが、正方形と長方形を表示するメソッド内で呼び出されています。

有効範囲

変数はメソッドの中だけでなく、外で宣言することもできます。その例を以下に示します。変数xの値が異なる場所で宣言されていますが、これらの識別子の通用する有効範囲(scope)が異なります。

//Scope.java
//識別子の有効範囲を確認

public class Scope {
    static int x = 700;
  //クラス有効範囲:フィールド

    static void printX(){
        System.out.println("x = " + x);
    }

    public static void main(String[] args){
        System.out.println("x = " + x);

        int x = 800;
  //ブロック有効範囲:局所変数
        System.out.println("x = " + x);
        System.out.println("Scope.x = " + Scope.x);
        printX();
    }
}
Scope.java実行結果
クラス有効範囲(class scope)

メソッドの外で宣言されている変数の識別子は、そのクラス全体に通用します。メソッドの外で宣言される変数はフィールド(field)と呼ばれ、メソッドの内で宣言された変数とは区別されます。

ブロック有効範囲

ブロック内部で宣言された変数は局所変数(local variable)呼ばれます。局所変数の識別子は、宣言された直後からそのブロックの終端である } まで適用されます。

引数を受け取らないメソッド

次のプログラムは、暗算力のトレーニングを行うものです。3桁の数の和を求めます。誤った数値は受け付けず、正解するまで続きます。

//MentalArithmetic.java
//暗算力トレーニング
import java.util.Random;
import java.util.Scanner;

public class MentalArithmetic {

    static Scanner stdIn = new Scanner(System.in);

    static boolean confirmRetry(){
        int cont;
        do{
            System.out.print("もう一度?<Yes…1/No…0>");
            cont = stdIn.nextInt();
        }while(cont != 0 && cont != 1);
        return cont == 1;
    }

    public static void main(String[] args){
        Random rand = new Random();

        System.out.println("暗算力トレーニング");
        do{
            int x = rand.nextInt(900) + 100;
            int y = rand.nextInt(900) + 100;
            int z = rand.nextInt(900) + 100;
        
            while(true){
                System.out.print(x + " + " + y + " + " + z + " = ");
                int k = stdIn.nextInt();
                if(k == x + y + z)
                    break;
                System.out.println("違いますよ");
            }
        }while(confirmRetry());

    }
}
MentalArithmetic.java実行結果

mainメソッドでは、3つの乱数x、y、zを生成した上で問題として提示します。キーボードから読み込んだ値がkが、3つの和と等しければ正解です。不正解である限り、while文が繰り返されるので終わることが出来ません。confirmRetryは、もう一度トレーニングするかの確認を行います。キーボードから入力された1か0の値でtrueかfalseを返します。

カテゴリー
Java

Java(Step7-1-1)

メソッド

メソッドとは

何か工作をする際に、色々な部品を組み合わせて作ることがあると思います。プログラムも部品が組み合わさって構成されています。プログラムの部品の中で、最も小さい単位がメソッドになります。

メソッド①

3人分の身長・体重・年齢を読み込んで、それぞれの最大値を表示するプログラムを作りましょう。各データを配列で扱ってみましょう。

//MaxHwa.java
//3人の身長・体重・年齢の最大値を求めて表示
import java.util.Scanner;

public class MaxHwa {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        int[] height = new int[3];
        int[] weight = new int[3];
        int[] age = new int[3];

        for(int i = 0; i < 3; i++){
            System.out.print("[" + (i + 1) + "]");
            System.out.print("身長:"); height[i] = stdIn.nextInt();
            System.out.print("体重:"); weight[i] = stdIn.nextInt();
            System.out.print("年齢:"); age[i] = stdIn.nextInt();
        }

        //身長の最大値を求める
        int maxHeight = height[0];
        if(height[1] > maxHeight) maxHeight = height[1];
        if(height[2] > maxHeight) maxHeight = height[2];

        //体重の最大値を求める
        int maxWeight = weight[0];
        if(weight[1] > maxWeight) maxWeight = weight[1];
        if(weight[2] > maxWeight) maxWeight = weight[2];

        //年齢の最大値を求める
        int maxAge = age[0];
        if(age[1] > maxAge) maxAge = age[1];
        if(age[2] > maxAge) maxAge = age[2];

        System.out.println("身長の最大値は" + maxHeight + "です。");
        System.out.println("体重の最大値は" + maxWeight + "です。");
        System.out.println("年齢の最大値は" + maxAge + "です。");
        stdIn.close();
    }
}
MaxHwa.java実行結果

このプログラムを以下の方針に従って改良していきましょう。

  • ひとまとまりの手続きは、1つの部品としてまとめる

プログラムの部品を実現するのが、メソッド(method)です。本プログラムの改良に必要なのは、「3つのint型の値を渡したら、その最大値を求めて返す」という部品です。

処理を行うメソッドを使っていくために以下に示す2つのことを学びましょう。

  • メソッドの作り方…メソッドの宣言
  • メソッドの使い方…メソッドの呼び出し

メソッドの宣言

まずは、メソッドの作り方です。以下に示すのは、「3つのint型の整数値を受け取って、その最大値を求めるメソッド」のメソッド宣言(method declaration)です。

static int max(int a, int b, int c){
       int max = a;
       if(b > max) max = b;
       if(c > max) max = c;
       return max;
}

まずは、各部の概略を理解しましょう。

メソッド頭部(method header)

プログラムの部品であるメソッドの名前と仕様を記した部分です。メソッド頭部という名前ですが、意味合いとしては顔に近いです。

  1. 返却型(return type)…自分を呼び出した部品=メソッドに戻す値である返却値の型です。本メソッドの場合、返却するのは最大値ですから、その型であるintとなっています。
  2. メソッド名(method type)…メソッドの名前です。メソッドは、名前を元に他の部品から呼び出されます。
  3. 仮引数並び(formal parameter list)…メソッドは、補助的な指示を受け取れます。受け取るための変数である仮引数を()の中で宣言します。複数の仮引数を受け取る場合は、コンマで区切ります。

メソッド本体(method body)

メソッドの本体は、ブロック({}で囲まれた0個以上の文の集合)です。メソッドmaxの本体では、maxという変数が宣言されています。メソッド中でのみ利用する変数は、そのメソッド中で宣言、利用するのが原則になります。

先ほどのプログラムをメソッドを用いて改良したのが以下になります。

//MaxHwaMethod.java
//3人の身長・体重・年齢の最大値を求めて表示(メソッド)
import java.util.Scanner;

public class MaxHwaMethod {
    static int max(int a, int b, int c){
        int max = a;
        if(b > max) max = b;
        if(c > max) max = c;
        return max;
    }
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        int[] height = new int[3];
        int[] weight = new int[3];
        int[] age = new int[3];

        for(int i = 0; i < 3; i++){
            System.out.print("[" + (i + 1) + "]");
            System.out.print("身長:"); height[i] = stdIn.nextInt();
            System.out.print("体重:"); weight[i] = stdIn.nextInt();
            System.out.print("年齢:"); age[i] = stdIn.nextInt();
        }

        //身長の最大値を求める
        int maxHeight = max(height[0], height[1], height[2]);
        
        //体重の最大値を求める
        int maxWeight = max(weight[0], weight[1], weight[2]);
        
        //年齢の最大値を求める
        int maxAge = max(age[0], age[1], age[2]);
       
        System.out.println("身長の最大値は" + maxHeight + "です。");
        System.out.println("体重の最大値は" + maxWeight + "です。");
        System.out.println("年齢の最大値は" + maxAge + "です。");
        stdIn.close();
    }
}
MaxHwaMethod.java実行結果

メソッド呼び出し

部品であるメソッドを利用することを「メソッド呼び出す」といいます。このプログラムで、メソッドmaxを呼び出しているのが、以下の部分です。

//身長の最大値を求める
int maxHeight = max(height[0], height[1], height[2]);
//体重の最大値を求める
int maxWeight = max(weight[0], weight[1], weight[2]);
//年齢の最大値を求める
int maxAge = max(age[0], age[1], age[2]);

身長・体重・年齢の最大値を求めるために、メソッドmaxを3回呼び出しています。メソッド呼び出しには、メソッド名後ろに( )を付けて行います。この( )は、メソッド呼び出し演算子(method invocation operator)です。メソッドに対する補助的な指示として与える実引数(actual argument)は、コンマで区切って( )の中に与えます。

メソッド呼び出しが行われると、プログラムの流れはそのメソッドに一気に移ります。呼び出されたメソッドでは、仮引数用の変数が生成されると同時に、実引数の値で初期化されます。初期化が終了すると、メソッド本体が実行されます。メソッドmaxは呼び出し元へ、最大値を返却します。それを行うのが以下の文です。

return max;

この文をreturn文と呼びます。これが実行されると、プログラムの流れは呼び出し元に戻ります。

return文

メソッドの実行を終了して、プログラムの流れを呼び出し元へと戻すreturn文は、返却値を指定するための式は省略できます。

3値の最大値求めるメソッドmaxを作ったので、そのメソッドを使ったプログラムを作ってみましょう。

//Max3Method.java
//3つの整数値の最大値を求める(メソッド)
import java.util.Scanner;

public class Max3Method {
    static int max(int a, int b, int c){
        int max = a;
        if(b > max) max = b;
        if(c > max) max = c;
        return max;
    }

    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.print("整数a:"); int a = stdIn.nextInt();
        System.out.print("整数b:"); int b = stdIn.nextInt();
        System.out.print("整数c:"); int c = stdIn.nextInt();

        System.out.println("最大値は" + max(a, b, c) + "です。");
        stdIn.close();
    }
}
Max3Method.java実行結果

このプログラムでは、メソッドに渡している実引数すべて変数です。変数だけでなく、整数リテラルなどの定数を渡すこともできます。

値渡し

べき乗(xの2乗)を求めるメソッドを作成しましょう。xがdouble型でnがint型であれば、プログラムは以下のようになります。

//Power.java
//べき乗をもとめる
import java.util.Scanner;
public class Power {
    
    //xのn乗を返す
    static double power(double x, int n){
        double tmp = 1.0;

        for(int i = 1; i <= n; i++)
            tmp *= x;
        return tmp;
    }

    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("aのb乗を求めます。");
        System.out.print("実数a:"); double a = stdIn.nextDouble();
        System.out.print("整数b:"); int b = stdIn.nextInt();

        System.out.println(a + "の" + b + "乗は" + power(a, b) + "です。" );
        stdIn.close();
    }
}
Power.java実行結果

nが整数ですから、xをn回掛け合わせた値がxのn乗です。メソッドをpowerでは、1.0で初期化された変数tmpに対して、xの値をn回掛けています。for文が終了した時に最終的な値がtmpに渡されます。このように引数として値がやり取りされるメカニズムを値渡し(pass by value)と呼ばれます。

カテゴリー
Java

Java(Step6-2)

配列

多次元配列

前の記事では、構成要素が直線状に並んだ配列を説明しました。構成要素自体が配列となっている複雑な構造の配列を、多次元配列と呼びます。

多次元配列

配列が構成要素型になっている配列が2次元配列です。そして、2次元配列が構成要素型となっている配列が3次元配列になります。これらを1次元配列と区別して多次元配列(multidimensional array)と言います。

2次元配列

まずは、多次元配列の中で最も単純な構造のint型の2次元配列を考えてみましょう。その実態は次のようになります。

『int 型を構成要素型とする配列』を構成要素とする配列

この配列の型は int[][] であり、以下に示すどれを使用しても宣言できます。

  1. int[][] x;
  2. int[] x[]; 『int 型を構成要素型とする配列』を構成要素とする配列の宣言
  3. int x[][];

具体的な例を考えていきましょう。『int 型を構成要素型とする要素数3の配列』を構成要素とする要素数2の配列

本体の生成を同時に行うと、配列変数の宣言は以下のようになります。

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

この宣言によって生成される配列xのイメージを表したのが次の図になります。

2次元配列のイメージ
3次元配列

続いて、3次元配列です。例えば、long型の3次元配列の型はlong[][][]になります。ここでは、以下の配列を考えてみましょう。

long[][][] y = new long[2][3][4];

ここで宣言されたyの型は、次のようになります。

『long型を構成要素型とする配列を構成要素型とする配列』を構成要素型とする配列

2次元配列xと、3次元配列yの構成要素型は、それぞれ次のようになっています。

  • x…int 型を構成要素型とする配列
  • y…long 型を構成要素型とする配列を構成要素型とする配列

これらの配列を、それ以上分解できない要素にまで分解すると、配列xはint型となり、配列yはlong型となります。このような型を要素型(element type)と呼び、要素型レベルの構成要素を要素(element)と呼びます。そして、全要素の個数が要素数です。

プログラム例

2次元配列を生成して全要素を0から99の乱数で格納するプログラムを以下に示します。

//Array2D.java
//二次元配列を生成して全要素を乱数で埋め尽くす
import java.util.Random;
import java.util.Scanner;

public class Array2D {
    public static void main(String[] args){
        Random rand = new Random();
        Scanner stdIn = new Scanner(System.in);

        System.out.print("行数:"); //行数を読み込む
        int h = stdIn.nextInt();

        System.out.print("列数:"); //列数を読み込む
        int w = stdIn.nextInt();

        int[][] x = new int[h][w];

        for(int i = 0; i < h; i++)
            for(int j = 0; j < w; j++){
                x[i][j] = rand.nextInt(100);
                System.out.println("x[" + i + "][" + j + "] = " + x[i][j]);
            }
        stdIn.close();
    }
}
Array2D.java実行結果

もう一つのプログラムでは、2つの行数の和を求めて表示するプログラムになっています。

//Matrix.java
//2行3列の行数を加算する

public class Matrix {
    public static void main(String[] args){
        int[][] a = {{1,2,3}, {4,5,6}};
        int[][] b = {{6,3,4}, {5,1,2}};
        int[][] c = {{0,0,0},{0,0,0}};

        for(int i = 0; i < 2; i++)
            for(int j = 0; j < 3; j++)
                c[i][j] = a[i][j] + b[i][j];
        
        System.out.println("行列a:");
        for(int i = 0; i < 2; i++){
            for(int j = 0; j < 3; j++)
                System.out.printf("%3d", a[i][j]);
            System.out.println();
        }

        System.out.println("行列b:");
        for(int i = 0; i < 2; i++){
            for(int j = 0; j < 3; j++)
                System.out.printf("%3d", b[i][j]);
            System.out.println();
        }

        System.out.println("行列c:");
        for(int i = 0; i < 2; i++){
            for(int j = 0; j < 3; j++)
                System.out.printf("%3d", c[i][j]);
            System.out.println();
        }
    }    
}
Matrix.java実行結果

多次元配列の内部

多次元配列の内部を詳しく説明していきます。まずは、配列変数の宣言と本体の生成を個別に行います。以下のように宣言、処理を分解します。

int[][] x;
x = new int[2][];
x[0] = new int[2];
x[1] = new int[2];

実に4段階も分解でき、2次元配列の内部構造が複雑なことを示します。以下の図を見ながら理解を深めましょう。

2次元配列の物理的なイメージ
  1. 2次元配列xの宣言になります。int[][]型のxは、配列本体ではなく、配列変数です。

2. 配列本体を生成するとともに、xがそれを参照するように代入を行います。ここで生成するのは、以下の配列になります。

構成要素型がint[]型で構成要素数が2の配列

3. 配列本体を生成するとともに、x[0]がそれを参照するように代入を行います。ここで生成するのは、以下の配列になります。

構成要素型がint[]型で構成要素数が2の配列

4. 配列本体を生成するとともに、x[1]がそれを参照するように代入を行います。ここで生成するのは、以下の配列になります。

構成要素型がint[]型で構成要素数が2の配列

凸凹な2次元配列の内部

上記の配列xの構成要素であるx[0]とx[1]は、それぞれが独立した配列変数です。そのため、配列の要素数は同一である必要はありません。生成する個々の配列の要素数を異なるものにすれば、凸凹な配列になります。

//UnevennessArray.java
//凹凸な2次元配列

public class UnevennessArray {
    public static void main(String[] args){
        int[][] c;
        c = new int[3][];
        c[0] = new int[5];
        c[1] = new int[3];
        c[2] = new int[4];

        for(int i = 0; i < c.length; i++){
            for(int j = 0; j < c[i].length; j++)
                System.out.printf("%3d", c[i][j]);
            System.out.println();
        }
    }
}
UnevennessArray.java実行結果

変数cが参照するのは、以下の配列です。

  • c…構成要素型がint[]型で構成要素数が3の配列

そして、各構成要素 c[0]、c[1]、c[2]は、以下の配列を参照します。

  • c[0]…構成要素型がint[]型で構成要素数が5の配列
  • c[1]…構成要素型がint[]型で構成要素数が3の配列
  • c[2]…構成要素型がint[]型で構成要素数が4の配列

初期化子

以下の例を見てましょう。配列aに対して初期化子が与えられています。この初期化子は、縦横に並べて宣言すると読みやすくなります。

int[][]a = {
   {1, 2, 3},   //0行目の要素に対する初期化子
   {4, 5, 6},   //1行目の要素に対する初期化子
};

0行目と1行目の間のコンマはなくても構いません、しかし、以下のメリットがあります。

  • 初期化子を縦に並べた際の見かけ上のバランスがとれる。
  • 行単位での初期化子の追加・削除が容易になる。
カテゴリー
Java

Java(Step6-1-2)

配列

配列②

配列の要素の最大値を求める

配列の要素の最大値を求める手続きを考えます。配列の要素数が3であれば、3つの要素a[0],a[1],a[2]の最大値は以下のようになります。

max = a[0];
if(a[1] > max) max = a[1];
if(a[2] > max) max = a[2];

このif文をより柔軟に書き換えた形が以下になります。

max = a[0];
for(int i = 1; i < a.length; i++)
if(a[i] > max) max = a[i];

このように配列の要素1つずつ順になぞっていく手続きを走査(traverse)と呼びます。

キーボードから読み込んだ値から最高点を求めるプログラムを走査を使って作成しましょう。

//HighScore.java
//点数を読み込んで最高値を表示
import java.util.Scanner;

public class HighScore {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);
        final int num = 5;
        int[] score = new int[num];

        System.out.println(num + "人分の点数を入力してください。");
        for(int i = 0; i < num; i++){
            System.out.print((i + 1) + "番の点数:");
            score[i] = stdIn.nextInt();
        }

        int max = score[0];
        for(int i = 1; i < score.length; i++)
            if(score[i] > max) max = score[i];

        System.out.println("最高点は" + max + "点です。");
        stdIn.close();
    }
}
HighScore.java実行結果

線形探索

配列の要素の中に、ある値が含まれているか調べるプログラムを作りましょう。ある値をもつ要素の存在を調べることを探索(search)と呼び、調べる値のことをキー(key)と呼びます。

探索は、配列の要素を先頭から順に走査することによって実現できます。探すべきキー値と同じ要素に会ったら、探索が成功します。これを線形探索(liner search)または逐次探索(sequential search)のアルゴリズムと呼びます。

それを実現したプログラムが以下になります。

//LinearSearch.java
//線形探索
import java.util.Random;
import java.util.Scanner;

public class LinearSearch {
    public static void main(String[] args){
        Random rand = new Random();
        Scanner stdIn = new Scanner(System.in);

        final int n = 12;
        int[] a = new int[n];

        for(int j = 0; j < n; j++)
            a[j] = rand.nextInt(10);

        System.out.print("配列aの全要素の値\n{");
        for(int j = 0; j < n; j++)
            System.out.print(a[j] + " ");
        System.out.println("}");

        System.out.print("探す数値:");
        int key = stdIn.nextInt();

        int i;
        for(i = 0; i < n; i++)
            if(a[i] == key)
                break;
        if(i < n)
            System.out.println("それはa[" + i + "]にあります。");
        else
            System.out.println("存在しません。");
        
        stdIn.close();
    }
}
LinearSearch.java実行結果

線形探索アルゴリズムは、キーと同じ要素が複数個存在する場合にその中で、最も先頭に位置する要素を見つけます。

拡張for文

ここまでのプログラムを見て分かるように、配列を扱う際はほとんどfor文を使用します。もう1つのfor文である拡張for文(enhanced statement)を用いると、配列の走査を簡潔に実現できます。そのプログラム例が以下になります。

//ArraySumForIn.java
//配列の全要素の和を求めて表示(拡張for文)

public class ArraySumForIn {
    public static void main(String[] args){
        double[] a = {1.0, 2.0, 3.0, 4.0, 5.0};

        for(int i = 0; i < a.length; i++)
            System.out.println("a[" + i + "] =" + a[i]);
        
        double sum = 0;
        for(double i : a)
            sum += i;
        System.out.println("全要素の和は" + sum + "です。");
    }    
}
ArraySumForIn.java実行結果

このfor文は以下のように理解しましょう。

  • 配列 a の先頭から末尾までの全要素を1個ずつ走査します。ループ本体では、現在着目している要素を i と表現します。

拡張for文を利用することには以下のメリットがあります。

  • 配列の長さ(要素数)を調べる手間が省ける
  • イテレータと同じ方法で走査を行える。

配列を逆順に並び替える

配列の全要素を逆順に並べ替えるプログラムを作りましょう。まずは、アルゴリズムを考えましょう。要素数がnである配列の要素を逆転させるには以下のようにアルゴリズムを組みましょう。

for(int i = 0; i < n / 2; i++)
a[i]とa[n - i - 1]を交換する

一般に要素数がnであれば、交換回数はn / 2回になります。なぜなら、要素数が奇数のときは、中央の要素を交換する必要がないからです。

このアルゴリズムをもとに作ったプログラムが次になります。

//ReverseArray.java
//配列の要素の順序を逆順に並べて表示
import java.util.Random;
import java.util.Scanner;

public class ReverseArray {
    public static void main(String[] args){
        Random rand = new Random();
        Scanner stdIn = new Scanner(System.in);

        System.out.print("要素数:");
        int n = stdIn.nextInt();
        int[] a = new int[n];

        for(int i = 0; i < n; i++){
            a[i] = 10 + rand.nextInt(90);
            System.out.println("a[" + i + "] :" + a[i]);
        }

        for(int i = 0; i < n / 2; i++){
            int t = a[i];
            a[i] = a[n - i - 1];
            a[n - i - 1] = t;
        }
        
        System.out.println("要素の並びを逆にしました。");
        for(int i = 0; i < n; i++)
            System.out.println("a[" + i + "] :" + a[i]);

        stdIn.close();
    }      
}
ReverseArray.java実行結果

配列のコピー

ある配列の全要素の値を、別の配列にまるまるコピーするプログラムを書いてみましょう。大事なのは、コピーする際は繰り返し文によって全要素を逐一コピーする必要があることです

//CoppyArray.java
//配列の全要素をコピー
import java.util.Scanner;

public class CoppyArray {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);
        
        System.out.print("要素数:");
        int n = stdIn.nextInt();
        int[] a = new int[n];
        int[] b = new int[n];

        for(int i = 0; i < n; i++){
            System.out.print("a[" + i + "] = ");
            a[i] = stdIn.nextInt();
        }

        for(int i = 0; i < n; i++)
            b[i] = a[i];

        System.out.println("aの全要素を配列bにコピーしました。");

        for(int i = 0; i < n; i++)
        System.out.println("b[" + i + "] = " + b[i]);

        stdIn.close();
    }
}
CoppyArray.java実行結果

配列のコピーを行うのが、以下の部分です。これにより、a[0]をb[0]にa[1]をb[1]にと i の数だけコピーしていくことができます。

b[i] = a[i];

文字列の配列

文字列は、String型で表せるため、その配列の型もString[]となります。まずは、ジャンケンの手である「グー」「チョキ」「パー」という文字列の配列を考えてみましょう。要素型がString型で要素数が3の配列を生成するので、以下のように書くことができます。

String[] hands = new String[3];
hands[0] = "グー";
hands[1] = "チョキ";
hands[2] = "パー";

宣言の仕方も使い方も、int型やdouble型と変わりません。以下のように宣言すれば、配列生成時に各要素を初期化できます。

String[] hands = {"グー", "チョキ", "パー"};

次に文字列の配列を利用して、月名の英単語を使用するプログラムを作りましょう。ランダムに選ばれた月の英単語を表示して、それが何月かを解答させ、当てるまで繰り返すようにします。

  1. 出題する月の値を0から11の乱数として生成し、その文字列を表示します。
  2. 解答を入力するように促して、月の値を m に読み込みます。
  3. 読み込んだ m が 英単語の月と等しくなるまで、while文で制御します。

プログラムにすると、以下のようになります。

//MonthCAI.java
//月を表す英単語の学習プログラム
import java.util.Random;
import java.util.Scanner;

public class MonthCAI {
    public static void main(String[] args){
        Random rand = new Random();
        Scanner stdIn = new Scanner(System.in);
        String[] month = {"Jenuary", "February", "March", "April", "May",
    "June", "July", "August", "September", "October", "November", "December"};

        int num = rand.nextInt(12);
        System.out.println("問題は" + month[num]);

        while(true){
            System.out.print("何月かな?:");
            int m = stdIn.nextInt();
            
            if(m == num + 1) break;
            System.out.println("違います。");
        }
        System.out.println("正解です。");
        stdIn.close();
    }    
}
MonthCAI.java実行結果

参照型とオブジェクト

以下のプログラムを実行してみましょう。これは、配列の要素の値ではなく、配列変数そのものの値を表示するプログラムです。

//PrintArray.java
//配列変換の値を表示

public class PrintArray {
    public static void main(String[] args){
        int[] a = new int[5];
        System.out.println("a = " + a);

        a = null;
        System.out.println("a = " + a);
    }
}
PrintArray.java実行結果
参照型とオブジェクト

new で生成される配列本体は、通常の変数と違って、プログラム実行時に生成され、そのための記憶領域が動的に確保されます。配列の本体は、通常の変数と異なるため、オブジェクト(object)と呼ばれます。オブジェクトを指すための変数の型が、参照型(reference type)となります。

空型と空参照・空リテラル

配列変数aにnullを代入していますが、nullは、空リテラル(null literal)と呼ばれます。代入されたaは、空参照(null reference)となります。空参照とは、何も参照していないことを表す特殊な参照で、型は空型(null type)になります。

ガーベジコレクション

配列変数に対して、nullを代入するなどして、もともとの配列本体はどこからも参照されないゴミになってしまいます。ゴミの放置は記憶領域の不足に繋がります。そのため、参照されなくなったオブジェクト用の領域は、再利用できるように自動的に解放されます。再利用できるようにすることをガーベジコレクション(garbage collection)と言います。

finalな配列

配列変数は以下のように、final変数として宣言できます。

final int[] a = new int[5];

配列変数を final にしておけば、誤ってnullを代入したり、他の配列本体への参照を代入したりするという事故を防げます。

カテゴリー
Java

Java(Step6-1-1)

配列

配列①

同一型の変数の集まりは、バラバラでなく、ひとまとめにすると扱いやすくなります。そこで、配列が有効になります。

配列

「テストの点数」を集計するプログラムを作って学んでいきましょう。以下に示すのは、5人の点数を読み込んで合計と平均を求めるプログラムになります。

//PointSumAve.java
//点数を読み込んで合計点、平均点を表示

import java.util.Scanner;

public class PointSumAve {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        int sum = 0;
        System.out.println("5人の点数を入力してください。");

        System.out.print("1番の点数:");
        int akane = stdIn.nextInt();
        sum += akane;

        System.out.print("2番の点数:");
        int baba = stdIn.nextInt();
        sum += baba;

        System.out.print("3番の点数:");
        int cocone = stdIn.nextInt();
        sum += cocone;

        System.out.print("4番の点数:");
        int docomo = stdIn.nextInt();
        sum += docomo;

        System.out.print("5番の点数:");
        int eita = stdIn.nextInt();
        sum += eita;

        System.out.println("合計は" + sum + "点です。");
        System.out.println("平均は" + (double)sum / 5 + "点です。");
        stdIn.close();
    }
}
PointSumAve.java実行結果

このプログラムでは、一人に対して1つずつ変数を割り当てています。人の数が多くなると管理が大変になってきます。

他にも問題があり、同じような作業を繰り返し行うことです。

  • 点数の入力を促す。
  • キーボードから読み込んだ数値を変数に格納する。
  • 読み込んだ数をsumに加える。

これらをひとまとめに出来ればとても便利です。そこで、配列(array)と呼ばれるデータ構造で実現します。イメージは次の図のように考えるといいでしょう。

配列のイメージ

配列は、同一型の変数である構成要素(component)が直線状に並んだものです。個々の構成要素の型である構成要素型(component type)は、何でも構いません。

配列変数の宣言

普通の変数と同様に、配列も利用する際に宣言が必要です。以下の形式で行います。

int[] a; //int型を構成要素型とする配列の宣言①
int a[];  //int型を構成要素型とする配列の宣言②

どちらの形式でも構わないのですが、①を主に使って記述していきます。この宣言によって作られるものは、配列変数(array variable)と呼ばれる特殊な変数です。

配列本体の生成

配列の本体は、配列変数とは別に生成する必要があります。前述したプログラムでは、5人分のデータを収集したので5個の配列の本体を生成します。

new int[5];  int型を構成要素型とする構成要素が5個の配列の本体を生成

生成した配列本体と配列変数は関連付ける必要があります。関連付けは以下の代入で行えます。

a = new int[5];

これで、変数aが配列本体を参照することになります。配列変数の宣言の初期化子に、配列本体の生成式を組み込むとプログラムは簡潔になります。

int[] a = new int[5]; aは構成要素変数5の配列を参照
構成要素のアクセス

配列本体内の個々の構成要素のアクセス(読書き)は、以下に示すようにインデックス(index)を[]中に与えることによって行います。

配列変数名[インデックス]

インデックスには、「先頭の構成要素から何個後ろの構成要素であるか」を示すint型の値を与えます。

配列の構成要素

まずは、単純なプログラムを作成しましょう。構成要素型がint型である配列を作って、構成要素に値を代入して表示するプログラムです。

//IntArray1.java
//構成要素がint型の配列(構成要素は5:newによって本体を生成)

public class IntArray1 {
    public static void main(String[] args){
        int[] a = new int[5];

        a[1] = 27;
        a[2] = 55;
        a[4] = a[1] * 2;
        
        System.out.println("a[" + 0 + "] = " + a[0]);
        System.out.println("a[" + 1 + "] = " + a[1]);
        System.out.println("a[" + 2 + "] = " + a[2]);
        System.out.println("a[" + 3 + "] = " + a[3]);
        System.out.println("a[" + 4 + "] = " + a[4]);
    }
}
IntArray1.java実行結果
既定値

実行結果は、値を代入していないa[0]とa[3]の値が0になっていることを示しています。配列の構成要素は、自動的に0で初期化されるという規則があります。この点は通常の変数と大きく異なります。

構成要素が初期化される値のことを既定値(default value)と呼びます。各型の既定値をまとめたのが以下の表になります。

既定値
byteゼロ すなわち(byte)0
shortゼロ すなわち(short)0
intゼロ すなわち 0
longゼロ すなわち 0L
floatゼロ すなわち 0.0f
doubleゼロ すなわち 0.0d
char空文字 すなわち u0000
boolean偽 すなわち false
参照型空参照 すなわち null
各型の既定値

一般に、構成要素型がTypeである配列のことを「Type型配列」と呼びます。

要素数の取得

要素数がint型で要素数が5の配列を作って、その先頭から順に1,2,3,4,5を代入して表示するプログラムを作りましょう。

//IntArray2.java
//配列の各要素に1,2,3,4,5を代入表示

public class IntArray2 {
    public static void main(String[] args){
        int[] a = new int[5];

        for(int i = 0; i < a.length; i++)
            a[i] = i + 1;
        for(int i = 0; i < a.length; i++)
            System.out.println("a[" + i + "] = " + a[i]);
    }
}
画像に alt 属性が指定されていません。ファイル名: image-22.png
IntArray2.java実行結果

2つのfor文の制御式では、以下の形式が使われています。

配列変数名.length

これは、配列の要素数を取得するための式です。

配列の要素への値の読み込み

ここまでのプログラムの配列の要素数は、すべて定数でしたが、キーボードから読みこむ形に書きかえましょう。

//IntArrayScan.java
//配列の全要素に値を読み込んで表示
import java.util.Scanner;

public class IntArrayScan {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.print("要素数:");
        int n = stdIn.nextInt();
        int[] a = new int[n];

        for(int i = 0; i < n; i++){
            System.out.print("a[" + i + "] = ");
            a[i] = stdIn.nextInt();
        }

        System.out.println("");
        
        for(int i = 0; i < n; i++)
            System.out.println("a[" + i + "] =" + a[i]);
        stdIn.close();
    }
}
IntArrayScan.java実行結果

プログラムの流れは以下のようになっています。

  1. 配列の要素数を変数nに読み込む。
  2. 配列の宣言。要素数がnの配列本体を生成するとともに、それを参照をするように配列変数aが初期化されます。
  3. for文によってiを0からn-1までインクリメントしながら、配列の要素a[i]に値を読み込みます。その結果、配列aの全要素に値が読み込まれます。
  4. 全要素の値を表示します。
棒グラフの表示

次は、全要素をランダムな値を埋め尽くすようにしましょう。配列の全要素に1から10の乱数を代入して表示するプログラムを以下に示します。

//IntArrayRand.java
//配列の全要素に乱数を代入して棒グラフ表示
import java.util.Random;
import java.util.Scanner;

public class IntArrayRand {
    public static void main(String[] args){
        Random rand = new Random();
        Scanner stdIn = new Scanner(System.in);

        System.out.print("要素数:");
        int n = stdIn.nextInt();
        int[] a = new int[n];

        for(int i = 0; i < n; i++)
            a[i] = 1 + rand.nextInt(10);
        for(int i = 0; i < n; i++){
            System.out.print("a[" + i + "] :");
            for(int j = 0; j < a[i]; j++)
                System.out.print('*');
            System.out.println();
        }
        
        stdIn.close();
    }
}
IntArrayRand.java実行結果

最初のfor文では、配列aの全要素に乱数を代入しています。2つ目のfor文では、記号文字*を並べた棒グラフによって要素の値を表示します。そのために、変数iの値を0からn-1までインクリメントしながら、以下の処理をn回繰り返します。

  1. このfor文の繰り返し回数はa[i]で、この数だけ*を表示します。
  2. 改行

配列の初期化と代入

配列の全要素は既定値である0で初期化されます。もし、個々の要素に入れるべき値があらかじめ分かっているのであれば、明示的に初期化を行うべきです。配列本体の個々の要素を初期化するようなプログラムを作りましょう。

//IntArrayInit.java
//配列の各要素を数字で初期化表示

public class IntArrayInit {
    public static void main(String[] args){
        int[] a = {1,2,3,4,5};

        for(int i = 0; i < a.length; i++)
            System.out.println("a[" + i + "] = " + a[i]);
    }
}
IntArrayInit.java実行結果

配列に与える初期化子は、各要素に対する初期化しをコンマで区切って順に並べてそれを{}で囲んだものです。生成される配列の要素数は、初期化子の個数に基づいて自動的に決定されます。

配列による成績処理

成績処理のプログラムを配列を用いて作り直しましょう。そのプログラムが以下になります。

//PointSumAveArray.java
//点数を読み込んで合計点、平均点を表示
import java.util.Scanner;

public class PointSumAveArray {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);
        int sum = 0;
        final int num = 5;
        int[] score = new int[num];

        System.out.println(num + "人分の点数を入力してください。");
        for(int i = 0; i < num; i++){
            System.out.print((i + 1) + "番の点数:");
            score[i] = stdIn.nextInt();
            sum += score[i];
        }
        System.out.println("合計は" + sum + "点です。");
        System.out.println("平均は" + (double)sum / num + "点です。");
        stdIn.close();
    }
}
PointSumAveArray.java実行結果
  1. 学生の人数を整数リテラルではなく、final変数で表しています。
  2. 配列のインデックスは0から始まる値です。このプログラムでは、点数の入力をする際に、インデックスに1を加えた「*番目の点数」と表示しています。
カテゴリー
Java

Java(Step5-3)

基本型と演算

拡張表記

ここでは、改行文字などを表すための手段である拡張表記について説明します。

拡張表記

逆斜線記号「\」を先頭にした文字の並びによって単一の文字を表す表記法を、拡張表記(escape sequence)と呼びます。拡張表記は、文字リテラルや文字列リテラルで利用します。その一覧は以下のようになります。

拡張表記(escape sequence)
b 後退…表示位置を直前の位置へ移動する
f 書式送り…改ページして次のページの先頭に移動
n 改行…改行して次の行の先頭へ移動する
r 復帰…現在の行の先頭位置へ移動
t 水平タブ…次の水平タブへ移動する
文字 ” …二重引用符
文字 ‘ …単一引用符
文字 \ …逆斜線
ooo oooは8進数
Unicode拡張(Unicode escape)
uhhhh hhhhは16進数…16進数でhhhhの値をもつ文字
拡張表記とUnicode拡張

■\b…後退

後退を出力すると、現表示位置が(その行内での直前の位置)に移動します。

■\f…書式送り

書式送りを出力すると、現表示位置が(次の論理ページの先頭位置)に移動します。通常の環境では、コンソール画面へ出力しても何も起こりません。プリンタへの出力においては、改ページを行う際に使います。

■\n…改行

改行を出力すると、現表示位置が(次の行の先頭)に移動します。

■\r…復帰

復帰を出力すると、現表示位置が(その行の先頭)に移動します。画面に復帰を出力すると、表示済みの文字を書き換えることができます。その例となるプログラムを以下に示します。

//CarriageReturn.java
//復帰の出力によって表示済み文字を書き換える

public class CarriageReturn{
    public static void main(String[] args){
        System.out.print("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        System.out.println("\r12345");
    }
}
CarriageReturn.java実行結果

■\t…水平タブ

水平タブを出力すると、現表示位置がその行における(次の水平タブ位置)に移動します。現表示位置が行における最後の水平タブ位置にある時やその位置を過ぎている場合の動作は規定されません。以下がその例です。

//HorizontalTab.java
//水平タブ文字の出力

public class HorizontalTab {
    public static void main(String[] args){
        System.out.println("ABC\t123");
    }    
}
HorizontalTab.jav実行結果

■\” と \’…二重引用符と単一引用符

これらを文字列リテラル中で使う場合と文字リテラルで使う場合とでは注意が必要になります。

□文字列リテラルでの表記

  • 二重引用符…拡張表記によって表さなければなりません。
  • 単一引用符…そのままの表記と拡張表記の両方で表記できます。

□文字リテラルでの表記

  • 二重引用符…そのままの表記と拡張表記の両方で表記できます。
  • 単一引用符…拡張表記によって表さなければなりません。

上記を利用したプログラムを以下に示します。

//Quotation.java
//拡張表記の利用例

public class Quotation {
    public static void main(String[] args){
        System.out.println("文字列リテラルと文字定数について");
        System.out.println("二重引用符で囲まれた\"ABC\"は文字列リテラルです。");
        System.out.print("一重引用符で囲まれた");
        System.out.print('\'');
        System.out.println("A'は文字リテラルです。");
    }    
}
Quotation.java実行結果

■\\…逆斜線

逆斜線の表記には拡張表記を用います。

■8進数拡張表記

8進数のコードで文字を表すのが、8進数拡張表記です。指定できる値の範囲は、0~377です。この範囲内で表せるのは、英数字や一部の記号文字になります。ひらがなや漢字は使えません。

カテゴリー
Java

Java(Step5-2)

基本型と演算

演算と型

整数を整数で割ると、商や剰余が整数として得られますが、実数で求める方法を説明します。

演算と型

2つの整数値を読み込んで、その平均値を表示するプログラムを作りましょう。

//Average1.java
//2つの整数値の平均値を実数で求める(間違い)
import java.util.Scanner;

public class Average1 {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("整数値xとyの平均値を求めます");
        System.out.print("xの値:"); int x = stdIn.nextInt();
        System.out.print("yの値:"); int y = stdIn.nextInt();

        double ave = (x + y) / 2;
        System.out.println("xとyの平均値は" + ave + "です。");
        stdIn.close();
    }
}
Average1.java実行結果

実行例を見ると分かりますが、実数を表すdouble型の変数aveに平均値を入れていますが、7と8の平均が7.5ではんく、7.0となっています。なぜなら、2で割る演算がint / intの結果がint型になり、小数点以下が切り捨てられてしまうためです。

演算中にintとdoubleが混在した場合は、結果が変わってきます。これらの時、2項数値昇格(binary numerical promotion)と呼ばれる型変換(type conversion)が行われます。int型がdouble型に格上げされて演算が行われます。それを確認したのが次のプログラムになります。

//Quotient.java
//2つの数値の商を求める

public class Quotient {
    public static void main(String[] args){
        System.out.println("15 / 2 =" + 15 / 2);
        System.out.println("15.0 / 2.0 =" + 15.0 / 2.0);
        System.out.println("15.0 / 2 =" + 15.0 / 2);
        System.out.println("15 / 2.0 =" + 15 / 2.0);
    }    
}
Quotient.java実行結果

double型は小数点以下の部分を格納する点でint型に比べ、余裕があります。具体的には、2項数値昇格における型変換は以下のようになっています。

  • 一方のオペランドがdouble型ならば、他方をdouble型に変換する
  • そうでないなら、一方のオペランドがfloat型ならば、他方をfloat型に変換する
  • そうでないなら、一方のオペランドがlong型ならば、他方をlong型に変換する
  • そうでなければ、両オペランドをint型に変換する

以上を踏まえ、平均値を実数値で求めるプログラムを改良すると以下のようになります。

//Average2.java
//2つの整数値の平均値を実数で求める(合計を2.0で割る)
import java.util.Scanner;

public class Average2 {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("整数値xとyの平均値を求めます");
        System.out.print("xの値:"); int x = stdIn.nextInt();
        System.out.print("yの値:"); int y = stdIn.nextInt();

        double ave = (x + y) / 2.0;
        System.out.println("xとyの平均値は" + ave + "です。");
        stdIn.close();
    }
}
Average2.java実行結果

キャスト演算子

普段の生活では、数字を割る時「2.0で割ろう」とは考えず、「2で割ろう」と考えます。2つの整数をはじめに実数に変換し、それを2で割ることで平均値を求めるようにしましょう。それが次のプログラムになります。

//Average3.java
//2つの整数値の平均値を実数で求める(キャスト演算子を利用する)
import java.util.Scanner;

public class Average3 {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("整数値xとyの平均値を求めます");
        System.out.print("xの値:"); int x = stdIn.nextInt();
        System.out.print("yの値:"); int y = stdIn.nextInt();

        double ave = (double)(x + y) / 2.0;
        System.out.println("xとyの平均値は" + ave + "です。");
        stdIn.close();
    }
}
Average3.java実行結果

平均値を求める部分が、以前のプログラムと異なります。演算子 / の左オペランドの式(double)(x + y)の形式を一般的に表すと次のようになります。

(型) 式

これは、式の値を型としての値に変換したものを生成するための式になります。この型変換をキャスト(cast)と呼びます。( ) は優先的に演算を行うための記号ではなく、キャスト演算子(cast operator)と呼ばれる演算子になります。

基本型の縮小変換

前のプログラムでは、int型の値をdouble型として取り出すためにキャスト演算を利用しました。次は逆の変換について考えてみましょう。

そこで、重要になるのがより小さい型への値の代入にはキャストが必須となることです。

次の変換がキャストが必須となる基本型の縮小変換(narrowing primitive conversion)となります。変換に伴い、数値の大きさの情報や精度が失われることがあります。

  • short から byte、char への変換
  • char から byte、short への変換
  • int から byte、short、char への変換
  • long から byte、short、char、int への変換
  • float から byte、short、char、int、long への変換
  • double から byte、short、char、int、long、float への変換
定数の代入・定数による初期化

基本型の縮小変換では、原則としてキャストが必要ですが、例外もあります。以下のようなケースです。

byte a = 0;
a = 5;
short b = 53;

代入の右辺の式や初期化子がbyte、short、char、int型の定数式で、代入先あるいは初期化先の変数の型がbyte、short、charであって、定数式の値が変数の型で表現できる場合は、基本型の縮小変換が自動で行われます。

基本型の拡大変換

縮小変換の逆が、基本型の拡大変換(widening primitive conversion)と呼びます。以下に示す種類があります。

  • byte から short、int、long、float、double への変換
  • short から int、long、float、double への変換
  • char から int、long、float、double への変換
  • int から long、float、double への変換
  • long から float、double への変換
  • float から double への変換

この変換は代入か初期化の際に自動的に行われます。基本型の拡大変換では、基本的に数値の大きさの情報が失われません。しかし、以下の変換では精度を失うことがあります。

  • int あるいは long の値から float への変換
  • long の値から double への変換

この場合の浮動小数点の変換結果は、最も近い値に丸められた整数値になります。例となるプログラムを以下に示します。

//IntegralToFloat.java
//整数型から浮動小数点型への変換(精度が失われる例)
public class IntegralToFloat {
    public static void main(String[] args){
        int a = 123456789;
        long b = 1234567890123456789L;

        System.out.println(" a = " + a);
        System.out.println("(float) a =" + (float)a);
        System.out.println(" b = " + b);
        System.out.println("(double) b =" + (double)b);
    }
}
IntegralToFloat.java実行結果

基本型の拡大変換と縮小変換

以下の変換は、基本型拡大変換と縮小変換の2段階で行われます。

  • byte から char への変換

まず、byteが基本型の拡大変換でintに変換され、その後、縮小変換によってintからchar型に変換されます。

繰り返しの制御

次のプログラムは、float型の変数xの値を0.0から1.0まで0.001ずつ増やしながら表示して、最後に合計を表示するプログラムになります。

//FloatSum1.java
//0.0から1.0まで0.001単位で増やして合計を表示(繰り返しをfloat制御)
public class FloatSum1 {
    public static void main(String[] args){
        float sum = 0.0F;

        for(float x = 0.0F; x <= 1.0F; x += 0.001F){
            System.out.println("x = " + x);
            sum += x;
        }
        System.out.println("sum =" + sum);
    }
}
FloatSum1.java実行結果

最後のxの値が1.0ではないことに気を付けましょう。浮動小数点数が、すべての桁の情報を失うことなく、表現できるとは限らないということです。そのため、1000個分の誤差がxに累積します。

繰り返しの制御を整数で行うように書き直したプログラムを以下に示します。

//FloatSum2.java
//0.0から1.0まで0.001単位で増やして合計を表示(繰り返しをint制御)

public class FloatSum2 {
    public static void main(String[] args){
        float sum = 0.0F;

        for(int i = 0; i <= 1000; i++){
            float x = (float)i / 1000;
            System.out.println("x = " + x);
            sum += x;
        }
        System.out.println("sum =" + sum);
    }
}
FloatSum2.java実行結果

for文では、変数iの値を0から1000までインクリメントします。繰り返しの度にiを1000で割った値をxとします。xが目的とする実数値をピッタリと表現できるわけではありません。毎回xの値を求め直すので、誤差は累積しません。

カテゴリー
Java

Java(Step5-1)

基本型と演算

基本型

これまでは、色々型の変数や定数を使用してきましたが、Javaでは基本型と参照型があります。

基本型

前までは、主にint、double、String型の変数などを使用していました。Javaで利用できる型を大きく区別すると、以下の図のようになります。

  • 数値型(numeric type)…整数を表す5種類の整数型と、実数を表す2種類の浮動小数点型に分かれます。
  • 論理型(boolean type)…論理値を表す論理型は、真と偽のいずれかの値を表現する型です。
型とビビット

以前、説明したように変数は型から作られます。例えば、

int x; //int型の変数

と宣言されたxは、int型になります。式には型があり、型と値が同じであれば、記憶域上での内部表現も同じものになります。内部表現は、0または1の値を持つデータ単位であるビットの集まりになります。

整数型

整数型(integral)は、有限範囲の連続した整数を表現する型です。以下に示す5種類があります。

char, byte, short, int, long

これらの型では、小数点以下をもつ実数を表せません。各型で表現できる数値の範囲とビット数をまとめたのが以下の図になります。

整数型で表現できる範囲とビット数
  • char型

文字を表すための型になります。図に示すように、非負の値しか表せないという点で、他の型と異なります。0と正の値を表現する符号なし整数型です。

  • byte型/short型/int型/long型

整数を表すための型です。負の値、0、正の値を表現する符号付き整数型です。各型の表現できる値が異なるのは、構成ビット数が異なるからです。

性質と用途
byte名前が示す通りに、1バイト(8ビット)の整数。1バイトのデータを表す際に利用します。
short短い整数です。小さな値しか使わないことが分かっていて、記憶域を節約したい時に使います。
int整数型の中で最も基本的な型です。通常はこの型を使います。
long長い整数です。int型では、表現できない大きな値に使用します。
整数型の性質と用途
整数リテラル

整数型の定数を表すのが、整数リテラル(integer literal)ですが、詳しく説明していきます。

整数リテラルには以下の6種類があります。

  • 10進数リテラル(int型/long型)
  • 8進数リテラル(int型/long型)
  • 16進数リテラル(int型/long型)
整数接尾語

整数リテラルは、基本的にはint型です。整数接尾語(integer type suffix)と呼ばれる1またはLを末尾に付けた整数リテラルの型はlong型となります。

最小値最大値単項 – 演算子のオペランドの最大値
int021474836472147483648
long09223372036854775807L9223372036854775808L
10進数リテラルで表現できる最小値と最大値
8進数リテラルと10進数リテラルで表現できる最小値と最大値
10進整数リテラル

これまで使ってきた10や55といった整数リテラルは、日常で使う10進数で表されており、10進整数リテラル(decimal integer literal)と呼ばれます。

8進整数リテラル

8進整数リテラル(octal integer literal)は、10進整数リテラルと区別がつくように、先頭に0をつけて2桁以上で表記します。以下の2つの整数リテラルは、同じように見えますが値はまったく別物になります。

  • 13…10進整数リテラル(10進数での13)
  • 013…8進整数リテラル(10進数でノ11)
16進整数リテラル

16進整数リテラル(hexadecimal integer literal)は、先頭に0xまたは、0Xを付けて表記します。A~Fは大文字でも小文字でもOKです。

  • 0xA…16進整数リテラル(10進数での10)
  • 0x13…16進整数リテラル(10進数での19)

各整数リテラルを10進数で表示するプログラムを以下に示すので、確認してみましょう。

//DecOctHexLiteral.java
//整数リテラル(10進数/8進数/16進数)

public class DecOctHexLiteral {
    public static void main(String[] args){
        int a = 13; //10進数の13
        int b = 013; //8進数の13
        int c = 0x13; //16進数の13

        System.out.println("a = " + a);
        System.out.println("b = " + b);
        System.out.println("c = " + c);
    }    
}
DecOctHexLiteral.java実行結果
整数の内部

値はビットの並びとして表現されます。ここでは、byte型、short型、int型、long型の表現について説明していきます。

符号ビット

整数型の値が、どのようにビットで表されるか示したのが下の図になります。

int型の整数値25と-25の内部
非負の値

非負の値は、その2進表現を各ビットに対応させたものとして表されます。上の図が示すように10進数の25は2進数で11001なので、上位ビットは0で埋め尽くされます。

負の値

負の値は、2の補数表現という方法で表現されます。例えば、正の数の全ビットを反転したものは、1の補数と呼ばれます。そして、1の補数に1を加えたものが、2の補数になります。以下がその図になります。

正の値から2の補数表現を用いた負の値への変換

浮動小数点型

小数点以下の部分を持つ実数を表すのが、浮動小数点型(floating-point type)です。以下の2種類があります。

float, double

そして、次のプログラムは、これらの型の変数に数値を入れて表示するプログラムです。

//FloatDouble.java
//float型とdouble型の精度が有限であることを体感

public class FloatDouble {
    public static void main(String[] args){
        float a = 123456789;
        double b = 1234567890123456789L;

        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }    
}
FloatDouble.java実行結果

実行結果から変数に入れた値が正確に表現されていないことが分かります。整数型が有限範囲の連続した整数を表現するとは異なり、浮動小数点型の表現範囲は、大きさと精度の両方からの制限を受けるためです。

形式表現範囲精度ビット数
floatIEEE754形式±3.40282347E+38~±1.40239846E-45約6~7桁32
doubleIEEE754形式±1.79769313486231507E+378~±4.94065645841246544E-324約15桁64
浮動小数点型の特性
浮動小数点リテラル

57.3のように実数を表す定数を浮動小数点リテラル(floating-point literal)と呼びます。型を指定するのが、浮動小数点接尾語(float type suffix)です。float型を指定するのがfとFであり、double型を指定するのがdとDです。指定しない場合はdouble型とみなされることになっています。

論理型

論理値を表す論理型(boolean型)は、以下の文脈で利用することになります。

  • if文の制御式(条件判定のための式)
  • do文・while文・for文の制御式(繰り返しを続けるかどうかの判定のための式)
  • 条件演算子? : の第1オペランド
論理値リテラル

論理型の値を表すfalseとtrueが論理値リテラルと呼ばれ、構文図で表すと以下のようになります。

論理値リテラルの構文図

関係演算子・等価演算子・論理否定演算子が論理型の値を生成することを検証するプログラムを作ってみましょう。

//BooleanTester.java
//関係演算子・等価演算子・論理否定演算子が生成する値を表示
import java.util.Scanner;

public class BooleanTester {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);
        System.out.print("整数a:"); int a = stdIn.nextInt();
        System.out.print("整数b:"); int b = stdIn.nextInt();

        System.out.println("a < b = " + (a < b));
        System.out.println("a <= b =" + (a <= b));
        System.out.println("a > b =" + (a > b));
        System.out.println("a >= b =" + (a >= b));
        System.out.println("a == b =" + (a == b));
        System.out.println("a != b =" + (a != b));
        System.out.println("!(a==0) =" + !(a == 0));
        System.out.println("!(b==0) =" + !(b == 0));

        stdIn.close();
    }
}
BooleanTester.java実行結果

「文字列+数値」と「数値̟+文字列」の演算では、数値が文字列に変換された上で連結されます同様に、「文字列+論理値」と「論理値+文字列」の演算では、論理型の値が”true”もしくは”false”という文字列に変換された上で連結が行われます。

カテゴリー
Java

Java(Step4-4)

プログラムの流れの繰り返し

多重ループ

繰り返し文のループ本体を繰り返し文にすると、二重、三重の繰り返しを行うことができます。このような繰り返しを多重ループといいます。

九九の表

二重ループを用いた例として、九九の表を表示するプログラムを作りましょう。

//MultiTable.java
//九九の表を表示

public class MultiTable {
    public static void main(String[] args){
        for(int i = 1; i <= 9; i++){
            for(int j = 1; j <= 9; j++){
                if( i * j < 10)
                    System.out.print("  ");
                else
                    System.out.print(" ");
                System.out.print(i * j);
            }
            System.out.println();
        }
    }    
}
MultiTable.java実行結果

外側のfor文では、iの値を1から9までインクリメントします。それは、縦方向の繰り返しになります。その内側にあるfor文は、jの値を1から9までインクリメントします。これは、横方向の繰り返しになります。

したがって、この二重ループでは、以下のように処理が行われます。

  • i が 1 のとき: j を 1 ➡ 9 とインクリメントしながら、1 * j を表示。そして改行。
  • i が 2 のとき: j を 1 ➡ 9 とインクリメントしながら、2 * j を表示。そして改行。
  • i が 3 のとき: j を 1 ➡ 9 とインクリメントしながら、3 * j を表示。そして改行。
  • …中略…
  • i が 9 のとき: j を 1 ➡ 9 とインクリメントしながら、9 * j を表示。そして改行。

プログラムのif文は、数値間の余白調整です。以下のように出力を行います。

  • 表示する値が10未満(すなわち1桁)…数値の前にスペースを2個表示。
  • 表示する値が10以上(すなわち2桁)…数値の前にスペースを1個表示。

直角三角形の表示

二重ループを使えば、記号文字を並べて三角形や四角形などの図形を表示できます。以下が三角形を表示するプログラムになります。

//IsoscelesTriangle.java
//左下側が直角の直角三角形を表示
import java.util.Scanner;

public class IsoscelesTriangle {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("左下直角の三角形を表示します。");
        System.out.print("段数は:");
        int n = stdIn.nextInt();

        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= i; j++)
                System.out.print('*');
            System.out.println();
        }
        stdIn.close();
    }    
}
IsoscelesTriangle.java実行結果

直角三角形の表示を行うフローチャートを以下に示します。

直角三角形を表示する二重ループのプログラムの流れ

実行例のように、nの値が7である場合を例にとって、どのように処理が行われるかを考えましょう。外側のfor文では、変数iの値を1からnすなわち7までインクリメントします。これは、三角形の各行に対応する縦方向の繰り返しです。その各行で実行される内側のfor文は、変数jの値を1からiまでインクリメントしながら表示を行います。これが、各行における横方向の繰り返しです。

break文とcontinue文

ここで、説明するのは、break文とcontinue文です。これらの文を利用すると、繰り返し文におけるプログラムの流れに変化を持たせることができます。

break文

以下に示すのは、読み込んだ整数の合計を表示するプログラムです。まず、最初に整数の個数を変数nに読み込みます。それから、for文によるn回の繰り返しの過程で、n個の整数を読み込んでいきながら加算を行います。ただし、読み込んだ値が0であれば入力は終了するようになっています。

//SumBreak1.java
//読み込んだ整数を加算
import java.util.Scanner;

public class SumBreak1 {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("整数を加算します。");
        System.out.print("いくつ加算しますか?:");
        int n = stdIn.nextInt();

        int sum = 0;
        for(int i = 0; i < n; i++){
            System.out.print("整数0で終了:");
            int t = stdIn.nextInt();
            if(t == 0)break;   //for文から抜け出せる
            sum += t;
        }
        System.out.println("合計は" + sum + "です。");
        stdIn.close();
    }
}
SumBreak1.java実行結果

繰り返し文の中で使われるbreak文は、その繰り返しを強制的に中断・終了させます。そのため、変数nに読み込んだ値が0であれば、for文の実行は終了します。

break文を利用した別のプログラム例を以下に示します。合計が1000を超えない範囲で読み込みと加算を行う仕様に変更しています。

//SumBreak2.java
//読み込んだ整数を加算(1000以下まで)
import java.util.Scanner;

public class SumBreak2 {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("整数を加算します。");
        System.out.print("いくつ加算しますか?:");
        int n = stdIn.nextInt();

        int sum = 0;
        for(int i = 0; i < n; i++){
            System.out.print("整数:");
            int t = stdIn.nextInt();
            if(sum + t > 1000){
                System.out.println("合計が1000を超えました。");
                System.out.println("最後の数値は無視されます。");
                break;
            }
            sum += t;
        }
        System.out.println("合計は" + sum + "です。");
        stdIn.close();
    }
}
SumBreak2.java実行結果

実行例では、3個の整数を読み込んでいます。3個目の392を加算すると、合計が1000を超えるため、読み込みを中断します。sumには最初に読み込んだ2個の合計が入っていることになります。

continue文

break文と対照的な構文を持つcontinue文(continue statement)です。continueが実行されると、ループ本体の残りの部分がすっ飛ばされて、プログラムの流れはループ本体の末尾に一気に飛びます。

continue文を利用したプログラム例を以下に示します。前のプログラムと同様に、読み込んだ整数を加算します。ただし、加算するのは0以上の値のみです。変数tに読み込んだ値が0未満であれば、「負の数は加算されません。」と表示したうえでcontinue文を実行します。そのため、加算を行う部分はスキップされます。

//SumContinue.java
//読み込んだ整数を加算(負の値は加算しない)
import java.util.Scanner;

public class SumContinue {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("整数を加算します。");
        System.out.print("いくつ加算しますか?:");
        int n = stdIn.nextInt();

        int sum = 0;
        for(int i = 0; i < n; i++){
            System.out.print("整数:");
            int t = stdIn.nextInt();
            if(t < 0){
                System.out.println("負の数は加算されません。");
                continue;
            }
            sum += t;
        }
        System.out.println("合計は" + sum + "です。");
        stdIn.close();
    }
}
SumContinue.java実行結果

ラベル付きbreak文

ここまで、break文とcontinue文を一重ループに適用したプログラム例を説明しました。多重ループの実行中に、外側の繰り返しを一気に抜けたり、強制的に繰り返しを行いたい場合、ラベル付きの文を使う必要があります。以下はラベル付きbreak文になります。

//SumGroup1.java
//読み込んだ整数のグループを加算
import java.util.Scanner;

public class SumGroup1 {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("整数を加算します。");
        int total = 0;

        Outer:
            for(int i = 1; i <= 10; i++){
                System.out.println("□第" + i + "グループ");
                int sum = 0;
            Inner:
                for(int j = 0; j < 5; j++){
                    System.out.print("整数:");
                    int t = stdIn.nextInt();
                    if(t == 99999)
                        break Outer;
                    else if(t == 88888)
                        break Inner;
                    sum += t;
                }
                System.out.println("小計は" + sum + "です。\n");
                total += sum;
            }
            System.out.println("\n合計は" + total + "です。\n");
    }    
}
SumGroup1.java実行結果

このプログラムは、5個の整数から構成されるグループの合計を求めます。グループは10個ですが、以下のように入力することで読み込みを中断できます。

  1. 99999を入力すると、全体の入力を終了する
  2. 88888を入力すると、現在入力中のグループの入力を終了する

プログラム全体は二重のfor文となっています。外側のfor文にはラベル Outer が付いて、内側のfor文には、ラベル Inner が付いています。

このように、ラベルが付いた文をラベル付き文(labeled statement)と呼びます。

プログラムの流れがラベル付きbreak文に差し掛かると、そのラベルをもった繰り返し文が終了します。そのため、本プログラムのbreak文は以下のように動きます。

  1. break文が実行されると、ラベル Outer: の付いた for 文の実行が中断される。
  2. break文が実行されると、ラベル Inner: の付いた for 文の実行が中断される。

ラベル付きcontinue文

もし、グループごとの小計を求める必要がなければ、より簡単に書き換えることができます。ラベル付きのcontinue文で書き換えた例が以下になります。

//SumGroup2.java
//読み込んだ整数のグループを加算
import java.util.Scanner;

public class SumGroup2 {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.println("整数を加算します。");
        int total = 0;

        Outer:
            for(int i = 1; i <= 10; i++){
                System.out.println("□第" + i + "グループ");
            
                for(int j = 0; j < 5; j++){
                    System.out.print("整数:");
                    int t = stdIn.nextInt();
                    if(t == 99999)
                        break Outer;
                    else if(t == 88888)
                        continue Outer;
                    total += t;
                }
            }
            System.out.println("\n合計は" + total + "です。\n");
    }    
}
SumGroup2.java実行結果

printfメソッド

次に画面表示を行うprintfメソッドについて説明します。これを利用すると、出力時に桁数などの書式設定が可能となります。

printfメソッド

以下に示すのは、九九の表を出力するプログラムからの抜粋になります。

for(int i = 1; i <= 9; i++){
            for(int j = 1; j <= 9; j++){
                if( i * j < 10)
                    System.out.print("  ");
                else
                    System.out.print(" ");
                System.out.print(i * j);
            }
            System.out.println();
        }

基数や桁数などの書式を制御して画面への表示を行う System.out.printf というメソッドを利用すれば、プログラムは簡潔になります。書き換えたのが以下のプログラムになります。

//MultitablePrintf.java
//九九の表示(System.out.printf利用)

public class MultitablePrintf {
    public static void main(String[] args){
        for(int i = 1; i <= 9; i++){
            for(int j = 1; j <= 9; j++){
                System.out.printf("%3d", i * j);
            }
            System.out.println();
        }
    }
}
MultitablePrintf.java実行結果

「%3d」は、コンマに続く整数値を少なくとも3桁の幅で10進数表示してください。という書式指定のための書式文字列です。%は、書式指定の開始を表す文字で、dは10進数という意味のdecimalの頭文字になります。

3桁でなく、少なくとも3桁と説明したのは、理由があります。出力すべき数値が指定された桁数で収まらなければ、その数値の全部の桁が出力されるからです。これは、下のプログラムを実行するとわかると思います。

//PrintfWidth.java
//いろいろな桁数の整数を少なくとも3桁で表示

public class PrintfWidth {
    public static void main(String[] args){
        System.out.printf("%3d\n", 1);
        System.out.printf("%3d\n", 12);
        System.out.printf("%3d\n", 123);
        System.out.printf("%3d\n", 1234);
        System.out.printf("%3d\n", 12345);
    }
}
PrintfWidth.java実行結果

次に、以下のプログラムを実行してみてください。書式文字列以外の文字が、そのまま画面に表示されることがわかると思います。

//PrintfDecimal.java
//System.out.printfによる整数値の出力

public class PrintfDecimal {
    public static void main(String[] args){
        int x = 55;
        int y = 123;
        System.out.printf("x = %3d\n", x);
        System.out.printf("y = %3d\n", y);
    }    
}
PrintfDecimal.java実行結果

キーボードから読み込んだ整数値と実数値をprintfメソッドによって表示するプログラム例を以下に示します。

//DecimalFloat.java
//整数値と実数値を表示
import java.util.Scanner;

public class DecimalFloat {
    public static void main(String[] args){
        Scanner stdIn = new Scanner(System.in);

        System.out.print("整数x:");
        int x = stdIn.nextInt();

        System.out.print("整数y:");
        double y = stdIn.nextDouble();

        System.out.printf("x =%3d y =%6.2f\n", x, y);
        stdIn.close();
    }
}
DecimalFloat.java実行結果

このプログラムでは、2つの数値の書式化を1度に行っています。複数の式の値を出力する場合は、各式をコンマ文字 , で区切ります。2つの変数の表示は、以下のように行われます。

  • 整数x…少なくとも3桁の10進数で表示します。
  • 実数y…全体を少なくとも6桁で、小数点以下の部分を2桁で表示します。f は、浮動小数点という意味のfloating-pointの頭文字です。

文字%は、書式指定の先頭文字です。文字%そのものを出力したい場合は、%%と表記する必要があります。

%d と %f 以外にも、色々な指定ができます。基本的なものを利用したプログラム例を以下に示します。

//PrintfTester.java
//System.out.printfのテストプログラム

public class PrintfTester {
    public static void main(String[] args) {
        System.out.printf("%d\n", 12345); //10進数
        System.out.printf("%3d\n", 12345); //少なくとも3桁
        System.out.printf("%7d\n", 12345); //少なくとも7桁
        System.out.println();

        System.out.printf("%5d\n", 123); //少なくとも5桁
        System.out.printf("%05d\n", 123); //少なくとも5桁
        System.out.println();

        System.out.printf("%d\n", 12345); //10進数
        System.out.printf("%o\n", 12345); //8進数
        System.out.printf("%x\n", 12345); //16進数(小文字)
        System.out.printf("%x\n", 12345); //16進数(大文字)
        System.out.println();

        System.out.printf("%f\n", 123.45); //浮動小数点数
        System.out.printf("%15f\n", 123.45); //全体を15桁
        System.out.printf("%9.2f\n", 123.45); //全体を9桁で小数点以下を2桁
        System.out.println();

        System.out.printf("XYZ\n"); //文字列変換なし
        System.out.printf("%s\n", "ABCDE"); //文字列
        System.out.printf("%3s\n", "ABCDE"); //少なくとも3桁
        System.out.printf("%10s\n", "ABCDE"); //少なくとも10桁
        System.out.println();
    }    
}
PrintfTester.java実行結果
変換文字解説
%d10進数で出力
%o8進数で出力
%x16進数で出力(a ~ f は小文字)
%X16進数で出力(A ~ F は大文字)
%c文字として出力
%f小数点形式で出力
%s文字列で出力
書式文字列