阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

JAVA中的泛型

90次阅读
没有评论

共计 6530 个字符,预计需要花费 17 分钟才能阅读完成。

1、泛型概述

1.1、泛型由来

先来看一个案例:

import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class FanXingDemo {public static void main(String[] args) {Collection arrayList = new ArrayList(); arrayList.add("java"); arrayList.add("php"); arrayList.add(100); Iterator i=arrayList.iterator(); while(i.hasNext()){String s=(String)(i.next()); System.out.println(s); } } }

运行结果:

JAVA 中的泛型

程序在运行时发生了问题java.lang.ClassCastException。为什么会发生类型转换异常呢?我们来分析下:由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastException。怎么来解决这个问题呢?

Collection 虽然可以存储各种对象,但实际上通常 Collection 只存储同一类型对象。例如都是存储字符串对象。因此在 JDK5 之后,新增了 泛型 (Generic) 语法,让你在设计 API 时可以指定类或方法支持泛型,这样我们使用 API 的时候也变得更为简洁,并得到了编译时期的语法检查。

泛型:可以在类或方法中预支地使用未知的类型。

1.2、使用泛型的好处

泛型带来了哪些好处呢?

将运行时期的 ClassCastException,转移到了编译时期变成了编译失败。

避免了类型强转的麻烦。

import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class FanXingDemo01 {public static void main(String[] args) {Collection<String> arrayList = new ArrayList<String>(); arrayList.add("java"); arrayList.add("php"); // 当集合明确类型后,存放类型不一致就会编译报错 //arrayList.add(100); // 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型 Iterator<String> i=arrayList.iterator(); while(i.hasNext()){// 当使用 Iterator<String> 控制元素类型后,就不需要强转了。获取到的元素直接就是 String 类型 String s=i.next(); System.out.println(s); } } }

2、泛型定义及使用

我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。

泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。

2.1、泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

2.1.1、格式

class 类名称 < 泛型标识:可以随便写任意标识号,标识指定的泛型的类型 >{private 泛型标识 /*(成员变量类型)*/ var; ..... } }

2.1.2、案例

public class Generic<T>{//key 这个成员变量的类型为 T,T 的类型由外部指定 private T key; public Generic(T key) {// 泛型构造方法形参 key 的类型也为 T,T 的类型由外部指定 this.key = key; } public T getKey(){// 泛型方法 getKey 的返回值类型为 T,T 的类型由外部指定 return key; } }

注意:

此处 T 可以随便写为任意标识,常见的如 T、E、K、V 等形式的参数常用于表示泛型

在实例化泛型类时,必须指定 T 的具体类型

public class FanXingDemo02{public static void main(String args[]){// 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型 // 传入的实参类型需与泛型的类型参数类型相同,即为 Integer. Generic<Integer> genericInteger = new Generic<Integer>(123456); // 传入的实参类型需与泛型的类型参数类型相同,即为 String. Generic<String> genericString = new Generic<String>("key_vlaue"); System.out.println("泛型测试 : key is" + genericInteger.getKey()); System.out.println("泛型测试 : key is" + genericString.getKey()); } }

2.2、泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

2.2.1、格式

interface 类名称 < 泛型标识:可以随便写任意标识号,标识指定的泛型的类型 >{public T next(); }

2.2.2、案例

// 定义一个泛型接口 public interface Generator<T> {public T next(); }
/** * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中 * 即:class FruitGenerator<T> implements Generator<T>{ * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class" */ class FruitGenerator<T> implements Generator<T>{@Override public T next() {return null; } }
/** * 传入泛型实参时:* 定义一个生产器实现这个接口, 虽然我们只创建了一个泛型接口 Generator<T> * 但是我们可以为 T 传入无数个实参,形成无数种类型的 Generator 接口。* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型 * 即:Generator<T>,public T next(); 中的的 T 都要替换成传入的 String 类型。*/ public class FruitGenerator implements Generator<String> {private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() {Random rand = new Random(); return fruits[rand.nextInt(3)]; } }

2.3、泛型方法

在 java 中, 泛型类的定义非常简单,但是泛型方法就比较复杂了。

尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。

2.3.1、格式

修饰符 < 代表泛型的变量 > 返回值类型 方法名(参数){ }

2.3.2、案例

// 这个类是个泛型类,在上面已经介绍过 public class Generic<T> {private T key; public Generic(T key) {this.key = key; } // 我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。 // 这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。 // 所以在这个方法中才可以继续使用 T 这个泛型。 public T getKey() {return key; } }
public class FanXingDemo03 {/** * 这才是一个真正的泛型方法。* 首先在 public 与返回值之间的 <T> 必不可少,这表明这是一个泛型方法,并且声明了一个泛型 T * 这个 T 可以出现在这个泛型方法的任意位置. * 泛型的数量也可以为任意多个 * 如:public <T,K> K showKeyName(Generic<T> container){ * ... * } */ public <T> T showKeyName(Generic<T> container) {System.out.println("container key :" + container.getKey()); // 当然这个例子举的不太合适,只是为了说明泛型方法的特性。 T test = container.getKey(); return test; } public static void main(String[] args) {}}
public class FanXingDemo04 {public static <T> void out(T t) {System.out.println(t); } public static void main(String[] args) {out("findingsea"); out(123); out(11.11); out(true); } } public class FanXingDemo04 {public static <T> void out(T... args) {for (T t : args) {System.out.println(t); } } public static void main(String[] args) {out("findingsea", 123, 11.11, true); } }
class Fruit {@Override public String toString() {return "fruit"; } } class Apple extends Fruit {@Override public String toString() {return "apple"; } } class Person {@Override public String toString() {return "Person"; } } class GenerateTest<T> {public void show_1(T t) {System.out.println(t.toString()); } // 在泛型类中声明了一个泛型方法,使用泛型 E,这种泛型 E 可以为任意类型。可以类型与 T 相同,也可以不同。 // 由于泛型方法在声明的时候会声明泛型 <E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。 public <E> void show_3(E t) {System.out.println(t.toString()); } // 在泛型类中声明了一个泛型方法,使用泛型 T,注意这个 T 是一种全新的类型,可以与泛型类中声明的 T 不是同一种类型。 public <T> void show_2(T t) {System.out.println(t.toString()); } } public class FanXingDemo05 {public static void main(String[] args) {Apple apple = new Apple(); Person person = new Person(); GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>(); //apple 是 Fruit 的子类,所以这里可以 generateTest.show_1(apple); // 编译器会报错,因为泛型类型实参指定的是 Fruit,而传入的实参类是 Person //generateTest.show_1(person); // 使用这两个方法都可以成功 generateTest.show_2(apple); generateTest.show_2(person); // 使用这两个方法也都可以成功 generateTest.show_3(apple); generateTest.show_3(person); } }

3、泛型通配符

可以用 <T>、<K,V>、<T extends Number> 等进行泛型的声明。其中,<T extends Number> 的声明方式限定了 T 的范围,T 只能为 Number 的子类。

3.1、通配符

E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类)K – Key(键)V – Value(值)N – Number(数值类型)?– 表示不确定的 java 类型(无限制通配符类型)Object – 是所有类的根类,任何类的对象都可以设置给该 Object 引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型 T、E 等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。

3.2、通配符基本使用

泛型的通配符: 不知道使用什么类型来接收的时候, 此时可以使用?,? 表示未知通配符。

此时只能接受数据, 不能往该集合中存储数据。

import java.util.ArrayList; import java.util.Collection; public class FanXingDemo06 {public static void main(String[] args) {Collection<Integer> list1 = new ArrayList<Integer>(); getElement(list1); Collection<String> list2 = new ArrayList<String>(); getElement(list2); } public static void getElement(Collection<?> coll){}}

3.3、受限泛型

之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在 JAVA 的泛型中可以指定一个泛型的 上限 下限

泛型的上限

格式:类型名称 <? extends 类 > 对象名称

意义:只能接收该类型及其子类

泛型的下限

格式:类型名称 <? super 类 > 对象名称

意义:只能接收该类型及其父类型

比如:现已知 Object 类,String 类,Number 类,Integer 类,其中 Number 是 Integer 的父类

import java.util.ArrayList; import java.util.Collection; public class FanXingDemo07{public static void main(String[] args) {Collection<Integer> list1 = new ArrayList<Integer>(); Collection<String> list2 = new ArrayList<String>(); Collection<Number> list3 = new ArrayList<Number>(); Collection<Object> list4 = new ArrayList<Object>(); getElement1(list1); //getElement1(list2);// 报错 getElement1(list3); //getElement1(list4);// 报错 //getElement2(list1);// 报错 //getElement2(list2);// 报错 getElement2(list3); getElement2(list4); } // 泛型的上限:此时的泛型?,必须是 Number 类型或者 Number 类型的子类 public static void getElement1(Collection<? extends Number> coll){} // 泛型的下限:此时的泛型?,必须是 Number 类型或者 Number 类型的父类 public static void getElement2(Collection<? super Number> coll){}}

正文完
星哥说事-微信公众号
post-qrcode
 
星锅
版权声明:本站原创文章,由 星锅 2022-06-06发表,共计6530字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中