Today marks the 25th birthday of the Java programming language. For a programming language to be consistently present at the top of the popularity list among the software developers, it takes something special and Java is special. This week marks 25 years since Java was first introduced to the world and what a journey it has been for Java!! In this year’s MovedByChannel, one of the Java Champions (and a person I truly admire) said that “Java would not have survived if it was only a programming language. It’s a combination of platform, library, garbage collection and everything else which created a perfect storm”. Today, in this blog, I will go through some unique, uncommon, least-known, cool Java things that you may not (or may) have encountered till now. Please note that, it is advisable not to use some of these features in your code since they are anti-patterns or may have poor readability. This blog is intended to only identify these cool features. Nevertheless, let’s start (Java version used is 1.8):
1) Usage of Labels – GOTO Implementation in Java
“Labels” act as Java identifiers (followed by a colon) which can identify a block of code and can be used to control the execution flow/branching. e.g. outer:, inner:, scope: …anything can be a label. The block can be a stand-alone block of code. We can use the labels along with the “break” and “continue” keywords to control the flow of execution. In the below program, “outer:” and “inner:” are the labels.
public void labelFunc() {
int k = 0;
outer: for (int i = 0; i < 4; i++) {
inner: for (int j = 0; j < 5; j++) {
System.out.println(k++);
if (k == 5) {
break outer;
}
}
}
}
We know that “goto” is an unused keyword in Java. So, if we declare a variable as int goto=1;, it will throw compile error. But, we can implement the “goto” functionality (similar to goto in C/C++) in Java with the help of labels, break and continue (as mentioned above). This is very useful particularly in case of exiting from complicated and deeply nested set of loops.
2) No Compile or Runtime Error for the below code
public static void main(String[] args) {
http: // www.sumondey.com
System.out.println("Happy Birthday Java !!");
}
At first glance, it looks strange that this is getting compiled successfully without any error. What is happening here is that, “http:” acts as a label and the rest of the statement becomes a single-line comment due to the use of “//”. In fact, you can use anything instead of “http” and it will compile successfully.
3) A Comment can get executed
In the below code, it looks like the line “\u000d value = 23;” is commented out with a single-line comment and the expected output is “Value: 22”.
public static void main(String[] args) {
int value = 22;
// \u000d value = 23;
System.out.println("Value: " + value);
}
However, if you run this program, this will print out “Value: 23”. Why? What’s happening here is that the compiler parses/decodes the Unicode character \u000d as a new line and the statement value=23; becomes a valid Java statement.
4) Unexpectedly, the below code will throw compile error
public static void main(String[] args) {
short a, b, c;
a = 22;
b = 23;
c=a+b;
}
We know that the “short” data type can hold value from -32,768 to 32,767. Then why the addition of 22 and 23 (=45), which falls within it’s range, is giving compilation error. Reason is: The arithmetic expression on the right-hand side of the assignment operator evaluates to int by default. The “+” operator performs a predefined implicit type conversion from short to int because of 32-bit operand stack used by JVM. Since, the type of the variable “c” is declared as “short” and no casting has been done, it is throwing error. Same is applicable for the “byte” data type too.
5) Objects can be made Immortal
JVM’s garbage collector removes an object which has no references when the object’s “finalize()” method is invoked. If we can create a reference to that object inside the object’s own “finalize()” method, then the object can be made immortal and it will never be “dead”.
public class Immortal {
private static final Set<Immortal> immortals = new HashSet<>();
@Override
protected void finalize() throws Throwable {
System.out.println(Immortal.class.getSimpleName() + ",finalize for: " + this);
immortals.add(this);
}
}
6) An interface can have a Nested Class inside it
Yes – you heard it right. A nested class can be declared inside an interface. According to the Java7 specification, an interface can contain member type declarations (static and public by default). One case where it can be useful is when an interface has to throw custom exceptions.
public interface Input {
public static class KeyEvent {
public static final int KEY_DOWN = 0;
public static final int KEY_UP = 1;
public int type;
public int keyCode;
public char keyChar;
}
}
7) An Empty Java File can be compiled
We can compile an empty Java file without any problem. Try to compile an empty “.java” source file with the “javac” command. The file will get successfully compiled with no “.class” files getting created. Reason is that an empty java file does not contain any compile-time/syntax error and hence can be compiled. Since the number of “.class” files produced from a single “.java” source file is equal to the number of classes defined inside that source, hence in this case, no “.class” file gets created upon compilation.
8) Underscore Character can be used with the Numeric Literals
Since Java7, we can use underscore character (“_”) in the numerical literals (only between digits). This feature enables us to separate groups of digits in numeric literals, which, in turn, can improve the readability of the code. Consecutive underscore characters can also be used between digits. All of the below statements are valid:
int numVal1 = 222_42_2;
int numVal2= 42__3;
double doubleVal = 4_3.42_4;
float floatVal = 42.42_2F;
long longVal = 5452345_55464423__232L;
9) Conditional Statements are weird
The below code will compile:
if (true) {
int a1 = 23;
}
But not this one:
if (true)
int a1 = 23;
Reason: The general syntax for using “if” statement is:
if (expression)
statement;
But here, int a1 = 23; is actually a “LocalVariableDeclarationStatement” which can come only inside a block. So, the code won’t compile in this case.
10) Array Syntax – the empty brackets are everywhere
All of the below are valid 2D array declaration:
int arr1[][] = new int[2][2];
int[] arr2[] = new int[2][2];
int[][] arr3 = new int[2][2];
int[] arr4[] = new int[2][2];
int[][] arr5 = new int[2][2];
int [][] arr6 = new int[2][2];
You can also define a method with an int array return type like below:
public int returnArr()[] {
return new int[] { 1, 2, 3 };
}
Java is pretty flexible on where we put the empty brackets for arrays.
11) Calling methods directly from an anonymous subclass
You can directly call a method from an anonymous (having no-name) subclass:
new Thread() {
@Override
public void run() {
System.out.println("Happy Birthday Java !!");
}
}.start();
12) Behavior of the equals() method in case of Long data type
The below code outputs “false” though it was expected to output “true”:
Long longValue=new Long(0);
System.out.println(longValue.equals(0));
What happens here is that the equals() method returns “true” only if the argument is also a long object and contains the same long value as the invoking object. The following three statements will generate output as “true”.
System.out.println(longValue.equals(0L));
System.out.println(longValue.equals(new Long(0)));
System.out.println(longValue.equals((long)0));
13) Java has Strong, Weak, Soft and Phantom type references
Java has these 4 types of references differentiated on the way by which they are collected during garbage collection.
Strong Reference (not eligible for garbage collection till the object newUser becomes null):
User newUser=new User();
Weak Reference (it will be garbage collected only when the JVM needs memory):
User newUser=new User();
WeakReference<User> weakRef = new WeakReference<User>( newUser);
Soft Reference (it will be garbage collected only when the JVM is in need of memory badly):
User newUser=new User();
SoftReference<User> softRef = new SoftReference<User>( newUser);
Phantom Reference (before being garbage collected, JVM puts them in the “reference queue”):
User newUser=new User();
ReferenceQueue<User> refQueue = new ReferenceQueue<User>();
PhantomReference<User> phantomRef = new PhantomReference<User>(newUser,refQueue);
14) JVM does not really care about Checked or Unchecked Exceptions
Java language knows about “Checked Exception” but the JVM understands no such thing as “Checked Exception”. e.g. A java code can get compiled successfully by the java compiler and can also throw the SQLException (without the throws or the catch clause)
15) Compound Assignment Operators: i *= j and i = i*j are not equivalent
Want to check? Try this:
public static void main(String[] args) {
int val = 100;
val *= 2.5; // outputs 250
val = val * 2.5; // compile error
System.out.println(val);
}
16) Conditional Expression behavior
The below code will produce different outputs for the objects obj1 and obj2, though they are expected to generate the same output. This happens because the Conditional Operator will implement numeric type promotion.
public static void main(String[] args) {
Object obj1 = true ? new Integer(1) : new Double(2.0);
Object obj2;
if (true)
obj2 = new Integer(1);
else
obj2 = new Double(2.0);
System.out.println(obj1); // outputs 1.0
System.out.println(obj2); // outputs 1
}
17) Java can have Type Aliases
Though we can’t define “type aliases” in Java at top level, but we can define aliases for the scope of a class/method like below:
public class Test<NewInt extends Integer> {
<NewLong extends Long> void x(NewInt i, NewLong l) {
System.out.println(i.intValue() + ", " + l.longValue());
}
}
18) Double Brace Initialization
Double braces can be used to create and initialize objects in a single Java expression.
public static void main(String[] args) {
List<Integer> myIntVals = new ArrayList<Integer>() {
{
add(1);
add(2);
add(3);
}
};
System.out.println("myIntVals: " + myIntVals);
}
First, we need to create an anonymous inner class which extends ArrayList. Then, we need to provide an instance initialization block which invokes the add method and adds the data to the ArrayList.
19) Instance Initializers
We are aware of the static blocks. But, do you know that the “Instance Initializer” blocks exist too. The “Instance Initializer” blocks can be used to initialize the instance data members, maybe after performing some operations. This gets executed each time before every new object of the class is created.
public class User {
{
System.out.println("Instance Initializer1 called");
}
{
System.out.println("Instance Initializer2 called");
}
public static void main(String[] args) {
User newUser1 = new User();
User newUser2 = new User();
}
}
20) Null check is not required if you are calling “instanceof”
There is no need to perform null check if you are checking for instanceof. According to the Java language specification – “At run time, the result of the instanceof operator is true if the value of the RelationalExpression is notnull and the reference could be cast to the ReferenceType without raising a ClassCastException. Otherwise the result is false”. So, the below null check is not required.
public static void main(String[] args) {
String strVal = "Hello";
if (null != strVal && strVal instanceof String) {
System.out.println("Happy Birthday Java !!");
}
}
21) Every class file starts with the same value (0xCAFEBABE)
Do you know that all the class files, generated from compiling the source file, starts with the same hex value 0xCAFEBABE (known as the magic value) to identify it as a valid JVM bytecode. The first four bytes of every Java class file are specified to be 0xCAFEBABE
, a magic number, that can help tools to quickly differentiate class files from the non-class files.
22) Classes for primitive data types
No – not the Integer, Double, Character wrapper classes. There are actually classes present for the primitive data types (e.g. int, double, float, char). Consider the below program:
public class Practice {
public static void main(String[] args) throws Exception {
Practice obj = new Practice();
Method m = obj.getClass().getMethod("foo", int.class);
m.invoke(obj, 4);
}
public void foo(int n) {
System.out.println("Value: " + n);
}
}
23) Invisible Identifiers
We can use invisible characters in identifiers in Java. These invisible characters (\u200e,\u200f) cannot appear at the start of the identifiers but can be anywhere else. Amazed? Run the below code (without these characters). This prints out: Happy Birthday Java !!
public static void main(String[] args) {
String _ = "Happy Birthday ";
String _ = "Java";
String _ = " !!";
System.out.println(_ + _ + _);
}
24) Printing “hello world” in the strangest way possible
Next time someone tells you that printing “hello world” is easy, show him this program 🙂
public static void main(String[] args) {
for (long longVal = 4946144450195624L; longVal > 0; longVal >>= 5)
System.out.print((char) (((longVal & 31 | 64) % 95) + 32));
}
25) Integer Cache
In the following code, when the value of both intVal1 and intVal2 is 1000 (can be any number out of the range -128 and 127), the output returns false. But once, they are changed to 23 (can be any number between -128 and 127), the output returns true.
public static void main(String[] args) {
Integer intVal1 = 1000;
Integer intVal2 = 1000;
System.out.println(intVal1 == intVal2); // outputs false
intVal1 = 23;
intVal2 = 23;
System.out.println(intVal1 == intVal2); // outputs true
}
That’s all for this blog. Hope you liked this curated list of cool Java things.