13章 ガーベージコレクター |
1.ガーベージコレクターとは |
ガーベージ(garbage)とは、不要なデータのことである。不要になったデータを集めて処理する機能のことをいう。例えば、C++言語ではあるクラスのインスタンスを作成したら、プログラムでdeleteしないと不要データがいつまでもメモリー上に残ることになる。このことをメモリリークという。 また、削除してしまったインスタンスを参照したりするとエラーになりOSなどによってはそのまま暴走してしまうといったバグも発生する可能性がある。 しかし、javaではインスタンスを作成しても削除する必要がない。不必要になったインスタンスはこのガーベージコレクターがすべて処理してくれるからである。 |
class GarbageTest{ public static void main (String args[ ]){ String str1 = new String("abc); String str2 = new String("ABC); str2 = str1; // "ABC"という文字列はどこからも参照されなくなり、ガーベージコレクトの対象となる |
C:\java>javac *.java
C:\java>java GarbageTest C:\Mjava> |
2.ガーベージコレクターの実行 |
Java Virtual Machine(JVM)は、インスタンスの生成が行われた時など、メモリーを厳守監視している。これにより後でどのメモリー(インスタンス)が参照されていて必要なのか、あるいはもう参照されなくなって不必要になったのかを調べることができるようにしている。 しかし、不必要になったデータを探すのは、アプリケーションが大きくなればなるほど、時間がかかる処理になる。したがってJVMはデータが不必要になったらすぐにガーベージコレクトを行うのではなく、何もしていないときにガーベージコレクトを行うようにしている。 実際にガーベージコレクトが行われるタイミングはJVMの実装に任されており、プログラマやユーザがそのタイミングを関知しなくても良いことになっている。 |
Javaのメリットはプラットフォームを(OS)を選ばないことである。パソコンのようなメモリーの比較的豊富なプラットフォームで実行している分にはメモリーのことをそれほど意識しなくても良いのだが、携帯電話など比較的リソースの乏しいプラットフォームでJavaを実行する場合は、メモリーをできるだけ少なくして使用したい場合がある。このような場合、ガーベージコレクトをJVMに任せていてはメモリーが足りなくなってしまう。 もちろんメモリーを使用したい時に残りのメモリーが足りなくなればJVMはガーベージコレクターを起動させるが、もし時間的に余裕がない場合、実行されてしまっては困る。そこで、プログラマがガーベージコレクタを起動させるタイミングを指定することができる。それには、Systemクラスのgc( )メソッドを使用する。 それには、Systemクラスのgc( )メソッドを使用する。 このメソッドはガーベージコレクタを起動させてほしいとJVMに伝えるだけで、いつ実行させるかはJVM次第である。よくJavaの解説書のこのメソッドの説明に「ガーベージコレクタを実行する」とあるが、これは間違いである。あくまでもできるだけ早くガーベージコレクタを実行させてほしいとJVMに要求するだけである。 |
2.finalize |
C++にはデストラクタがある。デストラクタとはコンストラクタの逆でインスタンスが破壊される時に呼ばれる関数である。しかし、Javaにはデストラクタがない。これは、Javaのインスタンスの破壊はガーベージコレクタに任されていて、インスタンスがいつ破壊されるかわからないためである。しかし、これでは不便なことがある。例えばファイルを開いた場合はファイルをクローズするとか、通信ポートを開いたらそれを閉じるといったメモリー以外のリソースを解放したい場合である。このような場合は、Objectクラスのfinalize( )メソッドをオーバーライドする。finalize( )メソッドはガーベージコレクタによって、メモリが解放される前に呼び出されるメソッドである。このメソッドをオーバーライドしその中でリソースの解放を行う。このfinalize( )メソッドもいつ呼ばれるかは保証されていない。インスタンスが参照されなくなったことが確認され、実際にそのインスタンスが占有していたメモリーを解放される間のどこかで実行されることが保証されているだけである。また、ガーベージコレクタが不要になったインスタンスを発見し、まだfinalize( )メソッドを実行していない場合、SystemクラスのrunFinalize( )メソッドを実行することで、finalize( )の実行を促すことができる。しかしFinalize( )メソッドを実行することでFinalize( )メソッドを実行するのではなく実行するタイミングはJVMの判断に任されている。 |
Category: Java
6.オブジェクト指向とは
6章 オブジェクト指向とは | |
1.オブジェクト指向とクラス | |
「オブジェクト指向」では、処理は二の次である。一番に考えるのはオブジェクトである。オブジェクトというのは「モノ」であり、実際に存在するもののことをいう。クラスとオブジェクトは等価。オブジェクトには決められた仕事や処理が決まっている。これがクラスで定義するメンバーメソッドである。 | |
例)色鉛筆は実際に存在するものである。したがって、オブジェクトつまり「色鉛筆クラス」になる。(色鉛筆構造体)色鉛筆には様々な色があるメンバー変数として「色」「芯の長さ」を決める。 | |
class Color{ int R; int G; int B; } class ColorPen{ |
|
色鉛筆は、絵を描くことができる。メンバーメソッドを定義する。 | |
class Color{ int R; int G; int B; } class ColorPen{ void draw( ){ |
|
オブジェクト指向では、このように「モノ」と「それを扱う関数(メソッド)」が一緒にクラスとして定義されているので、「色鉛筆で食べ物を食べる」というミスは文法的に書けないことになっている。
整理すると「オブジェクト指向」では「モノ(データ、デバイスなど)を第1優先に考える」という分析・設計・実装方法である。 |
|
2.継承 | |
色鉛筆は鉛筆の一種であると考えられる。鉛筆でも絵を描くことができるが、色は黒一色である。色鉛筆では様々な色で絵を描くことができる。 | |
class Color{ int R; int G; int B; } class Pencil{ // 黒で絵をかく class ColorPen extends Pencil{ // colorで絵をかく |
|
このように定義すると「鉛筆クラスは鉛筆クラスの一種」という意味になる。このように、既存のクラスの一種であるクラスを定義することを「継承」という。色鉛筆クラスには「長さ」定義されていないが、鉛筆クラスで定義されているの で、色鉛筆クラスにも「長さ」が定義されていることになる。 この場合、色鉛筆クラスからみて鉛筆クラスを「スーパークラス」、「基底クラス」あるいは「親クラス」といい、鉛筆クラスから見て色鉛筆クラスを「サブクラス」あるいは「子クラス」という。 |
|
上のクラス図は、基底クラスとして「哺乳類クラス」、その子クラスとして「犬クラス」「猫クラス」そして「象クラス」を定義している。さらに「猫クラス」の子クラスとして「三毛猫クラス」「シャム猫クラス」を定義している。 |
|
3.カプセル化 | |
クラスのメンバー変数、メンバーメソッドは誰が触っても良いものと、触るクラスを限定したい場合がある。誰が触っても良いものを「public」、自分と同じパッケージ内のクラスからもしくはサブクラスからしか触れられない物を「protected」、そして自分自身のクラスからしかさわれない物を「private」という。 すべてのメンバー変数、メンバーメソッドをpublicにして、プログラマーたちが決め事としてさわらないようにと決めても良いが、中には決め事を守らないプログラマーもいるため、public、protected、privateを決めておけば文法的にコンパイラでチェックしてくれる。 何故そんなことをするのだろう。これは、作ったプログラムの一部を部品化しやすいようにするためである。オブジェクト指向では、作ったプログラムを部品化しておき、別のプログラムを作る際に再利用できるようにする。例えばインターネットブラウザのプログラムを作った場合、ネットワーク関係の部品は、次に作るメーラーに再利用ができる。このように部品化したクラスやパッケージは使い方さえ知っていればそのソースコードを知らない人でも使うことができる。ところが、内部(ソースコード)を知らないプログラマーが出てくるとバグの原因になる。そこで、protectedであれば勝手なことはされなくて済む。 基本的にメンバー変数はすべてprivateかprotectedにする。そして、参照するためのメソッドと代入するメソッドを用意する。このようなメソッドをアクセサメソッドと呼ぶ。なぜ、こんな面倒なことをするのか、理由は3つある。 1、メンバー変数の型を「int」から「double」に変更しようとした場合、参照する場合と代入する場合の2つに影響してくる。publicな変数だとプログラムすべてに渡ってこの変数を参照する場所と代入する場所を探すのは面倒な作業である。それに対しメソッドを探すのは比較的容易だからである。 2、メンバー変数は他のクラスから参照しても良いが値を変更するのは×という変数があったとする。この場合、参照するメソッドはpublicに、代入するメソッドはportectedかprivateにすればよい。 3、マルチスレッドプログラムにした場合、複数のスレッドが同じ変数にアクセスする場合がある。例えば、あるスレッドがリストをソース中に、別のスレッドがそのリストを参照した場合、矛盾が生じてしまう。このような場合は「セマフォ」というリストを参照する権利を与え権利があるスレッドだけがリストを参照したりさわったりできるうようにする。あらかじめ参照するためのメソッドを用意しておけばセマフォの処理を組み込む場合もそのメソッドをちょっと直すだけなので簡単である。 このように変数やメソッドにアクセスできるクラスを制御することで、あるクラスの部品のようにすることができる。部品のようになったクラスは別のプログラムを作るときにも利用できる。例えば、Javaでは標準でGUIや通信mその他さまざまなクラスがたくさん用意されており、通信をしたいと思えばそのためのクラスをファイル操作したければそのためのクラスをプログラマーが使用するだけだから、プログラムが簡単に書ける。また、携帯電話用のソフトを書きたければ携帯電話会社が公開しているクラス群が利用できる。 また、通信をするためのクラスを利用しようと思った場合、通常その中身がどんなプログラムになっているのか、プログラマーは知る必要がない。つまり、通信をするためのクラスの中身はブラックボックスということになる。 このように、中身を知らなくても部品のように使えるように設計することを「カブセル化」という。 |
|
4.ポリモーフィズム | |
ポリモーフィズムとは、ギリシャ語で「多数の形態」という意味である。プログラムで「ポリモーフィズム」というと同じメソッドを定義することをいう。 例に例えると「鉛筆クラス」の「絵を描くメソッド」と「色鉛筆クラス」の「絵を描くメソッド」は、若干機能がことなるが「絵を描くメソッド」というメソッドの名前は同じである。 例えばint型の絶対値を求めるメソッドを「abs」と名づけると小数も扱えるようなメソッドを追加しようとした場合、「abs_float」と名前を変えなくてはならなかった。Javaではポリモーフィズムによって、引数が異なれば同じ名前のメソッドを複数定義することもできる。このように同じ名前のメソッド(またはコンストラクタ)を作ることを、メソッドをオーバーロードするという。 |
|
5.演算子
5章 演算子 | ||||||
1.演算子の種類 | ||||||
●演算子 ○算術演算子 ・四則演算子 +,-,*,/,% ・符号変換 - ○比較演算子 ・大小比較 <,>,<=,>= ・等価、非等価 ==,!= ○論理演算子 ・論理否定 ! ・論理積 && ・論理和 ∥ ○インクリメント演算子 ++ ○デクリメント演算子 – ○ビット演算子 ・論理積 & ・論理和 | ・排他的論理和 ^ ・論理否定(1の補数) ~ ○シフト演算子 ・左シフト << ・右シフト >> ○代入演算子 ・代入 = ・四則演算 +=,-=,*=,/=,%= ・シフト演算 <<=,>>=,>>>= ・ビット演算 &=,|=,^= ○cast演算子 (型) 例えば、(int),(char)など ○順次演算子 , ○条件演算子(三項演算子) ? : ○instanceof演算子 instanceof |
||||||
2.インクリメント演算子、デクリメント演算子 | ||||||
int i; for( i = 0 ; i < 10 ; i = i +1 ) System.out.println(i); |
||||||
上記の例のように、今まで変数を1増やす場合「 i = i + 1 」と書いてきた。しかし「変数を1増やす」という作業は頻繁に行われるため、特別の演算子が用意されている。上の例は、その演算子を使用すると下記のようになる。 | ||||||
int i; for( i = 0 ; i < 10 ; i++ ) System.out.println(i); |
||||||
この「++」がインクリメント演算子である。逆に「変数の値を1減らす」演算子はデクリメント演算子といい「――」である。 この演算子は「i++」のように変数の後に書くことも、「++i」のように変数の前に書くこともできる。しかし「j=i++」のように式の中に組み込むと、変数の前に書いた場合と、変数の後に書いた場合の動作が異なる。変数の前に書いた場合は、式を評価する前にインクリメントする。逆に変数の後に書いた場合は、式を評価した後にインクリメントする。 |
||||||
import java.io.*;
public static void main( String args[ ] ){ |
import java.io.*;
class Test{ |
|||||
D:\java>javac Test.java D:\java>java Test a=4 b=4 D:\java> |
D:\java>javac Test.java D:\java>java Test a=4 b=4 D:\java> |
|||||
一つの式の中に同じ変数に対するインクリメント演算子が複数あってはならないということである。もちろんデクリメント演算子も同じ注意がある。 | ||||||
例えば、「b=a++ +a++」というのはダメ。予期しない結果になることが多く、バグを招き兼ねない。「printf("%d %d\n",++a,++a);」というのもダメ。 | ||||||
import java.io.*;
class Test{ |
import java.io.*;
class Test{ System.out.println("a=" + a + " b=" + b); |
|||||
D:\java>javac Test.java D:\java>java Test a=2 b=3 D:\java> |
D:\java>javac Test.java D:\java>java Test a=2 b=1 D:\java> |
|||||
3.複合代入演算子 | ||||||
複合代入演算子とは、演算と代入を一つの演算子にまとめたものである。インクリメント演算子やデクリメント演算子も複合代入演算子の一種ということができる。 | ||||||
複合代入演算子 | 使用例 | 意味 | 普通の演算子 | |||
+= | a+=b; | aにbを加えた値をaに代入する | a-a+b | |||
-= | a-=b; | aからbを引いた値をaに代入する | a=a-b | |||
*= | a*-b; | aとbを乗じた値をaに代入する | a=a*b | |||
/= | a/=b; | a÷bをaに代入する | a=a/b | |||
%= | a%=b; | a÷bの余りをaに代入する | a=a%b | |||
>>>= | a>>>=b; | aをbビット右にシフトした値をaに代入する(符号なし整数、ビット列) | a-a>>b | |||
>>= | a>>=b; | aをbビット右にシフトした値をaに代入する(符号付き整数) | a=a>>b | |||
<<= | a<<=b; | aをbビット左にシフトした値をaに代入する | a=a<<b | |||
&= | a&=b; | aとbの論理積をaに代入する | a=a&b | |||
|= | a|=b; | aとbの論理和をaに代入する | a=a|b | |||
^= | a^=b; | aとbの排他的論理和をaに代入する | a=a^b | |||
4.ビット演算子 | ||||||
演算子 | 意味 | 使用法 | ||||
& | ビット積(AND) | a=b&c; | bとcの各ビット毎の論理積をとり、その結果をaに代入する | |||
| | ビット和(OR) | a=b|c; | bとcの各ビット毎の論理和をとり、その結果をaに代入する | |||
^ | ビット排他的論理和(XOR) | a=b^c; | bとcの各ビット毎の排他的論理和をとり、その結果をaに代入する | |||
~ | ビット否定(NOT) | a=~b; | bの各ビットを反転しその結果をaに代入する | |||
AND | ||||||
b | c | 結果(a) | ||||
0 | 0 | 0 | ||||
0 | 1 | 0 | ||||
1 | 0 | 0 | ||||
1 | 1 | 1 | ||||
AND演算はビットを0でマスクするのに使用する。マスクとはあるビットを0もしくは1に変えること。例えば、奇数か偶数かを調べるためには、最下位を残して0でマスクする。その値が1なら奇数、0なら偶数ということになる。 | ||||||
import java.io.*;
class Test{public static void main(String args[ ]) { int i = readNumber( ); if ( i = & 1 == 1 ) {System.out.pringtln("奇数です"); } else{ System.out.println("偶数です"); } } // キーボードから数字を入力するメソッド public static int readNumber( ){ byte b[ ] = byte [100]; try {System.out.println.in.read(b); return Integer.parseInt((new String( (b).trim( ) ).trim( ) ); }catch(Exception e) { return 0; } } } |
||||||
OR | ||||||
b | c | 結果(a) | ||||
0 | 0 | 0 | ||||
0 | 1 | 1 | ||||
1 | 0 | 1 | ||||
1 | 1 | 1 | ||||
OR演算はビットを1でマスクするのに使用する。 | ||||||
XOR | ||||||
b | c | 結果(a) | ||||
0 | 0 | 0 | ||||
0 | 1 | 1 | ||||
1 | 0 | 1 | ||||
1 | 1 | 0 | ||||
XOR演算はビットを必要な部分だけ反転させるのに使用する。 | ||||||
NOT | ||||||
b | 結果(a) | |||||
0 | 1 | |||||
1 | 0 | |||||
NOT演算は1の補数を表す。1の補数とはすべてのビットを反転する。 | ||||||
5.シフト演算子 | ||||||
シフト演算とは論理演算と同じようにビットを操作する命令である。具体的には、2進数の各ビットをずらすことである。右にずらすか左にずらすか、符号なしか、符号付きかによって3種類の演算子が用意されている。 | ||||||
演算子 | 意味 | 使用法 | ||||
<< | 左にシフト | a = b << c | bを左にcビットずらす | |||
>> | 右にシフト | a = b >> c | bを右にcビットずらす(符号付き) | |||
>>> | 右にシフト | a = b >> c | bを右にcビットずらす(符号なし) | |||
0000 0001 = 1 1111 1111 = -1 ↓ ↓ 0000 0010 = 2 1111 1110 = -2 ↓ ↓ 0000 0100 = 4 1111 1100 = -4 整数 負数 このように左にずらすと、その値は2倍になる。 |
||||||
6.キャスト演算子 | ||||||
キャストとは「型変換」のこと。下の例のように代入元と代入先の変数の型が異なる場合、型変換を行う必要がある。一般に「byte」型から「int」型への変換など、小さい入れ物から大きな入れ物へは自動的にキャストされる。これを「暗黙の型変換」という。 | ||||||
int i = 100; byte b = 100; i = b; // 暗黙の型変換 |
||||||
逆に大きな入れ物から小さな入れ物へのキャストは、データが失われる可能性があるため、暗黙の型変換は行われずえにエラーが出てしまう。このような場合、コンパイラに「データが失われるかもしれないのは、わかっているよ」と教えなければならない。これを「明示的な型変換」という。 | ||||||
int i = 100; byte b = 100; b = i; // エラーb = (byte)i; // 明示的なキャスト |
||||||
また、整数と小数(実数)の関係も場合によっては明示的な型変換が必要になる。下の左の例では、明示的な型変換はしていない。右の例では明示的な型変換をしている。キャストしなければ割り算の結果は整数になってしまう。 | ||||||
import java.io.*;
class Test{ |
import java.io.*;
class Test{ System.out.println( f ); |
|||||
D:\java>javac Test.java D:\java>java Test D:\java> |
D:\java>javac Test.java D:\java>java Test D:\java> |
|||||
7.順次演算子 | ||||||
カンマで区切られたいくつかの式を左から順に実行する。このカンマのことを順次演算子という。 | ||||||
import java.io.*;
class Test{ public static void main(String args[ ]){ int i; int j; for ( i = 0, j = 10 ; i < 10 ; i++, j– ) System.out.println(i*j); } } |
||||||
C:\java>javac Test.java C:\java>java Test 0 9 16 21 24 25 24 21 16 9 C:\java>java |
||||||