一、JavaSE 面试题
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)i = i++; 字节码解析
会先执行 = 号右边的,将 i 压入栈中,操作数栈为 1,然后 i 自增,局部变量表中 i 的值会从 1 变成 2,等号右边操作完成,然后是赋值操作,将操作数栈结果赋值 i 变量,则 i 变量的值为 1。
2)int j = i++; 字节码解析
首先执行等号右边操作,将 i 压入操作数栈中值为 1,然后 i 进行自增操作,局部变量表 i 的值就为 2,执行赋值操作,将操作数栈的 1 赋值给 j ,则局部变量表中 j 的值就为 1。
3)int k = i + ++i * i++; 字节码解析
首先执行等号右边的操作,将 i 压入到操作数栈中值为 2,然后先执行乘法操作,++i 操作是先自增,在局部变量表中,i 的值就为 3,然后将 i 压入到数据栈中值为 3 ,i++ 操作将局部变量表中 i 的值压入到操作数栈中值为3,然后执行自增操作,变量表中 i 的值就为 4,从数据栈中 pop 出 2 个最后压入栈的值进行乘法操作 3 * 3 = 9,然后 push 到栈中,此时栈中的值有两个,一个的最开始压入栈的 2 和 刚刚 push 栈的 9,从栈中取出两个数执行加法操作,结果为 11 ,然后压入栈中赋值给变量表中的 k,此时 k 的值为 11。
执行结果:
i = 4
j = 1
k = 11
总结:
1)赋值 =,最后计算
2)= 右边的从左到右加载值依次压入操作数栈
3)实际先算哪个,看运算符优先级
4)自增、自减操作都是直接修改变量的值,不经过操作数栈
5)最后赋值之前,临时结果也是存储在操作数栈
2、单例设计模式
1、什么是 Singleton ?
- Singleton:在 Java 中即指单例设计模式,它是软件开发中最常用的设计模式之一。
- 单:唯一
- 例:实例
- 单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
- 例如:代表 JVM 运行环境的 Runtime 类。
2、要点
1)某个类只能有一个实例(构造器私有化)
2)它必须自行创建实例( 含有一个该类的静态变量来保存这个唯一的实例)
3)它必须自行向整个系统提供这个实例(对外提供获取该类实例对象的方式直接暴露,用静态变量声明的方法获取)
3、常见的形式
常见单例有三种饿汉式和三种懒汉式共六种。
饿汉式:直接创建对象,不存在线程安全问题。
1)直接实例化饿汉式(简洁直观)
/**
* 直接实例化饿汉式
*/
public class Code_02_Singleton1 {
/**
* 1、构造器私有化
* 2、自行创建,并且用静态变量保存
* 3、向外提供实例
* 4、强调这是一个单例,我们可以用final修改
*/
public static final Code_02_Singleton1 INSTANCE = new Code_02_Singleton1();
private Code_02_Singleton1() {
}
}
2) 静态代码块饿汉式(适合复杂实例化)
/**
* 静态代码块饿汉式(适合复杂实例化)
*/
public class Code_02_Singleton2 {
public static final Code_02_Singleton2 INSTANCE;
static {
INSTANCE = new Code_02_Singleton2();
}
private Code_02_Singleton2() {
}
}
3)枚举式 (最简洁)
/**
* 枚举式 (最简洁)
*/
public enum Code_02_Singleton3 {
/**
* 枚举类型:表示该类型是有限的几个
*/
INSTANCE
}
懒汉式:延迟创建对象。
1)线程不安全(适用于单线程)
/**
* 线程不安全(使用于单线程)
*/
public class Code_02_Singleton4 {
/**
* 1、构造器私有化
* 2、用一个静态变量保存这个唯一的实例
* 3、提供一个静态方法,获取这个实例对象
*/
public static Code_02_Singleton4 instance;
private Code_02_Singleton4() {
}
public static Code_02_Singleton4 getInstance() {
if(instance == null) {
instance = new Code_02_Singleton4();
}
return instance;
}
}
2)双重检查(线程安全,适用于多线程)
/**
* 双重检查(线程安全,适用于多线程)
*/
public class Code_02_Singleton5 {
// 加 volatile 作用:防止指令重排, 当实例变量有修改时,能刷到主存中去是一个原子操作,并且保证可见性。
public static volatile Code_02_Singleton5 instance;
private Code_02_Singleton5() {
}
public static Code_02_Singleton5 getInstance() {
if(instance == null) {
synchronized (Code_02_Singleton5.class) {
if(instance == null) {
instance = new Code_02_Singleton5();
}
}
}
return instance;
}
}
3)静态内部类模式 (适用于多线程)
/**
* 静态内部类模式 (适用于多线程)
*/
public class Code_02_Singleton6 {
/**
* 1、内部类被加载和初始化时,才创建INSTANCE实例对象
* 2、静态内部类不会自动创建, 不会随着外部类的加载初始化而初始化,他是要单独去加载和实例化的
* 3、因为是在内部类加载和初始化时,创建的,因此线程安全
*/
public static class Inner {
private static final Code_02_Singleton6 INSTANCE = new Code_02_Singleton6();
}
private Code_02_Singleton6() {
}
public static Code_02_Singleton6 getInstance() {
return Inner.INSTANCE;
}
}
总结:
1、如果是饿汉式,枚举形式最简单
2、如果是懒汉式,静态内部类形式最简单
3、类初始化和实例初始化
首先看一道题目如下:
1、考点
1)类初始化过程
2)实例初始化过程
3)方法的重写
2、类初始化过程
1)一个类要创建实例需要先加载并初始化该类
- main 方法所在的类需要先加载和初始化
2)一个子类要初始化需要先初始化父类
3)一个类初始化就是执行 <clinit>() 方法。
- <clinit>() 方法由静态变量显示赋值代码和静态代码组成。
- 类变量显示赋值代码和静态代码块代码从上到下顺序执行
- <clinit>() 方法只执行一次
3、实例初始化过程
1)实例初始化就是执行 <init>() 方法
- <init>() 方法可能重载有多个,有几个构造器就有几个 <init>() 方法
- <init>() 方法由非静态实例变量显示赋值代码和非静态代码块,对应构造器代码组成
- 非静态实例变量显示赋