Summary of classic java interview questions (JavaSE)

Posted by bprof on Thu, 09 Dec 2021 08:50:29 +0100

1, JavaSE interview questions

1.1 self increasing variable

What should the following program output:

public static void main(){
    int i=1;
    i=i++;
    int j=i++;
    int k=i+++i*i++;
    System.out.println("i="+i);
    System.out.println("j="+j);
    System.out.println("k="+k);
}

The answer is:

i=4
j=1
k=11

The execution instructions are:
In the third line, first execute the statement i + + on the right of the assignment operator. The value of i is 1. First press 1 into the operand stack, and then i increases to 2; Then execute the assignment statement to assign the value 1 in the operand stack to i. at this time, the value of i is 1 and the operand stack is empty.
In the fourth line, first execute the statement i + + on the right of the assignment operator. The value of i is 1. First press 1 into the operand stack, and then i increases to 2; Then execute the assignment statement to assign the value 1 in the operand stack to j. at this time, the value of j is 1 and the operand stack is empty.
On the fifth line, first execute the statement i+++i*i + + on the right of the assignment operator and put it on the stack from left to right. The value of i is 2. First push 2 into the operand stack; Then execute the multiplication operation + + i*i + +, first execute + + i, i increases to 3, and press 3 into the operand stack; Then execute i + +, first press 3 into the operand stack, and then i increases to 4; Then perform multiplication, multiply the two numbers 3 and 3 at the top of the stack to get 9, then perform the addition operation 9 + 2 = 11, and finally perform the assignment operation to assign 11 to k.
So the final result is i=4, j=1, k=11;

Summary:

  • Final operation of assignment;
  • The statement on the right of the assignment loads the value from left to right, so as to push it into the operand stack;
  • Which is actually calculated first depends on the operator priority;
  • Both auto increment and auto decrement operations directly modify the value of a variable without going through the operand stack;
  • Before the final assignment, the temporary results are stored in the operand stack.
  • It is recommended to read the instructions in the JVM virtual machine specification.

1.2 single case design mode

Programming problem: write a Singleton instance.

What is Singleton?

  • In java, it refers to singleton design pattern, which is one of the most commonly used patterns in software development.
  • A single instance is the only instance.
  • Singleton design pattern is a code pattern in which only one instance object of a class can be obtained or used in the whole system.
  • For example, the Runtime class representing the JVM Runtime environment.

What's the point of Singleton?

  • First, a class can only have one instance.
    • Constructor privatization.
  • Second, he must create this instance himself.
    • Save this instance with a static variable that contains one of the classes.
  • Third, it must provide this example to the whole system by itself.
    • Direct exposure.
    • Get with the get method of static variables.

Several common patterns of Singleton?

  • Hungry Chinese style: directly create objects, and there is no thread safety problem.
    • Directly instantiate hungry Chinese style (concise and intuitive).
    • Enumeration (the most concise).
    • Static code block (suitable for complex instantiation).
  • Lazy: delay the creation of objects.
    • Thread unsafe (for single thread).
    • Thread safe (for multithreading).
    • Static internal class form (for multithreading).

Hungry Han style creation method I:

/**
 * It is created directly when the class is initialized, regardless of whether the instance is required or not.
 */
public class Singleton01 {
    //Emphasize with final that this is a singleton pattern
    public static final Singleton01 INSTANCE=new Singleton01();
    private Singleton01(){
    }
}

Hungry Han style creation method II:

/**
 * Enumeration type: indicates that there are a limited number of objects of this type.
 * Limiting one becomes a single example.
 */
public enum Singleton01{
    INSTANCE
}

Hungry Han style creation method III:

/**
 * It is generally used to pass parameters during initialization
 * For example, read attributes from a file in a static code block.
 */
public class Singleton03{
    private static final Singleton03 INSTANCE;
    static{
        INSTANCE=new Singleton03();
    }
    private Singleton03(){
    }
}

Lazy creation method I:

public class Singleton04{
    private static Singleton04 instance;
    private Singleton04(){
    }
    //Create an instance when this method is called (thread unsafe)
    public Singleton04 getInstance(){
        if(instance==null){
            instance=new Singleton04();
        }
        return instance;
    }
}

Lazy creation method 2:

public class Singleton05{
    private static Singleton05 instance;
    private Singleton05(){
    }
    public Singleton05 getInstance(){
        if(instance==null){
            //Add synchronization lock, thread safety
            synchronized(Singleton05.class){
                instance=new Singleton05();
            }
        }
        return instance;
    }
}

Lazy creation method 3:

/**
 * Static inner classes are not initialized with the loading and initialization of outer classes,
 * It needs to be loaded and initialized separately.
 */
public class Singleton06{
    private Singleton06(){}
    private static class Inner(){
        private static final Singleton06 INSTANCE=new Singleton06();
    }
    public static getInstance(){
        return Inner.INSTANCE;
    }
}

1.3 class initialization and instance initialization

What is the result of running the following code?
Code 1:

public class Father {
    private int i=test();
    private static int j=method();
    static {
        System.out.print("(1)");
    }
    Father(){
        System.out.print("(2)");
    }
    {
        System.out.print("(3)");
    }
    public int test(){
        System.out.print("(4)");
        return 1;
    }
    public static int method(){
        System.out.print("(5)");
        return 1;
    }
}

Code 2:

public class Son extends Father{
    private int i=test();
    private static int j=method();
    static {
        System.out.print("(6)");
    }
    Son(){
        System.out.print("(7)");
    }
    {
        System.out.print("(8)");
    }
    public int test(){
        System.out.print("(9)");
        return 1;
    }
    public static int method(){
        System.out.print("(10)");
        return 1;
    }

    public static void main(String[] args) {
        Son s1=new Son();
        System.out.println();
        Son s2=new Son();
    }
}

Initialization process of analysis class:

  1. To create an instance of a class, you need to load and initialize the class first.
    • The class where the main method is located needs to be loaded and initialized first.
  2. To initialize a subclass, you need to initialize the parent class first.
  3. A class initialization is to execute the < clinit > () method, which is automatically generated by the compiler.
    • The < clinit > () method consists of static class variable explicit assignment code and static code block.
    • The above two codes are executed in order from top to bottom.
    • The < clinit > () method is executed only once.

When the main method is empty, the executing program will initialize the class. The execution sequence is as follows:

  1. System.out.println("(5)");
  2. System.out.println("(1)");
  3. System.out.println("(10)");
  4. System.out.println("(6)");
    At this time, the input result is: (5) (1) (10) (6)

    Analysis instance initialization process:
  5. Instance initialization is to execute the < init > () method.
    • The method may have multiple overloaded methods, and there are several < init > () methods with several constructors.
    • The < init > () method consists of non static instance variable display assignment code, non static code block and corresponding constructor code.
    • The first two code blocks are executed in order from top to bottom, and their corresponding constructor code is executed last.
    • Each time an instance object is created and the corresponding constructor is called, the corresponding < init > () method is executed.
    • The first line of the < init > () method is super() or super (argument list), that is, the < init > () method of the corresponding parent class.

When the class is instantiated, the execution sequence is as follows:

  1. super() (it will be called no matter whether it is written or not, and it must be at the top)
  2. Non static instance variables display assignment codes.
  3. Non static code block.
  4. Constructor code. (must be implemented finally)
    Note: steps 2 and 3 are executed from top to bottom according to the actual sequence.

In the above code program, method Override is also involved:

  1. Which methods cannot be overridden?
    • final method.
    • Static method.
    • Methods that are not visible in subclasses such as private.
  2. What is the polymorphism of objects?
    • If a subclass overrides the method of the parent class, the method overridden by the subclass must be called through the subclass.
    • The default calling object for non static objects is this.
    • this object is the object being created in the constructor or in the < init > () method.

To sum up, the final execution result of the program is:

(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
Since two instance objects were created, < init > () was executed twice.

1.4 parameter transfer mechanism of method

What is the result of running the following code:

public class exam01 {
    public static void main(String[] args) {
        int i=1;
        String str="hello";
        Integer num=200;
        int[] arr={1,2,3,4,5};
        MyData my=new MyData();
        change(i,str,num,arr,my);//Argument list
        System.out.println("i="+i);
        System.out.println("str="+str);
        System.out.println("num="+num);
        System.out.println("arr="+ Arrays.toString(arr));
        System.out.println("my.a="+my.a);
    }
    public static void change(int j,String s,Integer n,int[] ar,MyData m){//parameter list 
        j+=1;
        s+="world";
        n+=1;
        ar[0]+=1;
        m.a+=1;
    }
}
class MyData{
    int a=10;
}

First, explain the parameter transmission mechanism of the method:

  1. Are formal parameters basic data types?
    • Pass data values.
  2. Is the argument a reference data type?
    • Pass address value.
    • Objects such as special type String and wrapper class are immutable.

Local variables are stored in their respective method stacks. For example, the argument list of main method is stored in stack 1, and the formal parameter list of change method is stored in stack 2. Analyze each transfer from argument to formal parameter:

  1. i passes the value to j, the value of J changes, and the value of i remains the same.
  2. The str value is stored in the constant pool. STR points to the constant pool "hello". When STR passes the address value to s, s also points to the constant pool "hello"; S generates string splicing. At this time, new constants "world" and "helloworld" are generated in the constant pool. S points to "helloworld" again, and STR remains the same.
  3. The value of num is stored in the heap. Num passes the address value to n, and both num and n point to 200; The value of n changes and re points to 201 in the heap, and the value of num remains the same.
  4. arr passes the address value to ar. ar changes the value of ar[0] according to the address. If the address remains unchanged, the arr[0] in the arr pointing to the same address changes.
  5. My passes the address value to M. m changes the value of a according to the address. If the address remains unchanged, the value of a attribute in my pointing to the same address changes.

To sum up, the results of this procedure are as follows:

i=1
str=hello
num=200
arr=[2, 2, 3, 4, 5]
my.a=11

1.5 recursion and iteration

Programming question: there are n steps. You can only go one or two steps at a time. How many walking methods are there?
First, analyze the law:

  1. When n is 1 and 2, there are n walking methods;
  2. When it is greater than 2, let the walking method be f(n), then f(n)=f(n-1)+f(n-2).
    Therefore, there are generally two methods: recursion and loop iteration.
    Recursion:
public int steps(int n){
    if(n==1||n==2){
        return n;
    }
    return steps(n-1)+steps(n-2);
}

Recursion is to repeat a certain work and consider extracting the repeated steps for circulation. There are "current state = previous step state + previous two step states". You can use one to save the previous step state and two to save the first two step States, and then add them circularly.
Iteration:

public int steps(int n){
    if(n==1||n==2){
        return n;
    }
    int one=2;
    int two=1;
    int sum=0;
    for(int i=3;i<=n;i++){
        sum=one+two;
        two=one;//Save the status of the first two steps
        one=sum;//Save previous step status
    }
    return sum;
}

Summary:

  • The method call itself is called recursion, and using the original value of the variable to deduce a new value is called iteration.
  • Recursion:
    • Advantages: large problems, small problems, small and concise code, good readability.
    • Disadvantages: recursive calls waste space. Too deep recursion is easy to cause stack overflow.
  • Iteration:
    • Advantages: high operation efficiency, time complexity only increases with the increase of the number of cycles, and there is no additional space overhead.
    • Disadvantages: the code is not concise and the readability is poor.

1.6 member variables and local variables

What are the results of the following programs:

public class Exam02 {
    static int s;
    int i;
    int j;
    {
        int i=1;
        i++;
        j++;
        s++;
    }
    public void test(int j){
        j++;
        i++;
        s++;
    }
    public static void main(String[] args) {
        Exam02 obj1=new Exam02();
        Exam02 obj2=new Exam02();
        obj1.test(10);
        obj1.test(20);
        obj2.test(30);
        System.out.println(obj1.i+","+obj1.j+","+obj1.s);
        System.out.println(obj2.i+","+obj2.j+","+obj2.s);
    }
}

First, clarify the difference between local variables and member variables:

  1. The position of the declaration is different.
    • Local variable: in method body, code block and formal parameter;
    • Member variable: outside the method in the class.
      • Class variable: static modification;
      • Instance variable: no static modifier.
  2. Modifiers are different.
    • Local variable: final.
    • Member variables: public,protected,private,final,static,volatile,transient
  3. Values are stored in different locations.
    • Local variable: stack;
    • Instance variable: heap;
    • Class variable: method area.
  4. Different scopes.
    • Local variable: from declaration to} end.
    • Instance variable: accessed with "this." in the current class (sometimes omitted), and accessed with "object name." in other classes.
    • Class variable: access with "class name." in the current class (sometimes omitted), and access with "class name." or "object name." in other classes.
  5. The life cycle is different.
    • Local variable: for each thread, each call execution is a new life cycle.
    • Instance variable: it is initialized with the creation of the object and dies with the recycling of the object. Each instance variable is independent.
    • Class variable: it is initialized with the initialization of the class and dies with the unloading of the class. All class variables of the class are shared.

Execution process of reanalysis program:

  1. Firstly, the obj1 variable is instantiated, the < init > () method is executed (see Section 1.3), and the non static code block is executed. Where i is the local variable, i + + is the self increment of the local variable (the variable with the same name and without this.) and will die after the end of the code block; j + + is the self increment of member variables, and the j value is 1; s is the self increment of class variables, which is shared in the whole class, and the value of s is 1.
  2. obj2 variable is instantiated, the < init > () method is executed, and the non static code block is executed. The value of i is the same as above; j + + is the self increment of member variables, and the j value is 1; s is the self increment of class variables, which is shared in the whole class, and the value of s is 2.
  3. obj1 calls the test method, passes 10 to j, where j is a local variable and dies after the method ends; i + + is the self increment of member variable (implicit this.), and the value of i is 1; s is the self increment of class variables, which is shared in the whole class, and the value of s is 3.
  4. obj1 calls the test method and passes 20 to j, where j is a local variable and dies after the method ends; i + + is the self increment of member variable (implicit this.), and the value of i is 2; s is the self increment of class variables, which is shared in the whole class, and the value of s is 4.
  5. obj2 calls the test method and passes 30 to j, where j is a local variable and dies after the method ends; i + + is the self increment of member variable (implicit this.), and the value of i is 1; s is the self increment of class variables, which is shared in the whole class, and the value of s is 5.
    To sum up, the code running result is:

2,1,5
1,1,5

Small test point: how to distinguish when the local variable and xx variable have the same name?

  1. Local variable and instance variable have the same name:
    • Add "this." before the instance variable.
  2. Local variable and class variable have the same name:
    • Add "class name." before the class variable.

Topics: Java Back-end Interview Singleton pattern