These New Things You Didn’t Know About Java

Sharing few new things about Java that you definitely don't know!1 min


cold coffee recipe

Being Java nerds, we are always interested in obscure details that may not be of direct use but teach us a lot about Java and the JVM (Java Virtual Machine). So, you’ve been working with Java since the very beginning? Remember the days when it was called “Oak” when OO was still a hot topic when C++ folks thought that Java had no chance when Applets were still a thing?

I bet that you didn’t know at least half of the following things. Let’s start with some great surprises about the inner workings of Java. Mostly, you must be aware of How HashMap, HashSet works internally, an abstract class and interface, etc. Now let’s dive into other concepts and get what we have in our surprise box!

Cover Photo by Jonas Jacobsson on Unsplash

Surprise 1: You can have method overloads differing only in return types

That doesn’t compile, right?

class Test {
Object x() { return "Rax"; }
String x() { return "789"; }
}

Right. The Java language doesn’t allow for two methods to be “override-equivalent” within the same class, regardless of their potentially differing throws clauses or return types.

But wait a second. Check out the Javadoc of Class.getMethod(String, Class...). It reads:

Note that there may be more than one matching method in a class because while the Java language forbids a class to declare multiple methods with the same signature but different return types, the Java virtual machine does not. This increased flexibility in the virtual machine can be used to implement various language features. For example, covariant returns can be implemented with bridge methods; the bridge method and the method being overridden would have the same signature but different return types.

Oh Yeah! that makes sense. In fact, that’s pretty much what happens when you write the following:

abstract class Parent {
abstract T x();
}

class Child extends Parent {
@Override
String x() { return "abc"; }
}

Check out the generated byte code in Child:

// Method descriptor #15 ()Ljava/lang/String;
// Stack: 1, Locals: 1
java.lang.String x();
0  ldc  [16]
2  areturn
Line numbers:
[pc: 0, line: 7]
Local variable table:
[pc: 0, pc: 3] local: this index: 0 type: Child

// Method descriptor #18 ()Ljava/lang/Object;
// Stack: 1, Locals: 1
bridge synthetic java.lang.Object x();
0  aload_0 [this]
1  invokevirtual Child.x() : java.lang.String [19]
4  areturn
Line numbers:
[pc: 0, line: 1]

So, T is really just Object in byte code. That’s well understood.

The synthetic bridge method is actually generated by the compiler because the return type of the Parent.x() signature may be expected to Object at certain call sites. Adding generics without such bridge methods would not have been possible in a binary compatible way. So, changing the JVM to allow for this feature was the lesser pain (which also allows covariant overriding as a side-effect…) Clever, huh?

Surprise 2: There is no such thing as a checked exception

Yeah, whatever you learned in your university was just a lie! forget that and move one! Understand what “surprise 2" says!

The JVM doesn’t know any such thing, only the Java language does.

Today, everyone agrees that checked exceptions were a mistake. As Bruce Eckel said on his closing keynote at GeeCON, Prague, no other language after Java has engaged in using checked exceptions, and even Java 8 does no longer embrace them in the new Streams API or when your Lambda expression uses IO or JDBC.

Here is the proof that the JVM doesn’t know such a thing? Try the following code in your local machine:

public class Test {

// No throws clause here
public static void main(String[] args) {
doThrow(new SQLException());
}

static void doThrow(Exception e) {
Test. doThrow0(e);
}

@SuppressWarnings("unchecked")
static  void doThrow0(Exception e) throws E {
throw (E) e;
}
}

Not only does this compile, but this also actually throws the SQLException, you don’t even need Lombok’s @SneakyThrows for that.

Surprise 3: You don't get the conditional expression

So, you thought you knew it all when it comes to using the conditional expression? Let me tell you, you didn’t. Most of you will think that the below two snippets are equivalent:

Object o1 = true ? new Integer(1) : new Double(2.0);

is it the same as given in the below snippet?

Object o2;

if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);

Nope, Nope, Nope. Let’s run a quick test,

System.out.println(o1);
System.out.println(o2);

Your output will be:

1.0
1

Yep! The conditional operator will implement numeric type promotion, if “needed”, with a very very strong set of quotation marks on that “needed”. Because, would you expect this program to throw a NullPointerException?

Integer i = new Integer(1);
if (i.equals(1))
i = null;
Double d = new Double(2.0);
Object o = true ? i : d; // NullPointerException!
System.out.println(o);

Obviously, you can circumvent this problem by casting numeric types to non-numeric types, e.g. Object Do you remember the Java Auto-Unboxing concept? — It will work in this case!

Surprise 4: All of these are two-dimensional arrays!

class Test {
int[][] a()  { return new int[0][]; }
int[] b() [] { return new int[0][]; }
int c() [][] { return new int[0][]; }
}

Yes, it’s true. Even if your mental parser might not immediately understand the return type of the above methods, they are all the same! Similar to the following piece of code:

class Test {
int[][] a = {{}};
int[] b[] = {{}};
int c[][] = {{}};
}

Do you think that’s crazy? Imagine using JSR-308 / Java 8 type annotations on the above. The number of syntactic possibilities explodes!

@Target(ElementType.TYPE_USE)
@interface Crazy {}

class Test {
@Crazy int[][]  a1 = {{}};
int @Crazy [][] a2 = {{}};
int[] @Crazy [] a3 = {{}};

@Crazy int[] b1[]  = {{}};
int @Crazy [] b2[] = {{}};
int[] b3 @Crazy [] = {{}};

@Crazy int c1[][]  = {{}};
int c2 @Crazy [][] = {{}};
int c3[] @Crazy [] = {{}};
}
Type annotations. A device whose mystery is only exceeded by its power

OR

When I do that one last commit just before my 4 week vacation (Below Image)

Coworkers! 🤦‍♂️🤦‍♀️🤣 | Photo by random google memes search

Surprise 5: Random integers

Now, this is more of a puzzler. Don’t read the solution yet. See if you can find this one out yourself. When I run the following program:

for (int i = 0; i < 10; i++) {
System.out.println((Integer) i);
}

… then “sometimes”, I get the following output:

236
183
39
193
33
84
221
45
48
29

How is that even possible?

It has to do with overriding the JDK’s Integer cache via reflection and then using auto-boxing and auto-unboxing. Don’t do this at home! Or in other words, let’s think about it this way, once more.

When I do that one last commit just before my 4 week vacation

Coworkers! 🤦‍♂️🤦‍♀️🤣 | Photo by random google memes search

Surprise 6: You also don't get the compound assignment operator

Quirky enough? Let’s consider the following two pieces of code:

i += j;
i = i + j;

Intuitively, they should be equivalent, right? But guess what. They aren’t! The JLS specifies:

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.

This is so beautiful, (check the answer on StackOverflow)

A good example of this casting is using *= or /=

byte b = 10;
b *= 5.7;
System.out.println(b); // prints 57

OR

byte b = 100;
b /= 2.5;
System.out.println(b); // prints 40

OR

char ch = '0';
ch *= 1.1;
System.out.println(ch); // prints '4'

OR

char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'

Now, how incredibly useful is that? I’m going to cast/multiply chars right there in my application. Now you know it!

Surprise 7: GOTO

This is one of my favorites. Java has GOTO! Type it…

int goto = 1;

This will result in:

Test.java:44: error:  expected
int goto = 1;
^

This is because goto is an unused keyword, just in case…

But that’s not the exciting part. The exciting part is that you can actually implement goto with break, continue and labeled blocks:


Jumping forward:

label: {
// do stuff
if (check) break label;
// do more stuff
}

In bytecode:

2  iload_1 [check]
3  ifeq 6          // Jumping forward
6  ..

Jumping backward:

label: do {
// do stuff
if (check) continue label;
// do more stuff
break label;
} while(true);

In bytecode:

2  iload_1 [check]
3  ifeq 9
6  goto 2          // Jumping backward
9  ..

Enough of these silly tricks. Now for something truly remarkable!

Surprise 8: Some types of relationships are undecidable!

OK, this will now get really funky, so take a cup of coffee and concentrate. Consider the following two types:

// A helper type. You could also just use List
interface Type {}
class C implements Type> {}
class D

implements Type>>> {}

Now, what do the types C and D even mean?

They are somewhat recursive, in a similar (yet subtly different) way that java.lang.Enum is recursive. Consider:

public abstract class Enum> { ... }

With the above specification, actual enum implementation is just mere syntactic sugar:

// This
enum MyEnum {}
// Is really just sugar for this
class MyEnum extends Enum { ... }

With this in mind, let’s get back to our two types. Does the following compile?

class Test {
Type< ? super C> c = new C();
Type< ? super D> d = new D();
}

The question is in fact undecidable:

Is C a subtype of Type?

Step 0) C <!--?: Type
Step 1) Type> <!--?: Type (inheritance)
Step 2) C  (checking wildcard ? super C)
Step . . . (cycle forever)

And then:

Is D a subtype of Type<!--? super D>?

Step 0) D <!--?: Type<? super C>
Step 1) Type>>> <!--?: Type<? super D>
Step 2) D <!--?: Type<? super D<D>>
Step 3) Type>> <!--?: Type<? super C>
Step 4) D> <!--?: Type<? super D<D>>
Step . . . (expand forever)

Try compiling the above in your Eclipse, it’ll crash!

Let this sink in…

Some type of relationships in Java are undecidable!

Surprise 9: Java has alias type

interface People => Set;

A People type constructed in such a way can then be used interchangeably with Set:

People?      p1 = null;
Set? p2 = p1;
People?      p3 = p2;

In Java, we can’t define type aliases at a top level. But we can do so for the scope of a class or a method. Let’s consider that we’re unhappy with the namings of Integer, Long etc, we want shorter names: I and L. Easy:

class Test {
 void x(I i, L l) {
System.out.println(
i.intValue() + ", " +
l.longValue()
);
}
}

In the above program, Integer is “aliased” to I for the scope of the Test class, whereas Long is “aliased” to L for the scope of the x() method. We can then call the above method like this:

new Test().x(1, 2L);

This technique is of course not to be taken seriously. In this case, Integer and Long are both final types, which means that the types I and L are effectively aliases (almost. assignment-compatibility only goes one way). If we had used non-final types (e.g. Object), then we’d be really using ordinary generics.

Who doesn't love bonuses + surprises?

Bonus Surprise: Type intersections

Java has a very peculiar feature called type intersections. You can declare a (generic) type that is in fact the intersection of two types. For instance:

class Test {}

The generic type parameter T that you’re binding to instances of the class Test must implement both Serializable and Cloneable. For instance, String is not a possible bound, but Date is:

// Doesn't compile
Test s = null;
// Compiles
Test d = null;

This feature has seen reuse in Java 8, where you can now cast types to ad-hoc type intersections. How is this useful? Almost not at all, but if you want to coerce a lambda expression into such a type, there’s no other way. Let’s assume you have this crazy type constraint on your method:

 void execute(T t) {}

You want a Runnable that is also Serializable just in case you’d like to execute it somewhere else and send it over the wire. Lambdas and serialization are a bit of a quirk.

Lambdas can be serialized:

You can serialize a lambda expression if its target type and its captured arguments are serializable.

But even if that’s true, they do not automatically implement the Serializable marker interface. To coerce them to that type, you must cast. But when you cast only to Serializable

execute((Serializable) (() -> {}));

… then the lambda will no longer be Runnable.

Arrrgh….

So — Cast it to both types:

execute((Runnable & Serializable) (() -> {}));

Wrapping off...

Always remember that,

Java is a device whose mystery is only exceeded by its power.

References:

[1] Oracle getMethod

[2] Bruce Eckel Speech, Prague

[3] Project Lombok — sneakythrows

[4] The java community process (jcp.org)

adsense


Discover more from 9Mood

Subscribe to get the latest posts sent to your email.


Like it? Share with your friends!

What's Your Reaction?

Lol Lol
0
Lol
WTF WTF
0
WTF
Cute Cute
0
Cute
Love Love
0
Love
Vomit Vomit
0
Vomit
Cry Cry
0
Cry
Wow Wow
0
Wow
Fail Fail
0
Fail
Angry Angry
0
Angry
Rakshit Shah

Legend

Hey Moodies, Kem chho ? - Majama? (Yeah, You guessed Right! I am from Gujarat, India) 25, Computer Engineer, Foodie, Gamer, Coder and may be a Traveller . > If I can’t, who else will? < You can reach out me by “Rakshitshah94” on 9MOodQuoraMediumGithubInstagramsnapchattwitter, Even you can also google it to see me. I am everywhere, But I am not God. Feel free to text me.

0 Comments

Leave a Reply

Choose A Format
Story
Formatted Text with Embeds and Visuals
List
The Classic Internet Listicles
Ranked List
Upvote or downvote to decide the best list item
Open List
Submit your own item and vote up for the best submission
Countdown
The Classic Internet Countdowns
Meme
Upload your own images to make custom memes
Poll
Voting to make decisions or determine opinions
Trivia quiz
Series of questions with right and wrong answers that intends to check knowledge
Personality quiz
Series of questions that intends to reveal something about the personality
is avocado good for breakfast? Sustainability Tips for Living Green Daily Photos Taken At Right Moment