JAVA常见面试题

JPA中所有的空接口及其特点

        Entity:这是最基本的空接口,用来标注实体类,.表示该实体类能够有JPA管理.

        MappedSuperclass:用于标注父类,将实体类中的公共属性抽取到一个父类汇总,用于减少实体类的复杂度.

        Embeddable:用于标注嵌入式对象,讲一个或多个非实体类的对象嵌入到实体类对象中,以此简化实体对象的设计.

        EntityListeners:用于标注监听器类,是一种AOP的实现方式,可以在实体类的生命周期中回调指定的方法.

        Converter:用于将实体属性进行转化,其中的converToDatabaseColumn方法将实体属性转化为数据库字段值,converToEntityAttribute方法将数据库字段值转为实体属性.

        AttributeConverter:是Converter的子接口,用于将实体类中较为复杂的属性转为数据库可以接受的简单类型,如JSON转为 VARCHAR

BIO NIO AIO的区别

        BIO是一种同步的I/O操作,它同一时间只能处理一个请求,如果在处理一个请求时,又接受到了的多个请求,那其他请求会等待当前请求处理完毕.

        NIO是一种异步的I/O操作,它在接受到请求之后会建立连接并且将这个请求注册到Selector中,Selector会轮询注册通道,如果发现可操作的通道之后,会返回通道集合,然后进行I/O操作.

        AIO也是一种异步的I/O操作,AIO在读取数据或发送数据时,不会等到数据准备完毕或数据传输完毕,而是会向操作系统注册一个回调函数,当数据传输完成之后操作系统会调用回调函数,这样就可以实现I/O的异步操作.

        区别:

  1. 阻塞方式:BIO同一时间只能处理一个I/O请求,当下一个请求到达之后,必须等待当前的请求处理完成之后才能进行下一个请求的处理.但是NIO和AIO都是异步的I/O操作,不需要等待当前的请求处理完成再去处理下一个请求,它们可以同时处理多个请求.
  2. 缓冲区:BIO使用字节流或字符流进行I/O操作,不适用缓冲流,但是NIO和AIO使用缓冲流进行高效的I/O操作.
  3. 数据处理方式:BIO处理数据是一连接一线程,每个请求都需要建立一个单独的线程.但是AIO和NIO是事件驱动的方式,共享多个连接,多个请求可以共享一个线程.
  4. API实现方式,BIO是传统的面向流的方式实现I/O,AIO和NIO使用面向缓冲区和事件驱动的方式实现的.

Java的集合框架

        在Java中,集合主要分为两类,分别是Map Collection,这两个是集合框架中的顶级接口,Collection下面有三个子接口List Set Queue.

Collection

        Collection下面主要分为三个子接扣,List 有序集合, Set 无序集合 Queue 队列.

List

        List有序集合,即存入和取出数据有序,可以根据索引去确定某一个数据在集合中的位置,也可以将其取出,或者对指定位置的数据做修改或删除操作,也可以在指定位置添加数据.

ArrayList

        ArrayList是Java中的动态数组,它是由一个可调整大小的数组实现的集合.它允许在任何时候通过索引访问列表中的元素,并提供了在列表末尾添加,插入和删除元素的操作.

  1. 动态扩容机制,当元素数量超出内部数组的容量时,ArrayList会自动扩容,以容纳更多的元素.ArrayList默认的容量为10,当它的数据量和容量一致后会创建一个新的数组,薪的数组是原数组长度的1.5倍,并将原数组中的所有元素复制到新数组中.
  2. 寻址.索引,排序机制.ArrayList的底层是一个Object类型的数组,寻址,索引,排序机制类似于常规数组,由于ArrayList是基于数组实现的,所以它的访问速度会很快,但是它的增删效率可能会较慢.
  3. ArrayList提供了一种遍历元素的方式,即迭代器,迭代器是一种对象,可以在数据集合或容器中进行迭代或遍历操作.使用ArrayList中的iterator()方法可以获得ArrayList的迭代器.
  4. ArrayList是线程不安全的,不能保证在多线程场景中的线程安全性.在多线程场景使用ArrayList时,需要进行同步控制,可以使用synchronized.
  5. ArrayList是一个动态扩容的数组,它的数据复制操作耗费一定的空间和时间.由于数组扩容过程是一项相对昂贵的操作,因此要避免对ArrayList进行频繁的插入或删除元素的操作.如果应用程序包含频繁的插入和删除操作,可以使用LinkedList作为ArrayList的替代方案.如果是查询数据的操作较多,ArrayList的速度更快.

LinkedList

        LinkedList是Java中的一个链表实现,它可以用来存储一个序列的元素,其中有一个指向下一个元素的引用.和ArrayList不同,LinkedList的元素不是被存储在一个连续的数组中,而是散步在内存中的不同位置上.链表的每个元素都包含一个指向下一个元素的引用.

  1. 插入和删除元素时速度较快.由于链表的元素不是存储在连续的内存中,所以插入和删除元素时只需要改变相应的元素的指针即可,时间复杂度较低.
  2. 随机访问时速度较慢,由于链表不是基于数组实现的.所以随机访问要从链表头开始顺序查找.时间复杂度较高.
  3. 不支持袭击访问,LinkedList并没有像ArrayList那样提供get(index)方法,因为从头到指定元素的顺序搜索需要O(n),它支持通过遍历链表访问特定的袁旭.
  4. 可以用来实现栈和队列,讲一个元素添加到链表开头通常被称为入栈操作,将一个元素从链表的开头删除通常被称为出栈操作.将一个元素添加到链表的末尾被称为入队列操作.
  5. LinkedList也是线程不安全的.

Vector

        Vector和ArrayList类似,也是基于数组实现的,并且具有相似的动态扩容机制,但是和ArrayList相比,还有一些特点:

  1. ArrayList是线程不安全的,但是Vector是线程安全的.多个线程同时访问同一个Vector对象,不用担心出现数据竞争或其他线程安全问题.
  2. 不适用于高并发场景,虽然Vectore是线程安全的,但是它并非高并发场景下性能最佳的选择.由于Vector的线程安全机制涉及到锁的使用,因此多个线程对Vector进行并发访问时,会导致性能的下降.

Set

        Set是Java中的一种集合,它以无序和唯一的方式存储对象,即不能在Set中重复添加同一个对象.

HashSet

        HashSet是Java中最常用的一种Set实现,它基于HashMap实现,不保证元素的顺序,但是能够实现快速的插入,查找和删除操作

  1. 内部实现机制,HashSet是基于HashMap实现的,在HashSet中,每个元素都会对应一个hashCode,这个值是通过对象的hashCode()方法获取的.当添加元素时,HashSet会先调用度一项的hashCode()方法获取它的哈希值,然后根据这个哈希值确定它在哈希表的位置.如果两个元素的哈希值相同,HashSet还会调用这两个元素的equals()方法判断他们是否相等,如果相等,HashSet就会认为这两个元素是同一元素,这样就不会将第二个元素添加到集合中.这样就保证了集合元素的唯一性.如果equals()返回了false,HashSet就会认为这两个元素是不同的元素,就会将第二个元素添加到集合中.
  2. 初始化容量和负载因子.HashSet的构造方法可以提供初始容量和负载因子两个参数,初始容量是指哈希表的大小,负载因子是指哈希表的填充因子.当哈希表中的元素数量达到了容量乘以负载因子的上限之后,就需要对哈希表进行扩容.
  3. HashSet是线程不安全的.如果需要在多线程的环境中使用Set,可以使用线程安全的集合类.

TreeSet

        TreeSet是实现SortedSet接口的一个集合类,它是一个基于红黑树的有序集合.能够实现常数时间的查找,插入和删除操作,

  1. 内部实现机制.TreeSet是基于红黑树实现的,它内部维护了一个红黑树用来存储数据,并且要求所有的元素都必须实现Comparable接口或者在构造TreeSet时提供一个Conparator接口,通过元素的Comparable接口或Comparator接口来比较元素的大小,从而将元素按照从小到大的顺序排列.红黑树的平衡性是的树的高度不会很高,查找,插入和删除节点都是O(log n)时间复杂度.
  2. TreeSet也是线程不安全的,在多线程场景下使用TreeSet需要慎重考虑.

Map

        Map是以key-value的形式存储并操作数据的.

HashMap

        HashMap是Java中最常用的Map实现类之一,它基于哈希表实现

  1. 内部实现机制.HashMap是基于哈希表实现的HashMap中每个键值对都会被存储在桶中,每个桶保存了一个链表的头节点,多个键值对可能被映射到同一个桶中,当HashMap的元素达到一定数量时,会自动重新调整桶的数量,以保证哈希表的负载因子不超过设定的阈值,负载因子的默认值为0.75,即当元素数量达到哈希表大小的0.75倍时,会自动触发扩容操作.
  2. 添加元素,向HashMap添加元素的方式是调用put()方法,参数是key和value,HashMap计算key的哈希值并根据该哈希值确定该元素应该存放在哪个桶中,如果新元素映射到的桶中已有元素,会将新元素的链表节点添加到这个链表的尾部,否则创建一个链表节点,并将其成为这个桶的头结点.
  3. 删除元素需要调用remove()方法.参数是目标元素的key,HashMap会根据key 的哈希值确定该元素存放在哪个桶中,然后遍历这个桶对应的链表,找到匹配的键值对,并将其删除.
  4. HashMap是线程不安全的,如果在多线程中使用,可以考虑使用ConcurrentHashMap

HashTable

        HashTable和HashMap类似,也是使用哈希表存储键值对,相比较于HashMap.HashTable也有一些特点:

  1. HashTable是线程安全的,在多线程环境下,可以保证线程安全.
  2. HashTable不允许key或value为null,但是HashMap允许.
  3. HashTable的初始容量为11,HashMap的默认初始容量为16,不过二者的扩容因子都是0.75

TreeMap

        TreeMap基于红黑树数据结构,可以根据键值的自然排序或自定义比较器排序.

  1. 内部实现机制,TreeMap的内部实现机制是基于红黑树数据结构实现的.红黑树是一种自平衡的二叉搜索数.TreeMap通过红黑树来维护元素的排序,以支持按照键排序的操作.
  2. TreeMap中的键值对是按照键排序的,因此它提供了自然排序和自定义排序两种排序方式.自然排序是指键所属的类实现了Comparable接口,能够进行比较,并按照升序排列.若键所属的类没有实现Comparable接口,则需要再创建TreeMap时传入一个自定义比较器,以根据比较器指定的排序方式来排序.
  3. 向TreeMap中添加元素时,会调用键的compare()方法来进行排序,如果新增的键已存在,TreeMap会用新值替换旧值.
  4. TreeMap是线程不安全的,如果多线程同时访问TreeMap,可能会出现竞态条件.

简单介绍一下什么是索引

        索引是数据库的一种优化技术,用于增加数据库的查询操作,如果一个数据表中的数据量较大时,查询某一条数据所消耗的时间就会变多.用查字典举例,我们传统的查询方式就是一页页去找,直到找到我们想要的数据为止,如果增加了索引之后,就类似于我们在查字典的时候按照音序或者部首偏旁的方式去查找,其效率会更高.但是字典中部首偏旁和音序占用了字典的前面一部分,也是会占用一定页数的.同样,数据库中的索引也会占用一定的磁盘空间.

        如果我们想要创建索引,可以使用 create index 索引名 on 表名(库名);

        根据逻辑维度,索引可以分为以下几类:

  1. 唯一索引,保证某些列的值不重复,可以用于主键或唯一约束条件.
  2. 聚簇索引,在数据表中按照主键重新组织数据,减少磁盘的I/O操作.
  3. 非聚簇索引,除了聚簇索引指定的排序方式外,指定其他列的排序方式.
  4. 多列索引,将多个列作为索引,这样可以提高复合查询和order by的效率.
  5. 全文索引,用于文本数据的搜索,例如使用关键字查询.

什么情况下索引会失效

        在使用索引的情况下,某些特定场景会导致索引失效,这样我们的查询效率会大大下降,主要有以下几个场景:

  1. 对索引列进行了函数操作.比如:对日期列使用 year(),这是因为在使用了函数之后,数据库需要重新计算列值,无法使用索引缓存的现有值.
  2. 模糊查询的时候对索引列使用%进行查询,因为数据库会对索引列的每一行数据库都进行计算,这样也会导致索引失效
  3. 使用了不等于的操作符:<>!=,因为索引列不能确定值的大小关系,所以这里的索引也会失效
  4. 当要查询的数据量非常大时,由于索引要从磁盘中读取数据,所以会I/0费用会非常巨大,反而会降低查询效率.
  5. 如果查询的数据分布不均,使用索引就没有办法去优化查询了,如一个 is_delete列中,值只有0和1,其中值为1的值占了绝大多数,那如果我们查询的条件是is_delete=1,这样索引是没有太大作用的.

        针对以上索引失效的问题,可以采用这些方式去解决:

  1. 避免使用函数去操作索引列.
  2. 尽可能的避免使用%开头的模糊搜索,采用全文索引或关键字容器进行优化
  3. 尽量避免使用不等于操作符:可以使用其他方式代替,如使用IN()或NOT()等。

  4. 针对大表格进行查询操作时,可以使用“分页”或“分区”技术,在不读取所有记录的情况下快速返回结果。

  5. 如果数据分布不均匀,可以使用数据分析工具进行分析,优化访问计划,并使用多列索引或覆盖索引来优化查询。

设计模式之单例设计模式

        单例设计模式可以保证一个类只有一个实例对象,并且可以在项目中全局访问.

        1.饿汉式,饿汉式单例模式是最基本的实现方式之一,通过在类加载时初始化单例实例,可以确保线程安全。

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return instance;
    }
}

        2.懒汉式,懒汉式单例模式与饿汉式单例模式的区别在于单例实例的初始化时机。懒汉式单例模式不会在类的加载阶段创建单例实例,而是在第一次调用时才进行初始化,这样可以减少系统启动时间。

public class LazySingleton {
    private static volatile LazySingleton instance = null;
    private LazySingleton() {}
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

        3.静态内部类单例模式,与懒汉式单例模式不同,静态内部类单例模式的单例实例的创建是在第一次调用时进行的,但是,由于他是在内部类的加载时进行初始化,所以线程安全也得到了保障.

public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {}
    private static class SingletonHolder {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

        4.双重校验锁单例模式,这是一种懒汉式单例模式的优化,在多线程的环境下更加安全和高效,它也是在第一次调用时才进行单例模式的初始化,在后续的访问中直接访问单例实例.

public class DoubleCheckSingleton {
    private static volatile DoubleCheckSingleton instance = null;
    private DoubleCheckSingleton() {}
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

        5.枚举单例模式,他说一种相对简单但非常强大的实现方式,它可以解决大多数单例实现中可能存在的序列化,反射,线程安全等问题.

public enum EnumSingleton {
    INSTANCE;
    public void method() {
        
    }
}

        6.注册式单例模式,它提供了单例验证的功能,为单例实例提供了确保线程安全和正确性的线程安装件结构.实现方式可以借助于容器中的注册机制,spring容器的bean注入等,具体的视线方式于技术选型相关.

public class RegistrySingleton {
    private static Map<String, RegistrySingleton> registry = new HashMap<>();
    static {
        RegistrySingleton singleton = new RegistrySingleton();
        registry.put(singleton.getClass().getName(), singleton);
    }
    protected RegistrySingleton() {}
    public static RegistrySingleton getInstance(String name) {
        if (name == null) {
            name = RegistrySingleton.class.getName();
        }
        if (!registry.containsKey(name)) {
            try {
                registry.put(name, (RegistrySingleton) Class.forName(name).newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return registry.get(name);
    }
}

        7.ThreadLocal单例模式,一种多线程环境下非常安全和高效的单例实现方式,它通过为每个线程提供一个独立的实力,从而保证了线程安全性和正确性,在大量线程的并发情况下非常可靠.

        通过ThreadLocal.withInitial()方法创建ThreadLocal实例,每个线程访问该实例都会返回各自独立的副本.这种方式不仅保证了线程安全性和可靠性,也减少了线程间的同步开销.

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = ThreadLocal.withInitial(ThreadLocalSingleton::new);
    private ThreadLocalSingleton() {}
    public static ThreadLocalSingleton getInstance() {
        return threadLocalInstance.get();
    }
}

        8.容器单例模式,一种灵活的单例实现方式,它通过容器来管理各种单例实例的创建和访问,可以在容器中灵活地管理单例实例的初始化,销毁等操作,同时也提供了更好的扩展性和灵活性,而且这种方式可以通过Spring的集成等方式实现.

public class ContainerSingleton {
    private static Map<String, Object> singletonMap = new HashMap<>();
    private ContainerSingleton() {}
    public static void putInstance(String key, Object instance) {
        if (key != null && !"".equals(key.trim()) && instance != null && !singletonMap.containsKey(key)) {
            singletonMap.put(key, instance);
        }
    }
    public static Object getInstance(String key) {
        return singletonMap.get(key);
    }
}

设计模式的六大原则

        1.开闭原则

        开闭原则是指类,模块,方法等实体是可扩展的,而不是不可修改的.用扩展类的方式实现新功能,而不是修改原有的代码.这样对现有接口的扩展不会影响其原有的代码结构.从而保证了程序的可维护性,可扩展性和可复用性.

        2.单一职责原则

        单一职责原则是指一个类或方法应该只有一个单一的职责.每个类都应该只有一项职责.如果一个类中又多重职责,不同的职责可能会引起类的变化,降低程序的可扩展性和可维护性.单一职责原则可以保证类的内聚性,使得类的功能更加专一,更加易于理解和维护.

        3.里氏替换原则

        里氏替换原则是指子类对象应该能够完全替换父类对象.即,在程序中使用父类对象的地方,可以使用其子类对象来代替,而程序的逻辑不应该发生改变.保证了程序的正确性.里氏替换原则保证了程序的可扩展性和可维护性,使得程序具有更好的灵活性和可靠性.

        4.依赖倒置原则

        依赖倒置原则是指高层模块不应该依赖于低层模块,而应该通过抽象接口来解耦.即任意两个类之间都应该通过抽象接口来相互通信,而不是直接依赖于具体的实现.依赖倒置原则可以保证程序的可扩展性和可维护性,使得程序具有更好的灵活性和可靠性.

        5.接口隔离原则

        接口隔离原则是指应该将不同的功能性接口分开,避免将过多的功能集中到同一个接口中.这样可以避免客户端依赖于不需要的接口,也可以使得功能更加模块化和易于维护.该原则可以避免冗余代码和接口.

        6.合成复用原则

        合成复用原则是指尽量使用合成/聚合关系,而不是继承关系,来实现代码的复用.因为击沉关系会有很强的耦合性,容易造成代码的膨胀和混乱,而合成/聚合关系能够更好的解耦,实现代码的复用和扩展.

        设计模式的六大原则是设计模式中最基础的原则,这些原则并不是互相孤立的,他们之间互相补充和交织,能够更好地知道和规范软件开发的实践.

设计模式的三大分类

        创建型

        创建模式的目的是尽可能的抽象对象的创建过程.并根据需求灵活的创建.例如:工厂模式,单例模式等.

        结构型

        结构型和对象之间的耦合有关,主要是为了提高系统的效率和灵活性,降低耦合度,例如:桥接模式,装饰模式等.

        行为型

        行为型模式主要关注对象之间的通信及职责分配,达到解耦和复用的目的,实现松耦合.比如:观察者模式,访问者模式等.

设计模式之常见的23中设计模式

创建型

工厂模式

        工厂模式是将对象的创建和使用进行了分离,定义一个接口用于创建对象,但让子类决定要实例化哪个类,从而可以在不改变其他相关类的情况下添加新的产品类.这种模式实现的解耦合,增强了系统的可扩展性.实现过程中可以使用抽象类和接口来定义工厂类和产品类.

抽象工厂模式

        抽象工厂模式提供了一种接口,可以创建一些列相互依赖的对象.通常在一个产品族中定义一组产品,不同的产品族有不同的工厂类,从而实现了不同产品族的解耦.这种模式的有点是能够保证同一产品族的产品在一起使用,并且可以在不修改客户端代码的前提下新增新产品.

单例模式

        单例模式是保证一个类仅有一个实例,并通过一个全局访问点.这种模式的优点是可以全员访问单例对象,而不需要将对象的引用传递给其他对象,从而简化了对该对象的管理和维护.

建造者模式

        建造者模式讲一个复杂对象的构建过程分解为多个简单的对象构造过程,从而依次组装起来,最终构建成一个复杂对象.它将构建过程和表示分离,从而使得相同的构建过程可以创建不同的表示,增强了系统化的灵活性和可扩展性.建造者模式适用于一个对象有复杂的内部结构,要求把生成过程封装起来以及生成的对象具有复杂的属性等.

原型模式

        原型模式通过拷贝一个现有对象来创建一个新的对象,而不是使用new关键字实现.这种方式可以避免复杂类的创建过程,提高创建对象的效率,同时可以保证对象的类型和属性的完整性,适用于对象的创建过程比较复杂或对象的类型不确定的情况.

结构型

适配器模式

        适配器模式通过重新定义一个适配器类将现有类的接口转化为客户端需要的接口,从而实现两个不兼容接口之间的通信.适配器模式 分为类适配器模式和对象适配器模式,在对象适配器模式中,适配器和它所适配id喜爱你个相互协作,将请求转发给该对象.

桥接模式

        桥接模式是将一个大类或一系列紧密相连的类分为两个独立的等级结构,从而是它们可以独立变化,相互之间减少耦合,提高系统的灵活性.这种模式的核心是将抽象部分和实现部分分离,实现部分可以在运行时动态切换,而不影响抽象部分.

组合模式

        组合模式是将对象组合成树形结构以表示"整体/结构"的层次结构关系,是的客户端可以将单个对象和组合对象同等对待,从而简化代码的开发和维护,组合模式分为透明模式和安全模式两种,在透明模式中,组合中的所有对象都符合同一接口,客户端不需要区分不同的对象类型.

装饰模式        

        装饰模式动态地给一个对象添加一些额外的职责,而不会改变其原有的接口和实现,从而使得类的功能更加灵活和可扩展.装饰模式实现方法是通过装饰类和被装饰类实现同一接口,从而实现透明的装饰.

外观模式

        外观模式为客户端提供了一组简单的接口,从而隐藏了复杂的系统结构和细节,使得客户端使用起来更加方便.外观模式通常将大量的逻辑和操作分解到不同的类中取,通过外观类的同一接口暴露给客户端,从而提高系统的易用性和可维护性.

享元模式

        享元模式通过共享对象来减少系统中的对象数量.提高系统的性能和效率.享元对象通常具有内部状态和外部状态,其中内部状态可以被共享,外部状态需要由客户端来管理.享元模式适用于需要大量的对象,而这些对象使用相同的数据进行初始化,从而达到复用的目的.

代理模式

        代理模式在不改变原有代码的情况下,提供了一个代理对象来控制对某个对象的访问,代理模式分为静态代理和动态代理两种,静态代理需要手动编写代理类,而动态代理使用Java反射机制实现动态代理,使得代理类更加灵活和易于维护.

行为型

职责链模式

        职责亮模式使得请求的发送者和接收者都不需要对方的存在,而是通过一条链处理请求.请求沿着链的传递,直到有对象处理该请求或没有对象可处理为止.职责链模式适用于存在多个处理对象且处理对象不确定的情况.

命令模式

        命令模式讲一个请求封装为一个对象,使得请求的发送者和接收者可以分别处理请求,从而将两者解耦.命令模式的优点是可以方便地实现撤销,重做和队列操作等功能,适用于将请求和处理对象解耦的情况.

解释器模式

        解释器模式定义一个语言的语法,并定义一个解释器来解释该语言中的句子,即根据规定的语法解释输入的内容.解释器模式适用于需要对复杂的语言或语法进行处理的情况.

迭代器模式

        迭代器模式提供一种无需直到内部结构的方式来访问集合对象中的元素,从而使得集合对象与访问对象解耦.迭代器模式的优点是增加了带阿米的复用性和灵活性,同时提高了系统的可扩展性和易用性.

中介者模式

        中介者模式定义了一个中介对象来封装对象之间的交互关系.使得每个对象都只需知道如何与中介对象进行交互,而不需要了解其他对象的信息.中介者模式适用于存在大量对象之间耦合的情况.

备忘录模式

        备忘录模式提供了一种在不破坏封装范围内恢复对象状态的方法.备忘录模式将对象状态封装到备忘录中,从而可以对对象的状态进行备份,并在需要时进行回复,备忘录模式的有点是可以有效地解决对象状态管理问题,从而提高系统的可靠性和灵活性.

观察者模式

        观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,它的所有依赖对象都会自动收到通知并进行更新.观察者模式适用于存在多个对象之间关系复杂,难以进行可靠管理的情况,具有减少耦合.增强对象之间的协作和可维护性等优点.

状态模式

        状态模式定义了一种状态机,将对象的状态从复杂的条件逻辑中分离出来,并将每一个状态封装成一个类.状态之间可以相互转移,通过调用不同状态类的方法来改变对象的状态.状态模式的优点是降低了对象之间的耦合度和复杂性,提高了系统的可扩展性和可维护性.

策略模式

        策略模式定义了一系列算法,将每个算法封装起来并使它们之间可以相互替换,从而使得算法可以独立于客户端而变化.策略模式的优点是可以动态地改变算法的视线,而不需要修改具体的实现类,提高了代码的可维护性和灵活性.

模板方法模式

        模板方法模式定义了一个操作中的算法框架,而将一些步骤延迟到子类中实现.模板方法模式的优点是可以在不影响算法框架的情况下更改某些步骤的实现,提高了系统的可扩展性和灵活性.

访问者模式

        访问者模式定义了一组访问方法,可以在不修改具体元素的类的情况下访问和操作元素中的部分或全部内容.访问者模式适用于数据结构相对稳定的情况,且需要增加新的操作但不希望影响到各个具体元素.访问者模式的优点是可以方便地增加访问和操作对象的功能,而不需要修改已有元素类的代码,提高了系统的可扩展性和灵活性.

代理模式

        静态代理

public interface Subject {
    void doSomething();
}

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject do something");
    }
}

public class ProxySubject implements Subject {
    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void doSomething() {
        System.out.println("Before invoke doSomething()");
        subject.doSomething();
        System.out.println("After invoke doSomething()");
    }
}

// 使用静态代理
Subject subject = new ProxySubject(new RealSubject());
subject.doSomething();

        JDK代理

public interface Subject {
    void doSomething();
}

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject do something");
    }
}

public class JDKProxy implements InvocationHandler {
    private Object target;

    public JDKProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke " + method.getName() + "()");
        Object result = method.invoke(target, args);
        System.out.println("After invoke " + method.getName() + "()");
        return result;
    }
}

// 使用JDK代理
Subject subject = new RealSubject();
Subject proxy = (Subject) Proxy.newProxyInstance(
        Subject.class.getClassLoader(),
        new Class[] { Subject.class },
        new JDKProxy(subject));
proxy.doSomething();

        CGLIB代理

public class RealSubject {
    public void doSomething() {
        System.out.println("RealSubject do something");
    }
}

public class CGLIBProxy implements MethodInterceptor {
    private Object target;

    public CGLIBProxy(Object target) {
        this.target = target;
    }

    public Object createProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before invoke " + method.getName() + "()");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After invoke " + method.getName() + "()");
        return result;
    }
}

// 使用CGLIB代理
RealSubject subject = new RealSubject();
CGLIBProxy proxy = new CGLIBProxy(subject);
RealSubject proxySubject = (RealSubject) proxy.createProxy();
proxySubject.doSomething();

 七层网络模型

        物理层、数据链路层、网络层、传输层,会话层、表示层、应用层

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值