this と super の復習
this と super の使い方を知る
基本的には this と super を使用する際の軸となる考え方は一緒で、主に下記のような場合に使用します。
自クラスの インスタンス の フィールド変数 / メソッド の前に付与して
- 自 または 親 クラス の フィールド や、メソッド( コンストラクタ含 )を優先的に参照する場合
- 自 または 親 クラス の フィールド や、メソッド( コンストラクタ含 ) の 引数 や、メソッド 内で 一時的に使用するローカル変数を区別する場合
this と super の挙動
こちらのコードをもとに、見ていきます。
【GroupWorker.java】
package practiceThisAndSuper;
//「団員」という概念のクラス
public class GroupWorker {
/** empId: 団員番号 */
private int groupId;
/** name: 氏名 */
private String name;
/** コンストラクタ: 引数なし */
public GroupWorker() {
}
public GroupWorker(int groupId, String name) {
this.groupId = groupId;
this.name = name;
}
// ... フィールド変数のゲッターとセッター(※省きます)
// Getter Setter -------------------------------
// Getter
public int getGroupId() {
return this.groupId;
}
// Setter
public void setGroupId(int groupId) {
this.groupId = groupId;
}
// Getter
public String getName() {
return this.name;
}
// Setter
public void setName(String name) {
this.name = name;
}
// Getter Setter -------------------------------
/**
* 団員情報をコンソールへ出力するメソッド
*/
public void printGroupWorker() {
System.out.println("団員番号: " + this.groupId);
System.out.println("氏名 : " + this.name);
}
}
【RocketGroupWorker.java】
package practiceThisAndSuper;
//ロケット団員クラス
class RocketGroupWorker extends GroupWorker {
/** isExecutive: 幹部フラグ(幹部であるかどうかを true/false で判断するためのフィールド変数) */
boolean isExecutive;
public RocketGroupWorker() {
}
/**
* コンストラクタ: 引数あり
*
* @param groupId
* @param name
*/
public RocketGroupWorker(int groupId, String name) {
// ①挙動確認: 親クラス(GroupWorker)のコンストラクタを呼び出す
super(groupId, name);
// ③挙動確認: thisを指定せずに格納する その1
boolean isExecutive;
if (groupId != 10) {
isExecutive = true;
} else {
isExecutive = false;
}
System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
}
/**
* コンストラクタ: 引数あり
*
* @param groupId
* @param name
* @param isExecutive
*/
public RocketGroupWorker(int groupId, String name, boolean isExecutive) {
// ①挙動確認: 親クラス(GroupWorker)のコンストラクタを呼び出す
super(groupId, name);
// ②挙動確認: 自クラスのフィールド変数(isExecutive)へコンストラクタの引数を格納する
this.isExecutive = isExecutive;
// ③挙動確認: thisを指定せずに格納する その2
isExecutive = true;
System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
// ④挙動確認: thisとsuperで同じメソッドを呼び出す
this.printGroupWorker();
super.printGroupWorker();
}
// ... フィールド変数のゲッターとセッター(※省きます)
// Getter
public boolean getIsExecutive() {
return this.isExecutive;
}
// Setter
public void setIsExecutive(boolean isExecutive) {
this.isExecutive = isExecutive;
}
}
①挙動確認: 親クラス(GroupWorker)のコンストラクタを呼び出す
//①挙動確認: 親クラス(GroupWorker)のコンストラクタを呼び出す
super(groupId, name);
ポピュラーの使用方法で、**「特に異なる 初期化処理 などを行う必要がない場合」**はこれ。
super の実体は、「GroupWorker.java(親クラス)」
「RocketGroupWorker.java」 の コンストラクタ(二箇所)で呼び出していますが、
継承先で オーバーライド せずに親クラスの コンストラクタ を利用しています。
②挙動確認: 自クラスのフィールド変数(isExecutive)へコンストラクタの引数を格納する
// ②挙動確認: 自クラスのフィールド変数(isExecutive)へコンストラクタの引数を格納する
this.isExecutive = isExecutive;
this の基本の使い方は「ローカル変数とフィールド変数を明確に区別するため」。
例では、フィールド 変数の isExecutive と 引数の isExecutive を明確に区別した使用法で、this を付与して正しい値の受け渡しを行えています。
③挙動確認: thisを指定せずに格納する
this を付与していないことが原因で ローカル変数/引数の値を変更してしまっています。
この場合、起こりうる結果としては以下になります。
【その1の場合】
フィールド変数へ値を格納することを意図していたとしても、実際に値が格納されるのはローカル変数
// ③挙動確認: thisを指定せずに格納する その1
boolean isExecutive;
if (groupId != 10) {
isExecutive = true;
} else {
isExecutive = false;
}
System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
【その2の場合】
- フィールド変数へ値を格納することを意図していたとしても、実際に値が格納されるのは引数
- 仮に②と③の処理順が逆であった場合でも、実際に値が格納されるのは引数になる
// ③挙動確認: thisを指定せずに格納する その1
boolean isExecutive;
if (groupId <= 10) {
isExecutive = true;
} else {
isExecutive = false;
}
System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
.....
.....
.....
.....
// ③挙動確認: thisを指定せずに格納する その2
isExecutive = true;
System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
何故かというとコンパイル時には以下のような「優先順位が存在するから」です。
「ローカル変数/メソッドの引数」 > 「フィールド変数」
ローカル変数と メソッド の 引数 が同名の場合 は、コンパイル時に ワーニングが 表示されます ので即時対応が可能なのですが、
フィールド変数 とローカル変数(or メソッドの引数)の場合 は、 ワーニングは 表示されません。
// mainメソッドで 定義
RocketGroupWorker be = new RocketGroupWorker(1000, "ムサシ");
// ③挙動確認: thisを指定せずに格納する その1
boolean isExecutive;
if (groupId != 10) {
isExecutive = true;
} else {
isExecutive = false;
}
System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
出力結果
幹部ではない
boolean 型の値に関しては、初期値が false となってます。
そのため、この場合は フィールド変数( isExecutive )の値と、 条件分岐後の格納値は一致しているため、
初期化時に期待する結果(フィールド変数の isExecutive が false であること)は変わりません。
isExecutive be = new isExecutive(1, "サカキ");
のように コンストラクタ を呼び出した場合は、
条件分岐の結果true が格納されてしまいますので、フィールド変数( isExecutive )は意図した初期化ができません。
上記はフィールド変数に値を格納したいはずなので、
- this を付与をする
- 同名のローカル変数は避ける
といったことをやるべきです。
「一時的に同名のローカル変数」を用意したい場合
2パターン挙げます
1. 頭文字に「一時的な」という意味の「Temporary」を使用する
boolean tmpIsExecutive;
if (groupId != 10) {
tmpIsExecutive = true;
} else {
isExecutive = false;
}
this.isExecutive = tmpIsExecutive;
2. 一番スッキリ書く方法
一番ベストの書き方はこちら
こちらの書き方がベストですが、最初のうちは 1の if と else
のブロックの考え方を意識すること。
// (groupId != 10)の結果値である true/false が戻り値として格納される
this.isExecutive = (groupId != 10);
実行結果
...
isExecutive = true;
System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
出力結果
幹部ではない
この処理において isExecutive = true;
が、
this.isExecutive = true; を意図してコーディングしたものである場合は話が変わってきます。
④挙動確認: thisとsuperで同じメソッドを呼び出す
こちらの
- this.printGroupWorker();
- super.printGroupWorker();
両方共、親 クラス である GroupWorker の printGroupWorker() を呼び出しています。
言い方を変えればオーバーライドすることにより、上記の this と super の呼び出し先は変わるということ。
// ④挙動確認: thisとsuperで同じメソッドを呼び出す
this.printGroupWorker();
super.printGroupWorker();
RocketGroupWorker クラスで printGroupWorker() をオーバーライドする
/**
* 団員情報をコンソールへ出力するメソッド(幹部フラグを含む)
*/
public void printGroupWorker() {
super.printGroupWorker();
System.out.println("幹部フラグ: " + this.isExecutive);
}
上記のようにオーバーライドした場合、 **「this は自クラスでオーバーライドした printGroupWorker() を参照する。」**
命名規則に関して重要なポイント
「値を受け渡す元と先の 変数に関して、意味的にも値として同じ場合、同様の命名規則」にしてください。
実装時はそのクラスの内容に適した 変数名・メソッドやメソッド名を実装していく訳ですが、要するに「疑問を招くような命名をするな」ということ。
例:「 name 」という変数に「 namae 」という[引数]が格納される
ですが、以下のように変数名と引数名がことなっていても、「同じ値がはいっていますよ」と明示したような引数名なら許容度です。
// parm は parameter(パラメーター)の意味
this.age = parmAge;
this.name = parmName;
ただ結論、**「this を付与する方向でコーディングしたほうが良い」**です。
thisをつけることによって、コード量が大量になって、似たような命名の変数やメソッドを使用するなった場合であっても、目当てのコードが探しやすくなります。
thisやsuperが許容されないケース
【ケース1】コンストラクタの前に処理が存在する
コンストラクタ内の処理として this や super を使用する場合は、処理の一番時最初に記述しなければなりません。
public BlueEmployee(int empId, String name) {
boolean isExecutive = (groupId == 1);
this(groupId, name); // ここでエラー
super(groupId, name); // ここでエラー
この場合、以下のようなエラーが表示されます。
コンストラクター呼び出しは、コンストラクター内の最初のステートメントである必要があります。
このような場合はエラーが吐かれて実行は不可ですので、上記のようなエラーの時はチェックしてみましょう。
【ケース2】static修飾子ついている
static修飾子 が付与されたもの(クラス,変数、定数、メソッド) にはthis と super は付与することができません
サンプルコード
問題ないコードはこちら
public class StaticError {
public static void main(String[] args) {
StaticError.callPrintln(); // OK
}
private static void callPrintln() {
System.out.println("解消方法1: 先頭にクラス名を付与する");
System.out.println("解消方法2: メソッドの定義時にstaticを追加");
}
}
実行結果
解消方法1: 先頭にクラス名を付与する
解消方法2: メソッドの定義時にstaticを追加
こちらはエラーになるコードです
public class StaticError {
public static void main(String[] args) {
this.callPrintln(); // staticメソッド内のためエラーになる
}
private static void callPrintln() {
System.out.println("解消方法1: 先頭にクラス名を付与する");
System.out.println("解消方法2: メソッドの定義時にstaticを追加");
}
}
実行結果
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
static コンテキストでは this を使用できません
at practiceThisAndSuper.StaticError.main(StaticError.java:6)
コード内に解消方法なども書きましたが、
static コンテキストでは this を使用できません
こちらのエラー(ワーニング)が出た際の解消方法は2つあります。