Java基础面试题
-
- 问:接口和抽象类的区别?
- 问:为什么接口里声明的都是常量
- 问:重载和重写的区别
- 问:sleep() 的 wait()区别
- 问:Synchronized 和 lock 的区别
- 问:属性赋值的先后顺序
- 问:异常的体系结构
- 问:线程的生命周期
- 问:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
- 问:StringBuffer与StringBuilder的内存解析
- 问:Vector、ArrayList和LinkedList的区别
- 问:HashMap的底层实现原理?(jdk7)
- 问:HashMap、LinkedHashMap、TreeMap、Hashtable的理解
- 问:CurrentHashMap 与 Hashtable的异同?
- 问:hashMap中容量为什么是2的n次方
- 问:面向对象的四个基本特征?
- 访问修饰符public,private,protected,以及不写时的区别?
- 重载和重写的设计思想
- 下面两个代码块能正常编译和执行吗?
- 基础考察,指出下题的输出结果
- &和&&的区别?
- String 是 Java 基本数据类型吗?
- String 类可以继承吗?
- String和StringBuilder、StringBuffer的区别?
- String s = new String("xyz") 创建了几个字符串对象?
- String s = "xyz" 和 String s = new String("xyz") 区别?
- 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
- 什么是反射
- 深拷贝和浅拷贝区别是什么?
- Java 静态变量和成员变量的区别
- 是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?
- 为什么不能根据返回类型来区分重载?
- Error 和 Exception 有什么区别?
- Java 中的 final 关键字有哪些用法?
- try、catch、finally 考察3,请指出下面程序的运行结果。
- JDK1.8之后有哪些新特性?
- Optional类:
- 线程的 sleep() 方法和 yield() 方法有什么区别?
- 线程的 join() 方法是干啥用的?
- 编写多线程程序有几种实现方式?
- Thread 调用 start() 方法和调用 run() 方法的区别
- 线程的状态流转
- synchronized 和 Lock 的区别
- synchronized 各种加锁场景的作用范围
- 如何检测死锁?
- 如何防止死锁:
- 为什么要使用线程池?直接new个线程不是很舒服?
- 线程池的核心属性有哪些?
- 说下线程池的运作流程。
- 线程池有哪些拒绝策略?
- List、Set、Map三者的区别?
- ArrayList 和 LinkedList 的区别。
- ArrayList 和 Vector 的区别。
- 介绍下 HashMap 的底层数据结构
- 为什么要改成“数组+链表+红黑树”?
- 那在什么时候用链表?什么时候用红黑树?
- HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?
- HashMap 的插入流程是怎么样的?
- HashMap 的扩容(resize)流程是怎么样的?
- 除了 HashMap,还用过哪些 Map,在使用时怎么选择?
- Java 内存结构(运行时数据区)
- 什么是双亲委派模型?
- Java虚拟机中有哪些类加载器?
- 类加载的过程
- 介绍下垃圾收集机制(在什么时候,对什么,做了什么)?
- GC Root有哪些?
- 垃圾收集有哪些算法,各自的特点?
- 一道LinkedHashMap<>题目
- 一道泛型题目
- final,static,this,super关键字总结
- 问:获取 Class 对象的四种方式
- 问:怎么用反射调用属性,方法
- 问:代理模式了解哪些?
- 问:如何做到 map 中容量为2的幂次方
问:接口和抽象类的区别?
答:
接口:
1.接口使用interface来定义
2.Java中,接口和类是并列的两个结构
3.接口中的成员
JDK7及以前:只能定义全局常量和抽象方法
>全局常量:public static final的.但是书写时,可以省略不写
>抽象方法:public abstract的
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)(可以由实现类的对象直接调用(注意遵守类优先原则和解决接口冲突问题))(为了解决接口中增加一个抽象方法,实现类必须重写的问题)和静态方法(只能由接口直接调用)
JDK9:添加了(静态)私有方法(为了简化默认方法和静态方法中的重复代码)
4. 接口中不能定义构造器的!意味着接口不可以实例化
5. Java开发中,接口通过让类去实现(implements)的方式来使用.
如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化
如果实现类没覆盖接口中所的抽象方法,则此实现类仍为一个抽象类
6. Java类可以实现多个接口 —>弥补了Java单继承性的局限性
格式:class AA extends BB implements CC,DD,EE
7. 接口与接口之间可以继承,而且可以多继承
8. 接口的具体使用,体现多态性
9. 接口,实际上可以看做是一种规范
10.接口:是自上而下设计,一般先设计好接口,然后在具体的业务中去实现的
抽象类说明:
1.此类不能实例化
2.抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
3.抽象方法是方法的声明,没方法体
4.包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
5.若子类重写了父类中的所有的抽象方法后,此子类方可实例化。
若子类没重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
6.抽象类:是自下而上的设计。类之间存在一定的关系,is-a,终极目标是实现代码的复用
角度:定义,继承和实现,设计理念,属性方法构造器
补充说明:
1.类优先原则(接口和抽象类之间)
答:
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。
2.接口冲突(接口与接口之间)
答:
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
那么在实现类没重写此方法的情况下,报错。
3.为什么abstract不能修饰静态方法
答:
因为静态方法是编译时的行为,而方法的覆盖操作发生在运行时,所以无法覆盖静态方法,失去了abstract的意义。
问:为什么接口里声明的都是常量
接口中不允许方法的实现,而抽象类是允许方法实现的及定义变量的,因此我们可以看出接口是比抽象类更高层次的抽象。如果接口可以定义变量,但是接口中的方法又都是抽象的,在接口中无法通过行为(例如set()方法)来修改属性。那么有些人就会提出可以通过实现接口的类的实例来修改接口的属性。那好,如果接口中有一变量c,我们通过实现它的A类对象改变了c的值,那么实现接口的B类,C类中的c变量都要跟着改变,可想而知这样就会造成混乱,很多想要实现接口的类就不知道接口里现在c变量到底是什么值,因为接口是在变的,它不再是那种高层的抽象,而是带了可变的成分。所谓的抽象就是把一些不可变的东西放在一起,而可变的东西往往放在实现里面。
所以我们深思接口的本意所在,实际上它是对一类事物属性和行为的高层次抽象,它体现的是OCP(对修改关闭,对扩展开放)原则,这也是我们软件开发中一直所追求。
问:重载和重写的区别
答:
“两同”即方法名相同、形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
问:sleep() 的 wait()区别
答:
1.来自不同类
wait => Object
sleep => thread
2、关于锁的释放
3、使用的范围
wait() 在同步方法或同步代码块中
sleep() 可以在任意地方
4、是否需要捕获异常
wait() 不需要捕获异常sleep() 必须捕获异常
问:Synchronized 和 lock 的区别
答:
1.内置的 java 关键字,Lock 是一个 java 类
2.是否可以获取锁的状态
3.是否自动上锁,解锁
4.是否会一直阻塞
5.锁是否可以设置(公平锁)
6.少量代码,大量代码
问:属性赋值的先后顺序
答:
(由父及子,静态先行)默认初始化 --> 显示初始化/在代码块中赋值 --> 构造器中初始化 --> 调用赋值
问:异常的体系结构
答:
java.lang.Throwable
|-----java.lang.Error:一般不编写针对性的代码进行处理。(比较严重,不该出现)
|------OutOfMemoryError
|------StackOverflowError
|------NoClassDefFoundError
|-----java.lang.Exception:可以进行异常的处理
|------编译时异常(checked)
|-----IOException
|-----FileNotFoundException
|-----ClassNotFoundException
|------运行时异常(unchecked,RuntimeException)
|-----NullPointerException
|-----ArrayIndexOutOfBoundsException
|-----ClassCastException
|-----NumberFormatException
|-----InputMismatchException
|-----ArithmeticException
问:线程的生命周期
问:String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
答:
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
补充说明:
2.字符串拼接方式赋值的对比
答:
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量
- 只要其中一个是变量,结果就在堆中(final 修饰过后是常量)
- 如果拼接的结果调用intern()方法,返回值就在常量池中
问:StringBuffer与StringBuilder的内存解析
答:
String:(每 new String()一个字符串,就新建一个 char[])
String str = new String();//char[] value = new char[0];
String str1 = new String(“abc”);//char[] value = new char[]{‘a’,‘b’,‘c’};
StringBuffer:(只创建一个 char[],有默认长度)
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append(‘a’);//value[0] = ‘a’;
sb1.append(‘b’);//value[1] = ‘b’;
StringBuffer sb2 = new StringBuffer(“abc”);//char[] value = new char[“abc”.length() + 16];
//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中。
StringBuilder:原理同StringBuffer,多了Sychoronized。
小结:
String在内存中开辟空间的时候,开辟的数组空间大小和构造器中的字符串长度一致;StringBuffer在内存中开辟空间的时候,开辟的数组空间大小为构造器中的字符串长度+16
问:Vector、ArrayList和LinkedList的区别
答:
ArrayList的源码分析(底层数组创建与扩容)
jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
jdk 8中ArrayList的变化
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
后续的添加和扩容操作与jdk 7 无异。
LinkedList的源码分析(底层链表创建)
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node {
E item;
Node next;
Node prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector的源码分析
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
在扩容方面,默认扩容为原来的数组长度的2倍。
补充说明
1.单列集合框架结构
问:HashMap的底层实现原理?(jdk7)
答:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
…可能已经执行过多次put…
map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
如果equals()返回false:此时key1-value1添加成功。----情况3
如果equals()返回true:使用value1替换value2。----情况4
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
补充说明
1.HashMap在jdk8中相较于jdk7在底层实现方面的不同
new HashMap():JDK8中,底层没创建一个长度为16的数组
jdk 8底层的数组是:Node[],而非Entry[]
jdk7底层结构:数组+链表。jdk8中底层结构:数组+链表+红黑树。
形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
底层转红黑树存储:当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时
问:HashMap、LinkedHashMap、TreeMap、Hashtable的理解
答:
HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历
原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素
对于频繁的遍历操作,此类执行效率高于HashMap
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry(带有左右指针),替换HashMap中的Node.
TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。
底层使用红黑树
要求key必须是由同一个类创建的对象
因为要照key进行排序:自然排序 、定制排序
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
问:CurrentHashMap 与 Hashtable的异同?
答:
在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,主要实现原理是实现了锁分离的思路解决了多线程的安全问题,如下图所示:
Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样。
ConcurrentHashMap 与HashMap和Hashtable 最大的不同在于:put和 get 两次Hash到达指定的HashEntry,第一次hash到达Segment,第二次到达Segment里面的Entry,然后在遍历entry链表
JDK1.8中,使用去掉了Segment,加锁的对象改为对每个数组的元素
问:hashMap中容量为什么是2的n次方
答:
hashmap进行hash散列的算法(存放元素的数组的位置)是hash&(length-1),而hash的容量建议都是取2的n次方
首先我们先说说这个算法,算法的目的是为了得到小于length的更加均匀的数,如果不均匀容易产生hash碰撞,换句话说只有全是1,进行按位与才是最均匀的,因为1与上任何数都等于任何数本身
为什么是length-1不是length了
16是10000 15是01111。16与任何数只能是0或者16。15与任何数等于小于16的任何数本身。
为什么容量是2的n次方呢
2的n次方一定是最高位1其它低位是0,
这样减1的时候才能得到01111这样都是1的二进制。
问:面向对象的四个基本特征?
答:
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这此行为的细节是什么
继承:目的:对父类和方法的复用
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口.面向对象的本质就是将现实世界描绘成一系列完全自治,封闭的对象,可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。封装给对象提供了隐藏内部特性和行为的能力,对象提供一些能这被其它对象访问的方法来改变它内部的数据。
多态:多态性是指允许相同或不同子类型的对象对同一消息作出不同响应
访问修饰符public,private,protected,以及不写时的区别?
答:
重载和重写的设计思想
答:
编译时期的多态既方法重载,这是静态分派(也就是对象的静态类型): 就是在编译时期对象找方法是根据静态类型来查找的. 如果不知道这一点的应该去仔细复习下深入理解Java虚拟机中相关章节.
运行时多态既方法的重写:这是一种动态分配,既会根据对象的实体类型进行分配.
众所周知Java中的多态分为两种,第一种:方法重载的多态,第二种:方法重写的多态。 或许我在表达上可能语言不是很准确, 但是对于我目前自己的理解上来看,Java中的多态目前就是这两种性质的多态. 或许我的理解在教科书上没有体现出来, 但是我觉的那也是多态的一种体现.
下面两个代码块能正常编译和执行吗?
// 代码块1
short s1 = 1; s1 = s1 + 1;
// 代码块2
short s1 = 1; s1 += 1;
答:
代码块1编译报错,错误原因是:不兼容的类型: 从int转换到short可能会有损失”。
代码块2正常编译和执行。
其实,s1 += 1 相当于 s1 = (short)(s1 + 1)
基础考察,指出下题的输出结果
public static void main(String[] args) {
Integer a = 128, b = 128, c = 127, d = 127;
System.out.println(a == b);
System.out.println(c == d);
}
答:
false,true。
原因:执行 Integer a = 128,相当于执行:Integer a = Integer.valueOf(128),基本类型自动转换为包装类的过程称为自动装箱(autoboxing)。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在 Integer 中引入了 IntegerCache 来缓存一定范围的值,IntegerCache 默认情况下范围为:-128~127。
本题中的 127 命中了 IntegerCache,所以 c 和 d 是相同对象,而 128 则没有命中,所以 a 和 b 是不同对象。
但是这个缓存范围时可以修改的,可能有些人不知道。可以通过JVM启动参数:-XX:AutoBoxCacheMax= 来修改上限值,如下图所示:
&和&&的区别?
答:
&&:逻辑与运算符。当运算符左右两边的表达式都为 true,才返回 true。同时具有短路性,如果第一个表达式为 false,则直接返回 false。
&:逻辑与运算符、按位与运算符。
按位与运算符:用于二进制的计算,只有对应的两个二进位均为1时,结果位才为1 ,否则为0。
逻辑与运算符:& 在用于逻辑与时,和 && 的区别是不具有短路性。所在通常使用逻辑与运算符都会使用 &&,而 & 更多的适用于位运算。
String 是 Java 基本数据类型吗?
答:
不是。Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type)。
小结
1.基本数据类型:数据直接存储在栈上
2.引用数据类型区别:数据存储在堆上,栈上只存储引用地址
String 类可以继承吗?
答:
不行。String 类使用 final 修饰,无法被继承。
String和StringBuilder、StringBuffer的区别?
String
:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。
String 真正不可变有下面几点原因
保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
StringBuffer
:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。
StringBuilder
:StringBuffer 的非线程安全版本,没有使用 synchronized,具有更高的性能,推荐优先使用。
String s = new String(“xyz”) 创建了几个字符串对象?
答:
一个或两个。如果字符串常量池已经有“xyz”,则是一个;否则,两个。
当字符创常量池没有 “xyz”,此时会创建如下两个对象:
一个是字符串字面量 “xyz” 所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,此时该实例也是在堆中,字符串常量池只放引用。
另一个是通过 new String() 创建并初始化的,内容与"xyz"相同的实例,也是在堆中。
String s = “xyz” 和 String s = new String(“xyz”) 区别?
答:
两个语句都会先去字符串常量池中检查是否已经存在 “xyz”,如果有则直接使用,如果没有则会在常量池中创建 “xyz” 对象。
另外,String s = new String(“xyz”) 还会通过 new String() 在堆里创建一个内容与 “xyz” 相同的对象实例。
所以前者其实理解为被后者的所包含。
两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
答:
不对。hashCode() 和 equals() 之间的关系如下:
当有 a.equals(b) == true 时,则 a.hashCode() == b.hashCode() 必然成立,
反过来,当 a.hashCode() == b.hashCode() 时,a.equals(b) 不一定为 true。
什么是反射
答:
反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为反射机制。
深拷贝和浅拷贝区别是什么?
答:
数据分为基本数据类型和引用数据类型。基本数据类型:数据直接存储在栈中;引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里。
浅拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。
深拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。
深拷贝相比于浅拷贝速度较慢并且花销较大。
Java 静态变量和成员变量的区别
静态变量 | 成员变量 | |
---|---|---|
存放位置 | 方法区 | 堆 |
生命周期 | 类 | 对象 |
所属 | 类 | 对象 |
调用对象 | 类 | 类和对象 |
是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?
区分两种情况,调用前是否显示创建了对象实例。
1)没有显示创建对象实例:不可以发起调用,非静态方法只能被对象所调用,静态方法可以通过对象调用,也可以通过类名调用,所以静态方法被调用时,可能还没有创建任何实例对象。因此通过静态方法内部发出对非静态方法的调用,此时可能无法知道非静态方法属于哪个对象。
public class Demo {
public static void staticMethod() {
// 直接调用非静态方法:编译报错
instanceMethod();
}
public void instanceMethod() {
System.out.println("非静态方法");
}
}
2)显示创建对象实例:可以发起调用,在静态方法中显示的创建对象实例,则可以正常的调用。
public class <