21.泛型设计
本章目标
- 泛型简介
- 泛型类及方法
- 泛型接口
- 泛型进阶
本章内容
一、泛型简介
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
从Java 1.0版本发布以来,变化最大的部分就是泛型,致使JDK 1.5增加泛型机制的主要原因是为了满足在1999年制定的最早的Java规范需求(JSR 14)
Generics: provides compile-time (static) type safety for collections and eliminates the need for most typecasts (type conversion) (specified by JSR 14)
为集合提供编译时(静态)类型安全,并消除了大多数类型转换的需要
1、泛型的特点
- 泛型可以将类型作为参数进行传递,即类型可以像参数一样实现参数化。
- 在编译的时候检查类型安全。
- 所有的强制转换都是自动和隐式的。
- Java引入泛型是安全简单的,并能提高代码的重用。
2、泛型引入前
在没有泛型的时候,基于通用性的考虑,早期的集合类存储的都是Object类型,一个集合便可以存储任意类型的对象,因为所有对象都是Object的子类型
那么这样做会存在什么样的问题呢,我们先看一个不使用泛型的例子
image-20230715085003849
例如这个程序代码段里就有一个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、定义员工接口
public interface EmployeeDao extends IBaseDao<Integer,Employee> {
}
3、定义员工的实现类
package com.woniuxy.generate.impl;
import java.util.List;
import com.woniuxy.entity.Employee;
import com.woniuxy.generate.IBaseDao;
public class EmployDaoImpl implements EmployeeDao{
@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;
}
}
4、定义部门接口
public interface DeptDao extends IBaseDao<Integer, Dept> {
long getCount() throws SQLException;
}
5、定义部门的实现类
package com.woniuxy.generate.impl;
import java.util.List;
import com.woniuxy.entity.Dept;
import com.woniuxy.generate.IBaseDao;
public class DeptDaoImpl implements DeptDao {
@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;
}
}
四、泛型进阶(了解)
除了用
表示泛型外,还有 这种形式。 被称作无限定的通配符
1、<?>
无界通配符
T 是一个确定的类型,通常用于泛型类和泛型方法的定义。
?是一个不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法
可以查看API,看到Class类中有大量的应用
返回类型 | 方法 |
---|---|
static Class<?> |
forName(String className) 返回与给定字符串名称的类或接口相关联的 类对象。 |
先看以下示例:
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
,那如果我们还想打印List 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) {
//list.add("bb");报错,因为?是不能确定类型的
for (Object obj : list) {
System.out.println(obj);
}
}
}
List是一个未知类型的List,不能向List中添加元素,但可以把List
,List 赋值给List<?> 很多人认为
List<?>
和List<Object >
是一样的,其实这是不对的,<Object>
表示任意类型,<?>
表示未知类型,可以向List<Object>
中添加元素,但是不能把List<String>
赋值给List<Object>
参考
2、上界通配符 < ? extends E>
上界(上限):用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
返回类型 | 方法 |
---|---|
<U> Class<? extends U> |
asSubclass(类<U> clazz) 类 这个 类 对象来表示由指定的类对象表示的类的子类。 |
在类型参数中使用 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 的父类。
返回类型 | 方法 |
---|---|
Class<? 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
和List 只是在编译期间有效果,当代码在实际运行时,会自动转化成class java.util.ArrayList
本文来自博客园,作者:icui4cu,转载请注明原文链接:https://www.cnblogs.com/icui4cu/p/18816577