⭐ 强烈推荐,本文复习参考
https://javaguide.cn/java/basis/java-basic-questions-03.html
⭐ 宝藏网站,适合复习面试
1. 泛型
- 泛型是
JDK5
中引入的一个新特性,它提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。其本质是参数化类型,即将所操作的数据类型指定为一个参数。 - Java的泛型是伪泛型,原因:Java在运行期间,所有的泛型信息都会被擦掉,即
类型擦除
。
List<Integer> list = new ArrayList<>();
// 没问题
list.add(99);
// 这里直接添加字符串"a"会报错
list.add("a");
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
// 但是通过 反射 添加是可以的
// 这就说明:在运行期间所有的泛型信息会被擦除
add.invoke(list, "kl");
System.out.println(list);
- 泛型一般有三种使用方式:泛型类、泛型接口、泛型方法
1.1 泛型类
// 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
// 在实例化泛型类时,必须指定T的具体类型
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
// 实例化泛型类
Generic<Integer> genericInteger = new Generic<Integer>(123456);
1.2 泛型接口
public interface Generator<T> {
public T method();
}
// 实现泛型接口,但不指定类型
class GeneratorImpl<T> implements Generator<T> {
@Override
public T method() {
return null;
}
}
// 实现泛型接口,且指定类型
class GeneratorImpl implements Generator<String> {
public String method() {
return "hello";
}
}
1.3 泛型方法
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.println("%s ", element);
}
System.out.println();
}
// 创建不同类型的数组:Integer、Double、Character
Integer[] intArray = {1, 2, 3};
String[] stringArray = {"Hello", "World"};
printArray(intArray);
printArray(stringArray);
常用通配符:T,E,K,V,?
- ?表示不确定的Java类型
- T 表示具体的一个Java类型
- K V 分别表示Java键值对(key-value)
- E 表示 Element
项目中用到泛型的例子:
- 自定义接口通用返回结果
CommonResult<T>
通过参数T
可根据具体的返回类型动态指定结果的数据类型 - 定义
Excel
处理类ExcelUtil<T>
用于动态指定Excel
导出的数据类型 - 构建集合工具类
2. 反射
反射赋予Java程序运行时分析类以及执行类中方法的能力,通过反射我们可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性。
2.1 反射机制优缺点
- 优点:使代码更加灵活、为各种框架提供开箱即用的功能提供便利
- 缺点:赋予我们运行时分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查,泛型参数的安全检查发生在编译时。此外,反射的性能也要稍微差一点。
2.2 反射的应用场景
Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
// 通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}
PS:注解也用到了反射,可以及与反射分析类,然后获取到类、属性、方法、方法的参数上的注解,再做进一步处理。
3. 注解
Annotation
(注解)是从 Java5 开始引进的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量。
注解本质是一个继承了Annotation
的特殊接口:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
public interface Override extends Annotation {
}
注解只有被解析后才会生效,常见的解析方法有两种:
- 编译期直接扫描:编译器在编译Java代码的时候扫描对应的注解并处理,比如某个方法使用
@Override
注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。 - 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的
@Value
、@Component
)都是通过反射来进行处理的。
JDK 提供了很多内置的注解(比如 @Override
、@Deprecated
),同时,我们还可以自定义注解。
4. I/O
4.1 序列化与反序列化
序列化:将数据结构或对象转换成二进制字节流的过程
反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
transient
关键字:阻止实例中那些用此关键字修饰的变量序列化;当对象被反序列化时,被此关键字修饰的变量值不会被持久化和恢复。
- transient关键字只能修饰变量,不能修饰类和方法。
- transient修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,修饰的是int类型,则反序列化后的结果就是0。
- static变量因为不属于任何对象,所以无论有没有transient关键字修饰,均不会被序列化。
4.2 获取键盘输入
方法一:通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
方法二:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
4.3 Java中IO流
- 按照流的流向分类:输出流、输入流
- 按照操作单元分类:字节流、字符流
- 按照流的角色分类:节点流、处理流
Java IO 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
- InputStream / Reader:所有的输入流的基类,前者是字节输入流,后者是字符输入流
- OutputStream / Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。
【面试题】既然有了字节流,为什么还要有字符流?
问题本质
想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节
,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
回答:字符流
是由 Java 虚拟机将字节
转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等
媒体文件用字节流
比较好,如果涉及到字符的话使用字符流比较好。