Java学习——泛型

Java学习——泛型

摘要:本文主要介绍了什么是泛型,为什么要用泛型,以及如何使用泛型。

部分内容来自以下博客:

https://www.cnblogs.com/lwbqqyumidi/p/3837629.html

https://blog.csdn.net/s10461/article/details/53941091

概述

什么是泛型

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?

顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

为什么要使用泛型

先来看一个使用集合的例子:

1 List list = new ArrayList();
2 list.add("abc");
3 list.add(100);
4 for (int i = 0; i < list.size(); i++) {
5     String item = (String) list.get(i);
6     System.out.println(item);
7 }

运行时会报异常,结果如下:

1 abc
2 Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

原因是因为List在进行实例化的时候没有指定集合里的元素类型,所以使用默认的Object类型,所以能放下String类型和Integer类型的数据。但是在使用的时候,如果不知道存放了Integer类型的数据,将所有的数据都转换成String类型的数据,就有可能报类型转换失败的异常。

解决办法是,在实例化时指定元素的类型,比如指定类型为String,那么如果存入了Integer类型的数据,在编译期间进行检查发现不是String类型的数据,就会进行错误提示。

1 List<String> list = new ArrayList<String>();
2 list.add("abc");
3 //list.add(100);// 编译报错
4 for (int i = 0; i < list.size(); i++) {
5     String item = (String) list.get(i);
6     System.out.println(item);
7 }

泛型的特性

还是拿List举例,既然使用泛型规定了List内元素的类型,那么,两个不同泛型的List的类型是不是也不同呢,我们可以验证一下:

1 List<String> strList = new ArrayList<String>();
2 List<Integer> intList = new ArrayList<Integer>();
3 System.out.println("strList getClass() >>> " + strList.getClass());
4 System.out.println("intList getClass() >>> " + intList.getClass());

运行结果如下:

1 strList getClass() >>> class java.util.ArrayList
2 intList getClass() >>> class java.util.ArrayList

运行之后可以发现,虽然指定了不同泛型,但他们的类型都是ArrayList。也就是说Java中的泛型,只在编译阶段有效。

使用泛型

泛型的使用有三种形式:泛型类,泛型接口,泛型方法。

泛型类

泛型类,是在实例化类的时候指明泛型的具体类型。

在使用和定义泛型类时,需要使用泛型标识来代替普通类中的类型。

1 public class 类名<泛型标识> {
2     private 泛型标识 成员变量名;
3 
4     ...
5 }

其中类名和成员变量名都可以任意取值,泛型标识可以使用诸如T、E、K、V等字母作为参数。

定义泛型类:

 1 public class Generic<T> {
 2     private T generic;
 3 
 4     public T getGeneric() {
 5         return generic;
 6     }
 7 
 8     public void setGeneric(T generic) {
 9         this.generic = generic;
10     }
11 }

使用定义的泛型类:

1 public static void main(String[] args) {
2     Generic<String> generic = new Generic<String>();
3     generic.setGeneric("test");
4     System.out.println("getGeneric() >>> " + generic.getGeneric());
5 }

泛型接口

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

定义泛型接口:

1 public interface Generator<T> {
2     public T showGeneric();
3 }

如果一个类实现了泛型接口,但没有指定泛型的类型,那么这个类也需要按照泛型类的方式去定义,即也需要使用泛型标识定义类:

1 public class Generic<T> implements Generator<T> {
2     @Override
3     public T showGeneric() {
4         return null;
5     }
6 }

如果一个类实现了泛型接口,并且指定了泛型的类型,那么这个类中有关泛型的方法都需要换成指定的泛型的类型,并且不需要使用泛型标识定义类:

1 public class Generic implements Generator<String> {
2     @Override
3     public String showGeneric() {
4         return null;
5     }
6 }

泛型方法

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

泛型方法和普通方法的不同之处,就在于泛型方法在访问修饰符和返回类型中间有一个用于声明泛型的泛型标识<泛型标识>,然后在参数列表中就可以是用这个泛型类型的参数了。

1 public <泛型标识> void show(泛型标识 generic) {
2     System.out.println("getClass >>> " + generic.getClass());
3 }

访问修饰符与返回类型中间的<T>非常重要,可以理解为声明此方法为泛型方法。

<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。

与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型,如果有多个的话使用逗号分隔。

定义普通的泛型方法:

1 public <T, K> void show(T t, K k) {
2     System.out.println("t getClass >>> " + t.getClass());
3     System.out.println("k getClass >>> " + k.getClass());
4 }

使用泛型方法:

1 public static void main(String[] args) {
2     Generic generic = new Generic();
3     generic.show(123, "abc");
4 }

运行结果如下:

1 t getClass >>> class java.lang.Integer
2 k getClass >>> class java.lang.String

泛型类中的方法不一定是泛型方法,是不是泛型方法要根据方法的访问修饰符与返回类型中间有没有生命的泛型标识来判断。

如下,在泛型类中的一个普通方法,虽然也用了泛型标识,但是在对类实例化的时候就已经确定了这个泛型的类型,并不是等到调用方法的时候才确定的泛型类型。

在泛型类中定义普通方法:

1 public class Generic<T> {
2     public void show(T t) {
3         System.out.println("t getClass >>> " + t.getClass());
4     }
5 }

因为在实例化泛型时就明确了T的类型是Integer类型,所以在调用方法的时候就只能传入Integer类型的数据,否则会报错。

使用泛型类中的方法:

1 public static void main(String[] args) {
2     Generic<Integer> generic = new Generic<Integer>();
3     generic.show(123);
4     // generic.show("abc");// 编译报错
5 }

运行结果如下:

1 t getClass >>> class java.lang.Integer

如果要在泛型类中定义泛型方法,需要在方法的访问修饰符和返回类型中间加入对泛型类型的声明。

方法的泛型类型标识可以和类的泛型类型标识一样,也可以取其他值。

在泛型类中定义泛型方法:

1 public class Generic<T> {
2     public <T> void show(T t) {
3         System.out.println("t getClass >>> " + t.getClass());
4     }
5 }

使用泛型类中的泛型方法:

1 public static void main(String[] args) {
2     Generic<Integer> generic = new Generic<Integer>();
3     generic.show(123);
4     generic.show("abc");
5 }

运行结果如下:

1 t getClass >>> class java.lang.Integer
2 t getClass >>> class java.lang.String

泛型与可变参数

定义可变参数的类型为泛型的泛型方法:

1 public <T> void show(T... ts) {
2     System.out.println("ts getClass >>> " + ts.getClass());
3     for (T t : ts) {
4         System.out.println("t getClass >>> " + t.getClass());
5     }
6 }

使用可变参数的泛型方法:

1 public static void main(String[] args) {
2     Generic<Integer> generic = new Generic<Integer>();
3     generic.show(123, "abc", 'a');
4 }

运行结果如下:

1 ts getClass >>> class [Ljava.io.Serializable;
2 t getClass >>> class java.lang.Integer
3 t getClass >>> class java.lang.String
4 t getClass >>> class java.lang.Character

泛型和静态方法

如果一个类似泛型类,那么这个类中的静态方法的参数不能使用泛型,如果要使用的话,需要将静态方法定义为泛型类。

在静态方法中使用泛型参数会在编译期间报错:

1 public static void show(T t) {
2     System.out.println("t getClass >>> " + t.getClass());
3 }

可以将静态方法声明为泛型方法:

1 public static <T> void show(T t) {
2     System.out.println("t getClass >>> " + t.getClass());
3 }

泛型通配符

为什么要使用类型通配符

在上文的学习中,我们知道了泛型类的类型和泛型类型无关,也就是说,Generic<Number>类型的对象和Generic<Integer>类型的对象,他们的类型都是Generic。

既然Number和Integer是父类和子类的关系,那么Generic<Number>和Generic<Integer>有没有父类和子类的关系呢,我们可以做一个测试。

1 public static void main(String[] args) {
2     testGenericFunc(new Generic<Number>());
3     // testGenericFunc(new Generic<Integer>());// 编译报错
4 }
5 
6 public static void testGenericFunc(Generic<Number> generic) {
7     System.out.println("testGenericFunc ...");
8 }

如果在调用方法的时候传入了Generic<Integer>类型的参数,会在编译期间报错,也就是说不能将Generic<Number>看做Generic<Integer>的父类。

那么,如果要想在调用方法的时候,传入任何泛型类型的参数都能使用,就需要使用类型通配符来实现了。

类型通配符一般是使用?代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box<Integer>、Box<Number>等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

如何使用类型通配符

使用方式很简单, 只需要将方法的参数列表中指定的泛型类型换成?就可以了。

1 public static void main(String[] args) {
2     testGenericFunc(new Generic<Number>());
3     testGenericFunc(new Generic<Integer>());
4 }
5 
6 public static void testGenericFunc(Generic<?> generic) {
7     System.out.println("testGenericFunc ...");
8 }

设置通配符的上下限

如果需要定义一个方法,但对类型实参有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限,具体做法是将<?>换为<? extends Number>。

1 public static void main(String[] args) {
2     testGenericFunc(new Generic<Number>());
3     testGenericFunc(new Generic<Integer>());
4     // testGenericFunc(new Generic<Object>());// 编译报错
5 }
6 
7 public static void testGenericFunc(Generic<? extends Number> generic) {
8     System.out.println("testGenericFunc ...");
9 }

如果要限制只能是Number类及其父类,就需要设置通配符下限,将<?>换为<? super Number>。

1 public static void main(String[] args) {
2     testGenericFunc(new Generic<Number>());
3     // testGenericFunc(new Generic<Integer>());// 编译报错
4     testGenericFunc(new Generic<Object>());
5 }
6 
7 public static void testGenericFunc(Generic<? super Number> generic) {
8     System.out.println("testGenericFunc ...");
9 }

泛型类型的数组

在Java中是不能创建一个确切的泛型类型的数组的。

也就是说,下面这个例子是不允许的:

1 List<String>[] listArr = new List<String>[10];

不过可以使用通配符的方式创建:

1 List<?>[] listArr = new List<?>[10];

也可以不指定泛型类型创建:

1 List<String>[] listArr = new List[10];
原文地址:https://www.cnblogs.com/shamao/p/10973579.html