07-撩课-Java面试宝典-第七篇

61.什么是并发修改异常?
什么是并发修改异常:
当我们在遍历实现了collection接口
与iterator接口的集合时(List、Set、Map), 
我们可以通过遍历索引
也可以通过迭代器进行遍历。
在我们使用迭代器进行遍历集合的时候,
会获取到当前集合的迭代对象。
在里面有封装了迭代器的remove方法
与集合自带的remove方法,
如果我们调用迭代器对象的remove方法
是没问题的,
但是当我们调用集合自带的remove方法时,
就会产生ConcurrentModificationException 
并发修改异常。
也就是说,
当我们通过迭代器进行遍历集合的时候,
是不允许集合本身在结构上发生变化的。
62.什么是CopyOnWriteArrayList,它与ArrayList有何不同?
CopyOnWriteArrayList:
CopyOnWriteArrayList这是一个
ArrayList的线程安全的变体,

其原理大概可以通俗的理解为:
初始化的时候只有一个容器,
很常一段时间,
这个容器数据、
数量等没有发生变化的时候,
大家(多个线程),都是读取
假设这段时间里只发生读取的操作
同一个容器中的数据,

所以这样大家读到的数据都是
唯一、一致、安全的,
但是后来有人往里面增加了一个数据,
这个时候CopyOnWriteArrayList 底层实现
添加的原理是先copy出一个容器
可以简称副本,

再往新的容器里添加这个新的数据,
最后把新的容器的引用地址
赋值给了之前那个旧的的容器地址,
但是在添加这个数据的期间,
其他线程如果要去读取数据,
仍然是读取到旧的容器里的数据。

Vector 
ArrayList 
CopyOnWriteArrayList 
这三个集合类都继承List接口
1、ArrayList是线程不安全的;
2、Vector是比较古老的线程安全的,
但性能不行;
3、CopyOnWriteArrayList在兼顾了
线程安全的同时,
又提高了并发性,
性能比Vector有不少提高

63.迭代器和枚举之间的区别?
在Java集合中,
我们通常都通过 “Iterator(迭代器)” 
或 “Enumeration(枚举类)” 去遍历集合。
Enumeration是一个接口,它的源码如下:
package java.util;
public interface Enumeration<E> {
  boolean hasMoreElements()
  E nextElement();
}
Iterator也是一个接口,它的源码如下:
package java.util;
public interface Iterator<E> {
  boolean hasNext();
  E next();
  void remove();
}

区别:
1 函数接口不同
Enumeration只有2个函数接口。
通过Enumeration,
我们只能读取集合的数据,
而不能对数据进行修改。
Iterator只有3个函数接口。
Iterator除了能读取集合的数据之外,
也能数据进行删除操作。
2.Iterator支持fail-fast机制,而Enumeration不支持。
Enumeration 是JDK 1.0添加的接口。 
使用到它的函数包括Vector、Hashtable等类,
这些类都是JDK 1.0中加入的,
Enumeration存在的目的
就是为它们提供遍历接口。
Enumeration本身并没有支持同步,
而在Vector、Hashtable实现Enumeration时,
添加了同步。
而Iterator 是JDK 1.2才添加的接口,
它也是为了HashMap、ArrayList等集合
提供遍历接口。
Iterator是支持fail-fast机制的:
当多个线程对同一个集合的内容进行操作时,
就可能会产生fail-fast事件。
Java API规范建议,
对于较新的程序,
Iterator应优先于Enumeration,
因为“ Iterator在Java集合框架中
代替Enumeration。”

64.Hashmap如何同步?
1、使用 synchronized 关键字,
这也是最原始的方法。
synchronized(anObject)  
{  
    value = map.get(key);  
}  
2、使用 JDK1.5 提供的锁
Java.util.concurrent.locks.Lock
lock.lock();  
value = map.get(key);  
lock.unlock();  

3.可以使用 JDK1.5 提供的读写锁
java.util.concurrent.locks.ReadWriteLock
rwlock.readLock().lock();  
value = map.get(key);  
rwlock.readLock().unlock();  

4.使用 JDK1.5 提供的 
java.util.concurrent.ConcurrentHashMap 类
该类将 Map 的存储空间分为若干块,
每块拥有自己的锁,
大大减少了多个线程
争夺同一个锁的情况
value = map.get(key); 

1、不同步确实最快,与预期一致。 
2、四种同步方式中,
ConcurrentHashMap 是最快的,
接近不同步的情况。 
3、synchronized 关键字非常慢
4、使用读写锁的读锁,比普通所稍慢。

1、如果 ConcurrentHashMap 够用,
则使用 ConcurrentHashMap。 
2、如果需自己实现同步,
则使用 JDK1.5 提供的锁机制,
避免使用 synchronized 关键字。 

65.IdentityHashMap和HashMap的区别?
前者比较key时是
“引用相等”
而后者是
“对象相等”,
即对于
k1和k2,当k1==k2时, 
IdentityHashMap认为两个key相等,
而HashMap只有在k1.equals(k2) == true 时
才会认为两个key相等。 

2.IdentityHashMap 允许使用null作为key和value. 
不保证任何Key-value对的之间的顺序, 
更不能保证他们的顺序
随时间的推移不会发生变化。 

3.IdentityHashMap有其特殊用途,
比如序列化或者深度复制。或
者记录对象代理。 
举个例子,
jvm中的所有对象都是独一无二的,
哪怕两个对象是同一个class的对象 
而且两个对象的数据完全相同,
对于jvm来说,
他们也是完全不同的, 
如果要用一个map来记录这样jvm中的对象,
你就需要用IdentityHashMap,
而不能使用其他Map实现 

66.如何获取某个日期是当月的最后一天?
import java.util.Calendar;

public class Test {

public static void main(String[] args) {
  System.out.println(daysCount(2010, 2));
}

public static int daysCount(int year, int month) {
  Calendar cal = Calendar.getInstance();
  cal.set(Calendar.YEAR, year);
  cal.set(Calendar.MONTH, month);
  cal.set(Calendar.DATE, 0);
  return cal.get(Calendar.DATE);
}
}
67.java中会存在内存泄漏吗,请简单描述
所谓内存泄露就是指
一个不再被程序使用的对象或变量
一直被占据在内存中。
Java中有垃圾回收机制,
它可以保证一对象不再被引用的时候,
即对象编程了孤儿的时候,
对象将自动被垃圾回收器
从内存中清除掉。
由于Java 使用有向图的方式
进行垃圾回收管理,
可以消除引用循环的问题,
例如有两个对象,相互引用,
只要它们和根进程不可达的,
那么GC也是可以回收它们的。

java中的内存泄露的情况:
长生命周期的对象持有
短生命周期对象的引用
就很可能发生内存泄露,
尽管短生命周期对象已经不再需要,
但是因为长生命周期对象
持有它的引用而导致不能被回收,
这就是java中内存泄露的发生场景,
通俗地说,
就是程序员可能创建了一个对象,
以后一直不再使用这个对象,
这个对象却一直被引用,
即这个对象无用
但是却无法被垃圾回收器回收的,
这就是java中可能出现
内存泄露的情况,
例如,缓存系统,
我们加载了一个对象放在缓存中(例如放在一个全局map对象中),
然后一直不再使用它,
这个对象一直被缓存引用,
但却不再被使用。 
检查java中的内存泄露,
一定要让程序将各种分支情况
都完整执行到程序结束,
然后看某个对象是否被使用过,
如果没有,
则才能判定这个对象属于内存泄露。

如果一个外部类的实例对象的方法
返回了一个内部类的实例对象,
这个内部类对象被长期引用了,
即使那个外部类实例对象不再被使用,
但由于内部类持久外部类的实例对象,
这个外部类对象将不会被垃圾回收,
这也会造成内存泄露。

内存泄露的另外一种情况:
当一个对象被存储进HashSet集合中以后,
就不能修改这个对象中的
那些参与计算哈希值的字段了,
否则,对象修改后的哈希值
与最初存储进HashSet集合中时的哈希值
就不同了,
在这种情况下,
即使在contains方法使用该对象的
当前引用作为的参数去HashSet集合中
检索对象,
也将返回找不到对象的结果,
这也会导致无法从HashSet集合中
单独删除当前对象,
造成内存泄露

68.java中实现多态的机制是什么?
靠的是父类或接口的
引用指向子类或实现类的对象,
调用的方法是内存中
正在运行的那个对象的方法。

Java实现多态有三个必要条件:
继承、
重写、
向上转型。

继承:
在多态中必须存在
有继承关系的子类和父类。

重写:
子类对父类中某些方法进行重新定义,
在调用这些方法时
就会调用子类的方法。

向上转型:
在多态中需要将子类的引用
赋给父类对象,
只有这样该引用才能够具备
技能调用父类的方法和子类的方法。

只有满足了上述三个条件,
我们才能够在同一个继承结构中
使用统一的逻辑实现代码处理不同的对象,
从而达到执行不同的行为。

多态机制遵循的原则概括为
当超类对象引用变量引用子类对象时,
被引用对象的类型
而不是引用变量的类型
决定了调用谁的成员方法,
但是这个被调用的方法
必须是在超类中定义过的,
也就是说被子类覆盖的方法,
但是它仍然要根据继承链中
方法调用的优先级来确认方法,
该优先级为:
this.method(O)、
super.method(O)、
this.method((super)O)、
super.method((super)O)。


69.局部变量和成员变量的区别?
成员变量与局部变量的区别
1、在类中的位置不同
成员变量:
在类中方法外面
局部变量:
在方法或者代码块中,
或者方法的声明上

2、在内存中的位置不同,
成员变量:在堆中(方法区中的静态区)
局部变量:在栈中

3、生命周期不同
成员变量:
随着对象的创建而存在,
随着对象的消失而消失

局部变量:
随着方法的调用或者代码块的执行
而存在,
随着方法的调用完毕或者
代码块的执行完毕而消失

4、初始值

成员变量:
有默认初始值

局部变量:
没有默认初始值,
使用之前需要赋值,
否则编译器会报错

70.什么是匿名类,有什么好处?
简单地说:
匿名内部类就是没有名字的内部类。

什么情况下需要使用匿名内部类?
如果满足下面的一些条件,
使用匿名内部类是比较合适的:
 
只用到类的一个实例。 
类在定义后马上用到。 
类非常小(SUN推荐是在4行代码以下) 
给类命名并不会导致你的代码更容易被理解。 

在使用匿名内部类时,要记住以下几个原则: 
匿名内部类不能有构造方法。 
匿名内部类不能定义任何静态成员、方法和类。 
匿名内部类不能是
public,protected,private,static。 
只能创建匿名内部类的一个实例。 
一个匿名内部类一定是在new的后面,
用其隐含实现一个接口或实现一个类。 
因匿名内部类为局部内部类,
所以局部内部类的所有限制都对其生效。 

猜你喜欢

转载自blog.csdn.net/lkitlike/article/details/84230209