Java泛型定义与用法实例详解
作者:喜欢特别冷的冬天下着雪 发布时间:2023-11-25 11:50:28
本文实例讲述了Java泛型定义与用法。分享给大家供大家参考,具体如下:
1. 泛型的由来
先看如下代码:
import java.util.List;
import java.util.ArrayList;
public class TestGeneric {
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add("1");
list.add(new Object());
System.out.println(list);
// 取值
Integer var1 = (Integer) list.get(0);
String var2 = (String) list.get(1);
Object var3 = list.get(2);
System.out.println(var1 + " " + var2 + " " + var3);
}
}
运行结果:
[1, 1, java.lang.Object@1db9742]
1 1 java.lang.Object@1db9742
这段代码很简单,将整形、字符串、对象放进list集合中,然后逐一取出。可以看出,由于List接口在定义时并不知道元素的类型,因此默认为Object,即任意类型元素进入list集合后都会自动装箱。而取值的过程更为复杂,所有取得的值都是装箱后的Object对象,必须得知道每一个元素的初始类型才能拆箱。一般使用集合的时候,集合的元素往往都是具有共同特征的,比如同属于一类的----那么,如果一开始限定了list集合元素的类型,那么就可避免上述不规范操作。代码如下,
import java.util.List;
import java.util.ArrayList;
public class TestGeneric {
@SuppressWarnings("unused")
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
// list.add(1);//报错
// list.add(new Object());//报错
list.add("1");
// 取值
String var1 = list.get(0);// 无需转换
}
}
如此一来,便有了泛型集合的说法。实际上,查阅List接口的Api会发现,List接口正是泛型接口,它可以接受一个类型参数E,若不传递参数,则默认是Object类型。
2. 泛型类型的继承关系
有如下功能的代码,实现打印任意集合的元素:
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric{
//打印任意集合元素
public void print(Collection<Object> c){
System.out.println(c);
}
public static void main(String[] args){
List<String> list=new ArrayList<String>();
new TestGeneric().print(list);
}
}
输出:
TestGeneric.java:11: 无法将 TestGeneric 中的 print(java.util.Collection<java.lang.Object>) 应用于 (java.util.List<java.lang.String>)
new TestGeneric().print(list);
^
1 错误
很明显,意思就是传递的参数类型不匹配。难道String不是继承自Object的吗?没错,String是继承自Object的,但是List<String>
与List<Object>
是截然不同的两个类型,两者之间没有任何继承关系。那如果真的要实现上面的功能,该如何呢?
2.1 类型通配符
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric {
// 打印任意集合元素
public void print(Collection<?> c) {
System.out.println(c);
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
new TestGeneric().print(list);
}
}
程序正常执行,这里的?表示一个未知类型,这个未知类型与Object不同,List<?>代表了所有的List<类型>的父类。
2.2 泛型方法
不只有通配符可以解决泛型继承的问题,若将上面的方法定义为泛型方法也具有同样的效果:
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric {
// 打印任意集合元素
public <T> void print(Collection<T> c) {
System.out.println(c);
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
new TestGeneric().print(list);
}
}
泛型方法的定义形式如下,
修饰符 <T,E> 返回值 方法名(形参)
其中<T,E>在修饰符的后面做为类型定义,为方法指明形参中需要用到的T,E类型是来自哪里。既然泛型方法和类型通配符都可以实现泛型中的继承,那么有什么区别?
2.3 泛型方法和通配符的区别
看如下代码:
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric {
// 打印任意集合元素
public <E, T extends E> void print(Collection<T> c1, Collection<E> c2) {
System.out.println(c1);
System.out.println(c2);
}
public static void main(String[] args) {
List<Father> list1 = new ArrayList<Father>();
List<Father> list2 = new ArrayList<Father>();
new TestGeneric().print(list1, list2);// 传2个father类型
List<Child> list3 = new ArrayList<Child>();
List<Father> list4 = new ArrayList<Father>();
new TestGeneric().print(list3, list4);// T为child,E为father
List<Father> list5 = new ArrayList<Father>();
List<Child> list6 = new ArrayList<Child>();
new TestGeneric().print(list5, list6);// T为father,E为child,报错
}
}
class Father {
}
class Child extends Father {
}
class Other {
}
上述泛型方法在定义T,E时已经指明了关系:T是E的子类,所以在传递参数的时候,T要么是E的子类,要么就是E本身,所以在传递关系不小心变为E exends T时,在第三次调用方法时报错了。而如果把上述代码换成?通配符的话,则不具有如此强的限定关系。
总之,泛型方法和?通配符都可以实现未知类型的继承,但是泛型方法主要强调多个未知类型之间的依赖关系。如果只是单纯用作成为一个通用类型的父类这一功能的话,两者都可以实现,反而?通配符较为简洁明了。
2.4 泛型参数上、下限的注意
看如下代码:
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric {
// 复制集合并返回原始集合的最后一个元素
public <T> T copy(Collection<T> des, Collection<? extends T> src) {
T lastElement = null;
for (T t : src) {
lastElement = t;
des.add(t);
}
return lastElement;
}
public static void main(String[] args) {
List<Number> des = new ArrayList<Number>();
List<Integer> src = new ArrayList<Integer>();
src.add(new Integer(1));
Integer lastElement = new TestGeneric().copy(des, src);//
System.out.println(lastElement.getClass());
}
}
输出:
TestGeneric.java:18: 不兼容的类型
找到: java.lang.Number
需要: java.lang.Integer
Integer lastElement= new TestGeneric().copy(des,src);//
^
1 错误
当调用完copy方法后,系统比对发现T类型为Number,?类型为Integer。所以函数返回的T类型是Number了,所以根本不兼容Integer。要修改上面的代码,有俩个办法,
方法1:
改为
Number lastElement=new TestGeneric().copy(des,src);
分析代码可以得出,?为T的子类,在方法中T=lastElement
这句表现为多态,虽然返回的是T类型,但是多态的表现为?类型,即Interger类型,调用lastElement.getClass()
也可发现返回的是java.lang.Integer类型,说明此处编译类型为T类型,实际运行类型为?类型。这就好比如下多态转换,
Father f=new Child();
Child c=f;//此处一定报错,类型不兼容
虽然f的多态表现为子类Child,但是上面一句连语法检测都过不了。这也就是为什么上面Integer不能兼容Number的原因了。
方法2:
改为
public <T> T copy(Collection<? super T> des,Collection<T> src)
这样一来,?类型变为了父类,T类型变为了子类,于是在方法中返回的T类型对象,即lastElement就不具有多态性了。泛型中的上下限是很有学问的,每次看源码时都会琢磨很久,但还是会在浩瀚的接口+泛型的设计中昏迷,这种设计真的完全是为了突出面向对象的特性,以后慢慢琢磨吧。
从这也再次可以看出?通配符在处理具有依赖关系的泛型方法中,显得过于灵活而会导致一些潜在的隐患。
希望本文所述对大家java程序设计有所帮助。
来源:https://blog.csdn.net/kkkkkxiaofei/article/details/18262049
猜你喜欢
- 本文实例讲述了Android基于SoftReference缓存图片的方法。分享给大家供大家参考,具体如下:Java中的SoftReferen
- 1、通过查找API文档:2、Map.Entry是一个接口,所以不能直接实例化。3、Map.entrySet( )返回的是一个collecti
- 可以给已有实体类动态的添加字段并返回新的实体对象,不影响原来的实体对象结构。添加依赖<dependency> &n
- Swagger是一款遵循 Restful 风格的接口文档开发神器,支持基于 API 自动生成接口文档,接口文档始终与 API 保持同步,不再
- 前言gps定位服务的学习是这段时间gps课程的学习内容,之前老师一直在将概念,今天终于是实践课(其实就是给了一个案例,让自己照着敲).不过在
- 现在提起Android开发工具,大多人第一个想到的肯定是Android Studio。谷歌专门为Android开发者推出的这款IDE,以其强
- goto在Java中是一个保留字,但在语言中并没有用到它;Java没有goto。但是,Java也能完成一些类似于跳转的操作,主要是依靠:标签
- malloc的全称是memory allocation,中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的
- [LeetCode] 9. Palindrome Number 验证回文数字Determine whether an integer is
- 自定义 webflux 容器配置配置代码@Componentpublic class ContainerConfig extends Rea
- 在《Spring Boot Hello World》中介绍了一个简单的spring boot例子,体验了spring boot中的诸多特性,
- 使用RateLimiter通过AOP方式进行限流1、引入依赖<!-- guava 限流 --><dependency>
- 只能输入数字:"^[0-9]*$"。只能输入n位的数字:"^\d{n}$"。只能输入至少n位的数字:
- 一. spring配置文件:application.xml<?xml version="1.0" encoding
- 一、新时间日期API常用、重要对象介绍ZoneId: 时区ID,用来确定Instant和LocalDateTime互相转换的规则Instan
- BitArray的基础可以看菜鸟编程BitArray 类管理一个紧凑型的位值数组,它使用布尔值来表示,其中 true 表示位是开启的(1),
- 1、引入依赖<dependency> <groupId>org.apache.pdfbox</gr
- 免责声明:本教程所有资源均来源于网络;仅用于学习交流,请勿用于任何商业行为;如需要,请使用正版授权;侵权联删。推荐最新 IntelliJ I
- “Java is still not dead—and people are starting to figure that out.”本教
- 代码如下import java.util.concurrent.Callable;import java.util.concurrent.E