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.
Java Weaknesses and Points of Interest (Jan-2019)
- By Reference By Value
- No Precompiler
- No default parameters
final not as good asconst final class how does that help- Anonymous (inline) functions can only access 'final' data members
- Limitations with static and inner classes
- Inner and anonymous classes leak
- No
unsigned int orunsigned byte - Characters are two bytes (wasting space)
- No pointers, hard to manipulate memory
- No
typedef . Without typedefs hard to customize data types- No inline
static values, onlyfinal - Autoboxing - performance overhead and wasted space to wrap primitive as an Object
- Generics only support objects, can't store primitives in template containers
- Generic Array access impossible to have safe cast
- Generic usage does not always allow polymorphism
- Generic extend syntax inconsistent
- Generic Number has no compare method or operator overloading
- Beware of Java performance
- Enumeration slow and expensive to use
- Enumeration cannot access private fields
- Static final int constants not always usable in switch-case
- Equal verses ==
- String interning
- StringBuilder - Not the best solution to build strings.
- String joining and manipulation (non-mutable)
- No friends
- No Operator overloading and no custom autoboxing
- Weak iterator implementation (limited reverse)
- Interface all public
- Package private does not support sub-packages
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
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
In C++ which supports full control of
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
}
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 */
}
}
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;
}
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.
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);
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.
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.
}
public class JavaFoo {
void bar() {
final int a;
a = 10;
}
}
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
}
}
public class JavaFoo {
private final int a;
private final int b = 11;
public Foo() {
a = 10;
}
}
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.
}
};
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() {
}
}
public class JavaBar {
public final void foo() {
}
}
public class BetterBar extends JavaBar {
// Error in java, can't override
public void foo() {
}
}
class CppBar {
public:
virtual void foo() final;
};
class BetterBar : public Bar {
public:
virtual void foo() final;
};
class CppBar {
public:
virtual void foo() const final;
};
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.
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.
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.
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.
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 Bytes | Range |
---|---|---|
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 bytes | approximately ±3.40282347E+38F (6-7 significant decimal digits) Java implements IEEE 754 standard |
double | 8 bytes | approximately ±1.79769313486231570E+308 (15 significant decimal digits) |
char | 2 byte | 0 to 65,536 (unsigned) |
boolean | not precisely defined* | true or false |
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
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.
Map<String, Integer> shapes = new HashMap<>();
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]
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.
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;
Integer i = new Integer(0);
Integer integer = 0;
integer++;
int temp = integer.intValue() + 1;
i = temp;
Integer sum = 0;
for (int i = 0; i <500; i++) {
sum += i; }
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.
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
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);
}
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
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
}
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"));
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;
}
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;
} }
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
}
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;
}
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
Android provides a useful annotation to simplify the transition from enumeration to integer values:
@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
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.
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;
}
}
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
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;
}
}
}
Java does not support operator overloading like C++, thus it can't implement an appropriate equality
operator
To perform a deep equality comparison you call
Example of class overriding
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);
}
}
The String objects are immutable and often interned (cached).
When you instantiate a string with
String string = new String("hello world");
String string = "hello world";
Common string concatenation using
String string = "Hello";
string += " ";
string += "world";
Faster example of above string concatenation:
StringBuilder strBuilder = new StringBuilder("Hello");
strBuilder.append(" ");
strBuilder.append("world");
See
https://www.ntu.edu.sg/home/ehchua/programming/java/J3d_String.html
Both
StringBuffer strBuilder = new StringBuffer(32);
strBuilder.append("First,");
strBuilder.append("Second,");
strBuilder.append("Third,");
strBuilder.append("Forth,");
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);
}
private static final TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
public String format(Date date, SimpleDateFormat dateFmt) {
dateFmt.setTimeZone(tz);
return dateFmt.format(date);
}
string format(Date date, SimpleDateFormat dateFmt) {
static const TimeZone tz("America/Los_Angeles");
dateFmt.setTimeZone(tz);
return dateFmt.format(date);
}
See Intern Reference: https://dzone.com/articles/string-interning-what-why-and
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;
}
/*
* 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);
}
}
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
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;
}
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;
}
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.
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.
Interface | Hash Table | Resizable Array | Balanced Tree | Linked List | Hash Table + Linked List |
Set | HashSet | TreeSet | LinkedHashSet | ||
List | |||||
Deque | ArrayDeque | ||||
Map | HashMap | TreeMap | LinkedHashMap |
C++ has forward, random and reverse iterators on most of the common container classes.
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 |
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.
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
@RestrictTo(RestrictTo.Scope.LIBRARY)Android RestrictTo Doc
Java 8 Scope Visibility
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.
Modifier Class Package Subclass
(same pkg)Subclass
(diff pkg)World public + + + + + protected + + + + - no modifier + + + - - private + - - - - + = visible, - = not visible