Java の基本(9) ・・・ オブジェクト


 今回はやっと、Java の Java らしい一面である「オブジェクト」という概念について学ぶ。これは、BASIC, FORTRAN, C といった「手続き型」プログラミング言語や、LISP などの「関数型」プログラミング言語、あるいは Prolog などに代表される「論理型」プログラミング言語とは言語設計の思想がまったく異なる、「オブジェクト指向型」と総称されるプログラミング言語を特徴付ける本質的な一面である。
 オブジェクトというものの基本が理解できたら、すでに基本を学んだ「配列」と、int, char, float, double, boolean といった基本データ型ではないがそれに劣らずよく使う「文字列」についてもここで学ぶ。文字列は String という「クラス」の具体的なオブジェクトであり、データ型ではない。
一方、配列はクラスオブジェクトとは異なるタイプのオブジェクトであるが、これらは「参照型」という概念で統一される。

 もっと詳しいことは ここの後半(§1.3)ここ


 オブジェクト指向とは
 「オブジェクト指向」は object-oriented の訳語である。すなわち、オブジェクトというものに「適合する」、オブジェクトというものに「向いている」、オブジェクトを「ベースにしている」という意味である。したがって、オブジェクトとは何かということがわからないと、オブジェクト指向型言語の(したがって Java の)本質は理解できないし、なぜ「そのように書くのか」に対する理屈も理解できない。

 オブジェクト指向プログラミング(object-oriented programming)では、プログラムが対象としようとしている世界に属す「もの」(=オブジェクト、object)を中心に据えて、それらの特徴を記述することがプログラミングである。
例えば、レストランという世界を考えよう。レストランでは個々の客、個々のウェイトレス、個々のコックなどがそれぞれオブジェクトであり、個々の客やウェイトレスやコックは誰も決められた一定の仕事(動作)をする(言い換えれば、それぞれに特徴的な独自の機能=役割を持っている)。例えば、客は料理の注文をし、ウエイトレスは客からの注文をコックに伝えたり、調理が終った料理を客に出したりし、コックは料理を作る。このような機能(動作)のそれぞれを Java ではメソッド(method)という。
 一方、こういった仕事をするためにはいろいろな種類の道具や材料(コックは鍋や包丁など、ウエイトレスはメニューや注文伝票やPDAなど)が必要であるし、個々のウエイトレスや個々の客や個々のコック(すなわち、個々のオブジェクト)を区別する名前や年齢、性別などの性質・特性(属性、attribute)を持っている。このような属性を保持するもの(変数)を Java ではフィールドという(他のオブジェクト指向言語ではデータメンバーとかメンバー変数とか、単にメンバーとか、スロットともいう)。

 こういったオブジェクトの属性・性質、すなわち、仕事・動作(機能・能力)や道具(情報・データ)をきちんと決めることによって、抽象的概念としての「客」とか「ウエイトレス」とか「コック」といったものを特徴付けることができる。 オブジェクト指向言語では、

 オブジェクト指向でないプログラミング言語ではメソッドのことを通常、関数といい、メッセージのことを関数の引数というのが普通である。
クラスは一種のテンプレート(template、雛形、鋳型)であると考えることもできる。 各クラスの具体的オブジェクトは、その鋳型を使って生成する。このようなことをインスタンス化(instantiation)という。

 例えば、レストランにおいては、客、ウェイトレス、コックという3つのクラスがあ り、各クラス(例えば、ウェイトレスというクラス)に具体的に A, B, C という3人の ウェイトレスがいるならば、A, B, C それぞれはウェイトレスというクラスのインスタ ンス(オブジェクト)である。客 D (客というクラスのオブジェクトの1つ)がウェイ トレス C というオブジェクトに渡すメッセージは注文する料理であり、そのメッセージ を受けてウェイトレスはコックに料理の注文を出す(この動作がウェイトレスというオ ブジェクトが備えているメソッドの1つである)。客は、ウェイトレスがどのような知 識を持っているかとか、受けた注文をどのようにコックに伝えるのかは知らない(知る 必要がない)。
 このように、あるオブジェクトの持っている情報・データや機能が外部 から分からないようにすること(情報隠蔽)をカプセル化(capsulation)といい、これもオブジェクト指向言語がもつ特徴の1つである。外部から見える情報があってもよい(publicなフィールドやメソッド)。カプセル化、public については現段階では分からなくてもよい。


 データ型の拡張としてのクラス
 Java のデータ型の基本についてはすでに学んだ:

  基本型
    論理型      boolean
    数値型
      整数型    byte, short, int, long
      文字型    char
      浮動小数点型 float, double
  参照型        この§で学ぶ
  null型        この§で学ぶ
 先ずここでは、Java のクラスという概念をデータ型の一般化として捉える視点からオブジェクトについて説明する。

 Java をはじめとするオブジェクト指向型言語では、データ型という概念はもっと一般化されてクラスというものになり、個々のデータ型の値を取る '変数' という「もの」もオブジェクトという概念に一般化される。すなわち、オブジェクトは、変数のように単に一定のタイプのデータを保持できるというだけでなく、複数のタイプのデータを同時に持つことができるし、一定の機能(つまり、何らかの処理を実行できる能力)もいくつか併せ持っている。したがって、オブジェクトは単なる「変数」以上に一般的な「もの」であるが、抽象的な概念ではなく、具体的に存在する「もの」である
例えば、文字列 "abc" や "uvwxyz" はそれぞれオブジェクトである。一方、文字列全体(あるいは別の言い方をすれば、文字列という概念)のことを Java では String クラスと言う。つまり、 "abc" や "uvwxyz" などの個々の文字列はどれも String クラスのオブジェクトである。このように、クラスは、ある一定のタイプのデータを保持できると同時に一定の機能を有したオブジェクト全体を呼ぶ呼称であり、そういったオブジェクトとは何かということを特徴付ける抽象的な概念である



 クラスの例 ・・・ String クラス
 我々はすでに、
  プログラム例: ここ    
といった Java のプログラムを書いてきた。実は、これは ex5 というクラスを定義しているプログラムである。そのような「クラスの定義の仕方」は次回に学ぶとして、ここでは Java のパッケージの1つである
    java.lang    
の中の1つのクラスとして定義されている String クラスについて、その基本的なことを学んでおきたい。
java.lang パッケージは Java のパッケージの中でも最も基本的なクラスの定義を含んでいるものなので、わざわざ
    import java.lang.*;    
と書かなくても暗黙に取り込まれる(よって、書かなくてもよい)ことになっている。


@ String クラスのオブジェクトの宣言の仕方

 String はクラスであり、クラスはデータ型の一般化であるから、int 型の変数 n を

    int n;  
と宣言するのと同様に、s が String クラスのオブジェクトであることを "宣言" するには
    String s;                 // @
のように書く(オブジェクトは変数の一般化であることに注意)。
ただし、宣言しただけではオブジェクトは生成されない(s は文字列の名前だよ、ということが宣言されるだけであって、s が値として持つ具体的な文字列は生成されない)。配列の宣言を思い出すとよい(やはり、宣言だけでは配列の実体は生成されなかった)。

A String クラスのオブジェクトの初期化/生成の仕方

    String s;
のように、すでに宣言されている String オブジェクト s の実体を生成する(すなわち、s を初期化して、具体的文字列を値として持たせる)ためには、配列の生成・初期化と同様に
    new演算子  
を使って次のように書く:
    s = new String("abcd");   // A
これも、やはり配列の初期化の場合と同様に、new を省略して、
    s = "abcd";               // A’
と書いてもよい。

B String クラスのオブジェクトの宣言と初期化・生成を同時に行なうには

 やはり配列の場合と同様に、宣言@と初期化・生成A(またはA’)を一緒にして

    String s = new String("abcd");   // B
と書くことや、さらに new を省略して
    String s = "abcd";               // B’
と書くことも許されている。



 文字列に対する演算
 String クラスには、2つの文字列の大小関係、相当性、文字列の一部(部分文字列)の取り出し、文字列をつなげる、等の操作を行なうためのメソッドが定義されている。それらのいくつかを挙げると・・・ (一般に、こういうメソッドやフィールド等について調べたいときには、API: http://java.sun.com/j2se/1.5.0/docs/api/index.html を参照せよ)、

String クラスのメソッド意 味
char charAt(int index) この文字列の指定した添字位置の文字
int compareTo(String anotherString) この文字列と引数の文字列とを辞書式順序で比較
String concat(String str) この文字列の末尾に引数の文字列を連接
int indexOf(int c) 文字cをこの文字列の先頭から探し、最初の出現位置を求める
int indexOf(String str) 文字列strをこの文字列の先頭から探し、最初の出現位置を求める
int length() この文字列の長さ
String substring(int beginIndex) この文字列の指定した添字位置から末尾までの部分文字列
String substring(int beginIndex, int endIndex) この文字列の指定した開始添字位置から指定した終了添字位置までの部分文字列
String toLowerCase() 小文字に変換
String toUpperCase() 大文字に変換
static String valueOf(boolean b) 論理値bを文字列に変換
static String valueOf(char c) 文字cを文字列に変換
static String valueOf(char[ ] data) 文字配列dataを文字列に変換
static String valueOf(double d) double型実数dを文字列に変換
static String valueOf(float f) float型実数fを文字列に変換
static String valueOf(int i) 整数iを文字列に変換

 上の表から、各メソッドがどのようなデータ型の引数を何個取り、どんなデータ型の値を戻り値とするかを読み取って欲しい。 例えば、

  1. メソッド charAt は int 型の引数 index を1つ取り(index という名前は仮に付けられているだけであるから、プログラムをかく人が別の名前に自由に変えてよい)、この文字列の先頭から index 番目の文字を戻り値とする(先頭文字は0番目であることに注意する)。String 型のオブジェクトが例えば
        String str = "abcdefg";    
    のように生成されているとき、
        char c = str.charAt(2);    
    のように使い、そのときの str が「この文字列」である。
    右辺の値である str.charAt(2) すなわち "abcedfg" の2文字目の文字 'c' が、左辺にある char 型の変数 c に代入される(2文字目の文字は 'b' ではないことに注いのこと)。

    static でないメソッドの使い方

    この例のように、static でないメソッド(表の中で static と付いていないもの)は、オブジェクトを必ず生成して

        オブジェクト名 .メソッド名(このメソッドの実引数)    
    のように使わなければならない。

    今の時点では参考程度の理解でよいが、

    • メソッドには大きく分けて

      • クラスメソッド (class method)
      • インスタンスメソッド (instance method)

      の2種類があり、
    • クラスメソッドは、修飾子 static を付けて定義されるメソッドのことである。このタイプのメソッドは 「クラス名.メソッド名」 のように、クラス名を指定するだけで呼び出して使うことができる(同一のクラス内であれば、クラス名の指定もいらない)が、
    • インスタンスメソッドは、修飾子 static を付けずに定義されるメソッドであり、このタイプのメソッドは、クラスのインスタンス(すなわち、オブジェクト)を生成することによって、そのインスタンスの中のメソッドとして初めて実体が存在するようになり、上の例に示したように「オブジェクト名.メソッド名」という形式で使わなければならない。
  2. メソッド compareTo は String 型の引数(すなわち、文字列)を1つ取り、この文字列と引数の文字列の大小関係を辞書式順序で比較し、int 型の戻り値(この文字列>引数の文字列のときは正の値、この文字列=引数の文字列のときは0、この文字列<引数の文字列のときは負の値)を返す。例えば、

       String str = "abcdefg";

    のとき、

       str.compareTo("xyz")

    の値は負である。オブジェクト str の部分は

       "abcdefg".compareTo("xyz")

    のように書いてもよい。

  3. メソッド length は引数を取らないメソッドであり、その文字列の長さを戻り値とする。例えば、

       str.lenth() "abcdefg".length()

    の値は 7 である。

  4. メソッド subString には2種類あり、引数(int 型の整数)が1つの場合はこの文字列の指定した添字位置から末尾までの部分文字列を戻り値とし、引数が2個の場合はこの文字列の指定した開始添字位置から指定した終了添字位置までの部分文字列を戻り値とする。例えば、

        str.substring(2)

    の値は文字列 "cdefg" であり、

        "abcdefg".substring(3,6)

    の値は文字列 "def" である(文字列の終わりは endIndex-1 番目の文字であり、この例の場合 "defg" ではないので注意のこと)。

  5. メソッド valueOf は static なメソッド(クラスメソッド)であるからオブジェクトに結び付けて使うことはせずに、このメソッドが定義されているクラスの名前である String と結びつけて

        String.valueOf(12>345)

    のように使う。この例の場合、値は文字列 "false" である。つまり、

    static なメソッドの使い方

    そのメソッドが定義されているクラスの名前と結びつけて

        クラス名 .メソッド名(このメソッドの実引数)

    のように使わなければならない。


(例) String クラスのメソッド


    String abc = new String("abc");            // Stringクラスのオブジェクトabcの生成
    String pqrst = "pqrst";                    // Stringクラスのオブジェクトpqrstの生成           
                                               // (abcの場合とは別の書き方)
    String s25 = "abcdefg".substring(2,5);     // 部分文字列の取り出し

    char c = pqrst.charAt(3);                  // 指定位置の文字
    System.out.println(pqrst.length());        // 文字列の長さ 
    System.out.println(pqrst.compareTo(abc));  // 文字列の大小比較 

    String PQRST;
    PQRST = pqrst.toUpperCase();               // 大文字への変換 
    System.out.println(PQRST.toLowerCase());   // 小文字への変換 
    String s123;
    s123 = String.valueOf(123);                // 数値を文字列へ変換 



 参照型
 一般に、変数には、それが保持する値を格納しておくための場所がコンピュータのメモリ(主記憶装置)上に割り当てられる。例えば、
    int a = 123;  double x;    
と宣言された変数 a, x にはそれぞれ4バイト、8バイトの領域が確保され、a, x が持っている現在値はそこに格納される。
    
      +-----+
    a | 123 |         a の値を入れる場所に、初期値123が入る
      +-----+

      +----------+
    x |          |    x の値を入れる場所は確保されるが、値は未定
      +----------+    
この後で、例えば
    x = 12.3;     
を実行すると、x の格納場所に浮動小数点数の値 12.3 が入れられる:
    
      +----------+
    x |   12.3   |    
      +----------+    
 一方、配列の場合には、
  1. メモリ内の連続する領域に配列要素の並び順に格納場所が割り当てられる。
  2. 同時に、配列名は、それらの配列要素が格納されているメモリの先頭アドレスを値に取る変数として扱われる。
  3. 一般に、アドレスは、メモリ内のその場所を「指している」値と考えることができるので、プログラミング言語によってはポインター(pointer)と呼ぶが、Java では参照(reference)という。また、アドレスを値に取る変数をポインター型変数(Java では参照型変数)という。
  4. 配列名は参照型の変数であり、それが指す先にはその配列の実体であるデータ(=オブジェクト)がある
一般に、参照が指すデータのことをオブジェクトと言う


String クラスの変数は String オブジェクト(文字列データ)への参照である。例えば、

    String s1;    
という宣言では、s1 が String クラスであることが宣言されるだけで、具体的な文字列データ(文字列オブジェクト)は生成されていない:
    
       +----------+
    s1 |          |    s1 の値(参照)は定義されていない
       +----------+    
このあとで、
    s1 = new String("This is a string");    
あるいは
    s1 = "This is a string";    
とすると、String クラスのオブジェクト s1 の実体(すなわち、具体的な文字列) "This is a string" が生成され、s1 にはその文字列が格納されているメモリ領域のアドレスが入る(s1 には "This is a string" へのポインターが入る):
    
       +----------+
    s1 |     ・---+------> "This is a string"
       +----------+    

 参照型には配列型、クラス型、インターフェース型の3つがあるが、String はもちろんクラス型である(つまり、String クラス)。クラス型については後で再度取り上げる。


(演習問題) String の利用

英字の文章を読み込み、その中に現われる各単語の出現頻度を求め、棒グラフで表わせ。

  1. まず、Java アプリケーションをつくれ。
  2. 次に、それをウェブ入出力するアプレットに直せ。