美文网首页
Java面试-高频基础题(附答案与解析)【1】

Java面试-高频基础题(附答案与解析)【1】

作者: 韶华易逝_铭 | 来源:发表于2020-02-12 22:58 被阅读0次

1、求以下这些例子的输出结果(考察点:自增变量):

public static void main(String[] args){
    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);
}

1.1分析:

1、int i = 1, 得i = 1

2、i = i++ ,目前i = 1,将其压入操作数栈,在“=”号返回之前,修改i的值为2,然后“=”将栈中的1赋给i,因此目前i = 1

3、同理上一步,j = 1,i = 2

4、目前i的值为2,将其压入操作数栈,然后遇到了++i,将3压入操作数栈,又遇到i++,也将3压入操作数栈,本地变量i变为4。

所以目前操作数栈:3 3 2。局部变量i = 4,j = 1

最后使用运算符计算值:k = 2 + 3 * 3 = 11

最终结果:

i = 4
j = 1
k = 11 

1.2小结:

1、赋值操作,最后才计算值。

2、等号右边的,从左到右加载值并依次压入操作数栈。

3、实际算哪个要看运算符的优先级。

4、自增、自减操作都是直接改变变量的值,不经过操作数栈。

5、没赋值之前,临时结果存储在操作数栈中。

2、单例模式(考点:多种实现方式,多线程环境)

  • 一个类只有一个实例
    • 构造器私有化
  • 必须是他自己创建实例
    • 必须有一个静态变量保存这个类的唯一实例
  • 必须向系统提供这个实例
    • 提供一个方法给外部获取实例

实现的方式

1.1饿汉式

特点:直接创建对象,不存在线程问题

  • 直接饿汉
  • 枚举类
  • 静态代码块实现饿汉式
/**
 * 直接饿汉:
 * 直接创建对象,不存在线程问题
 *
 */
public class Singleton01 {
    //使用public,而且是final
    public final static Singleton01 INSTANCE = new Singleton01();

    //私有构造
    private Singleton01(){
    }
}
/**
 * 枚举饿汉式
 * 枚举类就是限定若干个实例
 * 我们只限定一个实例就是单例模式
 */
public enum  Singleton02 {
    INSTANCE;
}

/**
 * 静态代码块的饿汉式
 * 在静态代码可以编写一些设置属性的方法,更灵活
 */
public class Singleton03 {
    //使用public,而且是final
    public final static Singleton03 INSTANCE ;

    private String info ;

    static {
        Properties properties = new Properties();
        try {
            //读取配置文件(提前在类路径下,写一个文件singleton/kv.properties)
            properties.load(Singleton03.class.getClassLoader().getResourceAsStream("singleton/kv.properties"));
            //读取值
            String info1 = properties.getProperty("info");
            INSTANCE = new Singleton03(info1);
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }

    private Singleton03(){

    }

    private Singleton03(String info){
        this.info = info;
    }

1.2懒汉式

特点: 延迟创建对象

  • 普通懒汉式,有线程安全问题
  • 基于普通懒汉式,增加双重校验锁避免线程安全问题
  • 用静态内部类实现,也能适应多线程环境
/**
 * 普通懒汉式:
 * 延迟加载,但是有线程安全问题
 */
public class Singleton04 {
    private static Singleton04 instance ;
    private Singleton04(){

    }

    /**
     * 调用获取方法时才加载实例
     * @return
     */
    public static Singleton04 getInstance()  {
        if (null == instance){
            try {
                //设置一些阻碍,更容易暴露多线程中的问题
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Singleton04();
        }
        return instance;
    }
}

public class TestSingleton04 {
    public static void main(String[] args) {
        /**
         * 多线程环境
         */
        Callable<Singleton04> singleton04Callable  = () -> Singleton04.getInstance();
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<Singleton04> submit = executorService.submit(singleton04Callable);
        Future<Singleton04> submit2 = executorService.submit(singleton04Callable);
        try {
            System.out.println(submit.get() == submit2.get());
            System.out.println(submit.get());
            System.out.println(submit2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }

    }
}
/**
 * 双重锁校验的懒汉式:
 * 延迟加载,且线程安全
 */
public class Singleton05 {
    private static Singleton05 instance ;
    private Singleton05(){

    }

    /**
     * 调用获取方法时才加载实例
     * @return
     */
    public static Singleton05 getInstance()  {
        //双重锁校验,提高了性能和安全
        if (null == instance){
            synchronized (Singleton05.class){
                if (null == instance){
                    try {
                        //设置一些阻碍,更容易暴露多线程中的问题
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Singleton05();
                }
            }
        }
        return instance;
    }
}

/**
 * 静态内部类实现的懒汉式:
 * 延迟加载,且线程安全
 */
public class Singleton06 {
    private Singleton06(){

    }

    /**
     * 静态内部类只有在调用时才会加载
     */
    private static class Inner{
        private static Singleton06 instance = new Singleton06();

    }

    /**
     * 调用获取方法时才加载实例
     * @return
     */
    public static Singleton06 getInstance()  {
        return Inner.instance;
    }
}

3、类的初始化和实例的初始化(考点:类是怎么初始化的?实例是怎么初始化的?多态性?)

Q:求出输出结果

public class Father {
    private int i = test();
    private static int j = method();

    static {
        System.out.println("(1)");
    }

    Father(){
        System.out.println("(2)");
    }

    {
        System.out.println("(3)");
    }

    public int test(){
        System.out.println("(4)");
        return 1;
    }

    public static int method(){
        System.out.println("(5)");
        return 1;
    }
}

public class Son extends Father{
    private int i = test();
    private static int j = method();

    static {
        System.out.println("(6)");
    }

    Son(){
        System.out.println("(7)");
    }

    {
        System.out.println("(8)");
    }

    public int test(){
        System.out.println("(9)");
        return 1;
    }

    public static int method(){
        System.out.println("(10)");
        return 1;
    }

    public static void main(String[] args) {
        Son son = new Son();
        System.out.println();
        Son son1 = new Son();
    }
    //解题步骤:

        //第一步:类的初始化:
        //从main方法存在的类开始加载,也就是Son类,但是加载前要先加载他的父类father的<clinit>()方法
        //一个类的初始化就是执行< clinit >()方法,且只会执行一次
        //< clinit >()方法由静态类变量显式赋值代码和静态代码块组成,从上到下按顺序执行
        //所以先输出(5)(1)
        //然后回到子类的初始化,输出(10)(6)

        //第二步:实例的初始化
        //执行实例初始化是执行<init>()方法,<init>()方法可能重载有多个,有几个构造器就有几个< init >()方法
        // < init >()方法由非静态实例变量显式赋值代码和非静态代码块、对应构造器代码组成
        // 非静态实例变量显式赋值代码和非静态代码块从上到下顺序执行,而对应构造器的代码最后执行
        // 每次创建实例对象,调用对应构造器,执行的就是对应的< init >()方法
        // < init >()方法的首行是super()或super(实参列表),即对应父类的< init >()方法
        // 所以执行new Son()时,先执行父类的非静态实例变量显式赋值和非静态代码块
        //执行父类 private int i = test();时,由于该方法已被子类重写,this的指向为子类,
        //因此执行的是子类的method方法,输出(9)
        //然后执行到父类的非静态代码块,输出(3)
        //最后才会执行父类的无参构造函数,输出(2)
        //回到子类,遇到子类的 private int i = test();时,输出(9)
        //执行子类的非静态代码块,输出(8)
        //最后才执行子类的无参构造函数,输出(7)

        /**最终结果:
         (5)
         (1)
         (10)
         (6)
         (9)
         (3)
         (2)
         (9)
         (8)
         (7)

         (9)
         (3)
         (2)
         (9)
         (8)
         (7)
         */
}

解题关键:

类初始化的过程

  • 一个类要创建实例需要先加载并初始化该类
    • main方法所在的类需要先加载和初始化
  • 一个子类的初始化需要先初始化父类
  • 一个类的初始化就是执行< clinit >()方法,且只会执行一次
    • < clinit >()方法由静态类变量显式赋值代码和静态代码块组成
    • 类变量显式赋值代码和静态代码块从上到下按顺序执行
    • < clinit >()方法只执行一次

实例初始化的过程

实例初始化就是执行< init >()方法

  • < init >()方法可能重载有多个,有几个构造器就有几个< init >()方法
  • < init >()方法由非静态实例变量显式赋值代码和非静态代码块、对应构造器代码组成
  • 非静态实例变量显式赋值代码和非静态代码块从上到下顺序执行,而对应构造器的代码最后执行
  • 每次创建实例对象,调用对应构造器,执行的就是对应的< init >()方法
  • < init >()方法的首行是super()或super(实参列表),即对应父类的< init >()方法

4、方法的参数传递机制

Q:以下代码的输出结果是?

/**
 * 参数的传递机制
 */
public class TestParameter {

    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);

        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[] a, MyData m){
        j += 1;
        s += "world";
        n += 1;
        a[0] += 1;
        m.a += 1;
    }

}

class MyData{
    int a = 10;
}

结果:

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

分析:

  • 1、形参是基本数据类型
    • 传递数据值
  • 2、实参是引用数据类型
    • 传递地址值
    • 特色的类型:String、包装类等对象不可变性

5、递归

时间复杂度?
空间复杂度?

Q:求N步的台阶,有多少种走法?

实现方式:

1、递归

    //递归实现 01
    public int f01(int n){
        if (n < 1){
            return 0;
        }
        if (n == 1 || n == 2){
            return n;
        }

        return f01(n-1) + f01(n-2);
    }

2、迭代

    /**
     * 迭代实现 02
     * @param n
     * @return
     */
    public long f02(int n){
        if (n < 1){
            return 0;
        }
        if (n == 1 || n == 2){
            return n;
        }
        //初始化:走第一级台阶有一种走法,走第二级台阶有两种走法
        int first = 1, second = 2;
        int third = 0;
        //把每次计算的结果保存在变量中,避免重复计算
        for (int i = 3; i <= n; i++) {
            third = first + second;
            first = second;
            second = third;
        }
        return third;
    }

相关文章

网友评论

      本文标题:Java面试-高频基础题(附答案与解析)【1】

      本文链接:https://www.haomeiwen.com/subject/qjyhfhtx.html