カテゴリー
Java

Java(Step8-1-2)

クラスの基本

クラスとは②

コンストラクタ

メソッドとよく似た形をしたコンストラクタ(constructor)の役割は、クラスのインスタンスを初期化することです。コンストラクタが呼び出されるのは、プログラムの流れが以下の宣言文を通過して、式が評価される際です。

Account2 first = new Account2("山田太郎", "12345", 1000);
Account2 second = new Account2("山田花子", "67891", 200);

以下の図が示すように、個々のインスタンスに対して、専用のコンストラクタが存在します。コンストラクタを定義しないクラスには、引数を受け取らず、その本体が空であるデフォルトコンストラクタ(default constructor)が自動的に作られます。

クラスのインスタンスとコンストラクタ

メソッド

クラス Account2 のメソッドを以下に再掲します。

//口座名義を調べる
    String getName(){
        return name;
    }

    //口座番号を調べる
    String getNo(){
        return no;
    }

    //預金残高を調べる
    long getBalance(){
        return balance;
    }

    //k円預ける
    void deposit(long k){
        balance += k;
    }

    //k円おろす
    void withdraw(long k){
        balance -= k;
    }
  • getName…口座名義を調べるためのメソッドです。フィールド name の値を String型で返します。
  • gerNo…口座番号を調べるためのメソッドです。フィールド no の値を String型で返します。
  • getBlance…預金残高を調べるためのメソッドです。フィールド balance の値を long型で返します。
  • deposit…お金を預けるためのメソッドです。預金残高が k 円だけ増えることになります。
  • withdraw…お金をおろすためのメソッドです。預金残高が k 円だけ減ることになります。

static を付けずに宣言されたメソッドは、そのクラスの個々のインスタンスごとに作られます。また、このメソッドは、インスタンスメソッド(instance method)と呼ばれます。インスタンスメソッドを呼び出す例は以下のようになります。

first.getBalance() //山田太郎の預金残高を調べる
first.withdraw(200) //山田太郎の口座から200円下す
second.deposit(100) //山田花子の口座に100円預ける

フィールドをアクセスする場合と同様にメンバアクセス演算子「.」を利用します。クラスの外部から直接アクセスできないデータも、メソッドを通じる事で間接的にアクセスが可能です。図で表すと以下のようになります。

インスタンスメソッドの呼び出しとメッセージ

メソッドとメッセージ

オブジェクト指向プログラミング(object oriental programming)の世界では、インスタンスメソッドを呼び出すことを、次のように表現します。

オブジェクトに「メッセージを送る」

これを行うことで、能動的に意思決定を行って、返却処理を行います。

クラスとオブジェクト

一般にメソッドは、フィールドの値をもとに処理を行って、必要に応じてフィールドの値を更新します。フィールドを非公開として外部から保護した上でメソッドとフィールドをうまく関係させることをカプセル化(encapsulation)といいます。

今までのプログラムは、実質的にはメソッドの集合であり、クラスはメソッドを包むだけの存在でした。本来はクラスの集合としてJavaプログラムを組みます。そうすることで、Javaのパワーを発揮できます。

カテゴリー
Java

Java(Step8-1-1)

クラスの基本

クラスとは①

以前は、メソッドについて説明しましたが、それと処理対象となるデータを組み合わせた構造を表すのがクラスになります。メソッドよりも一回り大きな単位の部品であるクラスはオブジェクト指向プログラミングの基礎的な技術になります。

データの扱い

まずは以下のプログラムを見てみましょう。二人の銀行口座のデータを表す変数に値を設定して表示する単純なものです。

//Accounts.java
//二人分の銀行口座データを扱うプログラム

public class Accounts {
    public static void main(String[] args){
        String firstAccountName = "山田太郎";
        String firstAccountNo = "12345";
        long firstAccountBalance = 1000;

        String secondAccountName = "山田花子";
        String secondAccountNo = "67891";
        long secondAccountBalance = 200;

        firstAccountBalance -= 200;
        secondAccountBalance += 100;

        System.out.println("■山田太郎の口座");
        System.out.println("  口座名義:" + firstAccountName);
        System.out.println("  口座番号:" + firstAccountNo);
        System.out.println("  預金残高:" + firstAccountBalance);

        System.out.println("■山田花子の口座");
        System.out.println("  口座名義:" + secondAccountName);
        System.out.println("  口座番号:" + secondAccountNo);
        System.out.println("  預金残高:" + secondAccountBalance);
    }
}
Accounts.java実行結果

二人分の銀行口座データを6個の変数で表しています。しかし、バラバラに宣言された口座名義、口座番号、預金残高の変数が、1つの銀行口座に関するものであるという関係はプログラム上で表現されていません。

クラス

我々がプログラムを作る際には、現実世界のオブジェクト(物)や概念を、プログラムの世界のオブジェクト(変数)に投影します。以下の図のように考えるのがクラス(class)の基本になります。

オブジェクトの投影とクラス

クラス

前の項目に基づいて書き直したプログラムを以下に示します。

//AccountTester.java
//銀行口座クラスのテスト

class Account{
    String name; //口座名義
    String no;  //口座番号
    long balance;  //預金残高
}
public class AccountTester {
    public static void main(String[] args){
        Account first = new Account();
        Account second = new Account();

        first.name = "山田太郎";
        first.no = "12345";
        first.balance = 1000;

        second.name = "山田花子";
        second.no = "67891";
        second.balance = 200;

        first.balance -= 200;
        second.balance += 100;

        System.out.println("■山田太郎の口座");
        System.out.println("  口座名義:" + first.name);
        System.out.println("  口座番号:" + first.no);
        System.out.println("  預金残高:" + first.balance);

        System.out.println("■山田花子の口座");
        System.out.println("  口座名義:" + second.name);
        System.out.println("  口座番号:" + second.no);
        System.out.println("  預金残高:" + second.balance);
    }
}
AccountTester.java実行結果

このプログラムは、2つのクラスから構成されるという点でこれまでと大きく異なります。それぞれのクラスの概略は以下の通りです。

  • Account:銀行口座クラス
  • AccountTester:クラス Account をテストするクラス

これまでのプログラムのクラスは、mainメソッドを中心とした構造でした。このプログラムでそれに相当するのが、AccountTester です。

クラス宣言

以下の図に注目してみましょう。これは、クラス Accountが「口座名義、口座番号、預金残高をセットにしたもの」であることを表すための宣言です。先頭の class Account~ が宣言の開始に辺り、このような宣言をクラス宣言(class declaration)と呼ばれます。

クラスとフィールド

{ }の中に置かれているのは、クラスを構成するデータを表すフィールド(field)の宣言です。クラス Accountは、3つのフィールドから構成されています。

  • 口座名義を表す String 型の name
  • 口座番号を表す String 型の no
  • 預金残高を表す long型の balance
クラスとオブジェクト

クラス宣言は、型を宣言するものであって、実体(変数)を宣言するものではありません。クラス Account 型の変数は、以下のように宣言します。

Account first;  //山田太郎の口座(クラス型変数)
Account second;  //山田花子の口座(クラス型変数)

この宣言で作られるのは、銀行口座クラスの実体ではなく、それを参照するためのクラス型変数(class type variable)になります。

クラスの実体は別に生成する必要があり、配列と同じように生成は、new 演算子を利用して行います。

new Account()

生成の様子を図で表すと以下のようになります。

クラスとインスタンス

new演算子によって生成されたクラス型の「実体」をインスタンス(instance)と呼び、生成することをインスタンス化と呼びます。

クラス型の変数とインスタンスは、関連付けが必要です。そのために以下の代入を行います。

first = new Account();
second = new Account();

これで、上記の2つは生成されたインスタンスを参照することになります。

インスタンス変数とフィールドアクセス

クラス Account 型のインスタンスは、口座名、口座番号、預金残高が「セット」になった変数です。その中の特定のフィールドをアクセスするために利用するのがメンバアクセス演算子(member access operator)です。通称はドット演算子ですので、覚えておきましょう。

インスタンス内のフィールドは、インスタンス毎に作られる変数で、インスタンス変数(instance variable)と呼びます。

フィールド(インスタンス変数)のアクセス
フィールドの初期化

配列内の個々の構成要素は「既定値」である0で初期化されたようにクラス内の個々のインスタンス変数も、既定値で初期化されます。

問題点

クラスの導入によって、口座のデータを表す変数間の関係がプログラムに実装されましたが、まだ問題があります。

  1. 確実な初期化に対する無保証…口座インスタンスのフィールドが明示的に初期化されていないので、思いもよらぬ結果を招くことがあります。
  2. データの保護に対する無保証…このプログラムでは、他から自由に読み書きができるので、現実では他人が口座からお金を下ろせてしまいます。
銀行口座クラス改良

以上を踏まえて改良したプログラムを以下に示します。

//AccountTester2.java
//銀行口座クラスのテストversion2

class Account2{
    private String name; //口座名義
    private String no;  //口座番号
    private long balance;  //預金残高

    //コンストラクタ
    Account2(String n, String num, long z){
        name = n;
        no = num;
        balance = z;
    }
    
    //口座名義を調べる
    String getName(){
        return name;
    }

    //口座番号を調べる
    String getNo(){
        return no;
    }

    //預金残高を調べる
    long getBalance(){
        return balance;
    }

    //k円預ける
    void deposit(long k){
        balance += k;
    }

    //k円おろす
    void withdraw(long k){
        balance -= k;
    }
}
public class AccountTester2 {
    public static void main(String[] args){
        Account2 first = new Account2("山田太郎", "12345", 1000);
        Account2 second = new Account2("山田花子", "67891", 200);

        first.withdraw(200); 
        second.deposit(100);

        System.out.println("■山田太郎の口座");
        System.out.println("  口座名義:" + first.getName());
        System.out.println("  口座番号:" + first.getNo());
        System.out.println("  預金残高:" + first.getBalance());

        System.out.println("■山田花子の口座");
        System.out.println("  口座名義:" + second.getName());
        System.out.println("  口座番号:" + second.getNo());
        System.out.println("  預金残高:" + second.getBalance());
    }
}
AccountTester2.java実行結果
データ隠ぺい

新しいクラス Account2 の構造を示したのが、以下の図になります。クラス宣言の内部が大きく3つの部分で構成されているのが分かります。

クラスAccount2の構造

1.フィールド

フィールドの宣言には、キーワード private が付いています。これがついたアクセス性は、非公開アクセス(private access)となります。非公開アクセスのフィールドは、クラスの外部に対して存在を隠します。 

そのため、クラス AccountTester2 のmainメソッドでは、非公開のフィールドにアクセスすることができません。情報を公開するのはクラス側の判断になります。このように、データを外部から隠して不正なアクセスから守ることをデータ隠ぺい(data hiding)といいます。データの保護性、隠ぺい性だけでなく、プログラムの保守性を向上できます。また、private が指定されていないフィールドはデフォルトアクセス(default access)となります。

2.コンストラクタ

この部分はコンストラクタ(constructor)と呼ばれます。形だけ見るとメソッドと似ていますが、以下が異なります。

  • クラスと同じ名前である。
  • 返却型がない。

3.メソッド

以前書いたメソッドとは異なり、static を付けずに宣言しています。また、フィールドやメソッドのことをメンバ(member)と総称します。

カテゴリー
Java

Java(Step7-3)

メソッド

配列を扱うメソッド

メソッドは、引数として配列を受け取ることもでき、返却することもできます。

最大値を求めるメソッド

身長と体重を読み込んで配列に格納して、それぞれの最大値を求めるプログラムを作りましょう。

//MaxOfHeightWeight.java
//最も背が高い人の身長と最も重い人の体重を求める
import java.util.Scanner;

public class MaxOfHeightWeight {
    //配列のaの最大値を求めて返却
    static int maxOf(int[] a){
        int max = a[0];
        for(int i = 1; i < a.length; i++)
            if(a[i] > max)
                max = a[i];
        return max;
    }

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

        System.out.print("人数は:");
        int ninzu = stdIn.nextInt();

        int[] height = new int[ninzu];
        int[] weight = new int[ninzu];

        System.out.println("身長と体重を入力してください。");

        for(int i = 0; i < ninzu; i++){
            System.out.print((i + 1) + "番目の身長:");
            height[i] = stdIn.nextInt();
            System.out.print((i + 1) + "番目の体重:");
            weight[i] = stdIn.nextInt();
        }
        System.out.println("最も身長が高い人の身長:" + maxOf(height) + "cm");
        System.out.println("最も太っている人の体重:" + maxOf(weight) + "kg");
        stdIn.close();

    }
}
MaxOfHeightWeight.java実行結果

maxOf メソッドは、配列の要素の最大値を求めて返却するメソッドです。配列を受け取るための仮引数は、int[] a と宣言されています。

呼び出されたmaxOf メソッドでは、配列変数である仮引数 a が、受け取った参照で初期化されます。その結果、配列変数 a は配列 height の本体を参照することになります。

線形探索

配列から目的とするキー値と同じ値を持つ要素を見つけ出す線形探索を以前作成しましたが、それを独立したメソッドとして実現させましょう。

//LinearSearch2.java
//線形探索
import java.util.Scanner;

public class LinearSearch2 {
   //配列aの要素からKeyと一致する最も先頭の要素を線形探索
   static int linearSearch(int[] a, int key){
       for(int i = 0; i < a.length; i++)
        if(a[i] == key)
            return i;  //探索成功
        return -1;  //探索失敗
   } 

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

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

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

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

       int idx = linearSearch(x, ky);

       if(idx == -1)
          System.out.println("その値の要素は存在しません。");
       else
          System.out.println("その値はx[" + idx + "]にあります。");
       stdIn.close();
   }
}
LinearSearch2.java実行結果

線形探索を行うのが、linearSearch2 メソッドです。配列 a から key と一致する要素の中から最も先頭側に位置する要素を線形探索します。探索に成功した場合は、見つけた要素のインデックスを返し、失敗した場合は、 -1 を返します。

使い捨ての配列

以下のプログラムを考えてみましょう。(k は適当な値)

int[] a = (1, k, K + 5, 2 * k);
int[] i = linearSearch(a,3); //値が3である要素を探す

先頭から順に、初期化された配列 a の要素中に値3が存在するかどうか調べます。ここでは、値3があるか調べるだけであり、終わったら配列 a が不要になります。このような使い捨ての配列にわざわざ参照する配列変数を割り当てる必要はないです。以下のように実現できるからです。

int i = linearSearch(new int[](1, k, k + 5, 2 + k), 3);

配列の要素の並びを逆転する

配列の要素の並びを逆転する独立したメソッドを実現したプログラムを作りましょう。

//ReverseArray2.java
//配列の要素に値を読み込んで並びを反転する
import java.util.Scanner;

public class ReverseArray2 {
    //配列の要素a[idx1]とa[idx2]を交換
    static void swap(int[] a, int idx1, int idx2){
        int t = a[idx1]; a[idx1] = a[idx2]; a[idx2] = t;
    }    

    //配列aの要素の並びを逆転
    static void reverse(int[] a){
        for(int i = 0; i < a.length / 2; i++)
            swap(a, i ,a.length - i - 1);
    }

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

        System.out.print("要素数:");
        int num = stdIn.nextInt();

        int[] x = new int[num];

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

        reverse(x);

        System.out.println("要素の並びを逆転しました。");
        for(int i = 0; i < num; i++)
            System.out.println("x[" + i + "] = " + x[i]);
        stdIn.close();
    }
}
ReverseArray2.java実行結果

並べ替えを行うのがメソッド reverse です。配列の要素の並びを逆転させるには2要素の交換を「要素数 / 2」回行います。交換を行うのが swap メソッドです。このメソッドが仮引数として受け取る a[idx1]と a[idx2]の値を交換します。

2つの配列の比較

これまでのメソッドは、単一の配列を扱うものでした。複数の配列の処理を行うメソッドを作りましょう。以下に示すのは、2つの配列が等しいかどうかを判定するプログラムです。

//ArrayEqual2.java
//2つの配列が等しいかどうかを判定
import java.util.Scanner;

public class ArrayEqual2 {
    //2つの配列a、bの全要素は等しいか?
    static boolean equals(int[] a, int[] b){
        if(a.length != b.length)
            return false;
        for(int i = 0; i < a.length; i++)
            if(a[i] != b[i])
                return false;
        return true;
    }    

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

        System.out.print("配列aの要素数:");
        int na = stdIn.nextInt();

        int[] a = new int[na];

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

        System.out.print("配列bの要素数:");
        int nb = stdIn.nextInt();

        int[] b = new int[nb];

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

        System.out.println("配列aとbは" + (equals(a, b) ? "等しいです。" : "等しくありません。"));
        stdIn.close();  
    }
}
ArrayEqual2.java実行結果

メソッド equals の判定は、以下の3つのステップで行います。

  1. 2つの配列 a、b の要素数を比較します。要素数が異なれば、配列が等しくないのがわかるため、false を返します。
  2. for文内では、2つの配列を先頭から走査し、a[i]と b[i]の値をを比較します。その過程で異なる要素を見つけると、return文より、falseを返します。
  3. プログラムの流れが最後まで到達するということは、for文が中断されなかったということなので、2つの配列が等しいと判断でき、trueが返ります。

配列を返すメソッド

メソッドは配列を受け取るだけでなく、返却することもできます。そのメソッドを作りましょう。

//GenIdxArray.java
//全要素がインデックスと同じ値を持つ配列の生成
import java.util.Scanner;

public class GenIdxArray {
    //全要素がインデックスと同じ値をもつ要素数nの配列を生成して返却
    static int[] idxArray(int n){
        int[] a = new int[n];
        for(int i = 0; i < n; i++)
            a[i] = i;
        return a;
    }

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

        System.out.print("要素数は:");
        int n = stdIn.nextInt();
        int [] x = idxArray(n);

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

メソッド idxArrayは、仮引数 n にint型整数値を受け取ります。メソッド本体で行うのは以下のことです。

  • 要素数が n である配列 a を生成。
  • 配列 a の全要素にインデックスと同じ値を代入。
  • a すなわち配列本体への参照を返却。

多重定義

よく似たメソッドの1つ1つに異なる名前を与えるとプログラムに色々な名前で溢れかえります。異なるメソッドに同じ名前を与える多重定義について説明します。

メソッドの多重定義

Javaでは、1つのクラスに宙に同じ名前のメソッドが複数存在することが許されます。同じ名前のメソッドを同一クラス内に複数宣言することをメソッドを多重定義(overload)と表現します。ただし、同じシグネチャ(signature)のメソッドは多重定義できないという制約があります。以下の図で示しています。

メソッドのシグネチャ

2つのint型の最大値を求めるメソッドと3つのint型の最大値を求めるメソッドを多重定義したプログラムを以下に示します。

//MaxOverload.java
//2値と3値の最大値を求めるメソッド(多重定義)
import java.util.Scanner;

public class MaxOverload {
   //a、bの最大値を返却
   static int max(int a, int b){
       return a > b ? a : b;
   } 

   //a、b、cの最大値を返却
   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("xの値:"); int x = stdIn.nextInt();
       System.out.print("yの値:"); int y = stdIn.nextInt();
       System.out.print("zの値:"); int z = stdIn.nextInt();

       System.out.println("x、yの最大値は" + max(x, y) + "です。");
       System.out.println("x、y、zの最大値は" + max(x, y, z) + "です。");
       stdIn.close();
   }
}
MaxOverload.java実行結果

メソッド呼び出し時にどのメソッドを呼び出すかといった指定は不要です。適したメソッドが自動的に呼び出されます。このように類似した処理を行うメソッドを多重定義すれば、プログラムが多くのメソッド名で溢れるのを阻止できます。

カテゴリー
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です。この範囲内で表せるのは、英数字や一部の記号文字になります。ひらがなや漢字は使えません。