0%

快速了解Java泛型(转载)

什么是泛型

Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常

泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型

参数化类型:
- 把类型当作参数一样传递
- <数据类型> 只能是应用类型

相关术语:
- ArrayList 中的E称为 类型参数变量
- ArrayList 中的Integer称为 实际类型参数
- 整个称为 ArrayList 泛型类型

为什么需要泛型

Object 向下强转不安全

如果没有泛型:
- 从集合中获取的元素都是Object类型,需要强转
- 集合对元素没有限制,强转不会有任何语法错误,但运行会出错

有了泛型以后:
- 代码更简洁(不需要强转)
- 程序更健壮(只要编译时没警告,那么运行时就不会有ClassCastException)
- 可读性和稳定性(创建集合时就指定了类型)

增强for循环遍历集合

1
2
3
4
5
6
7
8
9
10
11
//创建集合对象
ArrayList<String> list = new ArrayList<>();

list.add("hello");
list.add("world");
list.add("java");

//遍历,由于明确了类型.我们可以增强for
for (String s : list) {
System.out.println(s);
}

泛型基础

泛型类

在类上定义泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
1:把泛型定义在类上
2:类型变量定义在类上,方法中也可以使用
*/
public class ObjectTool<T> {
private T obj;

public T getObj() {
return obj;
}

public void setObj(T obj) {
this.obj = obj;
}
}

泛型方法

在方法上定义泛型

1
2
3
4
//定义泛型方法..
public <T> void show(T t) {
System.out.println(t);
}

泛型类的子类

  1. 如果子类明确类型参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /*
    把泛型定义在接口上
    */
    public interface Inter<T> {
    public abstract void show(T t);
    }

    /**
    * 子类明确泛型类的类型参数变量:
    */

    public class InterImpl implements Inter<String> {
    @Override
    public void show(String s) {
    System.out.println(s);
    }
    }
  2. 如果子类不明确类型参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 子类不明确泛型类的类型参数变量: 实现类也要定义出<T>类型的
    *
    */
    public class InterImpl<T> implements Inter<T> {
    @Override
    public void show(T t) {
    System.out.println(t);
    }
    }

类型通配符?

1
2
3
4
5
public void test(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}

?号通配符表示可以匹配任意类型,任意的Java类都可以匹配…..

注意:当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。

设定通配符上限

1
2
3
4
5
public void test(List<? extends Number> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}

设定通配符下限

1
2
3
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}

下限有啥用?
我们来想一下,当我们想要创建一个TreeSet<String>类型的变量的时候,并传入一个可以比较String大小的Comparator。那么这个Comparator的选择就有很多了,它可以是Comparator<String>,还可以是类型参数是String的父类,比如说Comparator<Objcet>

注意: 无论是设定通配符上限还是下限,都是不能操作与对象有关的方法。

通配符 vs 泛型方法

1
2
3
4
5
6
7
8
9
//使用通配符
public static void test(List<?> list) {

}

//使用泛型方法
public <T> void test2(List<T> t) {

}

原则:
* 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法
* 如果没有依赖关系的,就使用通配符,通配符会灵活一些.

泛型擦除

泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛型的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。

兼容性:JDK5以前是没有泛型的。当把带有泛型特性的集合赋值给老版本的集合时候,泛型会被擦除。

泛型的应用

  • 抽象DAO

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public abstract class BaseDao<T> {

    //模拟hibernate....
    private Session session;
    private Class clazz;

    //哪个子类调的这个方法,得到的class就是子类处理的类型(非常重要)
    public BaseDao(){
    Class clazz = this.getClass(); //拿到的是子类
    ParameterizedType pt = (ParameterizedType) clazz.getGenericSuperclass(); //BaseDao<Category>
    clazz = (Class) pt.getActualTypeArguments()[0];
    System.out.println(clazz);

    }

    public void add(T t){
    session.save(t);
    }

    public T find(String id){
    return (T) session.get(clazz, id);
    }

    public void update(T t){
    session.update(t);
    }

    public void delete(String id){
    T t = (T) session.get(clazz, id);
    session.delete(t);
    }

    }
  • 继承抽象DAO,该实现类就有对应的增删改查的方法了。

    1
    2
    3
    4
    5
    6
    7
    public class CategoryDao extends BaseDao<Category> {

    }

    public class BookDao extends BaseDao<Book> {

    }


转载自微信公众号Java3y泛型就这么简单