Wednesday, October 17, 2012

Java tricky questions

Introduction

There are a lot of articles that describe various java tricky questions. But IMHO most of that questions can be easily answered by person that knows java well. However there are more complicated tricks that typically cause even experienced people to fail. This article tries to put such kind of trick together.

Tricks classification

The tricks are typically based on

  • internal caches managed by JDK classes or JVM itself
  • compiler optimizations
  • hidden and not obvious type transformations including autoboxing
  • magic of generics
  • tricks of terminology
  • using of uncommon, bad and anti-conventional coding and naming that are expected to confuse the person being interviewed.

This classification as well as the following list are not full and cannot be full. However the examples show the general ideas.

Please be aware of the fact that author classify people that use such questions during job interview as criminals. Such questions do not check knowledge of programming language but can confuse even experienced programmer.


String concatenation

What will the following code fragment print? 


String str1 = "abc:5";
String str2 = "abc:5";
String str3 = "abc:"+5;
String str4 = "abc:"+(1+4);
String str5 = "abc:"+str1.length();
System.out.println(str1 == str1);
System.out.println(str1 == str2);
System.out.println(str1 == str3);

System.out.println(str1 == str4);
System.out.println(str1 == str5);
System.out.println(str1 == new String(str1));



Answer


true
true
true
true

false
false



Description

Even beginners know that direct comparison of references is not the same as invocation of equals() method. So, we can think that every invocation of == will return false. So, why does it return true in 4 first cases?  Let's review the results one by one. 

  1. First true is trivial. Reference is equal to itself. 
  2. String is not a regular class. It is built-in java type. String constants are cached, so str1 and str2 refer to exactly the same object. This is the reason for the second true.
  3. str3 is defined as "abc:"+5, so it should not be the same as str1: value of the variable is calculated at runtime. In fact java compiler performs all calculations of constant expressions, so expression "abc:"+5 is transformed to "abc:5" at compile time and therefore cached by regular string cache. 
  4. This case is similar to #3. Both mathematical expression and concatenation are done at compile time, so the string is taken from cache.  
  5. The case of str5 is special: java compiler cannot call method, so the string value calculation is done at runtime and the string cannot be cached. 
  6. This case is similar to #5. Constructor cannot be called at compile time, so new object with the same value is created. 

Magic integers

What will this code snippet print?

Integer v1_1 = 1;
Integer v1_2 = 1;

Integer v127_1 = 127;
Integer v127_2 = 127;

Integer v128_1 = 128;
Integer v128_2 = 128;

System.out.println(v1_1 == v1_2);
System.out.println(v127_1 == v127_2);
System.out.println(v128_1 == v128_2);


The answer is:
true
true
false

Now the question is "why?!" Indeed, how can value of variable affect the comparison result? How is it possible that 127==127 while 128!=128?

The reason is that line 

Integer v127_1 = 127;

is compiled to 

Integer v127_1 = Integer.valueOf(127);

Here is how de-compiled byte code looks like:

public static void main(java.lang.String[]);
  Code:
   0:   iconst_1
   1:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;


that uses internal cache to retrieve the value. Only if value is not in cache new instance of Integer is created. The cache is initialized to numbers from 0 to 127 inclusive. So, when we try to get value of 127 we get the same instance every time. However attempt to get value greater than 127 creates new object.


int method that returns null


Everyone understands that the following code cannot be compiled:

private int temp() {
    return null; 
}

The reason is that int type is incompatible with null value.

But the following code is compiled perfectly:

private int temp() {
    return (true ? null : 0); 
}

This code is compiled because compiler interprets null as a legal value of Integer and then performs autoboxing, i.e. calls intValue() of  Integer object that contains null. This causes NullPointerException at runtime. 
This question and description are taken from stackoverflow (feel free to up-vote). 




int method that does not return anything



If method declaration contains return type the method must return value:

private int temp() {
    return 0; 
}


The following declaration will cause compilation error:

private int temp() {
}

This method must return a result of type int



Actually each execution path of the method must end with either return or throw statement:


private int temp(int i) {
    if (i < 0) {
        throw new IllegalArgumentException("negative");
    }
    return i; 
}

So, is it indeed possible to create int method that neither returns value nor throws exception? The answer is yes:


private int temp() {
    while(true);
}

This method can be successfully compiled and however does not return value and doesn't throw exception. The reason is that this method also never terminates. Compiler knows this and does not complain that return statement is missing.

Now here is a tricky question: write the shortest syntactically correct method so that its return type is int. Every normal person will write something like:

int f() {return 0;} 

while the right answer is:

int f() {for(;;);} 

because it is shorter by 1 character. 


Arithmetic operations on values of byte and/or short types

Let's say that we have the following declaration:

byte b = 0;

Are the following 3 operators equal?

b++;
b+=1;
b = b + 1;

All three should increment the variable by 1, right? Yes, it is correct for first 2 lines. The third line cannot be compiled:

ByteIncrement.java:5: possible loss of precision
found   : int
required: byte
                b = b + 1;

This happens because numeric constants automatically belong to type int that bigger than byte. So, to make this line to work we have to add explicit casting:

b = b + (byte)1;

Now all operations indeed equal and increment the variable b by 1. 
This trick is taken from stackoverflow, so feel free to up-vote.

Throwing checked exception without declaration

Everyone knows that there are checked and unchecked exception in java. Unchecked exceptions extend directly or indirectly from RuntimeException and do not have to be declared. Checked exceptions extend directly or indirectly from Exception (but not from RuntimeException) and must be declared. Otherwise attempt to throw them causes compilation error. 

So, what do you think about the following code:



import java.sql.SQLException;

public class Test {

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

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

    static <E extends Exception> void doThrow0(Exception e) throws E {
        throw (E) e;
    }
}


Methods main() and doThrow() do not have "throws SQLException" declaration. Method doThrow0() defines expected exception using generic parameter E. Sending RuntimeException as a method generic parameter satisfies compiler and it does not require "throws SQLException" declaration. But in fact SQLException is created and thrown:

$ java Test
Exception in thread "main" java.sql.SQLException
        at Test.main(Test.java:7)
This trick was discussed by Lukas Eder here and here.


Tricks with Operators

What is easier and clearer than arithmetic operators? +, -, *, / are obvious. ++, --, x= (where x is +, -, *) are simple too.
But what about this line:

int i = + + 10;

Spaces are typically ignored by java language parser, so + + should be equal to to ++ and therefore should work as unary increment operator. But 10 is not a variable. It is a literal that cannot be incremented. This means that this code should rise compilation error.

But it passes compilation and when it runs value of variable i is 10.

The question is: why? And the answer is that it is not increment operator. These are 2 unary plus operators and the space between them does its job. The code indeed cannot be compiled without this space.

BTW such unary plus may be repeated unlimited number of times and combined with unary minus operator that obviously changes the result.


Terminology tricks

These tricks are typically used in multi and single-selection tests. Often both question and answers are very long and almost equal, so very small but significant differences must be found. Other type of the questions is based on the fact that people do not remember the exact meaning of terms. Here is an example.

Please select choose the list of words that contains maximal number of java keywords
  1. abstract, continue, true
  2. sealed, foreach, unless
  3. goto, const, strictfp
  4. class, for, null, false
Here is how typical person is thinking:
#2 may be rejected immediately. #1, #2, #3 contain words we use in java every day but #4 is longer, so the right answer is 4.


And this is wrong. true, false and null are not keywords. They are literals (see here). So, #1 and #4 contain only 2 java keywords. What happens with #3? Everyone knows that goto is not supported by java as well as constant values are defined using keyword final. The third word in the list strictfp looks strange at all. However all these words are legal java keywords:

  1. goto and const are reserved but not used (see here).
  2. strictfp was added in java 1.2 (!). It restricts floating point calculations to ensure portability.

3 comments: