java高级程序员面试笔试宝典
第1章 重视基础知识
1.1 不可变类
- 定义:对象一旦创建,成员变量不可被修改
- 例子:所有基本类型的包装类(Integer、Float),String类
- 创建不可变类:
private
修饰成员变量:防止其他类直接访问final
修饰类:确保所有方法不会被子类覆盖- 无
setXXX()
clone()
赋值:如果某个成员变量不是不可变量,如Date
,则成员初始化或者使用get方法获取该成员变量需要通过clone
方法解除引用关系,确保类的不可变性(this.d = (Date)d.clone();
)- 必要时重写
equals()
和hashCode()
1.2 equals()
和hashCode()
hashCode()
Object
类中hashCode()
方法返回对象在内存中地址转换成的int值- 如果没有重写
hashCode()
,任何对象的hashCode()
方法都是不相等的
equals()
和hashCode()
关系
- equals相等,hashCode相等
- hashCode不相等,equals不相等
- hashCode相等,equals不一定相等
- 场景:
HashMap
中判断key是否重复
1.3 Java关键字
static
- 作用:
- 对象分配单一存储空间;
- 属性或方法和类而不是对象绑定;
- 修饰:
- 变量
- 方法
- 代码块:类加载时执行
- 内部类
- 导入包
final
- 修饰:
- 属性:属性不可变(引用不可变,但对象的内容可变)
- 方法:方法不可被覆盖
- 类:类不可被继承
- 匿名内部类为什么可以使用final修饰的局部变量
- final修饰的局部变量在匿名内部类中有一个引用的副本
- 匿名内部类生存期比一半的局部变量更久
- final var
- var仍是和对象绑定,所以在声明、构造方法中初始化,除非用static修饰,否则不能在静态代码块中初始化
transient
- 作用:修饰不想被序列化(serialization)的属性
- 原理:transient修饰的属性,生命周期仅在内存中,表示对象的临时数据,不会被写到磁盘
- 序列化
- 实现方式
- 实现
Serializable
接口(内部无任何方法,用于标记该类的对象支持序列化,可以用ObjectOutputSream
对象的writeObject(Object obj)
写出)- 实现
Exteranlizable
接口,包含readExternal()
和writeExternal()
方法,可以决定哪些属性需要序列化,包括被transient
修饰的属性也可以被序列化- 不可以被序列化的属性
static
修饰的属性:static修饰的属性属于类,序列化是对象属性的序列化transient
修饰的属性:生命周期仅在内存,代表对象的临时数据
volitale
- 作用:修饰被多线程访问的属性,保证修改对所有线程可见
- 原理:
Memory Barrier(内存栅栏)
- 修改:
JVM
先执行Write-Barrier
指令,将CPU中的数据写回内存,并将CPU核心里引用了该内存地址的数据变成脏数据- 读取:
JVM
先执行Read-Barrier
指令,如果数据已经变脏,则从主存中重新获取数据
第2章 再论面向对象
2.1 继承
- 实现多重继承
- 方法1:实现多个接口
- 方法2:定义多个内部类,分别继承不同的父类
- 多态的表现形式
- 方法1:重载,编译时多态
- 方法2:覆盖,运行时多态
注:成员变量无法实现多态,成员变量的值取父类还是子类并不取决于创建对象的类型,而是取决于定义变量的类型,在编译时确定,如Base b = new Derived();
获取b.i
得到的是Base
类的成员变量不是Derived
的
2.2 反射
- 反射的作用
- 获取类的访问修饰符、方法、属性以及父类信息
- 运行时创建对象
- 运行时判断对象属于哪个类
- 生成动态代理
- 获取
Class
对象
Class<?> c = A.class;
不执行静态块和动态构造块Class<?> c = Class.forName("A");
执行静态块,不执行动态构造块Class<?> c = new A().getClass();
即通过Object.getClass()
获取,因为要创建对象所以执行静态块和动态构造块
Class
类的常用方法
- 获取类的成员变量
public Field[] getFields()
public Field getField(String name)
public Field[] getDeclaredFields()
public Field getDeclaredField(String name)
- 获取类的构造方法
public Constructor<?>[] getConstructors()
public Constructor<T> getConstructor(Class<?>...parameterTypes)
public Constructor<?>[] getDeclaredConstructors()
public Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes)
- 获取类的方法
public Method[] getMethods()
public Method getMethod(String name, Class<?>...parameterTypes)
public Method[] getDeclaredMethods()
public Method getDeclaredMethod(String name, Class<?>...parameterTypes)
- 获取内部类
public Class<?>[] getDeclaredClasses();
注:无declared
方法获取public
的...,有declared
的方法获取所有...
-
反射修改
private
成员变量Class<?> clazz = ReadOnlyClass.class; Field field = clazz.getDeclaredField("age"); // 获取private成员变量 field.setAccessible(true); ReadOnlyClass rc = new ReadOnlyClass(); // 具体对象 field.set(rc, 30); // 修改具体对象的private成员变量值
2.3 嵌套类
- 静态内部类
- 不依赖实例
- 不能与外部类有相同的名字
- 只能访问外部类的静态成员和静态方法
- 成员内部类
- 依赖于实例,所以不能定义静态的属性和方法
- 局部内部类
- 不能被
public
、protected
、private
、static
修饰- 局部静态内部类:定义在外部类静态方法或静态初始化代码段种的类
- 匿名内部类
- 属于局部内部类
- 必须继承一个类或实现一个接口
- 不能有构造函数(没用)
- 不能定义静态成员和方法
第3章 泛型
- 泛型:类型参数化
- 泛型的好处
- 代码重用
- 编译时类型检查(无泛型时需要运行时检查类型)
- 容器种存储类型声明时确定,省去类型转换
3.1 基本概念
- 泛型分类
- 泛型接口:接口名后跟
<T1, T2...>
- 泛型类:类名后跟
<T1, T2, ...>
- 泛型方法:返回值前跟
<T1, T2, ...>
泛型方法泛型参数的确定:根据方法参数确定;无参方法通过obj.<xxx>methodName();
指定
- 有界泛型
?
- 指代某一个任意类型,但并不是
Object
extends
- 定义泛型的上界,
T extends UpperBoundClass
super
- 定义泛型的下界,
T super LowerBoundClass
备注:
ArrayList<Parent> alist = new ArrayList<Sub>();
编译时报错T extends A
,反射当成A类来处理,当用反射获取的时候也是通过A类
- 复杂泛型
- 多个泛型参数使用逗号隔开:
<T, K>
- &连接同一个泛型参数的多个上界
- 多个上界类型种最多只能有一个类,其它必须为接口,如果上界里有类,那么必须放置在第一位
- 数组和泛型容器
- 协变性,逆变性,无关性
- 数组具有协变性(容易造成运行时类型不匹配异常),泛型具有无关性(类型不匹配直接编译异常)
- 数组和泛型不能混合使用,
List<String>[] list = new ArrayList<String>[2];
编译时异常
3.2 泛型擦除
- 编译器对泛型的两种实现方式
Code Specialization
:C++
- 每个泛型类实例都生成一份字节码
Code sharing
:Java
- 泛型类的不同实例使用同一份字节码,需要时再进行类型检查(原理:泛型擦除)
-
泛型擦除
-
泛型擦除(类型擦除)
- 由于编译器会在字节码指令集中抹去全部泛型类型信息,所以
Java
泛型不存在于运行时 - 必要的时候添加类型检查和类型转换的方法
- 字节码中仅保留泛型的原始类型
- 由于编译器会在字节码指令集中抹去全部泛型类型信息,所以
-
原始类型:抹去泛型信息后的类型
<T>
对应的原始类型Object
<T extends String>
对应的原始类型String
-
反编译工具
jd-gui
:可以查看泛型擦除后的Java
代码,可以看到做类型转换的地方
-