泛型
1.引入
情景模式描述,假设完成一个学生的成绩的情况:
- 整数: math=80,english=70
- 小数: math=85.6,englisth=77.8
- 字符串: math="66分",english="90.5分"
那么我们应该如何处理呢?我们需要设计的成员变量可以接收不同的数据类型,我们第一想到的是Object类型
定义Student类,使用Object类型
- package com.shxt.demo01;
-
- public class Student {
-
- private Object math;
- private Object english;
-
- public Student(Object math, Object english) {
- this.math = math;
- this.english = english;
- }
-
- public Object getMath() {
- return math;
- }
-
- public void setMath(Object math) {
- this.math = math;
- }
-
- public Object getEnglish() {
- return english;
- }
-
- public void setEnglish(Object english) {
- this.english = english;
- }
- }
-
- 复制代码
示例1:设置整型, int → 自动装箱(Integer) → 对象上转型Object
- package com.shxt.demo01;
-
- public class Demo01 {
- public static void main(String[] args) {
- Student student = new Student(80,70); //实例化对象并且对数据初始化
- //请注意这种强制只有在JDK7以后才能使用
- int math = (int) student.getMath();
- //JDK7以下的标准写法
- int english = (Integer) student.getEnglish();// Object -> Integer->自动拆箱
-
- System.out.println("数学成绩:"+math+",英语成绩:"+english);
- }
- }
- 复制代码
示例2:设置小数, double → 自动装箱(Double) → 对象上转型Object
- package com.shxt.demo01;
-
- public class Demo02 {
- public static void main(String[] args) {
- Student student = new Student(80.7,77.8); //实例化对象并且对数据初始化
- //请注意这种强制只有在JDK7以后才能使用
- double math = (double) student.getMath();
- //JDK7以下的标准写法
- double english = (Double) student.getEnglish();// Object -> Double->自动拆箱
-
- System.out.println("数学成绩:"+math+",英语成绩:"+english);
- }
- }
- 复制代码
示例3:设置字符串, 字符串 → 对象上转型Object
- package com.shxt.demo01;
-
- public class Demo03 {
- public static void main(String[] args) {
- Student student = new Student("66分","90.5分"); //实例化对象并且对数据初始化
- String math = (String) student.getMath();
- String english = (String) student.getEnglish();// Object -> String
-
- System.out.println("数学成绩:"+math+",英语成绩:"+english);
- }
- }
- 复制代码
代码分析:
上述的三个测试,我们感觉程序应该没有什么大的问题了,真的没有问题了吗? 请关注下面的测试
示例4:设置整数和字符串,并且转换为字符串的操作过程
- package com.shxt.demo01;
-
- public class Demo04 {
- public static void main(String[] args) {
- Student student = new Student(80,"66分"); //实例化对象并且对数据初始化
-
- String math = (String) student.getMath();
- String english = (String) student.getEnglish();
-
- System.out.println("数学成绩:"+math+",英语成绩:"+english);
- }
- }
- 复制代码
代码分析: 类型转换错误
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.shxt.demo01.Demo04.main(Demo04.java:7
说明我们上述的代码是存在安全隐患的,数据没有达到统一,我们可以通过判断改进代码,这个不符合我们的要求
判断数据的类型instanceof
- package com.shxt.demo01;
-
- public class Demo05 {
- public static void main(String[] args) {
- Student student = new Student(80,"66分"); //实例化对象并且对数据初始化
-
- String math = null;
- if(student.getMath() instanceof Integer){
- math = String.valueOf(student.getMath());
- }
-
- String english = (String) student.getEnglish();
-
- System.out.println("数学成绩:"+math+",英语成绩:"+english);
- }
- }
- 复制代码
对数据这样繁琐的判断我们是不想看到的,我们可以通过泛型解决问题
2.初识泛型
- 概念:泛型就是参数化类型,使用广泛的类型
- 起因:数据类型不明确
- 装入数据的类型都被当作Object类型,从而"丢失"了自己实际的类型
- 获取数据时往往需要转型,效率低,容易产生错误
- 作用:
- 安全: 在编译的时候检查数据类型的安全
- 省心: 所有的强制转换都是自动和隐式的,提高代码的重用率
如何定义泛型?在定义类使用泛型
-
定义格式,使用
<>
符号- class 类名<字母列表>{
- 修改符 字母 属性 ;
- 修饰符 构造函数(字母 参数名){
- }
- 修饰符 返回类型 方法(字母 参数名){
- }
- }
- 复制代码
-
修改Student类如下
- package com.shxt.demo02;
- public class Student<T> {
- private T math;
- private T english;
- public Student(T math, T english) {
- this.math = math;
- this.english = english;
- }
- public T getMath() {
- return math;
- }
- public void setMath(T math) {
- this.math = math;
- }
- public T getEnglish() {
- return english;
- }
- public void setEnglish(T english) {
- this.english = english;
- }
- }
- 复制代码
-
泛型中常见的字母列表说明
- T → Type表示类型
- K,V → 代表键值中的Key和value
- E → 代表Element
- N → 代表Number数字
- ? → 表示不确定性
-
泛型的使用规则
- 不能使用静态属性和静态方法上
- 使用的时候需要指定数据类型
- 编译时会检查数据的类型
- 获取数据不再需要强制类型转换
- 泛型使用是不能指定基本数据类型,只能使用起对应的包装类
示例1:设置统一的数据Float类型
- package com.shxt.demo02;
-
- public class Demo01 {
- public static void main(String[] args) {
- Student<Float> student = new Student<Float>(80.7F,77.8F);
- //获取数据不再需要强制类型转换,完成了数据的统一
- Float math = student.getMath();
- Float english = student.getEnglish();
-
- System.out.println("数学成绩:"+math+",英语成绩:"+english);
-
- }
- }
- 复制代码
3.泛型的使用
(1) 泛型类
对于学生的分数,我们可能需要多样的数据类型,那么我们可以设置多个泛型,代码修改属性
- package com.shxt.demo03;
-
- public class Student<T1,T2> {
- private T1 math;
- private T2 english;
-
-
- public Student(T1 math, T2 english) {
- this.math = math;
- this.english = english;
- }
-
- public T1 getMath() {
- return math;
- }
-
- public void setMath(T1 math) {
- this.math = math;
- }
-
- public T2 getEnglish() {
- return english;
- }
-
- public void setEnglish(T2 english) {
- this.english = english;
- }
- }
-
- 复制代码
示例1:设置整型和字符串
- package com.shxt.demo03;
-
-
- public class Demo01 {
- public static void main(String[] args) {
- Student<Integer,String> student = new Student<Integer,String>(80,"99分");
-
- int math = student.getMath();
- String english = student.getEnglish();
-
- System.out.println("数学成绩:"+math+",英语成绩:"+english);
- }
- }
- 复制代码
(2) 泛型接口
使用泛型定义接口我们称之为泛型接口,因为接口中只能有抽象方法和静态的公共常量(不能使用泛型修饰)
- interface Info<T>{ // 在接口上定义泛型
- public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
- public void setVar(T x);
- }
- 复制代码
####A.非泛型接口
但是在使用的时候,就出现问题了,我们先看看下面这个使用方法:
- class InfoImpl implements Info<String>{ // 定义泛型接口的子类
- private String var ; // 定义属性
- public InfoImpl(String var){ // 通过构造方法设置属性内容
- this.setVar(var) ;
- }
- @Override
- public void setVar(String var){
- this.var = var ;
- }
- @Override
- public String getVar(){
- return this.var ;
- }
- }
-
- public class Demo01{
- public void main(String arsg[]){
- InfoImpl i = new InfoImpl("hanpang");
- System.out.println(i.getVar()) ;
- }
- };
- 复制代码
代码分析:
先看InfoImpl的定义:
class InfoImpl implements Info<String>{ ………… } 要清楚的一点是InfoImpl不是一个泛型类!因为他类名后没有<T>! 复制代码
####B.泛型接口
在非泛型接口中,我们在类中直接把Info<T>
接口给填充好了,但我们的类,是可以构造成泛型类的,那我们利用泛型类来构造填充泛型接口会是怎样呢?
- interface Info<T>{ // 在接口上定义泛型
- public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
- public void setVar(T var);
- }
- class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
- private T var ; // 定义属性
- public InfoImpl(T var){ // 通过构造方法设置属性内容
- this.setVar(var) ;
- }
- public void setVar(T var){
- this.var = var ;
- }
- public T getVar(){
- return this.var ;
- }
- }
- public class Demo01{
- public static void main(String arsg[]){
- InfoImpl<String> i = new InfoImpl<String>("harvic");
- System.out.println(i.getVar()) ;
- }
- };
- 复制代码
代码分析:
在这个类中,我们构造了一个泛型类InfoImpl,然后把泛型变量T传给了Info,这说明接口和泛型类使用的都是同一个泛型变量。然后在使用时,就是构造一个泛型类的实例的过程,使用过程也不变。
使用泛型类来继承泛型接口的作用就是让用户来定义接口所使用的变量类型,而不是像方法一那样,在类中写死。
那我们稍微加深点难度,构造一个多个泛型变量的类,并继承自Info接口:
- class InfoImpl<T,K,U> implements Info<U>{ // 定义泛型接口的子类
- private U var ;
- private T x;
- private K y;
- public InfoImpl(U var){ // 通过构造方法设置属性内容
- this.setVar(var) ;
- }
- public void setVar(U var){
- this.var = var ;
- }
- public U getVar(){
- return this.var ;
- }
- }
- 复制代码
在这个例子中,我们在泛型类中定义三个泛型变量T,K,U并且把第三个泛型变量U用来填充接口Info。所以在这个例子中Info所使用的类型就是由U来决定的。 使用时是这样的:泛型类的基本用法,不再多讲,代码如下:
- public class Demo01{
- public void main(String arsg[]){
- InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");
- System.out.println(i.getVar()) ;
- }
- }
- 复制代码
###(3) 泛型方法
只能访问对象的信息,不能修改对象的信息
讲解了类和接口的泛型使用,下面我们再说说,怎么单独在一个函数里使用泛型。比如我们在新建一个普通的类StaticFans,然后在其中定义了两个泛型函数:
- package com.shxt.demo03;
-
- public class StaticFans {
- //静态函数
- public static <T> void staticMethod(T a){
- System.out.println("静态方法: "+a.toString());
- }
- //普通函数
- public <T> void otherMethod(T a){
- System.out.println("普通函数,无返回值: "+a.toString());
- }
-
- public <T> T returnOtherMethod(T a){
- System.out.println("返回泛型类型:"+a);
- return a;
- }
-
- }
- 复制代码
测试代码:
- public class Demo01 {
- public static void main(String[] args) {
- StaticFans.staticMethod(100);
- StaticFans.<String>staticMethod("悟空");
-
- //常规方法
- StaticFans sf = new StaticFans();
- sf.otherMethod(999F);
- sf.<Double>otherMethod(100.1);
-
- //返回值中存在泛型
- sf.returnOtherMethod("八戒");
- sf.<Integer>returnOtherMethod(999);
-
- }
- }
- 复制代码
代码说明:
方法一,隐式传递了T的类型,与上面一样,不建议这么做。 方法二,显示将T赋值为Integer类型,这样OtherMethod(T a)传递过来的参数如果不是Integer那么编译器就会报错。
(4) 泛型数组
在写程序时,大家可能会遇到类似String[] list = new String[8];
的需求,这里可以定义String数组,当然我们也可以定义泛型数组,泛型数组的定义方法为 T[],与String[]是一致的,下面看看用法:
- 没有泛型数组,不能创建泛型数组
- 可以只有声明,可以使用 ?
- //定义
- public static <T> T[] fun1(T...arg){ // 接收可变参数
- return arg ; // 返回泛型数组
- }
- //使用
- public static void main(String args[]){
- Integer i[] = fun1(1,2,3,4,5,6) ;
- Integer[] result = fun1(i) ;
- }
- 复制代码
4.泛型的擦除规则
我们上面的讲述过程中可以看出来可以使用子类或者实现类实现泛型,那么它们之间需要遵循一定的使用规则:
- 子类与父类(接口)一样使用泛型
- 子类指定具体的类型
- 子类与父类(接口)同时擦除泛型类型
- 子类使用泛型,而父类(接口)擦除泛型类型
- 注意错误: 不能子类擦除,而父类(接口)使用泛型
擦除后统一使用Object对象泛型
继承演示规则过程
- package com.shxt.demo04;
-
- /**
- * 父类为泛型类
- * 1.属性
- * 2.方法
- *
- * 1.要么同时擦除,要么子类大于等于父类的类型(泛型的个数)
- * 2.不能子类擦除,父类泛型
- * A.属性类型
- * 父类随父类而定
- * 子类随子类而定
- * B.方法重写:
- * 随父类而定
- */
- public abstract class Father<T> {
- protected T name;
- public abstract void test01(T t);
- }
-
- /**
- * 子类声明时指定了具体类型Father<String>
- * 属性类型为具体类型
- * 方法类型为具体类型
- */
- class Child01 extends Father<String>{
- String name;
-
- @Override
- public void test01(String s) {
-
- }
- }
-
- /**
- * 子类为泛型类,需要跟父类保持一致
- * 规则可以大于等于父类的类型
- */
- class Child02<T> extends Father<T>{
- T name;
- @Override
- public void test01(T t) {
-
- }
- }
-
- /**
- * 子类为泛型类,父类不指定类型,
- * 这个就是泛型擦除,使用Object替换
- * @param <T>
- */
- class Child03<T> extends Father{
- T name;
- @Override
- public void test01(Object o) {
-
- }
- }
-
- /**
- * 子类与父类同时擦除
- */
- class Child04 extends Father{
- String name;
- @Override
- public void test01(Object o) {
-
- }
- }
-
- /**
- * 错误:子类擦除,父类使用泛型
- */
- class Child05 extends Father<T>{
- String name;
- @Override
- public void test01(T o) {
-
- }
- }
- 复制代码
接口演示规则过程
- package com.shxt.demo04;
-
-
- public interface Comparable<T> {
- void compare(T t);
- }
-
- //声明子类,指定具体类型
- class Comp00 implements Comparable<String>{
- @Override
- public void compare(String s) {
-
- }
- }
-
- //子类与父类同时擦除,使用Object
- class Comp01 implements Comparable{
- @Override
- public void compare(Object o) {
-
- }
- }
-
- //子类泛型,父类擦除
- class Comp02<T> implements Comparable{
- @Override
- public void compare(Object o) {
-
- }
- }
-
- //子类泛型>=父类泛型
- class Comp03<T,U> implements Comparable<T>{
- @Override
- public void compare(T t) {
-
- }
- }
-
- //错误代码,父类泛型,子类擦除
- 复制代码
5.通配符
通配符:? extends super
- ?类型不定,使用时确定类型
- 可以用在声明类型以及声明方法参数上,不能用在声明类上
- ? 可以接收泛型的任意类型,只能接收和查看,不能修改
- ? extends 泛型上限 <=
- ? super 泛型下限 >=
###(1) 匹配任意类型的通配符
示例1:使用泛型传递数据
- package com.shxt.demo05;
-
- public class Demo01 {
- public static void main(String[] args) {
- Student<String> student = new Student<String>();//指定String为泛型
- student.setName("胖先森"); //设置数据
- //传递数据
- test01(student); // 错误,无法传递数据
- }
-
- public static void test01(Student<Object> temp){// 此处可以接收 Object 泛型类型的Student对象
- System.out.println("内容:"+temp);
- }
-
- }
- class Student<T>{
- private T name;
-
- public T getName() {
- return name;
- }
-
- public void setName(T name) {
- this.name = name;
- }
-
- @Override
- public String toString() {
- return "Student{" +
- "name=" + name +
- '}';
- }
- }
- 复制代码
程序编译错误:
- Error:(8, 16) java: 不兼容的类型: com.shxt.demo05.Student<java.lang.String>无法转换为com.shxt.demo05.Student<java.lang.Object>
- 复制代码
代码分析:
程序中尽管String是Object类的子类,但是在进行引用数据传递时也是同样无法进行操作,如果此时想让程序正常执行,可以将test01()方法中的定义的Student修改为Student,即不指定泛型
代码修改,编译通过
public class Demo01 { public static void main(String[] args) { Student<String> student = new Student<String>();//指定String为泛型 student.setName("胖先森"); //设置数据 //传递数据 test01(student); // 错误,无法传递数据 } public static void test01(Student temp){// 此处可以接收Student对象 System.out.println("内容:"+temp); } } 复制代码代码分析:
程序编译时不会出现任何的错误,也可以正常使用,但是编写test01()方法时Student中并没有指定任何的泛型类型,这样做有一些不妥当,所以为了解决这个问题,Java中引入了通配符"?",表示可以接收此类型的任意泛型对象(不能使用在定义类上)
使用通配符"?"修改代码
public class Demo01 { public static void main(String[] args) { Student<String> student = new Student<String>();//指定String为泛型 student.setName("胖先森"); //设置数据 //传递数据 test01(student); } public static void test01(Student<?> temp){// 此处可以接收Student对象 System.out.println("内容:"+temp); } } 复制代码(2) 受限泛型
在引用传递中,在泛型操作中也可以设置一个泛型对象的范围上限和范围下限.
上限通配符:使用extends关键字,表示这个类型必须是继承某个类或者实现某个接口,也可以是这个类或接口本身
声明对象格式:
类名称<? extends 类> 对象名称 复制代码定义类格式:
[访问权限] 类名称<泛型标识 extends 类>{} 复制代码
下限通配符:使用super关键字,表示这个类型必须是是某个类的父类或者是某个接口的父接口,也可以是这个类或接口本身
声明对象格式:
类名称<? super 类> 对象名称 复制代码定义类格式:
[访问权限] 类名称<泛型标识 super 类>{} 复制代码
上限通配符
假设一个方法中只能接收的泛型对象是数组(Byte/Short/Integer/Long/Float/Double)类型,此时在定义方法参数接收对象时,就必须指定泛型的上限,之前将包装类的时候我们说过Number类是数字的父类
示例1:方法定义,只能接收泛型为Number或者Number类型的子类
package com.shxt.demo06; public class Demo01 { public static void main(String[] args) { Student<Integer> s1 = new Student<Integer>(); //声明Integer的泛型对象 s1.setName(999); // 设置整数,自动装箱 test01(s1); // 继承了Number类,符合规范 Student<Float> s2 = new Student<Float>(); //声明Float的泛型对象 s2.setName(888.99F);// 设置小数,自动装箱 test01(s2);// 继承了Number类,符合规范 Student<Boolean> s3 = new Student<Boolean>();//声明Boolean的泛型对象 s3.setName(true);// 设置布尔,自动装箱 test01(s3);//继承了Object,没有继承Number,错误!错误!错误! Student<String> s4 = new Student<String>();//声明String的泛型对象 s4.setName("悟空"); test01(s4);//继承了Object,没有继承Number,错误!错误!错误! } // 接收student对象,范围的上限设置为Number,只能接收数字类型 public static void test01(Student<? extends Number> temp){ System.out.println("temp=>"+temp); } } class Student<T>{ private T name; public T getName() { return name; } public void setName(T name) { this.name = name; } @Override public String toString() { return "Student{" + "name=" + name + '}'; } } 复制代码示例2:类定义,只能接收泛型为Number或者Number类型的子类
package com.shxt.demo06; public class Demo01 { public static void main(String[] args) { Student<Integer> s1 = new Student<Integer>(); //声明Integer的泛型对象 s1.setName(999); // 设置整数,自动装箱 test01(s1); // 继承了Number类,符合规范 Student<Float> s2 = new Student<Float>(); //声明Float的泛型对象 s2.setName(888.99F);// 设置小数,自动装箱 test01(s2);// 继承了Number类,符合规范 Student<Boolean> s3 = new Student<Boolean>();//Boolean不是Number的子类,无法声明 Student<String> s4 = new Student<String>();//String不是Number的子类,无法声明 } // 接收student对象,范围的上限设置为Number,只能接收数字类型 public static void test01(Student<?> temp){ System.out.println("temp=>"+temp); } } class Student<T extends Number>{ // 泛型只能是数字类型 private T name; // 此变量的类型由外部决定 // 返回值的类型由外部指定 public T getName() { return name; } // 设置的类型由外部决定 public void setName(T name) { this.name = name; } @Override public String toString() { return "Student{" + "name=" + name + '}'; } } 复制代码下限通配符
package com.shxt.demo06; public class Demo01 { public static void main(String[] args) { Student<Integer> s1 = new Student<Integer>(); //声明Integer的泛型对象 s1.setName(999); // 设置整数,自动装箱 test01(s1); // 不是String的父类,错误 Student<Boolean> s2 = new Student<Boolean>();//声明Boolean的泛型对象 s2.setName(true); test01(s2); // 不是String的父类,错误 Student<String> s3 = new Student<String>(); s3.setName("悟空"); test01(s3); // 满足下限的范围 Student<Object> s4 = new Student<Object>(); s4.setName("悟空"); test01(s4); // 满足下限的范围 } // 接收student对象,范围的下限设置为String,只能接收String或者Object public static void test01(Student<? super String> temp){ System.out.println("temp=>"+temp); } } class Student<T>{ // 泛型只能是数字类型 private T name; // 此变量的类型由外部决定 // 返回值的类型由外部指定 public T getName() { return name; } // 设置的类型由外部决定 public void setName(T name) { this.name = name; } @Override public String toString() { return "Student{" + "name=" + name + '}'; } } 复制代码