面试题一:Java是值传递还是引用传递?
Java是值传递。这意味着当将一个变量传给一个方法的时候,我们实际上是传递的是这个变量的的副本。
但是对于对象来说,我们传递的是其的副本,我们不可以改变对象的引用本身,但是我们可以调用其方法和修改其字段
java
class Person {
String name;
Person(String name) {
this.name = name;
}
}
void changeName(Person person, String newName) {
person.name = newName;
}
Person john = new Person("John");
changeName(john, "Jack");
System.out.println(john.name); // 输出 "Jack"
void changeName(Person person, String newName) {
person = new Person(newName);
}
Person john = new Person("John");
changeName(john, "Jack");
System.out.println(john.name); // 输出 "John"
面试题二:反射的使用场景有哪些?如何实现反射?
在Java中,反射一种特性,它允许运行中的Java代码对自身进行检查,并对类,字段,方法进行操作。以下是反射的常见使用场景:
- Spring AOP功能:Spring是一个功能强大的企业级开发框架,它广泛使用反射来实现依赖注入,AOP(面向切片编程)等功能。通过反射,Spring能够在运行时动态地构建和管理对象,并且依赖注入到对象中
- MyBaits Plus 框架:MyBatis Plus 是一个强大的 ORM (对象关系映射)架,它是基于MyBatis 架的增强框架,MyBatis Plus 中大量使用了反射机制,例如 MyBatis Plus 通过反射来分析实体类的字段和方法,从而动态地生成 SOL 语句和处理数据库操作。通过反射,可以获取实体类的属性、字段名称、数据类型等信息,并将其映射到数据库表的列。
- JUnit 测试框架: JUnit 是用于 Java 单元测试的常用框架,它使用反射来实例化测试类、调用测试方法,并进行断言和校验。通过反射,JUnit 能够在运行时动态地执行测试代码,并获取测试方法的结果。
反射的实现主要涉及以下几个步骤:
获取Class对象:首先我们需要获取操作类的对象,通过.getClass()方法,或者通过类名直接获取
java
MyClass myObject = new MyClass();
Class<?> clazz1 = myObject.getClass();
System.out.println(clazz1.getName()); // 输出 "com.example.MyClass"
Class<?> clazz2 = Class.forName("com.example.MyClass");
System.out.println(clazz2.getName()); // 输出 "com.example.MyClass"
获取和操作成员:我们获得Class对象之后,就可以操作它的成员
java
Field field = clazz.getField("myField");
Object obj = clazz.newInstance();
field.set(obj, "New Value");
这段代码是使用Java反射API来操作类的字段。让我逐行解释一下:
-
Field field = clazz.getField("myField");
:这行代码是获取名为myField
的字段的Field
对象。clazz
是我们想要操作的类的Class
对象。 -
Object obj = clazz.newInstance();
:这行代码是创建一个clazz
类的新实例。newInstance()
方法会调用类的无参数构造器来创建新的对象。 -
field.set(obj, "New Value");
:这行代码是将obj
对象的myField
字段设置为"New Value"
。set()
方法需要两个参数:第一个参数是我们想要操作的对象,第二个参数是我们想要设置的新值。
所以,总的来说,这段代码的作用是创建了一个clazz
类的新实例,并将其myField
字段设置为"New Value"
。
创建和操作对象:我们还可以使用Class对象来创建新的对象实例,或者调用对象的方法:
java
public class Person {
public String name;
public Person(String name) {
this.name = name;
}
public void sayHello(String greeting) {
System.out.println(greeting + ", " + name + "!");
}
public static void main(String[] args) throws Exception {
// 获取Person类的Class对象
Class<?> clazz = Class.forName("Person");
// 获取Person类的构造器
Constructor<?> constructor = clazz.getConstructor(String.class);
// 使用构造器创建一个新的Person对象
Object obj = constructor.newInstance("John");
// 获取sayHello方法
Method method = clazz.getMethod("sayHello", String.class);
// 调用sayHello方法
method.invoke(obj, "Hello");
}
}
面试题三:反射有什么优缺点?为什么反射执行的比较慢?
反射优点:
- 动态性:反射是的程序在运行时可以动态获取类的信息和操作类或者对象,使得代码更加灵活通用
- 通用性:反射可以处理不同的类的对象,是的代码更加通用和复用
反射缺点:
- 性能比较低:由于反射需要在运行时动态获取信息和调用方法,会导致性能相对较低,因此在性能要求较高的场景下,应谨慎使用
- 安全性问题:反射可以访问和修改对象的私有字段和方法,这可能导致安全性问题。
为什么反射执行的比较慢?
反射执行慢的主要原因是反射涉及到运行时类型检查,访问权限检查,动态方法调用和一些额外的操作。具体要经历以下过程:
- 运行时类型检查:在使用反射的是哦胡,需要在运行时进行类型检查,以确保调用的方法,访问的属性等是有效的,这涉及到了额外的云心那个是判断和类型转换
- 访问权限检查:Java的反射机制可以突破访问权限的限制,可以直接访问私有的成员。因此在访问私有成员的时候,需要进行额外的权限检查和处理,这样会带来额外的开销
- 方法调用的动态性:对于通过反射调用的方法,需要在运行时动态的解析方法的签名,并确定要调用的具体的方法。这需要进行方法查找和动态绑定的过程,相对于直接调用方法会更加耗时
- 临时对象的创建:反射会导致对象的多次创建临时对象的产生,这在某些情况下可能会引起额外的开销。反射操作一般不会被JVM的即使编译器优化的,也没有缓存和重用,所以也会比较慢
- 禁止编译器优化:由于反射是在运行时进行的,而不是在编译时,这意味着编译器无法进行静态优化和代码优化。这会导致反射的效率比较低
面试题四:动态代理的使用场景有哪些?它和静态代理有什么区别?
- 动态代理:动态代理是在程序运行时,动态的创建目标对象的代理对象,并对目标对象中的方法啊进行功能性增强的一种技术
- 静态代理:静态代理其实就是事先写好代理类,可以手工编写也可以使用工具生成,但它的缺点是每个业务类都要对应一个代理类,特别不灵活,也不方便。
二者最大的区别就是:静态代理是在编译期确定的代理类,动态代理是在运行时由JVM根据反射机制确定的代理类
动态代理的使用场景如下:
- 统计每个API的请求耗时
- 统一的日志输出
- 校验被调用的API是否已经登陆和权限的鉴定
- Spring的额AOP功能模块就是采用动态代理的机制来实现切片编程
面试题五:BIO、NIO、AIO有什么区别?同步非阻塞和异步非阻塞有什么区别?
BIO、NIO、AIO时Java中的三种I/O模型,它们的区别主要体现在阻塞与非阻塞,同步与异步的两方面:
- BIO(同步阻塞模型):每个客户端连接都需要创建一个线程进行处理,如果这个链接不做任何十四请会造成不必要的线程开销
- NIO(同步非阻塞模型):服务器实现模式为一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理
- AIO(异步非阻塞模型):服务器实现模式为一个有效的请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理
同步非阻塞和异步非阻塞的主要区别在于是否需要等待I/O操作完成:
- 同步非阻塞:在发起一个I/O操作后,不需要等待其完成,可以继续执行其他操作。但是如果I/O操作还没完成,就需要不断询问其状态
- 异步非阻塞:在发起一个I/O操作后,也不需要等待其完成,可以继续执行其他操作。当I/O操作完成后,会得到一个通知
面试题六:Exception和Error有什么关联和区别?
关联:Exception和Error都是继承Throwable类,在Java中只有Throwable类型的实例,才可以throw异常或者catch异常,都是异常处理机制的基本组成类型
区别:
- Error:Error类一般是指与虚拟机相关的问题,如系统奔溃,虚拟机错误,内存空间不足,方法调用栈溢出。这类错误导致的应用程序中断,仅靠程序本身无法恢复和预防,建议程序终止
- Exception:Exception类表示程序可以处理的异常可以捕获且可能恢复。遇到这类异常,应该Exception尽可能处理异常,使程序恢复运行,而不应该随意终止异常
面试题七:抽象类和接口有什么区别?
抽象类和接口有以下几种区别:
- 类型扩展不同:抽象类是单继承,接口是多继承
- 方法/属性访问控制符不同:抽象类方法和属性使用访问修饰符无限制,只是抽象类的抽象方法不能被private修饰;而接口有限制,接口默认的是public控制符,不能使用其他修饰符
- 方法实现不同:抽象类中的普通方法必须有实现,抽象方法必须没有实现;而接口中普通方法不能有实现
- 使用目的不同:接口是为了定义规范,而抽象类是为了复用代码
java
// 接口定义规范
public interface Flyable {
void fly();
}
public class Bird implements Flyable {
@Override
public void fly() {
System.out.println("Bird is flying.");
}
}
// 抽象类复用代码
public abstract class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}
public class Dog extends Animal {
public void bark() {
System.out.println("Dog is barking.");
}
}
面试题八:Java中线程安全的容器有哪些?它们分别是怎么保证安全性的
Java中线程安全的包括Vector、Stack、BlockingQueue、Hashtable、ConcuurrentHashMap这几种,它们有很多方式来保证线程安全,如:
- Vector和Stack是通过synchronized加锁写入方法来保证线程安全的
- BlockingQueue是通过ReentrantLock来保证线程安全的
- Hashtable是通过synchronized保证线程安全的
- ConcurrentHashMap在JDK 1.7是通过分段锁保证线程安全的,之后通过synchronized和CAS保证线程安全
面试题九:ArrayList和LinkedList有什么区别?
ArrayList和LInkedList是Java中List的两种实现,它们的主要区别有以下几种:
- 数据结构:ArrayList是动态数组实现的,而LinkedList是双向链表实现的
- 随机访问:对于随机访问ArrayList是优于LinkedList,前者时间复杂度是O(1),后者是O(n)
- 插入删除:插入操作LinkedList是优于ArrayList,前者时间复杂度是O(1),后者是O(n)
- 内存占用:ArrayList是需要一块连续的地址存储数据,而LinkedList是不要求连续空间的,但是它需要额外的空间存储前后指针,所以LinkedList会更加占用空间
面试题十:HashMap底层是如何实现的?
不同的JDK 版本,HashMap 的底层实现是不一样的,总体来说: 在JDK 1.8 之前不含JDK 1.8)HashMap 使用的是数组 + 链表实现的,而JDK 1.8 之后(包含JDK 18)使用的是数组 + 链表或红黑树实现的,当插入到同一个桶的数据过多的时候就会使用红黑树。