前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >JAVA中的泛型

JAVA中的泛型

作者头像
星哥玩云
发布2022-09-14 20:05:50
发布2022-09-14 20:05:50
1.4K00
代码可运行
举报
文章被收录于专栏:开源部署开源部署
运行总次数:0
代码可运行

1、泛型概述

1.1、泛型由来

先来看一个案例:

代码语言:javascript
代码运行次数:0
复制
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.lang.ClassCastException。 为什么会发生类型转换异常呢? 我们来分析下:由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastException。 怎么来解决这个问题呢?

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

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

1.2、使用泛型的好处

泛型带来了哪些好处呢?

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

避免了类型强转的麻烦。

代码语言:javascript
代码运行次数:0
复制
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、格式
代码语言:javascript
代码运行次数:0
复制
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
 	private 泛型标识 /*(成员变量类型)*/ var; 
 		.....
 	}
}
2.1.2、案例
代码语言:javascript
代码运行次数:0
复制
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的具体类型

代码语言:javascript
代码运行次数:0
复制
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、格式
代码语言:javascript
代码运行次数:0
复制
interface 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
		public T next();
}
2.2.2、案例
代码语言:javascript
代码运行次数:0
复制
//定义一个泛型接口
public interface Generator<T> {
    public T next();
}
代码语言:javascript
代码运行次数:0
复制
/**
 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 * 即: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;
    }
}
代码语言:javascript
代码运行次数:0
复制
/**
 * 传入泛型实参时:
 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口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、格式
代码语言:javascript
代码运行次数:0
复制
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
2.3.2、案例
代码语言:javascript
代码运行次数:0
复制
 //这个类是个泛型类,在上面已经介绍过
    public class Generic<T> {
        private T key;

        public Generic(T key) {
            this.key = key;
        }

        //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
        //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
        //所以在这个方法中才可以继续使用 T 这个泛型。
        public T getKey() {
            return key;
        }
    }
代码语言:javascript
代码运行次数:0
复制
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) {
    }
}
代码语言:javascript
代码运行次数:0
复制
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);
    }
}
代码语言:javascript
代码运行次数:0
复制
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、通配符

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

3.2、通配符基本使用

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

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

代码语言:javascript
代码运行次数:0
复制
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的父类

代码语言:javascript
代码运行次数:0
复制
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){}

}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、泛型概述
    • 1.1、泛型由来
    • 1.2、使用泛型的好处
  • 2、泛型定义及使用
    • 2.1、泛型类
      • 2.1.1、格式
      • 2.1.2、案例
    • 2.2、泛型接口
      • 2.2.1、格式
      • 2.2.2、案例
    • 2.3、泛型方法
      • 2.3.1、格式
      • 2.3.2、案例
  • 3、泛型通配符
    • 3.1、通配符
    • 3.2、通配符基本使用
    • 3.3、受限泛型
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档