クラスについて


はじめに

この文書はJAVAの中でも理解しにくいと思われるクラスなどについて、できるだけわかりやすく、そして正しく理解してもらうために作りました。専門的な本は読んでいないので、正確性に欠ける部分があるかもしれませんが、もしあったらにメールなどで教えてください。

目次


メソッド


まずはメソッドは何かについて説明します。次のプログラムを見て下さい
class Js_1{
    public static void main(String[] args){
        int a = 567,b = 675;
        System.out.println(a+"と"+b+"の最大公約数は"+gcd(a,b));
    }
    
    static int gcd(int a , int b){
        int c;
        while(a > 1){
            c = a;
            a = b % a;
            b = c;
        }
        return b;
    }
}
簡単な復習をしておきます。上のプログラムでの"Js_1"はクラスといい、JAVAのプログラムはこれを基盤として動いています。2行目はいろいろ前に単語がくっついていますがこれは後で説明します。重要なのは"main"の部分で、プログラムは必ずmainと書かれた部分から開始されます。
さて、下にgcdと書かれた文があるでしょう。これがメソッドです。メソッドは、呼び出されると(4行目でgcd(a,b)と書くと)、制御がメソッドに移り(プログラムの実行の過程が7行目に移り)、値を受け取ります(gbd()の中身int a,int bに567,675という値が入る)。そしてその後の行が実行され、最小公倍数がbに入ります。return文はその後に続く値(上の場合はbの値)をメソッド自体の値としたのち、もとの4行目に制御がもどります。これがメソッドの仕組みです。メソッドは一種の関数とも言うことが出来ます。ちなみにmainもメソッドです。平方根を求めるMath.sqrt()もメソッドです。mainメソッドなど、メソッド自体に値を持たせる必要がなければintとかのかわりにvoidと指定します。値を持たせない場合はreturn文は不必要ですがif文の分岐の中で終了させたい場合などはreturn;と記述してください、値をもたせる場合は必ずreturn 値;という形でメソッドを抜け出さなければコンパイルエラーになります。

最後に初心者がよく陥りすぎなエラーについて説明しておきましょう。
次のプログラムを見て下さい。
class Js_1{
    public static void main(String[] args){
        double a = 567.0,b = 675.0;
        System.out.println(a+"と"+b+"の最大公約数は"+gcd(a,b));//間違い
    }
    
    static int gcd(int a , int b){
        int c;
        while(a > 1){
            c = a;
            a = b % a;
            b = c;
        }
        return b;
    }
}
変更点はmainの中のintをdoubleに変更しただけです。しかし、このプログラムをコンパイルしようとするとエラーが起こります。それは既に前の章で述べた型変換が原因です。これを解決するには
        System.out.println(a+"と"+b+"の最大公約数は"+gcd((int)a,(int)b));
としてやる必要があります。大変良くありがちなエラーなので覚えておきましょう。

Go to Index

クラスの構造 その1


さて、メソッドの説明が終わったので本格的にクラスの説明を始めます。クラスは一言で言えば関連性のあるいくつかの変数とメソッドのまとまりと言えます。形式は次のようになります。
class クラス名{
	型 変数名;
	型 変数名;
	:

	コンストラクタ
	メソッド
	メソッド
	:
}
これだけではイメージがつかめないと思うので実際のプログラムで説明しましょう。
import java.io.*;

public class Js_2{
	public static void main(String args[]) throws Exception{
		BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
		System.out.print("定円の半径=");
		double r=(new Double(in.readLine())).doubleValue();
		System.out.print("円錐の高さ=");
		double h=(new Double(in.readLine())).doubleValue();
		Ensui e=new Ensui(r,h);
		System.out.println("円錐の体積:"+e.taiseki());
		System.out.println("円錐の表面積:"+e.hyoumen());
	}
}

class Ensui{
	double r;
	double h;
	Ensui(double r1,double h1){
		r=r1;
		h=h1;
	}
	public double taiseki(){
		return (r*r*h*Math.PI)/3;
	}
	public double hyoumen(){
		return Math.PI*r*(Math.sqrt(r*r+h*h)+r);
	}
}
7,9行目はキーボードから入力した値を変数に代入する部分です。
さて、まずクラスの使い方を説明しましょう。クラスというのはそもそもintやdoubleと同じ、変数の形式を定義するものだと言えます。例えば、文字列を扱うクラス、Stringについて言うと、String str=new String();とすることで、strを文字列を扱う変数として定義することが出来ます。クラスで定義されるこの変数のことをオブジェクトといいます。上のプログラムでは、Js_2とEnsuiがクラスで、eがEnsuiクラスのオブジェクトということになります。
オブジェクトを宣言するときは次のような形式を取ります。

クラス名 オブジェクト名=new クラス名(パラメータ1,パラメータ2,...);

次に、下にかかれた円錐を扱うクラス"Ensui"に注目しましょう。
rは円錐の半径、hは円錐の高さです。これらの変数はクラスEnsuiの中ならどこでも使用することが出来ます。
さて、ここでインスタンス変数静的変数の違いを説明します。まず指定の仕方です。静的変数はstaticと指定することで利用できます。何も指定しなければインスタンス変数になります。
さて、両者の違いは、インスタンス変数はオブジェクトに属していて、静的変数はクラスに属しているというところにあります。次の例を見て下さい

public class Js_3{
	public static void main(String args[]) throws Exception{
		Dog poti=new Dog(12.34);
		Dog hati=new Dog(6.15);
		Dog.number_of_legs=4;
		poti.length_of_legs=13.35;
	}
}

class Dog{
	static int number_of_legs;
	double length_of_legs;
	Dog(double l){
		length_of_legs=l;
	}
}
犬を扱うクラスDogがあったとしましょう。ここで、犬の足の数というのは個々の犬によって違うということはなく、犬に共通の値を持ちます。このようなデータは、各オブジェクトにデータとして持たせるよりはクラス共通のデータとして扱ったほうがメモリを節約できます。そのため、number_of_legsをクラス共通の変数、静的変数としたのです。
一方、犬の足の長さは、個々の犬によって異なります。このようなデータは各オブジェクトにデータとして持たせる必要があります。そのため、length_of_legsをオブジェクトに属する変数、インスタンス変数としたのです。
なお、これらの変数を使用するには次のような形式をとります。
インスタンス変数の場合:オブジェクト名.変数名
例:上のプログラムの、poti.length_of_legs;

静的変数の場合    :クラス名.変数名
例:Math.PIなど


次にインスタンスメソッド静的メソッドについてです。意味としては変数と同様、前者がオブジェクトに属し、後者がクラスに属するということです。では、どういうのがオブジェクトに属し、あるいはクラスに属するのでしょうか。基本的な考え方としてはインスタンス変数を使用するメソッドはインスタンスメソッドでなければならず、それ以外のメソッドは静的メソッドにすべきということです。例えばStringクラスで小文字を大文字に変換するインスタンスメソッド、toUpperCase()は、オブジェクトごとに値として持っている文字列のデータを利用しているのでインスタンスメソッドでなければなりません。一方、Mathクラスで平方根を求める静的メソッド、sqrt()は、インスタンス変数を利用していないので静的メソッドにすべきなのです。使用方法は以下のようになります。
インスタンスメソッドの場合:オブジェクト名.メソッド名

静的メソッドの場合    :クラス名.メソッド名
最後にコンストラクタについて説明しましょう。コンストラクタもメソッドです。コンストラクタはオブジェクトを作ったときに、オブジェクトのインスタンス変数などを初期化する役割を持っています。もう一度先ほどのプログラムを見てみましょう。

import java.io.*;

public class Js_2{
	public static void main(String args[]) throws Exception{
		BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
		System.out.print("定円の半径=");
		double r=(new Double(in.readLine())).doubleValue();
		System.out.print("円錐の高さ=");
		double h=(new Double(in.readLine())).doubleValue();
		Ensui e=new Ensui(r,h);
		System.out.println("円錐の体積:"+e.taiseki());
		System.out.println("円錐の表面積:"+e.hyoumen());
	}
}

class Ensui{
	double r;
	double h;
	Ensui(double r1,double h1){
		r=r1;
		h=h1;
	}
	public double taiseki(){
		return (r*r*h*Math.PI)/3;
	}
	public double hyoumen(){
		return Math.PI*r*(Math.sqrt(r*r+h*h)+r);
	}
}

メソッド名がクラス名と同じメソッドがコンストラクタです。上の場合だとEnsui(double r1,double r2)がそうです。オブジェクトを指定するときに、コンストラクタと同じ形式でパラメータを指定するとそのコンストラクタが呼び出されます。上の例ではコンストラクタは引数の値をEnsuiクラスのインスタンス変数に代入する役割を持っています。
さて、ここまで説明すればクラスを一通り利用することができるようになります。いろいろプログラムを変えたりして試してみましょう。
なお最後に補足ですが、変数名やクラス名やメソッド名を決める時にある法則に従って決めるという習慣があります。それは次のようなものです。
・クラス名の最初の文字は必ず大文字にし、後は小文字にする。
・メソッド名の最初の文字は必ず小文字にし、後も小文字にする。
・メソッド名やクラス名がいくつかの英単語のまとまりである場合、2番目以降の単語の最初の文字は大文字にする。(例:InputStreamReaderクラス、removeElementAtメソッド)
・変数名は先頭は必ず小文字、できればすべて小文字にする。ただし、final指定された変数は定数として変数と区別ためすべて大文字で書く。

Go to Index

クラスの構造 その2


ここでは、クラスについて少し詳しく説明します。
まずはメソッドのオーバーロードについて説明しましょう。次のプログラムを見て下さい。
import java.io.*;

public class Js_6{
	public static void main(String args[]) throws Exception{
		System.out.println(Oval.menseki(3));
		System.out.println(Oval.menseki(3,4));
	}
}

public class Oval{
	public static double menseki(double a,double b){
		return Math.PI*a*b;
	}
	public static double menseki(double a){
		return Math.PI*a*a;
	}
}

実行結果:
28.274333882308138
37.69911184307752
上の値は半径3の円の面積、下の値は長軸8、短軸6の楕円の面積です。さて、Ovalクラスに注意を向けると、mensekiメソッドが2つあるのに気づくでしょう。JAVAではこのようにメソッド名が同じでも後に続く引数の指定が違っていればそれぞれ違うメソッドとして扱います。これがメソッドのオーバーロードです。呼び出すときに呼び出した側の引数の指定に対応したメソッドが実行されます。これは、例えば引数が6つや7つになるような複雑なメソッドがあったとして、それのすべての機能を使う必要がないときにいちいちそれらを指定するのが面倒だったら、引数が1つや2つのメソッドを新たにオーバーロードする、こんな使い方をします。上の場合だと、円の面積を求めるときにいちいち短軸の半分の長さ、長軸の半分の長さを指定するのは面倒だから、半径だけを引数に持つmensekiメソッドを新たにオーバーロードしたわけです。
さて、メソッドにこういう指定が出来ると言うことは、コンストラクタにも当然このような指定は出来るわけです。事実、Stringクラスには複数のコンストラクタがあります。
String str1=new String();
String str2=new String("Hello World");
1行目はstr1は空の文字列になります。str2は"Hello World"という文字列が入ります。このようにコンストラクタが複数あるとオブジェクトの初期化を楽に行うことができます。さて、次は変数の修飾子について説明しましょう。まずはfinalについてです。finalと指定すると、その変数は値の変更ができなくなります。つまり、定数になったということです。例えば次のような使いたかをします。

static final double PI=3.14159265358979;
次に、public,protected,privateについて説明します。privateは同じクラスの中からしかアクセスできません。protectedはサブクラスまたは同じパッケージ内のコードからしかアクセスできません。(サブクラス、パッケージについては後で説明)publicはどのクラスからでもアクセスできます。何も指定しないと同じパッケージのコードからのみアクセスできるようになります。
最後にクラスの継承について説明します。クラスの継承はオブジェクト指向プログラミングに欠かせないものです。これから作ろうとしているクラスに他のクラスの機能を引き継がせたい、既に作ったクラスの機能の拡張をしたいと思ったときにクラスの継承をします。次のプログラムを見て下さい

import java.io.*;

public class Js_7{
	public static void main(String args[]){
		Person taro=new Person(10,"太郎");
		//taro.age=15;	←ageがprotectedだとしてもコンパイルエラー
		System.out.println("名前:"+taro.getname());
		System.out.println("年齢:"+taro.getage());

		Animal poti=new Person(8,"ポチ");	//これは正しい
		//System.out.println("名前:"+poti.getname());	←誤り
		System.out.println("名前:"+((Person)poti).getname());	//これなら正しい
		System.out.println("年齢:"+poti.getage());	//これも正しい
		//Person poti=new Animal(4);	←これも誤り
	}
}

class Animal{
	private int age;
	Animal(int a){
		age=a;
	}
	int getage(){
		return age;
	}
}

class Person extends Animal{
	private String name;
	Person(int a,String b){
		super(a);
		//age=a;	←コンパイルエラー
		name=b;
	}
	String getname(){
		return name;
	}
}

上では年齢を扱うクラスAnimalとそれを継承してさらに名前まで扱えるようになったクラスPersonを定義しました。PersonクラスはAnimalクラスを継承しているので、Animalクラスのサブクラスと呼ばれます。逆に、AnimalクラスはPersonクラスのスーパークラスと呼ばれます。サブクラスでは基本的にはスーパークラスの変数、メソッドをすべて使うことが出来ます。そのため、taro.getage()として問題なかったわけです。
しかし、Personクラスの中でAnimalクラスのインスタンス変数ageを使おうとするとエラーが起こります。それはageがprivateの指定を受けているからです。privateは同じクラスの中からしかアクセスを許しません。従ってageはAnimalの中でしか直接操作することが出来ず、Personで直接値を参照するとエラーが起こるのです。サブクラスならアクセスできるprotectedの指定をageにすればPersonでもageを使えるようになります。それでも、AnimalクラスのサブクラスでないJs_7クラスの中でtaro.ageとして直接扱おうとするとエラーがおこります。どのクラスからもアクセスできるのはpublicです。これらの指定は不用意に他のクラスからアクセスされることを避けるために行われます。
最後にコンストラクタについて注意すべきことがあります。Personクラスの中でsuper(a)としているでしょう。これはスーパークラスのコンストラクタAnimalを呼び出す文です。ここでageの指定をprotectedにしてsuper(a)をage=aにしても問題ないのではと思う人もいるかもしれません。しかしそれではダメなのです。サブクラスのコンストラクタではその最初の行にsuper文がないとsuper()を勝手に実行してしまいます。上の場合、super()、つまりAnimal()は定義されていないのでコンパイルエラーが起こってしまいます。それでsuper(a)としているのです。
ポチに関する指定はまだまだ謎な部分も多いようですが、それについては3.newについての説明の後で説明します。
最後に補足ですが、すべてのクラスはObjectクラスをスーパークラスとして持っています。何も継承していないつもりでも実はObjectクラスのサブクラスだったのです。

Go to Index

newについて


ここでの説明は少々理解しづらいかもしれませんが、ここを理解することが出来ればJAVAを本当に理解することが出来るのでがんばりましょう。
 まず、メモリについてです。コンピュータの作業はメモリと呼ばれる記憶領域で行われます。メモリは電気信号で値を記憶しているので電源が消えれば記憶された内容も消えますが、高速で処理できるのでプログラムやそのデータなどはこのメモリ上で動かされます。次に説明する変数の値などもすべてこのメモリ上に記憶されています。
 次に変数について説明しましょう。ひとつプログラムの例を出しましょう。

public class Js_4{
	public static void main(String args[]) throws Exception{
		double a=3.2;
		double b=5.8;
		double c=a+b;
		System.out.println("a="+a);
		System.out.println("b="+b);
		System.out.println("a+b="+c);
	}
}

実行結果:
a=3.2
b=5.8
c=9.0


上のプログラムではdouble型の変数a,b,cを3行〜5行目で宣言しています。double型の変数は理科の定数の値のように、"1.243×10の8乗"のような形式で値をあらわしています。ここで重要なのは上のdouble型の変数は変数そのものが値をもっているということです。「は?」と思う人もいるかもしれませんがこの後に説明する配列変数やオブジェクト等はこれとは異なるので後でこの違いが重要になってきます。
ではどう違うのか説明しましょう。次のプログラムを見て下さい。
public class Js_5{
	public static void main(String args[]) throws Exception{
		String a=new String("Hello");
		String b=new String("World");
		System.out.println(a+b);
	}
}

実行結果:
HelloWorld

3、4行目では、a,bというStringクラスのオブジェクトを作っています。わからなかったら今はa,bは文字の列を扱う変数だというくらいに思って下さい。
さて、重要なのはこのプログラムでのa,bはメモリの場所を表す変数であり、aそのものが文字の値を持っているわけではないということです。
メモリの記憶場所にはそれぞれ番号がつけられています。そこで、例えばメモリの1050番〜1054番目にそれぞれ、'H','e','l','l','o'という文字が記憶されていたとしましょう。この場合、メモリ上でのaの値は"Hello"ではなく、1050という、"Hello"という値の保存されている場所の番号(アドレス)となります。JAVAでは、printlnの中などでaと書かれていた場合に、aの値が指し示すメモリ上の場所を探し、そこに保存されている値をあたかもaの値であるかのようにふるまいます。
さて、謎に思われてきたnewというキーワードについて説明します。newというのは「メモリの???番地にこの後に続く値を保存しました」ということを意味し、newの値自体はそれを保存した番地を返します。上の例でいえば、newは「メモリの1050番地から順番に"Hello"という文字を記憶しました」ということを意味し、それ自体は1050という値を持っているので、aも1050という値を持ち、結果としてaと指定すれば"Hello"の保存している場所を指し示す(参照する)ことができるようになるのです。
一般に、オブジェクトは、メモリの番地を記憶している変数であり、値を得るときはオブジェクトの指している場所に保存されている値をその変数の値とします。newは、メモリ上に値を保存し、その保存した場所を知らせてくれる演算子ということになります。

さて、ここでもう一度2の継承に関するプログラムを見てみましょう
import java.io.*;

public class Js_7{
	public static void main(String args[]){
		Person taro=new Person(10,"太郎");
		System.out.println("名前:"+taro.getname());
		System.out.println("年齢:"+taro.getage());

		Animal poti=new Person(8,"ポチ");	//これは正しい
		//System.out.println("名前:"+poti.getname());	←誤り
		System.out.println("名前:"+((Person)poti).getname());	//これなら正しい
		System.out.println("年齢:"+poti.getage());	//これも正しい
		//Person poti=new Animal(4);	←これも誤り
	}
}

class Animal{
	private int age;
	Animal(int a){
		age=a;
	}
	int getage(){
		return age;
	}
}

class Person extends Animal{
	private String name;
	Person(int a,String b){
		super(a);
		name=b;
	}
	String getname(){
		return name;
	}
}

ポチに関する文は今までは不可解にも思える文であったかもしれません。さて、これはどういうことなのか次の文で説明してみましょう。
Animal poti=new Person(8,"ポチ");
この文の意味は「メモリ上にポチのデータをPersonクラスのデータとして確保し、それをJAVA上ではAnimalのデータとして扱う」と言う意味です。つまり大は小を兼ねるといった感じで、Personクラスのデータとしてメモリ上に確保しておけば、AnimalクラスのデータとしてもPersonクラスのデータとしても扱えるということです。Animalクラスとして扱うつもりだったが、途中でPersonクラスの機能も使いたくなったという場合は、そのオブジェクトの前に(Person)をつけるとPersonクラスの機能も利用できるようになります。これについては、複数のオブジェクトのリストを扱う重要なVectorクラスで、Objectクラスを返すメソッドgetElementなどにこれを利用することができて便利です。
配列でもnewというキーワードが出てきましたが、実は配列もメモリ上の値を保存している場所を指し示す変数だったのです。このようにオブジェクト、配列のような、メモリの場所を指し示す変数のことを参照、そしてC言語ではポインタと呼びます。

Go to Index