JavaRush /Java Blog /Random EN /10 things you didn't know about Java
minuteman
Level 32

10 things you didn't know about Java

Published in the Random EN group
So, have you recently started working with Java? Remember the days when it was called "Oak", when object-orientation was still a hot topic, when C++ people thought Java had no chance, and when no one had even heard of applets? I can assume that you don't even know half of the following things. Let's start the week with some cool surprises about the inner workings of Java. 10 things you didn't know about Java - 11. There is no such thing as a checked exception. That's right! The JVM has no idea about such a thing, only the Java language does. Today everyone agrees that checked exceptions were a mistake. As Bruce Eckel said in his final talk at GeeCON in Prague, no other language since Java uses checked exception, even Java 8 no longer covers them in the new Streams API (which can be a bit of a nuisance when your lambdas use IO or JDBC). Do you want proof that the JVM does not know such a thing? Try the following code: Not only will this compile, it will also throw a SQLException, you don't even need to use Lombok's @SneakyThrows for this. 2. You can have overloaded methods that differ only in their return typespublic 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; } } This won't compile, right? class Test { Object x() { return "abc"; } String x() { return "123"; } } Right. The Java language does not allow two methods to be overridden equivalently within the same class at the same time, regardless of their differences in throws or return types. But wait a minute. Check the documentation again for Class.getMethod(String, Class…). It says: Note that it is possible that there is more than one corresponding method in a class, because while the Java language prohibits multiple methods with the same signature but different return types, the Java Virtual Machine does not. This flexibility in a virtual machine can be used to implement various language features. For example, covariant returns can be done with bridge methods; The bridge method and the overridden method would have the same signature but different return types. Wow, that makes sense. There's actually quite a lot going on when you write the following: Look at the generated bytecode: So t is actually an object in bytecode. This is well understood. The synthetic bridge method is actually generated by the compiler because the return type of Parent.x() can be expected at certain parts of the calls. Adding generics without such bridge methods will no longer be possible in binary representation. So, changing the JVM to allow such a feature produced less pain (which also allows covariant method overriding as a side effect...) Smart right? 3. All of the following are two-dimensional arrays. This is actually true. Even if your mental analyzer cannot immediately understand the return type from the methods described above, they are all the same! Like the next piece of code. Do you think this is crazy? The number of opportunities to write also simply explodes the imagination! Type annotation. A device whose mystery is second only to its power. Or in other words: When I make my last commit just before my 4 week vacation. I give you permission to use it in any way you like. 4. You won't get a conditional So, you thought you already knew everything about conditionals when you started using them? Let me disappoint you - you were wrong. Most of you will think that the following two examples are equivalent: equivalent to this? No. Let's use a quick test The program will output the following: Yes! The conditional operator will perform type casts if needed. Because otherwise you would expect the program to throw a NullPointerException? 5. You also won't get a compound assignment operator. Is resourcefulness enough? Let's look at the following two code snippets: abstract class Parent { abstract T x(); } class Child extends Parent { @Override String x() { return "abc"; } } // 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] class Test { int[][] a() { return new int[0][]; } int[] b() [] { return new int[0][]; } int c() [][] { return new int[0][]; } } class Test { int[][] a = {{}}; int[] b[] = {{}}; int c[][] = {{}}; } @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 [] = {{}}; } 10 things you didn't know about Java - 2 Object o1 = true ? new Integer(1) : new Double(2.0); Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0); System.out.println(o1); System.out.println(o2); 1.0 1 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); i += j; i = i + j; Intuitively, they should be equal right? But you know what - they are different. The JLS specification says: A compound expression of type 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. A good example is to use *= 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' So, is this still a useful tool? 6. Random integers Now for a more difficult task. Don't read the solution. See if you can find the answer yourself. When I run the following program: for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } Sometimes I get the following output: 92 221 45 48 236 183 39 193 33 84 But how is this even possible? Ok, the answer lies in overriding the JDK's Integer cache via reflection, and then using auto-boxing and auto-unboxing. Don't do this without adult permission! Or in other words: 10 things you didn't know about Java - 3 7. GOTO One of my favorites. Java has GOTO! Write this: int goto = 1; and you get this: That's because goto is an unused reserved word, just in case... But that's not the exciting part. The cool thing is that you can include goto paired with break, continue and marked blocks: Jumping forward In byte code: Jumping backward In byte code: 8. Java has type aliases In other languages ​​(like Ceylon), we can define type aliases very easy: The People class here is built in such a way that it can be interchanged with a Set Test.java:44: error: expected int goto = 1; ^ label: { // do stuff if (check) break label; // do more stuff } 2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 .. label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true); 2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 .. interface People => Set ; : In Java, we cannot simply define an alias at the top level. But we can do this for the needs of a class or method. Let's assume that we are not happy with names like Integer, Long, etc. and we want shorter names: I and L. Easy: In the example above, Integer is converted to I for the visibility of the Test class, while Long is converted to L for the needs of the x() method. Now we can call this method like this: Of course, this technique should not be taken seriously. In this case, Integer and Long are final types, which means that I and L are efficient conversions (almost, the conversion only goes one way). If we decided to use non-final types (for example Object), then we could get by with regular generics. We played a little and that was enough. Let's move on to something really interesting. 9. Some type relationships are undecidable! Okay, now this is going to get really interesting, so grab a cup of concentrated coffee and let's look at the following two types: People? p1 = null; Set ? p2 = p1; People? p3 = p2; class Test { void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } } new Test().x(1, 2L);// A helper type. You could also just use List interface Type {} class C implements Type > {} class D

implements Type >>> {} So what do C and D even mean? In a sense they are recursive, similar to the recursion in java.lang.Enum. Consider: Given the specifications above, the actual implementation of enum is just syntactic sugar: With that in mind, let's return to our two types. Will the following code compile? A difficult question... and it's not actually being resolved? Is C a subtype of Type ? Try compiling this in your Eclipse or Idea and they will tell you what they think. Flush it down the drain... Some type relationships in Java are undecidable! 10. Type Intersection Java has a very interesting feature called type intersection. You can declare a (generic) type that is actually the intersection of two types. For example: The custom type parameter T that you associate with instances of the Test class must include both the Serializable and Cloneable interfaces. For example, String cannot be restricted, but Date can: This feature has multiple uses in Java8, where you can cast types. How does this help? Almost nothing, but if you want to cast your lambda expression into the type you need, then there is no other way. Let's say you have such a crazy limitation in your method: You want Runnable which at the same time is Serializable only if you want to execute it in another place and send the result over the network. Lambda and serialization add a bit of irony. You can serialize your lambda expression if its target type and arguments are serializable. But even if this is true, they do not automatically enable the Serializable interface. You must bring them to this type yourself. But when you cast only to Serializable: then the lambda will no longer be Runnable, so cast them to both types: And in conclusion: public abstract class Enum > { ... } // This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum { ... } class Test { Type c = new C(); Type> d = new D (); } Step 0) C Step 1) Type > >? Step 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java is as powerful as it is mysterious.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION