常见面试题
1. 面向对象
面向对象的编程语言有封装、继承、抽象、多态等4个特征。
- 封装:把描述一个对象的属性和行为的代码封装在一个模块中,也就是一个类中,属性用变量定义,行为用方法进行定义,方法可以直接访问同一个对象中的属性。
- 抽象: 把现实生活中的对象抽象为类。分为过程抽象(属性)和数据抽象(方法)。
- 继承:子类继承父类的特征和行为。子类可以有父类的方法,属性(非private)。子类也可以对父类进行扩展,也可以重写父类的方法。缺点就是提高代码之间的耦合性。
- 多态:指同一个接口,使用不同的实例而执行不同操作方法覆盖和重载体现了多态性。
2.JDK、JRE、JVM
- JDK:Java开发工具,JDK由JRE和Java工具等组成。
- JRE:Java运行时环境,JRE由bin(JVM)和lib等组成。
- JVM:Java虚拟机
3.==和equals
- ==比较的是栈中的值,即基本数据类型比较的是值,引用类型比较的是地址值。
- equals在Object类中也是调用==的,在没有重新equals的情况下两者是一样的。
- 一般情况下都会重写equals方法让其比较引用类型时是比较值。
String str1 = "Hello";
String str2 = new String("Hello");
String str3 = str2;
// 因为str1的是存在堆中的常量池里,而str2是在堆中,所有两个的地址值是不相等的
str1 == str2; // false
str1 == str3; // false
str2 == str3; // true
str1.equals(str2); // true
str1.equals(str3); // true
str2.equals(str3); // true
4.fianl的作用
- 修饰类后该类不能被继承。
- 修饰方法后该方法不能被重写,但可以重载。
- 修饰变量后该变量值不能被修改。
- 修饰全局变量
- 如果是类变量(静态变量),必须在声明时就赋值,或者在静态代码块中赋值。
- 如果是成员变量,必须在声明时赋值,或者在代码块中赋值,或者在构造方法中赋值。
- 修饰局部变量
- 系统不会为局部变量初始化,所有修饰局部变量声明时可以不赋值,在使用时在指定值(之后的代码不可以再对变量赋值)。
- 修饰基本数据类型和引用类型
- 修饰基本数据类型其数值一旦初始化便不能再改变。
- 修饰引用类型则不能再让其指向另一个对象(地址值不能改变),但可以改变对象的值,比如修饰数值时不能使用等号修改数值对象,但可以通过下标改变数组的值。
5.为什么局部内部类和匿名内部类只能访问局部final变量
- 外部类和内部类是同级别,在编译时内部类也会生成一个class文件,所以内部类不会随着方法的结束而消亡,但方法结束后局部变量会被销毁,这样内部类访问的局部变量就不存在了。
- 实际上内部类在编译时会将局部变量复制一份到class文件中,为了防止复制后的值不变,就只能使用fianl修饰该变量,所以只能访问局部final变量。
6.String、StringBuffer、StringBuilder区别及使用场景
- String是final修饰的,不可变,每次操作都会产生新的String对象。
- StringBuffer和StringBuilder都是在原对象上操作的,不会浪费内存。
- StringBuffer是线程安全的,方法都是synchronized修饰的,StringBuilder是线程不安全的。
- 优先使用StringBuilder,多线程使用共享变量时使用StringBuffer。
7.重载和重写的区别
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同。与方法返回值和访问修饰符无关。
重写:发生在父子类中,方法名和参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类,如果父类方法访问修饰符为private,则子类不能重写该方法。
8.接口和抽象类的区别
- 抽象类可以存在普通成员函数,而接口只能存在public abstract 方法。
- 抽象类只能继承一个,接口可以实现多个。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
- 接口的设计目的是对类的行为进行约束,之约束行为的有无,不对如何实现进行限制,表达的是like的关系。
- 抽象类的设计目的是代码复用,先存在子类,然后将子类共性的部分抽取出来作为抽象类,表达的是is的关系
9.List和Set的区别
- List:有序,按对象进入的顺序保存对象,可重复,允许多个null元素对象,可以使用Iterator变量元素,也可以使用get方法获取指定元素
- Set:无需,不可重复,最多允许一个null元素对象,只能通过Iterator获取元素。
10.hashCode和equals
hashCode()的作用是获取哈希码,返回一个int整数,哈希码的作用是确定该对象在哈希表中的位置,hashCode()定义在JDK的Object.java中,Java中任何类都包含有hashCode()函数。
- 两个对象相等,则hashcode也一定相同。
- 两个对象相等,对两个对象分别调用equals方法都返回true。
- 两个对象有相同的hashcode,它们不一定是相等的。
11.HashSet如何检查重复
- 对象加入时,HashSet会先计算对象的hashcode值来判断对象加入的位置,查看该位置是否有值,如果没有,HashSet会假设对象没有重复出现,如果有值,这时会调用equals()方法来检查两个对象是否真的相同,如果相同,HashSet就不会让其加入操作成功,如果不同,就好重新计算到集体位置,这样大大减少equal的次数,提高执行速度。
12.ArrayList和LinkedList区别
ArrayList
- 基于动态数组,连续内存存储,适合下标访问(随机访问)。
- 扩容机制,因为数组长度固定,超出数组长度存数据时需要新建数组,将老数组的数据拷贝到新数组,新数组长度为老数组的1.5倍。
- 插入数据时,如果不是尾部插入,会涉及元素的复制移动,性能低下,如果使用尾插法并指定初始容量可以极大提升性能,甚至超过LindedList。
LinkedList
- 基于链表,可以存储在分散的内存中,适合做数据插入及删除,不适合查询。
- 遍历时尽量使用iterator而不要使用for循环,因为每次for循环体内每次通过get方法取得元素都要重新遍历,性能消耗极大。
- 尽量不要使用indexOf等返回元素索引,并利用其进行遍历,使用indexOf对list进行了遍历,即使结果为空也会遍历整个列表。
13.HashMap和HashTable的区别?底层实现是什么?
区别:
- HashMap方法没有synchronized修饰,线程不安全,HashTable线程安全。
- HashMap允许key和value为null,HashTable不允许。
底层实现:数组+链表+红黑树实现
- 插入时,计算key的hash值,二次hash然后对数组长度取模,对应得到数组下标。
- 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组。
- 如果产生hash冲突,先进行equal比较,相同则取代元素,不同则判断链表高度插入链表,链表高度到达8,并且数组长度达到64则转变为红黑树,长度低于6则将红黑树转回链表。
- key为null,存在下标0的位置。
- 有数组扩容机制。
14.CocurrentHashMap原理,jdk7和jdk8版本的区别
jdk7:
- 数据结构:ReentrantLock + Segment + HashEntry,一个Segment中包含一个HashEntry数组,没有HashEntry又是一个链表结构。
- 查找:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表头部,再使用equals对比。
- 锁:Segment分段锁,Segment继承了ReentranLock,锁定操作的Segment,其他Segment不受影响,并发为Segment个数,可以通过构造函数指定,数组扩容不会影响其他Segment。
- 读:get方法无需加锁,有volatile保证
jdk8:
- 数据结构:synchronized + CAS + Node + 红黑树,Node的val和next都用volatile修饰,保证可见性。
- 查找,替换,赋值操作都使用CAS。
- 锁:锁链表的head节点,不影响其他元素读写,扩容时,阻塞所有的读写操作,并发扩容。
- 读:Node的val和next使用volatile修饰,读写线程对该变量相互可见。数组用volatile修饰是为了保证扩容时被线程感知。
15.如何实现一个IOC容器
实现:
- 配置文件中指定需要扫描的包路径。
- 递归包扫描获取class文件,并添加到一个Set集合中。
- 遍历这个Set集合,获取类上有指定注解的类并交给IOC容器,定义一个安全的Map存储这些对象。
- 遍历这个IOC容器,获取到每个类的实列,判断里面需要依赖对象的类的实列,进行递归注入。
IOC的概念:
- IoC(Inversion of Control),意为控制反转,不是什么技术,而是一种设计思想。Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
- 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
- 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
16.什么是字节码?采用字节码的好处是什么?
- 编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换成特定系统的机器码执行。这种供虚拟机理解的代码叫做字节码(即扩展名为.class的文件)。
- Java语言通过字节码的方式,在一定程度上解决传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点,实现跨平台。
17.Java类加载器
JDK自带有三个类加载器:BootStrapClassLoader、ExtClassLoader、AppClassLoader
- BootStrapClassLoader:是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%/lib下的jar包和class文件。
- ExtClassLoader:是AppClassLoader的父类加载器,默认负责加载%JAVA_HOME%/lib/ext下的jar包和class类。
- AppClassLoader:是自定义加载器的父类,负责加载classpath下的类文件。系统类加载器、线程上下文加载器。
- 自定义类加载器想要实现双亲委派除了继承ClassLoader类还要指定parent为AppClassLoader。
18.双亲委派机制
双亲委派:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
好处:
- 主要是为了安全性,防止用户篡改Java的一些核心类。
- 同时可以避免类的重复加载。

19.Java中的异常体系
- Java中的所有异常都来自顶级父类Throwable。
- Throwable下有两个子类Exception和Error。
- Error是程序无法处理的错误,一旦出现,则程序将被迫停止运行。
- Exception不会导致程序停止,分为两部分RuntimeException运行时异常和CheckedException检查异常。
- RunTimeException常常发生再程序运行过程,会导致程序当前线程执行失败,CheckedException常常发生再程序编译过程中,会导致程序编译不通过。
20.GC如何判断对象可以被回收
- 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数器减1,计数为0时可以回收。
- 可达性分析法:从GC Roots开始向下搜索,搜索所走过哦的路径称为引用链,当一个对象到GC Root没有任何引用链相连时,则证明此对象不可用,虚拟机就判断是可回收对象。
GC Root的对象有
- 虚拟机栈中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI引用的对象。
可达性分析算法中是需要进行至少两次标记过程的,对象被发现与GC Root没有引用链相连时,会先进行标记,然后被标记的对象进入由虚拟机自动建立的Finalizer队列中判断是否覆盖或者执行过了finalize方法,如果没有覆盖或者已经执行过finalize方法,则直接回收,否则对象被放进F-Queue队列,由一低优先级线程去执行队列中对象的finalize方法(“执行”是指虚拟机会触发这个方法,但不承诺会等待它允许结束)。执行finalize方法完毕后,GC会对F-Queue队列中的对象进行第二次小规模标记,判断是否可达,不可达则回收,可达则移出队列。


浙公网安备 33010602011771号