花几分钟把java泛型吃透
Posted 野生java研究僧
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了花几分钟把java泛型吃透相关的知识,希望对你有一定的参考价值。
文章目录
1.什么是泛型?
泛型是从jdk5开始引入的东西,所谓的泛型就是将参数类型化,就是将具体的参数类型进行类型化,调用的时候再传递具体的参数类型。在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类(泛型类)、接口(泛型接口),方法(泛型方法)中。
2.为什么要引入泛型?
-
在没有引入泛型之前,如果要实现参数的任意类型化,那么就只能通过Object来实现,目的是达到了,但是这样的可读性不太好,而且不知道具体类型,需要进行强制类型装换,有的类型不兼容,会导致类型转换异常ClassCastException
-
比如在将Object类型的实际值为String类型的变量,强转为Charset的时候,编译期不会报错,而在运行时就会抛出ClassCastException 这种很明显的异常应该在编译期就要进行发现的,不应该留到运行时再去处理。
-
java通过引入泛型机制,将这种隐患在编译期就进行处理,开发人员可以知道实际的类型,避免进行不确定的强转,这样提高了代码的可读性,灵活性。
3.使用泛型和未使用泛型对比
用一个我之前比较常犯的一个错误案例:之前使用List集合的时候,总是不理解后面跟的泛型是什么东西,导致我老是进行强制转换,类型对还好,如果类型不一致就抛出异常。
public static void main(String[] args)
// 未使用泛型
List list = new ArrayList();
list.add("123");
list.add(new Character('A'));
for (Object value:list)
String str = (String) value;
System.out.println(str);
以上代码,在编译期不会提示任何错误,但是在运行时就会抛出:
Exception in thread "main" java.lang.ClassCastException: java.lang.Character cannot be cast to java.lang.String
使用泛型:在编译期就解决这种比较低级的错误,明确类型,无需进行强转
public static void main(String[] args)
// 使用泛型
List<String> list = new ArrayList();
list.add("123");
list.add(new Character('A'));
for (Object value:list)
String str = (String) value;
System.out.println(str);
使用泛型后,指定类型,在编译期就会出现错误提示,并不会等到运行时才会抛出异常
4.泛型中的通配符
在使用泛型时经常会看到T、E、K、V这些通配符,它们代表着什么含义呢?
本质上它们都是通配符,并没有什么区别,换成A-Z之间的任何字母都可以。这是开发者们的一些约定
- T (type) 表示具体的一个java类型;
- K (key) 表示java中的一个key 对应一个value (如:HashMap)
- V (value)表示java中的一个value 对应一个key(如:HashMap)
- E (element) 代表Element;
5.泛型消除
java中的泛型不是真泛型,只是在编译期有效,到运行时,都统一的转化为了Object类型
如下代码:在编译期报错,但是我们跳过编译期就不会报错,使用java的反射机制即可,反射出来的对象都是经过编译期了的。
上图中的代码对应的字节码文件:
public static void main(java.lang.String[]);
Code:
0: new #2 / class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String hello
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;) [最终add的是Object类型]
16: pop
17: return
使用反射跳过泛型检测机制,这就是所谓的泛型在运行时类型擦除
public static void main(String[] args) throws Exception
List<Integer> list = new ArrayList<>();
Method method = list.getClass().getDeclaredMethod("add", Object.class);
method.invoke(list,"hello");
System.out.println(list.get(0));
// 正确输出 hello , 并且不抛出任何异常信息
6.泛型的定义与使用
6.1 泛型类
泛型类的声明和非泛型类的声明类似,只是在类名后面添加了类型参数声明部分。由尖括号 <泛型通配符> 分隔的类型参数部分跟在类名后面。它指定类型参数(也称为类型变量)T1,T2,…Tn。一般将泛型中的类名称为原型,而将<>指定的参数称为类型参数。
// T为任意标识,比如用T、E、K、V等表示泛型 也可以是A~Z,a~z,或者是其他字母的组合,但是一般见名知意比较好
class User<T>
// 泛化的成员变量,T的类型由外部指定
private T userInfo;
// 构造方法类型为T,T的类型由外部指定
public User(T userInfo)
this.userInfo = userInfo;
// 方法返回值类型为T,T的类型由外部指定
public T getInfo()
return userInfo;
public static void main(String[] args)
// 实例化泛型类时,需要指定T的具体类型,这里为String,如果不指定那么就是Object。
// 传入的实参类型需与泛型的类型参数类型相同,这里为String。
User<String> user = new User("小明");
String userInfo = user.getInfo();
System.out.println(userInfo);
当然也可以不使用泛型化,直接:User user = new User(“小明”) 这种方式不推荐, 但是既然设计为泛型类,那么就使用泛型化
6.2 泛型接口
泛型接口的声明与泛型类一致,泛型接口语法形式:多个泛型参数可以用逗号进行分割。
interface UserDao<T>
T selectById(String id);
int updateUserInfo(T user);
泛型接口有两种实现方式:子类明确声明泛型类型和子类不明确声明泛型类型。
子类明确泛型类型:
class UserDaoImpl implements UserDao<User>
@Override
public User selectById(String id)
return null;
@Override
public int updateUserInfo(User user)
return 0;
子类不明确泛型类型:
class UserDaoImpl implements UserDao
@Override
public Object selectById(String id)
return null;
@Override
public int updateUserInfo(Object user)
return 0;
6.3 泛型方法
泛型类是在实例化类时指明泛型的具体类型;泛型方法是在调用方法时指明泛型的具体类型。泛型方法可以是普通方法、静态方法、抽象方法、final修饰的方法以及构造方法。
泛型方法语法形式如下:
public <T> T method(T object)
尖括号内为类型参数列表,位于方法返回值T或void关键字之前。尖括号内定义的T,可以用在方法的任何地方,比如参数、方法内和返回值。
static<K, V> V method(Map<K,V> map)
Map<K, V> hashMap = new HashMap<>();
return map.get("");
需要注意的是,泛型方法与类是否是泛型无关。另外静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
**泛型方法和可变参数的配合使用:**如果有多个参数,可变参数需要放在方法参数的最后一个位置,可变参数位置的参数可以是不同的类型
public void batchDelete(int cache,T... userId)
T[] userIds = userId;
for (int i = 0; i < userId.length; i++)
System.out.println(userId[i]);
// 调用:new User().batchDelete(1,"1","2","3",4);
小总结:如果能使用泛型方法尽量使用泛型方法,这样能将泛型所需到最需要的范围内。如果使用泛型类,则整个类都进行了泛化处理。
6.4 通配符
无界通配符:?
类型通配符一般是使用?代替具体的类型实参。当操作类型时不需要使用类型的具体功能时,只使用Object类中的功能,那么可以用?通配符来表未知类型。例如List<?>在逻辑上是 List<String>
、List<User>
等…所有List<具体类型实参>的父类。
// 方法调用实参传递过来的时候,只能是String类型,不能是别的类型
public void methodOne(List<String> args)
List<String> list1 = args;
// 方法调用实参传递过来的可以是任意类型,实参传递的是什么类型就是什么类型,这种方式比较灵活
public void methodTwo(List<?> args)
List<?> list= args;
// 方法调用实参传递过来的是Object类型,只要是Object类型的子类即可,不过这样容易强转
public void methodThree(List<Object> args)
List<?> list= args;
无界通配符,有两种应用场景:
- 使用Object类,可以是Object的任意子类
- 使用 ? 来泛型化具体参数
6.5 范围限制
既然有 无界通配符,那也就有 有界通配符,也就是不在是任意类型,而是限制你的范围,就比如说泛型参数必须是某个类的子类
上界通配符: 泛型参数必须是某个类的子类或实现类
public static void main(String[] args)
User<String> user1 = new User<>("hello");
User<Float> user2 = new User<>(1.5F);
User<Integer> user3 = new User<>(10);
//user1这个参数会报错,因为有规定必须是Number类的一个子类,String并不是Number的一个子类
method(user1);
method(user2);
method(user3);
public static void method(User<? extends Number> arg)
System.out.println(arg);
**下界通配符:**泛型参数必须是某个类的父类 或 某个类实现了该接口的类型
public static void main(String[] args)
User<Collection> user1 = new User<>();
User<Iterable> user2 = new User<>();
User<Integer> user3 = new User<>(10);
// List继承自Collection接口编译通过
method(user1);
// Collection继承自Iterable编译通过
method(user2);
// 此处编译通过,因为Integer并不是List父类型
method(user3);
public static void method(User<? super List> arg)
System.out.println(arg);
public static void main(String[] args)
User<Collection> user1 = new User<>();
User<Iterable> user2 = new User<>();
User<Integer> user3 = new User<>(10);
// List继承自Collection接口编译通过
method(user1);
// Collection继承自Iterable编译通过
method(user2);
// 此处编译通过,因为Integer并不是List父类型
method(user3);
public static void method(User<? super List> arg)
System.out.println(arg);
注意:基本数据类型不能使用泛型。如果要使用请使用他们对应的包装类型:int,long… 无法用于泛型,在使用的过程中需要通过它们的包装类 Integer, Long,…
6.6 泛型小案例:
该案例是JDBC中的一个通用的查询方法,让代码变的更加的灵活
/**
* 通用查询
*
* @param clazz 需要查询的类型 类名.Class
* @param sql 需要查询的sql语句
* @param args 占位符 可变参数,可以不需要 不传就是查询全部
* @param <T> 使用泛型机制,让这个查询使用于所有的实体类
* @return 如果查询到就返回一该对象的list集合 ,没有查询到返回null
*/
public <T> List<T> QueryForList(Class<T> clazz, String sql, Object... args)
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++)
preparedStatement.setObject(i + 1, args[i]);
resultSet = preparedStatement.executeQuery();
// 获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData rsmd = resultSet.getMetaData();
// 通过ResultSetMetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list = new ArrayList<T>();
while (resultSet.next())
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
for (int i = 0; i < columnCount; i++)
// 获取列值
Object columValue = resultSet.getObject(i + 1);
// 获取每个列的列名
String columnLabel = rsmd.getColumnLabel(i + 1);
// 给t对象指定的columnName属性,赋值为columValue:通过反射
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
//给指定对象属性名赋值
field.set(t, columValue);
list.add(t);
//返回查询结果集
return list;
catch (Exception e)
e.printStackTrace();
finally
//释放数据库连接资源
JDBCUtils.closeStatement(connection,preparedStatement,resultSet);
return null;
到这里泛型的内容就差不多结束了,希望对看到此文章的小伙伴有所帮助。
以上是关于花几分钟把java泛型吃透的主要内容,如果未能解决你的问题,请参考以下文章