Showing posts with label Java Tips. Show all posts
Showing posts with label Java Tips. Show all posts

2017-12-03

7: Cheating at Generics

<The previous article in this series | The table of contents of this series |

Main Body START

If We Are Being Troubled with Unhandiness of Generics, Cheating Might Be an Option

About: Java Programming Language

Well-Known Unhandiness of Generics and Understanding that Generic Types Are Fantasies That Only Source Codes See

Here are -Hypothesizer and -Rebutter sitting in front of a computer screen in a room on a brook among some mountains on the Bias planet.

-Hypothesizer

Generics is very handy except when it's unhandy.

-Rebutter

Ah, I can guess, but I won't. You should elaborate.

-Hypothesizer

Before I elaborate, let's clarify our terminology. As it's cumbersome to formulate accurate definitions of terms, let's think in an example.

-Hypothesizer writes this in an editor on the computer screen.

@Java Source Code
public class AGenericClass <T> {
 private T i_t;
 private List <T> i_listOfT;
 private List <String> i_listOfStrings;
 
 public <U> U aGenericMethod (U a_u) {
  return a_u;
 }
}

'AGenericClass <T>' is a generic class; '<U> aGenericMethod' is a generic method; 'T' and 'U' are type parameters; 'T' of 'i_t', 'List <T>', 'List <String>', 'U' as the return type of '<U> aGenericMethod', and 'U' of 'a_u' are parameterized types; 'AGenericClass' is a raw class.

-Rebutter

So, also 'List <T>' without 'T' determined as a concrete class is a parameterized type, in our terminology.

-Hypothesizer

As I'm reading a reference document, 'Lesson: Generics (Updated)' of 'The Java™ Tutorials', . . .

-Hypothesizer opens the document in a Web browser on the computer screen.

-Hypothesizer

. . . it calls 'List <String>' a parameterized type, but it doesn't mention what should 'List <T>' be called. We are calling 'List <T>' a parameterized type because, for our descriptions here, we seem to be able to treat them the same way. That is, we are concerned with whether these casts are allowed.

-Hypothesizer writes this in the 'aGenericMethod' method above.

@Java Source Code
  Object l_objectAsString = "string1";
  Object l_objectAsListOfStrings = new ArrayList <String> ();
  i_t = (T) l_objectAsString;
  i_listOfTs = (List <T>) l_objectAsListOfStrings ;
  i_listOfStrings = (List <String>) l_objectAsListOfStrings;

-Rebutter

So, that's the reason why we call also 'T' of 'i_t' a parameterized type . . .

-Hypothesizer

Most people may not call 'T' of 'i_t' a parameterized type, but certainly, it's the type of 'i_t', and it's parameterized . . .

-Rebutter scan the reference document.

-Rebutter

As far as I scan the reference document, it doesn't seem to mention whether casts of '(T)' or '(List <T>)' is allowed . . .

-Hypothesizer

Ah, I think, some descriptions in the reference document are confusing. Didn't you interpret them as though we "Cannot Use Casts with Parameterized Types" and "Typically," we "cannot cast to a parameterized type unless it is parameterized by unbounded wildcards"?

-Rebutter

Being asked whether I interpreted so, they are literal expressions in the reference document . . .

-Hypothesizer

But that's your misinterpretation: we can use casts except when they are definitely illegitimate. In fact, we can do those casts above, although, certainly, we get warnings. To say more, we can even cast 'a_u' to 'T'.

-Rebutter

Well, the reference document says of the compile error of this.

@Java Source Code
List <Integer> li = new ArrayList <> ();
List <Number> ln = (List <Number>) li;

But that isn't because casts with parameterized types aren't allowed, but because 'List <Integer>' isn't any sub class or super class of 'List <Number>'.

-Hypothesizer

That's the same argument with the one we did for arrays. Any 'List <Integer>' instance isn't a kind of 'List <Number>' because the definition of 'List <Number>' is 'a list that can accept any and only Number instances', which any 'List <Integer>' doesn't fulfill because it doesn't accept some Number instances such as Double instances.

-Rebutter

So, that cast is definitely illegitimate, which is the reason why that cast isn't allowed, not that "Cannot Use Casts with Parameterized Types".

-Hypothesizer

To make things clear, we get warnings for our casts above because of so-called 'type-erasure', which means that all the type parameters are replaced with Object (if they are unbounded) or their bound types (if not), after the compilation.

-Rebutter

So, 'type erasure' really means 'type replacement'.

-Hypothesizer

Of course. The type of a variable can't be just erased: the variable needs to be of a type.

-Rebutter

A cast is meant to be a two-phase operation: at the compile time, the compile error is suppressed, and at the run time, the instance is checked whether it conforms to the cast type.

-Hypothesizer

But for any parameterized type, the run time check can't be done properly because the parameterized type isn't preserved as we hope it to be at the run time: a cast, '(T)', has been really reduced to '(Object)', which is meaningless.

-Rebutter

Anyway, we can use casts with parameterized types if we don't need any run time check.

-Hypothesizer

In fact, there are such cases: as the compiler doesn't follow intricate logics to confirm type safety, the compiler doesn't know it's type safe, but the programmer knows it's type safe, at least practically.

By the way, the description in the reference document about "Cannot Create Arrays of Parameterized Types" is also confusing.

-Rebutter

Ah, I say, the real culprit is allowing the cast to 'Object []': as we discussed before, that cast shouldn't be allowed because 'List <Integer> []' isn't any kind of 'Object []' . . .

-Hypothesizer

Instantiating an array of a parameterized type isn't really a problem at all, but in order to gloss the misdeed of allowing such illegitimate casts, it is being prohibited . . .

-Rebutter

Well, prohibiting it is unreasonable, but actually, instantiating an array of a parameterized type isn't necessary or meaningful.

-Hypothesizer

That's true. As an instance of an array of a parameterized type is really an instance of an array of the raw type or Object because of 'type erasure', these two lines are the same in the run time world.

-Hypothesizer writes these on the editor.

@Java Source Code
List <String> [] l_listsOfStringsArray = new List <String> [1]; // Causes a compile error.

@Java Source Code
List <String> [] l_listsOfStringsArray = new List [1];

Or these two lines are the same in the run time world.

-Hypothesizer writes these on the editor.

@Java Source Code
T [] l_tsArray = new T [1]; // Causes a compile error.

@Java Source Code
T [] l_tsArray = (T []) new Object [1];

-Rebutter

We should note that the latter are allowed.

-Hypothesizer

Yes. "Cannot Create Arrays of Parameterized Types" means 'cannot instantiate arrays of parameterized types', not 'cannot define variables of arrays of parameterized types'.

-Rebutter

So, what are unhandy of generics?

-Hypothesizer

To say succinctly, we can't get the class of any type parameter.

-Rebutter

Ah.

-Hypothesizer

Not being able to create any instance of any type parameter is just a secondary matter of it: if we could get the class, we would be able to create instances from it.

-Rebutter

The well-known end run is to pass the class explicitly.

-Hypothesizer

I know, but it isn't always possible, not to mention that it's cumbersome.

-Rebutter

You mentioned it. . . . Anyway, when is it impossible?

-Hypothesizer

Well, I will show a problem I'm facing right now, which doesn't look so easy to solve.

Before that, note that we can't do 'new T', but can do 'new ArrayList <T> ()'.

-Rebutter

Ah, I can guess why.

-Hypothesizer

Considering 'type erasure', 'new ArrayList <T> ()' becomes really 'new ArrayList ()' anyway whatever 'T' is. So, 'new ArrayList <T> ()' isn't particularly meaningful, but isn't any hindrance for creating the instance.

To state boldly, if we call the world of run time the reality, generic types are fantasies that only source codes see.

A Problem with Generics

The same with the previous scene.

-Hypothesizer

I'm trying to create a method that builds a cascade map, reading a cells block on a spread sheet.

-Rebutter

What do you mean by 'cascade map'?

-Hypothesizer

For example, Map <String, Map <String, Map <String, Map <String, List <String>>>>>.

-Rebutter

Ah, the map's value is a map whose value is a map whose value is a map . . .

-Hypothesizer

And the last value is a List, in this case.

-Rebutter

And the depth of the hierarchy is variable, I guess?

-Hypothesizer

Yes.

So, what will be the method like? . . . Certainly, the logic to build the hierarchy isn't difficult. In fact, if we are fine with the result datum type's being 'Map <String, Object>', it's easy like this (this is just a simplified model: reading the spread sheet, etc. are omitted).

-Hypothesizer opens a source file on the computer screen.

@Java Source Code
package test.genericstest1;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Test2Test {
 private Test2Test () {
 }
 
 public static void main (String [] p_arguments) throws Exception {
  Test2Test.test ();
 }
 
 public static void test () {
  Map <String, Object> l_map = new HashMap <String, Object> ();
  Test2Test.buildMap (l_map, 5);
  System.out.println (l_map);
 }
 
 public static void buildMap (Map <String, Object> a_mapToBeBuilt, int a_remainingDepth) {
  String  l_key = String.format ("key%d", a_remainingDepth - 1);
  Object l_value = null;
  if (a_remainingDepth <= 1) {
   // The depth has to be at least 2 to build a map of this type
   return;
  }
  else {
   if (a_remainingDepth == 2) {
    List <String> l_childMap1 = new ArrayList <String> ();
    l_childMap1.add ("value1");
    l_childMap1.add ("value2");
    l_value = l_childMap1;
   }
   else {
    Map <String, Object> l_childMap2 = new HashMap <String, Object> ();
    Test2Test.buildMap (l_childMap2, a_remainingDepth - 1);
    l_value = l_childMap2;
   }
  }
  a_mapToBeBuilt.put (l_key, l_value);
 }
}

-Hypothesizer

The result is this.

@Output
{key4={key3={key2={key1=[value1, value2]}}}}

-Rebutter

Ah, a recursive method.

-Hypothesizer

But we want the datum in 'Map <String, Map <String, Map <String, Map <String, List <String>>>>>', not in 'Map <String, Object>'. . . . You might wonder why I want to get the map instance in the specific generic type, 'Map <String, Map <String, Map <String, Map <String, List <String>>>>>', not in 'Map <String, Object>', . . .

-Rebutter

I particularly don't . . .

-Hypothesizer

. . . the reason is that it's handy afterwards: otherwise, I would have to cast the Object value to Map at each level of the hierarchy.

So, my first idea was to make it like this.

-Hypothesizer opens another source file on the computer screen.

@Java Source Code
package test.genericstest1;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Test3Test {
 private Test3Test () {
 }
 
 public static void main (String [] p_arguments) throws Exception {
  Test3Test.test ();
 }
 
 public static void test () {
  Map <String, Map <String, Map <String, Map <String, List <String>>>>> l_map = new HashMap <String, Map <String, Map <String, Map <String, List <String>>>>> ();
  Test3Test. <Map <String, Map <String, List <String>>>>buildMap (l_map, 5);
  System.out.println (l_map);
 }
 
 @SuppressWarnings("unchecked")
 public static <U> void buildMap (Map <String, Map <String, U>> a_mapToBeBuilt, int a_remainingDepth) {
  String  l_key = String.format ("key%d", a_remainingDepth - 1);
  Map <String, U> l_value = null;
  if (a_remainingDepth <= 2) {
   // The depth has to be at least 3 to build a map of this type
   return;
  }
  else {
   if (a_remainingDepth == 3) {
    Map <String, U> l_childMap1 = new HashMap <String, U> ();
    String l_childKey = String.format ("key%d", 1);
    List <String> l_grandchildList = new ArrayList <String> ();
    l_grandchildList.add ("value1");
    l_grandchildList.add ("value2");
    l_childMap1.put (l_childKey, (U) l_grandchildList);
    l_value = l_childMap1;
   }
   else {
    /* ??? How can I get 'U' for the next iteration?
    Map <String, Map <String, ?>> l_childMap2 = new HashMap <String, Map <String, ?>> ();
    Test3Test. <?>buildMap (l_childMap2, a_remainingDepth - 1);
    l_value = (Map <String, U>) l_childMap2;
    */
   }
  }
  a_mapToBeBuilt.put (l_key, l_value);
 }
}

-Rebutter

Well, obviously, it is unfinished, having a '???' mark.

-Hypothesizer

Yes. I don't know how to do with them . . .. To make some explanations, 'U' is parameterized because it has to be 'Map <String, Map <String, Map <String, List <String>>>>', 'Map <String, Map <String, List <String>>>', 'Map <String, List <String>>', and 'List <String>', iteratively.

At the '???' mark, I have to extract 'U' for the next iteration from the present 'U', for example 'Map <String, Map <String, List <String>>>' from 'Map <String, Map <String, Map <String, List <String>>>>'. . . . Well, how?

-Rebutter

You know, you can't get such information from 'U'.

-Hypothesizer

I know. Such information is run time information, but at the run time, 'U' is just Object. . . . So, should I explicitly pass the class into the first iteration, and extract the next 'U' class in each iteration from the class passed into the iteration?

-Rebutter

I think, that's meaningless, because at the run time, the class is just the raw Map because of 'type erasure': you won't see it as 'Map <T, Map <T, Map <T, List <T>>>>'. As you said, there isn't such a thing in the reality. . . .

Let's Cheat!

The same with the previous scene.

-Hypothesizer

So, what can I do?

-Rebutter

May I speak freely, sir?

-Hypothesizer

You may.

-Rebutter

You can cheat.

-Hypothesizer

. . . How?

-Rebutter

. . . You don't really understand what you, yourself, said, do you?

-Hypothesizer

Well . . .

-Rebutter

You, yourself, said, "generic types are fantasies". In the reality, there isn't such thing as an instance of 'Map <String, Map <String, List <String>>>'.

-Hypothesizer

Yes, . . . so?

-Rebutter

You don't have to specify the exact type for the next 'U', which is a fantasy. Why don't you just substitute Object for 'U'?

-Hypothesizer

Ah----ha. How didn't I realize that?

-Rebutter

I don't know.

-Hypothesizer

Generic types are solely for compile checks. So, all what I have to do is just to silence the compiler: the result map instance is the same, a raw Map, whether I legitimately (in the world of fantasies) build the map instance or not. . . . So, I can just create an instance of Map <String, Object>, and cast it to 'U'.

-Hypothesizer edits the source file (included in a zip file; the explanation of know to use the zip file is here).

@Java Source Code
package test.genericstest1;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Test3Test {
 private Test3Test () {
 }
 
 public static void main (String [] p_arguments) throws Exception {
  Test3Test.test ();
 }
 
 public static void test () {
  Map <String, Map <String, Map <String, Map <String, List <String>>>>> l_map = new HashMap <String, Map <String, Map <String, Map <String, List <String>>>>> ();
  Test3Test. <Map <String, Map <String, List <String>>>>buildMap (l_map, 5);
  System.out.println (l_map);
 }
 
 @SuppressWarnings("unchecked")
 public static <U> void buildMap (Map <String, Map <String, U>> a_mapToBeBuilt, int a_remainingDepth) {
  String  l_key = String.format ("key%d", a_remainingDepth - 1);
  Map <String, U> l_value = null;
  if (a_remainingDepth <= 2) {
   // The depth has to be at least 3 to build a map of this type
   return;
  }
  else {
   if (a_remainingDepth == 3) {
    Map <String, U> l_childMap1 = new HashMap <String, U> ();
    String l_childKey = String.format ("key%d", 1);
    List <String> l_grandchildList = new ArrayList <String> ();
    l_grandchildList.add ("value1");
    l_grandchildList.add ("value2");
    l_childMap1.put (l_childKey, (U) l_grandchildList);
    l_value = l_childMap1;
   }
   else {
    Map <String, Map <String, Object>> l_childMap2 = new HashMap <String, Map <String, Object>> ();
    Test3Test. <Object>buildMap (l_childMap2, a_remainingDepth - 1);
    l_value = (Map <String, U>) l_childMap2;
   }
  }
  a_mapToBeBuilt.put (l_key, l_value);
 }
}

-Hypothesizer

Let's do the test.

-Hypothesizer opens a terminal on the computer screen, changes the current directory to 'coreUtilitiesTestToDisclose', and executes these commands.

For Gradle:

@bash or cmd Source Code
gradle run -PMAIN_CLASS_NAME="test.genericstest1.Test3Test"

For Ant:

@bash or cmd Source Code
ant run -DMAIN_CLASS_NAME="test.genericstest1.Test3Test"

@Output
{key4={key3={key2={key1=[value1, value2]}}}}

-Hypothesizer

It worked!

-Rebutter

I will tell you one thing that might be shocking to you.

-Hypothesizer

Which is?

-Rebutter

You didn't particularly have to create the new method: you could do with the old method that builds 'Map <String, Object>'.

-Hypothesizer

Huh?

-Rebutter

You can just cast the 'Map <String, Object>' instance to 'Map <String, Map <String, Map <String, Map <String, List <String>>>>>' by '(Map <String, Map <String, Map <String, Map <String, List <String>>>>>) (Map)'.

-Hypothesizer

!

Main Body END

References

  • Oracle and/or its affiliates. (2017). The Java™ Tutorials. Retrieved from https://docs.oracle.com/javase/tutorial/index.html

<The previous article in this series | The table of contents of this series |

2017-11-19

6: Let's Create a Navigable and Elements-Insertable Linked Hash Map

<The previous article in this series | The table of contents of this series | The next article in this series>

Main Body START

We Created a Navigable and Elements-Insertable Linked Hash Map

About: Java Programming Language

The Use of Our Navigable and Elements-Insertable Linked Hash Map

Here are -Hypothesizer and -Rebutter sitting in front of a computer screen in a room on a brook among some mountains on the Bias planet.

-Hypothesizer

This is something I encountered while we created the spread sheet cell editor: I couldn't find any navigable and elements-insertable linked hash map in the Java standard library.

-Rebutter

What do you mean by 'navigable'?

-Hypothesizer

I don't know whether that term is appropriate for what I mean, but I couldn't find any more appropriate term.

-Rebutter

Anyway, what do you mean by it?

-Hypothesizer

I mean that we can get the first element and the last element, and can get the preceding and the following element of any specified element, efficiently.

-Rebutter

Can't we do those things with the standard 'LinkedHashMap'?

-Hypothesizer

We can't. Although it remembers the order of the elements, it doesn't provide such features.

-Rebutter

Ah, so, we can enjoy the benefit of the order of the elements just for iterating the elements from the first element: we can't directly jump to the following element from a specified element.

-Hypothesizer

In short, 'LinkedHashMap' isn't a linked list: it's just a map that preserves the insertion order of the elements internally.

-Rebutter

Do you want to specify an element by the key?

-Hypothesizer

Yes. In fact, in the case of the spread sheet cell editor, we don't need values, but only keys.

-Rebutter

So, you can use a Set instead of a Map.

-Hypothesizer

Actually, yes, but that isn't any issue here: we can't use 'LinkedHashSet' on the same reason with 'LinkedHashMap'. We are talking about Map because it is of wider application: we can use a Map as a Set, ignoring values.

-Rebutter

But if we don't need key-value pairs, can't we use 'LinkedList' instead?

-Hypothesizer

Ah, as far as only features are concerned, we can: 'LinkedList' allows us to get the following element of a specified element like this, where 'l_linkedList' is the 'LinkedList' instance and 'l_specifiedElement' is the specified element.

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

However, the processing isn't efficient: internally, it iterates the elements from the first element to find the specified element.

-Rebutter

Hmm, isn't there significant demand for a 'navigable' linked hash map? I mean, is that the reason why the standard Java library doesn't have any 'navigable' linked hash map?

-Hypothesizer

Probably, there isn't much demand, but is some, for there is such a class, 'org.apache.commons.collections4.map.LinkedMap', in Apache commons.

-Rebutter

. . . Then, why don't we just use that one?

-Hypothesizer

We could, but I felt a reluctance to introduce the whole Jar just for a single class, especially, when that single class doesn't seem to be very difficult to implement.

-Rebutter

Well, . . . that seems a difficult decision.

-Hypothesizer

Besides, we also have to be able to insert elements before any specified element.

-Rebutter

Ah, with the standard 'LinkedHashMap', elements are always added at the tail. How about with 'LinkedMap' of Apache commons?

-Hypothesizer

It seems to be the same.

-Rebutter

I think, there may be another class that has features we want, in Apache commons.

-Hypothesizer

Well, . . .

-Hypothesizer searches the API document of Apache commons on the internet.

Is this the one?

-Rebutter

What is 'this'?

-Hypothesizer

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

-Rebutter scans the Javadoc page of the class.

-Rebutter

Well, at least this seems to have methods we need.

-Hypothesizer

But is this efficient? I don't know.

-Rebutter

In the first place, where did you want to use your navigable and elements-insertable hash map in the cell editor?

-Hypothesizer

To set the tab order of Swing components on the cell editor frame, we have to implement 'getComponentBefore' and 'getComponentAfter' methods of 'java.awt.FocusTraversalPolicy', each of which receives a Swing component, and returns a Swing component that will receive the focus next. I wanted to specify the tab order in a navigable and elements-insertable hash map.

-Rebutter

So that you can get the preceding or the following Swing component of the Swing component passed as the argument?

-Hypothesizer

Yes.

-Rebutter

Why do you have to insert elements before a specified element?

-Hypothesizer

Because I wanted to make the cell editor frame a sub class of a more generic editor frame. The generic editor frame has Swing components such as a search-a-specified-phrase button, and the cell editor has additional cell-editor-specific Swing components such as a go-to-the-left-cell button. We want to specify the tab order in the generic editor frame, and insert some Swing components before a certain Swing component in the tab order, in the cell editor frame.

-Rebutter

Anyway, your Swing components aren't so many as using 'LinkedList' would have any perceivable impact on performance, are they?

-Hypothesizer

Well, actually, . . . they aren't. Practically, there will be no problem in using 'LinkedList' in that case. But, you know, it seems ridiculous to have to iterate from the first element every time. Besides, this isn't the first time I felt a need for a navigable and elements-insertable linked hash map. So, I thought, "Why don't I create one this time?"

-Rebutter

Well, I don't say there is a reason why you shouldn't.

The Design of Our Navigable and Elements-Insertable Linked Hash Map

The same with the previous scene.

-Rebutter

So, what's your design?

-Hypothesizer

It's easy as I said. I just need a hash map instance whose value isn't just a value, but an object that has a value, the key of the preceding element, and the key of the following element. We can jump to a specified element by the key because that's a hash map, and can jump to the preceding element or the following element.

-Rebutter

So, that hash map instance isn't a 'LinkedHashMap' instance.

-Hypothesizer

We don't need 'LinkedHashMap' because anyway we couldn't use the order of elements stored in the 'LinkedHashMap' instance: we have to insert elements at arbitrary positions.

-Rebutter

So, we create entry sets ourselves, based on the preceding keys and the following keys links.

-Hypothesizer

Yes. And of course, we have to maintain the links as elements are put into our map instances, whose logic is the same with in a usual linked list.

-Rebutter

Will we extend the 'HashMap' class?

-Hypothesizer

No. That doesn't seem to work: we want to create 'NavigableLinkedHashMap <T, U>', but we can't extend 'HashMap <T, U>' because we use 'HashMap <T, ValueAndLinks <U, T>>', where 'ValueAndLinks' is the class that has the value, the preceding key, and the following key. We will have to implement the 'java.util.Map' interface. The 'HashMap' instance will be a member of our map.

-Rebutter

So, we will wrap the 'HashMap' instance.

-Hypothesizer

Yes.

The Implementation of Our Navigable and Elements-Insertable Linked Hash Map

The same with the previous scene.

-Hypothesizer

This is the whole code of our navigable and elements-insertable linked hash map.

@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

Ah-ha, certainly, that isn't a big job.

-Hypothesizer

The class is included in this zip file. How to use our zip files is described here.

-Rebutter

. . . And?

-Hypothesizer

And what?

-Rebutter

Shouldn't we at least test whether our navigable and elements-insertable linked hash map has some advantages in performance? I mean, it would be meaningless if there was none . . .

-Hypothesizer

Well, honestly, I didn't doubt about its having at least some advantages, if not much, considering the mechanisms.

-Rebutter

Well, I guess there would be some, but . . .

-Hypothesizer

OK. We will do some tests.

-Hypothesizer writes three classes in the 'coreUtilitiesTestToDisclose' project, taking some time.

-Rebutter

. . . . . . . . . zzz

-Hypothesizer

All right. This is the test class for our navigable and elements-insertable linked hash map.

@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");
 }
}

Of course, I also created one for 'LinkedList', and in fact, also for 'ListOrderedMap', but will refrain from showing them here because the logics are almost the same.

-Hypothesizer opens a terminal, change the current directory to the 'coreUtilitiesTestToDisclose' directory, and executes these commands.

@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"

The results are these.

@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

I said, results are these!

-Rebutter

Huh? . . . What is this?

-Hypothesizer

The results!

-Rebutter

Of course, I know. . . . I asked what each of these numbers means.

-Hypothesizer

I inserted a certain number of elements into a collection instance (of our navigable and elements-insertable linked hash map, 'LinkedList', or 'ListOrderedMap') with each element inserted in front of or after the lastly-inserted element alternately.

-Rebutter

Um? What is your intention?

-Hypothesizer

My intention is always inserting the new element nearly at the middle of the existing list. You know, inserting at the head or the tail would be meaningless as our test: it's obvious that elements can be efficiently added at the head or the tail of the 'LinkedList' instance.

-Rebutter

Of course.

-Hypothesizer

When the number of the elements became 10, 100, 1,000, etc, I outputted the number of the elements and the time that had been spent in inserting the elements so far. And then, I also got the following element of the lastly-inserted element (meaning an element nearly at the middle) from the collection instance, and outputted the time of doing so. And then, I also iterated through all the elements of the collection instance, and outputted the time of doing so.

-Rebutter

So, you did them at each time when the number of the elements became 10 to the power of any natural number?

-Hypothesizer

Yes. So, in short, in the table of 'NavigableLinkedHashMap' (which is our navigable and elements-insertable linked hash map) above, the third row means that 57,166,916 nanoseconds took in inserting 1,000 elements, 1,005 nanoseconds took in getting an element from 1,000 elements, and 17,431,587 nanoseconds took in iterating through 1,000 elements.

-Rebutter

Your explanation doesn't seem particularly 'short', but I understand what you mean.

-Rebutter sees through the 3 tables of numbers, taking a while.

-Rebutter

Hmm, . . . interesting.

Insertions for 'LinkedList' and 'ListOrderedMap' are faster when the numbers of the elements are small, but become unbearably slower when the numbers of the elements become bigger.

-Hypothesizer

In fact, that's the reason we didn't do 1,000,000 elements tests for them: they would take too much time.

-Rebutter

Search times of 'NavigableLinkedHashMap' are smaller, as expected, but the change according to the numbers of the elements is something I couldn't expect. Why are that decrease to 380 and the sudden increase to 57,463, but not increasing at 1,000,000 elements?

-Hypothesizer

Honestly, I have no idea.

-Rebutter

I guess, those numbers aren't very stable from test execution to test execution.

-Hypothesizer

Certainly, they aren't, but that tendency is fairly stable.

-Rebutter

And I notice that iterations of 'NavigableLinkedHashMap' are slower than the other two. Why is that?

-Hypothesizer

Well, there is a difference in that 'NavigableLinkedHashMap' has to create a map entry set as a view: we can't let the iterator iterate through the elements as they are in the map instance.

-Rebutter

I guess there is, but 'NavigableLinkedHashMap' is also slower than 'ListOrderedMap'.

-Hypothesizer

Yes. . . . Maybe just, our code of 'entrySet' method is too crude.

-Rebutter

And I wonder what is the implementation principle of 'ListOrderedMap'.

-Hypothesizer

According to the search times, it doesn't seem to be hash-based. The tendency resembles more to 'LinkedList' than to our map, but there are certain differences.

-Rebutter

And why are the search times and the iteration times for 10 elements such unreasonably long, regardless of the collection types?

-Hypothesizer

Ah, I don't know the fundamental reason, but that isn't because of the number of elements, but because they are the first measurements. In fact, as we execute tests in another mode like these, those anomalies disappear.

-Hypothesizer executes the commands below.

@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

Um? The anomaly of the search time of 'NavigableLinkedHashMap' doesn't look to have disappeared, to me . . .

-Hypothesizer

No . . ., but other anomalies have disappeared.

-Rebutter

Why not, only of the search time of 'NavigableLinkedHashMap'?

-Hypothesizer

I don't know.

-Rebutter

What did you do in the new mode?

-Hypothesizer

I just did the search and the iteration when the number of elements is 2, secretly.

-Rebutter

Hmm, so, the first search and the first iteration takes longer time, somehow . . .

-Hypothesizer

Honestly, I don't know the underlying mechanism . . .

Anyway, I included those test codes in the zip file, but 'Test3Test' is being disabled by its source file being renamed. You know, as I didn't include the Apache commons Jar file in the zip file, otherwise, the project won't be compiled successfully. We can enable it by downloading the Apache commons Jar file, registering the Jar file in 'OTHER_CLASSES_PATHS' in the project-specific Gradle built script or Ant build file, and restoring the 'Test3Test' source file name.

Main Body END

References

  • 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/

<The previous article in this series | The table of contents of this series | The next article in this series>

2017-10-08

5: How to Make Constant Names and Values of Any Constants Group Enumerable

<The previous article in this series | The table of contents of this series | The next article in this series>

Main Body START

Let's Make a Base Class of Constants Groups Whose Constant Names or Values Have to Be Enumerable

About: Java Programming Language

Why We Don't Just Use Enums

-Hypothesizer

We talked about an limitation of enums in a previous article.

-Rebutter

We can't extend any enum, whether it is a Java Enum or an enum that we created ourselves, without ruining the essence of enums.

-Hypothesizer

The essence of enums, at least for us, is to restrict an argument of a method to a set of predetermined values. If we don't need the restriction, we can just use constants groups. Then we can extend them as we like.

-Rebutter

What we mean by 'constants group' is typically an interface like this.

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

-Hypothesizer

Yes. Although we could make it a class, then, we wouldn't be able to use multiple inheritance. So, if there is no particularly reason, we will make it an interface.

-Rebutter

Ah-ha.

-Hypothesizer

Certainly, an enum is handy when we create a method that receives the enum. As the argument type is specific, not just String or something, it is clear what we can pass to the argument, and it is guaranteed that only predetermined values will be passed to the argument, without any necessity to do tests.

However, if we are passing a value to, say, a String argument of an existing method (for example, a method of the JDK standard library), there is not much merit in using an enum.

-Rebutter

I guess, you are talking about a case in which we don't want to use literals scattered in calling the method in many places.

-Hypothesizer

The method itself doesn't restrict the argument to finite options of possible values, but our program uses only a finite set of values, and we want to manage the set as a constants group.

-Rebutter

OK.

-Hypothesizer

If we use an enum in such a case, we will needlessly assume a handicap of not being able to extend it. In fact, sometimes, we want to extend it.

-Rebutter

So, we use constants groups in such cases.

-Hypothesizer

However, in some cases, we want to enumerate the values or the names of the constants contained in the constants group.

-Rebutter

That, we can't do with simple interfaces like the above one.

-Hypothesizer

So, here, we will make that possible.

What Is Our Design?

-Hypothesizer

We will create a base abstract class that has the functionality of collecting the names and the values of the constants contained in the class that extends it.

In fact, we create the class, 'abstract public class BaseEnumerableConstantsGroup <T>' (in the package, 'thebiasplanet.coreutilities.constantsgroups').

-Rebutter

What is 'T'?

-Hypothesizer

It's the class of the constant values.

-Rebutter

I see.

-Hypothesizer

We use the base class like this.

@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

We create the constants group, 'Test1ConstantsGroup1', as an interface, as usual, then create the enumerable constants group as a concrete class of the base class, implementing the constants group.

-Hypothesizer

Yes. We don't extend enumerable constants groups, but extend constants groups as interfaces and make enumerable versions of those constants groups. That way, we can use multiple inheritance.

This is an example.

@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

Well, what the concrete class has to do is to extend the abstract class, implement the interface, define the singleton instance, and make the constructor private.

-Hypothesizer

Yes. I hope, that's not too much.

-Rebutter

How do we get the names and the values of the constants?

-Hypothesizer

We can get them like this.

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

What Is Our Implementation?

-Rebutter

How do we implement it?

-Hypothesizer

The base class has the field that contains the constant names and values as a name-to-value LinkedHashMap; the constructor collects the names and the values of the constants and store them into the field; the public methods, 'getNames' and 'getValues' disclose the names and the values respectively.

-Rebutter

How do we collect the names and the values?

-Hypothesizer

We use the reflection feature of Java.

-Rebutter

Hmm, using the reflection feature can be a source of performance concerns, . . .

-Hypothesizer

That's the reason we have to make it a singleton. As it is a singleton, the initialization will, basically, run only once, and won't be, practically, a performance concern.

-Rebutter

Well, then, probably, there will be no performance problem.

-Hypothesizer

We get the names and the values of the constants from 'this' in the constructor of 'BaseEnumerableConstantsGroup', but we can't use the 'getFields' method of the class, java.lang.Class.

-Rebutter

Why not?

-Hypothesizer

Because the method gets also shadowed fields.

-Rebutter

So, the method gets not only 'Test1ExtendedConstantsGroup12.c_value1', but also 'Test1ConstantsGroup1.c_value1'?

-Hypothesizer

Yes. Besides, we don't like the order of the fields in which the method returns them: the fields of the sub interface come first.

-Rebutter

So, in the example above, we would get the fields in this order.

  • '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

We want them in this order.

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

So, the fields of the super interface come first, but when a field is overwritten, the field will be replaced at the original position.

-Hypothesizer

That's what we want.

So, I created a static method in a utility class, 'thebiasplanet.coreutilities.reflectionshandling.ReflectionHandler', that fulfills our requirement. The signature of the method is this.

@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

What is 'a_setAccessible'?

-Hypothesizer

If the argument is set 'true', the method tries to get also fields that are invisible from the caller (for example 'private' fields). However, it isn't guaranteed to be able to do so: it depends on the security configuration.

-Rebutter

In this case, we don't need to set it 'true' because all the fields are 'public'.

-Hypothesizer

The method has the argument because I mean it to be for general purposes, not only for this case.

-Rebutter

What are 'a_assignableClassesOfFields' and 'a_notAssignableClassesOfFields'?

-Hypothesizer

They are for selecting fields to be gotten: only fields that are assignable to a class in 'a_assignableClassesOfFields' and are not assignable to any class in 'a_notAssignableClassesOfFields' are gotten. Such a selection is necessary because, otherwise, the field, 'c_instance', would be needlessly gotten.

-Rebutter

Ah-ha.

I presume that 'a_recursive' specifies whether the method will search fields recursively into super classes, super interfaces, and implemented interfaces.

-Hypothesizer

Yes.

We call the method in the constructor of 'BaseEnumerableConstantsGroup' like this.

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

-Rebutter

What is 'ListFactory'?

-Hypothesizer

Ah, that's just another utility class that creates List instances: I created it because initiating a List isn't as handy as initiating an array.

-Rebutter

We seem to have passed 'null' to 'a_instance' because the instance isn't necessary when we get only static fields.

-Hypothesizer

Yes. Passing 'null' to 'a_instance' means getting only static fields.

Here Are the Codes

-Hypothesizer

Here, I cite the code of those utility classes and '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 ());
 }
}

In fact, the source files (including ones for the test) are here. Those projects use Gradle or Ant. Descriptions about how to build the developing environment are here for Linux or here for Windows (ignore descriptions about what are irrelevant here). Expanding the zip file with the directories structure preserved, changing the current directory to each project, and executing the command 'gradle' or 'ant' should build the project. To do the test, changing to the 'coreUtilitiesTestToDisclose' directory and executing the command, 'gradle test' or 'ant test' should do.

Main Body END

<The previous article in this series | The table of contents of this series | The next article in this series>