您的位置:首页 > 编程语言 > Java开发

详解Java泛型(一)之简单介绍

2016-12-09 16:48 477 查看

1. 概述

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。什么叫参数化类型呢?像java的方法有形参,然后调用方法的时候传递实参。而参数化类型,顾名思义就是把类型参数化,就是说类型是不固定的,是靠调用者传入进来的。

这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

比如说ArrayList类,在没有泛型的时候,获取的元素是Object类型的,要使用该元素,必须知道该元素的具体类型,并对其进行显示强制类型转换,而且一不小心就可能会抛出ClassCastException异常,不仅麻烦还不安全。就像下面的代码那样。

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {

public static void main(String[] args) {
List list = new ArrayList();
//在list中添加一个字符串元素
list.add("123");
//正常类型转换
String str = (String) list.get(0);
//会出现转换异常
Integer val = (Integer) list.get(0);
}
}


泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。 如上面的例子用上了泛型,就可以在编译的时候知道错误。

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {

public static void main(String[] args) {
List<String> list = new ArrayList<String>();
//在list中添加一个字符串元素
list.add("123");
String str = list.get(0);// 不用进行类型转换
Integer val = (Integer) list.get(0);// 编译出错
}
}


下面就来具体介绍一下泛型的用法

2.类型参数

类型参数就是尖括号(“<>”)里的东西,就像是getVal(Intger val)的方法参数是Intger类型变量,到时候调用这个方法的时候传入Intger类型的实参即可,而类型参数传入的不是一个数据类型的变量,而是一个数据类型,如上面代码
List<String> list = new ArrayList<String>();
的就是传入了一个String类型。

3. 定义泛型类

泛型类即带类型参数的类,像ArrayList类就是带有类型参数的类也就是泛型类,那我们怎么自己定义一个泛型类呢?很简单,在类名的后面加上<>并在里面指定一个或多个类型变量,多个类型变量时使用逗号隔开。 比如说我有一个类,可以存储两个相同类型的值,代码就可以这样子实现。

public class Pair<T> {
private T t1;
private T t2;
public T getT1() {
return t1;
}
public void setT1(T t1) {
this.t1 = t1;
}
public T getT2() {
return t2;
}
public void setT2(T t2) {
this.t2 = t2;
}
}


可以看到Pair类有一个类型变量T,这个类型我们并不知道是什么,只有实例化的时候传进来才会知道,然后可以像下面的代码那样实例化泛型类

public class Main {

public static void main(String[] args) {
Pair<String> p = new Pair<String>();
p.setT1("这是t1");
p.setT2("这是t2");
String t1 = p.getT1();
String t2 = p.getT2();
System.out.println(t1);
System.out.println(t2);
}
}


可以看到我用一个String类型作为实际的类型变量传进去,这样子Pair的set和get方法都是针对String类型的了。

4. 泛型方法

实际上,还可以定义一个带有类型变量的简单方法。如下面的代码

public class ArrayMiddle {

public static <T> T getMiddle(T ... ts){
return ts[ts.length/2];
}
}


这里的泛型方法并不是定义在泛型类中的,而是定义在一个普通类中。其实泛型方法可以定义在普通类中,也可以定义在泛型类中。需要注意的是,类型参数放在修饰符(这里是public static)的后面,返回类型之前。这个泛型方法的功能是接受一些相同类型的变量(数量不确定),然后返回这些变量当中中间的那个变量。

可以像下面那样使用泛型方法

public class Main {

public static void main(String[] args) {
String middle = ArrayMiddle.<String>getMiddle("a","b","c");
System.out.println(middle);
}
}


输出结果是b

当然也可以像
String middle = ArrayMiddle.getMiddle("a","b","c");
这样子调用泛型方法(一般我们都是这么用的),编译器可以根据上下文信息推断出该类型变量就是String类型。

5.类型变量的限定

现在考虑一个问题,我想让上面的泛型类Pair有一个getMax方法可以返回t1和t2中的最大值,那么我这的代码写出下面这样

public class Pair<T> {
private T t1;
private T t2;
public T getT1() {
return t1;
}
public void setT1(T t1) {
this.t1 = t1;
}
public T getT2() {
return t2;
}
public void setT2(T t2) {
this.t2 = t2;
}

public T getMax(){
int result = t1.compareTo(t2);
if(result > 0)
return t1;
else
return t2;
}
}


当然这段代码是不能编译通过的,因为你根本就不知道泛型变量T会是什么,很多类是没有compareTo方法的,但是实现了Comparable接口的类就会有这个方法。所以解决这个问题的的方案就是将T所属的类限制成实现了Comparable接口的类。所以我修改一下将
Pair<T>变为 Pair<T extends Comparable<T>>
,就可以编译通过了。然后使用一下这个方法

public class Main {

public static void main(String[] args) {
Pair<Integer> p = new Pair<Integer>();
p.setT1(11);
p.setT2(5);
Integer max = p.getMax();
System.out.println(max);

}
}


输出结果为11

需要注意的是,因为Integer类实现了Comparable接口,所以可以像上面那样使用。而像Thread类是没实现Comparable接口的,如果将Thread类传入类型变量,编译就会出错,所以实例化的时候不可以传入没有实现Comparable接口的类型变量,这样子就把类型变量限定住了。

还需要注意的是,虽然Comparable是接口,但是还是用extends关键字来限定,而不是用implements关键字。无论是类或者接口都是用extends关键字来限定的。

多个限定类型之间用”&”分隔,如
Pair<T extends Comparable & Serializable>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息