一、泛型简介
为集合提供编译时(静态)类型安全,并消除了大多数类型转换的需要
(由于泛型是在编译时起作用,所以在运行时动态生成的反射可以绕过泛型限制)
1、泛型的特点
-
泛型可以将类型作为参数进行传递,即类型可以像参数一样实现参数化。
-
在编译的时候检查类型安全。
-
所有的强制转换都是自动和隐式的。
-
Java引入泛型是安全简单的,并能提高代码的重用。
2、泛型引入前
在没有泛型的时候,基于通用性的考虑,早期的集合类存储的都是Object类型,一个集合便可以存储任意类型的对象,因为所有对象都是Object的子类型
那么这样做会存在什么样的问题呢,我们先看一个不使用泛型的例子
例如这个程序代码段里就有一个list集合,假设在这个集合里放的是整形对象,而我们很可能不小心把字符串对象放到里面,这时编译器却无法发现其中的错误,直到程序运行时才会发现错误,但为时已晚,这种情况甚至在应用系统已经部署之后才会发生。而利用泛型就可以提前发现这些错误
3、泛型引入后
在编译期就已经报错了,程序员可以提前解决这问题
二、泛型类
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别称为
泛型类
、泛型接口
和泛型方法
。
1、什么是泛型类
泛型类就是一个具有多种类型变量的类,泛型类可以拥有一个类型,也可以拥有多个类型
[访问修饰符] class 类名<T,U,...> { T 泛型成员1 ; U 泛型成员2; ... }
类型参数使用大写形式,且比较短,这是最常使用的
2、常用泛型标识符
Java泛型类中常用泛型通配符有:
通配符 | 说明 |
---|---|
T (type) | 表示具体的一个java类 |
K V (key value) | 分别代表java键值中的Key Value |
E (element) | 一般在集合中使用,表示集合中的元素类型 |
? | 表示不确定的 java 类型,经常出现在集合类中 |
本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些
3、示例
package com.woniuxy; public class Test<T> { private T value; public Test(T value) { super(); this.value = value; } public static void main(String[] args) { Test<String> t1 = new Test<String>("tom"); System.out.println(t1.value); Test<Integer> t2 = new Test<Integer>(22); System.out.println(t2.value); } }
4、示例
此时的Key即可以是整形还可以是字符串类型,也或以是其它类型
public class Student<K, V> { private K id; private V name; public Student(K id, V name) { super(); this.id = id; this.name = name; } public static void main(String[] args) { Student<Integer, String> student = new Student<Integer, String>(1001, "tom"); System.out.println(student.id); System.out.println(student.name); Student<String, String> student2 = new Student<String, String>("s1001", "tom"); System.out.println(student2.id); System.out.println(student2.name); } }
5、泛型方法
泛型方法,是在调用方法的时候指明泛型的具体类型
泛型方法的声明格式:
修饰符 返回类型 方法名(参数列表){ //方法体 }。例如: public T/void/... hello([T t]){ return t; }
示例
package com.woniuxy; public class Student<K, V> { private K id; private V name; public Student(K id, V name) { super(); this.id = id; this.name = name; } public K getId() { return id; } public void setId(K id) { this.id = id; } public V getName() { return name; } public void setName(V name) { this.name = name; } public static void main(String[] args) { Student<Integer, String> student = new Student<Integer, String>(1001, "tom"); student.setName("jerry"); System.out.println(student.getName()); } }
三、泛型接口
用于接口中,在接口名末尾对泛型进行声明;
关系型数据库是由表组成的,一张数据表一般保存一类信息,那么张数据表在Java中就对应一个简单Java类(vo类),而且我们会定义一个接口来规范操作这张数据表的实现类开发。
对于实体的操作的只有接口的名称以及每个方法的参数类型不一样,方法的名称以及形式都是一样的,如果有一百张数据表就意味着要定义一百个这样的接口。此时就出现了代码重复的现象,最好的做法是使用一个接口实现多张数据表的数据操作。要实现这样的操作需要使用泛型接口。之前每个接口只能操作一种类型的数据,现在使用泛型接口之后,把要操作的数据类型使用占位符标记,具体使用接口的时候再根据需求指定泛型的类型
1、定义泛型接口
package com.woniuxy.generate; import java.util.List; public interface IBaseDao<K, T> { /** * 查询全部 * @return */ public List<T> getAll(); /** * 根据id查询 * @param k * @return */ public T queryById(K k); /** * 添加 * @param t * @return */ public int add(T t); /** * 修改 * @param t * @return */ public int update(T t); /** * 删除 * @param k * @return */ public int delete(K k); }
2、定义员工的实现类
package com.woniuxy.generate.impl; import java.util.List; import com.woniuxy.entity.Employee; import com.woniuxy.generate.IBaseDao; public class EmployDaoImpl implements IBaseDao<Integer, Employee> { @Override public List<Employee> getAll() { // TODO Auto-generated method stub return null; } @Override public Employee queryById(Integer k) { // TODO Auto-generated method stub return null; } @Override public int add(Employee t) { // TODO Auto-generated method stub return 0; } @Override public int update(Employee t) { // TODO Auto-generated method stub return 0; } @Override public int delete(Integer k) { // TODO Auto-generated method stub return 0; } }
3、定义部门的实现类
package com.woniuxy.generate.impl; import java.util.List; import com.woniuxy.entity.Dept; import com.woniuxy.generate.IBaseDao; public class DeptDaoImpl implements IBaseDao<Integer, Dept> { @Override public List<Dept> getAll() { // TODO Auto-generated method stub return null; } @Override public Dept queryById(Integer k) { // TODO Auto-generated method stub return null; } @Override public int add(Dept t) { // TODO Auto-generated method stub return 0; } @Override public int update(Dept t) { // TODO Auto-generated method stub return 0; } @Override public int delete(Integer k) { // TODO Auto-generated method stub return 0; } }
四、泛型进阶(了解)
除了用
<T>
表示泛型外,还有<?>
这种形式。<?>
被称作无限定的通配符
1、<?>
无界通配符
T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义。
?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法
可以查看API,看到Class类中有大量的应用
static 类<?> | forName(String className) 返回与给定字符串名称的类或接口相关联的 类对象。 |
---|---|
static 类<?> | forName(String name, boolean initialize, ClassLoader loader) 使用给定的类加载器返回与给定字符串名称的类或接口相关联的 类 对象。 |
先看以下示例:
public class MainApp { public static void main(String[] args) { List<Integer> intlist = new ArrayList<Integer>(); intlist.add(11); intlist.add(22); intlist.add(33); List<String> strlist = new ArrayList<String>(); strlist.add("aa"); strlist.add("bb"); strlist.add("cc"); printList(intlist); } private static void printList(List<Integer> list) { for (Integer integer : list) { System.out.println(integer); } } }
此时,printList方法只能打印
List<Integer>
,那如果我们还想打印List<String> strlist
,这时该怎么办?
这时我们就需要通过<?>通配符来实现
public class MainApp { public static void main(String[] args) { List<Integer> intlist = new ArrayList<Integer>(); intlist.add(11); intlist.add(22); intlist.add(33); List<String> strList = new ArrayList<String>(); strList.add("aa"); strList.add("bb"); strList.add("cc"); printList(strList); } /** * 定义一个方法,接收所有的集合元素 * @param list */ private static void printList(List<?> list) { for (Object obj : list) { System.out.println(obj); } } }
List<?>`是一个未知类型的`List`,不能向`List<?>`中添加元素,但可以把`List<String>`,`List<Integer>`赋值给`List<?>很多人认为
List<?>
和List<Object >
是一样的,其实这是不对的,<Object>
表示任意类型,<?>
表示未知类型,可以向List<Object>
中添加元素,但是不能把List<String>
赋值给List<Object>
参考
2、上界通配符 < ? extends E>
上界(上限):用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
<U> 类<? extends U> | asSubclass(类<U> clazz) 类 这个 类 对象来表示由指定的类对象表示的类的子类。 |
---|---|
<A extends Annotation>A | getAnnotation(类<A> annotationClass) 返回该元素的,如果这样的注释 ,否则返回null指定类型的注释 |
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
-
如果传入的类型不是 E 或者 E 的子类,编译不成功
-
泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
package com.woniuxy.generate; import java.util.ArrayList; import java.util.List; public class MainApp { public static void main(String[] args) { List<Apple> applelist = new ArrayList<Apple>(); applelist.add(new Apple()); applelist.add(new Apple()); applelist.add(new Apple()); List<String> strList = new ArrayList<String>(); strList.add("aa"); strList.add("bb"); strList.add("cc"); printList(applelist); } /** * 定义一个方法,可以接收Fruit,以及Fruit所有的子类的集合元素 * @param list */ private static void printList(List<? extends Fruit> list) { for (Object obj : list) { System.out.println(obj); } } class Fruit { public void eat() { System.out.println("eat fruit"); } @Override public String toString() { return "Fruit []"; } } class Apple extends Fruit { private String name; @Override public String toString() { return "Apple []"; } } }
3、下界通配符 < ? super E>
下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object
在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
类<? super T> | getSuperclass() 返回 类表示此所表示的实体(类,接口,基本类型或void)的超类 类 。 |
---|---|
package com.woniuxy.generate; import java.util.ArrayList; import java.util.List; public class MainApp { public static void main(String[] args) { List<Fruit> applelist = new ArrayList<Fruit>(); applelist.add(new Fruit()); applelist.add(new Fruit()); applelist.add(new Fruit()); List<String> strList = new ArrayList<String>(); strList.add("aa"); strList.add("bb"); strList.add("cc"); printList(applelist); } /** * 定义一个方法,可以接收Apple,以及Apple所有的父类集合 * @param list */ private static void printList(List<? super Apple> list) { for (Object obj : list) { System.out.println(obj); } } class Fruit { public void eat() { System.out.println("eat fruit"); } @Override public String toString() { return "Fruit []"; } } class Apple extends Fruit { private String name; @Override public String toString() { return "Apple []"; } } }
4、泛型擦除
Java语言的泛型实现方式是擦拭法(Type Erasure)。
所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。
public class MainApp2 { public static void main(String[] args) throws Exception{ //Java泛型只在编译阶段起作用,实际运行中不起作用 List<String> strList = new ArrayList<>(); List<Integer> intList = new ArrayList<>(); intList.add(11); intList.add(22); //intList.add("hello"); System.out.println(strList.getClass());// 输出:class java.util.ArrayList System.out.println(intList.getClass());// 输出:class java.util.ArrayList System.out.println(strList.getClass() == intList.getClass());// 输出:true //通过java反射的方式可以绕过泛型, Class<? extends List> clazz = intList.getClass(); Method method = clazz.getMethod("add", Object.class); method.invoke(intList, "hello"); System.out.println(intList); } }
如上述代码所示,最终系统返回结果是true,说明List<Integer> 和List<String>只是在编译期间有效果,当代码在实际运行时,会自动转化成class java.util.ArrayList