「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」。
泛型
1.泛型的作用与定义
类型的参数化,就是可以把类型像方法的参数那样传递。
泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。
一说到泛型,大伙肯定不会陌生,我们代码里面有很多类似这样的语句:
List list=new ArrayList<>();
复制代码
ArrayList就是个泛型类,我们通过设定不同的类型,可以往集合里面存储不同类型的数据类型(而且只能存储设定的数据类型,这是泛型的优势之一)。“泛型”简单的意思就是泛指的类型(参数化类型)。想象下这样的场景:如果我们现在要写一个容器类(支持数据增删查询的),我们写了支持String类型的,后面还需要写支持Integer类型的。然后呢?Doubel、Float、各种自定义类型?这样重复代码太多了,而且这些容器的算法都是一致的。我们可以通过泛指一种类型T,来代替我们之前需要的所有类型,把我们需要的类型作为参数传递到容器里面,这样我们算法只需要写一套就可以适应所有的类型。最典型的的例子就是ArrayList了,这个集合我们无论传递什么数据类型,它都能很好的工作。
2. Java泛型介绍
下面我们来介绍Java泛型的相关内容,下面会介绍以下几个方面:
- Java泛型类
- Java泛型方法
- Java泛型接口
Java泛型类
类结构是面向对象中最基本的元素,如果我们的类需要有很好的扩展性,那么我们可以将其设置成泛型的。假设我们需要一个数据的包装类,通过传入不同类型的数据,可以存储相应类型的数据。
Java泛型方法
前面我们介绍的泛型是作用于整个类的,现在我们来介绍泛型方法。泛型方法既可以存在于泛型类中,也可以存在于普通的类中。如果使用泛型方法可以解决问题,那么应该尽量使用泛型方法。下面我们通过例子来看一下泛型方法的使用:
class DataHolder<T>{
T item; public void setData(T t) { this.item=t;
} public T getData() { return this.item;
} /**
* 泛型方法
* @param e
*/
public void PrinterInfo(E e) {
System.out.println(e);
}
}
复制代码
我们来看运行结果:
1AAAAA8.88
复制代码
Java泛型接口
Java泛型接口的定义和Java泛型类基本相同,下面是一个例子:
//定义一个泛型接口public interface Generator<T> { public T next();
}
复制代码
此处有两点需要注意:
- 泛型接口未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。例子如下:
/* 即:class DataHolder implements Generator{
* 如果不声明泛型,如:class DataHolder implements Generator,编译器会报错:"Unknown class"
*/class FruitGenerator<T> implements Generator<T>{ @Override
public T next() { return null;
}
}
复制代码
- 如果泛型接口传入类型参数时,实现该泛型接口的实现类,则所有使用泛型的地方都要替换成传入的实参类型。例子如下:
class DataHolder implements Generator<String>{ @Override
public String next() { return null;
}
}
复制代码
从这个例子我们看到,实现类里面的所有T的地方都需要实现为String
/**Test01案例
* Test02泛型接口
* Test03泛型方法
*Day01通配符
* 泛型类型只允许设置类和接口,而不能使用基本数据类型(不能int要Integer)\
* 通配符?表示能接收一切但不能修改
*/
class Message<T> {//设置泛型上限<T extends Number>
private T note ;
public T getNote() {
return note;
}
public void setNote(T note) {
this.note = note;
}
}
public class Day01 {
public static void main(String[] args) {
Message<String >msg = new Message<>();
msg.setNote("99");
fun(msg);
}
public static void fun(Message<? super String>temp){//设置泛型下限
System.out.println(temp.getNote());
}
}
复制代码
泛型中通配符
我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V 等等,这些通配符又都是什么意思呢?
常用的 T,E,K,V,?
本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:
- ?表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
上界通配符 < ? extends E>
上届:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
- 如果传入的类型不是 E 或者 E 的子类,编译不成功
- 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
private <K extends A, E extends B> E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
复制代码
下界通配符 < ? super E>
下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object
在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。
上界通配符主要用于读数据,下界通配符主要用于写数据。
案例
//在Point类定义的时候完全不知道x和y的属性是什么类型,而由使用者来决定
class Point<T>{//T表示参数
private T x;
private T y;
public T getX(){
return x;
}
public void setX(T x){
this.x = x;
}
public T getY(){
return y;
}
public void setY(T y){
this.y = y;
}
}
public class Test01 {
public static void main(String[] args) {
//第一步:设置数据
Point<String> p = new Point<String>();//相当于所有的T都变成String
p.setX("东经10度"); //设置坐标
p.setY("北纬10度");
//第二步:取出数据
String x = p.getX(); //避免了向下转型
String y = p.getY();
System.out.println("x = "+ x +"、y = "+y);
}
}
复制代码
泛型接口
//泛型接口的两种实现形式
interface IMessage<T>{//在接口上定义了泛型
public void print(T t);
}
class MessageImpl implements IMessage<String>{
public void print(String t){
System.out.println(t);
}
}
public class Test02 {
public static void main(String[] args) {
IMessage<String> msg = new MessageImpl();
msg.print("hello hh");
}
}
复制代码
泛型方法
//在之前定义的类或接口上发现都可以在里面的方法在泛型类或接口里面,也可以单独定义
//泛型方法(不建议使用)
public class Test03 {
public static void main(String[] args) {
Integer data[] = fun(1,2,3,4);
for (int temp : data) {//迭代和自动拆箱
System.out.println(temp);
}
}
//<T>描述的是泛型标记的声明
public static <T> T[] fun(T ... args){
return args;
}
}
复制代码