一、finally语句注意的细节:
当涉及到break和continue语句的时候,finally字句也会得到执行。
public class Test7 { public static void main(String[] args) { int i = 0; while (true) { try { i++; if (i == 3) break; } finally { if (i == 3) System.out.println("hi");//输出hi } } } }
二、异常在继承的时候需要注意的细节:
1、异常限制对构造器不起作用,子类构造器可以抛出任意异常,而不必理会基类所抛出的异常(这与方法不同),但是由于基类构造器必须以这样或那样的方式调用,子类构造器的异常说明必须包含基类构造器的异常说明。
class Dad { public Dad() throws AException { } } class Son extends Dad { public Son() throws AException,BException/*AException必须包含在声明*/ { super(); } }
2、子类方法可以不抛出任何异常,即使是基类定义的异常,因为假使基类的方法抛出了异常,这样做也不会破坏已有的程序,所以也没有问题。
interface AInterface { public void f() throws AException; } interface BInterface { public void f() throws BException; } class Impl implements AInterface,BInterface { @Override public void f() { } }
3、如果子类对象向上转型为基类的引用,那么在调用方法的时候,编译器就会要求捕获基类可能抛出的异常。同样是上例,在main方法里向上转型为AInterface,那么编译器会要求处理异常:
public class Test8 { public static void main(String[] args) throws AException{ AInterface imp=new Impl(); imp.f(); } } interface AInterface { public void f() throws AException; } interface BInterface { public void f() throws BException; } class Impl implements AInterface,BInterface { @Override public void f() { } }
4、异常说明本身不属于方法类型的一部分,方法类型是有方法的名字和参数的类型组成的。此外一个出现在基类方法的异常说明中的异常不一定会出现在子类中。
三、getCause方法的运用:(利用getCause处理利用异常链包装进其他异常的异常)
public class Test6 { public static void main(String[] args) { try { new A().f(); }catch(RuntimeException e) { System.out.println(e.getCause());//输出three.AException } } } class A { public void f() { try { throw new AException(); } catch (AException e) { throw new RuntimeException(e); } } }
四、异常处理的原则:注意能处理的处理,但是一定不能将重要的异常通过catch语句进行捕捉造成隐藏。
五、优先级队列(PriorityQueue)的介绍:
优先级队列始终保持队列顶部的元素始终是最小的,而其他元素无法保证,因此当调用队列的remove方法的时候,每次都是获得当前队列里面的最小的元素。PriorityQueue是怎么保证在添加或者删除元素的时候,顶部的元素始终是最小的呢?实际上,查看源码可以发现,其内部是一个二叉树的结构:
private void siftUpComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (key.compareTo((E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = key; }
while循环进行的工作是,当欲加入的元素小于其父节点时,就将两个节点的位置交换。这个算法保证了如果只执行add操作,那么queue这个二叉树是有序的:该二叉树中的任意一个节点都小于以该节点为根节点的子数中的任意其它节点。这也就保证了queue[0],即队顶元素总是所有元素中最小的。
六、Map的一个简单的实现
通过对于Map的一个简单的实现,可以更好的理解Map的内部实现机制,这里出于简单,Key和Value使用了ArrayList,但是实际上这种操作效率极低。
class MyMap<K,V> { private List<K> kList=new ArrayList<K>(); private List<V> vList=new ArrayList<V>(); private Set<MyEntry> entries=new HashSet<>(); public V put(K key, V value) { for(K k:kList) { //如果找到了key.那么替换key对应的value,返回旧的value if(k.equals(key)) { int index=kList.indexOf(k); V oldValue=vList.get(index); vList.set(index, value); return oldValue; } } kList.add(key); vList.add(value); return null; } public Object get(Object key) { for(K k:kList) { //如果找到了key.返回key对应的value if(k.equals(key)) { int index=kList.indexOf(k); V value=vList.get(index); return value; } } return null; } public class MyEntry implements Map.Entry<K, V> { int index; public MyEntry(int index) { this.index=index; } @Override public K getKey() { return kList.get(index); } @Override public V getValue() { return vList.get(index); } @Override public V setValue(V value) { return vList.set(index, value); } } public Set<MyEntry> entrySet() { for(int i=0;i<kList.size();i++) { entries.add(new MyEntry(i)); } return entries; } } public class Demo5 { public static void main(String[] args) { MyMap<String,String> map=new MyMap<>(); map.put("1", "哈士奇"); map.put("2", "柯基"); map.put("3", "金毛"); map.put("4", "博美"); for(Map.Entry<String, String> en:map.entrySet()) { System.out.println(en.getKey()+"==>"+en.getValue()); } } }
七、LinkedHashMap
可以在LinkedHashMap的构造方法中传入参数,使之采用基于访问的最近最少使用的算法。于是没有被访问过的元素就会出现在前面。下面是例子:
public class Demo6 { public static void main(String[] args) { LinkedHashMap<String, String> lh=new LinkedHashMap<>(5, 0.75f, true); lh.put("1", "小狗"); lh.put("2", "小兔子"); lh.put("3", "小猫"); lh.put("4", "小老鼠"); lh.put("5", "小鸡"); System.out.println(lh.keySet()); String test1=lh.get("2"); String test2=lh.get("3"); System.out.println(lh.keySet()); } }
输出为(其中最近访问的排在后面):
[1, 2, 3, 4, 5]
[1, 4, 5, 2, 3]
在这类HashMap的内部实现中,查询一个值的情况是,先计算散列码,然后根据散列码查询内部的数组,(数组可以看做桶,而数组中的一个个元素称之为桶位。)数组不保存值,而是保存值的List,然后对List中的值使用equals方法进行线性的查询。
LinkedHashMap的构造器参数中的float参数是负载因子。代表当负载情况(尺寸/容量)>负载因子的时候,容器将自动增加容量(桶位数),实现的情况是容量大致加倍,并且将现有的对象分配到新的桶位中。(称为再散列)