JAVA基础
讲讲final关键字的作用
数据:
声明数据为常量,可以是运行时常量,也可以是在运行时被初始化后不能被改变的常量。
- 对于基本类型,final使数值不变;
- 对于引用类型,final使引用不变,也就不能引用其他对象,但是被引用的对象本身是可以修改的。
方法
声明方法不能被子类重写。
private方法隐式的被指定为final,如果在子类种定义的方法和基类中的一个private方法签名相同,此时子类的方法不是重写基类的方法,而是在子类中定义了一个新的方法。
类
声明不允许被继承。
讲讲static关键字!
静态变量
又称为类变量,也就是说这个变量是属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量的内存中只存在一份。
静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象的方法。方法中不能有this和super关键字。
静态语句块在类初始化时运行一次。
非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。静态内部类不能访问外部类的非静态的变量和方法
public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
public static void main(String[] args) {
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}
静态变量和静态代码块的初始化顺序取决于他们在代码中的顺序
讲讲深克隆和浅克隆的区别
浅拷贝: 拷贝对象和原始对象的引用同一个对象
深拷贝: 拷贝对象和原始对象的引用类型引用不同的对象
讲讲受检异常和非受检异常。你知道那些搜检异常和非受检异常?
- 受检异常 : 需要try … catch… 语句捕获并进行处理,并且可以从异常中恢复(io,sql)
- 非受检异常: 是程序运行时错误,例如除0,调用不存在的方法… 会引发Arithmetic Exception, 此时程序奔溃并且无法恢复。
集合
哪些集合类是线程安全的?
- Vector: 就比ArrayList多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率 (页面相应速度)是优先考虑的。
- Stack: 堆栈类,先进后出
- Hashtable: 就比HashMap多了个线程安全
- Enumeration: 枚举,相当于迭代器
知道集合类的快速失败机制吗?
现象:
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了增加,删除,修改,则会抛出ConcurrentModificationException。
原理:
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合在被遍历期间如果发生内容的变化,就会改变modCount的值。每当迭代器使用hashNext/next遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常。
场景:
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)
注意: 这里的抛出异常条件是检测到modCount != exceptedmodCount这个条件。如果集合发生变化时修改modCount的值刚好等于exceptedmodCount的值,则异常不会抛出。因此,不能依赖这个异常是否抛出而进行并发操作的编程。这个异常只建议用于检测并发修改的bug。。
解决方法:
在遍历过程中,所有涉及到改变modCount值的地方全部加上synchronized。
使用CopyOnWriteArrayList来替换ArrayList
安全失败
现象:
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有的集合内容,在拷贝的集合上进行遍历
原理:
由于迭代器是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所做的修改并不能被迭代器检测到,所以不会出发ConcurrentModificationException
缺点:
基于拷贝内容的优点是避免了ConcurrentModificationException,但同样的,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。这也是他的缺点,同时,由于是需要拷贝的,所以比较吃内存。
场景:
java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
比如CopyOnWriteArrayList
摘自:快速失败&&安全失败机制
如果你想确保一个集合不能被修改你会怎么做?
可以使用Collections.unmodifiableCollection(Colletion c)方法来创建一个只读集合,这个改变集合的任何操作都会抛出Java. lang. UnsupportedOperationException 异常。
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
说一下HashSet的实现原理?
HashSet 是基于 HashMap实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此HashSet的实现比较简单,相关HashSet的操作,基本上都是调用底层的HashMap的相关方法来完成,HashSet不允许重复的值。
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;
}
BlockingQueue是什么?(实现类有哪些是用于线程池的队列)
Java.util.Concurrent.BlockingQueue是一个检索队列,在进行一个元素的检索或移除一个元素的时候,他会等待队列变为空;当在添加一个元素时,它会等待对立的可用空间。BlockingQueue接口是Java集合的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都是在BlockingQueue的实现类中被处理了。
Java 提供了集中BlockQueue的实现,比如ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue,SynchronizedQueue等。
为什么HashMap中的String,Integer这样的包装类适合作为K?
String,Integer等包装类的特性能够保证Hash值的不可更改和计算准确性,能够有效的减少碰撞的几率。
- 都是final类型,即不可变,保证key的不可更改性,不会存在获取hash值不同的情况。
- 内部重写了equals(),hashCode() 等方法,遵守了HashMap内部的规范(putVal()方法),不容易出现Hash计算错误的情况
String重写的equals方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String 重写的hashCode 方法
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
HashMap和ConcurrentHashMap的区别
- ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
- HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
ConcurrentHashMap底层具体实现知道吗?实现原理是什么?
JDK1.7
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
- 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;
- Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。
JDK1.8
在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
结构如下: