目次へ戻る

オブジェクト指向プログラミング(1)

オブジェクト指向プログラミング(1)では,以下の項目に関して学ぶ.

  1. Javaの基本文法
  2. クラスの記述
  3. インスタンスの生成とメソッドの実行
  4. パッケージの宣言とアクセス制御

Javaの基本文法

識別子と予約語

クラス,メソッド,変数などの名前を識別子(identifier)と呼ぶ.識別子には,半角英数字,アンダーバー,ドル記号,Unicode文字を使用できる.ただし,数字を先頭文字にすることはできない.また,ドル記号は,主に処理系が内部で識別子を自動生成する場合に用いられる.大文字と小文字は区別されて扱われる.

さらに,以下に示す予約語は,識別子には使えない.

abstract assert boolean break byte case catch char class const continue default do double else extends final finally float for goto if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while

演算子と文

基本的な文,式,制御構造,演算子などの構文はC言語やC++言語に類似している.

主な演算子には,算術演算子(+, -, *, /, %, ++, --, ?:),代入演算子(=, +=, -=, *=, /=, %=),比較演算子(<, <=, >, >=, ==, !=),論理演算子(&&, ||, !)がある.これら以外の重要な演算子として,インスタンスを生成するnew,型を変換するキャスト(型を括弧で括る),インスタンスの型を判定するinstanceof,配列の生成やアクセスに用いる[],限定名を形成するドット(.)がある.

また,条件文(if, switch-case),繰り返し文(while, for),break文,continue文,return文を備える.これらの記法と意味は,C言語やC++ 言語のものとほぼ同様である.

変数と定数

データを保持する場合には,変数(variable)を用いる.C言語と同様に,変数は使用する前に宣言しておく必要がある.変数の名前は,識別子で与えられる.

変数はすべて型(type)を持つ.Javaのデータ型には,基本型(primitive type)と参照型(reference type)がある.基本型は,論理型のboolean (trueあるいはfalseをとる),整数型のbyte (8ビット符号付), short (16ビット符号付), int (32ビット符号付), long (64ビット符号付),浮動小数点型のfloat (32ビット単精度), double (64ビット倍精度),Unicode文字型のchar (16ビット)である.配列,クラス,インタフェースは参照型であり,その変数の値はすべてインスタンスへの参照(ポインタのようなもの)となる.

変数の宣言,代入,初期化の方法は,C言語と同様である.ただし,Javaは強い静的型言語であるため,あらかじめ規定されている型変換が適用される場合や多態変数に対する代入を除いて,変数の型と異なる型の値を代入することはできない.また,変数に代入される値(value)の型には,静的な型(見かけ上の型)と動的な型(実行時の型)がある.静的な型はプログラムコードで明示的に宣言されているため,コードを見れば判別可能である.それに対して,動的な型はプログラムの実行時に決まる.

さらに,変数はスコープ(scope)を持つ.スコープとは,その変数を単純名(名前のみ)で参照できる範囲である.インスタンス変数(フィールド)のスコープは宣言されたクラスであり,メソッド引数のスコープは宣言されたメソッドである.ローカル変数のスコープは,それが宣言された位置から始まり,それが宣言されたブロックの終りまでとなる.

定数リテラル(literal)には以下のものがある.

整数リテラル,浮動小数点数リテラル,真偽リテラル,文字リテラルに関しては,それぞれ対応する基本型の変数を用いて扱う.文字列リテラルに関しては,それを扱う基本型は用意されていないので,文字列を扱うクラスStringStringBuilderStringBufferなどを用いる.Stringから生成したインスタンスは不変であるため,それに格納されている文字列を変更することはできない.これに対して,StringBuilderStringBufferから生成したインスタンスでは,文字の追加や挿入ができるようになっている.また,Stringのインスタンスは頻繁に利用されるため,new演算子を使わなくとも,次のようにインスタンスの生成が可能である.

String str = "Java";
// String str = new String("Java"); と同じ

変数の宣言にfinal修飾子をつけることにより,変数を定数のように扱うこともできる.たとえば,以下のように変数MAX_VALUEを宣言すると,一度代入が行われた変数は2度と値を変更できない.これにより,定数のように扱うことできる.

final int MAX_VALUE = 1024;

Stringから生成したインスタンスは参照型であるため,文字列の比較には==演算子ではなく,メソッドequals()compareTo()を用いる必要がある.nullリテラルは,null型に属する唯一の値であり,参照型変数がまだ有効な値(インスタンス)を指していないことを表す.

コメント

//からその行の終りまでの文字,/**/にはさまれる文字はコメントとなる.

クラスの記述

クラス(class)とはデータ(属性)とそれに対する処理(操作)をひとまとめにして定義した抽象データ型である.Javaプログラムは1つ以上のクラスで構成される.

クラスは,予約語classを用いて宣言する.スマートフォンを指すクラスSmartphoneを宣言するソースコードを以下に示す.

class Smartphone {
    // クラスの本体
}

Javaでは,クラスの保持する属性をフィールド(field)と呼ぶ.名前(name)と価格(price)のデータを格納するフィールド宣言を追加したソースコードを以下に示す.

class Smartphone {
    String name;  // 名前データを格納するフィールド(フィールド変数)
    int price;    // 価格データを格納するフィールド(フィールド変数)
}

クラスの保持する操作はメソッド(method)と呼ばれる.メソッド宣言を追加したソースコードを以下に示す.

class Smartphone {
    String name;
    int price;
    
    Smartphone(String name, int p) {  // コンストラクタ
        this.name = name;
        price = p;
    }
    
    Smartphone(String name) {         // コンストラクタ
        this(name, 30000);
    }
    
    String getName() {                // 名前データを取得するメソッド
        return name;
    }
    
    void setPrice(int p) {            // 価格データを設定するメソッド
        price = p;
    }
    
    int getPrice() {                  // 価格データを取得するメソッド
        return price;
    }
    
    String getPriceInfo() {           // 価格情報を取得するメソッド
        return String.valueOf(getPrice()) + "-yen";
    }
    
    void print() {                    // 名前と価格の情報を表示するメソッド
        System.out.println(name + ": " + getPriceInfo());
    }
}

getName()setPrice()getPrice()getPriceInfo()print()は通常のメソッドである.一方,クラスと同じ名前を持つメソッドSmartphone()は,コンストラクタ(constructor)である.コンストラクタはインスタンス生成時に実行されるため,インスタンスの初期化に用いる.Smartphone()内に存在する予約語thisは,自分自身のインスタンスへの参照を表す.たとえば,Smartphoneのコンストラクタが呼び出された場合,thisは生成したインスタンスそのものを指し,this.nameはそのインスタンスのフィールドnameを指す.コンストラクタ内の代入文の右辺のnameはローカル変数(メソッドの引数)である.よって,この代入文で,引数nameの値がフィールドnameに代入される.また,this()はコンストラクタ内のみで使用可能で,自分自身のインスタンスの別のコンストラクタを呼び出す際に用いる.

インスタンスではなくクラスに属する静的フィールドや静的メソッドを宣言するためには,予約語staticを付ける.これらは,クラス変数(class variable)またはクラスメソッド(class method)と呼ばれる.たとえば,クラスSystemのフィールドoutはクラス変数である.また,クラスStringのメソッドvalueOf()はクラスメソッドである.クラス変数やクラスメソッドは,インスタンスを生成しなくともクラス名を指定するだけで参照(System.out)や呼び出し(String.valueOf())が可能である.クラス変数は同一のクラスから生成したインスタンス群で値を共有したい場合や定数を保持するために用いる.また,クラスメソッドは,複数のクラスから利用されるユーティリティ関数の実現などに用いる.

ここで,メソッドprint()の本体に記述されているSystem.out.println()System.outは標準出力を意味し,println()は文字列を(標準出力に)表示するメソッドである.

最後に,可視性を付与して完成させたクラスSmartphoneのソースコードを以下に示す.

Smartphone.java

public class Smartphone {
    private String name;
    int price;
    
    public Smartphone(String name, int p) {
        this.name = name;
        price = p;
    }
    
    public Smartphone(String name) {
        this(name, 30000);
    }
    
    public String getName() {
        return name;
    }
    
    public void setPrice(int p) {
        price = p;
    }
    
    public int getPrice() {
        return price;
    }
    
    private String getPriceInfo() {
        return String.valueOf(getPrice()) + "-yen";
    }
    
    public void print() {
        System.out.println(name + ": " + getPriceInfo());
    }
}

クラスSmartphoneの宣言に付与されているpublicは,そのクラスがどこからでもアクセス可能なことを指す.また,フィールドpriceやメソッドgetPrice()の宣言に付与されているpublicは,あらゆるクラスから,そのフィールドやメソッドにアクセス可能なことを指している.一方,フィールドweightやメソッドgetPriceInfo()の宣言に付与されているprivateは,そのフィールドやメソッドがクラス内からのみアクセス可能なことを指している.可視性を表す予約語が付与されていないpriceは,同じパッケージのクラスからアクセス可能である.パッケージとアクセス制御の詳細については,オブジェクト指向プログラミング(2)で説明する.

Javaソースファイルを作成する際の注意として,ファイル内の公開クラスの名前(たとえば,Smartphone)とファイル名(この場合,Smartphone.java)は一致させる必要がある.

インスタンスの生成とアクセス

実行時には,クラスからインスタンス(instance)を生成し,生成したインスタンスが動作することで処理を行う.クラスとはインスタンスを生成する雛形のようなものである.

クラスSmartphoneのインスタンスを生成し,そのインスタンスのフィールドの値を読み書きしたり,メソッドに記述された処理を実行するソースコードを以下に示す.

Sample1.java

public class Sample1 {
    
    public static void main(String[] args) {
        Smartphone phone1 = new Smartphone("ABC", 35000);
        System.out.println("Price = " + phone1.price);
        System.out.println("Price = " + phone1.getPrice());
        
        Smartphone phone2 = new Smartphone("XYZ");
        System.out.println("Price = " + phone2.price);
        System.out.println("Price = " + phone2.getPrice());
    }
}

クラスSample1のメソッドmain()では,phoneというSmartphone型の変数を宣言し,new演算子により生成したインスタンスをその変数に保持させている.生成したインスタンスは,phoneという名前で参照する.フィールドやメソッドのアクセスには,ドット演算子を用いる.phone.priceによりphoneのフィールドpriceを参照したり,phone.getPrice()によりphoneのメソッドgetPrice()を呼び出したりすることができる.

メソッドmain()はJava プログラムにおいて特殊なメソッドであり,javaコマンドの引数として指定したクラスの実行開始点である.たとえば,クラスSample1を実行した場合,Sample1main()から実行が始まる.

Javaソースコードのコンパイルと実行

Javaプログラムは以下に示す手順で実行する.

  1. ソースファイルをエディタで作成する.
  2. ソースファイルをコンパイルする.
  3. Java実行環境で実行する.

ソースファイルとは,Javaソースコードを格納したテキストファイルであり,拡張子".java"を持つ.C言語のプログラム作成と同様に,Emacsなどのテキストエディタを用いて作成することができる.

ソースファイルをJavaコンパイラ(javac)でコンパイルすると,クラスやインタフェースごとにクラスファイルと呼ばれるバイナリファイルが作成される.クラスファイルは,拡張子".class"を持つ.クラスファイルはバイトコード(bytecode)とも呼ばれ,プラットフォームに依存しないという特徴を持つ.ソースファイルSample.javaのコンパイルは,以下のコマンドにより行う.

% javac Sample.java

バイトコードは,Java仮想機械(JVM: Java Virtual Machine)と呼ばれるインタプリタ上で解釈実行する.インタプリタは,バイトコードを各計算機に向けの機械語に逐次変換して,その処理を実行する.クラスファイルSample.classの実行は,以下のコマンドにより行う.

% java Sample

Sample1.javaのソースコードをコンパイルして実行すると,以下のようになる.

% javac Sample1.java
% java Sample1
Price = 35000
Price = 35000
Price = 30000
Price = 30000

ここで,クラスSmartphoneがクラスSample1で利用されているため,Smartphone.javaも自動的にコンパイルされる.

パッケージとアクセス制御

大規模なプログラムを作成する際には,他の開発者が作成したクラス(あるいはインタフェース)を再利用する場合が多い.さまざまな開発者が作成したクラスを混在させて利用する場合の名前の衝突を避けるため,Javaにはパッケージという仕組みが用意されている.Java SEのクラスライブラリに含まれる主なパッケージを以下に示す.

ソースファイルの先頭にpackage宣言が存在すると,そのファイルで定義したクラスやインタフェースはそのパッケージ(名前空間)に属することになる.たとえば,次のように記述すると,クラスFileはパッケージjp.ac.ritsumei.csに属する.

package jp.ac.ritsumei.cs;

public class File {
    // クラスの本体
}

この場合,Fileの完全限定名(fully-qualified name)はjp.ac.ritsumei.cs.Fileとなり,Java SEの標準ライブラリ(java.ioパッケージ)に含まれるjava.io.Fileと区別される.ソースファイルにpackage宣言が存在しない場合,そのファイルで定義されているクラスは無名パッケージに属することになる.同じパッケージに属するクラスどうしは,特にパッケージ名を指定しなくともお互いに利用可能である.異なるパッケージに属するクラスを利用する場合は,パッケージ名を含む完全限定名で指定することになる.たとえば,パッケージjp.ac.ritsumei.csに含まれるクラスFileを指定する場合は,jp.ac.ritsumei.cs.Fileとすればよい.また,import文を用いると,完全限定名を指定しなくともクラス名だけでクラスを指定可能となる.よって,次のコードにおいて,単純にFileと記述した場合は,jp.ac.ritsumei.cs.Fileと同じものを指す.つまり,file3の型はjp.ac.ritsumei.cs.Fileとなる.

import jp.ac.ritsumei.cs.File;

public class FileManager {
    jp.ac.ritsumei.cs.File file1;  // 完全限定名で指定
    java.io.File file2;            // jp.ac.ritsumei.cs.Fileとは異なるクラス
    File file3;                    // jp.ac.ritsumei.cs.Fileと同じクラス
}

特定のパッケージの直下に属するクラス群をまとめて指定する場合は,次のように"*"を用いてimport宣言を記述することができる.

import java.io.*;

public class FileManager {
  // クラスの本体
}

このように記述することで,パッケージjava.ioに属するクラスをクラス名だけで指定できる.ここで,java.langパッケージについては,特にimport文を記述しなくとも自動的に取り込まれている.たとえば,今まで何度も登場したSystemクラスの完全限定名はjava.lang.Systemである.

パッケージに関連して,アクセス制御(可視性)に関する)修飾子を以下にまとめる.