Home: Up

Java言語について


2.Javaの言語仕様(他言語との違い)

2−1.C++との文法/機能的な違い

2−1−1.基本データ型(Primitive Type)

Javaにおける基本データ型はC/C++のような処理系に依存する部分を一切排除しています.各データ型とその使用Bit数は以下のとおりです.

Bit数 概要
boolean 1 識別子true/falseしか代入できない
byte 8 -128⇔127
char 16 \u0⇔\uffff(unsigned Unicode文字)
short 16 -32768⇔32767
int 32 32Bit整数
long 64 64Bit整数
float 32 32Bit浮動小数点数
double 64 64Bit浮動小数点数

booleanに関してはCと異なり、TRUE=1、FALSE=0のような定数が定義されているわけではなくあくまでもtrueはtrueでしかありません.

関連→if文

Javaではunsigned宣言は存在しません.唯一のunsigned型はchar型ですが、これはあくまでも文字を表す型であるためJavaプログラマは数値演算にはsigned型を使用することが義務づけられています.

ちなみにJavaでも内部演算はint型を基準に行われているため、高速化のためにはbyteやshortよりもint型を用いたほうが早くなることを知っておくとよいでしょう.


2−1−2.配列

Javaにおける配列はオブジェクトです.
オブジェクトである為newによって生成しなければなりません.
プリミティブ型の配列は以下のように定義します.

int         i[] = new int[10];
int[]       j   = new int[10];
↑どちらの表記でもかまいません.

下の書き方の方がオブジェクト指向的(本来の書き方)であるというのはわかるでしょうか?
上の書き方は、非オブジェクト指向言語に慣れた方にとって直感的という事で用意された表記法なのかなと思っています.
(Cではint i[10];でした)
なぜかというと、下の抽象データ型配列の定義を見てみてください.

Integer     i[] = new Integer[10];
Integer[]   j   = new Integer[10];
↑どちらの表記でもかまいません.

Integerとはint型の値を管理するクラスです(ラッパクラスと呼ばれますがここでは意識しないようにします).
しかし、上の宣言ではIntegerクラスのインスタンス(オブジェクト)が生成されるわけではなく、 Integerクラスのインスタンスを10個まで格納できる入れ物オブジェクトを生成したに過ぎません.
実際に10個のIntegerオブジェクトを格納するには以下のようにします.

for (int k = 0; k < 10; k++) {
    i[k]    = new Integer(0);
    j[k]    = new Integer(0);
}

これで合計20個のIntegerオブジェクトをそれぞれの配列が参照している状態になります.
つまり、上の配列の定義ではIntegerオブジェクトを作っているわけではなく、 Integerオブジェクトの入れ物を作っているという感覚からは、 Integer[]という型という考え方の方がしっくり来ると思うわけです.
(だからといって自分が下の例のような書き方を常にしているわけではありませんが)

なぜ、配列を作った時に中身のオブジェクトが無いの?というと配列の中身は同じ オブジェクトしか入れられないわけではないからです.
(この辺は「動的結合」で述べたような理由によりますが、詳しい説明は省略します)

2−1−3.アクセス修飾子の振る舞い

アクセス修飾子はフィールド(変数に相当)/オペレーション(関数に相当)/クラスに対して他のクラスから参照または使用可能かを定義します.アクセス修飾子にはC++と同様にpublic/private/protectedを指定できる他、省略が可能です.

C++と殆ど同様ですが、Javaではパッケージ(後述)の概念が用いられているため、アクセス許可範囲の定義が若干異なる部分があります.
ちなみに文法的にも、C++ではpublic:ブロックとして記述したと思いますが、Javaではフィールドオペレーション毎に指定(記述)する必要があります.この辺はちょっとだらだらしてしまいますがこれにも理由があります.(後述)

以下にそれぞれの指定を行ったオブジェクトの参照可能範囲を記します.

アクセス修飾子なし(省略) 同じパッケージ内のクラスから参照可能
public すべてのクラスから参照可能
private 自クラスからのみ参照可能
protected 同じパッケージ内のクラス及び、自クラスのサブクラスからのみ参照可能

C++にあるfriend指定子(指定クラスから参照可能にします)などは存在しません.
これもあると便利かもしれませんが、ややこしくなるしなくても問題ないということで切りすてられています.


2−1−4.break用ラベル

breakまたはcontinueでのジャンプ先にラベルを指定することができます.ラベルはループ先頭にしか記述できません.
これの利用方法は多重ループから抜けるようなときに限定するべきです.

最初にcheckで引っかかった場所(i,j)を利用したい場合

int i, j;

LABEL:
for (i = 10; i > 0; i--) {
    for (j = 10; j > 0; j--) {
        if (check(i, j)) {
            break LABEL;
        }
    }
}

この機能についてはあまり使用することはないでしょうが、まともに多重ループを抜ける記述を書こうとすると(フラグなどを用いる事になる)プログラムの可読性が悪くなるために用意されたものと思われます.

ややこしくなるので知らなくても問題はありません.


2−1−5.final宣言

C++におけるconstと似た振る舞いと言えるでしょう.
final宣言したフィールドはプログラム中で値を変更することができません.
また、final宣言されたオペレーションはそのサブクラスにおいてオーバーライドすることができません.
さらに、final宣言されたクラスサブクラスを作成することができなくなります.

重要な処理や普遍的な処理はfinalオペレーションとして定義します.

static final変数の値は定数とみなされ、コンパイルの段階でインライン展開されます.
(Cの#defineのような使い方も一応可能です)

したがってfinal定数値を変更した場合は、そのフィールドを参照するすべてのクラスを再コンパイルする必要があります.


2−1−6.new演算子

C++におけるnewと同様に動的にインスタンスを生成する演算子で あり、コンストラクタを呼び出します.
Javaではnew演算子を用いないでインスタンス を得る手段は基本的にありません.
したがって、クラス名を型とした変数はすべて参照型であり、C++におけるオブジェクト変数の代入によるコピー(代入演算子のオーバーロード+コピーコンストラクタを用いる)などは使用できません.

オブジェクトの複製には変わりにclone()オペレーション(Objectクラス)が用意されています.
→シャローコピー/ディープコピー

C++ではnew演算子で生成した動的なインスタンスに関しては プログラマが責任を持って管理し、確実にdeleteする必要がありましたが、 JavaではVMのガベージコレクタがインスタンスを完全に管理しているため、 それを参照している個所がなくなれば自動的に解放が行われます.

ガベージコレクタがガベージコレクション(メモリ整理)を行って インスタンスの資源を解放する際に、 finalize()オペレーション(Objectクラス)を呼び出すことになっています. したがって、finalize()をオーバーライドしておけばインスタンスが 解放されるときの後処理を行うことが可能ですが、C++におけるデストラクタ とは呼び出しタイミングが異なる上、場合によっては呼び出されない事がある為あまり実用的ではありません.

なぜなら上記の「参照している個所がなくなれば」と表記している部分はじつは解放を行うわけではなく解放可能状態になるだけだからです.実際の解放(=finalize呼び出し)はその領域を他のインスタンスが利用しようとしたときに行われます.
(メモリ整理=GCを明示的に行う事も一応できます)
しかも、System#exit()及びユーザーによるVMの強制終了時にはGCもくそもなく、いきなりVMが終わっていしまう為、finalizeは呼ばれないままになる事があるわけです.

Stringオブジェクトについてのみの特例として、以下のようにnew演算子なしでインスタンスを生成することが許されています.

例:String str = "ABC"; //String str = new String("ABC");と等価
この他にもStringオブジェクトだけは演算子の特例がいくつか存在します(後述).

参照型

オブジェクトを指し示す変数.C++ではこれに対してオブジェクトそのものを示すオブジェクト型が存在します.
CやC++におけるポインタと似た概念ではありますが、ポインタのような操作はできません(アドレス演算など).

参照型のみとすることでフィールドオペレーションにアクセスする際C++のように「::」「->」を使い分ける必要がありません.
(Javaではどのような場合も「.」を使って関係を記述します.例:Class.toString() / a.status等)

コピーコンストラクタ

コンストラクタのパラメタとして自クラスのオブジェクトを受け取るものであり、オブジェクトの複製を行う場合にC++において頻繁に使用されます.Javaでもclone()の代わりに使用することがあります.(オブジェクトの複製については後述)

Objectクラスについて

JavaではC++と異なりスーパークラスを持たないクラスを生成することはできません.
スーパークラスを指定(extends:後述)しない場合、実際にはそのクラスはObjectクラスのサブクラスとなります.
従って、すべてのクラスはObjectクラスのサブクラスであり、Objectクラスのオペレーションは全てのインスタンスから使用できることになります.
また、これはクラス階層がrootが一つのシンプルなTree構造になる事を表します.


2−2.C++との振る舞い/設計方法の違い

2−2−1.演算子の違いについて

Javaの演算子の振る舞いについてC/C++と異なる部分を以下に記します.

「+」演算子による文字列(Stringオブジェクト)の結合をサポート.

Stringオブジェクトは通常のクラスと異なり、特別扱いとして上記仕様の他に、インスタンス生成時に明示的にコンストラクタを呼び出さなくても良い事になっています.
以下の二行は等価です.

String str = new String("ABC");
String str = "AB"+"C";
文字列リテラル

文字列リテラル(""で囲まれた文字列)はそれ自体がStringオブジェクトであるため以下のような記述も許されます.

//Stringオブジェクト(実際はObjectオブジェクト)のオペレーション呼び出し
if ("12345".equals(strObject)) {

この場合はもちろんstrObject.equals("12345")でも同じですが・・

unsigned整数の削減による影響

Javaでは基本的にunsigned宣言が存在しないためcharを除くすべての整数型は負の値を取ることがあります.
したがって右ビットシフト">>"を用いた場合、最上位ビットに依存してシフト結果が変わってしまうことになります.
このため、最上位ビットに0を補完する右ビットシフト演算子">>>""=>>>"が存在します.

>>>演算子は右シフトを行って最上位ビットを0で埋めるのですが、この演算子はオペコード/オペランド共にintであることが前提になっています.
従って、byteの値に対して>>>で右シフトをしようとしても、以下のような手順により、最上位ビットが1になってしまうことになります.
byte b = (byte)0x80;
b = b >>> 2;
  1. 左右の項がint型に(暗黙に)変換.(0xffffff80 >>> 0x00000002)
  2. 最上位を0で埋める右シフト実行.(0x3fffffe0)
  3. 結果の値をbに代入.(上記例では(byte)とキャストをかけないとコンパイルエラーになります).
  4. byte型にダウンキャストされた結果は0xe0となり、想定していたものと違う.
これはどうすれば回避出来るのでしょう.
結局、>>>はintの時しか有効でないわけですから、byteやshort等に対して>>>相当の動作を期待する場合は、以下のように書かないといけません.
b = (byte)((b & 0xff) >> 2);
0xffでのマスクにより、intでの正数に変換しているので>>を使おうが>>>を使おうが同じということになります.
ちょっと辛いですねえ...
まあこのような情報を保持する場合はint型を用いるのが正道であると考えた方がよいかもしれませんね.
単項演算子の解決順序

C/C++ではa=i+++(++i);のような式の結果は実は処理系依存でした.(ただし、大概の処理系ではi=1のときa=3,i=3になるんですが)しかし、Javaでは言語仕様上に明記されており、i=1の場合、a=4,i=3になります.

Javaの言語仕様では現在の式を評価しようとしているとき、それ以前(左)の式は解決されていなければならない(らしい).したがって、上記式は中央の+を演算するタイミングで1+3の演算となります.


2−2−2.クラスの宣言

クラスはclass クラス名{}により宣言します.クラス内にはフィールドオペレーション/クラス(JDK1.1仕様)を定義できます.また、プログラムはすべてクラスであり、その実行の開始は、実行しようとするクラスの「main」オペレーションからとなるため、スタンドアロンアプリケーションの場合、必ず以下のようなmainオペレーションを含むクラスを作成する必要があります.

アプレット の場合はmainオペレーションをブラウザ側に持っているためmain関数を必要としません.

public static void main(String[] args)

パラメタargsはコマンドラインパラメタの文字列オブジェクトの配列です.

スタンドアロンアプリケーション/アプレット

コマンドライン上から起動するJavaアプリケーションをスタンドアロンと呼びます.
ブラウザ上のHTML文書内に挿入されたJavaプログラムはアプレットと呼ばれます.


2−2−3.継承

クラスを宣言する際に[extends クラス名]を記述することでそのクラスの機能を継承します.
以下のクラスA,Bはまったく同じ機能を有します.

class A {
    private int     a;
    public A() {
        setA(1);
    }
    public int getA() {
        return(a);
    }
    public void setA(int a) {
        this.a = a;
    }
}
class B extends A {
}

クラスCはAの機能に内部データaをインクリメントする機能を追加します.

class C extends A {
    public void incA() {
        setA(getA() + 1);
    }
}

上記の場合フィールドaはprivateでありCからアクセスできないのでこのようなコーディングになります.

(aがprotectedであればincA(){a++;}と書けます)

継承をうまく利用するには親になるクラス(スーパークラス)の機能(性質)を把握しておく必要があります.

継承は一般にis-aの関係を表すのに適した構造です.

is-a

has-aという言葉と対になり、has-aがAという部品を持ったもの、is-aはAという部品を拡張したものと考えてください.
has-aの関係を表すにはオブジェクトをフィールドとして持つ事になります.
→オブジェクトコンポジション


2−2−4.抽象クラス/インターフェイス

abstruct宣言されたクラスは、インスタンス化することができません.
また、クラスと似た宣言ですが、interfaceとして宣言されたものもインスタンス化できません.
これらはそれぞれ抽象クラス/インターフェイスと呼ばれます.
これらの中には0個以上の実体のない(プロトタイプ宣言のみの)オペレーションが含まれています.(抽象オペレーションと呼びます)
したがって継承により、サブクラス内でそのオペレーション の実態を宣言する(実装)ことを前提としており、 その事を明示するための宣言と言えます.

具体的には以下のようなクラスを作成すると、そのクラス自身のオブジェクトを作る事はできなくなります.
abstract class Hoge {
  public void operation() {
  }
}
また、以下のように抽象オペレーションを定義した場合は、そのクラスは自動的に抽象クラスとなります.
(オペレーションの実体が無い為インスタンス化できないわけです)
class Hoge {
    public abstract void operation();
}
interfaceの定義は、以下のようになります.
interface Hoge {
    public void operation();
}
interfaceは言ってみれば究極の抽象クラスで、実装を記述できなくなっています.
(抽象クラスは実装のあるオペレーションを含める事ができます.) つまり、全ての定義が抽象オペレーションである事が明らかなのでabstract修飾子を記述する必要はありません.

しかし、これは抽象クラス/インターフェイスの役割のほんの一部でしかありません.(機能としてはそれがほぼすべてですが)
この機能のおかげで真のオブジェクト指向的なプログラムが書けるようになったといってもいいほどです.
この機能については・・・ (未稿)


2−2−5.マルチスレッド

Javaは言語仕様でマルチスレッドに対応しています.
マルチスレッドとは、スレッド(=実行単位)を複数持てる事=平行処理です.
(注:オブジェクトとスレッドに関連性はありません)
いくつかのマルチスレッドに関連するキーワードが存在します.
synchronized → モニタ(排他制御に使用)
volatole → フィールドへのアクセス順序を入れ替えない事を保証する(最適化関連)

そして、JDK core APIにjava.lang.Threadクラスが用意されています.


2−2−6.オブジェクトのロックと排他制御

2−2−5.で簡単に触れましたが、マルチスレッドで動作するアプリケーションの場合、 複数のスレッドが同じデータを同時に書き換えようとしたりする事がままあります.
その時の動作を超スローモーションで説明すれば、スレッドAがデータZに1加えようとして Zを読み出します.スレッドBはデータZから1減らそうとしてZを読み出します.
そして、スレッドAはZに1加えて格納します.スレッドBはZから1減らして格納します.
この時データZはどうなるでしょう.1加えて1減らすのですから元の値になっているはずですが、 そうならない場合もあると言うのが解るでしょうか?
スレッドAがZを読み込み→更新→書き込みする間に他のスレッドがZに対して何かしても無効になってしまいます.
逆にスレッドAが書き込んだ後にスレッドAが更新する前の値に対して他のスレッドが何かした場合、 スレッドA側の更新が無効になってしまう事もあります.(なんちゅう下手な説明や!)
そこで、共有データZに対する読み込み→更新→書き込みの間は他のスレッドがZを触れないようにします.
これがオブジェクトのロックです.

ロックの例

synchronized (Z) {      ←実際はプリミティブ型には使えません
        Z++;
}

これで安全にZを更新できます.


2−2−7.ネイティブオペレーション


2−2−8.例外の送出


2−2−9.instanceof演算子

Javaではオブジェクトの型の扱いが大変重要視されます.
その理由についてはinterfaceの項で説明することにしますが、まずは、オブジェクトは複数の型を持つことができます.そして、オブジェクトがどのような「型」を持っているかをinstanceof演算子によって調べることができます.

java.awt.Frame frame = new java.awt.Frame();
if (frame instanceof java.awt.Window) {
        :
        :
}

上記のif内の条件式はtrueになります.
(instanceofは演算子なので式の内部で使います)
オブジェクト instanceof 型名で、そのオブジェクトが「型名」で示された型を持っているときはtrueになるわけです.

classやinterfaceは継承が出来ますが、この「継承」には「インターフェイスの継承」と「実装の継承」(classのみ)に加えて、「型の継承」があります.
また、interfaceをクラスに「実装」(implements)した場合も「インターフェイスの継承」「型の継承」が行われます.
クラスには特定のclassからの継承と、複数のinterfaceの実装が行えるので、そのクラスは継承/実装された全てのclass/interfaceの持つ型を継承していることになります.

2−2−10.例外への対処


2−2−11.パッケージ

クラスファイルをグループに分ける単位です.
package文によりクラスを特定のパッケージに属するようにできます.
package文はソースファイル先頭に以下のように記述します.

package jp.co.idea_sw.shin;

これで、そのソースファイル内の全てのクラスがjp.co.idea_sw.shinパッケージに所属することになります.
(このファイルにTestクラスが定義されていたら、そのクラスのフルネームはjp.co.idea_sw.shin.Testとなります)
パッケージの構造はツリー構造となっており、そのノード毎にクラスを含ませる事ができます.
ディレクトリ構造に似てますね.ってそのとおりで、コンパイルされたクラスファイルは コンパイル時に指定したディレクトリをルートとして、パッケージのノード名と対応する ディレクトリ階層の下に出来上がります.
例えばjava.awt.Frameクラスは特定のディレクトリから見てjava/awtディレクトリ内に配置されています.
(Windowsならjava\awtの中ね)

上記のパッケージ名は私のドメイン名(idea-sw.co.jp)を元に作られていますが、みんながこのようにしてパッケージ名を決めていれば、クラス名を完全にユニークにすることができます.
(自分の作ったクラスの名前が他人の作ったクラス名と重複することがない)
また、パッケージはフィールドやオペレーションのデフォルトのアクセス権も意味します.
(privateの指定がない限り同じパッケージの別のクラスからフィールドにアクセスしたりオペレーションを呼び出したりできる)
同じパッケージ内には役割の近いクラスを集めるようにします.
JDKのクラス群の所属パッケージを見てみれば理解しやすいでしょう.


2−2−12.import

クラス名のフルネームはパッケージ名.クラス名です.
import文は、そのソースファイルで使用するクラス名にフルネームを使用するのが面倒な場合に使用するものです.

import java.awt.Frame;
import java.applet.*;

一行めは、そのソースファイル上ではjava.awt.Frameクラスを"Frame"と記述することを許すという意味です.
二行めはjava.applet.Appletクラスやその他のjava.appletパッケージに属す全クラスを、パッケージ部分を省略したクラス名で記述することを許すものです.
(そのパッケージだけであり、さらに下位層のパッケージ内のクラスには適用されません)

迂闊に("*"指定で)使うと、異なるパッケージの同じクラス名を持つものがあったときに結局名前の競合が発生してしまい、パッケージの意味がなくなってしまいます.
import文における*指定はできるだけ使わないようにしましょう.


2−2−13.インターフェイスと多重継承


2−2−14.transient修飾子

transientを指定して生成したオブジェクト(への参照)は、そのクラスの永続ストーレジ化時の対象外となります.永続ストーレジとはインスタンス化された状態のオブジェクトがその状態を保持しつづけることが可能なシステムのことであり、不揮発性の記憶装置(ハードディスク等)に保存したりネットワークへの送信を可能にします.Bean等はクラスとしてだけでなくオブジェクトとして保存できる必要があります.

(それらを貼り付けたオブジェクト(コンテナ)を保存する手段が用意されています)

基本的に永続ストーレジとしてオブジェクトを保存可能とするためにはそのクラスにSerializableインターフェイス(名前のみのインターフェイス)をインプリメントしておく必要がありますが、そのフィールドに非シリアライズなクラス(Graphics/Thread等)が存在した場合にtransientを指定してそのフィールドが保存不要であることを宣言しなければ実行時エラーになってしまいます.

class BeanA implements Serealizable {        //オブジェクトの保存可能
    transient Graphics gbuf;                   //オブジェクトとして保存する必要がないフィールド
    public BeanA() {}
    public bufferAlloc(Component o) {
        gbuf = o.getGraphics();
            :
            :

2−3.Javaの全予約語

以下にJava言語で使用される全予約語と、対応(動作が類似)するものがあればそのC++における予約語を列挙します.

グループ Java C++ 意味
アクセス修飾子 public public アクセス制限なし
private private 自分のインスタンス内からのみアクセス可能
protected protected 自分のパッケージ内のクラスと自分のサブクラス内からアクセス可能
モディファイア abstract virtual 抽象クラス/抽象オペレーションの宣言、このクラスのインスタンスは作成できません.
オペレーションに対して指定した場合はC++で言う純粋仮想関数の意味ですので、virtualとは意味が異なります.
(無指定がC++でいうvirtual関数となります)
static static クラスフィールド/クラスオペレーションを宣言します.
final const 定数.継承、オーバーライド及び継承を拒否します.
transient オブジェクトを永続ストーレジ化対象から外します.
volatile volatile 変数へのアクセスがコードに示した順になる事を保証します.最適化を抑制します.
synchronized その範囲内実行中指定オブジェクトをロックします
メソッドへのモディファイアとしてとステートメントとしての構文があります.
native ネイティブオペレーション(プラットフォーム依存).
実装は外部ライブラリ(WindowsではDLL)で与えられます.
ステートメント switch switch
case case
default default
if if
do do
break break Javaでは一度に多重ループを抜けることもできます.
else else
return return
while while whileループの開始、doループの終了
for for
continue continue
goto goto Javaでは未使用(予約語として存在するのみ)
throw throw 例外を送出します.
try try 例外をチェックします.
catch catch 送出(throw)された例外の処理を行います.
finally try/catchの後に必ず実行される部分です.
宣言/定義 boolean 2値を採る(1Bit)領域の宣言
byte 1BYTE整数の宣言
void 無形の宣言
char 2BYTE整数の宣言
short 2BYTE整数の宣言
int 4BYTE整数の宣言
long 8BYTE整数の宣言
float 4BYTE(単精度)浮動小数点型の宣言
double 8BYTE(倍精度)浮動小数点型の宣言
import 他のパッケージのクラスを省略名で使用可能にします.
package パッケージの宣言.そのクラスを指定パッケージに含めます.
class class クラス型の宣言
interface インターフェイスの宣言
extends クラス宣言に付加し、他の一つのクラスを継承します.
implements 複数のインターフェイス実装を行います.
throws そのクラスから例外を投げることを宣言します.
const const Javaでは未使用(予約語として存在するのみ)
組み込み変数 super カレントオブジェクトの直接のスーパークラスのオブジェクトを表します.
this this カレントオブジェクトのインスタンスを表します(自分自身).
その他(演算子など) instanceof A instanceof BでオブジェクトAが「型」Bのオブジェクトであるかチェックします.
new new インスタンスを生成します.

3.Javaのプログラム例

3−1.オブジェクト指向的なプログラム

3−1−1.プログラム例1:commandパターン

ポリモーフィズムの説明にもってこいのプログラムだと思います.
説明はソースコード中に記してあります.
解らない個所及び間違いがありましたら言ってください.

ソースコード[CommandTest.java]