和我一起学Effective Java之泛型
泛型
不要在新代码中使用原始类型
泛型(generic):声明中具有一个或多个类型参数
原始类型(raw type):不带任何实际类型参数的泛型名称
格式:
类或接口的名称<对应于泛型形式类型参数的实际参数>如
List<String>就是对应于List<E>的实际参数为String的参数化类型如与
List<E>对应的原始类型是List
优点:
- 在编译时发现插入类型的错误(越早发现错误越好,编译时发现好于运行时发现)
- 不再需要手工转换类型
//JDK5之前的写法,使用的是原始类型
private static final List stringList = new ArrayList();
//有了泛型之后的写法,使用泛型
private static final List<String> stringList2 = new ArrayList<String>();
//JDK7 能将后面<>里的类型省略,被称为Diamond
private static final List<String> stringList3 = new ArrayList<>();
public static void main(String[] args) {
String str = "test";
Integer integer = 1;
stringList.add(str);
stringList.add(integer);//可通过编译,但之后报ClassCastException错误
stringList2.add(str);
// stringList2.add(integer);//无法通过编译
for(Iterator iterator = stringList.iterator();iterator.hasNext();){
String string = (String) iterator.next();
System.out.println(string);
}
for(Iterator iterator = stringList2.iterator();iterator.hasNext();){
String string = iterator.next();
System.out.println(string);
}
List和List<Object>之间的区别?
List逃避了泛型检查,List<Object>则是告知编译器,它能够持有任意类型的对象
无限制的通配符类型:
使用泛型,但不确定或者不关心实际的类型参数,可以用一个问号代替。如List<?>
泛型信息在运行时会被擦除
学习链接:
1.https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
2.http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java
下面通过一个小demo说明类型擦除
//类型擦除
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
System.out.println(stringList.getClass().toString());
System.out.println(integerList.getClass().toString());
System.out.println(stringList.getClass()==integerList.getClass());
integerList.add(100);
Method method = integerList.getClass().getMethod("add",Object.class);
method.invoke(integerList,"abc");
System.out.println(integerList);
运行结果:

一般不在代码中使用原始类型,除了两种例外情况(都是因为泛型信息在运行时会被擦除):
- 1.在类文字(class literals)中
如:
List.class,String[].class,int.class都合法
List<String>.class,List<String>.class都不合法
- 2.instanceof
if(o instanceof Set){ //原始类型(Raw Type)
Set<?> set = (Set<?>)o;//通配符类型(WildCard Type)
}
下面的表格是泛型相关的术语:

下面这张图很好的介绍了无限制通配符和其他泛型符号之间的关系:

消除非受检警告
始终在尽可能小的范围内使用SuppressWarnings注解
Java源码中的ArrayList类中有个toArray方法,其中就有强转的警告:
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
最好是将范围进一步缩小。将注解由整个方法到局部的变量声明上去。
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size){
@SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elementData, size, a.getClass());
return result;
}
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
列表优于数组
- 数组是协变的(covariant),泛型则是不可变的
Object[] objectArray = new String[1];
List<Object> objectList = new ArrayList<String>();//无法通过编译 imcompatible types
// String类是Object类的子类
//String[]是Object[]的子类
//而List<String>并不是List<String>的子类型
- 数组是具体化的(reified),在运行时才知道并检查它们的元素类型约束。而泛型通过擦除来实现的。泛型只在编译时强化类型信息,并在运行时擦除它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码可以互用。
Object[] objectArray = new String[1];
List<String> objectList = new ArrayList<String>();
objectArray[0] = 3;//可通过编译,运行时报错
// objectList.add(1);//编译时报错

数组和泛型不能很好地混合使用。可用列表代替数组。
总结:数组是协变且可具体化的,泛型是不可变的且可被擦除的。-->数组提供了运行时类型安全而编译时类型不安全。而泛型反之。
优先考虑泛型
泛型相比于Object的优点:
- 不需要强制类型转换
- 编译时类型安全
public class SomeClazz<T> {
public Object dosthWithObj(Object obj){
return obj;
}
public T dosthWithT(T t){
return t;
}
public static void main(String[] args) {
SomeClazz<Foo> someClazz = new SomeClazz<Foo>();
Foo foo = new Foo();
Foo foo1 = (Foo) someClazz.dosthWithObj(foo);
Foo foo2 = someClazz.dosthWithT(foo);
}
}
public class Stack<E> {
private E [] elements;
private static final int MAX_SIZE = 16;
private int size = 0;
@SuppressWarnings("unchecked")
public Stack(){
elements = (E[]) new Object[MAX_SIZE];
}
public void push(E e){
ensureSize();
elements[size++]=e;
}
public E pop(){
if(size==0)
throw new EmptyStackException();
E e = elements[--size];
elements[size]=null;
return e;
}
private void ensureSize() {
if(size==elements.length){
elements= Arrays.copyOf(elements,2*size+1);
}
}
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
for(int i =0;i<50;i++){
stack.push(i);
}
for(int i = 0;i<10;i++){
System.out.println(i+": "+stack.pop());
}
}
}
class EmptyStackException extends RuntimeException{
}
前面曾鼓励优先使用列表而不是数组。并不意味着所有的泛型中都要使用列表。况且Java并不是生来就支持列表的。
每个类型都是它自身的子类型。
如有 SomeClazz<E extends Number>
SomeClazz<Number>是合法的
优先考虑泛型方法
方法可以考虑泛型化,特别是静态工具方法。
泛型方法语法:
方法修饰语 泛型 返回值 方法名()
public static <T> T foo(T args);
/**
* 使用泛型方法
* 返回两个集合的联合
* @param s1
* @param s2
* @param <E>
* @return
*
* 局限:两个参数和返回的结果的类型必须全部相同
* 解决方法:使用有限制的通配符
*/
public static <E> Set<E> unionGeneric(Set<E> s1,Set<E> s2){
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
public static <K,V> Map<K,V> newHashMap(){
return new HashMap<K,V>();
}
泛型单例工厂:
public interface UnaryFunction<T>{
T apply(T arg);
}
private static UnaryFunction<Object> IDENTITY_FUNCTION =
new UnaryFunction<Object>() {
@Override
public Object apply(Object arg) {
return arg;
}
};
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction(){
return (UnaryFunction<T>) IDENTITY_FUNCTION;
}
/**
* 每次都要创建一个,很浪费,而且它是无状态的.
* 泛型被具体化了,每个类型都需要一个恒等函数,但是它们被擦除后,就只需要一个泛型单例了.
* @param <T>
* @return
*/
public static <T> UnaryFunction<T> identityFunction2(){
return new
UnaryFunction<T>() {
@Override
public T apply(T arg) {
return arg;
}
};
}
递归类型限制:
通过某个包含该类型参数本身的表达式来限制类型参数
<T extends Comparable<T>>//针对可以与自身进行比较的每个类型T
利用有限制通配符来提升API的灵活性
参数化类型是不可变的。
虽然String类是Object类的子类,但是List<String>和List<Object>无关
/**
* 栈的实现
* @param <E>
* API:
* public Stack();
* public void push(E e);
* public E pop();
* public boolean isEmpty();
*
* 新增API:
* before:
* public void pushAll(Iterable<E> i);
* public void popAll(Collection<E> c);
* after:
* 使用有限制的通配符类型(bounded wildcard type)
* public void pushAll(Iterable<? extends E> i);
* public void popAll(Collection<? super E> c);
*
*/
class Stack<E>{
private E [] elements;
private static final int INIT_CAPABILITY = 16;
private int size = 0;
@SuppressWarnings("unchecked")
public Stack(){
elements = (E[]) new Object [INIT_CAPABILITY];
}
public void push(E e){
checkCapability();
elements[size++]=e;
}
public E pop(){
if(size==0)
throw new RuntimeException("Empty Stack");
E e = elements[--size];
elements[size]=null;
return e;
}
private void checkCapability() {
if(size==elements.length)
elements = Arrays.copyOf(elements,2*elements.length-1);
}
public boolean isEmpty(){
return size==0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String start = super.toString();
sb.append(start);
for(int i = 0 ;i<size;i++){
String s = " ["+elements[i]+"]";
sb.append(s);
}
return sb.toString();
}
//before
// public void pushAll(Iterable<E> i){
// for(E e:i){
// push(e);
// }
// }
// public void popAll(Collection<E> c){
// while (!isEmpty()){
// c.add(pop());
// }
// }
//after
public void pushAll(Iterable<? extends E> i){
for(E e:i){
push(e);
}
}
public void popAll(Collection<? super E> c){
while(!isEmpty()){
c.add(pop());
}
}
Stack<Number> stack= new Stack<>();
Iterable<Integer> integers = Arrays.asList(1,2,3,4,5);
Collection<Object> objectCollection = new LinkedList<>();
//before
// stack.pushAll(integers);//参数类型不对
// stack.popAll(objectCollection);//参数类型不对
//after
stack.pushAll(integers);
System.out.println(stack);
stack.popAll(objectCollection);
System.out.println(stack);
从上面的Demo中我们知道,Java中提供了有限制的通配符类型来提高API的灵活性。
如
Collection<? extends E>
Collection<? super E>
一般在表示生产者或消费者的输入参数上使用通配符类型。
PECS:Producer-extends Consumer-super
------------------
* 参数化类型 通配符类型
* T生产者 extends
* T消费者 super
* ------------------
和我一起学Effective Java之泛型的更多相关文章
- 和我一起学Effective Java之创建和销毁对象
前言 主要学习创建和销毁对象: 1.何时以及如何创建对象 2.何时以及如何避免创建对象 3.如何确保它们能够适时地销毁 4.如何管理对象销毁之前必须进行的清理动作 正文 一.用静态工厂方法代替构造器 ...
- 和我一起学Effective Java之类和接口
类和接口 使类和成员的可访问性最小 信息隐藏(information hiding)/封装(encapsulation):隐藏模块内部数据和其他实现细节,通过API和其他模块通信,不知道其他模块的内部 ...
- Effective java -- 4 泛型
第二十三条:请不要在代码中使用原生态类型就是像Set这种待泛型的,就把泛型明确写出来. 第二十四条:消除非受检警告就是Set<String> sets = new HashSet();这种 ...
- Effective Java 第三版——29. 优先考虑泛型
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——32.合理地结合泛型和可变参数
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- effective java笔记之java服务提供者框架
博主是一名苦逼的大四实习生,现在java从业人员越来越多,面对的竞争越来越大,还没走出校园,就TM可能面临失业,而且对那些增删改查的业务毫无兴趣,于是决定提升自己,在实习期间的时间还是很充裕的,期间自 ...
- Effective java笔记(二),所有对象的通用方法
Object类的所有非final方法(equals.hashCode.toString.clone.finalize)都要遵守通用约定(general contract),否则其它依赖于这些约定的类( ...
- 《Effective java》-----读书笔记
2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己!预计在2016年要看12本书,主要涉及java基础.Spring研究.java并 ...
- 《Effective Java》学习笔记——积累和激励
从一个实际案例说起 国庆长假前一个礼拜,老大给我分配了这么一个bug,就是打印出来的报表数量为整数的,有的带小数位,有的不带,毫无规律. 根据短短的两个多月的工作经验以及猜测,最终把范围缩小到以下这段 ...
随机推荐
- full GC触发的条件
full GC触发的条件除直接调用System.gc外,触发Full GC执行的情况有如下四种.1. 旧生代空间不足旧生代空间只有在新生代对象转入及创建为大对象.大数组时才会出现不足的现象,当执行Fu ...
- 设置TabBar图片
设置TabBar图片 // 拿到 TabBar 在拿到想应的item UITabBar *tabBar = _tabBarController.tabBar; UITabBarItem *item0 ...
- HTML5 Canvas制作雷达图实战
雷达图又叫蜘蛛网图,是一种对各项数据查看很明显的表现图,在很多游戏中,对游戏中的每个角色的分析图一般也用这种图. 下面,用HTML5的Cavas来实现雷达图. 效果 一.创建Canvas var mW ...
- Linux终端记录神器
我们在调试程序的时候,免不了要去抓一些 log ,然后进行分析.如果 log 量不是很大的话,那很简单,只需简单的复制粘贴就好.但是如果做一些压力测试,产生大量 log ,而且系统内存又比较小(比如嵌 ...
- 魅族便签,是否能成为国内便签应用的No.1?
继前不久锤子科技推出便签 Android 新版后,近期魅族在PRO 6公布会上也公布了最新的魅族便签应用.这一次魅族把便签应用拓展到了整个Android体系,也就是说.其它不论什么的Android手机 ...
- ELK菜鸟手记 (二) - 高级配置之多应用索引过滤
我们在实际的场景中,经常是多个网站或者服务端在一台服务器上,但是如果这些应用全部 记录到一台logstash服务器,大家日志都混在一起不好区分. 有人说,我可以在日志中打项目名,但是这样并不方便. 其 ...
- RHEL下修改市区
针对中国时区,修改操作如下 1. 修改文件 /etc/sysconfig/clock内容: ZONE=Asia/ShanghaiUTC=falseARC=false 2. rm /etc/ ...
- Fix Corrupt Blocks on HDFS
来自:http://centoshowtos.org/hadoop/fix-corrupt-blocks-on-hdfs/ How do I know if my hadoop hdfs filesy ...
- 1209 -The MySQL server is running with the --read-only option
1209 - The MySQL server is running with the --read-only option so it cannot execute this statement ...
- iOS中自动登录的设计
1.//这是登录控制器页面 - (void)viewDidLoad { [super viewDidLoad]; //lt.iSNextAutoLogin是单利中的一个属性,用来保存下次是否自动登录 ...