Thinking in Java读书笔记(IO和并发除外)
1.1、类中所有的private方法都隐式地指定为final的,由于其他类无法取用private方法,所以也就无法覆盖它;private方法无法被重写,继承对应private的方法无效,private的属性也无法拿到,但我们通常private的属性有对应的get、set方法,通常是public的,所以子类继承后可以调用方法来取得到父类的属性。
1.2、final类禁止继承,所以final类中所有的方法都隐式地指定为final的,因此无法覆盖他们,你也可以给他们添加final关键字,但这没什么意义。
1.3、java中所有方法都是通过动态绑定来实现多态的。
1.4、如果某个方法是静态的,它的行为就不具有多态性。所以构造器不具有多态性,他们实际上是static的,只不过该static声明是隐式的。
2.1、在方法的作用域内(而不是其他类的作用域内)创建一个完整的类,这个类称为局部内部类(其实命名就和局部变量一样,在方法中的变量叫局部变量)。
3.1、Set中,HashSet拥有最快的获取元素的方式;如果存储顺序很重要,那么使用TreeSet,它按照比较结果的升序保存对象,或者使用LinkedHashSet,它按照被添加的顺序保存对象。
3.2、List中,基本的arrayList擅长随机访问元素,但是在中间插入和移除元素时会比较慢;LinkedList和arrayList相反,中间插入和删除快,随机访问慢。
3.3、ListIterator是一个更加强大的迭代器,但看名字就知道只用于List类,普通的Iterator只能向前移动,就只有hasNext(),但ListIterator可以双向移动,有hasNext()和hasPrevious()两种移动方式。
3.4、Queue是队列,典型的先进先出容器,即从一端进去,另一端出来,取出顺序和放入顺序一样。
3.5、java容器实际上就四种:Map、List、Set和Queue。下面详细地介绍一下各个容器类型的性能特点区别:
3.5.1、List:元素可以重复。
ArrayList:底层是由数组支持的;
LinkedList:是由双向链表实现的,其中的每一个对象都包含数据的同时还包含了指向链表中前一个和后一个元素的引用。
因此如果要经常在表中插入和删除元素,LinkedList比较合适。
3.5.2、Set:元素必须唯一,不允许重复,是否重复由equals()方法判定,所以加入set的元素必须定义equals()方法以确保对象的唯一性。
HashSet:为快速查找而设计的Set,其中的元素必须定义hashCode()方法,一般情况下都用他。
Treeset:保存了次序的set,底层为(平衡)树结构,使用他可以从Set中提取有序的序列,其中的元素必须实现Comparable接口(实现里面的compareTo方法)。SortedSet是TreeSet的唯一实现。
LinkedHashSet:具有HashSet的查询速度,使用链表来维护元素的顺序(这个顺序就是插入的次序),所以使用迭代器遍历他时,结果就是插入的次序,元素必须定义hashCode()方法。
3.5.3、map:保存的是键值对,key不能重复,是唯一的,可以但只能有一个为null,value可重复,可为null。
HashMap:是map基于散列表的实现(现在已经取代了HashTable)。插入和查询键值对的开销是固定的,可以通过构造器设置容量和负载因子,以调整容器的性能。通常我们都使用他。
LinkedHashMap:在HashMap的基础上保存了键值对插入的次序,访问速度比hashMap慢一点,但迭代访问时反而更快,因为内部使用链表维护内部次序。
TreeMap:基于红黑树的实现,查看键或者键值对时,他们会被排序(次序由Comparable或者Comparator决定),因此特点就是结果是排过序的,他是唯一一个具有subMap()方法的map,可以返回一个子树。SortedMap是TreeMap现阶段的唯一实现。
ConcurrentHashMap:一种线程安全的map,它不涉及同步加锁,却比HashTable或者通过加锁来实现同步的Collections.synchronizedMap(Map)得到的同步map性能更好。Collections.synchronizedMap(Map)是把一个不同步的map作为参数返回一个同步的,靠加锁来实现的。
WeakHashMap:弱键映射,允许释放映射所指向的对象,为解决特色问题而设计的。
IdentityHashMap:使用==代替equals()对“键”进行比较的散列映射,专为解决特殊问题而设计。
3.6、Object.hashCode()方法生成散列码,而它默认使用对象的地址计算散列码,除非重写他;Object.equals()默认比较对象的地址是否相同,除非你重写他。
3.7、hashmap的结构我们通常叫他散列桶,散列表中的槽位通常称为桶位,因此实际散列表的数组命名为bucket。
3.8、设计hashCode()时,最重要的因素是,无论何时,对同一个对象调用hashCode()都应该返回同样的值,当然这个对象内的属性不应该发生了变化。
3.9、java容器采用快速报错机制,他会探查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦发现其他进程修改了容器,就会立刻抛出ConcurrentModificationException异常。一个简单的例子就可以看出快速报错的原理:
public static void main(String[ args){
Collection<String> c = new ArrayList<String>();
Iterator<String> it = c.iterator();
c.add("An Object");
try{
String s = it.next();
}catch(ConcurrentModificationException e){
System.out.println(e);
}
}
//输出:java.util.ConcurrentModificationException。。。
以上程序在运行时出现异常,catch后输出了异常信息,因为在容器取得迭代器后,又有东西放入到了容器中。当程序的不同部分修改了同一个容器时,就可能导致容器的状态不一致,就出现了这个异常。此例中,迭代器在获取容器后也可能会有修改容器的操作(比如iterator.remove()操作),所以在获取迭代器后,不能再调用容器的add方法改变容器,应该在获取迭代器之前就添加好元素。
3.10、在3.5中也提到了Hashtable,它和HashMap相似,甚至方法名也相似,Hashtable是同步的,但HashMap的同步问题也可通过Collections的一个静态方法得到解决: Map Collections.synchronizedMap(Map m) 这个方法返回一个同步的Map,这个Map封装了底层的HashMap的所有方法,使得底层的HashMap即使是在多线程的环境中也是安全的。 所以没有理由再使用Hashtable而不用HashMap了。
4.1、Throwable对象可分为两种类型(指从Throwable继承来的类型):Error用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception是可以抛出的基本类型。
4.2、因为finally子句一定会执行,即便在try子句中return,也会继续执行finally子句。
5.1、String对象是不可变的,String类中每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,最初的String对象则丝毫不动。
6.1、java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>和List<Integer>在运行时事实上是相同的类型,都被擦除成他们的“原生”类型--List。理解擦除以及如何处理它,是你在学习java泛型时面临的最大障碍。如下:
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);//输出:true
同样的因为擦除,同一个类就不能实现同一个泛型接口的两种变体,如下:
interface A<T> {}
Class B implements A<Integer> {}
Class C extends B implements A<String> {}
类C不能编译,A<Integer>和A<String>因为擦除而简化为相同的类型A,这样C就重复实现了接口A两次了。
但是有趣的是,如果把A的泛型参数去掉,编译是可以的。如下:
interface A {}
Class B implements A {}
Class C extends B implements A {}
类C可以编译。
6.2、java泛型的限制之一:不能将基本类型用作泛型的类型参数,如ArrayList<int>之类的是不能创建的。
7.1、编译器会为你创建的enum都继承自java.lang.Enum。
7.2、枚举的values()方法返回enum实例的数组,而且该数组中的元素严格保持在enum中的声明顺序。有趣的是,你的enum中没有values()方法,你会以为是编译器会为你创建enum时继承了Enum,values方法应该在Enum这个父类里。可惜,如果你研究一下Enum类就发现也不是,Enum里也没有。答案是,values()方法是由编译器添加的static方法,是编译器在创建enum时加进去的。
7.3、ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。
7.4、对应enum实例可以直接使用==比较,编译器会自动为你提供equals()和hashcode()方法。
7.5、我们也知道所有的enum都会继承自java.lang.Enum,因为java不支持多重继承,所以你的enum不能再继承其他类了。
enum EnumA extends ClassB{...}这是不行的。
7.6、enum还有一个非常有趣的特性,就是它允许你为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。大致步骤就是在enum中定义抽象方法,然后每个实例都实现它。如下:
public enum EnumTest{
SHI_LI_ONE {
String getInfo(){
return "实例1";
}
},
SHI_LI_TWO {
String getInfo(){
return "实例2";
}
};
abstract String getInfo();
}
8.1、switch语句是可以没用default子句的,当表达式的值和所有值都不同时就退出了switch语句,继续执行后面的。
9.1、注解也被称之为元数据,它为我们在代码里添加信息提供了一种形式化的方法。注解的定义用@interface。例如:
public @interface Test{}
9.2、注解不支持继承,不能使用extends来继承某个@interface。
10.1、我们都知道static修饰的变量存放在方法区中,和类信息存放在一起,所以只有一份,可以用它来修饰作为多线程任务区分的标识ID,例如每创建一个任务就加1等。