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型を用いたほうが早くなることを知っておくとよいでしょう.
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[]という型という考え方の方がしっくり来ると思うわけです.
(だからといって自分が下の例のような書き方を常にしているわけではありませんが)
なぜ、配列を作った時に中身のオブジェクトが無いの?というと配列の中身は同じ
オブジェクトしか入れられないわけではないからです.
(この辺は「動的結合」で述べたような理由によりますが、詳しい説明は省略します)
アクセス修飾子はフィールド(変数に相当)/オペレーション(関数に相当)/クラスに対して他のクラスから参照または使用可能かを定義します.アクセス修飾子にはC++と同様にpublic/private/protectedを指定できる他、省略が可能です.
C++と殆ど同様ですが、Javaではパッケージ(後述)の概念が用いられているため、アクセス許可範囲の定義が若干異なる部分があります.
ちなみに文法的にも、C++ではpublic:ブロックとして記述したと思いますが、Javaではフィールド/オペレーション毎に指定(記述)する必要があります.この辺はちょっとだらだらしてしまいますがこれにも理由があります.(後述)
以下にそれぞれの指定を行ったオブジェクトの参照可能範囲を記します.
| アクセス修飾子なし(省略) | 同じパッケージ内のクラスから参照可能 |
| public | すべてのクラスから参照可能 |
| private | 自クラスからのみ参照可能 |
| protected | 同じパッケージ内のクラス及び、自クラスのサブクラスからのみ参照可能 |
C++にあるfriend指定子(指定クラスから参照可能にします)などは存在しません.
これもあると便利かもしれませんが、ややこしくなるしなくても問題ないということで切りすてられています.
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;
}
}
}
この機能についてはあまり使用することはないでしょうが、まともに多重ループを抜ける記述を書こうとすると(フラグなどを用いる事になる)プログラムの可読性が悪くなるために用意されたものと思われます.
ややこしくなるので知らなくても問題はありません.
C++におけるconstと似た振る舞いと言えるでしょう.
final宣言したフィールドはプログラム中で値を変更することができません.
また、final宣言されたオペレーションはそのサブクラスにおいてオーバーライドすることができません.
さらに、final宣言されたクラスはサブクラスを作成することができなくなります.
重要な処理や普遍的な処理はfinalオペレーションとして定義します.
static final変数の値は定数とみなされ、コンパイルの段階でインライン展開されます.
(Cの#defineのような使い方も一応可能です)
したがってfinal定数値を変更した場合は、そのフィールドを参照するすべてのクラスを再コンパイルする必要があります.
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 str = "ABC"; //String
str = new String("ABC");と等価
この他にもStringオブジェクトだけは演算子の特例がいくつか存在します(後述).
オブジェクトを指し示す変数.C++ではこれに対してオブジェクトそのものを示すオブジェクト型が存在します.
CやC++におけるポインタと似た概念ではありますが、ポインタのような操作はできません(アドレス演算など).
参照型のみとすることでフィールド/オペレーションにアクセスする際C++のように「::」「->」を使い分ける必要がありません.
(Javaではどのような場合も「.」を使って関係を記述します.例:Class.toString()
/ a.status等)
コンストラクタのパラメタとして自クラスのオブジェクトを受け取るものであり、オブジェクトの複製を行う場合にC++において頻繁に使用されます.Javaでもclone()の代わりに使用することがあります.(オブジェクトの複製については後述)
JavaではC++と異なりスーパークラスを持たないクラスを生成することはできません.
スーパークラスを指定(extends:後述)しない場合、実際にはそのクラスはObjectクラスのサブクラスとなります.
従って、すべてのクラスはObjectクラスのサブクラスであり、Objectクラスのオペレーションは全てのインスタンスから使用できることになります.
また、これはクラス階層がrootが一つのシンプルなTree構造になる事を表します.
Javaの演算子の振る舞いについてC/C++と異なる部分を以下に記します.
Stringオブジェクトは通常のクラスと異なり、特別扱いとして上記仕様の他に、インスタンス生成時に明示的にコンストラクタを呼び出さなくても良い事になっています.
以下の二行は等価です.
String str = new String("ABC");
String str = "AB"+"C";
文字列リテラル(""で囲まれた文字列)はそれ自体がStringオブジェクトであるため以下のような記述も許されます.
//Stringオブジェクト(実際はObjectオブジェクト)のオペレーション呼び出し
if ("12345".equals(strObject))
{
この場合はもちろんstrObject.equals("12345")でも同じですが・・
Javaでは基本的にunsigned宣言が存在しないためcharを除くすべての整数型は負の値を取ることがあります.
したがって右ビットシフト">>"を用いた場合、最上位ビットに依存してシフト結果が変わってしまうことになります.
このため、最上位ビットに0を補完する右ビットシフト演算子">>>""=>>>"が存在します.
C/C++ではa=i+++(++i);のような式の結果は実は処理系依存でした.(ただし、大概の処理系ではi=1のときa=3,i=3になるんですが)しかし、Javaでは言語仕様上に明記されており、i=1の場合、a=4,i=3になります.
Javaの言語仕様では現在の式を評価しようとしているとき、それ以前(左)の式は解決されていなければならない(らしい).したがって、上記式は中央の+を演算するタイミングで1+3の演算となります.
クラスはclass クラス名{}により宣言します.クラス内にはフィールド/オペレーション/クラス(JDK1.1仕様)を定義できます.また、プログラムはすべてクラスであり、その実行の開始は、実行しようとするクラスの「main」オペレーションからとなるため、スタンドアロンアプリケーションの場合、必ず以下のようなmainオペレーションを含むクラスを作成する必要があります.
アプレット の場合はmainオペレーションをブラウザ側に持っているためmain関数を必要としません.
public static void main(String[] args)
パラメタargsはコマンドラインパラメタの文字列オブジェクトの配列です.
コマンドライン上から起動するJavaアプリケーションをスタンドアロンと呼びます.
ブラウザ上のHTML文書内に挿入されたJavaプログラムはアプレットと呼ばれます.
クラスを宣言する際に[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の関係を表すのに適した構造です.
has-aという言葉と対になり、has-aがAという部品を持ったもの、is-aはAという部品を拡張したものと考えてください.
has-aの関係を表すにはオブジェクトをフィールドとして持つ事になります.
→オブジェクトコンポジション
abstruct宣言されたクラスは、インスタンス化することができません.
また、クラスと似た宣言ですが、interfaceとして宣言されたものもインスタンス化できません.
これらはそれぞれ抽象クラス/インターフェイスと呼ばれます.
これらの中には0個以上の実体のない(プロトタイプ宣言のみの)オペレーションが含まれています.(抽象オペレーションと呼びます)
したがって継承により、サブクラス内でそのオペレーション の実態を宣言する(実装)ことを前提としており、 その事を明示するための宣言と言えます.
abstract class Hoge {
public void operation() {
}
}
また、以下のように抽象オペレーションを定義した場合は、そのクラスは自動的に抽象クラスとなります.class Hoge {
public abstract void operation();
}
interfaceの定義は、以下のようになります.interface Hoge {
public void operation();
}
interfaceは言ってみれば究極の抽象クラスで、実装を記述できなくなっています.しかし、これは抽象クラス/インターフェイスの役割のほんの一部でしかありません.(機能としてはそれがほぼすべてですが)
この機能のおかげで真のオブジェクト指向的なプログラムが書けるようになったといってもいいほどです.
この機能については・・・ (未稿)
Javaは言語仕様でマルチスレッドに対応しています.
マルチスレッドとは、スレッド(=実行単位)を複数持てる事=平行処理です.
(注:オブジェクトとスレッドに関連性はありません)
いくつかのマルチスレッドに関連するキーワードが存在します.
synchronized → モニタ(排他制御に使用)
volatole → フィールドへのアクセス順序を入れ替えない事を保証する(最適化関連)
そして、JDK core APIにjava.lang.Threadクラスが用意されています.
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を更新できます.
Javaではオブジェクトの型の扱いが大変重要視されます.
その理由についてはinterfaceの項で説明することにしますが、まずは、オブジェクトは複数の型を持つことができます.そして、オブジェクトがどのような「型」を持っているかをinstanceof演算子によって調べることができます.
java.awt.Frame frame = new java.awt.Frame();
if (frame instanceof java.awt.Window) {
:
:
}
上記のif内の条件式はtrueになります.
(instanceofは演算子なので式の内部で使います)
オブジェクト instanceof 型名で、そのオブジェクトが「型名」で示された型を持っているときはtrueになるわけです.
クラスファイルをグループに分ける単位です.
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のクラス群の所属パッケージを見てみれば理解しやすいでしょう.
クラス名のフルネームはパッケージ名.クラス名です.
import文は、そのソースファイルで使用するクラス名にフルネームを使用するのが面倒な場合に使用するものです.
import java.awt.Frame; import java.applet.*;
一行めは、そのソースファイル上ではjava.awt.Frameクラスを"Frame"と記述することを許すという意味です.
二行めはjava.applet.Appletクラスやその他のjava.appletパッケージに属す全クラスを、パッケージ部分を省略したクラス名で記述することを許すものです.
(そのパッケージだけであり、さらに下位層のパッケージ内のクラスには適用されません)
迂闊に("*"指定で)使うと、異なるパッケージの同じクラス名を持つものがあったときに結局名前の競合が発生してしまい、パッケージの意味がなくなってしまいます.
import文における*指定はできるだけ使わないようにしましょう.
transientを指定して生成したオブジェクト(への参照)は、そのクラスの永続ストーレジ化時の対象外となります.永続ストーレジとはインスタンス化された状態のオブジェクトがその状態を保持しつづけることが可能なシステムのことであり、不揮発性の記憶装置(ハードディスク等)に保存したりネットワークへの送信を可能にします.Bean等はクラスとしてだけでなくオブジェクトとして保存できる必要があります.
(それらを貼り付けたオブジェクト(コンテナ)を保存する手段が用意されています)
基本的に永続ストーレジとしてオブジェクトを保存可能とするためにはそのクラスにSerializableインターフェイス(名前のみのインターフェイス)をインプリメントしておく必要がありますが、そのフィールドに非シリアライズなクラス(Graphics/Thread等)が存在した場合にtransientを指定してそのフィールドが保存不要であることを宣言しなければ実行時エラーになってしまいます.
class BeanA implements Serealizable { //オブジェクトの保存可能
transient Graphics gbuf; //オブジェクトとして保存する必要がないフィールド
public BeanA() {}
public bufferAlloc(Component o) {
gbuf = o.getGraphics();
:
:
以下に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 | インスタンスを生成します. |
ポリモーフィズムの説明にもってこいのプログラムだと思います.
説明はソースコード中に記してあります.
解らない個所及び間違いがありましたら言ってください.