10 things you did not know about Java

Original author: Lukas Eder
  • Transfer
So, have you been working in Java since its inception? Do you remember the days when it was called “Oak”, when they talked about OOP on every corner, when the sluggish miners thought that Java had no chance, and applets were considered a cool thing?

I bet you didn't know at least half of what I'm going to tell you. Let's discover some amazing facts about the internal features of Java.

1. There are no checked exceptions.

Yes Yes! The JVM knows nothing about them, only Java knows.

Today, anyone will agree that checked exceptions were a bad idea. As Bruce Eckel said in his closing speech at GeeCON in Prague , not a single language after Java associated with checked exceptions, and even in the new Streams API in Java 8 they were rejected ( which can cause difficulties when your lambdas use I / O or databases data ).

Want to make sure the JVM doesn't know anything about them? Run this code:

public class Test {
    // Нету throws: исключения не объявлены
    public static void main(String[] args) {
        doThrow(new SQLException());
    static void doThrow(Exception e) {
        Test. doThrow0(e);
    void doThrow0(Exception e) throws E {
        throw (E) e;

This program not only compiles, but actually throws SQLException. You don’t even need @SneakyThrows from Lombok.

You can read more about this here , or here, on Stack Overflow .

2. You can create two methods that differ only in the return type

Such code does not compile, right?

class Test {
    Object x() { return "abc"; }
    String x() { return "123"; }

Right. The Java language does not allow in one class to have two "equivalently overloaded" methods, even if they differ in return type or declared exceptions.

But wait a minute. Let's read the documentation for Class.getMethod (String, Class ...) . It is written there:
Please note that a class may contain several suitable methods, because although the Java language prohibits declaring several methods with the same signature, the Java virtual machine still allows this if the return type is different. Such flexibility of a virtual machine can be used to implement some features of the language. For example, a covariant return type can be implemented using the bridge method, which differs from the real overloaded method only in the return type.
Oh how! Yes, that sounds reasonable. In fact, this will happen if you write:

abstract class Parent {
    abstract T x();
class Child extends Parent {
    String x() { return "abc"; }

Here is a bytecode that will be generated for the class 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]

It’s clear that the generic type Tin bytecode turns into just Object. The synthetic bridge method is generated by the compiler, because in some places where the method is called, the return type Parent.x()can be expected Object. It would be difficult to add generic types without bridge methods and provide binary compatibility. It turned out to be a lesser evil to modify the JVM to support this feature (and covariant return types appeared as a side effect). Cleverly, yes?

Interested in return type overloading? Read this discussion on Stack Overflow.

3. These are all 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. The return type of these methods is the same, even if the parser in your head did not immediately understand it! And here is a similar piece of code:

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

Say madness? And if you add Java 8 type annotations to this ? The number of options increases at times!

@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. Mysterious and powerful mechanism . Steeper than his mystery is perhaps his power .

Or in other words:

My last commit before my monthly vacation
My last commit before my monthly vacation

To find a real use case for these constructions I leave you as an exercise.

4. You do not understand conditional constructions

It seems to you that you know everything about conditional expressions? I will disappoint you. Most programmers believe that the following code fragments are equivalent:

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

Is this the same?

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

But no. Let's check:


The program will display the following:


Yeah! The conditional operator casts numerical types when "necessary" , and "necessary" in very bold quotes. After all, you do not expect this program to throw NullPointerException?

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

More details on this subject here .

5. You do not understand the compound assignment operator either

Do not believe? Consider two lines of code:

i += j;
i = i + j;

Intuitively, they should be equivalent, right? Surprise! They differ. As JLS says :

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

This is so beautiful that I would like to quote Peter Laurie 's answer on Stack Overflow :

An example of such a type conversion can be shown at * = or / =

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


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


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


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

See what a useful feature? Now I will multiply my characters with automatic casting. Because, you know ...

6. Random integers

It is rather a mystery. Do not peep at the solution, try to guess for yourself. When I run this code:

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

“In some cases” I get this result:


How is this possible ??

The answer is given here and consists in rewriting the JDK integer cache using reflection and using autoboxing. Do not try to repeat this at home! Well, or remember the picture above about the last commit before the vacation.


And here is my favorite. Java has goto! Try:

int goto = 1;

And you will get:

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

That's because it goto's an unused keyword . Just in case, when it comes in handy.

But this is not the most interesting. Most impressive is that you can actually implement goto with break, continueand blocks with labels:

Jump forward:

label: {
  // ... какой-то код...
  if (check) break label;
  // ...ещё какой-то код...

In bytecode:

2  iload_1 [check]
3  ifeq 6          // Прыжок вперёд
6  ..

Jump back

label: do {
  // ... какой-то код...
  if (check) continue label;
  // ...ещё какой-то код...
  break label;
} while(true);

In bytecode:

 2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Прыжок назад
 9  ..

8. Java has type aliases

In other languages, for example, in ( Ceylon ), we can easily declare an alias for a type:

interface People => Set;

The type is Peoplecreated in such a way that it can be used anywhere instead of :Set

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

In Java, we cannot declare type aliases globally. But it is possible to do this within a class or method. Suppose we do not like long names Integer, Longetc., we want to use short ones instead: Iand L. Easy:

class Test {
     void x(I i, L l) {
            i.intValue() + ", " + 

In this code I, this is the alias for Integerwithin the class Test, and the Lalias for Longwithin the method x(). We can safely call this method:

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

Of course, such a technique cannot be taken seriously. In our case, types are Integerboth Longdeclared final, and this means that generic types are efficientI and aliases (well, almost: compatibility with assignment only works in one direction). If we used types that were not declared as final (for example ), then these would be ordinary generics. Well, enough stupid tricks. The time has come for something more serious!LObject

9. Some relationships between types are not computable!

Okay, it will be really cool now, so pour yourself coffee and concentrate. Consider the following types:

// Вспомогательный тип. Можно использовать и просто List
interface Type {}
class C implements Type> {}
class D

implements Type>>> {}

What do the types Cand really mean D?

They are in some ways recursive and somewhat similar (though not completely) to type declarations java.lang.Enum. See:

public abstract class Enum> { ... }

In fact, a declaration enumis syntactic sugar:

// Это
enum MyEnum {}
// На самом деле сахар для этого
class MyEnum extends Enum { ... }

Remember this and return to our original types. Will such code compile?

class Test {
    Type c = new C();
    Type> d = new D();

The question is complex and Ross Tate has an answer. It is generally impossible to determine this:

Is C a subtype of Type ?
Step 0) C is Type
Step 1) is (inheritance) Step 2) is (check the mask? Super C) Step ... (infinite loop) If we just loop with C, then with D it’s even more fun: Is DType> Type
C Type

subtype Type >?
Step 0) is Step 1) is Step 2) is Step 3) is Step 4) is Step ... (infinite growth) Try to compile this into Eclipse and it will crash with a stack overflow! ( do not worry, I already reported to the bugtracker ) Have to come to terms:D Type>
Type>>> Type>
D Type>>
Type>>> Type>>
D> Type>>

Some relationships between types are not computable !

If you are interested in generic type problems, read Ross Tate's article “ Taming Templates in the Java Type System ” (co-authored by Alan Löng and Sorin Lerner), or our thoughts on the topic.

10. Intersection of types

Java has a very peculiar thing - type intersection. You can declare a generic type, which is the intersection of two types. For instance:

class Test {

The type that corresponds Tto specific instances of the class Testmust implement both interfaces Serializableand Cloneable. For example, it Stringdoesn’t work, but it’s Datecompletely:

// Ошибка компиляции
Test s = null;
// Работает
Test d = null;

This feature has been developed in Java 8, where you can convert a type to an intersection. Where can this come in handy? Almost nowhere, but if you need to cast a lambda expression to this type, you have no other options. Suppose your method has such an insane type restriction:

 void execute(T t) {}

You are satisfied with only one Runnablethat is also Serializablein case you want to transfer it over the network and run somewhere else. In principle, lambdas can be serialized :
You can serialize a lambda expression if its target type and captured arguments are serializable.
But even if this is respected, the lambda will not automatically implement the marker interface Serializable. You will need a cast. But if you only result in Serializable:

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

then lambda will be gone Runnable.

Eh ...

Remains ...

Lead to two types at once:

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


Usually I only say this about SQL, but it's time to end the article like this:
Java is a mysterious and powerful mechanism. Steeper than his mystery is perhaps his power.

Also popular now: