03-2: クラスの継承利用とオーバーライドについて
前回は、クラスについて大まかに説明しました。
ここでは、クラスの継承(extend
)を行います。
例えば、学生(Student
)というクラスがあったとき、うちの大学では学系があり、学籍番号とは別のEARTH IDを持っているので、それに合わせる必要があります。
その場合、Studentを継承し、TuisStudentを作成してみましょう。
継承利用は、継承元のロジックに強く紐づいているので、継承元を安易に変更しない、小規模なプログラムに有効という意見が多いです。大規模な実装だと、この先で学ぶインターフェースによる概念の実装やオブジェクト・コンポジションというものを意識して設計する必要があります。
ファイル
以下では、3つのファイルにそれぞれStudentとTuisStudent、それにテスト用のTuisTesterを配置しています。(もちろん、1つのファイルでもよいです)
少しファイルの整理をしましょう。
gatewayで作業している人は、mkdir
コマンドでディレクトリを作成し、その中で3つのファイルを作っておきましょう。
この作成したディレクトリが前に説明したパッケージの範囲と同等になります。
/home/user/java/
|
+- HelloWorld.java
+- TuisMember/ <-------------------- TuisMember Packages!
|
+- Student.[java|class]
+- TuisStudent.[java|class]
+- TuisTester.[java|class]
サンプルコード
Student.java
/*
* 継承元のStudent.java
*
* 大学名を静的に指定し、それ以外はインスタンス
*
*/
class Student {
private String id; //学籍番号
private String name; //氏名
private static String univName; //大学名
private String gradName; //学部名
private String deptName; //学科名
private int admissionYear; //入学年
// 大学名のセット`
static void setUnivName(String univ){
univName = univ;
}
// 大学名のゲット
static String getUnivName() {
return univName;
}
// コンストラクタ
Student(String id, String name, String gradName, String deptName, int admissionYear){
this.id = id;
this.name = name;
this.gradName = gradName;
this.deptName = deptName;
this.admissionYear = admissionYear;
}
// ゲッター
String getId() { return this.id; }
String getName() { return this.name; }
String getGradName() { return this.gradName; }
String getDeptName() { return this.deptName; }
int getAdmissionYear() { return this.admissionYear; }
}
TuisStudent.java
/*
* 本学用の学生クラス(学生クラスを継承)
*
* 学系名とEARTHのIDを追記
*
*/
import java.util.regex.Pattern; //正規表現のパターン
import java.util.regex.Matcher; //正規表現のマッチ
/*
* extends [継承元] で、継承する
* - 継承元を「スーパークラス」
* - 継承先を「サブクラス」と呼ぶ。
*/
class TuisStudent extends Student{
String seriesName; //学系名
String earthId; //EARTH_ID
/*
* 大学IDの正規表現パターン
* - 特定パターンの文字列抽出には、「正規表現(Regex)」を用いる。
* - Javaでは、`java.util.regex.*`を用いる。Linuxでは、vim/edコマンドやsedなどを利用する
* - 以下は、定数として、final変数
*/
final static String earthId_pattern = "([j|k]20|[j|k]19|[j|k]18|[j|k]17|j16|j15|j14|j13)[0-9][0-9][0-9][a-z][a-z]";
//=====================================
// コンストラクタ
//================
TuisStudent(String id, String name, String earthId, String gradName, String deptName, String seriesName, int admissionYear){
super(id, name, gradName, deptName, admissionYear); // super() は、スーパークラスのコンストラクタを呼び出す.
/*
* デフォルトでは、コンストラクタの引数がない場合は、サブクラスでコンストラクタを明示的に作成しなくても良い。これは、以下のようなものが暗黙的に作成されるため。
* // サブクラスのコンストラクタ
* SubClass(){
* super();
* }
* 引数が必要な場合はsuperを明示的に呼び出すことが必要、
*
*/
this.seriesName = seriesName;
this.earthId = earthId;
}//ここまでコンストラクタ
//======================================
// ゲッター
String getEarthId() { return this.earthId; }
String getSeriesName() { return this.seriesName; }
//======================================
// EarthIDかどうかを判断するUtility.
static boolean isEarthId(String id){
return Pattern.compile(earthId_pattern).matcher(id).matches();
}
}// クラス閉じる
TuisTester.java
/*
* TuisStudentのテスト用クラス
*/
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class TuisTester {
// 入力用メソッド
static String input(String text) throws IOException{
System.out.print(text + ": ");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
//=======================================
// メイン
//================
public static void main(String[] args) throws IOException{
// TuisStudent型のリストを作成する
List<TuisStudent> studentList = new ArrayList<TuisStudent>();
// Studentもとい、TuisStudentに大学名をセットする
TuisStudent.setUnivName("東京情報大学");
// 入力
while(true){
String id = input("学籍番号");
String name = input("名前");
String earthId = input("EARTH_ID");
String gradName = input("学部名");
String deptName = input("学科名");
String seriesName = input("学系名");
int admissionYear = Integer.parseInt(input("入学年(西暦)"));
String chk = input("追加しますか?[y/n]");
if(chk.equals("y")){
studentList.add(new TuisStudent(id,name,earthId,gradName,deptName,seriesName,admissionYear));
}
chk = input("続けますか?[y/n]");
if(chk.equals("n")) break;
}
// ここまで入力
//-------------------
// 出力
System.out.println("=========================================================");
printCSV(studentList,true);
//---------------------
}//クラス閉じる
/*
* CSVを標準出力(ArrayList<TuisStudent>,boolean ヘッダを付与するか)
*
* TODO:ファイルに追記に変更
*
*/
static void printCSV(List<TuisStudent> list,boolean header){
String sep = ",";
if(header) { System.out.println("ID,NAME,EARTH_ID,IS_EARTH_ID,UNIV_NAME,GRAD_NAME,DEPT_NAME,SERIES_NAME,ADMISSION_YEAR"); }
for(TuisStudent stu : list){ // 拡張For文
String line = stu.getId()
+ sep + stu.getName()
+ sep + stu.getEarthId()
+ sep + TuisStudent.isEarthId(stu.getEarthId())
+ sep + Student.getUnivName()
+ sep + stu.getGradName()
+ sep + stu.getDeptName()
+ sep + stu.getSeriesName()
+ sep + stu.getAdmissionYear();
System.out.println(line);
}
}
}
おまけ
複数のクラスが継承し、継承元のロジックを書き換えたりするとめんどくさいことになります。
クソコード動画「共通化の罠」 pic.twitter.com/MM750CNXc2
— ミノ駆動 (@MinoDriven) May 12, 2019
最終更新日: 2021年4月30日