10 things you did not know about Java
- 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.
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:
This program not only compiles, but actually throws
You can read more about this here , or here, on Stack Overflow .
Such code does not compile, right?
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:
Here is a bytecode that will be generated for the class
It’s clear that the generic type
Interested in return type overloading? Read this discussion on Stack Overflow.
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:
Say madness? And if you add Java 8 type annotations to this ? The number of options increases at times!
Or in other words:
To find a real use case for these constructions I leave you as an exercise.
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:
Is this the same?
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
More details on this subject here .
Do not believe? Consider two lines of code:
Intuitively, they should be equivalent, right? Surprise! They differ. As JLS says :
This is so beautiful that I would like to quote Peter Laurie 's answer on Stack Overflow :
See what a useful feature? Now I will multiply my characters with automatic casting. Because, you know ...
It is rather a mystery. Do not peep at the solution, try to guess for yourself. When I run this code:
“In some cases” I get this result:
How is this possible ??
And here is my favorite. Java has goto! Try:
And you will get:
That's because it
But this is not the most interesting. Most impressive is that you can actually implement goto with
Jump forward:
In bytecode:
Jump back
In bytecode:
In other languages, for example, in ( Ceylon ), we can easily declare an alias for a type:
The type is
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
In this code
Of course, such a technique cannot be taken seriously. In our case, types are
Okay, it will be really cool now, so pour yourself coffee and concentrate. Consider the following types:
What do the types
They are in some ways recursive and somewhat similar (though not completely) to type declarations
In fact, a declaration
Remember this and return to our original types. Will such code compile?
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)
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 D
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:
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.
Java has a very peculiar thing - type intersection. You can declare a generic type, which is the intersection of two types. For instance:
The type that corresponds
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:
You are satisfied with only one
then lambda will be gone
Eh ...
Remains ...
Lead to two types at once:
Usually I only say this about SQL, but it's time to end the article like this:
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);
}
@SuppressWarnings("unchecked")
static
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 {
@Override
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
T
in 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!
@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. Mysterious and powerful mechanism . Steeper than his mystery is perhaps his power .
Or in other words:
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);
else
o2 = new Double(2.0);
But no. Let's check:
System.out.println(o1);
System.out.println(o2);
The program will display the following:
1.0
1
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!
System.out.println(o);
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
orbyte b = 100; b / = 2.5; System.out.println (b); // print 40
orchar ch = '0'; ch * = 1.1; System.out.println (ch); // prints '4'
orchar 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:
92
221
45
48
236
183
39
193
33
84
How is this possible ??
Clue
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.
7. GOTO
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
, continue
and 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
People
created 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
, Long
etc., we want to use short ones instead: I
and L
. Easy:class Test {
void x(I i, L l) {
System.out.println(
i.intValue() + ", " +
l.longValue()
);
}
}
In this code
I
, this is the alias for Integer
within the class Test
, and the L
alias for Long
within 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
Integer
both Long
declared 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!L
Object
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
C
and 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
enum
is 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 D
Type>
Type
C
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
T
to specific instances of the class Test
must implement both interfaces Serializable
and Cloneable
. For example, it String
doesn’t work, but it’s Date
completely:// Ошибка компиляции
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
Runnable
that is also Serializable
in 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) (() -> {}));
Conclusion
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.