JAVASE进阶面试题大总结

​

 面向对象

1.解释一下什么是继承

  • 在编程领域,“继承”是面向对象编程中的一个重要概念。
  • 继承是指一个类(称为子类或派生类)可以从另一个类(称为父类或基类)获取属性和方法。通过继承,子类能够重用父类的代码和功能,同时还可以添加新的属性和方法,或者修改父类中已有的方法的实现,以满足特定的需求。
  • 继承的主要优点包括代码复用、提高代码的可维护性和可扩展性。例如,如果有多个类都具有某些共同的属性和行为,那么可以将这些共同部分提取到一个父类中,让其他类通过继承来获取这些共性,从而减少代码重复编写。缺点是不能使用子类中的特有方法。(解决这个缺点也非常简单使用强制类型转换)

2.java中所有类的父类是什么

  • 在Java中,所有类的父类是java.lang.Object类。Object类是Java类层次结构的根,位于java.lang包中,这个包在每个Java程序中都会自动导入。这意味着即使一个类没有显式地声明继承自任何其他类,它实际上也是Object类的子类,从而继承了Object类提供的基本行为和属性。
  • Object类提供了一些常用的方法,比如equals(Object obj)、hashCode()、toString()、clone()、finalize()等,这些方法可以被所有Java类使用和重写。通过继承Object类,Java确保了一致性和跨平台的可移植性,同时也为所有类提供了一个共同的基础。

3.思考重载和重写的区别

重写(Override)

  • 定义:重写发生在父类与子类之间,当子类中定义了一个方法,这个方法与父类中的某个方法具有相同的方法名、参数列表和返回类型时,我们说子类方法重写了父类方法。
  • 目的:子类可以通过重写父类的方法来提供自己特有的实现,或者修改父类的行为,以适应子类的需求。
  • 规则:
    • 参数列表必须与被重写的方法完全相同。
    • 返回类型必须与被重写的方法相同或兼容(如果是返回对象类型的话)。
    • 访问修饰符不能更严格(例如,如果父类方法是public,子类重写的方法不能是private)。(方法重写时子类的权限一定要小于等于父类的权限)
    • 异常声明:子类重写的方法抛出的异常不能比父类的方法更多或更广(对于受检异常而言)。

重载(Overload)

  • 定义:重载是在同一个类中定义多个同名方法,但这些方法的参数列表不同(参数的数量、类型或顺序不同)。方法的返回类型与方法的重载无关。
  • 目的:重载使我们能够使用相同的名称来执行类似但略有不同的操作,增强代码的可读性和简洁性。
  • 规则:
    • 参数列表必须不同,可以是参数数量、类型或顺序的差异。
    • 可以有不同的返回类型,但这不是区分重载方法的依据。
    • 可以有不同的访问修饰符。
    • 可以抛出不同的异常。

 4.解释一下this和super关键子的区别

​编辑

5.final关键字有什么用,String为什么是不可变的

  • 修饰类: 该类不能被继承(不能有子类,但是可以有父类)
  • 修饰方法: 该方法不能被重写
  • 修饰变量: 表明该变量是一个常量,不能再次赋值
    • 变量是基本类型: 数据值不能发生改变
    • 变量引用类型: 地址值不能发生改变,内容是可以发生改变的

​编辑

JDK中有哪些final类?
在JDK中,有很多常见的类都是final的,下面列举一些例子:

System类:提供了与底层系统交互的功能,如访问环境变量、加载文件等。由于它涉及到系统的核心功能,因此被设计为final,防止被继承。

String类:Java中的字符串类,被设计为不可变的。为了防止其他类通过继承来修改字符串的内容,String类被声明为final。

StringBuffer和StringBuilder类:这两个类用于构建可变的字符串。虽然它们本身是可变的,但它们的类定义是final的,以防止其他类通过继承来破坏它们内部的数据结构。

包装类(如Integer、Double等):Java中的基本数据类型(如int、double等)都有对应的包装类。这些包装类也被设计为final,以防止子类破坏它们与基本数据类型之间的映射关系。

6.new String() 和 String  a= 的区别

  1. 字符串池: String a = "hello"; 使用字符串池来管理字符串,可以节省内存。
  2. 堆内存: String a = new String("hello"); 每次都会在堆上创建一个新的 String 对象,即使池中已经有相同值的字符串。

总结来说,使用 String a = "hello"; 更高效,因为它利用了字符串池,而 String a = new String("hello"); 则会创建额外的对象,占用更多的内存。

7.static关键字在的作用(内存角度回答以及类加载的角度)

变量 :静态变量 与普通非静态成员变量的区别在于静态变量在内存中只存一份,可以作为所有对象的共享变量

1. 内存角度

  • 静态变量(Static Variables):静态变量是类级别的变量。它们在内存中只有一份拷贝,所有实例共享这份拷贝。这意味着所有对象都可以访问和修改相同的静态变量。静态变量在类加载时被分配内存,并在类的整个生命周期内存在。

  • 静态方法(Static Methods):静态方法属于类,而不是类的实例。它们可以直接访问静态变量和调用其他静态方法,而不能直接访问实例变量或调用实例方法。静态方法在类加载时被加载到内存中,并在类的生命周期内存在。

  • 静态代码块(Static Initialization Blocks):静态代码块用于初始化静态变量。当类第一次加载时,静态代码块会被执行一次,用于对静态变量进行初始化。静态代码块在内存中也是在类加载时被分配和执行的。

2. 类加载的角度

  • 类加载:当一个类被加载到 JVM 中时,静态变量和静态方法会被初始化和分配内存。类的静态成员(包括静态变量、静态方法和静态代码块)会在类加载阶段被处理。这些静态成员在整个应用程序的生命周期内只有一份拷贝,而不是每个实例都有一份。

  • 类初始化:在类被加载并且初始化之前,JVM 会执行静态代码块。这些代码块是在类加载时执行的,用于初始化静态变量或执行其他一次性的任务。这是保证类在使用之前能够正确初始化的一种机制。

总结来说,static 关键字帮助管理类的内存布局和初始化过程,使得类的静态成员在内存中有统一的管理方式,并确保类的初始化在使用之前完成。

面向函数

1.解释Java函数式编程lambda表达式的闭包,以及如何解决闭包的问题

​编辑

解决闭包问题把基本数据类型变成引用数据类型(线程不安全,使用原子类代替)Atomic原子类

​编辑

2.什么是构造器引用以及方法引用,使用条件是什么 

构造器引用和方法引用是 Java 8 中引入的特性,它们可以简化 Lambda 表达式的编写,使代码更加简洁和易读。

构造器引用:

构造器引用的语法格式是:类名称::new。它是方法引用的一种变体,用于引用类的构造函数。

使用条件是:需要创建一个与函数式接口中抽象方法的参数列表相匹配的类的实例。例如,如果有一个函数式接口的抽象方法接受某些参数并返回一个对象,而正好存在一个类的构造函数的参数列表与之匹配,就可以使用构造器引用。

方法引用:

方法引用是对已存在方法的直接引用,可以看作是 Lambda 表达式的一种更简洁的写法。

方法引用的格式有以下三种:

  1. 对象::实例方法:引用对象的实例方法,::前表示被引用的对象,::后表示被引用对象的实例方法。
  2. 类::静态方法:引用类的静态方法,::前表示被引用的类,::后表示被引用类的静态方法。
  3. 类::实例方法:本质上也是引用对象的实例方法,只是对象是 Lambda 表达式的第一个形参。::前表示被引用对象所属的类,::后表示被引用对象的实例方法。

使用方法引用需要满足以下条件:

  1. Lambda 体中只有一句语句,并且是通过调用一个对象的或类现有的方法来完成的。
  2. 针对不同的格式,有具体的参数和返回值类型要求:
    • 对于对象::实例方法,函数式接口中的抽象方法在被重写时使用了某一个对象的方法,如果方法的形参列表、返回值类型都相同,则可以使用该方法实现对抽象方法的重写、替换。此方法是实例方法,需要对象调用。
    • 对于类::静态方法,函数式接口中的抽象方法在被重写时使用了某一个类的静态方法,如果方法的形参列表、返回值类型都相同,则可以使用该静态方法实现对抽象方法的重写、替换。此方法是静态方法,需要类调用。
    • 对于类::实例方法,函数式接口中的抽象方法在被重写时使用了某一个对象的方法,如果抽象方法的返回值类型与该实例方法的返回值类型相同,同时抽象方法的形参列表中有 n 个参数,实例方法的形参列表有 n - 1 个参数,且抽象方法的第 1 个参数作为实例方法的调用者,且抽象方法的后 n - 1 参数与实例方法的 n - 1 参数匹配(类型相同或满足多态场景也可以)。此方法是非静态方法,需要对象调用,但形式上是写成对象所属的类来调用。

 API面试题

1.为什么不建议在循环中直接用加号拼接字符串?

用+拼接底层用的是StringBuilder每循环一次就创建了一次StringBuilder对象比较浪费内存,但用StringBuilder就不会了。

2.思考==和equals的区别

==对基本数据类型比较值,引用数据类型比较的是地址

equals不重写比较地址值,重写比较内容(看重写逻辑)

算法

1.请说出冒泡排序,选择排序以及二分查找的思路 

冒泡排序:基本思想:重复地走访要排序的数列,一次比较两个元素,如果顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

选择排序:基本思想:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

二分数查找:基本思想:也称为折半查找,它是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

异常

1.如何自定义异常

​

编辑

 2.说出异常继承体系结构以及什么是编译?运行异常?

​编辑

 编译异常在代码编译阶段发生的异常(如果你使用的编译器时idea的话会提示爆红)

运行时异常在代码运行过程中才发生的异常

 ​编辑

3.三种手动处理异常的方式 

throws声明异常,throw抛出异常,try...catch捕获异常

4.catch捕获多个异常时应该注意什么?

异常类型的顺序:在捕获多个异常时,应该将子类异常放在父类异常之前捕获。

 

异常处理的逻辑:对于不同的异常,可能需要采取不同的处理逻辑。在 catch 块中,应该根据异常的类型来执行相应的处理代码,以确保能够正确地处理异常情况。

 

资源清理:如果在 try 块中使用了一些资源(如文件、数据库连接等),需要在 finally 块中进行资源的清理和释放,以确保资源不会被泄漏。

 

避免过于宽泛的捕获:尽量避免捕获过于宽泛的异常类型,如 Exception。这样可能会隐藏一些潜在的问题,并且不利于调试和错误排查。应该尽可能地捕获具体的异常类型,以便能够更准确地处理异常情况。

 

记录异常信息:在处理异常时,应该记录异常的相关信息,如异常消息、堆栈跟踪等,以便于后续的故障排查和分析。

5.throw和throws的区别是什么?

throw:用于在方法体内主动抛出一个异常对象。它会立即终止当前方法的执行,并将异常对象抛给上层调用者。

 

throws:用于在方法声明处声明该方法可能会抛出的异常类型。它表示该方法不处理这些异常,而是将异常抛给上层调用者来处理。

 

总的来说,throw是用于主动抛出异常,而throws是用于声明方法可能抛出的异常类型。通过合理使用throw和throws,可以更好地处理程序中的异常情况,提高代码的健壮性。

一个时声明一个是抛出,一个用在方法签名上一个用在方法体内

6.如果catch有return,finally与catch里return的执行顺序?,如果finally里也有return,执行顺序如何?

  1. 如果 finally 块中没有 return 语句,catch 块中的 return 语句将会执行,但在此之前 finally 块中的代码会被执行。
  2. 如果 finally 块中有 return 语句,无论 try 或 catch 块中是否有 return 语句,最终返回值都将是 finally 块中的 return 值。

finally 块中的 return 语句将覆盖 try 或 catch 块中的 return 语句,因此需要谨慎使用,以避免意外的行为。

7.并发修改异常 

并发修改异常(ConcurrentModificationException)是在Java编程语言中使用集合类时经常遇到的一个运行时异常。这个异常通常在以下情况下抛出:

  1. 在使用迭代器遍历集合时修改集合:当你使用迭代器遍历一个集合(如List或Set)时,如果集合结构被修改(即添加、删除元素等),就会抛出ConcurrentModificationException。

  2. 在foreach循环中修改集合:foreach循环背后也是使用迭代器,所以在循环过程中修改集合也会导致这个异常。

集合

1.说一下java集合继承结构

Java 集合类主要包括 Collection 和 Map 两个接口,它们的继承结构如下:

  1. Collection接口:
    • List接口:有序、可重复的集合。
      • ArrayList:基于动态数组实现,随机访问效率高。
      • LinkedList:基于双向链表实现,插入和删除效率高。
      • Vector:与ArrayList类似,但线程安全。
      • ArrayList和LinkedList的区别
    • Set接口:无序、不可重复的集合。
      • HashSet:基于哈希表实现,不保证元素的顺序。
      • LinkedHashSet:基于哈希表和链表实现,保证元素的插入顺序。
      • TreeSet:基于红黑树实现,保证元素的自然顺序。
  2. Map接口:存储键值对的数据结构。
    • HashMap:基于哈希表实现,不保证键值对的顺序。
    • LinkedHashMap:基于哈希表和链表实现,保证键值对的插入顺序。
    • TreeMap:基于红黑树实现,保证键值对按照键的自然顺序排序。
    • Hashtable:与HashMap类似,但线程安全。

2.说一下List中常用的类及其特点

在 Java 中,List 是一个接口,它继承自 Collection 接口,表示一个有序的集合。常用的实现类有以下几种:

  1. ArrayList

    • 特点:底层数据结构是数组,支持随机访问。它的增删操作相对较慢(特别是在中间插入或删除),因为需要移动数组中的元素。
    • 线程安全:不是线程安全的。
    • 适用场景:适合频繁读取数据的场景。
  2. LinkedList

    • 特点:底层数据结构是双向链表,插入和删除操作速度较快(在任意位置),但随机访问较慢,因为需要遍历链表。
    • 线程安全:不是线程安全的。
    • 适用场景:适合频繁插入、删除操作的场景。

3.说一下如何使用LinkedList模拟栈和队列

LinkedList 是 Java 中一个非常灵活的类,它不仅可以用来实现链表,还可以用来模拟栈(Stack)和队列(Queue)。这是因为 LinkedList 提供了许多方便的方法来操作它的头部和尾部元素。

使用 LinkedList 模拟栈

栈(Stack)是一种后进先出(LIFO, Last In First Out)的数据结构。在 LinkedList 中,可以使用以下方法来实现栈的功能:

  • push(E e): 将元素推入栈顶。
  • pop(): 移除并返回栈顶的元素。
  • peek(): 返回栈顶的元素但不移除它。

使用 LinkedList 模拟队列

队列(Queue)是一种先进先出(FIFO, First In First Out)的数据结构。在 LinkedList 中,可以使用以下方法来实现队列的功能:

  • offer(E e): 将元素插入队列尾部。
  • poll(): 移除并返回队列头部的元素。
  • peek(): 返回队列头部的元素但不移除它。

 4.说出常见的数据结构及其优缺点

常见的数据结构有,栈(特点先进后出),队列(先进先出),数组(它可以存储固定类型的元素序列。),链表(它由节点组成,每个节点包含数据和指向下一个节点的引用。)

数组(Array)

优点:

  • 快速访问: 通过索引可以快速访问任意元素,时间复杂度为 O(1)O(1)O(1)。
  • 空间利用率高: 数组不需要额外的节点或指针,空间利用率较高。

缺点:

  • 固定大小: 数组的大小在创建时必须确定,无法动态扩展。
  • 插入和删除效率低: 在数组中插入或删除元素需要移动其他元素,时间复杂度为 O(n)O(n)O(n)。

链表(Linked List)

优点:

  • 动态大小: 链表可以动态扩展,不需要预先确定大小。
  • 插入和删除效率高: 在链表中插入和删除元素只需要修改指针,时间复杂度为 O(1)O(1)O(1)。

缺点:

  • 访问速度慢: 访问链表中的任意元素需要从头遍历,时间复杂度为 O(n)O(n)O(n)。
  • 空间利用率低: 链表需要额外的指针来存储节点关系,增加了内存开销。

​编辑

栈(Stack)

优点:

  • 操作简单: 栈的操作(如压栈和弹栈)非常简单,时间复杂度为 O(1)O(1)O(1)。
  • 适用场景多: 栈广泛用于递归计算、表达式求值和括号匹配等场景。

缺点:

  • 只支持后进先出(LIFO): 栈只能从顶部插入和删除元素,限制了其应用场景。

队列(Queue)

优点:

  • 操作简单: 队列的操作(如入队和出队)非常简单,时间复杂度为 O(1)O(1)O(1)。
  • 适用场景多: 队列广泛用于广度优先搜索(BFS)、任务调度和消息传递等场景。

缺点:

  • 只支持先进先出(FIFO): 队列只能从队尾插入元素,从队首删除元素,限制了其应用场景。

哈希表(Hash Table)

优点:

  • 快速查找、插入和删除: 哈希表的查找、插入和删除操作的平均时间复杂度为 O(1)O(1)O(1)。
  • 键值对存储: 哈希表可以高效地存储和查找键值对。

缺点:

  • 哈希冲突: 哈希表可能会遇到哈希冲突,导致性能下降。
  • 空间复杂度高: 为了减少冲突,哈希表通常需要额外的空间。

树(Tree)

优点:

  • 层次结构: 树可以很好地表示层次结构,如文件系统和组织结构。
  • 快速查找: 平衡树(如AVL树和红黑树)的查找、插入和删除操作的时间复杂度为 O(log⁡n)O(\log n)O(logn)。

缺点:

  • 实现复杂: 树的实现和维护(特别是平衡树)相对复杂。
  • 空间复杂度高: 树需要额外的指针来存储节点关系,增加了内存开销。

图(Graph)

优点:

  • 强大的表达能力: 图可以表示复杂的关系和结构,如社交网络和地图。
  • 广泛的应用: 图广泛应用于网络分析、路径查找和资源分配等领域。

缺点:

  • 实现复杂: 图的实现和操作(如遍历和最短路径查找)相对复杂。
  • 空间复杂度高: 图的存储(特别是稠密图)需要大量的内存。

这些数据结构各有优缺点,选择合适的数据结构取决于具体的应用场景和需求。

 

5.为什么重写 equals 必须重写 hashCode?

hash(哈希):是由hash算法对任意的输入产生一个整数,并且是固定的
哈希碰撞:同一个输入产生的hash值永远一样,但是不同的输入生成的hash值可能一样
hash如果内容一样就生成一样的hash值但是(如果内容相同)hash值可能相同,这时候就需要equals进行比较了。
如果两个对象根据 equals 方法认为是相等的,那么它们必须有相同的 hashCode 值。
如果两个对象 hashCode 值相等,它们未必 equals 相等,但如果 hashCode 不相等,它们一定不相等。

 6.HashMap和ArrayLIst需不需要指定初始化大小(需要)?为什么

虽然对于HashMap和ArrayList,不指定初始化大小也不会导致错误,但在实际应用中,根据预期的元素数量来指定一个合适的初始大小,可以帮助提高性能并优化内存使用。特别是在处理大量数据时,这种优化尤其重要。

7.如果ArrayList使用了泛型,那么只能存泛型规定的类型,如果使用反射泛型还是否生效?(不生效)为什么

1.保存数据使用的是Object[ ]

2.泛型只能维持到编译期,到运行时会泛型擦除,反射是运行时获取的方法

​编辑

8.集合工具类Collections,请简要介绍一下集合工具类中涉及到关于集合的方法 

​

编辑

9.为什么不用Vector来实现线程安全 

因为vector底层是读写都加锁比较影响效率,可以使用JUC并发包下的CopyOnWriteArrayList这个是写的时候加锁读的时候不加锁

10.ArrayList和LinkdList的底层数据结构是什么

`ArrayList` 和 `LinkedList` 在 Java 中的底层数据结构分别是:
- `ArrayList` 的底层数据结构是动态数组(Dynamic Array)。这意味着它使用一个数组来存储元素,并且当数组容量不足以容纳更多元素时,它会自动扩容(通常是创建一个更大的新数组,并将旧数组中的元素复制到新数组中)。
- `LinkedList` 的底层数据结构是双向链表(Doubly Linked List)。链表由一系列节点组成,每个节点包含数据和两个指针,一个指向前一个节点(prev),另一个指向下一个节点(next)。这使得 `LinkedList` 能够有效地在列表的任何位置进行插入和删除操作。
这两种数据结构的选择直接影响它们在插入、删除和访问操作上的性能特点。以下是简要的性能对比:
- **随机访问**:`ArrayList` 由于是基于数组的,所以它可以在 O(1) 时间复杂度内进行随机访问。而 `LinkedList` 需要从头开始遍历到特定位置,其时间复杂度为 O(n)。
- **插入和删除**:对于 `ArrayList`,除非是在末尾添加元素,否则在任意位置插入或删除元素都需要移动插入点后的所有元素,时间复杂度为 O(n)。而对于 `LinkedList`,插入和删除操作只需要改变指针,时间复杂度为 O(1)。
- **内存占用**:由于 `LinkedList` 需要额外的空间存储节点间的指针,所以在存储相同数量的元素时,`LinkedList` 通常会比 `ArrayList` 占用更多的内存。
因此,选择 `ArrayList` 还是 `LinkedList` 应该基于具体的应用场景和性能需求。

11. ArrayList默认大小是多少,是如何扩容的?为什么按照1.5被扩容

默认大小是零,当添加数据的时候会变成10

当 ArrayList 中的元素数量超过当前容量时,它会进行扩容。扩容的方式是将当前容量增大为原来的 1.5 倍。

ArrayList 按照 1.5 倍扩容主要有以下几个原因:

  1. 性能和空间平衡:这种扩容策略在空间使用和扩容操作的性能之间取得了较好的平衡。相比于每次扩容都增加固定的大小(如每次增加固定的 5 或 10 个元素的空间),1.5 倍的增长可以减少频繁扩容的次数,同时也不会在一开始就过度分配过多的空间。

  2. 避免过度浪费空间:如果扩容倍数过大,可能会在初始阶段就分配过多不必要的空间,造成内存浪费。

  3. 适应增长趋势:在大多数实际应用中,数据的增长往往不是线性的,而是具有一定的不确定性。1.5 倍的扩容策略能够较好地适应这种不确定性,在一定程度上预测未来可能的增长需求。

12.ArrayList是线程安全的吗?如何变成线程安全的 

 不是线程安全的,可以使用集合工具类中的一系列synchronized方法:Collections.synchronizedSet();或CopyOnWritrArrayList<Object> Object = new CopyOnWriteArrayList<>();或Vector<Object> Object = new Vector<>();

 13.怎么给List排序

 1.8新增的sort方法

list.sort();

 

stream流

list.stream().sorted()

 

集合工具类sort

collections.sort()

14.Arryas.asList方法后的list可以扩容吗?

不可以父类add抛出了一个异常

 15.能不能给一个已经定义好泛型的集合,添加其他的数据类型

可以,使用反射:

1.泛型在编译阶段会擦除

2.底层数据结构用的是object[]

设计模式

1.单例模式(Singleton Pattern)

确保一个类仅有一个实例,并提供一个全局访问点。常用于配置文件的读取、连接池的管理等场景。

从内存角度来看,该类只有有个对象存在

package com.lu.day15;

import java.util.Objects;

/**
 * 单例模式管理员类
 * 单例模式书写
 * 1.构造器私有
 * 2.组合当前类属性
 * 3.提供一个静态方法创建对象并返回
 * 
 * 面试题如何创建线程安全的单例模式
 */
public class Admin {//非线程安全的单例模式
    public static final String USERNAME = "admin";
    public static final String PASSWORD = "123456";
    private static Admin admin = new Admin();

    private Admin()
    {
    }

    public static Admin getAdmin() {
        if (Objects.isNull(admin)) {
            admin = new Admin();
        }
        return admin;
    }
}
package com.lu.day15;

public class Test1 {
    public static void main(String[] args) {
        Admin admin = Admin.getAdmin();
        Admin admin1 = Admin.getAdmin();
        System.out.println(admin == admin1);//true
    }
}

如何创建线程安全的单例模式

双重检查锁定(Double-Checked Locking)

双重检查锁定模式既实现了延迟加载,又保证了线程安全,同时避免了每次调用getInstance()都进行同步的开销。

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

懒汉式(线程安全,但效率低)

通过在getInstance()方法上添加synchronized关键字来保证线程安全,但这样会导致每次调用该方法时都进行线程锁定,效率较低。

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

2.策略模式(Strategy Pattern)

定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。(就是用枚举代替很多个复杂的if else语句)

package com.lu.atm.atmenum;

import com.lu.atm.service.ATMService;

public enum ATMEnum {
    REGISTER(1, "用户开户") {
        @Override
        public void show() {
            ATMService.register();
        }
    },
    LOGIN(2, "用户登录") {
        @Override
        public void show() {
            ATMService.login();
        }
    },
    USERAll(3, "查询所有用户") {
        @Override
        public void show() {
            ATMService.showAll();
        }
    },
    EXIT(4, "退出ATM") {
        @Override
        public void show() {
           ATMService.exit();
        }
    };

    private int code;
    private String desc;

    public static void compareParameters(int code){
        ATMEnum[] values = ATMEnum.values();
        for (ATMEnum value : values) {
            if (value.getCode() == code){
                value.show();
            }
        }
    }

    public abstract void show();

    ATMEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }
}

3.建造者模式(Builder Pattern)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

package com.lu.day15.pattern;

import java.time.LocalDate;
import java.time.LocalDateTime;

public class Book {
    private String bookName;
    private String author;
    private String publisher;
    private double price;
    /**
     * 字段
     */
    private int count;
    /**
     * 章节
     */
    private int chapter;
    private String type;
    /**
     * 出版日期
     */
    private LocalDate publishTime;

    static class Bulider{
        private String bookName;
        private String author;
        private String publisher;
        private double price;
        private int count;
        private int chapter;
        private String type;
        private LocalDate publishTime;
        public Bulider bookName(String bookName){
            this.bookName = bookName;
            return this;
        }
        public Bulider author(String author){
            this.author = author;
            return this;
        }
        public Bulider publisher(String publisher){
            this.publisher = publisher;
            return this;
        }
        public Bulider price(double price){
            this.price = price;
            return this;
        }
        public Bulider count(int count){
            this.count = count;
            return this;
        }
        public Bulider chapter(int chapter){
            this.chapter = chapter;
            return this;
        }
        public Bulider type(String type){
            this.type = type;
            return this;
        }
        public Bulider publishTime(LocalDate publishTime){
            this.publishTime = publishTime;
            return this;
        }
        public Book bulid(){
            return new Book(this);
        }
    }
    private Book(Bulider bulider){
        this.bookName = bulider.bookName;
        this.author = bulider.author;
        this.publisher = bulider.publisher;
        this.price = bulider.price;
        this.count = bulider.count;
        this.chapter = bulider.chapter;
        this.type = bulider.type;
        this.publishTime = bulider.publishTime;
        System.out.println(LocalDateTime.now());
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                ", author='" + author + '\'' +
                ", publisher='" + publisher + '\'' +
                ", price=" + price +
                ", count=" + count +
                ", chapter=" + chapter +
                ", type='" + type + '\'' +
                ", publishTime=" + publishTime +
                '}';
    }
}

package com.lu.day15.pattern;

import java.time.LocalDate;

public class Test {
    public static void main(String[] args) {
        /*

        特点:可以用链式编程
        简单建造者模式:
        用来创建对象的设计模式

        代替复杂的set方法以及有参构造器给属性赋值的


        1.类中创建一个静态内部类Builder
        2.Builder类的的属性全部都是被建造类的属性
        3.给每一个属性提供赋值方法
        该方法具备一下特点:方法名与属性名相同
                        返回值都是Builder对象
        4.提供最后构建对象的方法build,该方法返回值是被建造类对象
        5.给被构造者类提供私有化构造器,参数是Builder
         */
        Book.Bulider bulider = new Book.Bulider();
        bulider.author("罗贯中").bookName("三国演义").chapter(100).count(100).price(100).publishTime(LocalDate.now()).publisher("罗贯中").type("小说");
        System.out.println(bulider.bulid());

        Student.StudentBuilder builder = Student.builder();
        builder.address("北京").age(18).email("").name("张三").phone("").sex("男");
        System.out.println(builder);
    }
}
package com.lu.day15.pattern;

import lombok.Builder;



@Builder
public class Student {
    private String name;
    private int age;
    private String sex;
    private String address;
    private String phone;
    private String email;
}

4.装饰者模式(Decorator Pattern)

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式比生成子类更为灵活。(与继承一样,是为了增强一个类的功能,但是继承是高耦合性的此模式是在不适用继承的前提下,使用组合来增强一个类的功能,被装饰(被增强的)类与增强(装饰类)类具有相同的父类/父接口)也叫可插拔的设计模式

​编辑

​编辑

 ​编辑

5.静态代理(代理模式)- 动态代理

 对一个类的方法进行增强 -> 使用继承加重写

package com.lu.tlias84.aop.cglib;

/**
 * 狗类
 */
public class Dog {
    public void eat() {
        System.out.println("吃屎");
    }
}
package com.lu.tlias84.aop.cglib;

public class EnhanceDog extends Dog {
    @Override
    public void eat() {
        //增强之前
        System.out.println("吃屎前,喝泡尿");
        super.eat();
        //增强之后
        System.out.println("吃完屎,在舔舔人");
    }
}

动态代理模式

动态代理通常在运行时生成代理类,不需要显式地编写代理类。Java中常用的两种动态代理机制是JDK动态代理和CGLIB。

  • JDK动态代理:基于接口的代理,代理类在运行时生成,代理类实现了目标对象的接口。
  • CGLIB动态代理:基于子类的代理,通过生成目标类的子类来实现代理,适用于没有实现接口的类。
package com.lu.tlias84.aop.cglib;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class EatInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("刷牙");
        //调用父类的方法
        proxy.invokeSuper(obj,args);
        System.out.println("洗手");
        return obj;
    }
}
package com.lu.tlias84.aop.cglib;

import org.springframework.cglib.proxy.Enhancer;

public class TestCglib {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dog.class);
        enhancer.setCallback(new EatInterceptor());
        Dog dog = (Dog) enhancer.create();//动态代理
        dog.eat();
    }
}

多线程 

1.进程与线程的概念和区别

进程共享堆不共享栈

​

编辑

一个进程最少有一个线程,线程共享对资源 

​编辑
​编辑

2.并发与并行

​

编辑

​

编辑

3.同步与异步

同步

按照顺序一步步执行代码

异步Future

代码获取结果需要分开执行

4.java创建线程有几种方式,分别说一下哪几种

三种,第一种继承Thread类,第二种是实现实现Runnable接口,第三种是实现callable接口

5.你刚才讲的这几种创建方式有什么不同?

当你想获取线程执行后的结果的时候,只能使用Callable。

当你仅仅只想执行的时候不需要获取结果时,应该优先使用Runnable,因为java是单继承的。

6.调用start方法启动线程

与run方法的区别?

  • 调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run() 方法;直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
  • 一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。

多次调用start方法? 

一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。

创建多个线程观察运行顺序

无序的

7.线程操作共享数据会有什么问题?产生该问题的原因是什么?如何解决的?

多线程在操作数据时会出现安全问题(非原子操作)操作系统给每一个线程固定的运行时间超过就会停止这个线程执行下一个线程,解决办法在方法上添加synchronized锁。 

8.线程池执行流程图

​编辑

9.计算核心线程数和非核心线程数 

核心线程数:任务提交到线程池之后,创建之后不会销毁

 

最大线程数:用来计算非核心,非核心线程数会被销毁

 

非核心线程空闲活跃时间:指定非核心在不处理任务时的销毁时间

 

最大同时执行的任务数:
线程池中最大同时执行的任务数由最大线程数(即核心线程数加上非核心线程数)决定。

 

最大同时存在的任务数:
这是由线程池中的最大线程数和任务队列的容量决定的。即使线程池中的线程数达到了最大值,仍然可以向任务队列中添加任务,直到队列满为止。

10.阻塞与非阻塞 

阻塞(Blocking)

定义:

  • 当一个线程尝试进行某些操作,而这些操作在当前状态下无法立即完成时,该线程会被挂起,直到条件满足,这个过程称为阻塞。
  • 在阻塞期间,线程让出CPU执行权,进入等待状态。

例子:

  • 在Java中,使用InputStream的read()方法读取数据时,如果数据没有准备好,线程会阻塞直到数据到达。
  • 同样,使用Object.wait()方法也会使当前线程进入等待状态,直到另一个线程调用Object.notify()或Object.notifyAll()。

特点:

  • 简单易用,同步操作,不需要复杂的线程间协调。
  • 但效率较低,因为线程在等待期间什么事都不做。

 

非阻塞(Non-blocking)

定义:

  • 非阻塞方法在尝试进行操作时,如果操作不能立即完成,它不会挂起线程,而是返回一个特定的值表示操作无法完成。
  • 线程可以在执行其他任务后再次尝试该操作。

例子:

  • Java NIO(非阻塞I/O)提供了SelectableChannel,允许单一线程监控多个通道的数据状态,而不会在任一通道上阻塞。
  • java.nio.channels.FileChannel的tryLock()方法尝试获取文件锁,如果无法立即获得,则返回null而不是阻塞。

特点:

  • 提高了程序响应性和效率,因为线程可以同时处理多个任务。
  • 需要更复杂的逻辑来处理操作的结果和重试逻辑。

反射

1.获取class对象的三种方式

Class.forName   类名.class  对象引用.getClass

2.双亲委派模型

为了解决自定义类名和jdk类名重复

​

编辑

每个类加载器都很懒,加载类时都先让父加载器去尝试加载,父加载器加载不了时自己才去加载。 

3.类的加载时机

​编辑

​编辑

静态代码块和静态变量在类的加载阶段就已经赋值了,所以不能被非静态的变量或成员方法调用 

​编辑

​
posted @ 2024-10-12 19:36  freps  阅读(71)  评论(0)    收藏  举报