📜 ⬆️ ⬇️

Java rake

Recently, in our company, among developers, there was a competition for knowledge of the pitfalls of the Java language. The winners received honors and prizes, all received a lot of positive, as well as reasons for reflection and discussion. Hot on the heels we hurry to share the most interesting questions sounded on the quiz with habrew sociality. If you are new to Java or professional, but want to brush up on the dark side of the language, welcome under cat. There you are waiting for 4 tricky questions with answers, rationales and conclusions.


Let's start with the classic accordion about the packaging of primitives.
public static void main(String[] args) { System.out.println(Byte.valueOf((byte) 48) == Byte.valueOf((byte) 48)); System.out.println(Byte.valueOf((byte) 248) == Byte.valueOf((byte) 248)); System.out.println(Integer.valueOf(48) == Integer.valueOf(48)); System.out.println(Integer.valueOf(248) == Integer.valueOf(248)); } 

Answer:
 true true true false 


Flick dust off a book called the Java Language Specification (JLS). Clause 5.1.7. guarantees the return of the same object during boxing byte. When boxing int too. But only for the range -128..127. Our JVM, strictly following the specification, in the first three cases returns the same object for both parts of the equality, and in the latter - two different.
')
UPD: As senia correctly noted in the comments, according to JLS, int caching outside this range is -128..127 possible, but not necessary.

Moral: Boxing is hard.
Another moral: In order not to get confused, any objects should be compared through equals () and not through == . Except for the case when we need to explicitly determine that two links point to the same instance.


Moving on. A simple example of complex initialization. What will this code output?
 class TrickyClass { { value = 10; } private int value = 20; { value = 30;} public int getValue() { return value; } public static void main(String[] args) { System.out.println(new TrickyClass().getValue()); } } 

Answer:
 30 

Let's look at paragraph 12.5. Jls. The order of initialization of the object is as follows:
  1. Perform initialization of the parent class.
  2. Execute the field initializers and instance initializers (and the strange parts of the code in curly brackets are what they are) in order .
  3. Execution of the class constructor.


In our case, the initializer {value = 30;} will be executed last, and this value will remain in the field.


Another question about initialization. What will this code output?
 class Base { public Base() { System.out.println(getName()); } protected String getName() { return "Base";} } class Derived extends Base { private String name = "Derived"; @Override protected String getName() { return this.name;} public static void main(String[] args) { new Derived(); } } 

Answer
  null 

According to the already familiar point 12.5. , there are no special rules for overloaded methods when calling them from the constructor. In our particular case, the method of the child class Derived, which honestly tries to return the value of the name field to its parent constructor, is twitching. Unfortunately, the initialization of this field has not yet occurred, because field initializers are executed after calling the parent constructor. Therefore, the name contains a valid null .

Moral : Never call non-final methods in constructors. But if you have a final class, then you can.


And for dessert, a little serialization
 public class User implements Serializable { public final String name; public User(String name) { this.name = name; } private Object readResolve() { return new User("Darth Vader"); } public static void main(String[] args) throws Exception { User user = new User("Anakin Skywalker"); // Serialize user ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); new ObjectOutputStream(outputStream).writeObject(user); // Deserialize user ByteArrayInputStream inputStreamStream = new ByteArrayInputStream(outputStream.toByteArray()); User readUser = (User) new ObjectInputStream(inputStreamStream).readObject(); System.out.println(readUser.name); } } 

Answer:
 Darth Vader 


Serialization in Java is so extensive and dark subject that the devil himself will break a leg for her there is a separate specification Java Object Serialization Specification . Clause 3.7 states that if the readResolve method is declared on the object being deserialized, this method will be called after reading the object from the stream. And the result of this particular method is considered the result of deserialization. A typical example of using this trick is serialization / deserialization of singletons. Using readResolve along with other special methods readObject, writeObject, writeReplace allows very flexible control over the process of object serialization.

Moral: The process of serialization of objects in Java must be approached very carefully and provide for various options for its use. If the problem can be solved without a serialization mechanism, then it is better to do so.


In general, Java is a well-designed language. There is little rake here, it is difficult to advance. But their knowledge may one day save you a cloud of nerves and hours of debugging code.

Source: https://habr.com/ru/post/277273/


All Articles