toString() 、String.valueof()、(String)?
- toString():Object 类的方法,可以将一个对象转换为字符串类型
- String.valueOf():可以将一个对象或基本数据类型转换为字符串类型
如果参数是对象,则调用 toString() 方法;
如果是基本数据类型,则直接将其转换为字符串类型 - (String):强制类型转换,需要使用 instanceof 做类型检查,不建议使用
hashCode() 方法?
- HashCode() 是 Object 类方法,用于返回对象的哈希码,这个值通常用于快速查找对象
hashCode 和 equals 方法判断两个对象是否相等?
- 两个对象的 hashCode 值不相等,对象不相等
- 两个对象的 hashCode 值相等,不一定相等(哈希碰撞)
- 两个对象的 hashCode 值相等,并且 equals() 方法也返回 true,说明这两个对象相等
为什么重写 equals 时必须重写 hashCode 方法?
- 判断对象是否存在的时候,会先检查 hashCode 是否相等
- 如果不相等,会直接认为对象不相等,不会再调用 equals 进行比较
String、StringBuffer、StringBuilder?
- String 类型是不可变的,每次对 String 类型操作时,都会生成新的 String 对象
- StringBuffer 类型是可变的字符串,表示一个字符序列。是线程安全的,通过使用同步来保证多个线程之间的正确性
- StringBuilder 类型也是可变的字符串,和 StringBuffer 本质相同,但是是非线程安全的
String 为什么是不可变的?
- String 类被声明为 final,不可以被继承
- 使用字符数组来保存字符串的值,该数组被声明为 final,并且不提供方法以修改字符数组的内容
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
String 不可变的好处?
- 安全:String 不可变,所以可以用来当作常量使用
- 线程安全:String 不可变,所以可以在多个线程中安全的共享
- 编译器优化:编译器可以在编译时优化代码,例如使用常量折叠
String 的特性?
- 不可变:一旦字符串被创建,它的值就不能更改
- 比较:可以使用 equals() 方法比较字符串的值,也可以使用==运算符比较字符串的引用
- 字符串池:Java 创建了一个字符串池,用于存储字符串常量。当创建新字符串时,Java 会先检查字符串池中是否已经有该字符串,如果有,则返回该字符串的引用,否则创建新字符串并将其添加到字符串池中
字符串拼接用 + 还是 StringBuilder?
- Java 中的 + 运算符经过重载后,实际上是通过 StringBuilder 调用 append() 方法实现的
在循环内使用 + 进行字符串拼接时,由于编译器不会复用 StringBuilder 对象,会导致创建过多的 StringBuilder 对象 - 应该直接使用 StringBuilder 对象的 append() 方法
String 类和 Object 类的 equals() 方法?
- Object 的 equals() 方法:比较两个对象的引用是否相等
- String 类的 equals() 方法:经过重写,比较的是两个字符串的内容是否相等
String s1 = new String(“abc”) 这句话创建了几个对象?
会创建两个对象,这句代码的执行顺序如下:
- 在常量池中创建一个字符串 “abc” 的对象
- 在堆内存中创建一个新的字符串对象,该对象与字符串中的对象具有相同的值 “abc”,将堆创建的对象引用赋值给 s1 变量
String s = new String(“a”) + new String(“b”) 创建了几个对象?
- 对象 1:StringBuilder 对象,表示字符串拼接的缓冲区
- 对象 2:new 的 String(“a”) 对象
- 对象 3: 常量池对象 “a”
- 对象 4:new 的 String(“b”) 对象
- 对象 5: 常量池对象 “b”
- 对象 6: 使用 StringBuilder 的 toString() 方法获取最终的拼接字符串,创建的新的 String 对象 “ab”
intern 方法的作用?
- String 类的方法,作用是将字符串对象添加到字符串常量池,并返回常量池中该字符串对象的引用
// 在堆中创建字符串对象”Java“
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 = s1.intern();
// 会在堆中在单独创建一个字符串对象
String s3 = new String("Java");
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s4 = s3.intern();
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 == s4); //true
//创建两个对象,堆空间一个new对象,字符串常量池一个字符串常量对象"a"
String s = new String("a"); //s指向堆空间的对象地址
s.intern(); //调用此方法之前,字符串常量池已经有"a"
String s2 = "a"; //s2指向字符串常量池"a"
System.out.println(s == s2); //false
String s3 = new String("a") + new String("b"); //等价new String("ab"),但常量池没有"ab"
s3 = s3.intern(); //字符串常量池中创建一个指向堆对象的引用
String s4 = "ab"; //指向常量池中存在的"ab"
System.out.println(s3 == s4); //true
String 类型做 + 运算时发生了什么?
- String 类型在做 + 运算时,运行时会被转化为 StringBuilder 对象进行操作
在编译期间,Java 编译器会进行常量折叠,对字符串常量的拼接进行优化。但如果在运行时才能知道其确切值,就无法对其优化
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing"; //常量池中的对象 编译器会进行常量折叠 优化成"String"
String str4 = str1 + str2; //在堆上创建新的StringBuilder对象
//String str4 = new StringBuilder().append(str1).append(str2).toString();
String str5 = "string"; //常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
Exception 和 Error 有什么区别?
- Exception 类是程序在运行时可能出现的异常,比如说输入错误或者网络连接错误,可以通过程序进行处理
- Error 类表示 Java 虚拟机本身的错误,比如内存溢出等,会终止程序的运行
Checked Exception 和 Unchecked Exception 有什么区别?
- Checked Exception 是编译时异常,可以显式捕获,需要在代码中进行处理,否则会产生编译错误。常见的比如:IO 异常、 SQL 异常
- Unchecked Exception 是运行时异常,不需要被显式的捕获,交给 JVM 处理
常见的比如:NullPointerException(空指针异常) ArrayIndexOutOfBoundsException(数组越界错误)
Throwable 类常用方法有哪些?
- getMessage():获取异常的信息
- printStackTrace():在控制台打印异常信息
- getCause():获取抛出当前异常的原因
try-catch-finally 如何使用?
- try 中包含可能会抛出异常的语句;
- catch 用来捕获并处理异常;
- finally 中的代码块一定执行,用于关闭在 try-catch 中打开的资源
finally 中的代码一定会执行吗?
- 不一定
- 正常情况下,finally 块中的代码都会执行
- 但是在程序遇到致命错误,程序立即退出的情况下,不会执行
- 如果在 finally 执行之前,虚拟机被终止的话,finally 的代码也不会被执行
如何使用 try-with-resources 代替 try-catch-finally?
Java 引入新的 try-with-resources 语句,用于自动关闭程序中使用的资源,比如数据库连接,文件 I/O 流等,在 try 语句块结束后,自动关闭这些资源,不需要再写 catch 和 final 语句来释放资源
- 需要实现 lang.AutoCloseable 或者 io.Closeable 接口来使用该新语句
什么是泛型?有什么用?
- 泛型是 Java 的一个重要特性,用于实现参数化类型
- 泛型的作用:
- 使用泛型可以在编译时进行类型检测,减少运行时错误
- 通过泛型参数指定传入的对象类型
泛型的使用方式有哪几种?
- 泛型类
- 泛型接口
- 泛型方法
- 泛型类型通配符
什么是泛型擦除机制?为什么要擦除?
- 泛型擦除机制是 Java 在编译的时候去除泛型类型的参数信息,将其转换成原始类型
- 这样做的主要目的是为了保持 Java 代码的向后兼容性,和之前版本的 Java 代码兼容
什么是桥方法?
- 桥方法是一种虚拟机技术,是用来处理泛型类型中的继承和多态问题的。
- 编译器会在继承类或者实现接口中自动生成桥方法,保证编译后的字节码可以正确的执行。
- 桥方法的作用是将一个泛型方法转换为一个桥接方法,其实现是调用原方法,并将参数进行强制类型转换,最终返回结果。
泛型有哪些限制?为什么?
- 不能使用基本数据类型作为泛型类型参数:基本类型无法转换成 Object 类型
- 不能创建泛型数组,数组的类型必须确定:泛型的类型参数在编译时需要进行擦除
- 不能直接使用泛型类型实例化:因为泛型类型在运行时不能获取其具体类型。
什么是通配符?有什么作用?
- 通配符是一种符号,代表任何类型
- 通常和关键字 extends/super 结合使用,用来限制泛型类型的范围
<? extends Person> //限制类型为Person的子类
<? super Manager> //限制类型为Person的父类
通配符?和常用的泛型 T 有什么区别?
- 通配符?是一个符号,可以代表任何类型,需要和关键字 extends/super 结合使用来限制范围
- 泛型 T 是一个具体的类型参数,用于指定一个具体的数据类型
什么是无界通配符?
- 没有指定通配符类型的上界,表示可以接受任何类型的对象
- 通常有两种使用情况
- 泛型类型参数只用于方法的参数传递,不需要方法的返回值
- 泛型类型参数只用于类的成员变量,不进行任何操作
List<?>和 List 的区别?
- List 是一个具体类型的泛型列表,需要在创建时指定元素类型,可以添加该类型的元素
- List<?>是一个通配符泛型列表,表示未知类型,适合用于读取和遍历列表,但不能直接添加元素。
List<?> unknownList = new ArrayList<String>(); // 可以接受任何类型的列表
什么是上边界通配符?什么是下边界通配符?
- 上边界通配符:使用 extends 关键字限定泛型类型的上界,表示该泛型必须是其子类
- 下边界通配符:使用 super 关键字限定泛型类型的下界,表示该泛型必须是其父类。
反射?
- Java 的反射是指在运行时动态的获取和操作类的信息
- 包括类的结构、方法和构造函数等
- Java 提供反射 API,在编译时,通过名称获取类的信息
反射的应用场景了解么?
- 实现注解和注解处理器
- 动态代理
- Java Bean 的动态操作
- 调试工具
- 配置文件解析
- 单元测试
- 序列化和反序列化
- 依赖注入
谈谈反射机制的优缺点
- 反射使代码更加灵活,使程序具有更高的动态性和可扩展性
- 反射的灵活性降低了程序的性能,同时也增加了安全问题
获取 Class 对象的四种方式
- 使用类名.class 方法
- 使用对象的 getClass() 方法
- 使用 Class.forName() 方法
- 使用类加载器的 ClassLoader 的 loadClass() 方法
反射的一些基本操作
- 获取 Class 对象:例如使用类名.class 方法获取类的 Class 对象
- 获取类的属性:使用 Class 的 getDeclaredFields() 方法获取类的所有属性
- 获取类的方法:使用 Class 的 getDeclaredMethods() 方法获取类的所有方法
- 创建对象:使用 Class 的 newInstance() 方法根据类的信息动态创建对象
- 调用方法:使用 Method 类的 invoke() 方法对方法进行调用
- 设置属性:使用 Field 类的 set() 方法对属性进行设置
注解是什么?
- 注解 (Annotation) 是一种元数据,可以用来为程序中的代码元素,如类、方法、属性等添加额外的信息和标识,并可以在运行时动态获取这些信息或者在编译时进行相应的处理。
常用的注解?
- @Override:标识一个方法重写了父类的方法
- @Deprecated:标识一个方法或类已经过时,不应该再使用
- @SuppressWarnings:用于抑制编译器的警告信息
- @Autowired:自动依赖注入对象
- @Component:标识一个类为组件,可以被自动扫描和注册到容器中
- @RestController:标识一个类为 RESTful 服务的控制器
- @RequestMapping:标识一个方法对应的 URL 地址和 HTTP 请求方法
- @Transactional:标识一个方法或类需要进行事务管理
- @NotNull:标识一个方法或参数不能为空
- Spring 框架中的@Service、@Repository、@Controller 等
- JUnit 测试框架的@Test、@Before、@After 等。
注解的解析方法有哪几种?
- 编译期直接扫描:在编译时,编译器会扫描源代码中的注解,并根据注解的定义执行相应的处理。
例如,@Override 注解用于检查方法是否实际重写了父类的方法。编译器会在编译时检查并报告相关错误。 - 运行期通过反射处理:在运行时,通过反射机制可以获取类、方法、字段等的注解信息,并根据注解的定义执行相应的逻辑。这种方式适用于框架和库,在程序运行时动态地根据注解来实现特定的功能。
SPI 和 API 有什么区别?
- API:应用程序接口,是用于访问功能的标准接口,是一种调用方式,用于集成不同模块的功能。
- SPI:软件提供接口, 是一种软件设计模式,用于实现插件和扩展,提供了一种实现方式,让应用程序可以动态地选择和加载不同的实现
ServiceLoader 具体实现?
- ServiceLoader 是 Java 提供的一种用于实现 SPI 的机制。允许定义一组接口,并允许不同的服务提供者在运行时注册和被加载
- 具体实现流程:
- 服务提供者的注册: 服务提供者将其实现类在规定的路径下创建配置文件,写入实现类的全限定名
- 加载服务: 当调用 load 方法时,ServiceLoader 会根据传入的接口类获取其类加载器,并加载对应的配置文件,读取实现类的全限定名
- 实例化类:ServiceLoader 使用反射机制实例化每个实现类,然后将它们加入到内部的缓存中
- 提供迭代器:通过调用 iterator() 方法,获取已加载的实现类的迭代器,从而遍历并使用这些实现类
ServiceLoader 的工作原理主要包括服务提供者的注册、加载服务、实例化类和提供迭代器。这个机制使得可以在应用程序中不修改代码的情况下,通过添加或修改配置文件来加载新的服务实现,从而实现了一种插件化和可扩展的设计
示例配置文件(META-INF/services/com.example.MyService
):
com.example.impl.MyServiceImpl
com.example.impl.AnotherServiceImpl
示例代码:
import java.util.ServiceLoader;
public class ServiceLoaderExample {
public static void main(String[] args) {
ServiceLoader<MyService> serviceLoader = ServiceLoader.load(MyService.class);
for (MyService service : serviceLoader) {
service.doSomething();
}}}
其中,MyService
是接口,MyServiceImpl
和 AnotherServiceImpl
是实现类。
什么是序列化和反序列化?
- 序列化和反序列化是将对象转换为二进制数据流和将二进制数据流还原为对象的过程
- 可以用于网络传输、缓存系统等,对应于四层模型的应用层
常见序列化协议有哪些?
- JDK 自带的序列化方式
- JSON
- XML
JDK 自带的序列化方式
- JDK 自带的序列化,只需实现 java.io.Serializable 接口即可
serialVersionUID 有什么作用?
- serialVersionUID 是序列化号,用于在 Java 序列化和反序列化中比较两个对象的版本是否一致
- 如果序列化号不一致会抛出 InvalidClassExpection 异常
serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?
- serialVersionUID 虽然是一个 static 变量,但不会被序列化保存在对象中,只是一个版本号
- 在反序列化时,会读取对象的的 serialVersionUID 值,并与本地的做比较,如果不一致,则会抛出异常。
如果有些字段不想进行序列化怎么办?
- 使用 transient 关键字
- 使用 transient 标记的变量其值会在序列化和反序列化过程中被忽略掉,因此在反序列化之后需要重新初始化或者赋值,否则其值为 null
- transient 只能修饰变量
Java 的 Unsafe 类?
- JDK 提供的不安全的工具类,提供了可以直接操作内存和执行底层操作的方法
- Java 已经不再支持 Unsafe 类的使用
Java 语法糖?
- 提供给开发人员便于开发的一种语法
- 在编译时会在编译时被翻译成底层的底层语言
JDK1.8 都有哪些新特性?
- 接口默认方法和静态方法
- Lambda 表达式:本质上是一段匿名内部类,简化代码
- Stream API:用函数式编程方式在集合类进行复杂操作
- Optional 类:用来解决空指针异常的问题