Skip to content

Latest commit

 

History

History
707 lines (485 loc) · 17.3 KB

面试题.md

File metadata and controls

707 lines (485 loc) · 17.3 KB

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

i = 4

j = 1

k = 11

局部变量表

int i
int j
int k

加减乘除进入操作数栈

赋值操作改变局部变量表

过程:

  1. 局部变量表int i = 1;
  2. i++
    1. i的值压入操作数栈(入栈)
    2. i变量自增1(局部变量表i = 2
    3. 把操作数栈中的值赋值给i(出栈i = 1
  3. int j = i++;
    1. i的值压入操作数栈
    2. i变量自增1(局部变量表i = 2
    3. 把操作数栈中的值赋值给j(出栈i = 1

小结:

  • 赋值=,最后计算
  • =右边的从左到右加载值依次压入操作数栈
  • 实际先算哪个,看运算符优先级
  • 自增、自减操作都是直接修改变量的值,不经过操作数栈
  • 最后的赋值之前,临时结果也是存储在操作数栈中

2、单例设计模式

编程题:编写一个Singleton示例

Singleton概念:

  • Singleton:在Java中即指单例设计模式,它是软件开发中最常用的设计模式之一。
  • 单:唯一
  • 例:实例
  • 单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
  • 例如:代表JVM运行环境的Runtime类

要点:

  • 某个类只能有一个实例
    • 构造去私有化
  • 它必须自行创建这个实例
    • 含有一个该类的静态变量来保存这个唯一的实例
  • 它必须自行向整个系统提供这个实例
    • 对外提供获取该实例对象的方式
      1. 直接暴露
      2. 用静态变量的get方法获取

几种常见形式:

饿汉式

  • 饿汉式:直接创建对象,不存在线程安全问题

    • 直接实例化饿汉式(简洁直观)

      /**
      * 1、构造器私有化 
      * 2、自行创建,并且用静态变量保存
      * 3、向外提供这个实例
      * 4、强调这是一个单实例,用final修饰
      */
      public class Singeton1 {
          public static final Singeton1 INSTANCE = new Singleton1();
          private Singleton1() {
      
          }
      }
    • 枚举式(最简洁)

      /**
      * 枚举类型,表示该类型的对象是有限的几个
      * 我们可以限定为一个,就成了单例
      */
      public enum Singleton2 {
          INSTANCE
      }
    • 静态代码块饿汉式(适合复杂实例化)

      public class Singeton3 {
          public static final Singeton3 INSTANCE;
          static {
          	INSTANCE = new Singleton3();
          }
          private Singleton3() {
      
          }
      }

懒汉式

  • 懒汉式:延迟创建对象

    • 线程不安全(适用于单线程)

      /**
      * 1、构造器私有化 
      * 2、自行创建,并且用静态变量保存
      * 3、向外提供这个实例
      */
      public class Singleton4 {
          private static Singleton4 instance;
          private Singleton4() {
              
          }
          public static Singleton4 getInstance() {
              if (instance == null) {
                  instance = new Singleton4();
              }
              return instance;
          }
      }
    • 线程安全(适用于多线程)

      /**
      * 1、构造器私有化 
      * 2、自行创建,并且用静态变量保存
      * 3、向外提供这个实例
      */
      public class Singleton5 {
          private static Singleton5 instance;
          private Singleton5() {
              
          }
          public static Singleton5 getInstance() {
              if (instance == null) {
                  synchronized (Singleton5.class) {
                      if (instance == null) {
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedExcption e) {
                              e.printStackTrace();
                          }
                          instance = new Singleton5();
                      }
              	}
              }
              return instance;
          }
      }
    • 静态内部类形式(适用于多线程)

      在内部类被加载和初始化的时候,才会创建INSTANCE实例对象

      内部静态类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的

      因为是在内部类加载和初始化时,创建的,因此是线程安全的

      public class Singleton6 {
          private Singleton6() {
              
          }
          private static class Inner {
              private static final Singleton6 INSTANCE = new Singleton6();
          }
          public static Singleton6 getInstance() {
              return Inner.INSTANCE;
          }
      }

小结

  • 如果是饿汉式,枚举形式最简单
  • 如果是懒汉式,静态内部类形式最简单

3、类初始化和实例初始化等

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

分析:先初始化父类,再初始化子类

父类:

  1. private static int j = method();

  2. static {
            System.out.print("(1)");
        }

子类:

  1. private static int j = method();

  2. static {
            System.out.print("(1)");
        }

类初始化过程

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

实例初始化过程

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

在子类构造器中一定会调用父类构造器,super();一定在子类构造器(写或不写都在)

子类实例化方法:

  1. super()(最前)
  2. i = test();
  3. 子类的非静态代码块
  4. 子类的无参构造器(最后)

父类实例化方法:

  1. super()(最前)
  2. i = test();
  3. 子类的非静态代码块
  4. 子类的无参构造器(最后)

非静态方法前面有一个默认的对象this

this在构造器(或<init>)它表示的是正在创建的对象,因为这里是在创建Son对象,所以test()执行的是子类重写的代码(面向对象多态)

这里i = test();执行的是子类重写的test()方法

方法的重写Override

  1. 哪些方法不可以被重写
    • final 方法
    • 静态方法
    • private 等子类中不可见方法
  2. 对象的多态性
    • 子类如果重写了父类的方法,通过子类对象掉调用的一定是重写过的代码
    • 非静态方法默认的调用对象是 this
    • this 对象在构造器或者说<init>方法中就是正在创建的对象

Override 和 Overload的区别?

Override 重写的要求?

  • 方法名
  • 形参列表
  • 返回值类型
  • 抛出的异常列表
  • 修饰符

4、方法的参数传递机制

public class Exam4 {
    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 = " + my);

    }

    private 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;
}
  1. 形参是基本数据类型
    • 传递数据值
  2. 实参是引用数据类型
    • 传递地址值
    • 特殊的类型:String、包装类等对象不可变性

5、递归与迭代

编程题:有n步台阶,一次只能上1步或2步,共有多少种走法?

  1. 递归
  • n=1 -> 一步f(1) = 1
  • n=2 -> (1)一步一步;(2)直接2步f(2) = 2
  • n=3 -> (1)先到达f(1),然后从f(1)直接跨2步;(2)先到达f(2),然后从f(2)跨1步f(3) = f(1)+f(2)
  • n=4 -> (1)先到达f(2),然后从f(2)直接跨2步;(2)先到达f(3),然后从f(3)跨1步f(4) = f(2)+f(3)
  • ...
  • n=x -> (1)先到达f(x-2),然后从f(x-2)直接跨2步;(2)先到达f(x-1),然后从f(x-1)跨1步f(x) = f(x-2)+f(x-1)

实现:

public static int go(int n) {
    if (n < 1) {
        throw new IllegalArgumentException(n + "不能小于1");
    }
    if (n == 1) {
        return 1;
    }
    if (n == 2) {
        return 2;
    }
    return go(n - 1) + go(n - 2);
}
  1. 循环迭代
public static int loop(int n) {
    if (n < 1) {
        throw new IllegalArgumentException(n + "不能小于1");
    }
    if (n == 1 || n == 2) {
        return n;
    }
    int one = 2;
    int two = 1;
    int sum = 0;
    // 最后一步计算,有f(n)=f(n-2)+f(n-1)->f(n+1)=f(n-1)+f(n)
    // 最后一步永远是前两步的和
    for (int i = 3; i <= n; i++) {
        sum = one + two;
        two = one;
        one = sum;
    }
    return sum;
}

6、成员变量与局部变量

public class Exam5 {

    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) {
        Exam5 obj1 = new Exam5();
        Exam5 obj2 = new Exam5();
        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);
    }

}

区别:

  1. 声明的位置
    • 局部变量:方法体中{}中,形参,代码块{}
    • 成员变量:类中方法外
      • 类变量:有static修饰
      • 实例变量:没有static修饰
  2. 修饰符
    • 局部变量:fianl
    • 成员变量:publicprotectedprivatefinalstaticvolatiletransient
  3. 值存储的位置
    • 局部变量:栈
    • 实例变量:堆
    • 类变量:方法区
  4. 作用域
    • 局部变量:从声明处开始,到所属}结束
    • 实例变量:在当前类中this.(有时this.可以缺省),在其他类中对象名.访问
    • 类变量:当前类中类名.(有时类名.可以省略),在其他类中类名.对象名.访问
  5. 生命周期
    • 局部变量:每一个线程,每一次调用执行都是新的生命周期
    • 实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量是独立的
    • 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的

7、spring中bean的作用域

8、常用数据库事务传播属性和事务隔离级别

请简单介绍Spring支持的常用数据库事务传播属性和事务隔离级别?

事务属性:

  1. propagation

    传播属性 描述
    REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事物,并在自己的事务内运行
    REQUIRES_NEW 当前的方法必须启动新事物,并在它自己的事务内运行,如果有事务正在运行,应该把它挂起
    SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
    NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
    MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
    NEVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
    NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内部运行,否则,就启动一个新的事务,并在它自己的事务内运行

    事务的传播属性可以在@Transactional注解的propagation属性中定义。

  2. isolation

    用来设置事务的隔离级别 Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别 Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别

数据库并发问题:

假设现在有两个事务:Transaction01和Transaction02并发执行。

  1. 脏读 ①Transaction01 将某条记录的 AGE 值从 20 修改为 30。 ②Transaction02 读取了 Transaction01 更新后的值:30。 ③Transaction01 回滚,AGE 值恢复到了 20。 ④Transaction02 读取到的 30 就是一个无效的值。

  2. 不可重复读 ①Transaction01 读取了 AGE 值为 20。 ②Transaction02 将 AGE 值修改为 30。 ③Transaction01 再次读取 AGE 值为 30,和第一次读取不一致。

  3. 幻读

①Transaction01 读取了 STUDNENT 表中的一部分数据。 ②Transaction02 向 STUDENT 表中插入了新的行。 ③Transaction01 读取了 STUDENT 表时,多出了一些行。

事务的隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。**==一个事务与其他事务隔离的程度称为隔离级别。==**SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

1)读未提交:READ UNCOMMITTED 允许 Transaction01 读取 Transaction02 未提交的修改。

2)读已提交:READ COMMITTED 要求 Transaction01 只能读取 Transactiont02 己提交的修改。

3)可重复读:REPEATABLE READ 确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其它事务对这个字段进行更新。

4)串行化:SERIALIZABLE 确保 Transactiont01 可以多次从一个表中读取到相同的行,在 Transaction01 执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

9、SpringMVC中post请求中文乱码问题

CharacterEncodingFilter类初始化encoding属性为UTF-8

web.xml

<filter>
	<filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    	<param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<servlet>
	...
</servlet>

10、SpringMVC简单的工作流程

处理模型数据

方式一

将方法的返回值设置为ModelAndView

方式二

方法的返回值是String类型,在方法的参数中传入MapModel或者ModelMap

11、MyBatis中当实体类中的属性名和表中的字段名不一样

1、SQL语句写别名

2、在MyBatis的全局配置文件中开启驼峰命名规则

<settings>
	<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

3、在Mapper映射文件中使用resultMap来定自义映射规则