一、泛型
什么是泛型
Java 泛型(generics)是 JDK 5
中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型
,也就是说所操作的数据类型被指定为一个参数。
为什么用泛型
提高的代码的复用性,减少了数据的类型转换(泛型提供了类型检查),同时保证了类型安全。
减少类型转换?如,使用Comparable比较时每次都需要类型强转
泛型参数类型
规则
- 使用简练的名字作为类型形参的名字,最好为单个的大写字母,比如 T ;
- 如果一个泛型类有泛型方法,对于它们的类型形参来说,应避免使用相同的名字;
- 泛型的类型实参只能是类类型,不能是基本数据类型。
常见的参数类型起名
- K 键,比如映射的键 key的类型
- V 值,比如Map的值 value类型
- E 元素,比如Set<E> Element表示元素,元素的类型
- T 泛型,Type的意思
注意:泛型的类型名字是可以随便写的,上面的K,V,E,T只是我们常用的用法,有一定含义,我们对应的把T换成HAHAHA,也算是可以的。
如何了解泛型
我们可以大概从下面几点来开展文章。
- 泛型方法
- 泛型类
- 泛型接口
- 泛型的通配符
二、泛型方法
泛型方法定义格式
修饰符 <泛型参数列表> 返回值类型 方法名 (参数列表) {
……
}
.
.
我们先来看一个简单的泛型方法
private static <T> void inputContent(T t){
System.out.println("打印传入的数据:"+t);
}
简单规则
- 泛型返回返回值(void也需要)之前需要有
泛型类型参数的声明
,由尖括号包括,比如<T>
(也可以理解为带有返回值前带有泛型类型的都是参数方法 ) - 形参参数可以不是泛型参数
code1 泛型方法用法参考
public class TestClass {
public static void main(String[] args) {
inputContent(666);
inputContent("哈哈哈");
new Num("张三").say();
Num num = doClass(new Num("李四"));
num.say();
}
private static <T> void inputContent(T t){
System.out.println("打印传入的数据:"+t);
}
private static <T> T doClass(T t){
return t;
}
}
class Num{
private String name;
public Num(String str){
this.name = str;
}
public void say(){
System.out.println(name +" 调用了say方法");
}
}
.
.
输出
打印传入的数据:666
打印传入的数据:哈哈哈
张三 调用了say方法
李四 调用了say方法
.
.
code2 泛型方法,多参数类型以类型名
public class TestClass {
public static void main(String[] args) {
Map map = getAMap("张三", 18);
Set<String> set = map.keySet();
for(String key : set){
System.out.println("key "+key);
System.out.println("value "+map.get(key));
}
}
private static <HAHA extends String,XIXI> Map<String,XIXI> getAMap(HAHA haha,XIXI xixi){
Map map = new HashMap<HAHA,XIXI>();
map.put(haha, xixi);
return map;
}
}
输出
key 张三
value 18
从这个例子中,我们看到,泛型方法返回值前的<>里面的参数可以有多个,而且,参数类型名称我们基本可以随便起,不局限于一个字母,比如起名为HAHA,需要注意的是,
- 我们最好起有默认含义的,比如T,K,E,V
- <>里面的泛型参数列表已经限定了参数参数的类型,方法后面的形参的泛型参数类型只能在前面声明的类型中选择。
至于extends是什么怎么用,后面会涉及。
三、泛型类
格式
class 类名<泛型类型1,泛型类型2……>{
……
}
简单规则
- 在类名后面带上<>,在<>里面声明泛型参数列表
code3 泛型方法示例
public class TestClass {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("张三"));
System.out.println("整型值为 :"+integerBox.get());
System.out.println("字符串为 :"+ stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
.
.
输出
整型值为 :10
字符串为 :张三
如上 Box类就是泛型类
.
.
.
泛型接口
泛型接口,就是在接口后面跟着一对<>,在<>里面放上泛型参数列表。
在接口里面的抽象方法,我们就可以结合接口定义上的泛型参数列表做一些事情。
当然,方法可以使用泛型参数类型必须是接口的声明的泛型参数列表以内的。
code4 泛型接口的使用
public class TestClass {
public static void main(String[] args) {
SayClass class1 = new SayClass();
System.out.println(class1.doSomeThing());
}
}
class SayClass implements Generator<String>{
@Override
public String doSomeThing() {
String str = "被指定的类型为String";
return str;
}
}
interface Generator<T> {
// 接口的里的抽象方法繁殖可以不是T
// 但是一般这里我们指定为T才有一定意义,不然不就白泛型了
public T doSomeThing();
}
输出
被指定的类型为String
.
.
.
四、泛型的限定符/通配符
四.1、泛型限定符的种类
- <? extends Type> 子类限定/上界通配符
- <? super Type> 父类限定 / 下界通配符
- <?> 无限定
Person<?> 和 Person<? extends Object> 等同.
为了演示,我们弄几个类,一个父类Water,Water下有SofdDrink(汽水)和Juice(果汁),Juice下有分为OrangeJuice和AppleJuice类。
image.png四.2、子类限定 extends
<? extends Type>
子类限定,有上界,代表传入实参必须是指定类型的子类
- 1、只读,不可写
- 2、对于<? extends T>,那么限定传入类型只能 T类或者T的子类
- 3、<T extends Runnable & Serializable>如果给T限定多个类型,则需要使用符号&,比如
<T extends Runnable & Serializable>
code5 泛型的子类限定示例
import java.util.ArrayList;
import java.util.List;
public class TestClass{
public static void main(String[] args) {
Water water = new Water("纯净水");
SodaDrink sodaDrink = new SodaDrink("可乐");
Juice juice = new Juice("果汁");
OrangeJuice orangeJuice = new OrangeJuice("果粒橙");
AppleJuice appleJuice =new AppleJuice("牛顿牌果汁");
ArrayList<OrangeJuice> tempList = new ArrayList<OrangeJuice>();
tempList.add(orangeJuice);
// ======
// 证明1、<? extends T>,那么限定传入类型只能 T类或者T的子类
ArrayList<? extends Juice> arrayList1 = new ArrayList<Juice>();
//ArrayList<? extends Juice> arrayList2 = new ArrayList<Water>(); // 编译报错 Water不是Juice也不是Juice的子类
ArrayList<? extends Juice> arrayList3 = new ArrayList<OrangeJuice>();
// 证明2、ArrayList<? extends Juice> arrayList1,是只读的,不可写入
// 我们无法插入任何数据到arrayList1中,因为我们无法确定插入的到底是哪一种类型,是Juice? 还是OrangeJuice? 还是AppleJuice?
// 泛型extends是不可写的,非要写也只能是写入null
//arrayList1.add(orangeJuice); //
// 泛型extends是可读的
ArrayList<? extends Juice> arrayList4 = tempList;
Juice juice23 =arrayList4.get(0);
juice23.sayName();
}
}
// 商店
class Shop<T>{
private T t;
public Shop(T t){
this.t= t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
class Water {
private String name;
public Water(String name) {
this.name = name;
}
public void sayName() {
System.out.println("name===: "+name);
}
public String getName(){
return name;
}
}
class SodaDrink extends Water{
public SodaDrink(String name) {
super(name);
}
public void sayName() {
System.out.println("汽水/苏打水===: "+getName());
}
}
class Juice extends Water{
public Juice(String name) {
super(name);
}
public void sayName() {
System.out.println("果汁===: "+getName());
}
}
class OrangeJuice extends Juice{
public OrangeJuice(String name) {
super(name);
}
public void sayName() {
System.out.println("橙汁===: "+getName());
}
}
class AppleJuice extends Juice{
public AppleJuice(String name) {
super(name);
}
public void sayName() {
System.out.println("苹果汁===: "+getName());
}
}
.
打印
橙汁===: 果粒橙
如上,我们知道,对于作了子类限定的泛型
1、实参只能传入限定类型的子类或者其子类
2、是只读的,不能调用set,只能set(null)
? extends T 限定了类型为T和T的子类型
ArrayList<? extends Juice> arrayList1 = new ArrayList<Juice>();
ArrayList<? extends Juice> arrayList2 = new ArrayList<Water>(); // 编译报错 Water不是Juice也不是Juice的子类
ArrayList<? extends Juice> arrayList3 = new ArrayList<OrangeJuice>();
对于上面的代码,arrayList1和arrayList3是合法。arrayList2会编译报错,因为我们限定了传入的是Juice或者Juice的子类,但是arrayList2传入的是Juice的父类Water。
泛型extends的读和写
读
可读并且只能读取到限定的类型(无法读取到子类型)
对于ArrayList<? extends Juice> arrayList1
- 可以从 arrayList1 中读取到 Juice 对象, 因为 arrayList1 中包含的元素是 Juice 类型或 Juice 的子类型.
- 无法从 arrayList1 中读取到 OrangeJuice 类型, 因为 arrayList1 中可能保存的是 AppleJuice 类型.
.
.
写
不可写,无法写入除了null外任何元素,包括Juice类型 或者 Juice类型的子类
对于ArrayList<? extends Juice> arrayList1
- 不能添加 Juice 到 arrayList1 中, 因为 numberArray 有可能是List<AppleJuice> 类型
- 不能添加 OrangeJuice 到 numberArray 中, 因为 numberArray 有可能是 List<AppleJuice> 类型
- 不能添加 AppleJuice 到 numberArray 中, 因为 numberArray 有可能是 List<OrangeJuice> 类型
四.2、父类限定 super
- 可写,不可读。
- 对于<? super T>,那么限定传入类型只能 T类或者T的父类
code6 泛型的父类限定示例
public class TestClass{
public static void main(String[] args) {
Water water = new Water("纯净水");
SodaDrink sodaDrink = new SodaDrink("可乐");
Juice juice = new Juice("果汁");
OrangeJuice orangeJuice = new OrangeJuice("果粒橙");
AppleJuice appleJuice =new AppleJuice("牛顿牌果汁");
// ======
// 证明1、<? super T>,那么限定传入类型只能 T类或者T的父类
ArrayList<? super Juice> arrayList1 = new ArrayList<Juice>();
ArrayList<? super Juice> arrayList2 = new ArrayList<Water>();
//ArrayList<? super Juice> arrayList3 = new ArrayList<OrangeJuice>();// 编译报错 OrangeJuice不是Juice也不是Juice的子类
// 证明2、ArrayList<? extends Juice> arrayList1,是可写,不可读。
// 关于写,我们只能写入 Juice或者Juice的子类,而不能写入Juice的父类
arrayList1.add(juice);
arrayList1.add(orangeJuice);
//arrayList1.add(water); // 编译报错
arrayList2.add(juice);
//arrayList2.add(water); // 编译报错
//关于读,我们无法读取到类型为Juice的数据,或者Juice的值类型,因为存入的可能是Juice,也可能是Juice的子类
// 唯一可以确定的是,读取的出来的肯定是个Object,但是如果你非常强转也是可以的
Object obj = arrayList1.get(0); // 只能确定读出出来的是Object
Juice jui = (Juice) arrayList1.get(1);
jui.sayName();
}
}
// 商店
class Shop<T>{
private T t;
public Shop(T t){
this.t= t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
class Water {
private String name;
public Water(String name) {
this.name = name;
}
public void sayName() {
System.out.println("name===: "+name);
}
public String getName(){
return name;
}
}
class SodaDrink extends Water{
public SodaDrink(String name) {
super(name);
}
public void sayName() {
System.out.println("汽水/苏打水===: "+getName());
}
}
class Juice extends Water{
public Juice(String name) {
super(name);
}
public void sayName() {
System.out.println("果汁===: "+getName());
}
}
class OrangeJuice extends Juice{
public OrangeJuice(String name) {
super(name);
}
public void sayName() {
System.out.println("橙汁===: "+getName());
}
}
class AppleJuice extends Juice{
public AppleJuice(String name) {
super(name);
}
public void sayName() {
System.out.println("苹果汁===: "+getName());
}
}
打印输出
橙汁===: 果粒橙
? super T 限定了类型为T和T的父类型
ArrayList<? super Juice> arrayList1 = new ArrayList<Juice>();
ArrayList<? super Juice> arrayList2 = new ArrayList<Water>();
//ArrayList<? super Juice> arrayList3 = new ArrayList<OrangeJuice>();// 编译报错 OrangeJuice不是Juice也不是Juice的子类
这里很清楚的说明,我们限定了传入类型只能是限定类型的或者其父类。
泛型super的读和写
读
对于 ArrayList<? super Juice> arrayList1
- 无法确定读出来的是Juice或者OrangeJuice或者AppleJuice。
- 可以确定的是,读出来肯定是一个Object(如果你非要强转也行)。
.
.
写
- 写入的必须是T类型的或者T的子类型
.
.
.
四.3、泛型限定extends和super的使用原则 PECS
PECE 原则: Producer Extends, Consumer Super
因为extends,可读不可写;super可写不可读
所以:
1、如果我们的操作基本上是只读的,那么用extends
2、如果我们的操作基本上是只写的,那么用super
** 小结:extends和super **
- 阿敏说:extends
比如 ArrayList<? extends Juice> arrayList1,肯定不可以写入,我们都不知道你放进来的是苹果汁还是橙汁,然后别人到会来取得时候,已经限定了要一杯果汁,那我机器人这么笨怎么知道给一杯什么,所以你不要给我放进来了,因为我也无法给出去啊,多浪费啊,少年你自己喝了吧。
你这么笨机器人,你可以取,但是不不能存,没有存哪有取,要你何用?
少年你这么说就不对了,你可以不要一个个添加嘛,你先把所有的符合我要求的数据批量准备好,比如 ArrayList<Juice> temp1 或者 ArrayList<Juice> temp2 ,数据你自己填充好,然后直接给我,这样我给别人的时候也好给啊,我这么聪明的机器人一定不会弄错啦。
ArrayList<Juice> tempList1 = new ArrayList<Juice>();
tempList1.add(new Juice("果汁1"));
tempList1.add(new Juice("果汁2"));
ArrayList<? extends Juice> juiceList1 = tempList1;
juiceList1.get(0).sayName();
juiceList1.get(1).sayName();
// =======
ArrayList<OrangeJuice> tempList2 = new ArrayList<OrangeJuice>();
tempList2.add(new OrangeJuice("果粒橙1"));
tempList2.add(new OrangeJuice("果粒橙2"));
ArrayList<? extends Juice> juiceList2 = tempList2;
juiceList2.get(0).sayName();
juiceList2.get(1).sayName();
输出
果汁===: 果汁1
果汁===: 果汁2
橙汁===: 果粒橙1
橙汁===: 果粒橙2
.
- 阿敏说:super存取疑惑
.
比如 ArrayList<? super Juice> arrayList2,可以写入,为什么,你放进来的可以是果汁,可以是苹果汁,可以是橙汁,都没问题。但是取是万万不能的,里面存放辣么多不同的饮料,或者可能不同的饮料,你说要果汁,我是机器人那么笨,我拿什么给你。
参考:
JAVA泛型•通配符限定
网友评论