Javaのgetter/setter長くてUZEEEEE!!!!と思っていたら、こういう記事を見つけたのでちょっと考えさせられました。そこで大好きな(得意ではない)D言語でちょっと考えてみようと思います。
- OpenJDK 1.8.0_222
- gdc 8.3.0
#Javaのgetter/setterについて
多摩科技ACなので、一応オブジェクト指向とJavaの説明をします。本題はここからです。
オブジェクト指向はソフトウェア設計の指針の一つで、「あらゆる物はオブジェクト(関連するデータをひとまとまりにし、それに対して代入、変換、関数などを行える実体)である」という考え方のことです。
初めてその言葉を使ったアラン・ケイによれば、
- すべてはオブジェクトである。
- オブジェクトはメッセージの受け答えによってコミュニケーションする。
- オブジェクトは自身のメモリーを持つ。
- どのオブジェクトもクラスのインスタンスであり、クラスもまたオブジェクトである。
- クラスはその全インスタンスの為の共有動作を持つ。インスタンスはプログラムにおけるオブジェクトの形態である。
- プログラム実行時は、制御は最初のオブジェクトに渡され、残りはそのメッセージとして扱われる。
らしいです。
##アクセス権
オブジェクトの設計図とでも言うべきものがclass
で、これを最初に書かないことには何も始まりません。
class Student{
public String name;
public int number;
Student(String name, int number){
this.name = name;
this.number = number;
}
}
public class Test1{
public static void main(String[] args){
Student taro = new Student("Teraoka Taro",27);
Student jiro = new Student("Yamashita Jiro",35);
System.out.println("taro's name:" + taro.name);
System.out.println("jiro's number:" + String.valueOf(jiro.number));
}
}
Java久々すぎてStringをstringって書いちゃったのは別のお話。
Student
は生徒データの設計図で、main
内でnewしているtaro
とjiro
がその実体であるオブジェクト(インスタンス)です。
classがまとめているデータをメンバといいます。Student内のname
やnumber
もメンバ(メンバ変数)です。多分それぞれ氏名と生徒番号でしょう。
Studentクラスの中にStudent(string,int)
というメソッドがあります。クラスと同名のメソッドをコンストラクタと呼び、オブジェクト作成時(今回はnew Student(〜)...のところ)で呼び出されるものです。
メンバ変数の前にpublic
というキーワードが付加されています。これらはアクセス修飾子と呼ばれます。
いくつか種類があるのですが、とりあえず
- public:どこからでもアクセスできる
- private:自クラス内からしかアクセスできない。
だけ押さえておけばOKです。Test1.javaのname,numberをprivateにすると
class Student{
private String name;
private int number;
Student(String name, int number){
this.name = name;
this.number = number;
}
}
$ javac Test2.java
Test2.java:15: エラー: nameはStudentでprivateアクセスされます
System.out.println("taro's name:"+taro.name);
^
Test2.java:16: エラー: numberはStudentでprivateアクセスされます
System.out.println("jiro's number:"+String.valueOf(jiro.number));
てなことになります。
##カプセル化
もし全てのメンバがpublicだと、色々と面倒なことになります。
例えばTest1.javaについて、こんなこともできちゃいます。
taro.number = -23;
jiro.name = null;
生徒番号に負の値を使う学校なんて、性格ひん曲がりもいいところですね。ましてや名前がnullなんて馬鹿馬鹿しいにも程があります。
ともかく、プログラマのミス(もしくは悪意)によっては、このようなバグが起こるかもしれません。
そこで、メンバ変数は全てprivateとし、それにアクセスするために別のメンバメソッドを用意する方法が考えられます。
class Student{
private String name;
private int number;
Student(String name, int number){
this.name = name;
this.number = number;
}
public String getName(){return name;}
public void setName(String name){
assert name != null;
this.name = name;
}
public int getNumber(){return number;}
public void setNumber(int number){
assert number > 0;
this.number = number;
}
}
public class Test3{
public static void main(String[] args){
Student taro = new Student("Teraoka Taro",27);
Student jiro = new Student("Yamashita Jiro",35);
jiro.getNumber(-5)
System.out.println("taro's name:" + taro.getName());
System.out.println("jiro's number:" + String.valueOf(jiro.getNumber()));
}
}
$ javac Test3.java
$ java -ea Test3
Exception in thread "main" java.lang.AssertionError
at Student.setNumber(Test3.java:18)
at Test3.main(Test3.java:27)
ちゃんと再設定時に確認ができてますね(コンストラクタでもやれって話ですけど)。
このようなメンバメソッドをgetter/setterといいます。
##getter/setter長い
さて、試しにStudentクラスに国語、数学、英語の点数を追加してみましょう。
class Student{
private String name;
private int number;
private int japanese;
private int math;
private int english;
Student(String name, int number){
this.name = name;
this.number = number;
}
public String getName(){return name;}
public void setName(String name){
assert name != null;
this.name = name;
}
public int getNumber(){return this.number;}
public void setNumber(int number){
assert number > 0;
this.number = number;
}
public int getJapanese(){return japanese;}
public void setJapanese(int japanese){
assert japanese >= 0 && japanese <= 100;
this.japanese = japanese;
}
public int getMath(){return math;}
public void setMath(int math){
assert math >= 0 && math <= 100;
this.math = math;
}
public int getEnglish(){return english;}
public void setEnglish(int english){
assert english >= 0 && english <= 100;
this.english = english;
}
}
クッソなげぇ
もしこれが10教科とかになったら...地獄ですね。
というわけで冒頭の話に戻ります。
#D言語で
##property
とりあえずTest3.javaをproperty使って書き換えてみましょう。
class Student{
private string _name;
private int _number;
@property{
void name(string _name){
assert(_name!=null);
this._name=name;
}
string name(){return _name;}
void number(int _number){
assert(_number>0);
this._number=_number;
}
int number(){return _number;}
}
this(string _name, uint _number){
this._name=_name;
this._number=_number;
}
}
ん〜、コードの長さあんまり変わんなくね?
しかしpropertyによって、あたかも変数を直接いじっているような簡単操作ができます。
void main(){
import std.stdio;
Student saburo=new Student("Mukai Saburo",18);
saburo.name.writeln;
saburo.number=-1;
saburo.number.writeln;
}
$ ./test3
Mukai Saburo
core.exception.AssertError@test3.d(16): Assertion failure
(以下略)
ちゃんとAssertErrorも出ました。
##invariant
しかしこのままではあんまりにあんまりです。そこで、メンバの不変条件を決定できるinvariant
の存在を知ったので使ってみます。invariant内は以下の時に呼ばれます。
- コンストラクタ実行後、デストラクタ実行前
- メンバ関数の実行前と実行後
class Student{
private string _name;
private int _number;
invariant{
assert(_name!=null);
assert(_number>0);
}
@property{
void name(string _name){
this._name=name;
}
string name(){return _name;}
void number(int _number){
this._number=_number;
}
int number(){return _number;}
}
this(string _name, uint _number){
this._name=_name;
this._number=_number;
}
}
void main(){
import std.stdio;
Student saburo=new Student("Mukai Saburo",18);
saburo.name.writeln;
saburo.number=-1;
saburo.number.writeln;
}
$ ./test3
Mukai Saburo
core.exception.AssertError@test4.d(9): Assertion failure
(以下略)
上出来。
これなら当然コンストラクタについてもチェックできるので、そっちにassert
を置く必要もありません。
ちなみにdmdのv2.081.0からこういう書き方もできるようになっているそうです。
invariant(_name!=null);
invariant(_number>0);
#参考
Qiita: 結局のところgetter/setterは要るのか?要らないのか?
dlang.org: Contract Programming