公告

👇微信扫码添加好友👇

Skip to content

每日一题 - 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 表示类型的下界。

java
// 通配符
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、泛型类

java
// HTTP 请求响应类
public class CommonResponse<T> {
    // 状态码
    private int code;
    // 消息
    private String message;
    // 数据
    private T data;
    // getter 泛型方法
    public T getData() {
        return data;
    }
    // setter
    // ...
}

如上方实例,HTTP请求的数据响应类型并不能在编写代码时确定,所以使用泛型类来定义。

2、泛型方法

java
// 泛型方法
public <T> T get(T t) {
    return t;
}

泛型方法可以在方法调用时指定具体的类型,上方泛型类实例中,data 属性的 getter 方法即为泛型方法。

3、泛型接口

java

// 泛型接口
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

什么是建造者模式?

建造者模式是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的方式进行创建。 工厂类模式是提供的是创建单个类的产品, 而建造者模式则是将各种产品集中起来进行管理,用来具有不同的属性的产品

java

// 产品
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 的反射机制动态生成代理类,然后在代理类中实现接口方法,实现对目标对象的代理访问。

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

什么是静态代理?

静态代理是指在编译时就已经确定代理的对象,通过在代码中显式定义一个代理类,代理类通过实现与目标对象相同的接口 然后在代理类中维护一个目标对象,通过构造方法或者其他方式传入目标对象,同时在代理类中实现接口方法,实现对目标对象的代理访问。

示例:

java
// 接口
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

设计模式的六大原则?

  1. 开闭原则:对扩展开放,对修改关闭。
  2. 里氏替换原则:子类可以替换父类。
  3. 依赖倒置原则:面向接口编程,依赖于抽象而不依赖于具体。
  4. 接口隔离原则:使用多个专门的接口,而不使用单一的总接口。
  5. 迪米特法则:一个对象应该对其他对象保持最少的了解。
  6. 合成复用原则:尽量使用合成/聚合的方式,而不是使用继承。

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 属性配置。