首页 / 面试 / 【Java问题面试总结】
【Java问题面试总结】
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了【Java问题面试总结】,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含15819字,纯文字阅读大概需要23分钟。
内容图文
![【Java问题面试总结】](/upload/InfoBanner/zyjiaocheng/616/c955f05a9f48402785521837727cff49.jpg)
一、 Java基础总结
大部分内容部分转自 https://blog.csdn.net/ThinkWon/article/details/104390612,我只是针对大佬的博文进行自己相对常见问题的总结。大家想了解更多可以关注订阅上述链接
1.JVM、JRE和JDK的关系
JVM:java虚拟机,java跨平台
JRE:java所需核心库,主要包含java.lang包
JDK:java开发工具,内涵JRE,提供了java编译工具,打包工具
2.JAVA数据类型
基本数据类型:整、浮、字符、布
引用类型:类、接口,数组
3.final 有什么用?
只是针对变量的引用
用于修饰类、属性和方法;
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的
4.this关键字的用法
指向对象本身的一个指针
- 引用本类的构造函数
- this相当于是指向当前对象本身
- 形参与成员名字重名
5.super关键字的用法
指向的是离自己最近的一个父类
- 指向当前对象的父类的引用
- 区分子类和父类方法成员变量重名
- 引用父类构造函数
6.this和super要注意的地方
- super()和this()均需放在构造方法内第一行。
- his()和super()都指的是对象,所以,均不可以在static环境中使用
7.static主要意义
- 无对象调用方法 即使没有创建对象,也能使用属性和调用方法!
- 对象共享用static修饰的变量和方法不属于任何一个实例对象,但是可以被所有实例对象共享
8.面向对象、面向过程区别
- 面向过程:强调性能(Linux、单片机啥的)
- 面向对象:设计出低耦合的系统,使系统更加灵活、更加易于维护
9.面向对象三大特性
- 封装 :隐藏细节,提供对外访问方式,安全性和复用性高
- 继承:以已有类为基础进行非私有拓展
- 多态:引用变量所指向的具体类型 和 通过该引用变量发出的方法在编程时不确定,只有在程序运行时才确定
10.抽象类和接口的对比
抽象类:模板设计
接口:行为抽象
相似处:
- 接口和抽象类都不能实例化
- 都位于继承的顶端,用于被其他实现或继承
- 都包含抽象方法,其子类都必须覆写这些抽象方法
不同处:
- 声明方式
- 构造器
- 实现
- 继承与实现
- 字段声明
11.内部类的分类有哪些
- 成员内部类 :class作为成员变量
- 局部内部类 :在方法内部声明
- 静态内部类 :static class
- 匿名内部类:
12.匿名内部类特点
- 必须继承一个抽象类或者实现一个接口。
- 不能定义任何静态成员和静态方法why
- 所在的方法的形参需要被匿名内部类使用时,必须声明为 final why?生命周期不一致,加了final,可以确保局部内部类使用的变量与外层的局部变量区分开
public class Outer {
private void test(final int i) {
new Service() {
public void method() {
for (int j = 0; j < i; j++) {
System.out.println("匿名内部类" );
}
}
}.method();
}
}
//匿名内部类必须继承或实现一个已有的接口
interface Service{
void method();
}
局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
public class Outer {
private int age = 12;
class Inner {
private int age = 13;
public void print() {
int age = 14;
System.out.println("局部变量:" + age);
System.out.println("内部类变量:" + this.age);
System.out.println("外部类变量:" + Outer.this.age);
}
}
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.print();
}
}
13.重载(Overload)和重写(Override)的区别
- 重载: 同一个类中,方法名相同,参数列表(类型,个数,顺序可不同)
- 重写:父子类中。方法名、参数列表必须相同。其他任何东西都小于父类(返回值,异常,访问修饰符)
14.== 和 equals 的区别是什么
- == 和equal一般来说都是比较对象的内存地址
- == 针对基本数据类型的比较是比较值
- ==针对引用数据类型的比较是比较内存地址
- 针对String例子中重写了equal方法
15.String中重写的equal方法比较步骤
- 先比较内存地址
- 再比较类型是否为String
- 比较String内容
16.hashCode 与 equals (重要)
- equal方法被重写时,hashCode方法也必须被重写
- 如果两个对象相等equal为true,则hashcode一定也是相同的
- 两个对象有相同的hashcode值,它们也不一定是相等的
17.值传递和引用传递
- Java程序设计语言总是采用按值调用,方法得到的是所有参数值的一个拷贝
- 按引用传递时,虽然传递的是引用的拷贝,但是该拷贝的引用仍指向原来的对象
18.BIO,NIO,AIO 有什么区别?
- BIO (Blocking I/O):Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO (New I/O):Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- AIO (Asynchronous I/O):Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
19.反射
在程序运行的时候动态获取类对象的信息以及动态调用对象的方法
举例:
- ①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;
- ②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:
- 1 将程序内所有 XML 或 Properties 配置文件加载入内存中;
- 2 Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息;
- 3 使用反射机制,根据这个字符串获得某个类的Class实例;
- 4动态配置实例的属性
20.Java获取反射的三种方法
- 1.通过new对象实现反射机制 getClass
- 2.通过路径实现反射机制 Class.forName
- 3.通过类名实现反射机制 类.class
public class Student {
private int id;
String name;
protected boolean sex;
public float score;
}
public class Get {
//获取反射机制三种方式
public static void main(String[] args) throws ClassNotFoundException {
//方式一(通过建立对象)
Student stu = new Student();
Class classobj1 = stu.getClass();
System.out.println(classobj1.getName());
//方式二(所在通过路径-相对路径)
Class classobj2 = Class.forName("fanshe.Student");
System.out.println(classobj2.getName());
//方式三(通过类名)
Class classobj3 = Student.class;
System.out.println(classobj3.getName());
}
}
21.String和StringBuffer、StringBuilder的区别是什么?
- 可变性
String不可变:原因使用final修饰的字符数组保存字符串(private final char value[])
StringBuffer和StringBulider继承AbstractStringBuilder类,都普通使用char[] value保存字符串 - 线程安全性
String不可变->参量->线程安全
StringBuffer有同步锁->线程安全
StringBuilder无锁->线程不安全 - 性能
String每次改变的时候都会生成一个新的String对象,并将原来的指针指向新的String对象
StringBuffer/StringBulider 只是对StringBuffer/StringBuilder对象本身进行操作,并不会生成新的对象。相同情况下,StringBulider比StringBuffer性能高。
对于三者使用的总结
- 如果要操作少量的数据用 = String
- 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
- 多线程操作字符串缓冲区下操作大量数据 = StringBuffer
22.Java常见异常有哪些
java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。
java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。
java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
(运行时)java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。
(运行时)java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
23.常见的 RuntimeException 有哪些? ClassCastException(类转换异常)
- IndexOutOfBoundsException(数组越界)
- NullPointerException(空指针)
- ArrayStoreException(数据存储异常,操作数组时类型不一致)
- 还有IO操作的BufferOverflowException异常
24.Java异常处理最佳实践
- 在finally中清理资源:close
- 优先明确的异常
- 对异常进行文档说明
- 优先捕获最具体的异常
- 包装异常时不要抛弃原始的异常
二、Java集合总结
1.List,Set,Map三者的区别?
Java 容器分为 Collection 和 Map 两大类
- List:有序,可重复,可null。ArrayList、LinkedList 和 Vector
- Set:一个无序,不可重复,只允许存入一个null元素。HashSet、LinkedHashSet 以及 TreeSet)
- Map:键值对集合,可重复,不为null。HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
2.Java集合的快速失败机制 “fail-fast”?
- 集合的一种错误检测机制
- 多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
原理如下:
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
3.遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?
- for循环,用i作计数器,通过get()方法获取集合元素的值。
- foreach遍历:内部也是采用了 Iterator 的方式实现,不需要显式声明 Iterator 或计数器
- Iterator迭代器遍历
- 最佳方式是:支持Random Access用for,不支持则用lterator或foreach
4.迭代器Iterator
Iterator 接口提供遍历任何 Collection 的接口
使用如下:
List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
特点如下:
- 可以边遍历边修改
- 只能单向遍历,安全性高
5.ArrayList、Vector、LinkedList的区别
ArrayList是动态数组结构的实现,LinkedList是双向链表的数据结构,Vector是线程安全容器
- 随机访问效率:ArrayList > LinkedList
- 增加和删除效率:ArrayList > LinkedList
- 内存空间占用: ArrayList>LinkedList
- 线程安全:Vector > ArrayList\LinkedList
- 扩容方面:Vector每次扩容增加1倍,ArrayList每次扩容则增加50%
6.HashSet实现原理
- 底层使用HashMap实现,将其value作为HashMap的key存储,因此HashSet的value是不可重复的。而HashMap的value则存入PRESENT的虚值
- 添加元素操作需要进过两层验证:hashcode和equal
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
return map.put(e, PRESENT)==null;
}
7.HashMap原理
基本实现:
基于Hash算法:在往HashMap中put()存储数据的时候,通过key值计算hashcode,从而获得当前对象的数组下标;
- 若key的hash值相同,若key相同则覆盖原先的值,若key不同则将该元素放入key-value链表中
- 若key的hash值不同,则直接存入table数组之中
在JDK1.7时候:
- HashMap的数据结构为:拉链法:数组+链表
- 数组存放规则:无冲突时,存放数组;冲突时,存放链表
JDK1.8之后
HashMap的数据结构为:拉链法:数组+链表+红黑树
无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树
HashMap的put方法的具体流程
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
putVal源代码:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//实现Map.put和相关方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 步骤①:tab为空则创建
// table未初始化或者长度为0,进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 步骤②:计算index,并对null做处理
// (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 桶中已经存在元素
else {
Node<K,V> e; K k;
// 步骤③:节点key存在,直接覆盖value
// 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 将第一个元素赋值给e,用e来记录
e = p;
// 步骤④:判断该链为红黑树
// hash值不相等,即key不相等;为红黑树结点
// 如果当前元素类型为TreeNode,表示为红黑树,putTreeVal返回待存放的node, e可能为null
else if (p instanceof TreeNode)
// 放入树中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 步骤⑤:该链为链表
// 为链表结点
else {
// 在链表最末插入结点
for (int binCount = 0; ; ++binCount) {
// 到达链表的尾部
//判断该链表尾部指针是不是空的
if ((e = p.next) == null) {
// 在尾部插入新结点
p.next = newNode(hash, key, value, null);
//判断链表的长度是否达到转化红黑树的临界值,临界值为8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//链表结构转树形结构
treeifyBin(tab, hash);
// 跳出循环
break;
}
// 判断链表中结点的key值与插入的元素的key值是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 相等,跳出循环
break;
// 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
p = e;
}
}
//判断当前的key已经存在的情况下,再来一个相同的hash值、key值时,返回新来的value这个值
if (e != null) {
// 记录e的value
V oldValue = e.value;
// onlyIfAbsent为false或者旧值为null
if (!onlyIfAbsent || oldValue == null)
//用新值替换旧值
e.value = value;
// 访问后回调
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
}
// 结构性修改
++modCount;
// 步骤⑥:超过最大容量就扩容
// 实际大小大于阈值则扩容
if (++size > threshold)
resize();
// 插入后回调
afterNodeInsertion(evict);
return null;
}
HashMap的扩容操作是怎么实现的?(难点!!)
HashMap是怎么解决哈希冲突的
哈希冲突:当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象
如何解决:
- 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均
- 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快
- 在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动);
8. HashMap 与HashTable 、CurrentHashMap区别、TreeMap
HashMap与HashTable(基本被淘汰)
-
线程安全:HashMap 是非线程安全的,HashTable 是线程安全的,由于HashTable内部方法基本都进过synchronized修饰
-
效率:HashMap 要比 HashTable 效率高一点
-
对Null key 和Null value的支持:HashMap中允许其key唯一为null,允许多个value为null的键;HashTable不允许其键值为null
HashMap与CurrentHashMap
-
对Null key 和Null value的支持:HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
-
底层数据结构:ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
-
线程安全:HashMap 是非线程安全的,CurrentHashMap是线程安全的,由于CurrentHashMap在JDK1.8之前使用分段锁,而JDK1.8之后使用Node + CAS + Synchronized来保证并发安全进行实现
HashMap 和TreeMap
-
插入、删除选HashMap,由于TreeMap的底层原理是一颗红黑树的实现
-
搜索的话使用TreeMap
9.CurrentHashMap原理(难点!!)
10.TreeMap 和 TreeSet 在排序时如何比较元素?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进行排序。
11.Collections 工具类中的 sort()方法如何比较元素?
第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;
三、Java JVM总结
四、Java 多线程总结
内容总结
以上是互联网集市为您收集整理的【Java问题面试总结】全部内容,希望文章能够帮你解决【Java问题面试总结】所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。