Java Weaknesses - Why Java is not a good language

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 itemizing those weaknesses and issues which were unexpected or disappointing.

[Go Back]


Java

Java Weaknesses and Points of Interest:

  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. Awkward using static instance of static class
  10. No unsigned int or unsigned byte
  11. Characters are two bytes (wasting space)
  12. No pointers, hard to manipulate memory
  13. No typedef. Without typedefs hard to customize data types
  14. No inline static values, only final
  15. Autoboxing - performance overhead and wasted space to wrap primitive as an Object
  16. Generics only support objects, can't store primitives in template containers
  17. Generic usage does not always allow polymorphism
  18. Generic extend syntax inconsistent
  19. Beware of Java performance
  20. Enumeration slow and expensive to use
  21. Enumeration cannot access private fields
  22. Static final int constants not always usable in switch-case
  23. Equal verses ==
  24. String interning
  25. StringBuilder - Not the best solution to build strings.
  26. String manipulation
  27. No friends
  28. Interface all public
  29. No Operator overloading and no custom autoboxing

  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");
        getBlue(red);
    
        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 getBlue(Color color) {
        color.getName().equals("Max");  // true
    
        color = new Color("blue");
        color.getName().equals("blue"); // true
    }
    

    In this example red.getName() will still return "red". The value of red within test() is not modified by calling function getBlue 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");
        getBlue(red);
    
        if (red.getName() == "red") {  // false
            cout << "Passed by value\n";
    
        } else if (red.getName() == "blue") { // true
            cout << "Passed by reference\n";
        }
    }
    
    void getBlue(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 getBlue() 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 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;
        }
    }
    

    The other way, 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 const 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 which prevents class inheritance was a good idea. 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.

    If you're interested in a more detailed comparison between Java and C# closures, I have an article which goes into it further. I wanted to focus on the Java side in this answer :)

    [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 classes have internal references to their parent class. This child-to-parent reference makes these two constructs very good at making memory leaks. Read the details in my Android Memory Leak document.

    [To Top]


  9. Awkward using static instance of static class

    
    // static derived class.
    public static class NoLog extends Applog {
        public  static void d(String tag, String msg) {
        }
        public  static void w(String tag, String msg) {
        }
        public  static void e(String tag, String msg) {
        }
      }
    
    // Get warning if you don't assign a value even thou static and no data in class.
    public static final Applog DEBUG_FOO = new NoLog(); 
    
    // Get warning about accessing static method via instance
    Applog.DEBUG_FOO.d(tag, message)
    

    [To Top]


  10. 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]


  11. 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]


  12. 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. Points also make it efficient to manipulate large data structures such as images. Points in C/C++ are also dangerous and cause a lot of bugs and crashes.

    [To Top]


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

    Map<String, String>  radarId2ImageResource = new HashMap<>();

    In C++ you can typedef your Map declaration parameters. You can specify the key has to be a RadarId. Typedef makes the code more readable and forces type safety reducing errors which are caught at compile time.
    typedef std::string RadarId; 
    std::map<RadarId, std::string> radarId2ImageResource;
    

    [To Top]


  14. No inline static values, only final.

       
    void foo() {
        static int RETRY_MAX = 10;      //  <<< ERROR in JAVA
        final int RETRY_MAX = 10;       //  This is okay in Java
        for (int tryCnt = 0; tryCnt <RETRY_MAX; tryCnt++) {
        }
    }
    
    Inline static in C/C++ is a powerful tool to create singletons.

    [To Top]


  15. 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:

    • java.lang.Byte
    • java.lang.Short
    • java.lang.Integer
    • java.lang.Long
    • java.lang.Float
    • java.lang.Double
    • java.lang.Boolean
    • java.lang.Character

    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:
    • First of all, the integer value is taken from the Integer value integer and it's increased by 1:
      int temp = integer.intValue() + 1;

    • Then, the result is assigned to the integer, but this means that a new autoboxing operation needs to be executed:
      i = temp;

    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]


  16. 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 static <T> int asInt(T idx) {
        return (int)idx;
    }
    
    public void Problem() {
    
        int[] colors = {1, 2, 3, 4, 5} ;
        int color = 3;
        int idx = ArrayFind(colors, color); // colors not valid type.
        idx = asInt(123.456);
    }
    
    public void Okay() {
    
        Integer[] colors = {1, 2, 3, 4, 5} ;
        Integer color = 3;
        int idx = ArrayFind(colors, color);
    }
    

    [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. Beware of Java performance

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

    • The Iterator cycle
    • The while cycle
    • The for cycle

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


  20. 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 Map like 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 Map like object which is created as a singleton and can be expanded to include methods. See my ALog wrapper enumeration

    [To Top]


  21. 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 privatefields. 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]


  22. 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 with the static final declaration.

    These static final values will always have the same value no matter what you do to CODE_VAL becaues they are set when the code is

    
    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;
            }
        }
    }
    

    The following alternative code compiles 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]


  23. 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 then using ==. If you compare using == you are comparing if the two objects are the exact same instance because you are comparing their pointers (references). If you have a custom class (object) and it does not override and implement equals method you get the default implementation which if left to the Object implementation is identical to == and only compares object pointers (references).

    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]


  24. 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) {
        DateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd");
        return dateFmt.format(date);
     }
    

    The unmodified dataFmt 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 DateFormat.
    
    private static final DateFormat DATEFMT = new SimpleDateFormat("yyyy-MM-dd");
    public String format(Date date) {
        return DATEFMT.format(date);
    }
    

    In C++ the static method can be used inline to perform the same optimization.
    
    string format(Date date) {
        static DataFormat DATEFMT =  new SimpleDateFormat("yyyy-MM-dd");
        return DATEFMT.format(date);
    }
    

    String Interning

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

    [To Top]


  25. 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 can use a lot less memory.
    
    /*
     * Copyright (c) 2016 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 http://landenlabs.com/
     */
    
    import java.util.Arrays;
    
    /**
     * Alternative to Java StringBuilder which delays allocation of final
     * string storage and merging till all strings are appended.
     * 
     * This will use less memory if the strings are persistent (ex literals or interned).
     *
     * 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.
         */
        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]


  26. String manipulation

    Since Java Strings are non-mutable, you need to either work hard building a new string from parts or use StringBuffer. The other challenge is joining a string array.

    
    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(","))
    

    [To Top]


  27. 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;
      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]


  28. 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]


  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

    • From type boolean to type Boolean
    • From type byte to type Byte
    • From type short to type Short
    • From type char to type Character
    • From type int to type Integer
    • From type long to type Long
    • From type float to type Float
    • From type double to type Double
    • From the null type to the null type

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