Javaのコンストラクタ内でSetter、Getterを使ってはならないらしい

f:id:ts0818:20201123210027j:plain

コムギ(小麦)はイネ科コムギ属に属する一年草植物。一般的にはパンコムギ学名Triticum aestivum)を指すが、広義にはクラブコムギ(学名: Triticum compactum)やデュラムコムギ(学名: Triticum durum)などコムギ属(学名: Triticum)の植物全般を指す。世界三大穀物の一つで、パン麺類菓子など様々に調理され、食用にされる。古くから栽培され、世界で最も生産量の多い穀物の一つである。年間生産量は約7.3億トンであり、これはトウモロコシの約10.4億トンには及ばないが、米の約7.4億トンにほぼ近い(2014年)。

コムギ - Wikipedia

⇧ いつも買っている安い乾麺の蕎麦のそば粉の含有量が10%未満と知って、最早それを蕎麦と言えるのかという疑心暗鬼に駆られる、どうもボクです。

そんな「小麦」と言えば、「ゲノム」が「ヒト」より大きいというね。Wikipediaさんに聞いてみたところ、以下のような関係になってるんだと。(一部抜粋)

生物種  ゲノムの大きさ 
 ココナッツカダンカダンウイロイド
 アボカドサンブロッチウイロイド
 246(最小のゲノムを持つウイロイド
 大腸菌  4.6×106(一般的な細菌)
 イネ  3.9×108
 トウモロコシ  2.3×109
 ヒト  3.0×109
 マウス  3.3×109
 コムギ   1.7×1010
 ポリカオス・ドゥビウム(アメーバ)  6.7×1011(最大のゲノムを持つ生物)

「アメーバ」の「ゲノム」が最大ってのが衝撃ね... 

 我らが「ヒト」の「ゲノム」の解析はどうなっとるのかというと、また新たな発見があたそうな。

www.riken.jp

理化学研究所理研)生命医科学研究センターゲノム免疫生物学理研白眉研究チームのニコラス・F・パリッシュ理研白眉研究チームリーダー、劉暁渓基礎科学特別研究員(研究当時)、小出りえ特別研究員、ゲノム解析応用研究チームの寺尾知可史チームリーダーらの共同研究グループは、日本人の大規模なゲノム解析を行い、一部のヒトゲノムに「ヒトヘルペスウイルス6(HHV-6)」のゲノム全長に類似したDNA配列が組み込まれていることを発見しました。

日本人ゲノムに存在する古代ウイルスの化石 | 理化学研究所

今回、共同研究グループは、日本人7,485人の全ゲノム配列を解析した結果、日本人の約200人に1人がゲノムにHHV-6由来のDNA(内在性HHV-6)の配列を持つことを明らかにしました。これらの内在性HHV-6配列は、約3万年前に東アジア人の祖先のゲノムに入り込んだと考えられます。また、内在性HHV-6配列のほとんどは、ゲノムの複製や老化に関連するとされるテロメア 領域に組み込まれていることが分かりました。

日本人ゲノムに存在する古代ウイルスの化石 | 理化学研究所

⇧「老化」のメカニズムが解明されることを祈りますか。

まったく関係ない話が長々と続きましたが、今回もJavaの話です。レッツトライ~。 

 

コンストラクタって?

Oracleの「The Java® Language Specification(Java SE 15 Edition)」の説明によると、

docs.oracle.com

constructor is used in the creation of an object that is an instance of a class

https://docs.oracle.com/javase/specs/jls/se15/html/jls-8.html#jls-8.8

⇧ と「クラスのインスタンスのオブジェクトを作成するために使用されるもの」とありますと。

Javaってのは、以下のように「データの型」が用意されてますと。

docs.oracle.com

The types of the Java programming language are divided into two kinds: primitive types and reference types. The primitive types are the boolean type and the numeric types. The numeric types are the integral types byteshortintlong, and char, and the floating-point types float and double. The reference types are class types, interface types, and array types. There is also a special null type. An object is a dynamically created instance of a class type or a dynamically created array. The values of a reference type are references to objects. All objects, including arrays, support the methods of class Object. String literals are represented by String objects.

https://docs.oracle.com/javase/specs/jls/se15/html/jls-4.html

⇧ 大きく分けて、「primitive types」「reference types」の2つに分かれますと。

で、「クラス」っていうのは「reference types」に属する「データの型」になりますと。

何が言いたいかと言うと、「コンストラクタ」は、この「クラス」に用意されている仕組みなんだと。

 

Setter、Getterの起源は、Java Bean?

無茶苦茶に分かり辛いんけど、

“A Java Bean is a reusable software component that can be manipulated visually in a builder tool.”

https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf?AuthParam=1606117841_b457de8714a73df46d7699c11b0e7bed

⇧「Java Bean」っていうのは、GUIを構築する目的のための「コンポーネント」として誕生してましたと。

まぁ、「Java」は元々は「サーバーサイド言語」っていう扱いじゃなかったらしいんで、自然な流れだったんですかね。

で、「Java Bean」の最も重要な特徴というのが3つあると言っていて、

The three most important features of a Java Bean are the set of properties it exposes, the set of methods it allows other components to call, and the set of events it fires.

https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf?AuthParam=1606117841_b457de8714a73df46d7699c11b0e7bed

⇧上記の文書の「2.2 Properties, events, and methods」の部分に記載があり、

  1. the set of properties
  2. the set of methods
  3. the set of events

 でありますと。

で、この「2.the set of methods」が「Setter」「Getter」の起源なのではないかという情報がネットでは溢れてますと。(あくまで推測でしかないとは思われますが。)

で、「Property」っていうのは、「7 Properties」の説明によると、

Properties are discrete, named attributes of a Java Bean that can affect its appearance or its behaviour. For example, a GUI button might have a property named “Label” that represents the text displayed in the button.

https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf?AuthParam=1606117841_b457de8714a73df46d7699c11b0e7bed

⇧「Java Bean」を特定するラベルみたいなもんで、

Properties can have arbitrary types, including both built-in Java types such as “int” and class or interfaces types such as “java.awt.Color”.

https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf?AuthParam=1606117841_b457de8714a73df46d7699c11b0e7bed

⇧「primitive types」「reference types」のどっちも含むことができるんだと。

「7.1 Accessor methods」の説明によると、

Properties are always accessed via method calls on their owning object. For readable properties there will be a getter method to read the property value. For writable properties there will be a setter method to allow the property value to be updated. Thus even when a script writer types in something such as “b.Label = foo” there is still a method call into the target object to set the property, and the target object has full programmatic control. So properties need not just be simple data fields, they can actually be computed values. Updates may have various programmatic side effects. For example, changing a bean’s background color property might also cause the bean to be repainted with the new color. For simple properties the accessor type signatures are:
  void setFoo(PropertyType value); // simple setter
  PropertyType getFoo(); // simple getter

https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf?AuthParam=1606117841_b457de8714a73df46d7699c11b0e7bed

⇧「Accessor methods」で「Properties」にアクセスできるようで、この「Accessor methods」ってのが「Setter」「Getter」の起源っぽい。(あくまで推測でしかないですが。)

で、「Accessor methods」の説明を「Setter」「Getter」 にも当てはめれたと仮定して矛盾しない場合、命名規則なんかもあるっぽい。

8.3 Design Patterns for Properties

8.3.1 Simple properties

By default, we use design patterns to locate properties by looking for methods of the form:

  public <PropertyType> get<PropertyName>();

  public void set<PropertyName>(<PropertyType> a);

If we discover a matching pair of “get<PropertyName>” and “set<PropertyName>” methods that take and return the same type, then we regard these methods as defining a read-write property whose name will be “<PropertyName>”. We will use the “get<PropertyName>” method to get the property value and the “set<PropertyName>” method to set the property value. The pair of methods may be located either in the same class or one may be in a base class and the other may be in a derived class. If we find only one of these methods, then we regard it as defining either a read-only or a writeonly property called “<PropertyName>

https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf?AuthParam=1606117841_b457de8714a73df46d7699c11b0e7bed

⇧ 上記の説明によると、「Setter」は「set」と「PropetyName」の文字列連結、「Getter」は「get」と「PropetyName」の文字列連結したメソッド名になるんだと。(あくまで推測でしかないですが。)

 

コンストラクタ内でSetter、Getterを使ってはならないらしい

はい、だいぶ、脱線しましたが、「Java Bean」の「Accessor methods」の説明が「Setter」「Getter」にそのまま当てはめることができたと仮定して矛盾しない場合を前提として話を進めますが、そんな「Setter」「Getter」については、「コンストラクタ」内で使うのはNGらしいですと。

で、何で『「コンストラクタ」内で「Setter」「Getter」を使ってはならない』って話を気にしたかというと、

「コンストラクタ」内で、「クラス」の「フィールド」に「コンストラクタ」の引数を代入してるのをよく見かけるから、「コンストラクタ」内で「Setter」を使えば良いんじゃない?って思ったんですよ。

つまり、以下のようなコードがあったとして、

class Employee {

  private String employeeId;
  
  public Employee (String employeeId) {
    this.employeeId = employeeId;
  }
  
  public void setEmployeeId (String employeeId) {
    this.employeeId = employeeId;
  }
  
  public String getEmployeeId () {
    return this.employeeId;
  }
  
}

以下のように、コンストラクタ内でSetterを使う感じにすれば良いんじゃないかね?って思ったんだけど、

class Employee {

  private String employeeId;
  
  public Employee (String employeeId) {
    this.setEmployeeId(employeeId);
  }
  
  public void setEmployeeId (String employeeId) {
    this.employeeId = employeeId;
  }
  
  public String getEmployeeId () {
    return this.employeeId;
  }
  
}

どうも、これはやってはいけないことらしい。

www.366service.com

コンストラクタからゲッターとセッターを呼び出すべきではありません。

フィールドの初期化を保証する唯一の方法は、それらを割り当てることです。セッターを呼び出すと、それがオーバーライドされる可能性があり、何か他のことをする可能性があります。まだ初期化されていないサブクラスのメソッドを呼び出すことがあります。

コンストラクタでゲッターとセッターを使用する必要がありますか? 

同じクラスからフィールドを取得するだけであれば、getterを呼び出すことも悪い考えです。それがsuper-classで宣言されていれば、それを正当化するかもしれません。サブクラスのsuper-classからデータを取得する必要がある場合は、getterを呼び出す必要があります(保護されていない限り)。構築中にsub-classからsuper-classにデータを通信する必要がある場合は、パラメータとして渡す必要があります。

コンストラクタでゲッターとセッターを使用する必要がありますか?

⇧ という感じで、「Setter」に何やら手を加えてしまっている場合、意図した通りの初期化が行われない危険がありますと。

つまり、以下のような「Setter」の実装をしてしまっているケースが有り得るかもしれないんだと。(そもそも「Setter」であれこれ手を加えるのはどうかと思うんだけどね)

  
  public void setEmployeeId (String employeeId) {
    this.employeeId = "ID" + employeeId;
  }
  

「Setter」でこういうことをされてた場合、コンストラクタ内で「Setter」を使っていると、コンストラクタでフィールドの初期化を行う際に、結果が変わってきてしまうんだと。

  public Employee (String employeeId) {
    this.employeeId = employeeId;
  }

 

  public Employee (String employeeId) {
    this.setEmployeeId(employeeId);
  }

 

ちなみに、「Setter」「Getter」の命名規則とか結構厄介です。

living-sun.com

 

Javaって25周年を迎えたって聞いたけど、「Setter」「Getter」のような「Accessor methods」についてちゃんと定義された情報が見当たらないってどうなのかね...

あくまで、「Java Bean」での「Accessor methods」の説明でしかないけど、それを「Java」の「クラス」でよく見かける「Getter」「Setter」にも当てはめちゃっていいものなのかね?

まぁ、そんなこんなで、今回もモヤモヤ感が半端ない。

今回はこのへんで。