泛型<K,V>,指定key,value的类型,保证了类型的安全,如果没有泛型,比如说一个程序员往Map里面put一个User的对象,另一个程序员需要使用这个User,使用AbstractMap中的get方法会得到一个Object,他要使用User的话就需要对这个Object进行类型转换,那么这个时候就大大提高的出错的可能,这个时候如果有泛型,那么第一个程序员定义Map<String,User>来put一个User对象,第二个程序员get的就是一个User对象而不再是一个Object,避免了ClassCastException出现的可能,保证了类型的安全,可能这样有人会问,写泛型和不写泛型的的性能会有什么差距嘛?
运行上面的代码我们可以得知他们执行的是同一份字节码!所以说性能上没有什么影响,因为泛型在运行期间会被擦除!
接下来就是extends AbstractMap 和 implements Map 的故事,细心的人可能会发现 AbstractMap已经实现了Map接口,这里为什么还要再实现一次Map接口呢?这样的目的是什么,是为了提升程序可读性吗?可是这样会让人思考HashMap中的put方法到底是AbstractMap的还是Map的,唯一让人想到的可能是自己写jdk动态代理的时候会用到getInterfaces,这样确实不能得到父类的的Map接口,但是HashMap等集合类是jdk1.2中就有的,而动态代理的两个核心类Proxy和invacationHandler是jdk1.3中才有的,所以这一点也不怎么合理。最后在网上有一个相关的博客,回答得票最高的人问了当初写这段代码的Josh Bloch,得知这是一个错误的写法。
Cloneable(可复制的)和Serializable(串行化)
Cloneable是对一个对象的复制,这里我们提一下Java中的栈(stack)和堆(heap),所有的基本类型变量,对象的引用变量都在栈内存中分配,代码执行完毕以后,变量会立即释放节约内存空间,栈中的变量没有默认的初始值需要手动赋值,变量的值都是一个指向堆内存中的地址,对内存用来存放new创建的对象和数组,堆内存中的所有实体都有内存地址值,这些实体都是用来存放数据的有默认的初始值,这些实体不再背栈内存中的变量值指向的时候,jvm会启动垃圾回收自动清除。所以Java中把一个对象的引用赋值给两个变量的时候,其中一个变量改变这个对象的时候另一个变量指向的那个对象也被改变了,因为他们指向的是同一块堆内存是同一个对象,这也是Cloneable出现的原因,有时我们并不想要上面所说的那种操作一个引用的对象另一个引用的对象也被改变了,我们需要两个完全一样的对象而堆内存中的地址不同。
Serializable是对一个对象的串行化,对象的生命通常随着生成该对象的程序的中终止而终止,有时候我们需要把内存中的对象(堆内存中的值)保存下来,并在需要的时候将其恢复,虽然你可能有很多种保存的方法,但是Java给你提供了一种很好的保存机制,那就是序列化技术,序列化可以让你把一个对象的状态写入一个Byte流里(序列化),并可以从其他地方把这个Byte流的数据读出来(反序列化),当我们想把对象保存在文件中或者数据库中的时候或者在网络中进行传播的时候就可以使用序列化的技术,如果我们想使用序列化的话只需要对这个类实现Serializable的接口就可以了,我们可以使用ObjectOutStream的writeObject()把这个类的对象写到你想要的地方,再通过ObjectInputStream的readObject()的方法把这个类读出来,如果我们把这个类的Serializable接口去掉在执行上面的操作会出现NotSerializableException的错误。
我们实现了Serializable的接口以后,编辑器会让我们生成一个serialVersionUID,这个东西写不写对我们上面的操作不会有什么影响,那么这个东西到底是做什么的呢?序列化再Eclipse中提供了两种生成策略,一种是固定的1L,还有一种是long类型的数据(实际上是通过jdk工具,根据类名、接口名、成员方法及属性等生成的),在网络中进行传播这个对象的话,如果两个客户端中的这个类的序列化ID不一致的话那么对方拿到这个对象是不能正常的反序列化的会出现InvalidClassException异常。需要注意的如果类中的成员变量是静态的那么反序列化的结果得到的是null,这其实也很好理解,因为序列化保存的是对象的状态而静态的成员变量是属于类的状态,在实现了序列化的类中我们可能会见到transient关键字,transient的作用是给那些不想进行序列化保存同时又不适合static关键字申明的属性,这样在反序列化的时候得到的结果就是null。