目录
一、泛型语法
如果我们想实现一个可以存放任何类型的数组,我们该如何设计呢?这时,我们就需要引出泛型来进行操作。
1.1 语法
泛型的语法如下:
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
接下来,我们来设计一个可以存放任何类型的数组。
class Array<T> {
public T[] t = (T[]) new Object[10];
public T getPos(int pos) {
return this.t[pos];
}
public void setT(int pos,T t) {
this.t[pos] = t;
}
@Override
public String toString() {
return "Array{" +
"t=" + Arrays.toString(t) +
'}';
}
}
public class Test {
public static void main(String[] args) {
Array<Integer> array = new Array<>();
array.setT(1,3);
array.setT(2,5);
array.setT(0,2);
System.out.println(array.getPos(0));
System.out.println(array.toString());
}
}
这就是利用泛型来设计的一个什么类型都可以存放的数组。如果我在<>里面输入的是Integer之后,该数组就只能输入int类型数据,其他类型的数据无法存放。
注意:1. <>里面只能使用包装类类型。
2. 类名后的<T>代表占位符,表示当前类是个泛型类。
3. 编译器会检测当前类型是否符合要求。
二、泛型类的使用
2.1 语法
泛型类<类型实参> 变量名;// 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参) // 实例化对象
例子:
Array<Integer> array = new Array<Integer>();
还可以把后面<>中的参数省略,因为编译器会从前往后推导出类型实参。
三、泛型的上界
有的时候,我们需要限制泛型的范围,那么我们就需要通过类型边界来约束。
3.1 语法
class 类名<类型形参 extends 类型边界> {
//............
}
接下来,我们来一个例子:
class Array<T extends Number> {
int a;
}
public class Test {
public static void main(String[] args) {
Array<Number> array = new Array<>();
Array<Integer> array = new Array<>();
Array<String> array1 = new Array<String>(); //编译错误,String不是Number的子类
}
}
这就表明,只接受Number的子类(或者Number)做为T的类型参数。
注意:没设置边界的时候,可以默认为是extends了Object类型。
还有一个例子:
class Student<T extends Comparable<T>>{
}
当我们这样定义一个类的时候,在实例化Student的时候,<>里必须填入实现Comparable接口的类型参数。
四、泛型方法
4.1 泛型方法的定义
方法限定符 <类型形参列表> 返回类型 方法名(参数列表){
//.........
}
例如:
public static <T> void func(T t){
//.........
}
注意:<>是在返回类型的前面。
4.2 示例
public static <T> void func(T t){
System.out.println("T:"+t+" 泛型方法");
}
public static void main(String[] args) {
Integer a = 10;
func(a);
}
上面就是一个非常简单的泛型方法。在使用泛型方法的时候,我们需要将参数列表一一对应的写好。<>可以省略,因为编译器可以通过后面的参数来推导出T的类型。这就叫做类型推导。
五、通配符
5.1 认识通配符
我们先来看一个例子:
class Message<T>{
public T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public static void func(Message<String> message){
System.out.println(message.getT());
}
public static void main(String[] args) {
Message<String> message = new Message<>();
message.setT("啦啦啦");
func(message);
}
程序很简单,就是实例化一个Message对象,然后调用set方法赋值,接着调用func函数来输出数据。但是,当我们的泛型类型为Integer的时候,程序就有问题了。
Message<Integer> message1 = new Message<>();
message1.setT(10);
func(message1);
类型不兼容异常,这是因为func函数中参数为Message<String> message,这样的话,func就只能接收泛型参数为String的参数,Integer当然就不行了。
如果要解决这个问题,我们就需要我们的通配符 ?了。
public static void func(Message<?> message){
System.out.println(message.getT());
}
public static void main(String[] args) {
Message<String> message = new Message<>();
message.setT("啦啦啦");
func(message);
Message<Integer> message1 = new Message<>();
message1.setT(10);
func(message1);
}
当我把func的类型参数改为Message<?> message,程序就不会报错了,并且可以正常运行。
注意:在func里面无法修改参数,因为你并不知道 ?到底是什么类型。
因此,通配符的主要作用就是可以接收所有的泛型类型,但是用户无法修改参数。
5.2 通配符的上界
在<?>的基础上,通配符还可以限定范围。
<? extends 类>:设置通配符的上限(上界)
<? super 类>:设置通配符的下限(下界)
现在,我们先将通配符的上界
例子如下:
class Message<T>{
public T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Banana extends Food{
}
class RedApple extends Apple{
}
public static void main(String[] args) {
Message<Apple> message = new Message<>() ;
message.setT(new Apple());
func(message);
Message<Fruit> message1 = new Message<>();
message1.setT(new Fruit());
func(message1);
Message<Food> message2 = new Message<>();
message2.setT(new Food());
//func(message2);//报错
}
public static void func(Message<? extends Fruit> message){
//message.setT(new Fruit());
//message.setT(new Apple());报错
Fruit fruit = message.getT();
System.out.println(fruit);
}
从上面的代码我们可以知道:
1.当<? extends Fruit>的时候,?就只能是Fruit本身或者其子类。其余的会报错。
2.在func里,只能读取数据,但是却不可以修改数据。这是因为?的类型是Fruit以及其子类,我们并不知道?传过来得是什么类型,假如传一个Fruit过来,但你并不知道它是Fruit,因此你并不知道设置/修改一个什么数据。
这个是<? extends Fruit>的关系图:
5.3 通配符的下界
语法:
<? super 下界>
<? super Integer>//代表可以传入Integer或者Integer的子类
例子如下:
class Message<T>{
public T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Banana extends Food{
}
class RedApple extends Apple{
}
public static void main(String[] args) {
Message<Apple> message = new Message<>() ;
message.setT(new Apple());
//func(message);//报错
Message<Fruit> message1 = new Message<>();
message1.setT(new Fruit());
func(message1);
Message<Food> message2 = new Message<>();
message2.setT(new Food());
func(message2);
}
public static void func(Message<? super Fruit> message){
message.setT(new Fruit());Fruit本身,可以修改
message.setT(new Apple());Fruit的子类,可以修改
//message.setT(new Food()); Fruit的父类,报错
System.out.println(message.getT());//可以直接输出
//Fruit fruit = message.getT(); 不能接收,这里无法确定是哪个父类
}
由上述代码我们可以知道:
1.如果通配符设置了下限,那么就只能修改数据,而不可以获取数据。因为此时?传来的都是Fruit本身或者自己的父类,你无法知道用什么类型去接收数据。
2.当<? super Fruit>的时候,就只能接收Fruit本身或者其父类。