Java面试必懂知识点总结
1、介绍一下面向对象
-
面向对象是基于万物皆对象。
-
封装:隐藏方法的具体实现细节,提供出一个公共接口给API调用,提高了代码的可维护性,和安全性。
-
继承:继承就是在已知类上建立新类的技术,子类继承父类的方法,子类也可以有自己的属性和方法。但不能选择性地继承父类。通 过使用继承可以提高代码复用性。继承是多态的前提。
-
多态:父类后接口定义的引用变量可以指向子类或具体实现类的实例对象。Java实现多态有三个必要条件:继承、重写、向上转型。
- 编译时多态:方法的重载
- 运行时多态:方法的重写
2、Java语言的反射机制
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性的方法,对于任何一个对象,都能调用它的任意属性和方法,这种在动态的获取类信息和调用对象的方法和属性的功能,称为Java语言的反射机制。
实现反射机制的三种反射:
1.通过new对象实现反射机制
2.通过路径实现反射机制
3.通过类名实现反射机制
1 public class Student {
2 private int id;
3 String name;
4 protected boolean sex;
5 public float score;
6 }
1 public class Get {
2 //获取反射机制三种方式
3 public static void main(String[] args) throws ClassNotFoundException {
4 //方式一(通过建立对象)
5 Student stu = new Student();
6 Class classobj1 = stu.getClass();
7 System.out.println(classobj1.getName());
8 //方式二(所在通过路径‐相对路径)
9 Class classobj2 = Class.forName("fanshe.Student");
10 System.out.println(classobj2.getName());
11 //方式三(通过类名)
12 Class classobj3 = Student.class;
13 System.out.println(classobj3.getName());
14 }
15 }
3、接口和抽象类的区别
| 参数 | 抽象类 | 接口 |
|---|---|---|
| 声明 | 抽象类使用abstract关键字声明 | 接口使用interface关键字声明 |
| 实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现 | 子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现 |
| 构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
| 访问修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符是public。并且不允许定义为 private 或者 protected |
| 多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
| 字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
4、List去重的实现
1、使用两个for循环实现list 去重(有序)
public static List removeDuplicationByFor(List<Integer> list){
for (int i = 0; i < list.size(); i++) {
for (int j = i+1; j < list.size(); j++) {
if (list.get(i).equals(list.get(j))){
list.remove(j);
}
}
}
return list;
}
2、使用HashSet实现list去重(无序)
public static List removeDuplicationByHashSet(List<Integer> list){
HashSet hashSet = new HashSet(list);
//把list所有元素清空
list.clear();;
//把hashSet对象添加到list集合
list.addAll(hashSet);
return list;
}
3、使用List集合contains方法循环遍历
public static List removeDuplicationByContains(List<Integer> list){
ArrayList<Integer> newList = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
boolean isContains = newList.contains(list.get(i));
if (!isContains){
newList.add(list.get(i));
}
}
list.clear();
list.addAll(newList);
return list;
}
4、使用TreeSet实现List去重(有序)
public static List removeDuplicationByTreeSet(List<Integer> list){
TreeSet set = new TreeSet(list);
//清空list 结合的所有元素
list.clear();;
//把HashSet对象添加到list集合
list.addAll(set);
return list;
}
5、使用java8的新特性 实现list去重(有序)
public static List removeDuplicationByStream(List<Integer> list){
List<Integer> newlist1 = list.stream().distinct().collect(Collectors.toList());
return newlist1;
}
5、线程的生命周期

线程的五个状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
6、线程里面 wait 和 sleep 方法的区别
sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。
sleep 是Thread 类的静态本地方法,wait 则是Object 类的本地方法。
7、线程Run() Start() 的区别
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的, run()方法称为线程体。通过调用Thread类的start()方法来启动一个线程。 start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。 start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待 run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接使用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法
8、创建线程的方法
创建线程有四种方式:
- 继承 Thread 类;
- 实现 Runnable 接口;
- 实现 Callable 接口;
- 使用 Executors 工具类创建线程池继承 Thread 类
步骤
- 定义一个Thread类的子类,重写run方法,将相关逻辑实现,run()方法
就是线程要执行的业务逻辑方法
-
创建自定义的线程子类对象
-
调用子类实例的star()方法来启动线程
1 public class MyThread extends Thread {
2
3 @Override
4 public void run() {
5 System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
6 }
7
8 }
1 public class TheadTest {
2
3 public static void main(String[] args) {
4 MyThread myThread = new MyThread();
5 myThread.start();
6 System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
7 }
8
9 }
10
运行结果
1 main main()方法执行结束
2 Thread‐0 run()方法正在执行...
实现 Runnable 接口
步骤
-
定义Runnable接口实现类MyRunnable,并重写run()方法
-
创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
-
调用线程对象的start()方法
1 public class MyRunnable implements Runnable {
2
3 @Override
4 public void run() {
5 System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
6 }
7
8 }
1 public class RunnableTest {
2
3 public static void main(String[] args) {
4 MyRunnable myRunnable = new MyRunnable();
5 Thread thread = new Thread(myRunnable);
6 thread.start();
7 System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
8 }
9
10 }
执行结果
1 main main()方法执行完成
2 Thread‐0 run()方法执行中...
实现 Callable 接口
步骤
-
创建实现Callable接口的类myCallable
-
以myCallable为参数创建FutureTask对象
-
将FutureTask作为参数创建Thread对象
-
调用线程对象的start()方法
1 public class MyCallable implements Callable<Integer> {
2
3 @Override
4 public Integer call() {
5 System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
6 return 1;
7 }
8
9 }
1 public class CallableTest {
2
3 public static void main(String[] args) {
4 FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
5 Thread thread = new Thread(futureTask);
6 thread.start();
7
8 try {
9 Thread.sleep(1000);
10 System.out.println("返回结果 " + futureTask.get());
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 } catch (ExecutionException e) {
14 e.printStackTrace();
15 }
16 System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
17 }
18
19 }
执行结果
1 Thread‐0 call()方法执行中...
2 返回结果 1
3 main main()方法执行完成
使用 Executors 工具类创建线程池
Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool,后续详细介绍这四种线程池
1 public class MyRunnable implements Runnable {
2
3 @Override
4 public void run() {
5 System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
6 }
7
8 }
1 public class SingleThreadExecutorTest {
2
3 public static void main(String[] args) {
4 ExecutorService executorService = Executors.newSingleThreadExecutor();
5 MyRunnable runnableTest = new MyRunnable();
6 for (int i = 0; i < 5; i++) {
7 executorService.execute(runnableTest);
8 }
9
10 System.out.println("线程任务开始执行");
11 executorService.shutdown();
12 }
13
14 }
执行结果
1 线程任务开始执行
2 pool‐1‐thread‐1 is running...
3 pool‐1‐thread‐1 is running...
4 pool‐1‐thread‐1 is running...
5 pool‐1‐thread‐1 is running...
6 pool‐1‐thread‐1 is running...
9、原生JDBC的执行流程
1.注册驱动程序:Class.forName(“com.mysql.jdbc.Driver”);
2.使用驱动管理类来获取连接conn = DriverManager.getConnection.
3.创建Statement stmt = conn.getStatement;(sql会话对象)
4.执行sql : stmt.executeQuery(sql);
5.处理结果集:ResultSet,如果SQL前如果有参数值就设置参数值setXXX()
7.关闭连接。
public class JDBC {
private static String url = "jdbc:mysql://localhost:3306/day16";
private static String user = "root";
private static String password ="root";
public static void main(String[] args) throws Exception {
//1.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2.使用驱动管理类来获取连接
Connection conn = DriverManager.getConnection(url, user, password);
//3.准备sql
String sql = "select * from student ";
//4.在连接的基础上,创建Statement
Statement stmt = conn.createStatement();
//5.执行sql
ResultSet set = stmt.executeQuery(sql);
//6.关闭资源
set.close();
stmt.close();
conn.close();
}
}
10、事务的并发问题
多个事务访问同一个数据库的时候,会产生事务的并发问题。
并发问题:
脏读:一个事务读取到了其他事务还没有提交的数据,读到了其他事务 “update”的数据
不可重复读:一个事务多次读取,结果不一样
幻读:一个事务读取到了其他事务还没有提交的数据,读到了其他事务 “insert”的数据
如何解决并发问题?
通过设置隔离级别来解决并发问题:
| 脏读 | 不可重复读 | 幻读 | |
|---|---|---|---|
| read uncommitted: 读未提交 | × | × | √ |
| read committed:读已提交 | √ | × | × |
| repeatable read:可重复读 | √ | √ | × |
| serializable:串行化 | √ | √ | √ |
11、事务的三大范式
1、第一范式(列不可再分):原子性的操作,所有字段值都是不可分解的原子值。
2、第二范式(属性完全依赖于主键):前提是满足第一范式的情况下,一个表中只能保存一种数据,不可以把多种数据保存在同一张表中。
3、第三范式(属性直接依赖于主键):前提是满足第一范式和第二范式的情况下,每一列数据都和主键直接相关,而不能间接相关。
12、事务的特性
Atomicity原子性:一组事务操作,要么成功,要么失败。
Consistency一致性:两个事务做多次操作,最后的结果始终保持一致。
Isolation隔离性:事务执行操作,不受其他事务的干扰
Durability持久性:对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。
13、左链接、右链接
数据库中的左连接和右连接的区别可以概括为一句话来表示即左连接where只影响右表,右连接where只影响到左表。
左连接(Left Join)
select * from tbl1 Left Join tbl2 where tbl1.ID = tbl2.ID
左连接后的检索结果是显示tbl1的所有数据和tbl2中满足where 条件的数据。
简言之 Left Join影响到的是右边的表
右连接(Right Join)
select * from tbl1 Right Join tbl2 where tbl1.ID = tbl2.ID
检索结果是tbl2的所有数据和tbl1中满足where 条件的数据。
简言之 Right Join影响到的是左边的表。
内连接(inner join)
select * FROM tbl1 INNER JOIN tbl2 ON tbl1.ID = tbl2.ID
它的功能和 select * from tbl1,tbl2 where tbl1.id=tbl2.id相同。
14、MySQL中的最左匹配原则
最左匹配原则就是指在联合索引中,如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配。
15、mybatis 自增主键
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
insert into Author xxx
</insert>
useGeneratedKeys=“true” 使用自增主键
keyProperty=“id” 自增主键返回到映射类的id属性
16、maven install package
Maven install 安装指令,其做了两件事情:
1. 将项目打包(jar/war),将打包结果放到项目下的 target 目录下
2. 同时将上述打包结果放到本地仓库的相应目录中,供其他项目或模块引用
Maven package 打包指令,其就做了一件事:
1. 将项目打包(jar/war),将打包结果放到项目下的 target 目录下 (也要先clean)
17、说一说 Spring IOC AOP
IOC
控制反转:原来对象是有使用者来进行控制的,有了Spring之后,可以把整个对象交给spring来帮我们进行管理。
DI:依赖注入,把对象的属性的值注入到具体对象中,@Autowired,populateBean完成属性值的注入。
容器:使用Map结构来存储,在spring中一般存在三级缓存,singletonObjects存放完整的对象,整个Bean的声明周期,从创建到销毁全部都是由容器来管理的。
AOP
aop是ioc的一个扩展功能,现有的ioc,再有的aop,aop只是在ioc整个流程中新增的一个扩展点而已–》BeanPostProcess
18、AOP的实现方式,原理
一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)。
可以读一下:
[Spring系列之AOP实现的两种方式 - 平凡希 - 博客园 (cnblogs.com)](https://www.cnblogs.com/xiaoxi/p/5981514.html#:~:text=AOP常用的实现方式有两种,,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ) 。)
19、AOP的代理模式 原理
[AOP代理模式 - 眼泪,还是流了 - 博客园 (cnblogs.com)](https://www.cnblogs.com/yang82/p/8098982.html#:~:text=AOP代理模式 1 、静态代理:就是设计模式中的proxy模式 2,、动态代理:jdk1.5中提供,利用反射。 … 3 、利用cglib)
20、谈一下spring事务传播?
传播特性有几种?7种 Required,Requires_new,nested,Support,Not_Support,Never,Mandatory
总:
事务的传播特性指的是不同方法的嵌套调用过程中,事务应该如何进行处理,是用同一个事 务还是不同的事务,当出现异常的时候会回滚还是提交,两个方法之间的相关影响,在日常工作中,使 用比较多的是required,Requires_new,nested
分:
1、先说事务的不同分类,可以分为三类:支持当前事务,不支持当前事务,嵌套事务
2、如果外层方法是required,内层方法是,required,requires_new,nested
3、如果外层方法是requires_new,内层方法是,required,requires_new,nested
4、如果外层方法是nested,内层方法是,required,requires_new,nested
21、SpringMVC的工作流程
(1条消息) SpringMVC工作流程 – 详解_布诺i的博客-CSDN博客_springmvc工作流程
22、SpringBoot的核心配置文件
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 配置文件有以下几个应用场景。
- 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
- 一些固定的不能被覆盖的属性;
- 一些加密/解密的场景;