ラベル Javaのつかみどころ の投稿を表示しています。 すべての投稿を表示
ラベル Javaのつかみどころ の投稿を表示しています。 すべての投稿を表示

2017年12月17日日曜日

6: ナビゲート可能で要素挿入可能なリンクトハッシュマップを作ろう

<このシリーズの、前の記事 | このシリーズの目次 |

本文 START

ナビゲート可能で要素挿入可能なリンクトハッシュマップを作った

話題: Javaプログラミング言語

ナビゲート可能で要素挿入可能なリンクトハッシュマップの用途

バイアス惑星の山あいの小川のほとりのある部屋で-Hypothesizerと-Rebutterがコンピュータースクリーンの前に座っている。

-Hypothesizer

これは、スプレッドシートセルエディターを作った際に遭遇したことだが、Javaの標準ライブラリには、ナビゲート可能で要素挿入可能なリンクトハッシュマップが見当たらなかった。

-Rebutter

「ナビゲート可能」というのはどういう意味だ?

-Hypothesizer

私が意味していることにその用語が適切なのかどうか知らないが、より適切な用語が見つからなかった。

-Rebutter

とにかく、君はどういう意味で使っているのか?

-Hypothesizer

私が意味しているのは、先頭の要素や末尾の要素を得ることができ、任意の指定した要素の前や後の要素を得ることができること、効率的にだ。

-Rebutter

それらは、標準の'LinkedHashMap'でできないのか?

-Hypothesizer

できない。それは要素の順序は覚えているのだが、そうした機能は提供しない。

-Rebutter

ああ、それでは、要素の順序の恩恵は、先頭の要素から要素をイテレートするためだけに受けられるわけか。指定した要素から次の要素に直接ジャンプすることはできない。

-Hypothesizer

要は、'LinkedHashMap'はリンクトリストではない。要素の挿入順序を内部的に保持するマップにすぎない。

-Rebutter

要素は、キーで指定したいのか?

-Hypothesizer

そうだ。実際、スプレッドシートセルエディターの場合には、値は必要なくて、キーだけが必要だ。

-Rebutter

それでは、Mapの代わりに、Setでいいわけだ。

-Hypothesizer

実はそうだ、だが、それはここでは問題ではない。'LinkedHashMap'においてと同じ理由で、'LinkedHashSet'も使えない。Mapについて話しているのは、用途がより広いからだ。値を無視すれば、MapをSetとして使える。

-Rebutter

しかし、キー‐値ペアが要らないのであれば、代わりに、'LinkedList'を使えないのか?

-Hypothesizer

ああ、機能だけを考えれば、使える。'LinkedList'では、指定した要素の次の要素を以下のように得られる。'l_linkedList'は'LinkedList'のインスタンスであり、'l_specifiedElement'は指定した要素だ。

@Java Source Code
  l_searchedElement = l_linkedList.get (l_linkedList.indexOf (l_specifiedElement) + 1);

しかしながら、処理は効率的ではない。内部的に、それは、指定した要素を見つけるために、要素を先頭の要素からイテレートする。

-Rebutter

ふーむ、「ナビゲート可能」なリンクトハッシュマップにはほとんど需要がないのだろうか?つまり、それが、標準Javaライブラリに「ナビゲート可能」なリンクトハッシュマップがない理由なのか?

-Hypothesizer

おそらく、多くの需要がないのだろう、しかし、いくらかはある、というのは、Apache.commonsにそういうクラス、'org.apache.commons.collections4.map.LinkedMap'があるから。

-Rebutter

. . . じゃあ、なぜそれを使わないのか?

-Hypothesizer

使ってもよいが、1つのクラスのためにJarを丸ごと導入するのに躊躇したわけ。特に、その1つのクラスを実装するのが別に難しく思えなかったから。

-Rebutter

うーん、 . . . そこは、難しい判断だ。

-Hypothesizer

それに、任意の指定した要素の前に要素を挿入できる必要もある。

-Rebutter

ああ、標準の'LinkedHashMap'では、要素は常に末尾に追加される。Apache commonsの'LinkedMap'はどうだ?

-Hypothesizer

同じのようだ。

-Rebutter

我々の求める機能を持ったクラスがApache commonsに別にあるのじゃないか。

-Hypothesizer

うーん、 . . .

-Hypothesizerは、インターネットで、Apache commonsのAPI文書の中を探す。

これかな?

-Rebutter

「これ」とは?

-Hypothesizer

'org.apache.commons.collections4.map.ListOrderedMap'だ。

-RebutterはそのクラスのJavadocページに目を通す。

-Rebutter

まあ、少なくとも、これには、我々の求めるメソッドがあるようだ。

-Hypothesizer

しかし、これは効率的かな?わからないな。

-Rebutter

そもそも、君は、セルエディターのどこで、ナビゲート可能で要素挿入可能なリンクトハッシュマップを使いたかったのか?

-Hypothesizer

セルエディターフレームの上のSwingコンポーネント群のタブ順を設定するために、'java.awt.FocusTraversalPolicy'のメソッド、'getComponentBefore'および'getComponentAfter'を実装しなければならない。これらメソッドのそれぞれは、Swingコンポーネントを受け取り、次にフォーカスを受けるSwingコンポーネントを戻す。このタブ順を、ナビゲート可能で要素挿入可能なリンクトハッシュマップで指定したかったわけ。

-Rebutter

引数として渡されたSwingコンポーネントの前または後のSwingコンポーネントを得られるように?

-Hypothesizer

そう。

-Rebutter

なぜ、指定した要素の前に要素を挿入しなければならないのか?

-Hypothesizer

セルエディターフレームを、もっと汎用的なエディターフレームのサブクラスにしたかったから。汎用的なエディターフレームには、「指定されたフレーズを検索する」ボタンなどのSwingコンポーネント群があって、セルエディターには、追加の、「左のセルに移動する」ボタンなどのセルエディター特有のSwingコンポーネント群がある。汎用的なエディターフレームでタブ順を指定し、そのタブ順のあるSwingコンポーネントの前に、セルエディターフレームで、いくつかのSwingコンポーネントを挿入したい。

-Rebutter

とにかく、君のSwingコンポーネントは、'LinkedList'を使ってパフォーマンスに感知できるほどのインパクトがあるほどそんなに多くないんじゃないか?

-Hypothesizer

まあ、実のところ、 . . . 多くない。実際問題としては、その件で'LinkedList'を使うことに何の問題もないだろう。しかし、先頭要素から毎回イテレートしなければならないのは、ねえ、ばからしい気がするんだよ。それに、ナビゲート可能で要素挿入可能なリンクトハッシュマップを必要に思ったのはこれが最初ではないし。そこで思ったわけ、「今回作ったらどうだろう?」

-Rebutter

まあ、そうしていけない理由があるとは言わない。

我々の、ナビゲート可能で要素挿入可能なリンクトハッシュマップ、のデザイン

前場と同じ。

-Rebutter

それで、君のデザインは?

-Hypothesizer

先に言った通り、簡単だ。値が単に値ではなく、値、先行する要素のキー、後続する要素のキーを持つオブジェクトであるハッシュマップのインスタンスが必要なだけだ。ハッシュマップなので、キーによって指定した要素にジャンプでき、先行する要素や後続する要素にもジャンプできる。

-Rebutter

それでは、そのハッシュマップインスタンスは'LinkedHashMap'インスタンスではないわけだ。

-Hypothesizer

'LinkedHashMap'は必要ない、どのみち、'LinkedHashMap'インスタンスに保存された要素の順序は使えないから。我々は、任意の位置に要素を挿入しなければならない。

-Rebutter

すると、エントリーセットを、先行キー、後続キーリンクに基づいて自分で作ることになる。

-Hypothesizer

そう。そしてもちろん、要素が我々のマップインスタンスに放り込まれるにつれ、リンクが維持されなければならない。そのロジックは普通のリンクトリストにおけるものと同じだ。

-Rebutter

'HashMap'クラスを拡張するのか?

-Hypothesizer

いや。それはうまくいかないようだ。'NavigableLinkedHashMap <T, U>'を作りたいが、我々は'HashMap <T, U>'を拡張できない。'ValueAndLinks'を値、先行キー、後続キーを持つクラスとして、'HashMap <T, ValueAndLinks <U, T>>'を使うから。我々は、'java.util.Map'インターフェースを実装しなければならない。'HashMap'インスタンスは、我々のマップのメンバーになる。

-Rebutter

すると、'HashMap'インスタンスをラップするわけだ。

-Hypothesizer

そう。

我々の、ナビゲート可能で要素挿入可能なリンクトハッシュマップ、の実装

前場と同じ。

-Hypothesizer

以下が、我々の、ナビゲート可能で要素挿入可能なリンクトハッシュマップ、の全コードだ。

@Java Source Code
package thebiasplanet.coreutilities.collections;

import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

public class NavigableLinkedHashMap <T, U> implements Map <T, U> {
 private HashMap <T, ValueAndLinks <T, U>> i_keyToValueAndLinksHashMap;
 private T i_firstKey;
 private T i_lastKey;
 
 private class ValueAndLinks <T, U> {
  U i_value;
  T i_previousKey;
  T i_nextKey;
  
  public ValueAndLinks (U a_value, T a_previousKey, T a_nextKey) {
   i_value = a_value;
   i_previousKey = a_previousKey;
   i_nextKey = a_nextKey;
  }
  
  public U getValue () {
   return i_value;
  }
  
  public void setValue (U a_value) {
   i_value = a_value;
  }
  
  public T getPreviousKey () {
   return i_previousKey;
  }
  
  public void setPreviousKey (T a_previousKey) {
   i_previousKey = a_previousKey;
  }
  
  public T getNextKey () {
   return i_nextKey;
  }
  
  public void setNextKey (T a_nextKey) {
   i_nextKey = a_nextKey;
  }
 }
 
 public NavigableLinkedHashMap () {
  i_keyToValueAndLinksHashMap = new HashMap <T, ValueAndLinks <T, U>> ();
  initialize ();
 }
 
 public NavigableLinkedHashMap (int a_initialCapacity) {
  i_keyToValueAndLinksHashMap = new HashMap <T, ValueAndLinks <T, U>> (a_initialCapacity);
  initialize ();
 }
 
 public NavigableLinkedHashMap (int a_initialCapacity, float a_loadFactor) {
  i_keyToValueAndLinksHashMap = new HashMap <T, ValueAndLinks <T, U>> (a_initialCapacity, a_loadFactor);
  initialize ();
 }
 
 public NavigableLinkedHashMap (Map <? extends T,? extends U> a_map) {
  
  i_keyToValueAndLinksHashMap = new HashMap <T, ValueAndLinks <T, U>> (a_map.size ());
  initialize ();
  putAll (a_map);
 }
 
 private void initialize () {
  i_firstKey = null;
  i_lastKey = null;
 }
 
 @Override
 public boolean containsKey (Object a_key) {
  return i_keyToValueAndLinksHashMap.containsKey (a_key);
 }
 
 @Override
 public boolean containsValue (Object a_value) {
  for (Map.Entry <T, ValueAndLinks <T, U>> l_keyToValueAndLinksHashMapEntry: i_keyToValueAndLinksHashMap.entrySet ()) {
   U l_value = l_keyToValueAndLinksHashMapEntry.getValue ().getValue ();
   return (a_value == null ? l_value == null : a_value.equals (l_value));
  }
  return false;
 }
 
 @Override
 public U get (Object a_key) {
  return i_keyToValueAndLinksHashMap.get (a_key).getValue ();
 }
 
 @Override
 public int hashCode () {
  return i_keyToValueAndLinksHashMap.hashCode ();
 }
 
 @Override
 public boolean isEmpty () {
  return i_keyToValueAndLinksHashMap.isEmpty ();
 }
 
 @Override
 public Set <T> keySet () {
  LinkedHashSet <T> l_keys = new LinkedHashSet <T> ();
  for (T l_key = i_firstKey, l_nexyKey = null; l_key != null; l_key = l_nexyKey) {
   ValueAndLinks <T, U> l_valueAndLinks = i_keyToValueAndLinksHashMap.get (l_key);
   l_keys.add (l_key);
   l_nexyKey = l_valueAndLinks.getNextKey ();
  }
  return l_keys;
 }
 
 @Override
 public U put (T a_key, U a_value) {
  ValueAndLinks <T, U> l_valueAndLinks = i_keyToValueAndLinksHashMap.get (a_key);
  if (l_valueAndLinks != null) {
   U l_value = l_valueAndLinks.getValue ();
   l_valueAndLinks.setValue (a_value);
   return l_value;
  }
  else {
   l_valueAndLinks = new ValueAndLinks <T, U> (a_value, i_lastKey, null);
   i_keyToValueAndLinksHashMap.put (a_key, l_valueAndLinks);
   T l_previousKey = l_valueAndLinks.getPreviousKey ();
   if (l_previousKey != null) {
    i_keyToValueAndLinksHashMap.get (l_previousKey).setNextKey (a_key);
   }
   else {
    i_firstKey = a_key;
   }
   i_lastKey = a_key;
   return null;
  }
 }
 
 public U putBefore (T a_insertedPositionKey, T a_key, U a_value) {
  ValueAndLinks <T, U> l_existingValueAndLinks = i_keyToValueAndLinksHashMap.get (a_key);
  if (l_existingValueAndLinks != null) {
   return null;
  }
  else {
   ValueAndLinks <T, U> l_insertedPositionValueAndLinks = i_keyToValueAndLinksHashMap.get (a_insertedPositionKey);
   if (l_insertedPositionValueAndLinks == null) {
    return null;
   }
   else {
    T l_previousKeyOfInsertedPosition = l_insertedPositionValueAndLinks.getPreviousKey ();
    ValueAndLinks <T, U> l_valueAndLinks = new ValueAndLinks <T, U> (a_value, l_previousKeyOfInsertedPosition, a_insertedPositionKey);
    i_keyToValueAndLinksHashMap.put (a_key, l_valueAndLinks);
    if (l_previousKeyOfInsertedPosition != null) {
     i_keyToValueAndLinksHashMap.get (l_previousKeyOfInsertedPosition).setNextKey (a_key);
    }
    else {
     i_firstKey = a_key;
    }
    l_insertedPositionValueAndLinks.setPreviousKey (a_key);
    return l_insertedPositionValueAndLinks.getValue ();
   }
  }
 }
 
 @Override
 public void putAll (Map <? extends T,? extends U> a_map) {
  for (Map.Entry <? extends T,? extends U> l_mapEntry: a_map.entrySet ()) {
   put (l_mapEntry.getKey (), l_mapEntry.getValue ());
  }
 }
 
 @Override
 public U remove(Object a_key) {
  ValueAndLinks <T, U> l_valueAndLinks = i_keyToValueAndLinksHashMap.get (a_key);
  if (l_valueAndLinks != null) {
   i_keyToValueAndLinksHashMap.remove (a_key);
   T l_previousKey = l_valueAndLinks.getPreviousKey ();
   T l_nextKey = l_valueAndLinks.getNextKey ();
   if (l_previousKey != null) {
    i_keyToValueAndLinksHashMap.get (l_previousKey).setNextKey (l_nextKey);
   }
   else {
    i_firstKey = l_nextKey;
   }
   if (l_nextKey != null) {
    i_keyToValueAndLinksHashMap.get (l_nextKey).setPreviousKey (l_previousKey);
   }
   else {
    i_lastKey = l_previousKey;
   }
   return l_valueAndLinks.getValue ();
  }
  else {
   return null;
  }
 }
 
 @Override
 public void clear () {
  i_keyToValueAndLinksHashMap.clear ();
  i_firstKey = null;
  i_lastKey = null;
 }
 
 @Override
 public Set <Map.Entry <T, U>> entrySet () {
  LinkedHashSet <Map.Entry <T, U>> l_keyToValueEntries = new LinkedHashSet <Map.Entry <T, U>> ();
  for (T l_key = i_firstKey, l_nexyKey = null; l_key != null; l_key = l_nexyKey) {
   ValueAndLinks <T, U> l_valueAndLinks = i_keyToValueAndLinksHashMap.get (l_key);
   l_keyToValueEntries.add (new AbstractMap.SimpleEntry <T, U> (l_key, l_valueAndLinks.getValue ()));
   l_nexyKey = l_valueAndLinks.getNextKey ();
  }
  return l_keyToValueEntries;
 }
 
 @Override
 public Collection <U> values () {
  LinkedHashSet <U> l_values = new LinkedHashSet <U> ();
  for (T l_key = i_firstKey, l_nexyKey = null; l_key != null; l_key = l_nexyKey) {
   ValueAndLinks <T, U> l_valueAndLinks = i_keyToValueAndLinksHashMap.get (l_key);
   l_values.add (l_valueAndLinks.getValue ());
   l_nexyKey = l_valueAndLinks.getNextKey ();
  }
  return l_values;
 }
 
 @Override
 public boolean equals (Object a_object) {
  if (a_object == null || a_object instanceof Map) {
   return false;
  }
  return entrySet ().equals ( ( (Map) a_object).entrySet ());
 }
 
 @Override
 public int size () {
  return i_keyToValueAndLinksHashMap.size ();
 }
 
 public T getPreviousKey (T a_key) {
  ValueAndLinks <T, U> l_valueAndLinks = i_keyToValueAndLinksHashMap.get (a_key);
  if (l_valueAndLinks != null) {
   return l_valueAndLinks.getPreviousKey ();
  }
  else {
   return null;
  }
 }
 
 public T getNextKey (T a_key) {
  ValueAndLinks <T, U> l_valueAndLinks = i_keyToValueAndLinksHashMap.get (a_key);
  if (l_valueAndLinks != null) {
   return l_valueAndLinks.getNextKey ();
  }
  else {
   return null;
  }
 }
 
 public T getFirstKey () {
  return i_firstKey;
 }
 
 public T getLastKey () {
  return i_lastKey;
 }
}

-Rebutter

ははあ、確かに、大したものじゃない。

-Hypothesizer

クラスはこのzipファイルに含まれている。我々のzipファイルの使い方は、ここに書いてある。

-Rebutter

. . . それで?

-Hypothesizer

それで、何?

-Rebutter

少なくとも、我々の、ナビゲート可能で要素挿入可能なリンクトハッシュマップ、にパフォーマンス上何らかのアドバンテージがあるかテストすべきじゃないか?だって、もし何にもなければ、意味がないだろう . . .

-Hypothesizer

うーん、正直なところ、少なくとも幾分かのアドバンテージがあることは疑っていなかった、たいしたものじゃないとしても、メカニズムを考えれば。

-Rebutter

まあ、何かある可能性はあるが、 . . .

-Hypothesizer

オーケー。ちょっとテストしてみよう。

-Hypothesizerは、'coreUtilitiesTestToDisclose'プロジェクトに3つのクラスを書く、若干の時間をかけて。

-Rebutter

. . . . . . . . . zzz

-Hypothesizer

いいだろう。これが、我々の、ナビゲート可能で要素挿入可能なリンクトハッシュマップ、のテストクラスだ。

@Java Source Code
package test.navigablelinkedhashmaptest1;

import java.util.Map;
import thebiasplanet.coreutilities.collections.NavigableLinkedHashMap;

public class Test1Test {
 private Test1Test () {
 }
 
 public static void main (String [] p_arguments) throws Exception {
  int l_numberOfElements = 10000;
  int l_mode = 0;
  if (p_arguments.length > 0) {
   l_numberOfElements = Integer.valueOf (p_arguments [0]);
  }
  if (p_arguments.length > 1) {
   l_mode = Integer.valueOf (p_arguments [1]);
  }
  Test1Test.test (l_numberOfElements, l_mode);
 }
 
 // a_mode: 0 -> without dummy processing, 1 -> with dummy processing, 2 -> show some intermediate results without dummy processing
 public static void test (int a_numberOfElements, int a_mode) {
  long l_nanoTimeForInsertionStart = -1;
  long l_nanoTimeForInsertionStop = -1;
  long l_nanoTimeForInsertionAccumulation = 0;
  long l_nanoTimeForSearchStart = -1;
  long l_nanoTimeForSearchStop = -1;
  long l_nanoTimeForIterationStart = -1;
  long l_nanoTimeForIterationStop = -1;
  int l_power = -1;
  NavigableLinkedHashMap <Integer, String> l_navigableLinkedHashMap = new NavigableLinkedHashMap <Integer, String> (a_numberOfElements);
  Integer l_lastElement = null;
  Integer l_currentElement = null;
  Integer l_searchedElement = null;
  l_power = 1;
  System.out.println ("#For NavigableLinkedHashMap Beginning");
  System.out.println ("#Elements  : Insertion for (ns); Search from (ns); Iteration for (ns)");
  l_nanoTimeForInsertionStart = System.nanoTime ();
  l_nanoTimeForInsertionAccumulation = 0;
  for (int l_elementIndex = 0; l_elementIndex < a_numberOfElements; l_elementIndex ++) {
   l_currentElement = Integer.valueOf (l_elementIndex);
   if (l_lastElement != null) {
    l_navigableLinkedHashMap.putBefore (((l_elementIndex % 2) == 0) ? l_navigableLinkedHashMap.getNextKey (l_lastElement) : l_lastElement, l_currentElement, "");
   }
   else {
    l_navigableLinkedHashMap.put (l_currentElement , "");
   }
   l_lastElement = l_currentElement;
   // Dummy Processing BEGINNING
   if (a_mode == 1 && l_elementIndex == 1) {
    l_nanoTimeForInsertionStop = System.nanoTime ();
    l_nanoTimeForInsertionAccumulation += l_nanoTimeForInsertionStop - l_nanoTimeForInsertionStart;
    l_searchedElement = l_navigableLinkedHashMap.getNextKey (l_lastElement);
    for (Map.Entry <Integer, String> l_navigableLinkedHashMapEntry: l_navigableLinkedHashMap.entrySet ()) {
     Integer l_element = l_navigableLinkedHashMapEntry.getKey ();
    }
    l_nanoTimeForInsertionStart = System.nanoTime ();
   }
   // Dummy Processing END
   if (l_elementIndex + 1 == Math.pow (10, l_power)) {
    l_nanoTimeForInsertionStop = System.nanoTime ();
    l_nanoTimeForInsertionAccumulation += l_nanoTimeForInsertionStop - l_nanoTimeForInsertionStart;
    l_nanoTimeForSearchStart = System.nanoTime ();
    l_searchedElement = l_navigableLinkedHashMap.getNextKey (l_lastElement);
    if (a_mode == 2) {
     System.out.println (String.format ("#Searched Element: %d", l_searchedElement));
    }
    l_nanoTimeForSearchStop = System.nanoTime ();
    l_nanoTimeForIterationStart = System.nanoTime ();
    for (Map.Entry <Integer, String> l_navigableLinkedHashMapEntry: l_navigableLinkedHashMap.entrySet ()) {
     Integer l_element = l_navigableLinkedHashMapEntry.getKey ();
     if (a_mode == 2 && l_power == 2) {
      System.out.println (String.format ("#Iterated Element: %d", l_element));
     }
    }
    l_nanoTimeForIterationStop = System.nanoTime ();
    System.out.println (String.format ("# %,9d: %,18d; %,16d; %,18d", l_elementIndex + 1, l_nanoTimeForInsertionAccumulation, l_nanoTimeForSearchStop - l_nanoTimeForSearchStart, l_nanoTimeForIterationStop - l_nanoTimeForIterationStart));
    l_nanoTimeForInsertionStart = System.nanoTime ();
    l_power ++;
   }
  }
  System.out.println ("#For NavigableLinkedHashMap End");
 }
}

もちろん、'LinkedList'用にも作ったし、実は、'ListOrderedMap'用にも作った。しかし、ここに示すのはやめておこう、ロジックはほとんど同じだから。

-Hypothesizerはターミナルを開き、カレントディレクトリを'coreUtilitiesTestToDisclose'ディレクトリに移し、以下のコマンドを実行する。

@bash or cmd Source Code
gradle run -PMAIN_CLASS_NAME="test.navigablelinkedhashmaptest1.Test1Test" -PCOMMAND_LINE_ARGUMENTS="1000000 0"
gradle run -PMAIN_CLASS_NAME="test.navigablelinkedhashmaptest1.Test2Test" -PCOMMAND_LINE_ARGUMENTS="100000 0"
gradle run -PMAIN_CLASS_NAME="test.navigablelinkedhashmaptest1.Test3Test" -PCOMMAND_LINE_ARGUMENTS="100000 0"

結果は以下の通りだ。

@Output
#For NavigableLinkedHashMap Beginning
#Elements  : Insertion for (ns); Search from (ns); Iteration for (ns)
#        10:         23,149,950;            4,906;          6,691,418
#       100:         27,823,886;              956;          4,523,059
#     1,000:         57,166,916;            1,005;         17,431,587
#    10,000:         98,915,977;              380;         55,901,657
#   100,000:        401,701,939;           57,463;        339,302,036
# 1,000,000:      1,847,431,862;           54,360;      1,391,781,924
#For NavigableLinkedHashMap End

#For LinkedList Beginning
#Elements  : Insertion for (ns); Search from (ns); Iteration for (ns)
#        10:            673,338;           32,917;            798,186
#       100:          2,880,055;           12,040;             78,205
#     1,000:         36,506,260;           23,692;          5,656,543
#    10,000:      1,047,642,813;          260,234;          6,519,878
#   100,000:    128,732,712,283;        2,437,329;         27,707,494
# 1,000,000:                  -;                -;                  -
#For LinkedList End

#For ListOrderedMap Beginning
#Elements  : Insertion for (ns); Search from (ns); Iteration for (ns)
#        10:          7,039,154;          107,793;         58,504,655
#       100:         10,158,446;           10,332;            856,906
#     1,000:         57,747,306;           19,041;         15,046,638
#    10,000:      1,685,974,232;          165,994;         20,261,828
#   100,000:     88,471,135,958;          601,450;         39,563,050
# 1,000,000:                  -;                -;                  -
#For ListOrderedMap End

-Rebutter

zzzzzz . . .

-Hypothesizer

結果は以下の通りだと言ったんだよ!

-Rebutter

はあ?. . . 何だこれは?

-Hypothesizer

結果だ!

-Rebutter

もちろん、分かっている。. . . これらの数字のそれぞれがどういう意味かを聞いたのだ。

-Hypothesizer

コレクションインスタンス(我々のナビゲート可能で要素挿入可能なリンクトハッシュマップの、または'LinkedList'の,または'ListOrderedMap'の)に一定数の要素を、各要素が最後に挿入された要素の、交互に前または後になるように挿入した。

-Rebutter

うむ?その意図は?

-Hypothesizer

その意図は、新たな要素を、常に、既存のリストのほとんど中央に挿入することだ。だって、先頭または末尾に挿入するのは我々のテストとしては意味がないだろう。'LinkedList'インスタンスの先頭または末尾に効率的に挿入できるのは分かりきっている。

-Rebutter

もちろん。

-Hypothesizer

要素の数が10、100、1,000、などになったとき、要素数とそこまでの挿入にかかった時間を出力した。次に、コレクションインスタンスから、最後に挿入した要素(ほとんど中央にある要素という意味になる)の次の要素を取得して、そうするのにかかった時間を出力した。次に、コレクションインスタンスの全要素をイテレートして、そうするのにかかった時間を出力した。

-Rebutter

それらを、要素数が10の累乗になる度に行なったわけか?

-Hypothesizer

そうだ。短く言うと、上の'NavigableLinkedHashMap'(我々のナビゲート可能で要素挿入可能なリンクトハッシュマップ)の表で、第3行は、1,000要素の挿入に57,166,916ナノ秒かかり、1,000要素から1要素を取得するのに1,005ナノ秒かかり、1,000要素をイテレートするのに17,431,587ナノ秒かかったことを意味する。

-Rebutter

君の説明は特に「短く」は思えないが、意味は分かった。

-Rebutterは、しばらくかけて、3つの数表に目を通す。

-Rebutter

ふーむ、 . . . 興味深い。

'LinkedList'と'ListOrderedMap'のほうが、挿入は、 要素数が小さい時は速いが、要素数が大きくなると耐え難く遅くなる。

-Hypothesizer

実際、それが、それらに、1,000,000要素のテストをしなかった理由だ。時間がかかりすぎる。

-Rebutter

'NavigableLinkedHashMap'のほうが、予期通り検索時間は小さいが、要素数に応じた変化は予期できなかったものだ。なぜ、380へ減少し、突然57,463へ増加し,しかし、1,000,000要素では増加しないのか?

-Hypothesizer

正直、まったく分からない。

-Rebutter

これらの数字は、テスト実行毎にそんなに一定していないと思うが。

-Hypothesizer

もちろん、そんなに一定していないが、上記傾向はかなり一定している。

-Rebutter

それに、'NavigableLinkedHashMap'のイテレーションは他の2つより遅いな。なぜだ?

-Hypothesizer

まあ、'NavigableLinkedHashMap'は、マップエントリーセットをビューとして作らなければならないという違いはある。イテレータに、マップインスタンスにあるままの要素をイテレートさせるわけにはいかない。

-Rebutter

それはあると思うが、'NavigableLinkedHashMap'は'ListOrderedMap'と比べても遅い。

-Hypothesizer

そう。. . . 我々の'entrySet'メソッドが粗野にすぎるだけかもしれない。

-Rebutter

'ListOrderedMap'の実装原理は何なのだろうか?

-Hypothesizer

検索時間からすると、ハッシュベースではないようだ。傾向は、我々のマップによりは'LinkedList'のほうに似ているが、ある種の違いもある。

-Rebutter

10要素の検索時間とイテレーション時間は、コレクションタイプにかかわらず、なぜ、こんなに不可解に長いのか?

-Hypothesizer

ああ、根本的な理由はわからないが、それは、要素数のためではなく、最初の計測だからだ。実際、テストを以下のように、別のモードで動かすと、それらのアノマリーは消える。

-Hypothesizerは、以下のコマンドを実行する。

@bash or cmd Source Code
gradle run -PMAIN_CLASS_NAME="test.navigablelinkedhashmaptest1.Test1Test" -PCOMMAND_LINE_ARGUMENTS="1000000 1"
gradle run -PMAIN_CLASS_NAME="test.navigablelinkedhashmaptest1.Test2Test" -PCOMMAND_LINE_ARGUMENTS="100000 1"
gradle run -PMAIN_CLASS_NAME="test.navigablelinkedhashmaptest1.Test3Test" -PCOMMAND_LINE_ARGUMENTS="100000 1"

@Output
#For NavigableLinkedHashMap Beginning
#Elements  : Insertion for (ns); Search from (ns); Iteration for (ns)
#        10:         28,520,467;            2,908;            156,256
#       100:         33,321,037;              797;          2,413,758
#     1,000:         38,163,177;            1,065;         13,511,453
#    10,000:         89,912,506;              391;         30,831,210
#   100,000:        395,634,986;           62,369;        234,624,658
# 1,000,000:      1,879,539,755;           57,196;      1,385,221,677
#For NavigableLinkedHashMap End

#For LinkedList Beginning
#Elements  : Insertion for (ns); Search from (ns); Iteration for (ns)
#        10:            647,509;            3,608;             12,280
#       100:          2,723,686;            9,707;             76,147
#     1,000:         63,961,605;           23,011;          7,731,741
#    10,000:      1,384,062,815;          259,357;          4,904,449
#   100,000:    132,988,500,832;        2,434,372;         28,290,306
# 1,000,000:                  -;                -;                  -
#For LinkedList End

#For ListOrderedMap Beginning
#Elements  : Insertion for (ns); Search from (ns); Iteration for (ns)
#        10:            510,002;            5,851;             32,134
#       100:          3,148,831;            8,390;          2,031,163
#     1,000:         49,244,310;           16,848;         15,572,011
#    10,000:        920,952,896;          168,168;          8,021,630
#   100,000:     82,659,823,277;          655,516;         41,154,981
# 1,000,000:                  -;                -;                  -
#For ListOrderedMap End

-Rebutter

うむ?'NavigableLinkedHashMap'の検索時間のアノマリーが消えたようには見えないな、私には。. .

-Hypothesizer

そう . . .、しかし、別のアノマリーは消えた。

-Rebutter

なぜ、'NavigableLinkedHashMap'の検索時間のだけ消えない?

-Hypothesizer

知らない。

-Rebutter

新しいモードでは何をしたのか?

-Hypothesizer

要素数が2のときに、密かに、検索とイテレーションをしただけだ。

-Rebutter

ふーむ、すると、最初の検索と最初のイテレーションには長い時間がかかるわけだ、なぜか . . .

-Hypothesizer

正直、その裏にあるメカニズムは知らない。. .

とにかく、これらのテストコードをzipファイルに含めたが、'Test3Test'は、ソースファイルをリネームして無効化しておいた。Apache commonsのJarファイルをzipファイルに含めなかったから、さもないと、プロジェクトが正常にビルドできない。有効化するには、Apache commonsのJarファイルをダウンロードし、そのJarファイルを、プロジェクト用GradleビルドスクリプトまたはAntビルドファイルの'OTHER_CLASSES_PATHS'に登録し、'Test3Test'のソースファイル名を元に戻せばよい。

本文 END

参考資料

  • Oracle and/or its affiliates. (2017). Overview (Java Platform SE 8). Retrieved from https://docs.oracle.com/javase/8/docs/api/
  • The Apache Software Foundation. (2017). Overview (Apache Commons Collections 4.2-SNAPSHOT API). Retrieved from https://commons.apache.org/proper/commons-collections/apidocs/

<このシリーズの、前の記事 | このシリーズの目次 |

2017年12月10日日曜日

5: 定数グループの定数群の名前や値を列挙可能にする方法

<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>

本文 START

定数群の名前や値が列挙可能でなければならない定数グループのベースクラスを作ろう

話題: Javaプログラミング言語

なぜただEnumを使わないのか

-Hypothesizer

以前の記事で我々はenumの制限について話した。

-Rebutter

enumは拡張することができない、それがJavaのEnumであろうが、自ら作ったenumであろうが、enumの本質を損なうことなしには。

-Hypothesizer

enumの本質は、少なくとも我々にとっては、メソッドの引数をあらかじめ定められた値の集合に制限することだ。その制限が要らないのであれば、ただ定数グループを使えばよいだけだ。そうすれば、それらを好きなように拡張できる。

-Rebutter

我々が「定数グループ」と呼んでいるのは、典型的には、以下のようなインターフェースのことだ。

@Java Source Code
public interface AConstantsGroup {
 String c_value1 = "Value1";
 String c_value2 = "Value2";
}

-Hypothesizer

そう。クラスにすることもできるが、そうしたら、多重継承が使えないだろう。だから、特に理由がなければ、我々はそれをインターフェースにする。

-Rebutter

ははあ。

-Hypothesizer

enumを受け取るメソッドを作る場合、確かに、enumは便利だ。引数のタイプが特定(ただのStringとかではない)だから、引数に何を渡せるかが明確であり、テストしなくても、あらかじめ定められた値だけが渡されることが保証される。

しかし、値を、既存のメソッド(例えば、JDKの標準ライブラリのメソッド)の、例えば、String引数に渡そうとする場合、enumを使うメリットがあまりない。

-Rebutter

そのメソッドを多くの場所で呼ぶ際に、リテラルを散乱させたくない場合のことを君は言っているのだろう。

-Hypothesizer

メソッドそのものは、引数を可能な値の有限数のオプションに制限しないのだが、我々のプログラムは、値の有限数の集合のみを使い、その集合を、我々は定数グループとして管理したい。

-Rebutter

オーケー。

-Hypothesizer

そうした場合にenumを使うと、それを拡張できないというハンディキャップを、必要もなく負うことになってしまう。実際、場合により、我々はそれを拡張したいのだ。

-Rebutter

すると、そうした場合に、我々は、定数グループを使うわけだ。

-Hypothesizer

しかしながら、場合により、定数グループに含まれる定数群の値や名前を列挙したいということがある。

-Rebutter

それは、上記のような単純なインターフェースではできない。

-Hypothesizer

そこで、ここでは、それを可能にしよう。

我々のデザインはどのようなものか?

-Hypothesizer

ベース抽象クラスを作り、それを拡張するクラスに含まれる定数群の名前と値を収集する機能を持たせる。

実際には、'abstract public class BaseEnumerableConstantsGroup <T>'というクラスを(パッケージ、'thebiasplanet.coreutilities.constantsgroups'に)作る。

-Rebutter

'T'は何を表わすのか?

-Hypothesizer

定数値群のクラスだ。

-Rebutter

なるほど。

-Hypothesizer

このベースクラスを以下のように使う。

@Java Source Code
package test.enumerableconstantsgrouptest1;

public interface Test1ConstantsGroup1 {
 String c_value1 = "Value1";
 String c_value2 = "Value2";
}

package test.enumerableconstantsgrouptest1;

import thebiasplanet.coreutilities.constantsgroups.BaseEnumerableConstantsGroup;

public final class Test1EnumerableConstantsGroup1 extends BaseEnumerableConstantsGroup <String> implements Test1ConstantsGroup1 {
 public static final Test1EnumerableConstantsGroup1 c_instance = new Test1EnumerableConstantsGroup1 ();
 
 private Test1EnumerableConstantsGroup1 () {
 }
}

-Rebutter

定数グループ、'Test1ConstantsGroup1'を通常どおりインターフェースとして作り、この定数グループを実装した、先ほどのベースクラスの具象クラスとして、列挙可能な定数グループを作るわけだ。

-Hypothesizer

そうだ。列挙可能定数グループは拡張せず、インターフェースである定数グループを拡張し、それら定数グループの列挙可能バージョンを作ることにする。そうすれば、多重継承が使える。

以下が例だ。

@Java Source Code
package test.enumerableconstantsgrouptest1;

public interface Test1ConstantsGroup2 {
 String c_value3 = "Value3";
 String c_value4 = "Value4";
}

package test.enumerableconstantsgrouptest1;

public interface Test1ExtendedConstantsGroup12 extends Test1ConstantsGroup1, Test1ConstantsGroup2 {
 String c_value1 = "Value1 overwritten";
 String c_value4 = "Value4 overwritten";
 String c_value5 = "Value5";
 String c_value6 = "Value6";
}

package test.enumerableconstantsgrouptest1;

import thebiasplanet.coreutilities.constantsgroups.BaseEnumerableConstantsGroup;

public final class Test1EnumerableExtendedConstantsGroup12 extends BaseEnumerableConstantsGroup <String> implements Test1ExtendedConstantsGroup12 {
 public static final Test1EnumerableExtendedConstantsGroup12 c_instance = new Test1EnumerableExtendedConstantsGroup12 ();
 
 private Test1EnumerableExtendedConstantsGroup12 () {
 }
}

-Rebutter

ふーむ、具象クラスがしないといけないのは、抽象クラスを拡張し、インターフェースを実装し、シングルトンインスタンスを定義し、コンストラクタをプライベートにすることか。

-Hypothesizer

そうだ。たいした負担でなければよいが。

-Rebutter

定数群の名前や値はどうやって得るのか?

-Hypothesizer

以下のようにして得られる。

@Java Source Code
  LinkedHashSet <String> l_names = Test1EnumerableExtendedConstantsGroup12.c_instance.getNames ();
  ArrayList <String> l_values = Test1EnumerableExtendedConstantsGroup12.c_instance.getValues ();

我々の実装はどのようなものか?

-Rebutter

どのように実装する?

-Hypothesizer

ベースクラスには、定数群の名前と値を名前−to−値のLinkedHashMapとして格納するフィールドを持たせる。コンストラクタは、定数群の名前と値を収集し、そのフィールドに格納する。パブリックメソッド、'getNames'と'getValues'が名前群と値群をそれぞれ公開する。

-Rebutter

名前群と値群はどのように収集するのか?

-Hypothesizer

Javaのリフレクション機能を使う。

-Rebutter

ふーん、リフレクション機能は、パフォーマンスの懸念の元ともなりうるが . . .

-Hypothesizer

それが、これをシングルトンにしなければならない理由だ。シングルトンなので、初期化は、基本的には、1度だけ動き、実際上、パフォーマンスの心配の種にはならないだろう。

-Rebutter

まあ、それであれば、おそらく、パフォーマンス上の問題はないだろう。

-Hypothesizer

'BaseEnumerableConstantsGroup'のコンストラクタで、定数群の名前や値を'this'から得るが、java.lang.Classクラスの'getFields'メソッドは使えない。

-Rebutter

なぜ使えないのか?

-Hypothesizer

このメソッドは、シャドーイングされたフィールドも取ってしまうから。

-Rebutter

それでは、このメソッドは、'Test1ExtendedConstantsGroup12.c_value1'だけでなく、'Test1ConstantsGroup1.c_value1'も取得するのか?

-Hypothesizer

そうだ。それに、このメソッドが戻すフィールドの並び順が都合よくない。サブインターフェースのフィールドが先になる。

-Rebutter

すると、上の例では、フィールドを以下の並び順で得ることになる。

  • 'Test1ExtendedConstantsGroup12.c_value1'
  • 'Test1ExtendedConstantsGroup12.c_value4'
  • 'Test1ExtendedConstantsGroup12.c_value5'
  • 'Test1ExtendedConstantsGroup12.c_value6'
  • 'Test1ConstantsGroup1.c_value1'
  • 'Test1ConstantsGroup1.c_value2'
  • 'Test1ConstantsGroup2.c_value3'
  • 'Test1ConstantsGroup2.c_value4'
-Hypothesizer

我々は、以下の並び順で欲しい。

  • 'Test1ExtendedConstantsGroup12.c_value1'
  • 'Test1ConstantsGroup1.c_value2'
  • 'Test1ConstantsGroup2.c_value3'
  • 'Test1ExtendedConstantsGroup12.c_value4'
  • 'Test1ExtendedConstantsGroup12.c_value5'
  • 'Test1ExtendedConstantsGroup12.c_value6'
-Rebutter

スーパーインターフェースのフィールドが先に来るが、フィールドが上書きされた場合は、そのフィールドは、元の位置のまま置き換えられる。

-Hypothesizer

それが我々の要求だ。

そこで、ユーティリティクラス、'thebiasplanet.coreutilities.reflectionshandling.ReflectionHandler'に、我々の要求を満たすstaticメソッドを作った。このメソッドのシグネチャは以下のとおりだ。

@Java Source Code
 public static LinkedHashMap <String, Object> getFieldNamesAndValues (Class <?> a_class, Object a_instance, boolean a_publicOnly, boolean a_setAccessible, List <Class <?>> a_assignableClassesOfFields, List <Class <?>> a_notAssignableClassesOfFields, boolean a_recursive) throws IllegalAccessException

-Rebutter

'a_setAccessible'というのは何だ?

-Hypothesizer

この引数を'true'にセットすると、メソッドは、呼び出し元から見えないフィールド(例えば'private'フィールド)も取得しようとする。しかし、そうできる保証はない。セキュリティ設定に依存するから。

-Rebutter

本件では、これを'true'にする必要はない。すべてのフィールドが'public'だから。

-Hypothesizer

このメソッドがこの引数を持っているのは、本件のためだけでなく、一般的な用途にと考えているためだ。

-Rebutter

'a_assignableClassesOfFields'と'a_notAssignableClassesOfFields'は何だ?

-Hypothesizer

それらは、取得するフィールドを選択するため。'a_assignableClassesOfFields'の中のあるクラスにキャストでき、'a_notAssignableClassesOfFields'内のどのクラスにもキャストできないフィールドだけが取得される。そうした選択が必要なのは、さもなければ、フィールド、'c_instance'が不必要に取得されてしまうから。

-Rebutter

ははあ。

'a_recursive'は、メソッドが、スーパークラス、スーパーインターフェース、実装されたインターフェースへと再帰的にフィールドを探すかどうかを指定するものだと思うが。

-Hypothesizer

そう。

このメソッドを、'BaseEnumerableConstantsGroup'のコンストラクタで以下のように呼ぶ。

@Java Source Code
   i_nameToValueMap = ReflectionHandler.getFieldNamesAndValues (this.getClass (), null, true, false, null, ListFactory. <Class <?>>createArrayList (this.getClass ()), true);

-Rebutter

'ListFactory'というのは何だ?

-Hypothesizer

ああ、それは、Listのインスタンスを生成する別のユーティリティクラスだ。Listをインスタンス化するのは配列をインスタンス化するほど便利ではないから、それを作った。

-Rebutter

'a_instance'に'null'を渡したのは、スタティックフィールドのみを取得する場合にはインスタンスは必要ないからのようだ。

-Hypothesizer

そう。'a_instance'に'null'を渡すと、スタティックフィールドのみを取得するという意味になる。

これがコードだ

-Hypothesizer

ここで、上記ユーティリティクラス群と'BaseEnumerableConstantsGroup'のコードを示す。

@Java Source Code
package thebiasplanet.coreutilities.collectionshandling;

import java.util.ArrayList;
import java.util.Arrays;

public class ListFactory {
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T> ArrayList <T> createArrayList (Object ... a_items) {
  ArrayList <T> l_arrayList = new ArrayList <T> ();
  if (a_items != null) {
   Arrays.stream (a_items).forEach (a_item -> {
    l_arrayList.add ( (T) a_item);
   });
  }
  return l_arrayList;
 }
 
 // This doesn't accept any array as an expandable item; pass Iterables instead.
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T> ArrayList <T> createArrayListExpandingItems (Object ... a_items) {
  ArrayList <T> l_arrayList = new ArrayList <T> ();
  if (a_items != null) {
   Arrays.stream (a_items).forEach (a_item -> {
    if (a_item instanceof Iterable) {
     for (Object l_element: (Iterable) a_item) {
      l_arrayList.add ( (T) l_element);
     }
    }
    else {
     l_arrayList.add ( (T) a_item);
    }
   });
  }
  return l_arrayList;
 }
}

package thebiasplanet.coreutilities.collectionshandling;

import java.util.LinkedHashSet;
import java.util.Arrays;

public class SetFactory {
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T> LinkedHashSet <T> createLinkedHashSet (Object ... a_items) {
  LinkedHashSet <T> l_linkedHashSet = new LinkedHashSet <T> ();
  if (a_items != null) {
   Arrays.stream (a_items).forEach (a_item -> {
    l_linkedHashSet.add ( (T) a_item);
   });
  }
  return l_linkedHashSet;
 }
 
 // This doesn't accept any array as an expandable item; pass Iterables instead.
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T> LinkedHashSet <T> createLinkedHashSetExpandingItems (Object ... a_items) {
  LinkedHashSet <T> l_linkedHashSet = new LinkedHashSet <T> ();
  if (a_items != null) {
   Arrays.stream (a_items).forEach (a_item -> {
    if (a_item instanceof Iterable) {
     for (Object l_element: (Iterable) a_item) {
      l_linkedHashSet.add ( (T) l_element);
     }
    }
    else {
     l_linkedHashSet.add ( (T) a_item);
    }
   });
  }
  return l_linkedHashSet;
 }
}

package thebiasplanet.coreutilities.collectionshandling;

import java.util.Map;
import java.util.LinkedHashMap;

public class MapFactory {
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T, U> LinkedHashMap <T, U> createLinkedHashMap (Object ... a_alternatelyKeyAndValue) {
  LinkedHashMap <T, U> l_linkedHashMap = new LinkedHashMap <T, U> ();
  if (a_alternatelyKeyAndValue != null) {
   T l_key = null;
   for (Object l_keyOrValue: a_alternatelyKeyAndValue) {
    if (l_key == null) {
     l_key = (T) l_keyOrValue;
    }
    else {
     l_linkedHashMap.put (l_key, (U) l_keyOrValue);
     l_key = null;
    }
   }
  }
  return l_linkedHashMap;
 }
 
 @SafeVarargs
 @SuppressWarnings("unchecked")
 public static <T, U> LinkedHashMap <T, U> createLinkedHashMapExpandingItems (Object ... a_alternatelyKeyAndValueOrMaps) {
  LinkedHashMap <T, U> l_linkedHashMap = new LinkedHashMap <T, U> ();
  if (a_alternatelyKeyAndValueOrMaps != null) {
   T l_key = null;
   for (Object l_keyOrValueOrMap: a_alternatelyKeyAndValueOrMaps) {
    if (l_keyOrValueOrMap instanceof Map) {
     for (Map.Entry <?, ?> l_mapEntry: ((Map <?, ?>) l_keyOrValueOrMap).entrySet ()) {
      l_linkedHashMap.put ((T) l_mapEntry.getKey (), (U) l_mapEntry.getValue ());
      l_key = null;
     }
    }
    else {
     if (l_key == null) {
      l_key = (T) l_keyOrValueOrMap;
     }
     else {
      l_linkedHashMap.put (l_key, (U) l_keyOrValueOrMap);
      l_key = null;
     }
    }
   }
  }
  return l_linkedHashMap;
 }
}

package thebiasplanet.coreutilities.reflectionshandling;

import java.util.List;
import java.util.LinkedHashMap;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class ReflectionHandler {
 // Fields are stored in the order of from the super class to the sub class and from the implemented interfaces to the implementing class.
 // Duplicate named fields are replaced by the sub class's.
 // If all the gotten fields are static, set a_instance to be null.
 public static LinkedHashMap <String, Object> getFieldNamesAndValues (Class <?> a_class, Object a_instance, boolean a_publicOnly, boolean a_setAccessible, List <Class <?>> a_assignableClassesOfFields, List <Class <?>> a_notAssignableClassesOfFields, boolean a_recursive) throws IllegalAccessException {
  LinkedHashMap <String, Object> l_fieldNameToValueMap = new LinkedHashMap <String, Object> ();
  if (a_recursive) {
   LinkedHashMap <String, Object> l_superClassOrImplementedInterfaceFieldNameToValueMap = null;
   Class<?> l_superClass = a_class.getSuperclass();
   if (l_superClass != null) {
    l_superClassOrImplementedInterfaceFieldNameToValueMap = getFieldNamesAndValues (l_superClass, a_instance, a_publicOnly, a_setAccessible, a_assignableClassesOfFields, a_notAssignableClassesOfFields, true);
    l_fieldNameToValueMap.putAll (l_superClassOrImplementedInterfaceFieldNameToValueMap);
   }
   Class<?>[] l_implementedInterfaces = null;
   l_implementedInterfaces = a_class.getInterfaces();
   for (Class<?> l_implementedInterface: l_implementedInterfaces) {
    l_superClassOrImplementedInterfaceFieldNameToValueMap = getFieldNamesAndValues (l_implementedInterface, null, a_publicOnly, a_setAccessible, a_assignableClassesOfFields, a_notAssignableClassesOfFields, true);
    l_fieldNameToValueMap.putAll (l_superClassOrImplementedInterfaceFieldNameToValueMap);
   }
  }
  Field [] l_fields = l_fields = a_class.getDeclaredFields ();
  FieldsLoop: for (Field l_field: l_fields) {
   int l_fieldModifiers = l_field.getModifiers ();
   if (a_publicOnly && !Modifier.isPublic (l_fieldModifiers)) {
    continue;
   }
   if (a_instance == null && !Modifier.isStatic (l_fieldModifiers)) {
    continue;
   }
   if (a_assignableClassesOfFields != null) {
    boolean l_assignable = false;
    for (Class <?> l_assignableClassOfFields: a_assignableClassesOfFields) {
     if (!l_assignableClassOfFields.isAssignableFrom (l_field.getType ())) {
      l_assignable = true;
      break;
     }
    }
    if (!l_assignable) {
     continue;
    }
   }
   if (a_notAssignableClassesOfFields != null) {
    for (Class <?> l_notAssignableClassOfFields: a_notAssignableClassesOfFields) {
     if (l_notAssignableClassOfFields.isAssignableFrom (l_field.getType ())) {
      continue FieldsLoop;
     }
    }
   }
   boolean l_isAccessible = l_field.isAccessible ();
   if (a_setAccessible && !l_isAccessible) {
    l_field.setAccessible (true);
   }
   l_fieldNameToValueMap.put (l_field.getName (), l_field.get (a_instance));
   if (a_setAccessible && !l_isAccessible) {
    l_field.setAccessible (l_isAccessible);
   }
  }
  return l_fieldNameToValueMap;
 }
}

package thebiasplanet.coreutilities.constantsgroups;

// ## Comments that start with the mark, ##, are instructions for extending this class, where XXX is the class name of the extended class.
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import thebiasplanet.coreutilities.reflectionshandling.ReflectionHandler;
import thebiasplanet.coreutilities.collectionshandling.ListFactory;
import thebiasplanet.coreutilities.collectionshandling.SetFactory;
import thebiasplanet.coreutilities.collectionshandling.MapFactory;

// ## The class has to be public, extend BaseConstantsGroup, and implement some constants group interfaces of T constants
abstract public class BaseEnumerableConstantsGroup <T> {
 private LinkedHashMap <String, T> i_nameToValueMap;
 // ## Define this.
 // ## public static final XXX c_instance = new XXX ();
 
 // ## Define the private constructor.
 
 protected BaseEnumerableConstantsGroup () {
  try {
   i_nameToValueMap = MapFactory. <String, T>createLinkedHashMapExpandingItems (ReflectionHandler.getFieldNamesAndValues (this.getClass (), null, true, false, null, ListFactory. <Class <?>>createArrayList (this.getClass ()), true));
  }
  catch (IllegalAccessException l_exception) {
   throw new RuntimeException (l_exception);
  }
 }
 
 public LinkedHashSet <String> getNames () {
  return SetFactory. <String>createLinkedHashSetExpandingItems (i_nameToValueMap.keySet ());
 }
 
 public ArrayList <T> getValues () {
  return ListFactory. <T>createArrayListExpandingItems (i_nameToValueMap.values ());
 }
}

実は、ソースファイル群(テスト用のものを含む)はここにある。これらのプロジェクトはGradleまたはAntを使っている。開発環境を構築する方法についての記述は、Linux用はここWindows用はここにある(本件に関係ない記述は無視すればよい)。zipファイルを、ディレクトリ構造を維持して展開し、各プロジェクトにカレントディレクトリを移して、コマンド、'gradle'または'ant'を実行すれば、プロジェクトがビルドされるはずだ。テストするには、'coreUtilitiesTestToDisclose'ディレクトリに移動して、コマンド、'gradle test'または'ant test'を実行すればいいはずだ。

本文 END

<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>

2017年10月7日土曜日

4: クラスメンバーアクセス解決の厳密なルールを復習しよう

<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>

本文 START

任意の表現についてどのクラスメンバーがアクセスされるかを決定する、例外なく正しいルールを復習しよう

話題: Javaプログラミング言語

以下の理解は厳密には正しくない

-Hypothesizer

クラスメンバーアクセス解決のルールについての私の理解は正確ではなかった。

-Rebutter

君の理解はどのようなものだったのか?

-Hypothesizer

私の理解は、インスタンスメソッドについては、インスタンスタイプが即、呼ばれるメソッドを決めるというものだった。

-Rebutter

もっと説明してくれ。

-Hypothesizer

例えば、ベースクラス、'BaseClass'とサブクラス、'ExtendedClass'があるとする。

-Rebutter

いいだろう。

-Hypothesizer

'ExtendedClass'のインスタンスは、タイプ、'BaseClass'の変数で参照することができる。

-Rebutter

その変数を'l_anObject'と名付けよう。

-Hypothesizer

この変数を使ってあるメソッド、例えば'aMethod'を'l_anObject.aMethod ()'のように呼んだとき、私の理解は、ただ、「インスタンスタイプ、'ExtendedClass'だけが問題であり、変数タイプは関係ない」というものだった。

-Rebutter

ああ、それは単純に過ぎるだろう、そういう単純化した理解は快いものではあるが。

-Hypothesizer

「変数タイプが何であろうが、インスタンスがアクセスされるわけであり、そのインスタンスだけが問題だ。だから、'ExtendedClass'が指定した名前のメソッドを持っていれば、それが、呼ばれるものであろう」と私は思っていた。

-Rebutter

それは、一つの理想かもしれないが、Javaプログラム言語はそのように作られていない。

-Hypothesizer

以下の例は、私の理解がいかに間違っていたかを示している。

@Java Source Code
package test.classmemberaccessresolutiontest1;

public class Test1BaseClass {
 void aMethod () {
  System.out.println ("From Test1BaseClass.");
 }
}

package test.classmemberaccessresolutiontest2;

import test.classmemberaccessresolutiontest1.Test1BaseClass;

public class Test1ExtendedClass extends Test1BaseClass {
 public void aMethod () {
  System.out.println ("From Test1ExtendedClass.");
 }
}

package test.classmemberaccessresolutiontest1;

import test.classmemberaccessresolutiontest2.Test1ExtendedClass;

public class Test1TestClass {
 public void test () {
  Test1BaseClass l_anObject = new Test1ExtendedClass ();
  l_anObject.aMethod ();
 }
}
-Rebutter

君の不正確な理解によると、'Test1ExtendedClass'のメソッドが呼ばれることになるだろうが、Javaはそのようには動かない。

-Hypothesizer

別の直感的であるが、不正確な理解は、メソッドを継承することを、サブクラスが、継承したメソッドを自分のものとして持っているかのようのに思い描くイメージだ。

例えば、'Test2BaseClass'があるメソッド、'printValueA'を持っており、'Test2ExtendedClass'が'Test2BaseClass'を、以下のように拡張するとする。

@Java Source Code
package test.classmemberaccessresolutiontest1;

public class Test2BaseClass {
 protected static String s_valueA = "initial value A";
 
 public static void printValueA () {
  System.out.println (String.format ("The value A is \"%s\".", s_valueA));
 }
}

package test.classmemberaccessresolutiontest1;

public class Test2ExtendedClass extends Test2BaseClass {
 protected static String s_valueA = "overwritten value A";
}

package test.classmemberaccessresolutiontest1;

public class Test2TestClass {
 public void test () {
  Test2ExtendedClass.printValueA ();
 }
}
-Rebutter

'Test2ExtendedClass'は'printValueA'を継承したが、それでも、'printValueA'は、'Test2BaseClass'のものであって、'Test2ExtendedClass'のものではない。あたかも、'Test2ExtendedClass'が以下のようになったかのようなイメージを持ったら、

@Java Source Code
package test.classmemberaccessresolutiontest1;

public class Test2ExtendedClass {
 protected static String s_valueA = "overwritten value A";

 public static void printValueA () {
  System.out.println (String.format ("The value A is \"%s\".", s_valueA));
 }
}

それは間違いだ。

-Hypothesizer

上にイメージしたような状態になっているのであれば、上書きされた値がプリントされるだろうが、実際にはそうではない。

実際、世俗の世界では、相続(インヘリタンス)は、相続者が遺産を所持することになることを意味するが、Javaでは、相続(インヘリタンス)は、相続者に遺産が見えて、相続者が遺産を使えることを意味するに過ぎない。

-Rebutter

それらの誤った理解は、直感的なイリュージョンだが、我々がなぜそうしたイリュージョンに陥りがちかは理解できる。おそらく、そうしたイリュージョンは、望まれていたが、プログラム言語の実装上の諸事情のために実現されなかったものなのだろう。

そこで、クラスメンバーアクセス解決の厳密なルールを復習しよう

-Hypothesizer

上記の理解はシンプルだが、一部のケースでは問題を起こす。ここでは、厳密なルールを復習したい。直感的ではあるが例外をはらんだ、厳密ではないイメージではなく。

-Rebutter

試してみよう。

-Hypothesizer

我々が求めるのは、任意の表現に対して、どのクラスメンバーがアクセスされるかを決定するルールだ。

例えば、上の表現、"l_anObject.aMethod ()"に対して、'Test1ExtendedClass'の'aMethod'がアクセスされるのか、それとも'Test1BaseClass'の'aMethod'がアクセスされるのか?

-Rebutter

いいだろう。

第1に、表現中の、メンバー名への修飾を特定しなければならない

-Hypothesizer

第1に、表現中の、メンバー名への修飾を見なければならない。

表現が'l_anObject.aMethod ()'の時、'l_anObject'が、メンバー名、'aMethod'への修飾だ。

どんな場合も、何らかの修飾がなければならない。というのも、ただ'aMethod ()'とだけ言ったのでは、コンパイラもランタイムも、それがどの'aMethod'なのか判断できないから。

修飾が明示的に述べられていない場合は、暗黙的な修飾がある。その場合は、その暗黙的修飾を知らなければならない。

-Rebutter

修飾が暗黙である場合、誤判断をよりしがちなようだ。修飾が見えないので、事態を奔放に想像しがちだ。

-Hypothesizer

そう。上の、'Test2BaseClass'中の'printValueA'の中で、's_valueA'への暗黙の修飾は'Test2BaseClass'だ。修飾が明示的に述べられていれば、誤判断もしないだろうが、修飾が見えないので、'Test2ExtendedClass'の値がアクセスされるのではないかという期待を抱きがちだ。

-Rebutter

だから、修飾が暗黙である場合、その修飾を意識することが助けになる。

-Hypothesizer

表現がスタティックコンテキストの中にある場合は、暗黙の修飾は、その表現を包含するクラスであり、表現がインスタンスコンテキストの中にある場合は、暗黙の修飾は、'this'だ。

'this'が使える任意の場所がインスタンスコンテキストであり、その他がスタティックコンテキストだ。例えば、スタティックメソッドの中は、スタティックコンテキストであり、インスタンスメソッドの中は、インスタンスコンテキストだ。

-Rebutter

オーケー。

第2に、修飾のクラスを知らなければならない

-Hypothesizer

第2に、修飾のクラスを知らなければならない。修飾のクラスはインスタンスクラスのことではないことに注意しよう。

-Rebutter

'l_anObject'の場合、修飾のクラスは、'Test1BaseClass'であって、'Test1ExtendedClass'(インスタンスクラス)ではない。

-Hypothesizer

上で学んだように、我々は、インスタンスクラスへ即ジャンプするということはできない。修飾のクラスが常にスターティングポイントだ。

-Rebutter

いいだろう。

-Hypothesizer

修飾は、ただの変数やただのクラスよりも複雑なものかもしれないが、常に1つのクラスを持っている。

-Rebutter

例えば、修飾は、'(new Test1ExtendedClass ())'や'((Test1BaseClass) new Test1ExtendedClass ())'といった、一種のObjectかクラスを戻す任意の表現かもしれない。

-Hypothesizer

そう。その第1の例のクラスは'Test1ExtendedClass'であり、第2の例のクラスは'Test1BaseClass'だ。私の言っている意味は分かるだろう。

-Rebutter

分かる。

-Hypothesizer

'this'のクラスは、表現を包含するクラスであることに注意しよう。

-Rebutter

ここでも、我々は、インスタンスクラスについて話しているのではなく、変数タイプについて話している。

-Hypothesizer

'this'という変数は、我々自身で定義したものではないが、我々は、それが以下のように暗黙に定義されていると考えるべきだ。

@Java Source Code
class XXX {
 private XXX this;
}
-Rebutter

そのように思われる。'this'は、サブクラスのインスタンスを参照できるが、'this'の変数タイプは、サブクラスへ、魔法のように変化したりはしない。変数タイプは'XXX'として固定されている。

第3に、修飾がクラスを表わすのであれば、そのクラスのメンバーがアクセスされる(その場合の解決は完結する)

-Hypothesizer

第3に、修飾がクラスを表わす(インスタンスではなく)のであれば、そのクラスのメンバーがアクセスされる(もちろん、メンバーはそのクラスそのもので定義されておらず、スーパークラスから継承されているかもしれないが、私の言う意味は分かるだろう)。

-Rebutter

修飾が'Test2BaseClass'の場合、それは、クラスを表わしている。対して、修飾が'l_anObject'の場合、それは、インスタンスを表している。

-Hypothesizer

そうだ。

修飾がクラスの場合、もちろん、指定したメンバーはスタティックでなければならない。さもなければ、コンパイルエラーが起きる。

-Rebutter

見るべきインスタンスが存在しないので、メンバーを探す場所は、修飾のクラスそのものしかない。

第4に、メンバーが、修飾のクラスからインスタンスクラスへ向けてどのようにオーバーライドされているかを知らなければならない

-Hypothesizer

今は、修飾がインスタンスを表している場合のみを扱っている。

-Rebutter

他の場合については、解決は、この前のセクションで既に完結している。

-Hypothesizer

メンバーが、修飾のクラスから(もちろん、メンバーは、修飾のクラスで定義されておらず、スーパークラスから継承されているかもしれない)インスタンスクラスへ向けてどのようにオーバーライドされているかを我々は知らなければならない。

-Rebutter

「オーバーライド」という用語の定義について、若干の混乱があるかもしれない。

-Hypothesizer

ああ、あるかもしれない。「オーバーライド」の公式な定義が何なのか私は知らないが、「オーバーライド」で我々が何を意味しているかを明確にしよう。

-Rebutter

すると、我々の定義は、公式なものや他の誰かのものとは異なっているかもしれない。

-Hypothesizer

かもしれない。しかし、我々は我々の定義を使う。というのも、他の定義(そういうものがもしあるとして)は我々の目的の役に立たないから。

第1に、我々は、シャドーイングは、オーバーライドに含まない。

-Rebutter

すると、フィールドはシャドーイングされるのであって、決して、オーバーライドされることはない。

-Hypothesizer

そうだ。

第2に、サブクラスから見えないメソッドは、決してオーバーライドされない。

-Rebutter

例えば、以下を考えてみよう。

@Java Source Code
public class ClassA {
 private void aMethod () {
  System.out.println ("From ClassA.");
 }
}

public class ClassB extends ClassA {
 public void aMethod () {
  System.out.println ("From ClassB.");
 }
}

public class ClassC extends ClassB {
 public void aMethod () {
  System.out.println ("From ClassC.");
 }
}

'ClassA'のメソッドは、'ClassB'からは見えないので、オーバーライドされているのではない。対して、'ClassB'のメソッドはオーバーライドされている。

-Hypothesizer

'ClassB'は、'ClassA'のメソッドとは無関係の新たなメソッドを作ったのだ。

-Rebutter

分かった。

-Hypothesizer

どんなフィールドもどんなスタティックメソッドもオーバーライドされることはないことを知っておく必要がある。

-Rebutter

インスタンスメソッドだけがオーバーライドされることができる。

-Hypothesizer

修飾のクラスからインスタンスクラスへのオーバーライドの連続するリンクの最後のクラスを特定したら、その最後のクラスのメンバーがアクセスされる。

-Rebutter

すると、リンクは修飾のクラスから始めて連続していなければならないわけだ。

-Hypothesizer

それが、まず最初に、修飾のクラスを見なければならない理由だ。インスタンスクラスを即見て、インスタンスクラスのメソッドがアクセスされると判断してはいけない。

-Rebutter

上の例では、インスタンスクラスが'ClassC'である場合、インスタンスクラスのメソッドは'ClassB'のメソッドをオーバーライドしているが、'( (ClassA) new ClassC ()).aMethod ()'という表現に対して'ClassC'のメソッドが呼ばれると言うことはできない。

-Hypothesizer

そうだ。

実際には、修飾のクラスのメソッドがパブリックかプロテクテッドの場合、シグネチャが同じサブクラスのメソッドは修飾のクラスのメソッドをオーバーライドしていることが保証される。なぜなら、アクセス権限は、継承のチェーンの中で弱めることができないから。

-Rebutter

だから、オーバーライドのリンクが途中のどこかで断ち切られることはない。

-Hypothesizer

しかし、修飾のクラスのメソッドのアクセス権限が「未指定」(同じパッケージからのみ見えることを意味する)の場合、インスタンスクラスは、修飾のクラスと同じパッケージ内にいなくても、修飾のクラスのメソッドをオーバーライドしているかもしれない。

-Rebutter

例えば、以下を考えてみよう。

@Java Source Code
package test.classmemberaccessresolutiontest1;

public class Test3BaseClass {
 void aMethod () {
  System.out.println ("From Test3BaseClass.");
 }
}

package test.classmemberaccessresolutiontest1;

public class Test3ExtendedClass extends Test3BaseClass {
 public void aMethod () {
  System.out.println ("From Test3ExtendedClass.");
 }
}

package test.classmemberaccessresolutiontest2;

import test.classmemberaccessresolutiontest1.Test3ExtendedClass;

public class Test3ExtendedExtendedClass extends Test3ExtendedClass {
 public void aMethod () {
  System.out.println ("From Test3ExtendedExtendedClass.");
 }
}

package test.classmemberaccessresolutiontest1;

import test.classmemberaccessresolutiontest2.Test3ExtendedExtendedClass;

public class Test3TestClass {
 public void test() {
  ( (Test3BaseClass) new Test3ExtendedExtendedClass ()).aMethod ();
 }
}

結果は以下のとおりだ。

@Output
From Test3ExtendedExtendedClass.
-Hypothesizer

'Test3ExtendedClass'がメソッドをパブリックにして、'Test3ExtendedExtendedClass'から見えるようにしたことが重要だ。そのため、'aMethod'は'Test3BaseClass'から'Test3ExtendedExtendedClass'まで連続的にオーバーライドされている。

-Rebutter

したがって、どのメソッドが呼ばれるかを知るには、中間にいるサブクラスも見なければならない。修飾のクラスとインスタンスクラスだけではなく。

-Hypothesizer

他方で、「インスタンスクラスは、修飾のクラスと同じパッケージ内にいても修飾のクラスのメソッドをオーバーライドしていないかもしれない」と私は思ったのだが、なぜだか、そうではなさそうだ。

-Rebutter

ふむ?. . . なぜだ?私もそう思った。

-Hypothesizer

この例を考えてみよう。

@Java Source Code
package test.classmemberaccessresolutiontest1;

public class Test4BaseClass {
 void aMethod () {
  System.out.println ("From Test4BaseClass.");
 }
}

package test.classmemberaccessresolutiontest2;

import test.classmemberaccessresolutiontest1.Test4BaseClass;

public class Test4ExtendedClass extends Test4BaseClass {
 public void aMethod () {
  System.out.println ("From Test4ExtendedClass.");
 }
}

package test.classmemberaccessresolutiontest1;

import test.classmemberaccessresolutiontest2.Test4ExtendedClass;

public class Test4ExtendedExtendedClass extends Test4ExtendedClass {
 public void aMethod () {
  System.out.println ("From Test4ExtendedExtendedClass.");
 }
}

package test.classmemberaccessresolutiontest1;

public class Test4TestClass {
 public void test() {
  ( (Test4BaseClass) new Test4ExtendedExtendedClass ()).aMethod ();
 }
}

結果は以下のとおりだ。

@Output
From Test4ExtendedExtendedClass.

「未指定」アクセス権限メソッドの奇妙なオーバーライド

-Rebutter

ふむ?ふーむ、一体何が起きているのだろう?

-Hypothesizer

「'Test4BaseClass'の'aMethod'は'Test4ExtendedClass'から見えないから、'Test4ExtendedClass'はそれをオーバーライドしていない。'Test4ExtendedClass'の'aMethod'は、'Test4BaseClass'の'aMethod'とは関係ない新しく作られたメソッドだ。」と私は思ったんだが。

-Rebutter

そのはずだ。

-Hypothesizer

そして、「'Test4ExtendedExtendedClass'は'Test4ExtendedClass'の'aMethod'を見るから、'Test4ExtendedClass'の'aMethod'をオーバーライドしている。だから、'Test4BaseClass'の'aMethod'はオーバーライドされていない。結果は、'From Test4BaseClass.'のはずだ。」と思ったわけ。

-Rebutter

ふーむ、何が起きているか見え始めてきた。

-Hypothesizer

何が起きているのだ?

-Rebutter

'Test4BaseClass'の'aMethod'は、'Test4ExtendedClass'の'aMethod'によってシャドーイングされていないので、'Test4ExtendedExtendedClass'から見えている。

-Hypothesizer

ふむ?. . . ふーむ、それはそうだ。'Test4ExtendedClass'が行なったのはシャドーイングではない。見えないものをシャドーイングはできない。実際、'Test4BaseClass'の'aMethod'と'Test4ExtendedClass'の'aMethod'は全くの別物であって、両方が'Test4ExtendedExtendedClass'から同一のシグネチャで見える。

Javaはそういう事態を避けるために多重継承を禁止しているが、抜け穴があるようだ。

-Rebutter

それでは、どちらのメソッドを'Test4ExtendedExtendedClass'はオーバーライドしているのか?

-Hypothesizer

上の'Test4TestClass'を以下のように変えてみよう。

@Java Source Code
package test.classmemberaccessresolutiontest1;

import test.classmemberaccessresolutiontest2.Test4ExtendedClass;

public class Test4TestClass {
 public void test() {
  ( (Test4BaseClass) new Test4ExtendedExtendedClass ()).aMethod ();
  ( (Test4ExtendedClass) new Test4ExtendedExtendedClass ()).aMethod ();
 }
}

結果は以下のとおりだ。

@Output
From Test4ExtendedExtendedClass.
From Test4ExtendedExtendedClass.
-Rebutter

うーん、'Test4ExtendedExtendedClass'は両方をオーバーライドしているようだ。

'Test4ExtendedExtendedClass'がメソッドを'public'と宣言しなかったらどうなるのか?

-Hypothesizer

コンパイラは以下のエラーを出す。

@Output
error: aMethod() in Test4ExtendedExtendedClass cannot override aMethod() in Test4ExtendedClass
attempting to assign weaker access privileges; was public
-Rebutter

ははあ . . .、両方をオーバーライドしなければならない。

'Test4ExtendedExtendedClass'からオーバーライドを除いて、'test'メソッドの最後に以下を追加してみよう。

@Java Source Code
  (new Test4ExtendedExtendedClass ()).aMethod ();

この行の出力はどうなるか?

-Hypothesizer

実は、以下のとおりだ。

From Test4ExtendedClass.

-Rebutter

ふーむ . . .、'Test4ExtendedClass'の'aMethod'が優先しているようだ。しかし、どちらにしても、'Test4BaseClass'の'aMethod'もオーバーライドされているという事実は変わらないようだ。

オーバーライドのリンクが途中で断ち切られている例

-Hypothesizer

オーバーライドのリンクが途中で断ち切られている例を挙げよう。

@Java Source Code
package test.classmemberaccessresolutiontest1;

public class Test5BaseClass {
 void aMethod () {
  System.out.println ("From Test5BaseClass.");
 }
}

package test.classmemberaccessresolutiontest1;

public class Test5ExtendedClass extends Test5BaseClass {
 void aMethod () {
  System.out.println ("From Test5ExtendedClass.");
 }
}

package test.classmemberaccessresolutiontest2;

import test.classmemberaccessresolutiontest1.Test5ExtendedClass;

public class Test5ExtendedExtendedClass extends Test5ExtendedClass {
 void aMethod () {
  System.out.println ("From Test5ExtendedExtendedClass.");
 }
}

package test.classmemberaccessresolutiontest1;

import test.classmemberaccessresolutiontest2.Test5ExtendedExtendedClass;

public class Test5TestClass {
 public void test() {
  ( (Test5BaseClass) new Test5ExtendedExtendedClass ()).aMethod ();
 }
}

結果は以下のとおりだ。

@Output
From Test5ExtendedClass.
-Rebutter

結果は、我々が予測したとおりだ。'Test5ExtendedClass'の'aMethod'は、'Test5ExtendedExtendedClass'によってオーバーライドされていない。'Test5ExtendedExtendedClass'からは見えないからだ。

このように、我々は、修飾のクラスからスタートして、インスタンスクラスへ向けてオーバーライドのリンクをたどらなければならない。

'@Override'アノテーションを使うべき

-Hypothesizer

我々は意図的に'@Override'アノテーションを使用しなかった(トラップを事前に明かさないため)が、我々は、ミスを避けるため、'@Override'を使うべきだ。

本文 END

<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>