Javaの泛型

一、泛型的概念

我们经常需要对多个类型的数据做相同的操作,但为各个类型分别编写方法和类是十分低效的。为此,JDK-5引入了泛型
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型
泛型的实质是类型参数化,将数据类型作为参数进行传递。
类型参数必须使用引用型类型

二、泛型

1.泛型方法

泛型方法可以在调用时接收不同类型的参数、规定不同类型的返回值
一个泛型方法定义如下:

<类型参数声明>返回类型 方法名(形参表){
	//方法定义
}

下面是简单的举例,并使用两种方法调用泛型方法:

public class Demo {
	public static void main(String[] args) {
		Integer i = 123;
		Demo.<Integer>print(i);		//给定类型参数调用,该方法必须配合使用句点表示法,无法自动推断时使用
		print(i);					//Java8新增类型推断,一般采用该方法
	}
	
	public static <T> void print(T t) {
		System.out.println(t);
	}
}

如果想要的类型参数在某一个特定范围内(通常是继承层次中的范围),可以通过使用extends关键字。这个关键词规定了类型实参必须继承了某个类,或者实现了某个接口

//如没有extends,编译无法通过,因为不知道T是否定义了compareTo方法
public <T extends Comparable<T>> boolean smallerThan(T o1,T o2) {	
	if(o1.compareTo(o2) < 0)	
		return true;
	else
		return false;
}

泛型方法也可以使用可变参数,如:

public <T> void print(T...args) {
	for(T t : args)
		System.out.println(t);
}

2.泛型类

泛型类可以对不同类型的数据开放相同的接口。最典型的就是各种容器类。
一个泛型类定义如下:

class 类名<类型参数声明>{
	//实例/局部变量和实例方法可以使用类声明的类型参数
}

下面是一个简单的举例及两种使用方法:

class Test<T>{
	T value;
	public void set(T value) {
		this.value = value;
	}
	
	public T get() {
		return value;
	}
}

public class Demo {
	public static void main(String[] args) {
		//方法一:给定类型实参,实例对象只能接受给定类型的数据
		Test<Integer> test1 = new Test<Integer>();
		test1.set(6);
		//方法二:不给定类型实参,实例对象可以接受任何类型的数据
		Test test2 = new Test();
		test2.set(1);
		test2.set(2.0);
		test2.set("3");
	}
}

泛型方法每次调用时都会重新压栈,重新推断类型,因此是安全的。在可以自动推断的情况下应尽量使用。
泛型类的实例一旦初始化,在其作用域内就始终存在。中途改变其类型参数是不安全的,应使用给定实参的方式。

泛型类中的泛型方法
并不是所有泛型类中的方法都是泛型方法,如:

class Test<T>{
	T value;
	public void set(T value) {	//不是一个泛型方法,只是使用了类声明的类型形参
		this.value = value;
	}
	
	public T get() {		//同上,不是一个泛型方法
		return value;
	}
	
	public <E> void print(E arg){		//这是一个泛型方法,他有单独的类型参数声明,这个方法中T和E均可使用
		System.out.print(arg.toString());
	}
	
	public <T> void println(T arg) {	//这也是一个泛型方法,但它将类给定的类型参数T隐藏(屏蔽)了,这里的T是自身的类型形参
		System.out.println(arg.toString());
	}
	//这两个方法主要是为了说明泛型类中的泛型方法,明确概念,两个函数本身不具应用意义
}

静态方法与泛型
静态方法无法访问实例成员,而类型参数也是实例成员的一种,因此静态方法不可使用类的类型参数
例如在上面的类中添加如下的静态方法是不能通过编译的:

public static void print2(T arg){	//Error:不能对非静态类型T进行静态引用
	System.out.println(arg);
}

应当为静态方法给出一个自身的类型参数声明,修改如下:

public static <T> void print2(T arg) {
	System.out.println(arg);
}

3.泛型接口

泛型接口的定义与泛型类基本相同,常被用在各种类的生产器,例如比较器

interface Print <T>{			//输出器
	public void print(T o);
}

使用类实现泛型接口时,需要为泛型接口传递类型参数。常见的是泛型类为泛型接口传递形参:

interface Print <T>{
	public void print(T o);
}

class Test<T> implements Print<T>{
	@Override
	public void print(T o) {
		System.out.println(o);
	}
}

4.类型通配符

当同一个泛型类被不同的类型参数实例化后,就变成了不同的版本。版本不同的实例是不兼容的,哪怕是基类与子类之间。

public class Demo {
	public static void main(String[] args) {
		test(new Test<Base>());			//无错误
		test(new Test<Heir>());			//Error:方法 test(Test<Base>)对于参数(Test<Heir>)不适用
	}
	
	public static void test(Test<Base> arg) {	//规定仅接受Test<Base>版本,<>里必须给定类型,否则报错
		System.out.println(arg);
	}
}

class Base{}
class Heir extends Base{}
class Test<T>{}

为了解决这一问题,我们使用 ? 作为类型通配符,它可以指代所有的类型。将上面的test方法作如下修改,就可以通过编译。

//public static void test(Test<Base> arg)
public static void test(Test<?> arg) {
	System.out.println(arg);
}

5.泛型的上下边界

使用泛型时,我们可以为接受的类型参数界定上下边界
规定传入类型必须是某个类型的子类/实现某个接口(上界),或必须是某个类型的父类(下界)。
extends 关键字用于规定上界,规定上界可以允许泛型中使用父类的实例成员,并允许多态

public class Demo {
	public static void main(String[] args) {
		test(new Test<Base>(new Base()));	//class learning_test.Base
		test(new Test<Heir>(new Heir()));	//class learning_test.Heir
	}
	
	public static void test(Test<? extends Base> arg) {
		arg.print();
	}
}

class Base{}
class Heir extends Base{}
class Test<T>{
	T value;
	public Test (T value) {
		this.value = value;
	}
	public void print() {
		System.out.println(value.getClass().toString());
	}
}

super关键字用于规定下界,即只接收给定类型的父类类型。将上面的test形参该位Test<? super Heir>,main方法中会报错。

test(new Test<Base>(new Base()));	//无措
test(new Test<Heir>(new Heir()));	//Error:方法 test(Test<? super Base>)对于参数(Test<Heir>)不适用

测试时发现,extends对于任意的泛型都可以使用,但super只能与类型通配符一起使用。

public static <T extends Base> void print1(T o){}	//无错误
public static <T super Base> void print2(T o){}		//Error:标记“super”上有语法错误,应为,
//第二行的错误报告看上去很奇怪,可能是将super识别为父类关键字super使用了
public static void print3(List<? extends Base> list) {}	//无错误
public static void print4(List<? super Base> list) {}	//无错误
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章