1.Java语言有哪些特点
Java语言有以下特点:
- 简单易学:Java的语法与C++类似,但去掉了C++中复杂的指针、多重继承、运算符重载等特性,使得Java语法更加简单易学。
- 跨平台性:由于Java语言的运行环境(JRE)提供了一层中间层,使得Java代码可以在不同平台上运行,这也是Java最大的特点之一。
- 面向对象:Java语言是一种真正的面向对象编程语言,所有的数据类型都是对象,所有的方法都是对象的方法,这种面向对象的特性使得Java在编程时更加灵活。
- 安全性高:Java具有很高的安全性,因为在Java编译器对程序进行编译时,会对程序代码进行检查,排除可能存在的潜在安全问题。
- 高性能:由于Java的垃圾回收机制和JVM优化技术,使得Java程序性能较高,可以用于开发各种复杂的应用程序。
- 多线程:Java是一种支持多线程编程的语言,可以轻松实现多线程编程,实现同时处理多个任务。
2.访问修饰符 public,private,protected,以及不写(默认)时的区别?
访问修饰符 public、private、protected 是用来限定类里面的成员(变量、函数、类等)在外部能否被访问的权限。其中,public 表示公开的,任何地方都能访问; private 表示私有的,只有在类内部才能访问; protected 表示受保护的,只有派生类和自身类内部能访问。
如果不写访问修饰符,则默认为 private,即只能在类内部访问。需要注意的是,如果将成员设置为 private,但是又需要在其他类中访问,可以通过提供 public 的成员函数来实现。
总而言之,访问修饰符可以帮助我们控制程序的访问权限,从而提高程序的安全性和可靠性。
3.重载和重写的区别
重载 (overloading) 指的是在同一个类中定义两个或多个名称相同的方法,但是这些方法的参数列表不同。这样,我们就可以使用相同的方法名称来完成不同的功能。
重写 (overriding) 指的是在子类中定义一个与父类中同名的方法,并使用 @Override 注解进行标记。这样,当调用这个方法时,会优先使用子类中的定义,而不是父类中的定义。
重载的目的是为了在同一个类中提供多个版本的同一个方法,以便能够满足不同的需求。而重写则是在继承关系中,为了提供一个类的特定实现,覆盖父类的方法。
4.equals与==的区别
equals() 方法是用于比较两个对象的内容是否相同,而 == 运算符则是用于比较两个对象的引用是否相同。换句话说,equals() 方法比较的是两个对象的值是否相等,而 == 运算符比较的是两个对象在内存中的地址是否相同。如果两个对象的内容相同,那么它们的 equals() 方法应该返回 true,但是如果它们的引用不同,那么它们的 == 运算符会返回 false。
5.ArrayList和linkedList的区别
ArrayList 和 LinkedList 是两种不同类型的集合。ArrayList 是基于数组的集合,而 LinkedList 是基于链表的集合。下列是它们之间的几个区别:
- 插入元素:在 ArrayList 中,如果向集合的中间添加或删除元素,则需要移动所有后续元素的索引。而在 LinkedList 中,则只需要修改相邻元素之间的指针即可。
- 访问元素:在 ArrayList 中,通过索引查找元素非常快,因为索引确定了元素在数组中的位置。在 LinkedList 中,则需要从头开始遍历整个链表查找元素,这可能需要花费更长的时间。
- 删除元素:在 ArrayList 中,如果向集合的中间删除元素,则需要移动所有后续元素的索引。而在 LinkedList 中,则只需要修改相邻元素之间的指针即可。
- 内存使用:ArrayList 要求在最初创建集合时分配内存空间。如果添加更多元素,而集合的大小已经等于分配的内存空间,则需要重新分配更多空间,并将所有元素复制到新的空间中。而 LinkedList 只需要为每个元素分配内存空间,因此可以动态地添加更多元素而不需要重新分配内存。
- 迭代器操作:在 ArrayList 中,迭代器操作效率更高,因为它们只涉及在数组中的索引操作。而在 LinkedList 中,则需要从头开始遍历整个链表,这可能需要花费更长的时间。
6.String、String StringBuffer 和 StringBuilder 的区别是什么?
String、StringBuffer 和 StringBuilder 都可以表示字符串,但是它们的区别在于:
- String 是不可变的,即一旦创建就不能再被修改,每次操作 String 都会创建一个新的对象,因此在频繁修改字符串的情况下会造成性能问题。
- StringBuffer 是可变的,每次操作都会在原字符串对象的基础上进行,不会创建新的对象,因此在频繁修改字符串的情况下使用 StringBuffer 可以有效提高性能。
- StringBuilder 也是可变的,与 StringBuffer 的区别在于 StringBuilder 是线程不安全的,而 StringBuffer 是线程安全的。因此在单线程环境下建议使用 StringBuilder,而在多线程环境下建议使用 StringBuffer。
7.深拷贝和浅拷贝的区别是什么?
深拷贝和浅拷贝都是用于复制一个对象或数据结构的操作,但是它们的实现方式和复制的结果有所不同。
浅拷贝只是复制了对象中的基本类型数据和引用类型数据的引用地址,而没有对引用类型数据所对应的对象进行复制。这意味着,如果原始对象中的引用类型数据发生了变化,那么浅拷贝出来的新对象中对应的引用类型数据也会发生变化。
深拷贝则是对整个对象进行复制,包括基本类型数据和引用类型数据所对应的对象都进行了复制,因此无论原始对象中的引用类型数据是否发生变化,深拷贝出来的新对象都不会受到影响。
总之,深拷贝和浅拷贝的主要区别就在于对于引用类型数据的处理方法和复制结果的不同。
8.HashTable, HashMap,TreeMap区别?
HashTable, HashMap和TreeMap都是用于存储键值对的数据结构,但是它们有以下的区别:
- HashTable是线程安全的,HashMap是非线程安全的。
- TreeMap基于红黑树实现,可以保证键值对的有序性,而HashMap和HashTable不保证键值对的有序性。(因为hashMap和hashTable的键是通过hash算法计算出来的)
- 在HashMap和HashTable中,键是通过hash算法计算出来的,所以查询速度快,而TreeMap中的键是按照比较器排序的,所以查询速度较慢。
- HashMap对于键值对的null值作为key和value都是允许的,而HashTable不允许key和value为null,而HashMap 和 Hashtable 有什么区别?。
9.Java面向对象的三个特征 及其解释
Java面向对象的三个特征是封装、继承和多态。
封装是指将数据和操作数据的方法绑定在一起,使其成为一个“黑盒”,外部无法直接访问内部的数据,只能通过暴露出来的接口来进行访问和操作。这样可以避免程序出现意外的影响,提高了程序的安全性和稳定性。
继承是指子类可以继承父类的属性和方法,可以重写父类的方法以满足子类的需求,实现代码的复用和扩展,减少了代码的冗余,提高了代码的可读性和可维护性。
多态是指同一种类型的对象在不同的情况下表现出不同的行为,即同一方法可以有不同的实现方式。这样可以使程序具备更高的灵活性和扩展性,可以根据实际需要动态地选择适当的实现方式。(就是实现类)
10.int和Integer的区别
int是Java中的基本数据类型,它是一个32位的有符号整数。而Integer是一个包装类,它是int的封装类,提供了一些方便的方法来操作整数。Integer可以进行自动装箱和拆箱操作,可以将int类型的数据转换为Integer类型的数据,反之亦然。而int和Integer之间的最主要区别是,int是一个基本数据类型,而Integer是一个对象。
11.ArrayList、Vector和LinkedList的区别及使用场景
ArrayList、Vector和LinkedList都是Java中的集合框架中的List接口的实现类。它们有以下区别:
- 实现机制:ArrayList和Vector都是通过数组实现的,而LinkedList则是由双向链表实现的。
- 长度变化:当容量不足时,ArrayList会增加50%的容量,而Vector会增加100%的容量。
- 线程安全性:Vector是线程安全的,因为其所有方法都是同步的。而ArrayList和LinkedList是非线程安全的。
- 性能:在读取元素时,ArrayList是最快的,因为它是由连续的空间组成的。在插入和删除元素时,LinkedList则是最快的,因为它不需要移动元素。
在实际应用中,ArrayList和Vector适用于需要随机访问的场景,LinkedList适用于需要频繁插入和删除元素的场景。如果应用中涉及到线程安全问题,需要使用Vector。
12.Collection和Collections的区别
Collection是Java中的接口,代表一组对象的集合,可以用于存储、读取、删除和更新对象。Collections是Java中的类,用于操作和处理Collection接口的实现类对象,提供了一系列静态方法来对集合进行操作。简单来说,Collection是接口,而Collections是实现类。
13.HashMap底层实现原理和扩容机制
HashMap底层实现是基于数组和链表/红黑树实现的,数组是主要的数据存储结构,而链表/红黑树用于解决哈希冲突。每个数组元素都是一个链表/红黑树的头结点,元素的key通过哈希函数映射到数组下标。
扩容机制是为了解决哈希表存储空间不足的问题,通常在达到负载系数的阈值时进行扩容,扩容后会重新调整数组大小,重新计算哈希值。在扩容过程中,需要重新分配新的数组,并将旧数组元素重新哈希到新数组中。新数组的长度为原数组长度的两倍。
14.HashMap什么样的类适合作为键
HashMap中的键需要满足两个条件:可哈希性和相等性。因此,适合作为键的类需要满足以下要求:
- 实现hashCode()方法。
- 重写equals()方法,确保两个不同的实例在逻辑上相等时equals()方法返回true。
15.sleep()和wait()的区别
sleep()和wait()都是暂停程序执行的方法,但是它们的作用不同。
sleep()是让线程休眠一段时间,期间线程不会释放锁,其他线程不能访问线程正在执行的代码。等待时长到后,线程会自动恢复执行。
wait()则是让线程等待,线程会释放锁,其他线程可以访问线程正在等待的对象。需要使用notify()或者notifyAll()来唤醒等待中的线程。
因此,sleep()是暂停当前线程的执行,不会释放锁,而wait()是暂停当前线程的执行,释放锁,等待其他线程通知自己唤醒。
16.抽象类和接口的区别、以及使用场景
抽象类和接口都是Java中的抽象概念,它们的主要区别在于实现方式和使用场景。
一、抽象类
抽象类是一种不能被实例化的类,它的定义使用了abstract关键字。抽象类可以包含抽象方法和非抽象方法。
抽象方法是一种没有实现的方法,它的定义使用了abstract关键字。子类必须实现抽象方法才能被实例化。
非抽象方法是一种有实现的方法,它的定义没有使用abstract关键字。子类可以重写非抽象方法,也可以直接继承使用。
使用场景:
1.抽象类用于定义一些公共的属性和方法,被继承的子类可以实现和重写这些方法和属性,增强代码重用性和维护性。
2.抽象类可以作为一种规范、约束或标准来使用,让不同的子类去实现不同的行为或功能,提高代码的灵活性。
二、接口
接口是一种不能被实例化的纯抽象类,它的定义使用了interface关键字。接口定义了一系列的方法和常量,但没有实现。
接口中的所有方法都是抽象方法,定义使用了abstract关键字。
使用场景:
1.接口用于定义一些规范或标准,被实现的子类需要实现规定的行为或功能,提高灵活性和可维护性。
2.接口可以用于实现多重继承,一个接口可以被多个类实现,使得一个类具有更多的功能和行为。
17.什么是序列化
序列化是将数据结构或对象,转换成一系列字节,以便存储或传输。序列化的目的是将数据结构或对象从一种形式转换为另一种形式,以便在程序和系统之间进行交换或存储。在序列化中,数据结构或对象被编码为一种格式,通常是二进制格式,使其能够在网络上传输或存储在文件中。在序列化后,可以将数据结构或对象发送到另一个系统或重新读取以重建原始数据结构。
18.什么是AOP、Spring AOP的底层原理是什么
AOP(面向切面编程)是一种编程范式,它可以将横向关注点(比如日志、事务、安全等)从纵向业务逻辑中分离出来,实现可重用性和扩展性。
Spring AOP的底层原理是基于动态代理实现的。在Spring容器中,通过使用JDK自带的接口或者CGLIB库来在运行时生成代理对象,达到对目标对象的增强(即AOP的切入点)。代理对象拦截到切入点之后,再执行相应的增强逻辑(即AOP的通知)。因此,Spring AOP主要由三部分组成:切入点、通知和代理。
19.什么是IOC、IOC注入方式有哪些
IOC是Inversion of Control的缩写,即控制反转。它是一种编程思想,通过将某些关键的控制权交给框架或容器来实现组件之间的松散耦合,以便更好地管理复杂性。
IOC注入方式通常有三种:构造函数注入、setter注入和接口注入(注解)。构造函数注入是将依赖关系通过构造函数参数传递进组件中。setter注入是通过公共属性来注入依赖关系。接口注入是通过实现接口并注册依赖项来注入依赖关系。这些方式都可以实现依赖关系的注入,具体取决于应用程序需要的情况。
public class UserService {
private IUserDao userDao;
//构造函数注入
public UserService(IUserDao userDao) {
this.userDao = userDao;
}
//属性注入
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
//接口注入
@Autowired
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
}
20.Mybatis中 #{} 和 ${}的区别
#{}是预编译,可防止SQL注入。
${}是直接拼接在SQL语句中。
21.Spring Boot的核心注解是什么,它是由哪几个注解组成的
Spring Boot的核心注解是**@SpringBootApplication**,它是由三个注解组成的:@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。
22.SpringBoot 怎么读取配置文件
SpringBoot可以通过**@Value注解读取配置文件中的值,也可以通过@ConfigurationProperties注解读取整个配置文件中的值。另外,还可以通过Environment对象、PropertiesLoaderUtils类**等方式读取配置文件中的值。
23.MySQL优化相关的技巧和方法有哪些?
MySQL优化技巧和方法有很多,一些实用的技巧和方法如下:
- 使用索引:使用适当的索引可以大大提高查询效率。
- 优化查询语句:避免使用SELECT *,使用LIMIT分页查询等方式。
- 使用缓存:使用缓存可以减少对数据库的查询次数。
- 分区表:将大表拆分成多个小表,可以大大提高查询效率。
- 使用存储过程:存储过程可以减少客户端和服务器之间的数据传输量以及提高执行效率。
- 调整MySQL参数配置:根据实际情况调整参数配置,比如缓冲池大小、连接数、后台线程等。
- 数据库分离:将读/写分离可以减轻主数据库的负担,提高整个系统的性能。
24.MySQL的存储过程为什么会加快查询
MySQL的存储过程可以加快查询的原因有以下几点:
- 减少网络传输的数据量:存储过程位于数据库服务器上,客户端调用时只需要发送存储过程的参数,而不需要发送具体的 SQL 语句和数据,这样可以大大减少网络传输的数据量。
- 缓存执行计划:存储过程在第一次执行时,MySQL 会为其生成一个执行计划并缓存起来,再次执行时可以直接使用缓存中的执行计划,避免了再次解析 SQL 语句和生成执行计划的时间。
- 节省 CPU 资源:存储过程是预编译的,执行时可以直接调用编译好的机器码**,不需要再进行语法分析、编译等操作**,从而节省了 CPU 资源。
综上所述,MySQL的存储过程可以通过减少网络传输的数据量、缓存执行计划和节省 CPU 资源等方式,从而加快查询的速度。
25.什么是单例模式?有几种?
单例模式是一种创建型设计模式,它可以保证一个类在任何情况下都只有一个实例,并提供一个全局访问点。一般在需要控制某些资源的时候使用单例模式,例如线程池、数据库连接等。
根据实现方式的不同,单例模式可以分为两种:饿汉式和懒汉式。饿汉式是指在类加载的时候就已经创建了实例,而懒汉式是在第一次使用时才会创建实例。
26.创建线程有几种方式?
在Java语言中创建线程有两种方式:一种是继承Thread类,另一种是实现Runnable接口并将其作为参数传递给Thread构造函数。另外,还可以使用线程池来管理线程。
27.Runnable和Callable的区别?
Runnable和Callable都是Java中用于多线程编程的接口。
Runnable是一个函数式接口,其中只有一个run()方法,用于定义线程要执行的任务。
Callable也是一个函数式接口,其中只有一个call()方法,用于定义线程要执行的任务,并且可以返回结果。
所以,Runnable只能执行任务,而Callable既能执行任务,还能返回结果。
在使用时,可以通过将Runnable实例传递给Thread构造函数来创建一个新线程,而Callable通常要与Executor框架一起使用。
总之,Runnable适用于没有返回值的情况,Callable适用于需要返回值的情况。
28.线程相关的基本方法?
线程相关的基本方法包括:
- 创建线程:通过调用线程库提供的创建线程函数,在程序中创建一个新的线程。
- 启动线程:通过调用线程对象的start()方法,让线程开始执行。
- 线程同步:通过使用锁、信号量、条件变量等同步工具,控制不同线程对共享资源的访问。
- 线程间通信:通过使用管道、消息队列、共享内存等通信方式,让不同线程之间相互传递数据。
- 终止线程:通过调用线程对象的join()方法或者改变线程的状态变量,终止线程的执行。
29.java中常用的线程池类?
常用的线程池类有ThreadPoolExecutor和ScheduledThreadPoolExecutor。
30.java线程池创建时核心参数?
Java线程池创建时核心参数包括:核心线程数、最大线程数、空闲线程存活时间、任务队列、拒绝策略等。具体来说,通过设置核心线程数,可以确定线程池中应该维护的最小线程数;通过最大线程数,可以确定线程池中线程的最大数量;通过空闲线程存活时间,可以指定非核心线程空闲多长时间后被回收;通过任务队列,可以定义存放任务的缓冲区;通过拒绝策略,可以在任务队列满且线程池中线程数量达到最大线程数时,处理无法处理的任务。
31.同步锁、死锁、乐观锁、悲观锁?
同步锁,也称为互斥锁,是一种并发控制的方式。同步锁可以保证同一时刻只有一个线程可以访问共享资源,以避免竞态条件的出现。
死锁是指两个或多个线程互相持有对方所需要的资源,导致彼此都无法继续执行的情况。
乐观锁和悲观锁是两种并发控制方式。
悲观锁是在执行操作前先加锁,以避免并发访问的冲突。而乐观锁则是假设不会有并发冲突,只在提交数据时判断是否发生冲突,如果发生冲突则进行回滚或者重试操作。
32.谈谈对面向对象的理解?
当我们谈论面向对象时,我们指的是一种编程模式,其中数据和方法被封装到一个单独的对象中,被认为是一个实体。通过这种方式,我们可以使用面向对象编程技术来模拟现实世界中的对象,并实现复杂系统的模块化和可维护性。对象具有各自的属性和行为,并且可以与其他对象进行交互和协作。这种编程模式强调了代码的可重用性以及灵活性,并且有助于降低复杂系统的开发和维护成本。
33.jvm Gc垃圾回收,如何发现垃圾,如何回收垃圾?
jvmGc垃圾回收是Java虚拟机自动管理内存机制的一个关键部分。在Java虚拟机运行时,垃圾回收器会定期扫描内存中不再使用的对象,并将其标记为垃圾。垃圾回收器会自动回收这些垃圾,释放内存以供其他对象使用。
垃圾回收器有多种算法,包括标记清除算法、标记复制算法、标记整理算法等。这些算法的实现方式各不相同,但它们都有一个共同点:扫描内存,将不再使用的对象标记为垃圾,并回收这些垃圾。
垃圾回收的速度和效率对于应用程序的性能影响非常大,因此垃圾回收器的性能优化一直是研究的热点之一。为了提高垃圾回收效率,开发者可以采用一些技术手段,如调整垃圾回收器参数、手动控制对象的生命周期等。
总之,垃圾回收是Java虚拟机内存管理的关键机制,有助于提高应用程序的性能和稳定性。
34.mysql数据库的聚合函数常用的有那些?
常用的MySQL聚合函数包括:SUM(求和)、AVG(求平均数)、MAX(求最大值)、MIN(求最小值)、COUNT(统计行数)。
35.SQL Select 语句完整的执行顺序
SQL Select 语句的完整执行顺序如下:
- FROM:选择数据的表格或视图
- WHERE:根据条件筛选出需要的数据
- GROUP BY:按照指定的列对数据进行分组
- HAVING:对分组后的数据进行筛选
- SELECT:根据指定的列返回数据
- DISTINCT:对结果进行去重
- ORDER BY:按照指定的列对结果进行排序
- LIMIT/OFFSET:对结果进行分页处理。
36.MySQL的事务特性
MySQL的事务特性是ACID,即原子性、一致性、隔离性和持久性。原子性指事务是一个不可分割的工作单位,要么全部执行,要么全部不执行;一致性指事务执行前后,数据库的状态必须保持一致;隔离性指多个事务并发执行时,每个事务都不能看到其他事务的中间状态;持久性指事务执行后,其所做的修改必须被永久保存到数据库中。
37.数据库三范式
数据库的三范式是指,一个数据库表必须符合三个范式的要求,才能被认为是规范化的。第一范式要求每一列必须具有原子性,即不可再分解;第二范式要求表中非关键字列必须完全依赖于主键,不能存在部分依赖;第三范式要求**表中的每一列只与主键有直接关系,不能存在传递依赖。**这三个范式的目的是为了避免数据冗余、数据不一致和数据更新异常等问题,使得数据库能够高效、可靠地存储和查询数据。
38.索引的分类
MySQL索引可以分为以下几种类型:
- B-Tree索引:也被称为普通索引或者单列索引,是最常用的索引类型,可以用于所有比较操作符,包括=、>、<、>=、<=和BETWEEN等。
- 唯一索引:与普通索引类似,但是唯一索引中不能有重复的值。
- 全文索引:用于全文搜索查询,支持多种语言,以及自定义的分词规则。
- 空间索引:用于在空间数据中进行查询,支持几何关系查询和缓冲区查询。
- 单列索引和组合索引:单列索引是只包含一列的索引,而组合索引则是包含多个列的索引,可以提高查询的效率。
- 哈希索引:基于哈希表实现的索引,适合进行等值比较查询,但是对于范围查询和排序操作则效率比较低。
39.mysql如何优化索引
MySQL 优化索引的方法主要有以下几种:
- 确保每个表都有一个主键,以便避免使用 MySQL 内部的行 ID 作为索引。
- 在 WHERE、JOIN 和 ORDER BY 子句中使用索引来提高查询效率。
- 避免使用索引,尤其是在较小的表中。使用太多的索引会导致查询变慢,因此只需要使用必要的索引。
- 使用覆盖索引来减少 I/O 操作,并且使用最左前缀原则来创建索引。
- 定期执行 OPTIMIZE TABLE 操作以优化表格,并使用 EXPLAIN 命令来分析查询计划。
40.sql语句如何进行调优
SQL语句调优是为了提高查询性能和效率而进行的操作。以下是一些SQL语句调优的技巧:
- **确保表有正确的索引。**索引可以大大提高查询性能,因为它们允许数据库快速查找特定行。
- **避免使用SELECT *。**只选择所需的列,因为查询返回的数据越少,查询的速度就越快。
- 使用INNER JOIN、LEFT JOIN和RIGHT JOIN而不是CROSS JOIN。
- 避免使用子查询。子查询可以在某些情况下很慢,尤其是在大型表中,而且很难调试和优化。
- 尽量在WHERE子句中使用索引列。这样可以利用索引提高查询性能。
- 使用UNION ALL而不是UNION。UNION允许您组合两个查询的结果,但是它必须进行排重。UNION ALL则不需要排重。
- 避免使用游标。游标是一种慢而低效的方法来处理查询结果。
41.在mybatis中,${} 和 #{} 的区别是什么?
在MyBatis中,**${}中的值是直接替换到SQL语句中的,相当于直接拼接字符串,**容易受到SQL注入攻击,同时也无法避免类型转换问题。
而**#{}中的值是使用预编译参数方式传入SQL语句中的。这种方式可以有效的避免SQL注入问题**,也可以自动进行类型转换,使代码更加健壮和可靠。
42.在mybatis中,resultType和ResultMap的区别是什么?
在MyBatis中,resultType和ResultMap都是用于映射查询结果到Java对象的方式。区别如下:
- resultType指定了查询结果的类型,MyBatis会将查询结果自动映射到对应的Java对象上。例如:resultType=“com.example.entity.User”
- ResultMap是自定义映射规则,可以实现相对复杂的查询结果映射。例如,查询结果中包含了多个Java对象,或者查询结果需要特殊处理才能映射到Java对象。ResultMap定义了查询结果字段与Java对象属性之间的映射关系。
43.mybatis的重要组件?
Mybatis 的重要组件包括 SqlSessionFactory、SqlSession、Mapper 接口和映射 XML 文件。其中,
SqlSessionFactory 是 Mybatis 的核心对象,它用于创建 SqlSession 对象;
SqlSession 是 Mybatis 执行持久化操作的主要对象,用于提交 SQL 语句、获取持久化数据和关闭资源;
Mapper 接口和映射 XML 文件则用于定义 SQL 语句和执行 SQL 语句与 Java 对象之间的映射关系。
44.Spring支持bean的作用域有几种吗? 每种作用域是什么样的?
Spring支持5种作用域:singleton、prototype、request、session、global session。
- singleton:默认作用域,容器中只会存在一个共享的实例。
- prototype:每次请求都会创建一个新的实例。
- request:每个 HTTP 请求都会创建一个新的实例,该实例仅在当前请求内有效。
- session:每个 HTTP Session 会创建一个新的实例,该实例在整个会话期间有效。
- global session:基于 Servlet 3.0+ 的全局 HTTP Session 会话中每个应用程序都会创建一个新的实例。
45.Spring的对象默认是单例的还是多例的? 单例bean存不存在线程安全问题呢?
**Spring的对象默认是单例的,也可以配置为多例的。**单例bean在多线程环境下可能存在线程安全问题,因为多个线程共享同一个实例,如果线程之间修改了该实例的状态,可能会导致其他线程出现异常。因此,在开发中应该注意线程安全问题,可以采用同步或者使用ThreadLocal等方式解决。而多例bean则不存在线程安全问题,因为每个线程会创建一个新的实例。
46.Spring框架中都用到了哪些设计模式?
Spring框架中使用了许多设计模式,其中最常用的包括:
1.工厂模式(Factory):Spring框架中的BeanFactory就是使用了工厂模式设计的。
2.单例模式(Singleton):Spring框架中的Bean默认都是单例的,也可以通过配置实现非单例模式。
3.模板模式(Template):Spring框架中的JdbcTemplate就是一个模板模式的典型例子。
4.观察者模式(Observer):Spring框架中的事件驱动模型就是观察者模式的一种应用。
5.装饰器模式(Decorator):Spring框架中的AOP(面向切面编程)就是基于装饰器模式实现的。
6.代理模式(Proxy):Spring框架中的JDK动态代理和CGLib动态代理就是代理模式的应用。
47.Spring的常用注解?
常用的Spring注解包括:
- @Component:将类标记为Spring管理的组件
- @Controller:标记一个类作为Spring MVC控制器
- @Service:将类标记为业务逻辑层的组件
- @Repository:将类标记为数据存储层的组件
- @Autowired:自动注入一个类的实例
- @Qualifier:在多个实现类中指定要注入的Bean名称
- @Value:注入属性值
- @RequestMapping:将请求映射到处理程序方法
- @PathVariable:获取请求路径中的参数
- @RequestBody:将请求内容反序列化为参数
- @ResponseBody:将方法的返回值序列化为响应的格式
48.Spring的循环依赖问题以及怎么解决?
Spring的循环依赖指的是两个或多个类彼此依赖,形成了一个循环依赖的死循环。这样会导致Spring容器在创建bean实例时出现问题,无法完成依赖注入和对象的创建。
Spring解决循环依赖的方式一般有两种:
- 构造器注入解决:通过使用构造器注入的方式,可以让Spring在创建实例的时候直接传入依赖的对象,从而避免了循环依赖的问题。
- 延迟注入解决:通过使用Spring的延迟注入机制,可以让Spring在使用到某个对象的时候才对其进行注入。这样可以避免循环依赖问题,因为只有在需要的时候才会进行注入。
49.SpringBoot的优点?
SpringBoot的优点包括:
- 简化开发:SpringBoot提供了很多默认配置和约定大于配置的机制,可以快速搭建开发环境,减少开发者的工作量。
- 快速启动:SpringBoot使用嵌入式Web服务器,应用程序可以在几秒钟内启动和运行。
- 自动配置:SpringBoot可以自动配置依赖项,并为应用程序提供自动配置的特性。
- 版本管理:SpringBoot使用版本控制,可以轻松管理版本依赖,提高应用程序的可靠性。
- 监控管理:SpringBoot提供了许多监控管理的工具,可以方便地监控应用程序的运行情况。
- 简化部署:SpringBoot具有很好的可移植性,可以在不同的环境中轻松部署应用程序。
50.SpringBoot运行原理剖析?
SpringBoot是一个快速开发Web应用程序的框架,其运行原理可以概括为以下几个步骤:
- 构建和配置Spring应用程序上下文:SpringBoot会自动根据约定和配置文件来构建和配置应用程序上下文。
- 扫描和注册bean:SpringBoot会扫描应用程序中定义的bean,并将其注册到应用程序上下文中。
- 启动Web容器:SpringBoot会启动内嵌的Web容器(如Tomcat、Jetty等),并将应用程序部署到容器中。
- 处理HTTP请求:Web容器会监听HTTP请求,SpringBoot会将请求分派到相应的控制器进行处理。
- 渲染响应:控制器处理完成后,SpringBoot会渲染响应并将其返回给客户端。
51.SpringBoot常用注解?
- @SpringBootApplication:用于启动类中,表示启动一个SpringBoot应用,自动扫描该类所在包以及子包下的Bean组件。
- @RestController:用于定义Controller类,表示该类返回的数据为Json或XML格式的数据。
- @GetMapping或@PostMapping或@PutMapping或@DeleteMapping:用于定义HTTP请求的类型。
- @RequestBody:用于接收HTTP请求中的非表单数据。
- @RequestParam:用于接收HTTP请求中的参数数据。
- @PathVariable:用于获取HTTP请求中的路径变量。
- @Autowired:用于自动注入Bean组件。
- @Component:用于标注一个组件类。
52.Redis的常见数据类型?
Redis的常见数据类型包括字符串(string)、列表(list)、哈希(hash)、集合(set)和有序集合(sorted set)等。
53.redis缓存更新问题描述以及怎么解决?
对于 Redis 缓存,更新问题主要指的是缓存和数据库数据的一致性问题。当数据库中的数据发生更新时,如何及时更新 Redis 缓存以保证数据的一致性。
解决这个问题的方法有以下几种:
- 触发器实现:在数据库中设置触发器,当数据更新时自动触发 Redis 缓存更新操作;
- 定时刷新:定时扫描数据库中的数据,将更新的数据同步到 Redis 中;
- 主动更新:在数据更新后,通过代码主动更新 Redis 缓存;
- 延迟同步:当数据更新时不立即更新 Redis 缓存,而是将需要更新的数据暂时缓存到一个队列中,等待一定时间后再进行同步。
54.redis缓存穿透问题描述以及怎么解决?
Redis缓存穿透是指查询一个一定不存在的数据,由于缓存中没有相应的缓存值,每次针对这个不存在的数据请求都会直接落到数据库上,导致数据库压力过大甚至宕机的现象。一些攻击者可以利用缓存穿透漏洞来进行恶意攻击。
解决Redis缓存穿透问题的方法包括:
- 布隆过滤器:将所有可能存在的数据哈希到一个足够长的二进制向量中,并将其映射为一个布隆过滤器。在对一个查询请求进行判断时,先判断查询请求对应的数据是否在布隆过滤器中,若不在,则可以直接将查询请求返回,否则再进行进一步的查询操作。
- 缓存空对象:对于一些非法请求,在Redis中存储一个空对象,这样当再次请求相同的数据时,就会直接从缓存中读取空对象,而不会去查询数据库,从而解决缓存穿透问题。
- 定期刷新缓存:可以定期刷新缓存,保证缓存中的数据与数据库中的数据保持一致,从而避免因为数据失效导致的缓存穿透问题。
- 采用限流等手段:通过限流等手段,对恶意攻击进行拦截,从而减轻数据库的压力。
55.redis缓存击穿问题描述以及怎么解决?
关于 redis 缓存击穿问题,它指的是高并发场景下,一个 key 的缓存已经过期或者不存在,然后同时有大量的请求访问这个 key,导致请求直接落到了数据库上,造成了数据库的压力过大,导致系统的性能急剧下降甚至宕机。为了解决这个问题,可以使用以下几种方法:
1.设置热点数据永不过期,也就是说,把热点数据的过期时间设置成永久,这样可以避免热点数据负责击穿缓存。
2.加互斥锁,当缓存过期或不存在时,首先可以尝试通过加互斥锁来避免缓存击穿,也就是让只有一个请求去读取数据,并且成功获取到数据之后再释放锁,其他请求如果发现锁已经被其他请求持有,就等待或者抛弃读取请求。
3.使用布隆过滤器,通过布隆过滤器来判断有没有缓存命中,如果没有命中,直接返回结果即可,不需要去查询数据库。
4.对于请求量较大的场景,可以考虑使用 Redis Cluster,集群的状态会更加稳定,可以承受更大的并发请求。
56.redis缓存雪崩问题描述以及怎么解决?
Redis缓存雪崩是指在某一个时间段,缓存集中过期失效,同时又有大量的请求直接打到数据库,导致数据库瞬间压力过大甚至宕机的情况。造成缓存雪崩有多种原因,比如缓存数据的过期时间设置相近、缓存key设计不合理等等。
解决缓存雪崩问题的方法是:
- 设置缓存数据过期时间时,合理地分散缓存过期时间,比如为每个key设置不同的过期时间,或者为每个key的过期时间加上一个随机值,避免大量key在同一时间过期失效;
- 控制缓存的并发访问量,防止查询缓存和写入缓存的大量请求同时打到数据库,可以通过限流、降级等手段实现;
- 使用多级缓存架构,比如引入Redis主从架构、Redis集群、增加本地缓存等方式,提高缓存的稳定性和可用性。
57.Redis的持久化?
Redis提供两种持久化方式:RDB和AOF。RDB持久化会在指定的时间间隔内生成快照,将数据写入磁盘。**AOF持久化则是将Redis执行的每一条命令都记录下来,以文本方式保存到磁盘中。**两种方式都有优劣,可以根据实际情况选择。
58.为什么使用Executor框架?
1、每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。
2、 调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。
3、 接使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。
59.你能保证Gc执行吗?
不能,可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC的执行。
60.volatile关键字的原理是什么?干什么用的?
Volatile是Java中的一个关键字,它的主要作用是保证变量的可见性和禁止指令重排序优化。在多线程编程中,一个变量被多个线程共享时,每个线程都有自己的缓存,如果一个线程修改了该变量,其他线程可能无法立即看到修改后的值,使用volatile关键字可以强制让所有线程都能看到该变量的最新值,避免了数据的不一致性。此外,volatile还能禁止指令重排序优化,保证程序的执行顺序按照代码的先后顺序执行,避免由于指令重排而导致的程序错误。
61.synchronized 和 Lock 有什么区别?
synchronized 和 Lock 都可以用于实现多线程同步,但是它们有以下区别:
- 锁的类型:synchronized 是一种内置锁,而 Lock 是一种显式锁。
- 用法:synchronized 加锁和解锁是自动执行的,而 Lock 需要手动执行 lock 和 unlock 操作。
- 锁的可重入性:synchronized 是可重入锁,在同一个线程中可以重复获取同一个锁,而 Lock 默认情况下是不可重入的。
- 锁的灵活性:Lock 可以实现多种加锁模式,比如公平锁和非公平锁,而 synchronized 只能实现非公平锁。
- 锁的细粒度控制:Lock 可以锁住更细粒度的代码块,而 synchronized 只能锁住整个方法或代码块。
总的来说,synchronized 简单易用,而 Lock 更灵活。在性能方面,Lock 在高并发情况下比 synchronized 更优。
62.公平锁和非公平锁区别?
公平锁和非公平锁是指在多线程环境下保护共享资源的锁的两种不同实现方式。公平锁是指多个线程请求同一个锁时,获取锁的顺序是按照请求的顺序来的,即先到先得,保证线程获取锁的公平性;非公平锁则没有先来先得的规则,请求锁的线程有可能会被插队,导致先后顺序颠倒。
一般来说,公平锁的处理能够保证每个线程请求锁时有相同的机会,但是在高负载情况下,可能会导致竞争非常激烈、性能降低;而非公平锁的处理,请求锁的线程不必等待,可能会导致某些线程长时间无法获得锁,造成不公平性等问题。因此,在实际应用中,应根据不同的场景选择合适的实现方式。
63.HashMap当两个对象的hashcode相同会发生什么?
当两个对象的hashcode相同时,它们将被存储在同一个bucket中,同时在bucket中进行线性搜索以查找具有相同hashcode的键值对。如果两个对象的equals方法也返回true,则它们将被视为相等并且可以互相代替。否则,它们将按顺序存储在链表中。在一些情况下,这可能会导致HashMap的性能下降,因为需要执行更多的搜索操作和比较操作。因此,尽可能避免两个对象具有相同的hashcode所带来的影响,可以通过实现好的hashCode和equals方法来降低相同hashcode带来的影响。
64.HashMap中bucket是啥意思
HashMap中的bucket是指桶或者槽,是用来存储键值对的数据结构。每个bucket中可以存储多个键值对,其中每个键值对都有一个唯一的键,通过键可以快速地查找到对应的值。HashMap会根据键的哈希值来计算它在哪个bucket中存储,因此能够提高查找效率。
65.new String(“a”) + new String(“b”) 会创建几个对象?
会创建两个对象,一个是字符串 “a” 的对象,另一个是字符串 “b” 的对象。无论是使用字符串字面量还是使用 new 关键字来创建字符串对象,都会在堆内存中创建一个新的对象。
66.Java反射机制介绍及其代码示例
Java反射机制是指在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法,并且能够对它们进行操作。通过反射,可以在运行期间动态创建对象,调用方法和访问变量,避免了硬编码和增加了程序的灵活性和可扩展性。
以下是Java反射机制的代码示例:
复制public class ExampleClass {
private String name;
private int age;
public ExampleClass() {
this.name = "Alice";
this.age = 20;
}
public void printInfo() {
System.out.println("name: " + name + ", age: " + age);
}
}
public class Main {
public static void main(String[] args) throws Exception {
// 创建类对象
Class<?> exampleClass = Class.forName("ExampleClass");
// 获取构造方法并创建对象
Constructor<?> constructor = exampleClass.getDeclaredConstructor();
ExampleClass example = (ExampleClass) constructor.newInstance();
// 获取私有属性并修改值
Field nameField = exampleClass.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(example, "Bob");
// 获取公共方法并调用
Method printInfoMethod = exampleClass.getDeclaredMethod("printInfo");
printInfoMethod.invoke(example);
}
}
此示例代码创建了一个ExampleClass
类的对象,并使用了反射机制来获取其私有属性和公共方法,并进行修改和调用。
67.String str="i"与 String str=new String(“i”)一样吗?
不完全相同。String str = “i” 是采用常量池的方式来创建字符串对象,而 String str = new String(“i”) 是通过创建一个新的 String 对象来创建字符串对象。前者是直接从字符串池中获取字符串对象,而后者是在堆上创建一个新的对象。
68.如何决定使用 HashMap 还是 TreeMap?以及原因
HashMap 和 TreeMap 都是 Java 中实现 Map 接口的容器类。两者的主要区别在于数据存储和遍历的方式不同。
**HashMap 使用哈希表存储键值对,可以快速地进行插入、删除、查找操作。**HashMap 的性能优于 TreeMap,并且在大多数情况下,HashMap 的使用更加广泛。但是由于哈希表需要使用哈希函数将键映射到一个存储桶中,如果哈希函数不好,就可能导致哈希冲突,影响性能。
**TreeMap 使用红黑树实现,因此它的元素是有序的,并且可以进行区间查找。**由于使用了红黑树,TreeMap 天生就具有自平衡的特点,因此所有操作的时间复杂度均为 O(log n)。但是由于需要维护元素的有序性,所以 TreeMap 的性能不如 HashMap。
因此,如果需要对键值对进行快速插入、删除、查找操作并且不需要有序性,可以考虑使用 HashMap;如果需要对键值对进行有序访问,并且对性能有更高的要求,可以选择 TreeMap。
69.Array 和 ArrayList 有何区别?
Array 是一个固定大小的数据结构,它包含一组连续的内存空间,用于存储一组相同类型的数据。它的访问速度很快,但它的大小不能动态增加或减少。
ArrayList 是 Java 中的一个类,它实现了 List 接口,并且可以动态增加或减少大小。它是基于数组实现的,但可以根据需要自动调整大小。它还提供了一些方便的方法来操作列表数据。
70.Java 线程池都有哪些状态?
Java 线程池有如下状态:
- RUNNING:线程池处于运行状态,可以接收新的任务。
- SHUTDOWN:线程池关闭状态,不接受新任务,但能处理完队列中已有的任务。
- STOP:线程池停止状态,不接受新任务,也不处理队列中已有的任务,正在执行的任务会被中断。
- TIDYING:所有任务都已终止,工作线程数量为0,线程池就处于该状态。在转换到TERMINATED状态之前,执行terminated()钩子方法。
- TERMINATED:线程池被终止,terminated()方法执行完成后,线程池就处于该状态。
71.Java中线程池中 submit() 和 execute() 方法有什么区别?
Java中线程池中submit()和execute()方法的主要区别在于它们的返回值不同。submit()方法会返回一个Future对象,该对象可以用于检查任务是否已完成,以及获取任务执行的结果;而execute()方法则没有返回值。此外**,submit()方法还可以接受Callable对象作为参数,而execute()方法只接受Runnable对象。**另外,submit()方法可以抛出异常,而execute()方法只能在任务执行时抛出异常。
72.在 Java 程序中怎么保证多线程的运行安全?
在 Java 程序中,可以通过以下方式来保证多线程的运行安全:
- 使用同步方法或者同步块:在多线程访问共享资源时,使用同步方法或同步块可以确保在同一时间内只有一个线程可以访问共享资源。
- 使用 volatile 关键字:在多线程访问共享的变量时,使用 volatile 关键字可以确保变量的可见性和原子性。
- 使用线程安全的数据结构:Java API 中提供了很多线程安全的数据结构,如 ConcurrentHashMap、CopyOnWriteArrayList 等,使用这些数据结构可以确保线程安全。
- 使用 Lock 接口:Java 中的 Lock 接口提供了更灵活的加锁和解锁方式,使用 Lock 接口可以确保线程安全。
- 避免死锁:在多线程编程时,使用多个锁可能会导致死锁,所以要避免出现死锁的情况。
73.ThreadLocal 是什么?有哪些使用场景?
**ThreadLocal 是 Java 中的一个线程局部变量,它可以在多线程并发的情况下,为每一个线程提供一个独立的变量副本,不同的线程之间互不干扰。**ThreadLocal 主要用于保证线程安全,在多线程的程序中,如果多个线程共享同一个变量时,如果不加控制的访问该变量,可能会产生数据不一致或者数据冲突等问题。使用 ThreadLocal 可以有效避免这类问题的出现。
ThreadLocal 的使用场景主要包括以下几个方面:
- 时间格式化:每个线程需要根据自己的时区来格式化时间,使用 ThreadLocal 可以为每个线程提供一个独立的 DateFormat 实例。
- 数据库连接管理:为每个线程提供一个独立的数据库连接,避免多个线程同时访问同一个数据库连接。
- 用户身份信息管理:对于 Web 应用程序,在每个请求中保存用户的身份信息,可以使用 ThreadLocal 将用户信息与当前线程绑定。
- 数据缓存:使用 ThreadLocal 可以为每个线程提供一个独立的缓存,避免多个线程同时访问同一个缓存。
74.synchronized 和 volatile 的区别是什么?
synchronized 和 volatile 都可以用来保证多线程环境下的数据同步,但它们的作用机制不同。
synchronized 是通过互斥锁来实现的,可以保证同一时刻只有一个线程可以进入被 synchronized 修饰的代码块或方法,从而保证了线程安全。
**volatile 则是通过禁止指令重排来实现的,被 volatile 修饰的变量对所有线程可见,**所有线程都会从主内存中读取该变量的最新值,而不是从自己的缓存中读取。因此,volatile 保证了数据的可见性,但并不能保证原子性。
总之,synchronized 是一种排他锁,保证了访问的原子性和可见性,但是效率较低,而 volatile 则是一种轻量级的锁,只保证变量的可见性,不保证原子性。
75.synchronized 和 Lock 有什么区别?
synchronized 和 Lock 都可以用于实现多线程的同步,但是有以下区别:
- 实现方式:synchronized 是 Java 中的一种内置锁,可以用于修饰方法或代码块;Lock 是 Java 提供的一个接口,通过实现它的子类 ReentrantLock 来实现锁定。
- 粒度:synchronized 的粒度较大,一次只能锁住一个对象或方法;Lock 可以实现更细粒度的锁定,比如可以通过实现公平锁或非公平锁来控制线程的获取锁的先后顺序。
- 阻塞性质:synchronized 的阻塞性质较好,在获取锁失败时会自动阻塞等待,而 Lock 则需要手动实现阻塞方式。
- 扩展性:Lock 的可扩展性较好,可以通过实现各种子类来实现更灵活的功能,比如公平锁、可重入锁等。
76.Java中常见的设计模式,以及介绍和代码示例?
在Java开发中,常见的设计模式包括23种,它们被分为三类:创建型模式、结构型模式和行为型模式。
下面简单介绍一下这三类模式:
1.创建型模式:这类模式用于描述如何创建对象。常见的创建型模式包括工厂方法模式、抽象工厂模式、单例模式、建造者模式和原型模式。
-
工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。
Java 工厂方法模式的代码示例。
首先,让我们来定义一个产品接口 Product:
public interface Product { void use(); }
然后,我们可以创建两个具体的产品类 ConcreteProduct1 和 ConcreteProduct2,它们实现了 Product 接口:
public class ConcreteProduct1 implements Product { @Override public void use() { System.out.println("使用 ConcreteProduct1"); } } public class ConcreteProduct2 implements Product { @Override public void use() { System.out.println("使用 ConcreteProduct2"); } }
接下来,定义一个工厂接口 Factory,用于创建产品对象:
public interface Factory { Product createProduct(); }
接着,我们可以创建两个具体的工厂类 ConcreteFactory1 和 ConcreteFactory2,它们实现了 Factory 接口:
public class ConcreteFactory1 implements Factory { @Override public Product createProduct() { return new ConcreteProduct1(); } } public class ConcreteFactory2 implements Factory { @Override public Product createProduct() { return new ConcreteProduct2(); } }
最后,我们可以在客户端中使用该工厂方法创建所需产品。例如:
public static void main(String[] args) { Factory factory1 = new ConcreteFactory1(); Product product1 = factory1.createProduct(); product1.use(); Factory factory2 = new ConcreteFactory2(); Product product2 = factory2.createProduct(); product2.use(); }
以上就是 Java 工厂方法模式的代码示例。希望这可以帮助您更好地理解该设计模式。
-
抽象工厂模式:提供一个创建一系列相关或依赖对象的接口,而无需指定它们的具体类。
-
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
-
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
Java建造者模式的代码示例:
public class User { private String username; private String password; private int age; private String address; private User(UserBuilder builder) { this.username = builder.username; this.password = builder.password; this.age = builder.age; this.address = builder.address; } public String getUsername() { return username; } public String getPassword() { return password; } public int getAge() { return age; } public String getAddress() { return address; } public static class UserBuilder { private String username; private String password; private int age; private String address; public UserBuilder(String username, String password) { this.username = username; this.password = password; } public UserBuilder age(int age) { this.age = age; return this; } public UserBuilder address(String address) { this.address = address; return this; } public User build() { return new User(this); } } }
使用建造者模式创建 User 对象的示例代码如下:
User user = new User.UserBuilder("username", "password") .age(20) .address("123, Main Street") .build();
这样我们就可以使用链式调用来创建 User 对象,比直接在构造函数中传入参数更加灵活和易用。
-
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
2.结构型模式:这类模式用于描述如何将类或对象按某种布局组成更大的结构。常见的结构型模式包括适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式和代理模式。
-
适配器模式:将一个接口转换成客户希望的另一个接口。适配器模式可以使得原本不兼容的类可以一起工作。
Java中适配器模式代码示例:
首先,定义接口Target:
public interface Target { void request(); }
接着,定义一个实现了Target接口的类:
public class ConcreteTarget implements Target { @Override public void request() { System.out.println("ConcreteTarget的request方法被调用!"); } }
然后,定义一个Adaptee类(需要被适配的类):
public class Adaptee { public void specificRequest() { System.out.println("Adaptee的specificRequest方法被调用!"); } }
接着,定义一个类Adapter,实现Target接口,并持有一个Adaptee的引用作为属性:
public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void request() { System.out.println("Adapter的request方法被调用!"); adaptee.specificRequest(); } }
最后,可以测试下适配器模式的使用:
public class Client { public static void main(String[] args) { Target target = new ConcreteTarget(); target.request(); Adaptee adaptee = new Adaptee(); Target adapter = new Adapter(adaptee); adapter.request(); } }
输出结果:
ConcreteTarget的request方法被调用! Adapter的request方法被调用! Adaptee的specificRequest方法被调用!
-
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
Java中实现桥接模式的代码示例如下:
interface DrawAPI { public void drawCircle(int radius, int x, int y); } class RedCircle implements DrawAPI { public void drawCircle(int radius, int x, int y) { System.out.println("画红色圆形,半径为 " + radius + ", x 坐标为 " + x + ", y 坐标为 " + y); } } class GreenCircle implements DrawAPI { public void drawCircle(int radius, int x, int y) { System.out.println("画绿色圆形,半径为 " + radius + ", x 坐标为 " + x + ", y 坐标为 " + y); } } abstract class Shape { protected DrawAPI drawAPI; protected Shape(DrawAPI drawAPI){ this.drawAPI = drawAPI; } public abstract void draw(); } class Circle extends Shape { private int x, y, radius; public Circle(int x, int y, int radius, DrawAPI drawAPI) { super(drawAPI); this.x = x; this.y = y; this.radius = radius; } public void draw() { drawAPI.drawCircle(radius,x,y); } } public class BridgePatternDemo { public static void main(String[] args) { Shape redCircle = new Circle(100,100, 10, new RedCircle()); Shape greenCircle = new Circle(100,100, 10, new GreenCircle()); redCircle.draw(); greenCircle.draw(); } }
-
装饰模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。
-
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。
-
外观模式:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,使得这个子系统更加容易使用。
-
享元模式:运用共享技术有效地支持大量细粒度的对象。
-
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
3.行为型模式:这类模式用于描述类或对象之间怎样相互协作共同完成单个对象无法完成的任务。常见的行为型模式包括模版方法模式、策略模式、命令模式、职责链模式、状态模式、观察者模式、中介者模式、迭代子模式和解释器模式。
- 模版方法模式:定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模版方法可以使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 策略模式:定义一系列算法,将它们一个个封装起来,并且使它们可以相互替换。策略模式使得算法可独立于它们的使用者而变化。
- 命令模式:将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
- 职责链模式:使得多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
- 状态模式:允许对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。
- 观察者模式:定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
- 中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合性松散,而且可以独立地改变它们之间的交互。
- 迭代子模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示。
- 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
77.为什么要使用 spring?
Spring 是一个开源的轻量级 JavaEE 开发框架,其优点包括:提供了依赖注入(DI)和面向切面编程(AOP)的支持,并且可以与各种开源框架无缝集成,使得开发变得更加简单高效。同时,Spring 也是一个非常成熟的框架,提供了完善的文档和社区支持。因此,使用 Spring 可以帮助开发者提高开发效率和项目质量。
78.spring的事务?
Spring事务可以这样理解:当多个操作需要同时进行时,它们应该能够作为一个整体被执行,即要么全部执行成功,要么全部执行失败。Spring事务就是对此类多个操作进行控制的机制,它通过一组原子性的操作(包括开始事务、提交事务、回滚事务)来保证事务能够以一种可靠和高效的方式执行。这可以帮助开发人员处理数据库操作等容易出现异常的情况,从而实现数据的完整性和一致性。
79.spring 事务的实现方式有哪些?
- 声明式事务:声明式事务也有两种实现方式,基于 xml 配置文件的方式和注解方式(在类上添加 @Transaction 注解)。
- 编码方式:提供编码的形式管理和维护事务。
80.spring 的事务隔离及其介绍
Spring支持的事务隔离级别包括READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE四种。该机制主要是为了提供多用户并发访问数据库时避免数据不一致或者并发问题的解决方案,同时保证事务的完整性。
READ_UNCOMMITTED是最低级别,它会允许其他事务看到未提交的数据,也是最少使用的隔离级别。
READ_COMMITTED会保证其他事务只能看到已提交的数据。
REPEATABLE_READ则会保证其他事务只能看到已提交的数据,且在当前事务中多次读取同一行数据的结果始终一致。
SERIALIZABLE则能保证所有事务按串行方式执行,从而避免并发问题的出现。
81.Java中 什么是 ORM 框架?
ORM(Object-Relational Mapping)框架是一种将对象模型表示为关系数据库模型的技术。ORM框架使得开发者能够使用面向对象的方式来操作数据库,而不需要编写大量的 SQL 语句。在 Java 中,经典的 ORM 框架有 Hibernate、MyBatis 等。
82.MyBatis 有几种分页方式
MyBatis有两种分页方式:基于物理分页和基于逻辑分页。基于物理分页是在数据库层面进行分页查询,使用的是数据库特定的分页语法,如MySQL的LIMIT语法;而基于逻辑分页是在应用层面进行分页查询,先查询出全部数据,然后在内存中进行分页处理。
83.MyBatis 是否支持延迟加载?延迟加载的原理是什么?
是的,MyBatis支持延迟加载。延迟加载的原理是通过动态代理实现的。当我们查询一个对象时,MyBatis会在内存中生成一个代理对象,该代理对象仅仅持有一个对象引用和一个方法调用的信息。当我们第一次使用该对象的某个属性时,MyBatis会**通过代理对象发送SQL语句查询相应的结果并解析结果并将结果缓存到内存中。**这样的延迟加载方式可以有效地减少不必要的查询,提高系统性能。
这种方式是在查询时不会直接查询关联对象的数据,而是在第一次访问它们时才会进行查询。通过配置 MyBatis 的 resultMap 节点里的 lazyLoadingEnabled 属性为 true,就可以启用延迟加载。下面是一个示例代码:
<resultMap id="UserResult" type="User" >
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="orders" ofType="Order" resultMap="OrderResult" lazyLoadingEnabled="true"/>
</resultMap>
84.MyBatis 的一级缓存和二级缓存?
MyBatis的一级缓存是指在同一个SqlSession生命周期中,对同一个查询条件进行的查询会将查询结果缓存到该SqlSession的缓存中,下次查询时直接从缓存中获取结果,提高查询效率。**而二级缓存是指将查询结果缓存到SqlSessionFactory的缓存中,多个SqlSession共享一个缓存,可以跨SqlSession使用缓存,但需要手动开启和设置缓存的失效时间。**在并发高和数据经常变化的情况下,需要慎重使用二级缓存。
85.RabbitMQ 的使用场景有哪些?
RabbitMQ 是一个流行的开源消息队列系统,可以用于实现可靠的消息传输和异步通信。它可以被广泛地应用于以下场景:
- 异步任务处理。将任务请求发送给 RabbitMQ 后,任务的处理可以在后台完成,不影响前端用户的响应速度。
- 系统集成。RabbitMQ 可以用于不同系统之间的通信,包括不同编程语言、不同平台之间的通信。
- 应用程序解耦。通过将应用程序的不同模块之间使用消息队列进行通信,可以有效地减少模块之间的耦合性。
- 日志收集。将系统中产生的日志通过消息队列发送到指定的接收方进行集中处理和分析。
- 负载均衡。可以使用 RabbitMQ 实现负载均衡和任务分发,将任务或请求发送到多个接收方进行处理和响应。
总的来说,RabbitMQ 可以被广泛地应用于异步任务处理、系统集成、应用程序解耦、日志收集和负载均衡等场景。
86.RabbitMQ 有哪些重要的角色?
RabbitMQ 是一个消息中间件,它有四个重要的角色:生产者、消费者、交换机和消息队列。
生产者将消息发送到交换机,
交换机根据指定的路由规则将消息路由到一个或多个消息队列,
消费者从消息队列中接收消息并进行处理。
绑定将交换机和消息队列关联起来,定义了消息从哪个交换机路由到哪个队列。
这些角色共同工作,构成了 RabbitMQ 的核心机制。
87.RabbitMQ 有哪些重要的组件?
RabbitMQ有如下几个重要的组件:
- Broker:消息代理,用于接收、存储和转发消息。
- Exchange:消息交换机,用于接收消息并将其路由到一个或多个队列中。
- Queue:消息队列,用于存储消息直到消费者准备好处理它们。
- Connection:客户端与代理之间的TCP连接。
- Channel:虚拟连接,用于将工作负载划分为单独的任务单元,以便可以更好地控制它们的流。
- Routing key:用于将消息路由到特定的队列或交换机。
- Binding:用于将队列或交换机与某个交换机绑定,以定义不同的路由规则。
88.一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 MySQL 数据库,又插入了一条数据,此时 id 是几?
- 表类型如果是 MyISAM ,那 id 就是 8。
- 表类型如果是 InnoDB,那 id 就是 6。
InnoDB 表只会把自增主键的最大 id 记录在内存中,所以重启之后会导致最大 id 丢失。
89.MySQL 的内连接、左连接、右连接有什么区别?
MySQL的内连接、左连接、右连接是三种数据库表连接方式。
内连接是两个表中只有符合条件的记录才会被返回,若某一个表没有符合的记录,则不会被返回。
左连接是以左边的表作为主表,返回左边表中所有的记录,即使右边表没有匹配的记录。
右连接是以右边的表作为主表,返回右边表中所有的记录,即使左边表没有匹配的记录。
简单来说,内连接返回两个表中共同符合条件的记录,左连接返回左表中所有记录和右表中匹配的记录,右连接返回右表中所有记录和左表中匹配的记录。
需要注意的是,对于左连接和右连接而言,返回的结果集合中,若某一行在左表或右表中没有与之匹配的行,则返回的该行中右表或左表的所有值均为NULL。
90.说一下 MySQL 的行锁和表锁?
MySQL 中的行锁和表锁是 InnoDB 存储引擎默认实现的两种不同的锁机制。行锁只锁定表中特定行,其他行不受影响,而表锁锁定整个表,其他用户不能访问被锁定的表。行锁可以提高并发性,但是在高并发的情况下会影响性能,而表锁则不会影响性能,但会导致整个表被锁定,无法操作其他行。因此,选择行锁还是表锁取决于具体的应用场景和业务需求。
91.说一下乐观锁和悲观锁?
乐观锁和悲观锁是用于解决多线程并发修改同一数据时出现的数据不一致问题的两种方案。乐观锁假定在执行修改操作之前不会有其他线程对数据进行修改,因此不加锁地进行操作,并在更新数据时比较版本信息,如果版本信息不一致,说明别的线程已经修改了数据,则放弃本次操作,重新尝试。悲观锁则假定在执行修改操作之前会有其他线程对数据进行修改,因此会加锁,使得其他线程不能修改该数据,保证操作的原子性。一般情况下,乐观锁适用于读多写少的场景,而悲观锁适用于写多读少的场景。
92.MySQL 问题排查都有哪些手段?
MySQL 问题排查有以下几个手段:
- 查看 MySQL 日志,包括错误日志、慢查询日志和一般查询日志,以了解 MySQL 的运行状态和错误信息。
- 执行 EXPLAIN 命令分析查询语句的执行计划,以确定查询语句是否使用了正确的索引。
- 使用 SHOW PROCESSLIST 命令查看 MySQL 当前正在执行的所有进程,以了解哪些进程占用了大量的资源。
- 使用 SHOW INNODB STATUS 命令查看 InnoDB 存储引擎的状态信息,以了解当前数据库的负载情况和可能出现的问题。
- 使用 MySQL 内置的性能分析工具,如性能统计器和查询缓存分析器,以了解 MySQL 的性能瓶颈和优化方案。
- 使用第三方性能监控工具,如Percona Toolkit、mysqlreport和pt-query-digest等来帮助诊断和优化MySQL性能问题。
93.如何做 MySQL 的性能优化?
- 使用适当的数据类型:使用适当的数据类型可以减少所需的存储空间、提高查询速度和减少索引大小等。例如,用 TINYINT 代替 INT 可以减少对存储空间的需求。
- 对索引进行优化:对于经常使用的索引,应该对它们进行优化以提高查询性能。您可以使用 EXPLAIN 命令来帮助确定应该如何优化查询以及是否正确使用了索引。
- 避免重复查询:尽量避免重复查询,可以在应用程序中使用缓存来减少查询的数量。缓存可以使用工具如 Memcached 或 Redis 来实现。
- 使用分区表:如果您的表非常大,那么使用分区表可能会提高查询性能。分区表将数据拆分成多个区域,使得每个区域的查询更快。
- 避免过多使用子查询:过多使用子查询可能会导致查询效率变慢。尽量使用 JOIN 来代替子查询。
94.Redis 为什么是单线程的?
Redis采用单线程模型的主要原因是为了避免锁竞争和上下文切换的开销。由于Redis多数场景下是CPU密集型,单线程模型可以利用CPU的高速缓存加速数据的访问,同时避免了多线程带来的性能损失。另外,Redis也通过异步I/O和多个数据库实现了并发操作的能力。
95.说一下 JVM 的主要组成部分?及其作用?
JVM 的主要组成部分包括类加载器、运行时数据区、执行引擎等。
类加载器用于将编译后的 .class 文件加载到内存中,并进行验证、准备、解析等操作。
运行时数据区包括方法区、堆、虚拟机栈、本地方法栈等。方法区用于存放类的元数据信息,堆用于存放对象实例和数组,虚拟机栈和本地方法栈用于存储线程执行方法时的局部变量表和操作数栈。
执行引擎负责解释和执行字节码指令,在运行时将代码转换为机器码,执行相应的操作。
JVM 的作用是提供一个平台无关的执行环境,使得 Java 程序可以在不同的操作系统和硬件平台上运行,同时也提供了垃圾回收、内存管理、线程管理等机制,方便程序员进行开发和调试。
96.说一下 JVM 运行时数据区?
JVM运行时数据区是指JVM内部的数据存储区域,包括了以下几个部分:
1.方法区:存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等。
2.堆区:存储实例对象和数组,由于Java是垃圾回收式语言,因此堆区也是被垃圾回收器所管理的区域。
3.虚拟机栈:每个线程运行时都有自己的虚拟机栈,用于存储局部变量、方法参数等。
4.本地方法栈:与虚拟机栈类似,但为本地方法服务。
5.PC寄存器:存储线程执行的字节码指令地址。
这些数据区在JVM内部具有不同的作用和生命周期,对于Java程序的执行和内存管理有着重要的影响。
97.说一下堆栈的区别?
堆和栈是计算机内存中的两个不同的区域,其主要区别如下:
- 存储方式:栈中存储的数据是以先进后出的方式进行存储,而堆中存储的数据则没有特定的存储顺序。
- 空间分配:栈的空间分配是由系统自动完成的,而堆的空间分配需要由程序员手动分配和管理。
- 存储数据类型:栈中只能存储基本数据类型和对象的引用,而堆中可以存储任意数据类型,包括对象。
- 存储大小:栈的大小通常是固定的,而堆的大小可以动态地调整。
总之,栈更适合存储较小、生命周期较短的数据,而堆更适合存储较大、生命周期较长的数据。同时,使用堆需要注意防止内存泄漏等问题。
98.Java中什么是双亲委派模型?
双亲委派模型是Java虚拟机中一种类加载机制,它的核心思想是:在类加载的过程中,除了顶层的引导类加载器以外,其余的类加载器都应该有自己的父类加载器,即委托父类加载器负责自己加载的类。在类加载的过程中,如果一个类加载器要加载一个类,它首先会去委托它的父类加载器加载,如果父类加载器无法加载,则该类加载器才会尝试自己加载。这样可以保证类加载的顺序和层次性,并且防止类的重复加载。
99.java中怎么判断对象是否可以被回收?
在Java中,当一个对象不再被引用时,它可能会被垃圾回收器回收。判断对象是否可以被回收有以下两种方式:
- 引用计数法:这种方法是通过记录可以访问每个对象的引用数来判断对象是否可以被回收。但Java中没有使用此方法进行垃圾回收,因为它无法解决循环引用的问题。
- 可达性分析算法:这种方法是通过判断从GC Roots(一组根对象,如虚拟机栈、方法区中类静态属性引用的对象)开始,是否能够到达该对象来判断对象是否可以被回收。如果无法到达该对象,则证明该对象不再被引用,可以被回收。
总之,Java中的垃圾回收器会自动进行垃圾回收,而不需要手动进行操作。
100.说一下 JVM 有哪些垃圾回收算法?
JVM(Java虚拟机)有几种常见的垃圾回收算法,包括标记-清除算法、复制算法、标记-整理算法、分代收集算法等。其中,
标记-清除算法会将所有活动对象标记,然后清除没有标记的对象。
复制算法则将堆内存分为两部分,通过复制存活对象的方式进行垃圾回收。
标记-整理算法则会先标记出所有活动对象,然后将存活对象向一端移动,以便在另一端创建大块连续的自由空间,最后清除掉边界以外的全部对象。
分代收集算法则将堆内存分为新生代和老年代,并且针对不同代使用不同的垃圾回收算法。
101.简述分代垃圾回收器是怎么工作的?
分代垃圾回收器是针对不同年龄段对象采用不同回收策略的垃圾回收器。通常将 Java 堆内存划分为新生代和老年代两个区域,新生代又被划分为 Eden 区和两个 Survivor 区。新创建的对象会被分配到 Eden 区,当 Eden 区满时,发生一次 Minor GC,将 Eden 区和 Survivor 区中的存活对象复制到另外一个 Survivor 区中,同时清空 Eden 区和前一个 Survivor 区。当 Survivor 区也满了时,将其中存活的对象移动到老年代。老年代中的对象因为存活时间较长,所以一般采取标记-清除或标记-整理算法进行垃圾回收。这种分代垃圾回收器的优点是可以分别针对不同年龄段的对象采用不同的回收策略,从而提高垃圾回收效率。
102.说一下 JVM 调优的工具?
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
103.常用的 JVM 调优的参数都有哪些?
一些常用的 JVM 调优参数包括:
-Xms:指定 JVM 堆内存的初始大小;
-Xmx:指定 JVM 堆内存的最大大小;
-XX:PermSize:指定永久代的初始大小;
-XX:MaxPermSize:指定永久代的最大大小;
-XX:+UseParallelGC:启用并行垃圾收集器;
-XX:+UseConcMarkSweepGC:启用并发标记清除垃圾收集器;
-XX:+UseG1GC:启用 G1 垃圾收集器;
-XX:NewRatio:新生代和老年代的比例;
-XX:SurvivorRatio:新生代中 Eden 区域和 Survivor 区域的比例等。
以上仅是其中的一些常见参数,实际使用时还需要根据具体情况进行选择和调整。
104.Java字符串池(String pool)介绍
在Java中,为了提高字符串的效率和节省内存,Java引入了字符串池的概念。字符串池是一种特殊的存储区域,其中包含所有的字符串字面量。在Java中,每当我们创建了一个字符串字面量时,虚拟机首先会检查该字面量是否存在于字符串池中,如果存在,则返回池中的对象引用,如果不存在,则在池中创建该对象。这种优化技术可以避免同一内容字符串的重复创建,从而减少了内存的占用,提高了程序的执行效率。
105.Java中String不可变的好处
Java中String不可变的好处包括:
- 线程安全:String对象一旦被创建,它的值就不能被修改。这使得String对象是线程安全的,可以在多线程环境下共享。
- 安全性:由于String对象是不可变的,一旦被创建,它的值就不能被修改。这是一种安全性保证,可以确保任何对象都无法修改String对象的值,防止潜在的安全漏洞。
- 提高性能:由于String对象是不可变的,它们的值可以被缓存和重复使用,这样可以提高性能,避免频繁创建新的对象。
- 代码优化:由于String对象是不可变的,编译器可以进行代码优化,例如在拼接字符串时使用StringBuilder等。
总的来说,Java中String不可变的好处是多方面的,包括线程安全、安全性、性能和代码优化。
106.为什么静态方法不能调用非静态方法和变量?
静态方法属于类,不依赖于实例对象,而非静态方法和变量是依赖于实例对象的,因此静态方法不能直接访问非静态方法和变量。如果要使用非静态方法或变量,可以在静态方法内部创建一个实例对象,然后通过实例对象访问非静态方法或变量。
107.Java的类加载流程
Java的类加载流程可以分为三个步骤:
- 加载:在此步骤中,虚拟机通过类加载器将.class文件加载到内存中,生成对应的Class对象。
- 链接:在此步骤中,虚拟机对类进行验证、准备和解析等操作,为静态变量分配内存并设置默认值,将符号引用转化为直接引用等。
- 初始化:在此步骤中,虚拟机为类变量赋值,执行静态代码块等操作,使类准备好被使用。
Java虚拟机在加载类时采用的是双亲委派模型,即先让父类加载器尝试加载类,如果加载失败再由自己的类加载器进行加载。这种机制的优势是避免了类的重复加载,保证了类的安全性。
108.Java中字节流和字符流的区别
Java中字节流和字符流的主要区别在于它们处理数据类型的方式不同。字节流是基于字节的输入输出操作,适用于处理所有种类的二进制数据,比如图片、音频、视频等。而字符流是基于字符的输入输出操作,适用于处理字符数据,比如文本文件、字符串等。
此外,字符流使用了缓冲区,能够提高输入输出效率。而字节流没有缓冲区,缺点是无法在处理过程中进行缓存,需要频繁地从磁盘读写数据。
总的来说,字节流和字符流各有优缺点,需要根据具体的需求来选择合适的输入输出方式。
109强引用、软引用、弱引用、幻象引用有什么区别?
Java中的四种引用类型有不同的生命周期和回收机制,它们分别是:
1.强引用(Strong Reference):强引用是最常见的引用类型,如果一个对象具有强引用,那么它就不会被垃圾回收器回收,即使内存不足时也不会被回收。可以使用 new 关键字创建一个强引用。
Object obj = new Object(); //创建一个强引用
2.软引用(Soft Reference):软引用是一种比强引用弱一些的引用类型,在内存不足时可能会被垃圾回收器回收。可以使用 SoftReference 类来创建一个软引用。
SoftReference<Object> softRef = new SoftReference<>(obj); //创建一个软引用
3.弱引用(Weak Reference):弱引用比软引用更弱一些,它只能存活到下一次垃圾回收之前。可以使用 WeakReference 类来创建一个弱引用。
WeakReference<Object> weakRef = new WeakReference<>(obj); //创建一个弱引用
4.幻象引用(Phantom Reference):幻象引用也叫虚引用,它是最弱的一种引用,无法通过引用获取到对象实例,也无法通过幻象引用将对象恢复。可以使用 PhantomReference 类来创建一个幻象引用。
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, referenceQueue); //创建一个幻象引用
其中,referenceQueue 是一个对象队列,当幻象引用所引用的对象被垃圾回收器回收时,该对象会被加入到 referenceQueue 中。
示例代码如下:
import java.lang.ref.*;
public class ReferenceTest {
public static void main(String[] args) {
// 创建一个强引用
Object strongObj = new Object();
// 创建一个软引用
SoftReference<Object> softRef = new SoftReference<>(strongObj);
// 创建一个弱引用
WeakReference<Object> weakRef = new WeakReference<>(strongObj);
// 创建一个幻象引用
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(strongObj, referenceQueue);
// 输出各个引用的状态
System.out.println("strongObj: " + strongObj);
System.out.println("softRef: " + softRef.get());
System.out.println("weakRef: " + weakRef.get());
System.out.println("phantomRef: " + phantomRef.get());
// 将 strongObj 置为 null,使其成为垃圾
strongObj = null;
// 通知垃圾回收器回收垃圾
System.gc();
// 输出各个引用的状态
System.out.println("strongObj: " + strongObj);
System.out.println("softRef: " + softRef.get());
System.out.println("weakRef: " + weakRef.get());
System.out.println("phantomRef: " + phantomRef.get());
// 输出 referenceQueue 中的对象
Reference<?> ref;
while ((ref = referenceQueue.poll()) != null) {
System.out.println("referenceQueue: " + ref);
}
}
}
输出结果:
strongObj: java.lang.Object@4d7e1886
softRef: java.lang.Object@4d7e1886
weakRef: java.lang.Object@4d7e1886
phantomRef: null
strongObj: java.lang.Object@4d7e1886
softRef: java.lang.Object@4d7e1886
weakRef: null
phantomRef: null
referenceQueue: java.lang.ref.PhantomReference@6d06d69c
110.Java的特点有哪些
Java 是一种面向对象的编程语言,具有以下特点:
- 简单性:Java 的语法相对简单,易于学习和理解。
- 可移植性:Java 程序可以在任何支持 Java 的平台上运行,因为 Java 代码是先编译成字节码,然后再由 JVM 解释执行。
- 面向对象性:Java 是一种真正的面向对象语言,具有封装、继承和多态等特性。
- 安全性:Java 有很多安全特性,例如自动内存管理、类型检查和异常处理等,可以避免许多常见的安全漏洞。
- 高性能:Java 的执行速度相对较快,因为它的编译器将 Java 代码编译成字节码,然后由 JVM 解释执行。
- 多线程支持:Java 支持多线程编程,可以轻松地实现并发和多任务处理。
- 开放性:Java 是一种开放的编程语言,有大量的开源库和框架可供使用,可以帮助开发人员快速开发高质量的应用程序。
111.Java 面向对象特性
Java 是一种面向对象的编程语言,具有以下面向对象的特性:
- 封装性:Java 支持封装,可以使用关键字 private、public、protected 来限制类的访问权限,从而保证了数据的安全性。
- 继承性:Java 支持继承,可以通过 extends 关键字来实现类的继承,从而减少了代码的重复性。
- 多态性:Java 支持多态,可以通过重载和重写实现多态性,在调用方法时根据实际情况自动选择合适的方法。
112.Java中不同类型的数据进行运算
在 Java 中,不同类型的数据进行运算时,系统会自动进行类型转换,将数据转换为同一类型再进行运算。具体的转换规则如下:
- 布尔型和除字符型外的其他基本数据类型之间不能进行运算。
- 字符型可以和整型(byte、short、int、long)、浮点型(float、double)进行运算,系统会将字符型转换为对应的整型或浮点型,然后进行运算。
- 整型之间可以进行运算,系统会将较小的整型转换为较大的整型,然后进行运算。例如,byte 和 short 将会转换为 int 进行运算。
- 浮点型之间可以进行运算,系统会将较小的浮点型转换为较大的浮点型,然后进行运算。例如,float 会自动转换为 double 进行运算。
- 整型和浮点型之间可以进行运算,系统会将整型转换为浮点型,然后进行运算。
需要注意的是,类型转换可能会导致精度丢失或数据溢出,因此在进行运算时需要谨慎处理。
113.Java中不同类型的数据和String进行运算
在Java中,不同类型的数据和String进行运算时,会自动进行类型转换。具体规则如下:
- 如果String和数字类型进行加法运算,则数字类型会被自动转换为String类型,然后进行字符串拼接。
- 如果String和布尔类型进行运算,则布尔类型会被自动转换为String类型,然后进行字符串拼接。