Java Weaknesses - Why Java is not a good language (Jan-2019)

The following is my personal list of weaknesses (issues) with Java (compared to C++). For some background I am using MacBook Pro to develop Android Apps using Android Studio and Java 1.8

During my developement process I have itemized these weaknesses and issues which are unexpected or disappointing. These weaknesses were uncovered using Java 1.8 for Android development.

[Go Back]

External References (Jan-2019)



Java

Java Weaknesses and Points of Interest (Jan-2019)

  1. By Reference By Value
  2. No Precompiler
  3. No default parameters
  4. final not as good as const
  5. final class how does that help
  6. Anonymous (inline) functions can only access 'final' data members
  7. Limitations with static and inner classes
  8. Inner and anonymous classes leak
  9. No unsigned int or unsigned byte
  10. Characters are two bytes (wasting space)
  11. No pointers, hard to manipulate memory
  12. No typedef. Without typedefs hard to customize data types
  13. No inline static values, only final
  14. Autoboxing - performance overhead and wasted space to wrap primitive as an Object
  15. Generics only support objects, can't store primitives in template containers
  16. Generic Array access impossible to have safe cast
  17. Generic usage does not always allow polymorphism
  18. Generic extend syntax inconsistent
  19. Generic Number has no compare method or operator overloading
  20. Beware of Java performance
  21. Enumeration slow and expensive to use
  22. Enumeration cannot access private fields
  23. Static final int constants not always usable in switch-case
  24. Equal verses ==
  25. String interning
  26. StringBuilder - Not the best solution to build strings.
  27. String joining and manipulation (non-mutable)
  28. No friends
  29. No Operator overloading and no custom autoboxing
  30. Weak iterator implementation (limited reverse)
  31. Interface all public
  32. Package private does not support sub-packages

  1. No control of by reference or by-value.

    Java passes everything by value, but the value is often an object (reference or pointer). Java makes it hard to return multiple values via method arguments.

    See method getBlue() in following sample code:

    
    public static void test(){
        Color red = new Color("red");
        changeToBlue(red);  // Internally changes color to blue but does not propagate out of method.
    
        if (red.getName().equals("red")) { // true
            System.out.println( "Passed by value." );
    
        } else if (red.getName().equals("blue")) {
            System.out.println( "Passed by reference." );
        }
    }
    
    public static void changeToBlue(Color color) {  // Jave should default parameters to final 
        color.getName().equals("red");  // true, as expected
    
        color = new Color("blue");
        color.getName().equals("blue"); // true, but value not passed back
    }
    

    In this example red.getName() will still return "red". The value of red within test() is not modified by calling function changeToBlue because the object reference is passed by value. If it were passed by reference, then the color object would change to blue.

    In C++ which supports full control of By Reference and By Value:

    
    void test(){
        Color red = Color("red");
        changeToBlue(red);   // Color red is modified to blue.
    
        if (red.getName() == "red") {  // false, as expected
            cout << "Passed by value\n";
    
        } else if (red.getName() == "blue") { // true, as expected
            cout << "Passed by reference\n";
        }
    }
    
    void changeToBlue(Color& color) {
        color.getName() == "red";  // true
    
        color = Color("blue");
        color.getName() == "blue"; // true
    }
    

    The above C++ code, the value of red inside test() will be modified by calling changeToBlue() and will end up with value "blue" demonstrating pass By Reference.

    [To Top]


  2. No precompiler in Java. Hard to manage optional code blocks or modify code at build time. The C/C++ Precompiler allows the data and code to be modified prior to compilation. This allows conditional code to be compiled in or out and avoids if-tests in the compiled run-time code.

    Example of C/C++ precompiler:

    
    #include <stdio.h>
    #include <assert.h>
    
    #define MESSAGE "hello world"
    // #define TEST 1
    
    int main(int argc, const char * argv[]) {
    #if defined (MESSAGE)
       printf("Here is the message: %s\n", MESSAGE);  
    #endif
       
    #ifdef TEST
       printf("this is TEST code\n");
    #endif
    
       assert(argc == 1);   // assert only in DEBUG build.
       return 0;
    }
    

    Java compiler, like other copilers, can optimize out code blocks inside if-test conditions that are constant. The following will cause the else side of if-test to be unreachable and possibly removed from compiled executable. You cannot set a break point on the else side. If you have Java syntax errors on the else side they will be flagged so the compiler does parse both sides. This constant if-else-test kind of works like a precompiler but can not span multiple methods.

    
    void test(){
        Color color;
        boolean makeRed = true;
        if (makeRed) {
           color = Color.RED;   /* Code always executed */
        } else {
           color = Color.BLUE;  /* Code not reachable  */
        }
    }
    

    The common alternative in Java is to use a large comment block, but this can get messy if you have nested comments or need to span methods or classes.
    
    void test(){
        Color color;
        
        color = MakeRed();   /* Code always executed */
        /* Hide this code for now
           color = MakeBlue();  // Code not reachable
        */
    }
    
    /*  Hide this code for now
    Color MakeBlue() {
        return Color.Blue;
    }
    */ 
    Color MakeRed() {
        return Color.RED;
    }
    

    Java has Meta Annotation The meta annotation is a powerful feature, see Wiki https://en.wikipedia.org/wiki/Java_annotation

    Some anit-Annotation links:

    The annotations are not method calls and will not, by themselves, do anything. Rather, the class object is passed to the JPA implementation at run-time, which then extracts the annotations to generate an object-relational mapping.

    [To Top]


  3. No default parameter values (no default arguments).

    Java does not provide a syntax to define method parameters with default values.

    One solution is to implement every possible variant of your parameters list and try to collapse the code by calling into other functions. For example:

    
    // Old style class with multiple constructors of methods.
    class BasicClass {
        String mParam1;
        int mParam2;
        boolean mParam3;
        
        public BasicClass(String param1)
        {
            init(param1, 0, false);
        }
    
        public BasicClass(String param1, int param2)
        {
            init(param1, param2, false);
        }
        
        public BasicClass(String param1, int param2, boolean param3)
        {
            init(param1, param2, param3);
        }
        
        private void init(String param1, int param2, boolean param3)
        {
            mParam1 = param1;
            mParam2 = param2;
            mParam3 = param3;
        }
    }
    
    Compared to C++
    
    // Old style class with multiple constructors of methods.
    class BasicClass {
        string mParam1;
        int mParam2;
        bool mParam3;
        
        public BasicClass(string param1 = "hello, int param2 = 0, mParam3 = false)
        {
            mParam1 = param1;
            mParam2 = param2;
            mParam3 = param3;
        }
    }
    

    The other way for Java is to use a Factory build pattern. The factory pattern is nice but it does not force an order on setting the parameters. The factory pattern also requires more code and thus more maintenance.

    
    // Factory pattern, initialize default values allow optional override of parameter values.
    class FactoryClass {
        String mParam1;
        int mParam2;
        boolean mParam3;
        
        FactoryClass() {
           // set default values
           mParam1 = "unknown";
           mParam2 = 0;
           mParam3 = false;
        }
        public FactoryClass setParam1(String param1)
        {
            mParam1 = param1;
            return this;
        }
    
        public FactoryClass setParam2(int param2)
        {
            mParam2 = param2;
            return this;
        }
    
        public FactoryClass setParam3(boolean param3)
        {
            mParam3 = param3;
            return this;
        }
    }
    

    
         FactoryClass foo1 = new FactoryClass().setParam1("foo1");
         FactoryClass foo2 = new FactoryClass().setParam1("foo2").setParam2(2);
         // Can set parameters in any order
         FactoryClass foo3 = new FactoryClass().setParam1("foo3").setParam3(true).setParam2(3);
    

    The third way is to use variable arguments, but now you have no type safety. If the object types are unique you can support flexible argument order.

    
    // Example variable arguments with default values. No type safty and assumes fixed order.
    class VariableArgs {
        String mParam1;
        int mParam2;
        boolean mParam3;
        
        VariableArgs(Object... args) {
            switch (args.length) {
               case 3:
                   mParam3 = (Boolean)args[2];
               case 2:
                   mParam2 = (Integer)args[1];
               case 1:
                   mParam1 = (String)args[0];
                   break;
                default:
                    throw  new InvalidParameterException("Expect 1 to 3 parameters"); 
            }
        }
    }
    

    
     VariableArgs foo1 = new VariableArgs("foo1");
     VariableArgs foo2 = new VariableArgs("foo2", 2);
     // Order is depends on variable argument parser, this example requires fixed order. 
     VariableArgs foo3 = new VariableArgs("foo3", 3, true);
    

    [To Top]


  4. final not as good as const

    Java has final which can be used on a value, method or class. When used on a value, it marks the value as the 'final' value and cannot be changed beyond its initial value. When used on a method or class it marks the item as the 'final' implementation and cannot be overriddened.

    Const on the other hand is available in C/C++/C# to force read-only access. The latest version of the GNU C++ compiler supports both const and final.

    java's final vs const

    In C++ marking a member function const means it will not modify any part of its class and can only be called on const instances. Java does not have an equivalent to this:

    
    class CppConstMethod {
    public:
       void readOnly() const;
       void modifyClass();
    };
    
    void test(const CppConstMethod& constMethods) {
       constMethods.readOnly();   // fine
       constMethods.modifyClass();   // error - const method not allowed to use non-const class.
    }
    

    Java's final on data members only prevents a value from being assigned more than once. Java class const member values must be assigned exactly once prior to completion of the object constructor.
    
    public class JavaFoo {
       void bar() {
         final int a;
         a = 10;
       }
    }
    

    Whereas C++ allows const anywhere and the value must be set at the time it is declared or in an initialization list.
    
    class CppConstData {
       const int a = 10;
       const int b = 20;
       
       void bar() {
          const int a = b;     // constant value of 'a' which is scoped inside of bar().
          const int c = a + b; // c = 20 + 20 
       }
    }
    

    In Java final class data member values must be set before the constructor has finished, this can be achieved in one of two ways:
    
    public class JavaFoo {
       private final int a;
       private final int b = 11;
       public Foo() {
          a = 10;
       }
    }
    

    In C++ you can use an initialization lists to set const member values:
    
    class CppFoo {
       const int a;  // If left uninitialized, must be set in constructor.
       const int b = 20;
       const int c;  
    public:
       CppFoo() : a(10) {
          a = 10; // error - Assignment to cons not allowed.
          // error -  because 'c' is not initialized.
       }
    };
    

    In Java final can be used to mark things as non-overridable. Pre-C++11 does not support this.
    
    public class JavaBar {
       public final void foo() {
       }
    }
    
    public class BetterBar extends JavaBar {
       // Error in java, can't override foo marked as final
       public void foo() {
       }
    }
    

    C++11 has support for final. It allows you to mark both classes and member functions as final, with identical semantics to the same feature in Java, for example in Java:
    
    public class JavaBar {
       public final void foo() {
       }
    }
    
    public class BetterBar extends JavaBar {
       // Error in java, can't override
       public void foo() {
       }
    }
    

    Can now be written in C++11 as:
    
    class CppBar {
    public:
      virtual void foo() final;
    };
    
    class BetterBar : public Bar {
    public:
      virtual void foo() final;
    };
    

    I had to compile this example with a pre-release of G++ 4.7. Note that this does not replace const in this case, but rather augments it, providing the Java-like behaviour. So if you wanted a member function to be both final and const you would do:
    
    class CppBar {
    public:
      virtual void foo() const final;
    };
    

    [To Top]


  5. final class ... how does that help

    Why did the inventor of Java ever think that a final class was a good idea. A final class prevents class inheritance. The claim is it gives the author and subsequent uneducated user protection from unexpected behavior when using such a class. When ever a developer needs to extend the functionality of a final class they work around it by using composition instead of inheritance. This forces the author to reimplement ALL of the interface and pass it through to the internal object. This works, but creates code bloat and is harder to follow and maintain. Having an Object Oriented language such as Java should never restrict inheritance.

    The claims of unexpected behavior by using classes not protected by final is just an excuses. Every java class can have unexpected behavior if it is not written well or is misused. Object inherticance and polymorphism is the cornerstone of Object Orient programming. If you can't handle the concepts move to a Structured language like C, Fortan, Basic, etc. But please don't restrict access to inheritance.

    Further the claim that final is some kind of a security fix is also a weak claim. Java is one of the easiest languages to decompile, hack and rebuild. There is no security in Java so get over it.

    [To Top]


  6. Anonymous (inline) functions can only access 'final' data members.

    Java doc

    An anonymous class has access to the members of its enclosing class. An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final(Effectually final means that the variable is never changed after it is initialized. Method parameters are often effectually final.) The reason for this restriction becomes apparent if we shed some light on how local classes are implemented. An anonymous local class can use local variables because the compiler automatically gives the class a private instance field to hold a copy of each local variable the class uses. The compiler also adds hidden parameters to each constructor to initialize these automatically created private fields. Thus, a local class does not actually access local variables, but merely its own private copies of them. The only way this can work correctly is if the local variables are declared final, so that they are guaranteed not to change. With this guarantee in place, the local class is assured that its internal copies of the variables accurately reflect the actual local variables.

    why-are-only-final-variables-accessible-in-anonymous-class

    When you create an instance of an anonymous inner class, any variables which are used within that class have their values copied in via the autogenerated constructor. This avoids the compiler having to autogenerate various extra types to hold the logical state of the "local variables", as for example the C# compiler does... (When C# captures a variable in an anonymous function, it really captures the variable - the closure can update the variable in a way which is seen by the main body of the method, and vice versa.)

    As the value has been copied into the instance of the anonymous inner class, it would look odd if the variable could be modified by the rest of the method - you could have code which appeared to be working with an out-of-date variable (because that's effectively what would be happening... you'd be working with a copy taken at a different time). Likewise if you could make changes within the anonymous inner class, developers might expect those changes to be visible within the body of the enclosing method.

    Making the variable final removes all these possibilities - as the value can't be changed at all, you don't need to worry about whether such changes will be visible. The only ways to allow the method and the anonymous inner class see each other's changes is to use a mutable type of some description. This could be the enclosing class itself, an array, a mutable wrapper type... anything like that. Basically it's a bit like communicating between one method and another: changes made to the parameters of one method aren't seen by its caller, but changes made to the objects referred to by the parameters are seen.

    [To Top]


  7. Limitations with static and inner classes.

    1. Static inner class cannot access non-static members of enclosing class. It can directly access static members (instance field and methods) of enclosing class same like the procedural style of getting value without creating object.
    2. Static inner class can declare both static and non-static members. The static methods have access to static members of main class. However, it cannot access non-static inner class members. To access members of non-static inner class, it has to create object of non-static inner class.
    3. Non-static inner class cannot declare static field and static methods. It has to be declared in either static or top level types. You will get this error on doing so saying "static fields only be declared in static or top level types".
    4. Non-static inner class can access both static and non-static members of enclosing class in procedural style of getting value, but it cannot access members of static inner class.
    5. The enclosing class cannot access members of inner classes until it creates an object of inner classes. IF main class in accessing members of non-static class it can create object of non-static inner class.
    6. If main class in accessing members of static inner class it has two cases:
      Case 1: For static members, it can use class name of static inner class
      Case 2: For non-static members, it can create instance of static inner class.
      

    [To Top]


  8. Inner and anonymous classes leak

    Both non-static inner classes and anonymous inner classes have internal references to their parent class. This hidden link between child (inner class) and parent (outer class) can increase your chance for memory bloat by making memory relationships more complex. Read the details in my Android Memory Leak document.

    [To Top]


  9. No unsigned primitive type, such as unsigned int or unsigned byte.

    Makes it tough to optimize work to unsigned domain.

    https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

    List of Java's primitive data types

    Type Size in BytesRange
    byte 1 byte -128 to 127
    short 2 bytes -32,768 to 32,767
    int 4 bytes-2,147,483,648 to 2,147,483, 647
    long 8 bytes-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
    float 4 bytesapproximately ±3.40282347E+38F (6-7 significant decimal digits) Java implements IEEE 754 standard
    double8 bytesapproximately ±1.79769313486231570E+308 (15 significant decimal digits)
    char2 byte0 to 65,536 (unsigned)
    boolean not precisely defined*true or false

    [To Top]


  10. Characters are two bytes (wasting space).

    The ISO 8859-1 encodes into 8 bits. This character-encoding scheme is used throughout the Americas, Western Europe, Oceania, and much of Africa. Other languages such as C# or C++ used by Microsoft support both single and multi-byte characters.

    Java manages characters as UTF-16

    [To Top]


  11. No pointers, hard to manipulate memory.

    No pointers in Java. Can't do pointer operations or point math to manipulate large data regions. Related is a lot of Java objects are not-mutable so to modify the middle of a String you have to make a new String or use a specialized String object like StringBuffer. With C/C++ most objects are mutable and with pointers you can easily modify the middle of a data region. Pointers also make it efficient to manipulate large data structures such as images. Pointers in C/C++ are also dangerous and cause a lot of bugs and crashes.

    [To Top]


  12. No typedef. Without typedefs hard to customize data types.

    Map<String, Integer>  shapes = new HashMap<>();

    In C++ you can typedef your Map declaration parameters. You can specify the key has to be a Shape. Typedef makes the code more readable and forces type safety reducing errors which are caught at compile time.
    typedef std::string ShapeType;    // Circle, Line, Triangle, Rectangle
    typedef unsigned int ShapeSides;  // 0, 1, 3, 4
    std::map<ShapeType, ShapeSides> shapes;
    

    Look at this redundant declaration of the Map types required to create a custom comparitor. If only there was a typedef you could define the Entry<String, Long> type once and reuse the new type name.

    
    SortedSet<Map.Entry<String, Long>> resultSet = new TreeSet<>(
            new Comparator<Map.Entry<String, Long>>() {
                @Override
                public int compare(Map.Entry<String, Long> e1, Map.Entry<String, Long> e2) {
                    return Long.compare(e1.getValue(), e2.getValue());
                }
            }
    );
    
    [To Top]


  13. No inline static values, only final.

       
    double distanceFromCenter(LatLng pos) {
        static LatLng CENTER_EARTH(0, 0);      //  <<< ERROR in JAVA
        final LatLng CENTER_EARTH = new LatLng(0, 0);       //  This is okay in Java
        return CENTER_EARTH.distanceKM(pos);
    }
    
    Inline static in C/C++ is a powerful tool to create singletons. In C++ the static creation is delayed until the first instance is needed.

    [To Top]


  14. Autoboxing - performance overhead and wasted space to wrap primitive as an Object.

    Autoboxing is the term used to indicate an automatic conversion between a primitive type and its corresponding wrapper class object. Primitive type wrapper classes are as follows:

    They can be instantiated using the assignment operator as for the primitive types and they can be used as their primitive types:

    Integer i = 0;

    This is exactly the same as the following:
    Integer i = new Integer(0);

    But the use of autoboxing is not the right way to improve the performance of our applications. There are many costs associated with it: worst of all, the wrapper object is much bigger than the corresponding primitive type. For instance, an Integer object needs 16 bytes in memory instead of 16 bits for the primitive type. Hence, more memory is used to handle it. Then, when we declare a variable using the primitive wrapper object, any operation on that implies at least another object allocation. Have a look at the following snippet:
    
    Integer integer = 0;
    integer++;
    

    Every Java developer knows what it is, but this simple code needs an explanation of what happened step by step: These operations are slower than if we used the primitive type instead of the wrapper class. Things can get worse in loops, where the preceding operations are repeated every cycle.
    Integer sum = 0;
    for (int i = 0; i <500; i++) {
       sum += i; }

    In the above case, there are a lot of wasted allocations caused by autoboxing. Using primitives there are no wasted allocations.
    int sum = 0;
    for (int i = 0; i <500; i++) {
        sum += i; }   
    

    These primitive wrapper classes were created to work around problems like the following issue with Generics.

    [To Top]


  15. Generics support objects and not primitives

    Generics in Java are handicapped because you can't use any primitives and are force to box primitives wasting time and memory.

    A few good references on Generic limitations:

    Android added SparseArray<Object> as an optimization of Map<Integer, Object> and there are a few other custom variants to optimize the shortcumings of Java.

    Java Containers

    Can't make generic with primities, has to be an Object, like Integer (boxed integer).

    
    public  static <T>  int ArrayFind(T[] array, T find) {
        for (int idx = 0; idx <array.length; idx++){
            if (array[idx] == find)
                return idx;
        }
        return -1;
    }
    
    public void Problem() {
    
        int[] colors = {1, 2, 3, 4, 5} ;
        int color = 3;
        int idx = ArrayFind(colors, color); // colors not valid type.
    }
    
    public void Okay() {
    
        Integer[] colors = {1, 2, 3, 4, 5} ;
        Integer color = 3;
        int idx = ArrayFind(colors, color);
    }
    

    [To Top]


  16. Generic Array access impossible to have safe cast

    It is impossible to perform a safe cast from an Object to Generic container.

    
    List<Object> listObj = new ArrayList<>();
    List<String> listStr = new ArrayList<>();
    
    listObj.add(listStr);
    
    // Does not compile, incorrect type, listObj returns an object
    List<String> prefListStr = listObj.get(0);
    
    // Lint fails - unsafe cast
    List<String> prefListStr = (List<String>)listObj.get(0);
    
    // instanceof does not support generics
    if (listObj.get(0) instanceof List<String>) {
        List<String> prefListStr = (List<String>)listObj.get(0);
    }
    
    // Does not help with cast safety
    if (listObj.get(0) instanceof List<?>) {
        List<String> prefListStr = (List<String>)listObj.get(0);
    }
    

    Nice discussion of related issues

    [To Top]


  17. Generic usage does not always allow polymorphism

    The Java language is inconsistent with when polymorphism is provided for free and when you have to explicitly enabled it via the extends keyword.

    Passing a class as a parameter supports polymorphism with no additional keywords but if you use the class in a generic you must explicitly state you allow polymorphism by prefixing the type with extends.

    
     static class Base {
            public String name;
    
            public Base(String name) {
                this.name = name;
            }
        }
        static class Derived extends Base {
    
            public Derived(String name) {
                super(name);
            }
        }
    
        static void ProcessGenerics1(Collection<Base> list) {
        }
    
        // Just on Generics, polymorphism does not work unless you 
        // provided the keyword extends
        static void ProcessGenerics2(Collection<? extends Base> list) {
        }
    
        static void ProcessPolymorphism(Base list) {
        }
        
        static public void TestGeneric1() {
            List<Base> mListBase = new ArrayList<Base>();
            List<Derived> mListDerived = new ArrayList<>();
    
            ProcessGenerics1(mListBase);
            ProcessGenerics1(mListDerived);     // <-- Does not compile, WHY ?
    
            ProcessGenerics2(mListBase);
            ProcessGenerics2(mListDerived);
            
            Base base = new Base("base");
            Derived derived = new Derived("derived");
            ProcessPolymorphism(base);
            ProcessPolymorphism(derived);      // <-- Works as exepcted
        }
    

    [To Top]


  18. Generic extend syntax inconsistent

    As show above, you need to provide <? extends SomeBaseClass> like syntax on a data object to allow polymorphism when passed as a parameter. Turns out the inverse is true for a local data object.

    
    
    static class Base {
        public String name;
    
        public Base(String name) {
            this.name = name;
        }
    }
    static class Derived extends Base {
    
        public Derived(String name) {
            super(name);
        }
    }
    
    List<? extends Base> mListsBasePlus = new ArrayList<>();
    mListsBasePlus.add(new Base("base1"));           // <-- does not compile
    mListsBasePlus.add(new Derived("derived1"));     // <-- does not compile
    
    List<? extends Object> mListsObjPlus = new ArrayList<>();
    mListsObjPlus.add(new Base("base2"));            // <-- does not compile
    mListsObjPlus.add(new Derived("derived2"));      // <-- does not compile
    
    // The following examples compile
    List<Base> mListsBase = new ArrayList<>();
    mListBase.add(new Base("base3"));
    mListBase.add(new Derived("derived3"));
    
    List<Object> mListsObj = new ArrayList<>();
    mListsObj.add(new Base("base4"));
    mListsObj.add(new Derived("derived4"));
    
    

    [To Top]


  19. Generic Number has no compare method or operator overloading

    The generic Number class does not inculde a compare method nor does it implement any basic operators. Without a compare method, you are forced to either determine its type and use a big switch statement or hope a single value type comparison is good enough.

    If Number supported subtraction, you could just return:

    
    Number num1;
    Number num2
    int compareVal = (num1 - num2).intValue();
    
    But no such luck, so you can take a dangerous short cut and implement as:
    
    public static int compareTo(Number n1, Number n2) {
        return Double.compare(n1.doubleValue(), n2.doubleValue());
    }
    

    Or take the long road and implement as:

    
    public static int compareTo(Number item1, Number item2) {
        int result;
        if (item1 == item2) {
            result = 0;                         // Both identical, maybe both null
        } else if (item1 == null || item2 == null) {
            result = (item1 == null) ? -1 : 1;  // One item is null
        } else if (!item1.getClass().equals(item2.getClass())) {
            // Two types are different, fallback and do string compare.
            result = item1.toString().compareTo(item2.toString());
        } else if (item1 instanceof Integer) {
            result = ((Integer)item1).compareTo((Integer) item2);
        } else if (item1 instanceof Long) {
            result = ((Long)item1).compareTo((Long) item2);
        } else if (item1 instanceof Number) {
            result = Double.compare(((Number)item1).doubleValue(), ((Number)item2).doubleValue());
        } else {
            result = item1.toString().compareTo(item2.toString());
        }
        return result;
    }
    

    [To Top]


  20. Beware of Java performance

    Various ways to iterate over an array in order of slowest to fastest:


    
    // Slowest
    private void iteratorCycle(List list) {
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            String stemp = iterator.next();
        }
    }
    
    // Faster
    private void whileCycle(List list) {
        int j = 0;
        while (j <list.size()) {
            String stemp = (String) list.get(j);
            j++;
    } }
    
    // Fastest 
    private void forCycle(List list) {
        for (int i = 0; i <list.size(); i++) {
            String stemp = (String) list.get(i);
    } }
    
    
    

    Various ways to tune the for-loop from Slowest to Fastest:

    
    // Slowest - computes array length every loop
    private void classicCycle(Dummy[] dummies) {
           int sum = 0;
           for (int i = 0; i <dummies.length; ++i) {
               sum += dummies[i].dummy;
    } }
    
    // Faster - loop length computed once.
    private void fasterCycle(Dummy[] dummies) {
           int sum = 0;
           int len = dummies.length;
           for (int i = 0; i <len; ++i) {
               sum += dummies[i].dummy;
           }
    }
    
    // Fastest - using Java 1.5+ optimize syntax.
    private void enhancedCycle(Dummy[] dummies) {
           int sum = 0;
           for (Dummy a : dummies) {
               sum += a.dummy;
    } }
    

    [To Top]


  21. Enumeration slow and expensive to use. Enumerations are common construct used by developers to limit number of elements, descriptive names, and therefore improved code readability. Java took enums to a whole new level and made them into something more advanced, like a Set of objects. They support customization and can have logic bound to each value and thus are heavily used for extended behavior. But as a basic enumeration they are very heavy and thus avoid. Java language lacks a funcational enumeration primitive.

    The main alternative to an enumeration is the declaration of integers that are publicly accessible and static. For example:

    
    public enum SHAPE {
        RECTANGLE,
        TRIANGLE,
        SQUARE,
        CIRCLE
    }
    

    Is often replaced with:
    
    public class SHAPE {
        public static final int RECTANGLE = 0;
        public static final int TRIANGLE = 1;
        public static final int SQUARE = 2;
        public static final int CIRCLE = 3;
    }
    

    Now, which one is more expensive from a memory perspective? The answer to this question is twofold: we can check the DEX size produced for our app that, then, affects the heap memory usage during execution with enumerations or with integer values. Our example enumeration is converted into four objects allocated with String for the name and an integer value as the ordinal plus an array and the wrapper class. Verses the class implementation is light because it just allocates the four integer values which uses considerably less memory. Basic usage of an enumeration with a switch...case statement:
    
    public void drawShape(SHAPE shape) {
        switch (shape) {
        case RECTANGLE:
            // draw it
            break;
         case TRIANGLE:
             // draw it
             break;
         case SQUARE:
             // draw it
             break;
         case CIRCLE:
             // draw it
             break;
    } }
    

    Similar code using the integer values:
    
    public void drawShape(int shape) {
        switch (shape) {
        case RECTANGLE:
            // draw it
            break;
         case TRIANGLE:
             // draw it
             break;
         case SQUARE:
             // draw it
             break;
         case CIRCLE:
             // draw it
             break;
    } }
    

    Both use cases look similar with the int implementation avoiding the excessive memory overhead cost of the enumeration.

    Android provides a useful annotation to simplify the transition from enumeration to integer values: @IntDef. This annotation can be used to enable multiple constants by using the flag attribute in the following way:

    
    @IntDef(flag = true, value = {VALUE1, VALUE2, VALUE3})
        public @interface MODE {
        }
    }
    

    This annotation says that the possible values are those specified inside the annotation itself. For example, let's change our integer values to use the annotation and transform those values to something similar to an enumeration without all the memory performance issues:
    
    public static final int RECTANGLE = 0;
    public static final int TRIANGLE = 1;
    public static final int SQUARE = 2;
    public static final int CIRCLE = 3;
    @IntDef({RECTANGLE, TRIANGLE, SQUARE, CIRCLE})
    public @interface Shape {
    }
    

    Now, to use it in our code, simply specify the new annotation where you are expecting to have a Shape value:
    
    public void drawShape(@Shape int mode);
    @Shape
    public abstract int getShape();
    

    Enumerations affect the overall memory performance because of their unneeded allocations. Where possible, avoid using enums and swap to static integer values. Create an annotation to use those integer values as if they were an enumeration.

    But the point stands that Java is lacking a true enumeration primative and developers have to work around the shortfall with meta magic and manually unique value assigments. On the upside, the Java enumeration is a powerful data type if you want a Set of objects which is created as a singleton and can be expanded to include methods. See my ALog wrapper enumeration

    [To Top]


  22. Enumeration scope rules don't allow access to own private fields

    Java enums have an odd set of rules. Individual enum values cannot directly access their own private fields. It is as if they are a nested class of a parent class which holds the member fields. Each enum instance can only access its public, protected or package scoped member fields.

    Enums don't allow you to derived sub classes, but you can provied an interface.

    The following sample code shows an interface and the how the enum instances can access various components except the private member fields. The enums can access a private static member but not a non-static private.

    These scope rules are not intutive.

    
    public interface IEnum {
        // Return value set in constructor
        String getValue();
    
        // Return public value set per enum method.
        String getPubMsg();
    
        // Return protected value set per enum method.
        String getProtMsg();
    
        // Return private value set per enum method.
        String getPrivMsg();
    }
    


    
    public enum enumScopeTest implements IEnum {
        RECOMMEND("Recommend") {
            @Override
            public String getPubMsg() {
                if (mPubMsg == null)
                    mPubMsg = "Recommende-pub";
                return super.getPubMsg();
            }
    
            @Override
            public String getProtMsg() {
                if (mProtMsg == null)
                    mProtMsg = "Recommended-prot";
                return super.getProtMsg();
            }
    
            @Override
            public String getPrivMsg() {
                /*
                 * private member field not directly accessible, but
                 * can be accessed via a public or protected method.
                 *
                if (mPrivMsg == null)
                    mPrivMsg = "Recommended-priv";
                */
                if (super.getPrivMsg() == null)
                    setPrivMsg("Recommended-priv"); // setPrivMsg cannot be private
                return super.getPrivMsg();
            }
    
            @Override
            public String getPRIV_Msg() {
                if (PRIV_MSG == null)
                    PRIV_MSG = "Recommended-PRIV";
                return PRIV_MSG;
            }
        }
        ;
    
        // Per enum instance.
        private final String mValue;
        public  String mPubMsg;
        protected String mProtMsg;
        private String mPrivMsg;            // Not directly settable by enum body.
        private static String PRIV_MSG;     // Single instance across all enums.
    
        enumScopeTest(String value) {
            mValue = value;
        }
    
        // Return value set in constructor
        public String getValue() {
            return mValue;
        }
    
        // Return public value set per enum method.
        public String getPubMsg() {
            return mPubMsg;
        }
    
        // Return protected value set per enum method.
        public String getProtMsg() {
            return mProtMsg;
        }
    
        // Return private value set per enum method.
        public String getPrivMsg() {
            return mPrivMsg;
        }
    
        void setPrivMsg(String msg) {
            mPrivMsg = msg;
        }
    
        // Return private static value set per enum method.
        public  String getPRIV_Msg() {
            return PRIV_MSG;
        }
    }
    
    

    [To Top]


  23. Static final int constants not always usable in switch-case

    If you need to create a large set of unique constant static integers you could manually populate them all with unique values or you could let the compiler help you by setting a base value and using its incremented value to populate your constants. This is demonstrated below, but sadly the static final int values are not usable in switch-case statement even thou they specifiy static final declaration.

    This example does not work even thou it is clean Java logic.

    
    public class Constants {
    
        private static int CODE_VAL = 10000;
    
        public static final int CODE_VAL1 = CODE_VAL++;
        public static final int CODE_VAL2 = CODE_VAL++;
        
        public static void test1(int value) {
    
            if (value == CODE_VAL1) {   // Okay - code compiles
                System.out.println("Got CODE_VAL1");
            }
    
            switch (value) {
                case CODE_VAL1:     // Fails to compile, "constant expression required"
                    System.out.println("Got CODE_VAL1");
                    break;
                case CODE_VAL2:     // Fails to compile, "constant expression required"
                    System.out.println("Got CODE_VAL2");
                    break;
            }
        }
    }
    

    This alternative code works but requires more work to correctly chain the values. If you cut and paste, you could forget to update the chain correctly.


    
    public class Constants {
    
        private static final int CODE_VAL0 = 10000;         // Must be final 
    
        public static final int CODE_VAL1 = CODE_VAL0 +1;
        public static final int CODE_VAL2 = CODE_VAL1 +1;   // Chain values
        public static final int CODE_VAL3 = CODE_VAL2 +1;   // Chain values
        public static final int CODE_VAL4 = CODE_VAL3 +1;   // Chain values
        public static final int CODE_VAL5 = CODE_VAL4 +1;   // Chain values
    
        public static void test1(int value) {
    
            if (value == CODE_VAL1) {   // Compiler happy
                System.out.println("Got CODE_VAL1");
            }
    
            switch (value) {
                case CODE_VAL1:     // Compiler happy
                    System.out.println("Got CODE_VAL1");
                    break;
                case CODE_VAL2:     // Compiler happy
                    System.out.println("Got CODE_VAL2");
                    break;
            }
        }
    }
    

    [To Top]


  24. Equal verses ==

    Java does not support operator overloading like C++, thus it can't implement an appropriate equality operator ==. To work around this shortfall Java implements the method equals(String other)

    To perform a deep equality comparison you call equals on objects rather than using ==. If you compare using == you are comparing if the two objects are the exact same instance because you are comparing their pointers (identity hash code). If you have a custom class (object) and it does not override and implement equals method you get the default implementation which is identical to == and only compares object pointers (identity hash code).

    Example of class overriding equals() and with proper implementation of hashCode() as well.

    
    class Person {
        String firstName;
        String lastName;
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (!(obj instanceof Person)) // null test not required.
                return false;
            Person person = (Person)obj;
            // Android use of Objects requires API 19
            return Objects.equals(firstName, person.firstName)
                    && Objects.equals(lastName, person.lastName);
        }
        
        // Must override both equals and hashcode.
        @Override
        public int hashCode() {
            // Android use of Objects requires API 19
            return Objects.hash(firstName, lastName);
        }
    }
    

    [To Top]


  25. Strings, local variables and interning

    The String objects are immutable and often interned (cached). When you instantiate a string with new you are wasting an allocation:

    
    String string = new String("hello world");
    

    Simple assignment is more efficient:
    
    String string = "hello world";
    

    String concatenation

    Common string concatenation using + operator creates multiple objects and is not efficient:

    
    String string = "Hello";
    string += " ";
    string += "world";
    

    Java created two auxiliary StringBuffer and StringBuilder methods to manipulating strings because they work on character arrays.

    Faster example of above string concatenation:

    
    StringBuilder strBuilder = new StringBuilder("Hello");
    strBuilder.append(" ");
    strBuilder.append("world");
    

    StringBuffer is thread safe and thus slower then StringBuilder which is not thread safe.

    StringBuilder is 2x faster than StringBuffer, and 300x faster than String.
    See https://www.ntu.edu.sg/home/ehchua/programming/java/J3d_String.html

    Both StringBuilder and StringBuffer have an initial capacity of 16 characters, and when full they increase by allocating a new object with twice the capacity and the old one is waiting for the next garbage collection to be done. To avoid this unnecessary waste of memory, if you know an estimation of the final capacity you can instantiate StringBuilder or StringBuffer by specifying a better initial capacity:

    
    StringBuffer strBuilder = new StringBuffer(32);
    strBuilder.append("First,");
    strBuilder.append("Second,");
    strBuilder.append("Third,");
    strBuilder.append("Forth,");
    

    This way, no object recreation is needed if the final capacity is lower than 32 characters and it will not be collected until it's no longer referenced.

    Construction of unmodified Local variables

    Creating a local variable which is not modified can be wasteful.

    
    public String format(Date date, SimpleDateFormat dateFmt) {
        final TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
        dateFmt.setTimeZone(tz);
        return dateFmt.format(date);
     }
    

    The unmodified tz should be created once outside of the method and reused to avoid wasted overhead of constantly creating the same object each invocation. Better implementation avoiding repetitive creation of TimeZone.
    
    private static final TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
    public String format(Date date, SimpleDateFormat dateFmt) {
        dateFmt.setTimeZone(tz);
        return dateFmt.format(date);
    }
    

    In C++ the static method can be used inline to perform the same optimization. In C++ the static is only instantiated the first time it is needed.
    
    string format(Date date, SimpleDateFormat dateFmt) {
        static const TimeZone tz("America/Los_Angeles");
        dateFmt.setTimeZone(tz);
        return dateFmt.format(date);
    }
    

    String Interning

    See Intern Reference: https://dzone.com/articles/string-interning-what-why-and

    [To Top]


  26. StringBuilder - Not the best solution to build strings.

    Turns out that StringBuilder is not very good at merging multiple strings together unless you correctly preallocate it during construction. It defaults to a 16 character buffer. Meaning if you append strings which total beyond 16 characters it has to regrow its working buffer.

    
    // StringBuilder's append implementation:
    public append(CharSequence s, int start, int end) {
        if (s == null)
            s = "null";
        if ((start < 0) || (end < 0) || (start > end) || (end > s.length()))
            throw new IndexOutOfBoundsException(
                "start " + start + ", end " + end + ", s.length() "
                + s.length());
        int len = end - start;
        if (len == 0)
            return this;
        int newCount = count + len;
        if (newCount > value.length)
            expandCapacity(newCount);
        for (int i=start; i < end; i++)
            value[count++] = s.charAt(i);
        count = newCount;
        return this;
    }
    

    StringAppender

    Here is an alternate version which stores the strings in a list an only when toString() is called does it allocate the merged buffer. This implementation will avoid regrowth (re-allocation) of memory during the append operation, but it does store the raw strings in an array which itself will suffer from memory growth (re-allocation).
    
    /*
     * Copyright (c) 2019 Dennis Lang (LanDen Labs) landenlabs@gmail.com
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
     * associated documentation files (the "Software"), to deal in the Software without restriction, including
     * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
     *  following conditions:
     *
     *  The above copyright notice and this permission notice shall be included in all copies or substantial
     *  portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
     * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
     * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
     * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     *
     *  @author Dennis Lang  (12/10/2016)
     *  @see https://landenlabs.com/
     */
    
    import java.util.Arrays;
    
    /**
     * Alternative to Java StringBuilder which delays allocation of final
     * string storage until toString called, at which point a single 
     * storage buffer is allocated and all strings merged togehter. 
     * 
     * This will use less memory if the strings are persistent (ex literals or interned),
     * because final allocation will be a perfect fit and will avoid regrowth during 
     * final string building. 
     *
     * Note - Individual strings are stored in ArrayList which will suffer from its
     * own growth and reallocation. 
     *
     * Created by Dennis Lang on 12/10/16.
     */
    public class StringAppender {
        private String[] mArray = null;
        private int mSize = 0;
        private int mLength = 0;
    
        private char[] m_allParts;
    
        public StringAppender() {
            ensureCapacityInternal(16);
        }
    
        public StringAppender(int initialCapacity) {
            ensureCapacityInternal(initialCapacity);
        }
    
        /***
         * Start building a string.
         * @param str
         * @return this
         */
        public StringAppender start(String str) {
            clear();
            return append(str);
        }
    
        /***
         * Append string to list for later merging.
         * @param str
         * @return this
         */
        public StringAppender append(String str) {
            ensureCapacityInternal(mSize + 1);
            mArray[mSize++] = str;
            mLength += str.length();
            return this;
        }
    
        /**
         * Clear list of strings.
         */
        public void clear() {
            mSize = 0;
            mLength = 0;
        }
    
        private void ensureCapacityInternal(int minCapacity) {
            if (mArray == null) {
                mArray = new String[minCapacity];
                return;
            }
    
            if (minCapacity <= mArray.length) {
                return;
            }
    
            int oldCapacity = mArray.length;
            int newCapacity = oldCapacity * 2 + 2;
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
    
            if (true) {
                // Solution similar to StringBuilder
                mArray = Arrays.copyOf(mArray, newCapacity);
            } else {
                //  Alternate solution, merge prior to grow
                String current = toString();
                mSize = 0;
                // Uncomment next line to allow list to grow.
                // mArray = new String[newCapacity];
                mArray[mSize++] = current;
            }
        }
    
        /***
         * @return length of all parts.
         */
        public int length() {
            return mLength;
        }
    
        /***
         * @return Merge all string parts.
         * TODO - optimize to store merged results and return on subsequent toString() calls. 
         */
        public String toString() {
            if (mLength == 0)
                return "";
    
            if (m_allParts == null || m_allParts.length < mLength)
                m_allParts = new char[mLength];
    
            int pos = 0;
            for (int idx = 0; idx < mSize; idx++) {
                String str = mArray[idx];
                int len = str.length();
                str.getChars(0, len, m_allParts, pos);
                pos += len;
            }
    
            return new String(m_allParts, 0, mLength);
        }
    }
    

    [To Top]


  27. String joining and manipulation (non-mutable)

    Since Java Strings are non-mutable, you need to build a new string from parts, so joining requires making a new string. Similar problem if you want to change part of a string, such as replace characters or remove some. In C++ you can create a String which has reserved storage allowing string manipulation to occur in-place resulting in superior performance.

    Example of Java manually joining strings using StringBuilder (similar methods available in TextUtils):

    
    public static String strJoin(String[] aArr, String sSep) {
        StringBuilder sbStr = new StringBuilder();
        for (int i = 0, il = aArr.length; i < il; i++) {
            if (i > 0)
                sbStr.append(sSep);
            sbStr.append(aArr[i]);
        }
        return sbStr.toString();
    }
    

    Java 8 simplifies the solution with new join method on String:

    
    // Directly specifying the elements
    String joined1 = String.join(",", "a", "b", "c");
    
    // Using arrays
    String[] array = new String[] { "a", "b", "c" };
    String joined2 = String.join(",", array);
    
    // Using iterables
    List list = Arrays.asList(array);
    String joined3 = String.join(",", list);
    

    If you have Apache Commons installed:

    
    StringUtils.join(array, separator)
    
    String str = Arrays.asList(array).stream().collect(Collectors.joining(","))
    

    For comparison, C++ requries similar code but you can pre-allocate the merged string to improve performance. First example which does not preallocate.

    
    template<typename... Args>
    std::string concat(Args const&... args)
    {
        std::string result;
        for (auto s : {args...}) result += s;
        return result;
    }
    
    Example usage:
    std::string bigStr = concat("This ", "is ", "a ", "test!");
    

    Example with pre-allocation:

    
    template<typename... Args>
    std::string concat(Args const&... args)
    {
        size_t len = 0;
        for (auto s : {args...})  len += strlen(s);
     
        std::string result;
        result.reserve(len);    // <--- preallocate result
        for (auto s : {args...})  result += s;
        return result;
    }
    

    [To Top]


  28. Java has no friends

    C++ has the keyword friend to grant access to private and protected members. From a scope access point of view, this is similar to Java's inner class having access to the outer class. The C++ friend keyword grants access to one or more classes and can be used at both the class level and function level.

    
    // friend class
    #include <iostream>
    using namespace std;
    
    class Square;
    
    class Rectangle {
        int width, height;
      public:
        int area ()
          {return (width * height);}
        void convert (Square a);
    };
    
    class Square {
      friend class Rectangle;    // <-- Give Rectangle access to class.
      private:
        int side;
      public:
        Square (int a) : side(a) {}
    };
    
    void Rectangle::convert (Square a) {
      width = a.side;
      height = a.side;
    }
      
    int main () {
      Rectangle rect;
      Square sqr (4);
      rect.convert(sqr);
      cout << rect.area();
      return 0;
    }
    

    [To Top]


  29. No Operator overloading and no custom autoboxing

    Java doesn't support user-defined operator overloading. The only aspect of Java which comes close to "custom" operator overloading is the handling of + for strings, which either results in compile-time concatenation of constants or execution-time concatenation using StringBuilder/StringBuffer. You can't define your own operators.

    Autoboxing is automatic type conversion which is similar to overloaded type conversion available in C++. Autoboxing conversion converts expressions of primitive type to corresponding expressions of reference type. Specifically, the following nine conversions are called the boxing conversions and occur automatically. Both directions of the conversion are automatic (boxing and unboxing). JLS, section 5.1.7

    Sadly you can't add autoboxing to your own types. For example MutableInt does not support autoboxing, while Integer does.

    [To Top]


  30. Weak iterator implementation (limited reverse support)

    Java has a weak iterator implementation compared to C++.

    Java has Iterator (forward only) and ListIterator (bidirection), but the ListIterator is only available on a small subset of containers.

    Oracle Collections Framework Overview
    * which provide ListIterator

    Interface Hash Table Resizable Array Balanced Tree Linked List Hash Table + Linked List
    Set HashSet TreeSet LinkedHashSet
    List ArrayList (*) LinkedList (*)
    Deque ArrayDeque LinkedList (*)
    Map HashMap TreeMap LinkedHashMap

    C++ has forward, random and reverse iterators on most of the common container classes.

    Summary of C++ std container class iterators
    Iterator form Produced by
    input iterator istream_iterator
    output iterator ostream_iterator
    inserter()
    front_inserter()
    back_inserter()
    bidirectional iterator list
    set and multiset
    map and multimap
    random access iterator ordinary pointers
    vector
    deque

    [To Top]


  31. Interface all public

    Java's interface is a handy way to share access to components. Interfaces are also a way to emulate C++'s multiple enheritance because a single class can implement several interfaces. The problem with interface is all methods are forced to be declared in public access scope. This coupled with the lack of friend access makes it difficult to expose a subset of methods to a single class because everyone can access all your interface declared methods.

    [To Top]


  32. Package private does not support sub-packages

    Java scope modifiers are similar to C++ but lack the friend modifier. Swift and C# provide internal modifier to limit scope, but not Java. Java 9 has a new feature call Jigsaw but that is not currently available to Android developers which can only use up to Java 8 and even that has some limitations. Android Java has a lint warning solution provided by meta string.

    @RestrictTo(RestrictTo.Scope.LIBRARY)
    Android RestrictTo Doc

    Java 8 Scope Visibility

    Modifier Class Package Subclass
    (same pkg)
    Subclass
    (diff pkg)
    World
    public + + + + +
    protected + + + + -
    no modifier + + + - -
    private + - - - -
     
    + = visible, - = not visible
    Since package private scope (no modifiers) only allows classes with the exact same package to have access, you cannot create a hierarchy of packages which could give package access to children. The current implementation forces library developers to work in a flat file hierarchy to keep all classes in a single package and use public and private scopese to control access to library methods.

    [To Top]