はじめに..
全くの未経験の友達にプログラミング(Java)を教えることになり、復習ついでにJavaGoldを受講することにした。
本稿では、紫本を参考にまとめている。
まずは、以下の章についてまとめる。
- 第1章 : Javaクラスの設計
第1章まとめ
switch文
多分岐処理を行う文として、switch文がある。
式を評価した結果とcaseで指定した定数を比較し、一致した場合にそのcase以降に記述した処理文を実行する。
一致しない場合は、次のcase文と比較する
下記コードはBが表示される
int num = 2;
switch(num){
case 1:
System.out.println("A");
break;
case 2:
System.out.println("B");
break;
case 3:
System.out.println("C");
}
switch文の式の結果はデータ型として、byte, char, short, int, enum, Stringのいずれかであるか、基本データ型のラッパークラスである Charcter, Byte, Short, Integerのオブジェクトである必要がある。
拡張されたリテラル
整数リテラルとして、10進数、8進数、16進数の表現のほか、Java SE7からは2進数の表現が可能になった。
先頭に0bを入れると2進数として判断されるようになった。
下記コードは各新数で『26』を表したコード
int a = 26; //10進数
int b = 032; //8進数
int c = 0x1a; //16進数
int d = 0b11010; //2進数
_がある整数リテラル
Java SE7から整数リテラルを扱う際に数字の途中で「_」を使用できるようになった。
桁数の大きい整数リテラルの可読性を高めるもので「100,000」を「100_00」のように書ける。
以下の注意点がある。
- リテラルの先頭、末尾に使用できない
- 浮動小数点リテラルにある小数点の前後には使用できない
- float値を表すF(or f)及びlong値を表すL(or l)の前には使用できない
- 16進数で使用する0xと2進数で使用する0bの途中及び前後には使用できない
上記の通り「_」はリテラルの先頭及び末尾、記号の前後には使用できない
アクセス修飾子とカプセル化
Java言語では、クラス・メソッド・コンストラクタ・メンバ変数に対して他のクラスからアクセスを許可させるかさせないかなどをアクセス修飾子を使って指定する。
アクセス修飾子 | 説明 | 適用場所 |
---|---|---|
public | どのクラスからでもアクセス可能 | クラス / コンストラクタ / メンバ変数 / メソッド |
protected | このクラスを継承したサブクラス、もしくは同一パッケージ内のクラスから利用可能 | コンストラクタ / メンバ変数 / メソッド |
デフォルト(指定なし) | 同一パッケージ内のクラスからのみ利用可能 | クラス / コンストラクタ / メンバ変数 / メソッド |
private | 同一クラスからのみ利用可能 | コンストラクタ / メンバ変数 / メソッド |
メンバ変数に対してアクセス修飾子は使用できるが、ローカル変数には使用できない。
カプセル化
属性と操作を一体化させて表現することをカプセル化という。
カプセル化されたクラスは、属性であるインスタンス変数が他クラスからむやみに変更されることを防ぐ(データを隠蔽する)ために、一般的にインスタンス変数はprivate指定にし、操作するメソッドはpublic指定する。
JavaBeansとイミュータブルオブジェクト
上記カプセル化で記述したメンバ操作するメソッドをメンバ変数のgetterメソッド / setterメソッドと呼ぶ。
便宜上、これらのメソッド名の先頭にget及びsetをつける。
JavaBeansと呼ばれる特別なJavaクラスの場合は、このメソッド命名規則が標準として適用される。
その他規則は以下の通り
- メンバ変数はprivateとし、外部からはgetterメソッドとsetterメソッドを通じてのみアクセス可能とする
- getter及びsetterメソッドは、それぞれget、setで始まりメンバ変数名が続く。 なお、メンバ変数名の先頭は大文字にする(例 : getName())。 このJavaBeansを使用するすべてのクラスが呼び出せるようにpublicとする
- getterメソッドの戻り値の型は、対応するメンバ変数の型に一致し、引数は持たない
- setterメソッドの戻り値の方はvoidで、対応するメンバ変数の型を表す引数を持つ
- booleanの型のメンバ変数に対するgetterはgetXXX()の他、isXXX()でも良い
また、以下のようにクラス定義をすると、インスタンス化時に指定された値を保持し、その後、属性を変更させない**イミュータブルオブジェクト(不変オブジェクト)**を作成できる。
- クラスが拡張できないことを保証するためfinalクラスとする。またはgetterメソッドにfinalを付与しオーバーライドさせない
- メンバ変数はprivate final修飾にする
- オブジェクトの状態を変更するようなメソッドは定義しない
- メンバ変数に参照型の変更可能なオブジェクトを持つ場合、変更されないように配慮する
public final class Basket {
private final String name;
private final List<Fruit> fruits;
public Basket(String name, List<Fruit> fruits){
this.name = name;
List<fruit> fruitsList = new ArrayList<Fruit>();
fruitsList.addAll(fruits);
this.fruits = fruitsList;
}
public String getName(){
return this.name;
}
public List<Fruit> getFruits(){
List<fruit> fruitsList = new ArrayList<Fruit>();
fruitsList.addAll(fruits);
return fruitsList;
}
final修飾子とstatic修飾子
final修飾子はクラス、メソッド、変数に適用できる。
それぞれ適用した時の振る舞いは以下の通り
適用箇所 | 意味 |
---|---|
クラス | final指定されたクラスをもとに、サブクラスは作れない |
メソッド | final指定されたメソッドを、サブクラス側でオーバーライドできない |
変数 | final指定された変数は、定数となる |
final修飾子の構文は以下の通り
-
クラスに適用
┗ [アクセス修飾子] final class クラス名{ } -
メソッドに適用
┗ [アクセス修飾子] final 戻り値の型 メソッド名(引数リスト) { } -
変数に適用
┗ [アクセス修飾子] final データ型 変数名 = 初期値;
final指定されたメンバ変数は 宣言時に初期化するかコンストラクタやイニシャライザで初期化する必要がある。
final修飾子はローカル変数にも適用可能
static修飾子
static修飾子はメソッド、変数に指定することができる。
static変数はクラス変数、staticメソッドはクラスメソッドとも呼ばれていて、クラスに対して静的に存在するメンバを意味する。
class Sample1 {
int instanceVal = 100;
static int staticVal = 200;
void methodA() {
System.out.println("methodA(): " + instanceVal);
}
static void methodB() {
System.out.println("methodB(): " + staticVal);
}
}
instanceValはインスタンス変数、staticValはstatic変数。
また、methodA()は非static(インスタンス)メソッド、methodB()はstaticメソッド。
定義の違いはstatic修飾子の有無のみ。
また、メモリの確保場所も異なる。
static修飾子のないインスタンスメンバは、クラスのインスタンスごと領域が確保される。
例えば、1つのクラスから3つのインスタンスを生成すれば「3 x インスタンスメンバの数」だけ領域が確保される。確保されるタイミングは、インスタンス化を行ったとき。
一方、staticメンバはクラス単位で領域が確保される。
staticメンバが確保される領域は、複数のインスタンスが生成されても、1つのみ。
領域が確保されるのは、クラスをメモリ上に読み込んだとき。
staticメンバの呼び出し
staticメンバはインスタンス化しても領域は1箇所しか用意されないため、インスタンス化しなくても呼び出せるようになっている。
呼び出し方は以下の通り
「クラス名.static変数名」「クラス名.staticメソッド名()」
インスタンス化してからも呼び出せる。
「参照変数名.staticメンバ名」「参照変数名.staticメソッド名()」
なお、非staticメンバはインスタンス化しないと呼び出せない
クラス名.インスタンスメンバ名といった呼び出し方はできない
インスタンスメンバとstaticメンバのクラス内でのアクセス
クラス内に定義したメンバ間のアクセスは次のようなルールがある。
- クラス内で定義したインスタンスメンバは、クラス内で定義したstaticメンバに直接アクセスできる
- クラス内で定義したstaticメンバは、クラス内で定義したインスタンスメンバに直接アクセスできない。 アクセスする場合は、インスタンス化してからアクセスする
package Sample1;
public class Sample1_5 {
int instanceVal;
static int staticVal;
int methodA() {
// インスタンスメソッド→インスタンス変数なので問題ない
return instanceVal;
}
int methodB() {
// インスタンスメソッド→static変数なので問題ない
return staticVal;
}
// static int methodC() {
// staticメソッド→インスタンス変数なのでNG
// return instanceVal;
// }
static int methodD() {
// staticメソッド→static変数なので問題ない
return staticVal;
}
static int methodE() {
// staticメソッド内で、一旦インスタンス化してからインスタンス変数にアクセスしてるので問題ない
Sample1_5 obj = new Sample1_5();
return obj.instanceVal;
}
}
イニシャライザブロック
static変数、staticメソッドに加え、クラスはstatic イニシャライザと呼ぶブロックを定義できる。
staticイニシャライザはクラスファイルがロードされたタイミングで1度だけ実行されるブロック。
クラスをインスタンス化する前や、main()メソッドを呼び出す前に実行したい処理に使用する
初期値の設定や、API接続をここで行うイメージ
static { }
また、インスタンス変数を初期化するには、多くのコンストラクタを用いるが、イニシャライザを使用することもできる。
{ }
デザインパターン
設計の原則
オブジェクト指向プログラミング的に好ましいのは、適切なクラス分割をし、かつ各クラスの依存度が低い設計。
ソフトウェア品質の観点から、いい設計の定義はそれぞれだが、以下4点のみ掲載
- 正確性 :仕様を正しく満たしていること
- 統一性 :設計上の個々の概念が統一されていること
- 可読性 :設計の成果物が読みやすく理解しやすいこと
- 変更容易性:修正が容易であること
最低限上記4つを意識すればなんとかなると思っている...(今までの現場は酷かった..)
デザインパターンとは
オブジェクト指向言語でよく使われる設計をパターン化したもの
もっと噛み砕くと 「設計における定番をまとめたもの」
シングルトンパターン
紫本ではシングルトンパターンを解説する。
通常クラスは、newキーワードを使用することで多くのオブジェクトを生成することができるが、あるクラスをもとに作られるオブジェクトは1つだけに限定したいというケースがある。
その際に、シングルトンパターンを適用する。
package Sample1;
public class MySingleton {
private static final MySingleton instance = new MySingleton();
private MySingleton() { }
public static MySingleton getInstance() {
return instance;
}
}
ポイントは以下
ポイント | 説明 |
---|---|
instance変数 | private指定し、static finalとして宣言。これによりクラスがロードされたときに一度だけインスタンス化され、変数に格納される |
MySingleton()コンストラクタ | コンストラクタ定義していないクラスは、コンパイル時にデフォルトコンストラクタが追加され外部からインスタンス化できてしまう。それを防ぐために明示的にprivateコンストラクタを用意する |
getInstance()メソッド | ロード時に初期化されたinstance変数に格納されているMySingletonオブジェクトを返す役割 |
利用する際はgetInstance()を呼ぶ
package Sample1;
public class Sample1_6 {
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
MySingleton obj1 = MySingleton.getInstance();
MySingleton obj2 = MySingleton.getInstance();
if (obj1 == obj2) {
System.out.println("同じもの");
}else {
System.out.println("違うもの");
}
}
}
上記実行すると「同じもの」と出力される。
列挙型
列挙型とは
列挙型とは、特定の値のみを持つ型でプログラマが任意に定義できる。
定義するにはenumキーワードを使用する
[修飾子] enum 列挙型名 { 値1, 値2, 値3...値n }
列挙型は、クラス定義の中、あるいは列挙型の定義だけを記述したソースファイルで定義できる。
メソッドの中では定義できない。
enum Card1 { SPADES, CLUBS, DIAMONDS, HEARTS }
enumクラス
上記のソースを保存しコンパイルをするとCard.classファイルが生成される。
final class Card1 extends java.lang.Enum<Card1>{
public static final Card1 SPADES;
public static final Card1 CLUBS;
public static final Card1 DIAMONDS;
public static final Card1 HEARTS;
public static Card1[] values(){....}
public static Card1 valueof(java.lang.String){....}
static{....}
}
列挙型は、java.lang.Enumクラスを継承したfinalクラスとなり列挙した値は、public static final指定されたクラス変数の名前となる。だから、列挙型の値の前後にダブルクォーテーションはいらない。
また、values() と valueOf() というstaticメソッドが自動で追加される。
メソッド名 | 説明 |
---|---|
static 列挙型[] values() | 列挙した値(定数)の全てを配列で返す |
static 列挙型 valueOf(String name) | 引数で指定した名前を持つ値(定数)を返す |
列挙型は明示的にインスタンス化できない。
呼び出すには 「列挙型名.列挙した値」 と記述する。
列挙型のポイント
- 列挙型の定義では、enumキーワードを使用する
- クラスと同様に、コンストラクタ、メソッド、メンバ変数を定義できる
- コンパイルするとクラスファイルが生成される
- 列挙した値を参照するには「列挙型名.列挙した値」とする
- newキーワードによるインスタンス化はできない
- 列挙型によって作成されたクラスはfinalクラスであるためextendsによる継承はできない
- 抽象メソッドの利用やインタフェースの実装は可能
- 列挙型は、Comparableインタフェースを実装しており、各定数は列記した順番で管理される
Onjectクラス
Objectクラスとは
java.lang.Objectクラスは、Javaのクラス階層を構成するためのルートとなるクラス。
すべてのクラスは、Objectクラスの配下に位置する。extendsを使用せず、独自に定義したクラスは、Objectクラスを継承したクラスになる。
ObjectクラスのtoString()メソッド
ObjectクラスのtoStringメソッドは、そのオブジェクトのクラス名、@、及びオブジェクトのハッシュコードの符号なし16進数表現から構成される文字列を返す。
package Sample1;
class Foo {}
class Bar {
public String toString() {
return "This is an object made from Bar";
}
}
public class Sample1_10 {
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
int[] ary = {1,2};
String obj1 = "tanaka";
Foo obj2 = new Foo();
Bar obj3 = new Bar();
System.out.println(ary.toString()); //[I@6504e3b2
System.out.println(obj1.toString()); //tanaka
System.out.println(obj2.toString()); //Sample1.Foo@379619aa
System.out.println(obj3.toString()); //This is an object made from Bar
}
}
Objectクラスのequals()メソッド
Objectクラスのequals()メソッドは2つのオブジェクトを比較し、同じオブジェクトであればtrueを返すメソッド。
これは、==演算子と同じ振る舞い(同じ参照かどうかの比較)
package Sample1;
class FooSample11 {}
class BarSample11 {}
public class Sample1_11 {
public static void main(String[] args) {
FooSample11 f1 = new FooSample11();
FooSample11 f2 = new FooSample11();
// 別々でインスタンス化されたオブジェクトを参照している為false
System.out.println("f1.equals(f2) : " + (f1.equals(f2)));
FooSample11 f3 = new FooSample11();
FooSample11 f4 = f3;
// f3が参照してるオブジェクトをf4も参照している為true
System.out.println("f3.equals(f4) : " + (f3.equals(f4)));
BarSample11 b1 = new BarSample11();
// 違うクラスのオブジェクトをそれぞれが参照している為false
System.out.println("f3.equals(b1) : " + (f3.equals(b1)));
// nullと比較している為false
System.out.println("f3.equals(null) : " + (f3.equals(null)));
}
}
ObjectクラスのhashCode()メソッド
hashCode()メソッドは、オブジェクトのハッシュコードを返す。
ハッシュコードとは、オブジェクトに付与された整数値で、Java実行環境がオブジェクトの識別を行うために使う。
package Sample1;
class Foo_Sample12{}
public class Sample1_12 {
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
Foo_Sample12 f1 = new Foo_Sample12();
Foo_Sample12 f2 = new Foo_Sample12();
System.out.println("f1 : " + f1.hashCode());
System.out.println("f2 : " + f2.hashCode());
Foo_Sample12 f3 = new Foo_Sample12();
Foo_Sample12 f4 = f3;
System.out.println("f3 : " + f3.hashCode());
System.out.println("f4 : " + f4.hashCode());
}
}
f1 : 515132998
f2 : 474675244
f3 : 932583850
f4 : 932583850
異なるオブジェクトから取り出したハッシュコードは異なり、同じオブジェクトから取得したハッシュコードは同じになる。
equals()メソッドとhashCode()メソッドのオーバーライド
独自定義クラスから生成されたオブジェクトにおいて、参照先は異なっても、保持する値がすべて同じであれば等価であると判断したい場合もある。
その時は、equals()とhashCode()をオーバーライドする。
package Sample1;
class FooSample1_13{
private int num;
public boolean equals(Object o) {
if ((o instanceof FooSample1_13) && (((FooSample1_13)o).num == this.num)) {
return true;
}else {
return false;
}
}
public int hashCode() {
return num * 5;
}
}
public class Sample1_13 {
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
FooSample1_13 f1 = new FooSample1_13();
FooSample1_13 f2 = new FooSample1_13();
System.out.println("f1.equals(f2) : " + (f1.equals(f2)));
}
}
f1.equals(f2) : true
instanceof演算子は、ある特定のオブジェクトが特定のクラスの方を持つかどうかを判定する。
左辺で指定した変数に右辺で指定した型を持っていればtrueを返す。左辺と右辺が同一の方でなくても、右辺がスーパークラスやインタフェースで、左辺がサブクラスや実装クラスであればtrueを返す。なお、左辺と右辺に継承関係がない場合は、コンパイルエラーとなる。
staticインポート
staticインポートとは、クラス名を指定せずにstatic変数やstaticメソッドを使用する機能。
import staticキーワードを使用し、完全クラス名およびインポート一したいstatic変数やstaticメンバを指定する。
①import static パッケージ名.クラス名.static変数名;
完全修飾名で指定されたクラスにあるstatic変数をstaticインポートする
②import static パッケージ名.クラス名.staticメソッド名;
完全修飾名で指定されたクラスにあるstaticメソッドをstaticインポートする
③import static パッケージ名.クラス名.*;
完全修飾名で指定されたクラスにあるstaticメンバをstaticインポートする
package Sample1;
// random()メソッドはMathクラスのstaticメソッド
import static java.lang.Math.*;
// out変数はSystemクラスのstatic変数
import static java.lang.System.*;
public class Sample1_14 {
public static void main(String[] args) {
// staticインポートしたstaticメンバを使用したコード
out.println(random());
// staticインポートを使用しないコード
System.out.println(Math.random());
}
}
0.220183175406941
0.8543344918985595
最後に..
長くなってしまった。。。
お疲れさまでした。