每日一题 - 202403
3-31
获取 class 对象有哪些方式?
- 通过类名获取:
Class.forName("com.example.User")
- 通过类对象获取:
User.class
- 通过对象获取:
new User().getClass()
- 通过类加载器获取:
ClassLoader.getSystemClassLoader().loadClass("com.example.User")
3-30
反射的应用场景有哪些?
- 动态代理: 反射可以在运行时动态创建代理类,实现动态代理。
- 工厂模式: 反射可以通过类名动态创建对象,实现工厂模式。
- 注解: 反射可以获取类、方法、字段等的注解信息,实现注解功能。
- 框架: 反射可以实现框架的灵活性,如 Spring 框架的依赖注入。
3-29
什么是通配符?
泛型类型是固定的,在某些场景下的使用不够灵活,所以引入了通配符 ?
,通配符可以用来表示未知类型。
如 List<?>
表示未知类型的 List 集合,? extends T
表示类型的上界,? super T
表示类型的下界。
// 通配符
public void print(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
// 上界通配符
public void print(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
// 下界通配符
public void print(List<? super Number> list) {
for (Object o : list) {
System.out.println(o);
}
}
3-28
什么是泛型擦除机制?
Java的泛型是伪泛型,因为在编译期间会擦除所有泛型信息,即泛型擦除。
编译期间会动态地将泛型 T
擦除为 Object
或将 T extends xxx
擦除为限定类型 xxx
。
引入泛型是为了增强代码的可读性以及稳定性,但为了保证引入泛型机制但不创建新的类型,减少虚拟机的开销,所以在编译期间会将泛型擦除。
3-27
泛型的使用方式有哪些?
1、泛型类
// HTTP 请求响应类
public class CommonResponse<T> {
// 状态码
private int code;
// 消息
private String message;
// 数据
private T data;
// getter 泛型方法
public T getData() {
return data;
}
// setter
// ...
}
如上方实例,HTTP请求的数据响应类型并不能在编写代码时确定,所以使用泛型类来定义。
2、泛型方法
// 泛型方法
public <T> T get(T t) {
return t;
}
泛型方法可以在方法调用时指定具体的类型,上方泛型类实例中,data 属性的 getter 方法即为泛型方法。
3、泛型接口
// 泛型接口
public interface Generator<T> {
T next();
}
// 实现泛型接口,指定类型
public class IntegerGenerator implements Generator<Integer> {
@Override
public Integer next() {
return 1;
}
}
// 实现泛型接口,不指定类型
public class GeneratorImpl implements Generator<T> {
@Override
public T next() {
return "Hello";
}
}
3-26
什么是 AIO?
AIO(Asynchronous IO)异步 IO 模型是 JDK7 引入的异步非阻塞 IO。服务器实现模式为一个有效请求对应一个线程, 客户端的 IO 请求都是由操作系统先完成 IO 操作后再通知服务器应用来直接使用准备好的数据。 适用连接数目多且连接时间长的场景。
3-25
什么是 NIO?
NIO(Non-blocking IO)非阻塞 IO 是一种半同步、半异步的IO模型,在进行IO操作时,程序不会被完全阻塞,但是仍然需要主动轮询IO状态。
在 NIO 中,可以使用选择器(Selector)来管理多个通道(Channel),以达到同时处理多个 IO 操作的目的。
3-24
什么是 BIO?
BIO(Blocking IO)阻塞IO是一种同步的 IO 模型,当一个线程从流中读取或写入数据时,它会一直阻塞直到数据完全被读取或写入。 在 BIO 中,每个 I/O 操作都会阻塞当前线程,直到数据准备好或者操作完成。
3-23
JAVA 中有哪些流?
- IO 流 数据传输的抽象,对文件中内容进行读写操作。包括输入和输出流,又根据传输数据类型分为字节流、字符流。
- Stream 流 JAVA 8 新特性,基于函数式编程操作处理数据,元素流在管道中经过中间操作(intermediate operation)的处理, 最后由最终操作(terminal operation)得到前面处理的结果。
3-22
什么是建造者模式?
建造者模式是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的方式进行创建。 工厂类模式是提供的是创建单个类的产品, 而建造者模式则是将各种产品集中起来进行管理,用来具有不同的属性的产品
// 产品
class Product {
private String partA;
private String partB;
private String partC;
public void setPartA(String partA) {
this.partA = partA;
}
public void setPartB(String partB) {
this.partB = partB;
}
public void setPartC(String partC) {
this.partC = partC;
}
public void show() {
System.out.println(partA + partB + partC);
}
}
// 抽象建造者
abstract class Builder {
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public abstract Product getResult();
}
// 具体建造者
class ConcreteBuilder extends Builder {
private Product product = new Product();
@Override
public void buildPartA() {
product.setPartA("A");
}
@Override
public void buildPartB() {
product.setPartB("B");
}
@Override
public void buildPartC() {
product.setPartC("C");
}
@Override
public Product getResult() {
return product;
}
}
// 指挥者
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
// 测试
public class Test {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
Product product = director.construct();
product.show();
}
}
3-22
什么是动态代理?
动态代理是指在运行时动态生成代理类,通过 Java 的反射机制动态生成代理类,然后在代理类中实现接口方法,实现对目标对象的代理访问。
//接口
public interface UserDao {
void save();
}
//接口实现类
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("保存数据方法");
}
}
// 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对象
public class InvocationHandlerImpl implements InvocationHandler {
// 这其实业务实现类对象,用来调用具体的业务方法
private Object target;
// 通过构造函数传入目标对象
public InvocationHandlerImpl(Object target) {
this.target = target;
}
//动态代理实际运行的代理方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用开始处理");
//下面invoke()方法是以反射的方式来创建对象,第一个参数是要创建的对象,第二个是构成方法的参数,由第二个参数来决定创建对象使用哪个构造方法
Object result = method.invoke(target, args);
System.out.println("调用结束处理");
return result;
}
}
//测试
public class Test {
public static void main(String[] args) {
// 被代理对象
UserDao userDaoImpl = new UserDaoImpl();
InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDaoImpl);
//类加载器
ClassLoader loader = userDaoImpl.getClass().getClassLoader();
Class<?>[] interfaces = userDaoImpl.getClass().getInterfaces();
// 主要装载器、一组接口及调用处理动态代理实例
UserDao newProxyInstance = (UserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
newProxyInstance.save();
}
}
3-21
什么是静态代理?
静态代理是指在编译时就已经确定代理的对象,通过在代码中显式定义一个代理类,代理类通过实现与目标对象相同的接口 然后在代理类中维护一个目标对象,通过构造方法或者其他方式传入目标对象,同时在代理类中实现接口方法,实现对目标对象的代理访问。
示例:
// 接口
interface ISubject {
void request();
}
// 目标对象
class RealSubject implements ISubject {
@Override
public void request() {
System.out.println("RealSubject request");
}
}
// 代理对象
class ProxySubject implements ISubject {
private ISubject realSubject;
public ProxySubject(ISubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("ProxySubject before request");
realSubject.request();
System.out.println("ProxySubject after request");
}
}
// 测试
public class Test {
public static void main(String[] args) {
ISubject realSubject = new RealSubject();
ISubject proxySubject = new ProxySubject(realSubject);
proxySubject.request();
}
}
3-20
代理模式的分类?
静态代理: 简单代理模式,是动态代理的理论基础。常见使用在代理模式 jdk动态代理: 使用反射完成代理。需要有顶层接口才能使用,常见是mybatis的mapper文件是代理。 cglib: 第三方动态代理,使用反射完成代理,可以直接代理类(jdk动态代理不行),使用字节码技术,不能对 final类进行继承。
3-19
什么是代理模式?
通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理/添加新的功能。(也就是AOP的微实现)
代理在原有代码乃至原业务流程都不修改的情况下,直接在业务流程中切入新代码,增加新功能,这也和 Spring 的(面向切面编程)很相似
3-18
什么是工厂模式?
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。 工厂模式提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。 实现了创建者和调用者分离,工厂模式分为简单工厂、工厂方法、抽象工厂模式。
3-17
单例模式有哪些创建方式?
- 饿汉式: 类初始化时,会立即加载该对象,线程天生安全,调用效率高。
- 懒汉式: 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
- 静态内部方式: 结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
- 枚举单例: 使用枚举实现单例模式 优点:实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。
3-16
什么是合成复用原则?
- 原则思想: 它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
- 描述: 合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
- 优点: 它维持了类的封装性,新旧类之间的耦合度低,复用的灵活性高。
3-15
什么是迪米特法则?
迪米特法则又称最少知识原则。
- 原则思想: 一个对象应该对其他对象保持最少的了解。
- 描述: 一个对象应该对其他对象保持最少的了解,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都应该将逻辑封装在类的内部,对外提供的方法越少越好。
- 优点: 迪米特法则可以减少类间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性。
3-14
什么是接口隔离原则?
- 原则思想: 使用多个专门的接口,而不使用单一的总接口。
- 描述: 接口隔离原则是指客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
- 优点: 接口隔离原则可以减少类间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性。
3-13
什么是依赖倒置原则?
- 原则思想: 面向接口编程,依赖于抽象而不依赖于具体。
- 描述: 依赖倒置原则是指在设计软件结构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
- 优点: 依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性。
3-12
什么是里氏替换原则?
- 原则思想: 使用的基类可以在任何地方使用继承的子类,完美的替换基类。
- 描述: 子类可以扩展父类的功能,但不能改变父类原有的功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。
- 优点: 增加程序的健壮性,即使增加了子类,原有的子类还可以继续运行,互不影响。
3-11
什么是开放封闭原则?
- 思想: 尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化
- 描述: 一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性。
- 优点: 单一原则告诉我们,每个类都有自己负责的职责,里氏替换原则不能破坏继承关系的体系。
3-10
设计模式的六大原则?
- 开闭原则:对扩展开放,对修改关闭。
- 里氏替换原则:子类可以替换父类。
- 依赖倒置原则:面向接口编程,依赖于抽象而不依赖于具体。
- 接口隔离原则:使用多个专门的接口,而不使用单一的总接口。
- 迪米特法则:一个对象应该对其他对象保持最少的了解。
- 合成复用原则:尽量使用合成/聚合的方式,而不是使用继承。
3-9
设计模式有哪些?
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
3-8
设计模式有哪些类别?
设计模式有 23 种,分为创建型模式、结构型模式和行为型模式。
3-7
什么是设计模式?
设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被理解、保证代码的可靠性、程序的可维护性。
3-6
Mybatis Plus 有哪些主要的 API?
- Wrapper API:条件构造器,用于构造查询条件。包括 QueryWrapper、UpdateWrapper、LambdaQueryWrapper、LambdaUpdateWrapper。
- Service API:Service 接口,用于定义 Service 层的接口。
- Page API:分页插件,用于分页查询。
- Entity API:实体类接口,用于定义实体类。
3-5
什么是 Mybatis Plus?
Mybatis Plus 是 Mybatis 的增强工具包,它是在 Mybatis 的基础上进行了扩展,简化了 Mybatis 的开发,提供了很多实用的功能, 比如分页、逻辑删除、自动填充、性能分析等。
3-4
MyBatis 的接口绑定和实现方式
接口绑定是指在 Mybatis 中任意定义接口,然后把接口中的方法和 SQL 语句绑定,我们直接调用接口方法就可以, 这样比原来 SqlSession 提供的方法更加直观和简洁,也可以更加灵活。
实现方式如下:
- 注解绑定,就是在接口的方法上加
@Select
,@Update
等注解。 - XML 绑定,就是在 XML 文件中定义接口的方法和 SQL 语句的映射关系。
3-3
请描述 Mybatis 的动态 SQL。
Mybatis 的动态 SQL 是通过标签来实现的,
标签可以根据条件来动态拼接 SQL 语句。
- if 标签:判断条件是否成立,成立则拼接 SQL 语句。
- choose 标签:类似于 Java 中的 switch 语句,根据条件来选择拼接 SQL 语句。
- when 标签:配合 choose 标签使用,表示条件成立时拼接 SQL 语句。
- otherwise 标签:配合 choose 标签使用,表示条件不成立时拼接 SQL 语句。
- trim 标签:去除 SQL 语句中的多余字符。
- where 标签:配合 trim 标签使用,去除 SQL 语句中的多余字符。
- set 标签:配合 trim 标签使用,去除 SQL 语句中的多余字符。
- foreach 标签:遍历集合,拼接 SQL 语句。
3-2
请描述 Mybatis 的缓存机制。
Mybatis 的缓存机制分为一级缓存和二级缓存。
一级缓存 是 SqlSession 级别的缓存,当调用 SqlSession 的查询方法时,Mybatis 会将查询结果放入到缓存中, 当再次查询相同的数据时,直接从缓存中获取,不会再次查询数据库。
二级缓存 是 Mapper 级别的缓存,多个 SqlSession 共享同一个 Mapper 的二级缓存,当调用 SqlSession 的查询方法时, Mybatis 会将查询结果放入到缓存中,当再次查询相同的数据时,直接从缓存中获取,不会再次查询数据库。
3-1
MyBatis 实现一对一有几种方式?
有联合查询和嵌套查询。
联合查询 是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;
嵌套查询 是先查一个表,根据这个表里面的结果的 外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置。