因为这两年互联网整体环境不好,所在公司也在大批量裁员,故花很多时间在看面试题。看了好久,决定自己整理百家面试题,如果能帮助正在找工作的程序员们,那就更好了。会一直更新,直到找到满意工作为止...
借鉴以下原文:
Java面试题全集(上)_骆昊的博客-CSDN博客
Java后端面试八股文汇总_hutc_Alan的博客-CSDN博客
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段(如果不能理解请阅读阎宏博士的《Java与模式》或《设计模式精解》中关于桥梁模式的部分)。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说都是透明的(就像电动剃须刀是A系统,它的供电系统是B系统,B系统可以使用电池供电或者用交流电,甚至还有可能是太阳能,A系统只会通过B类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
修饰符 | 当前类 | 同 包 | 子 类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
类的成员不写访问修饰时默认为default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java中,外部类的修饰符只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。
先后顺序:静态成员变量、成员变量、构造方法
详细的先后顺序:父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码块、子类构造函数
相同点:
1.都不能被实例化
2.接口的实现类或抽象类的子类需实现接口或抽象类中相应的方法才能被实例化
不同点:
1.接口只能有方法定义,不能有方法的实现,而抽象类可以有方法的定义与实现
2.实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但只能继承一个抽象类
3.当子类和父类之间存在逻辑上的层次结构,推荐使用抽象类,有利于功能的累积。当功能不需要,希望支持差别较大的两个或更多对象间的特定交互行为,推荐使用接口。使用接口能降低软件系统的耦合度,便于日后维护或添加删除方法。
为了程序的结构能够更加清晰从而便于维护。假设Java语言支持多重继承,类C继承自类A和类B,如果类A和B都有自定义的成员方法f(),那么当代码中调用类C的f()会产生二义性。Java语言通过实现多个接口间接支持多重继承,接口由于只包含方法定义,不能有方法的实现,类C继承接口A与接口B时即使他们都有方法f(),也不能直接调用方法,需实现具体的f()方法才能调用,不会产生二义性。
多重继承会使类型转换、构造方法的调用顺序变得复杂,会影响到性能。
Java提供了两种用于多态的机制,分别是重载与重写(覆盖)
重载:重载是指同一个类中有多个同名的方法,但这些方法有着不同的参数,因此在编译时就可以确定到底调用哪个方法,它是一种编译时多态。重载可以被看作一个类中的方法多态性。
重写(覆盖):子类可以覆盖父类的方法,因此同样的方法会在父类与子类中有着不同的表现形式。在 Java 语言中,基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类的实例对象。同样,接口的引用变量也可以指向其实现类的实例对象,而程序调用的方法在运行期才动态绑定(绑定指的是将一个方法调用和一个方法主体连接到一起),就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。通过这种动态绑定的方法实现了多态。由于只有在运行时才能确定调用哪个方法,因此通过方法覆盖实现的多态也可以被称为运行时多态。
10.重载与重写(覆盖)的区别?
重写是父类与子类之间的关系,是垂直关系;重载是同一类中方法之间的关系,是水平关系
重写只能由一个方法或一对方法产生关系;重载是多个方法之间的关系
重写要求参数列表相同;重载要求参数列表不同
覆盖中,调用方法体是根据对象的类型来决定的,而重载是根据调用时实参表与形参表来对应选择方法体
重载方法可以改变返回值的类型;重写方法不能改变返回值的类型
11.final、finally和finalize的区别是什么?
final用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖、类不可继承。
finally作为异常处理的一部分,只能在try/catch语句中使用,finally附带一个语句块用来表示这个语句最终一定被执行,经常被用在需要释放资源的情况下。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的finalize()方法。当垃圾回收器准备好释放对象占用空间时,首先会调用finalize()方法,并在下一次垃圾回收动作发生时真正回收对象占用的内存。
12.出现在Java程序中的finally代码块是否一定会执行?
当遇到下面情况不会执行。
当程序在进入try语句块之前就出现异常时会直接结束。
当程序在try块中强制退出时,如使用System.exit(0),也不会执行finally块中的代码。
其它情况下,在try/catch/finally语句执行的时候,try块先执行,当有异常发生,catch和finally进行处理后程序就结束了,当没有异常发生,在执行完finally中的代码后,后面代码会继续执行。值得注意的是,当try/catch语句块中有return时,finally语句块中的代码会在return之前执行。如果try/catch/finally块中都有return语句,finally块中的return语句会覆盖try/catch模块中的return语句。
13.Java语言中关键字static的作用是什么?
static的主要作用有两个:
1.为某种特定数据类型或对象分配与创建对象个数无关的单一的存储空间。
2.使得某个方法或属性与类关联在一起而不是对象,在不创建对象的情况下可通过类直接调用方法或使用类的属性。
具体而言static又可分为4种使用方式:
1.修饰成员变量。用static关键字修饰的静态变量在内存中只有一个副本。只要静态变量所在的类被加载,这个静态变量就会被分配空间,可以使用’‘类.静态变量’‘和’‘对象.静态变量’'的方法使用。
2.修饰成员方法。static修饰的方法无需创建对象就可以被调用。static方法中不能使用this和super关键字,不能调用非static方法,只能访问所属类的静态成员变量和静态成员方法。
3.修饰代码块。JVM在加载类的时候会执行static代码块。static代码块常用于初始化静态变量。static代码块只会被执行一次。
4.修饰内部类。static内部类可以不依赖外部类实例对象而被实例化。静态内部类不能与外部类有相同的名字,不能访问普通成员变量,只能访问外部类中的静态成员和静态成员方法。
14.判等运算符==与equals的区别?
== 比较的是引用,equals比较的是内容。
1.如果变量是基础数据类型,== 用于比较其对应值是否相等。如果变量指向的是对象,== 用于比较两个对象是否指向同一块存储空间。
2.equals是Object类提供的方法之一,每个Java类都继承自Object类,所以每个对象都具有equals这个方法。Object类中定义的equals方法内部是直接调用 == 比较对象的。但通过覆盖的方法可以让它不是比较引用而是比较数据内容。需保证equals方法相同对应的对象hashCode也相同。
3.(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同
15.为什么要把String设计为不变量?
16.序列化是什么?
序列化是一种将对象转换成字节序列的过程,用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统里,并在需要的时候把该流读取出来重新构造成一个相同的对象。
17.Java反射机制是什么?
Java反射机制是指在程序的运行过程中可以构造任意一个类的对象、获取任意一个类的成员变量和成员方法、获取任意一个对象所属的类信息、调用任意一个对象的属性和方法。反射机制使得Java具有动态获取程序信息和动态调用对象方法的能力。可以通过以下类调用反射API。
18.简述注解
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。其可以用于提供信息给编译器,在编译阶段时给软件提供信息进行相关的处理,在运行时处理相应代码,做对应操作。
一般常用的注解可以分为三类:
19.简述元注解
元注解可以理解为注解的注解,即在注解中使用,实现想要的功能。其具体分为:
20.事务的ACID是指什么?
21.转发(forward)和重定向(redirect)的区别?
答:forward是容器中控制权的转向,是服务器请求资源,服务器直接访问目标地址的URL,把那个URL 的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。redirect就是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,因此从浏览器的地址栏中可以看到跳转后的链接地址,很明显redirect无法访问到服务器保护起来资源,但是可以从一个网站redirect到其他网站。forward更加高效,所以在满足需要时尽量使用forward(通过调用RequestDispatcher对象的forward()方法,该对象可以通过ServletRequest对象的getRequestDispatcher()方法获得),并且这样也有助于隐藏实际的链接;在有些情况下,比如需要访问一个其它服务器上的资源,则必须使用重定向(通过HttpServletResponse对象调用其sendRedirect()方法实现)。
22.什么是ORM?
答:对象关系映射(Object-Relational Mapping,简称ORM)是一种为了解决程序的面向对象模型与数据库的关系模型互不匹配问题的技术;简单的说,ORM是通过使用描述对象和数据库之间映射的元数据(在Java中可以用XML或者是注解),将程序中的对象自动持久化到关系数据库中或者将关系数据库表中的行转换成Java对象,其本质上就是将数据从一种形式转换到另外一种形式。
23.解释一下什么叫AOP(面向切面编程)?
答:AOP(Aspect-Oriented Programming)指一种程序设计范型,该范型以一种称为切面(aspect)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern)。
24.深拷贝和浅拷贝的区别?
浅拷贝:在拷贝一个对象时,对对象的基本数据类型的成员变量进行拷贝,但对引用类型的成员变量只进行引用的传递,并没有创建一个新的对象,当对引用类型的内容修改会影响被拷贝的对象。
深拷贝:在拷贝一个对象时,除了对基本数据类型的成员变量进行拷贝,对引用类型的成员变量进行拷贝时,创建一个新的对象来保存引用类型的成员变量。
实现深拷贝的两种方法:
1.序列化该对象,然后反序列化回来,就能得到一个新的对象了。
序列化:将对象写入到IO流中; 反序列化:从IO流中恢复对象 序列化机制允许将实现序列化的java对象转化为字节序列,这些字节序列可以保存到磁盘或者网络传输上,以达到以后恢复成原来的对象,序列化机制使得对象可以脱离程序的运行而独立存在
2. 继续利用clone()方法,对该对象的引用类型变量再实现一次clone()方法。
25.数组和链表的区别?
数组:
链表:
不能随机查找,必须从第一个开始遍历,查找效率低
26.Hashmap和treemap的区别?
1)HashMap无序,TreeMap有序。
2)HashMap覆盖了equals()方法和hashcode()方法,这使得HashMap中两个相等的映射返回相同的哈希值;
TreeMap则是实现了SortedMap接口,使其有序。
3)HashMap的工作效率更高,而TreeMap则是基于树的增删查改。更推荐使用HashMap。
4)HashMap基于数组+链表+红黑树(jdk1.8之后)实现,TreeMap是基于红黑树实现。(如果单链表元素超过8个,则将单链表转变为红黑树;如果红黑树节点小于6时,再将红黑树变为单链表)
5)两者都不是线性安全的。
27.Mysql执行计划的type性能从高到低依次是?
null>system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
28.接口的幂等性是啥?
答:是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变
实现方案:
1)数据库唯一标识
2)乐观锁
3)悲观锁
4)Token机制
5)分布式锁
29.Nacos作为注册中心的原理?
30.https协议与http的区别
1)https协议需要到CA(Certificate Authority,数字证书认证机构)申请证书,一般免费证书较少,因而需要一定费用。
2)http是明文传输,数据未加密,安全性差;https则是具有安全性的ssl加密传输协议,安全性好。
3)http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4)http页面响应速度比https快,因为http使用TCP三次握手建立连接,客户端和服务器需要交换 3 个包,而https除了TCP的三个包,还要加上ssl握手需要的 9 个包,所以一共是 12 个包。
5)https=SSL/TLS+http,所以,https比http要更耗费服务器资源。
31.TCP和UDP的区别?
32.简述MVC工作原理?
MVC模式(Model-view-controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
1、由客户端发起请求;2、服务端接收请求,并解析请求;3、根据解析出来的请求,找到对应的控制器,并执行控制器;4、控制器调用模型获取数据,并将数据传给视图;5、视图将数据渲染出来
33.autowired和resource注解的区别?
相同点:
不同点:
2.@Resource注解有两个重要的属性,分别是name和type,如果name属性有值,则使用byName的自动注入策略,将值作为需要注入bean的名字,如果type有值,则使用byType自动注入策略,将值作为需要注入bean的类型.如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。即@Resource注解默认按照名称进行匹配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找,当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
3.@Autowired注解是spring的注解,此注解只根据type进行注入,不会去匹配name.但是如果只根据type无法辨别注入对象时,就需要配合使用@Qualifier注解或者@Primary注解使用.
34.spring中常见的设计模式?
(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象。通过唯一标识来获取bean对象。
(2)单例模式:Bean默认为单例模式
(3)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
(4)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate
(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller
(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。
(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
35.如何保持redis和数据库的一致性?
缓存延时双删:
异步更新缓存(最终一致性):
可以使用阿里的canal将binlog日志采集发送到MQ队列里面然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性
1).线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。
sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu
的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而
是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程
序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出
interruptexception异常返回,这点和wait是一样的。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("22222222");
}
});
t1.start();
t1.join();
// 这行代码必须要等t1全部执行完毕,才会执行
System.out.println("1111");
}
22222222
1111
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获
得正确的结果,我们就说这个对象是线程安全的
在 Java 中,堆是 Java 虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
//会卖出多一倍的票
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
new MyThread().start();
new MyThread().start();
}
static class MyThread extends Thread{
private int ticket = 5;
public void run(){
while(true){
System.out.println("Thread ticket = " + ticket--);
if(ticket < 0){
break;
}
}
}
}
}
//正常卖出
public class Test2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread2 mt=new MyThread2();
new Thread(mt).start();
new Thread(mt).start();
}
static class MyThread2 implements Runnable{
private int ticket = 5;
public void run(){
while(true){
System.out.println("Runnable ticket = " + ticket--);
if(ticket < 0){
break;
}
}
}
}
}
Spring 框架在事务开始时会给当前线程绑定一个 Jdbc Connection, 在整个事务过程都是使用该线程绑定的connection 来执行数据库操作,实现了事务的隔离性。 Spring 框架里面就是用的 ThreadLocal 来实现这种隔离
private long count = 0;
public void calc() {
count++;
}
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
int a = 0;
bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() {
if (flag) { //3
int ret = a * a;//4
}
}
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
int a = 0;
bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() {
if (flag) { //3
int ret = a * a;//4
}
}
1)继承Thread类创建线程类,重写run()方法,
2)实现Runnable接口创建线程,实现run()方法
3)实现Callable接口,实现call()方法,通过FutureTask包装器来创建Thread线程(有返回值)
4)从线程池获取
1)编程式事务: 如果系统需要明确的事务,并且需要细粒度的控制各个事务的边界,此时建议使用编程式事务
2)声明式事务: 如果系统对于事务的控制粒度较为粗糙,则建议使用声明式事务,可以通过注解@Transational实现,原理是利用Spring框架通过AOP代理自动完成开启事务,提交事务,回滚事务。回滚的异常默认是运行时异常,可以通过rollbackFor属性制定回滚的异常类型
1)、注册中心组件(服务治理):Netflix Eureka;
2)、负载均衡组件:Netflix Ribbon,各个微服务进行分摊,提高性能;
3)、熔断器组件(断路器):Netflix Hystrix,Resilience4j ;保护系统,控制故障范围;
4)、网关服务组件:Zuul,Spring Cloud Gateway;api网关,路由,负载均衡等多种作用;
5)、配置中心:Spring Cloud Config,将配置文件组合起来,放在远程仓库,便于管理;