Jdk自从5.0后引入泛型之后一直没有删除,而且在我们的集合框架中进场能使用的到,今天我们就详细介绍一下泛型的一些特性和使用须知,希望能对你的编程学习带来一些帮助.
1.什么是泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),
然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,
操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
2.泛型的使用
这里我们引用一个例子
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { List list = new ArrayList(); list.add(110); list.add("aaa"); for (int i = 0; i < list.size(); i++) { String item = (String)list.get(i); System.out.println(item); } } }
这里显然产生了错误,ArrayList可以存放任意类型的数据,例子中我添加了一个String类型的数据和一个Integer的数据,但是我都以String类型取出,这就是不可取的,,但是我们如果在声明的时候就给加上一个String泛型,这样如果我添加100,就会在编译期间就报错,同时也提高了代码的可读性.
3.泛型的特性
泛型实际上只在编译阶段起作用,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型,我们可以用以下代码加以证明.
4.泛型的使用
泛型的定义和使用:在Java中,可以用“<>”来定义泛型。泛型可以定义在类、接口和方法上。在定义泛型时,可以使用一个或多个类型参数,例如<T>、<T, U>等。在使用泛型时,可以用具体的类型来替代类型参数,例如ArrayList<String>、HashMap<Integer, String>等,以下,我们将以泛型类,泛型泛型接口,泛型方法来介绍泛型.
注:泛型的类型参数不能是简单类型,可以是Integer这样的封装类,也可以是自定义类型,但是不能是int这样的简单类型.
4.1 泛型类
我们先举个例子说明,泛型类的使用
以下示例中,使用“Box<String>”来创建一个可以存储字符串的“Box”对象。在对象创建后,可以使用“set”方法来设置字符串,并使用“get”方法来获取字符串。由于使用了泛型,无需进行类型转换,代码更加简洁和安全。
public class Box<T> { private T item; public void set(T item) { this.item = item; } public T get() { return item; } } Box<String> stringBox = new Box<String>(); stringBox.set("Hello World!"); String item = stringBox.get(); System.out.println(item); // 输出 "Hello World!"
泛型类的判断(本质上就是有一个不确定的类型)
先给一个泛型类Order
public class Order<T> { //声明了类的泛型参数以后,就可以在类中使用此泛型参数 T t;//看成有一个类型就是T,可以看成String... int OrderId; public Order(T t, int orderId) { this.t = t; OrderId = orderId; } public Order() { } public T getT() { return t; } public void setT(T t) { this.t = t; } public int getOrderId() { return OrderId; } public void setOrderId(int orderId) { OrderId = orderId; } @Override public String toString() { return "Order{" + "t=" + t + ", OrderId=" + OrderId + '}'; } public <E> ArrayList<E> copy(E[] arr) { ArrayList <E> list = new ArrayList<>(); for(E e : list) { list.add(e); } return list; } }
public class SubOrder1 extends Order{ } //不是泛型类 public class SubOrder2 extends Order<Integer>{ } //不是泛型类,因为这个时候子类的泛型已经为确定的类型了 public class SubOrder3<T> extends Order<T>{ public void show() { System.out.println(t); } }//是泛型类,因为这个时候子类延续了父类的泛型T public class SubOrder4<E> extends Order<Integer>{ } //是泛型类,因为这个时候子类的泛型和父类声明的泛型类型不是同一种 public class SubOrder4<T,E> extends Order<T>{ } //是泛型类
使用说明:要使用泛型类就一路都用,否则就别用,泛型如果没有指明就擦除了,默认用Object,但不等价于Object().
注:不能new E[],但是可以E [ ] elements = (E [ ])new Object[capacity]);因为这时候E是不确定的.
泛型类中声明的泛型参数不能在static方法中使用.因为我们可以在继承或者实例化的时候确定泛型类型,但是类去调用静态方法的时候,此时这个泛型还不确定.
4.1.2 类型推断(JDK7)
这里我们的泛型类的声明也可以是这样的
Box<String> box = new Box<>();
这时候编译器会自动进行类型推断.
或者我们也可以这样写
Box box = new Box("hello"); Box box = new Box(666);
这里不写泛型类型默认就是Object类型的.
4.2 泛型接口
和泛型方法类型,这里不做过多讲解,举一个例子
public interface GenericInterface<T> { T performAction(T input); } public class StringAction implements GenericInterface<String> { @Override public String performAction(String input) { return input.toUpperCase(); } }
在这个示例中,定义了一个名为“StringAction”的类,它实现了泛型接口“GenericInterface<String>”。这个类实现了“performAction”方法,将传入的字符串转换为大写并返回。由于使用了泛型接口,这个类可以处理任意类型的字符串,提高了代码的可重用性。
注:虽然我们只定义了一个泛型接口,但是我们可以传入无数种类型给泛型参数,使得代码很灵活.
4.3 通配符
通配符就是一个?,它是为了解决泛型类型不兼容问题而出现的,我们之前说过泛型的类型本质上是一样的,我们知道Number是Integer的父类,那么泛型参数为Integer的元素是否能添加到参数为Number中呢?答案是否定的
由此我们得到一个结论:泛型对应多个版本,但是版本之间是不兼容的,为了解决这种不兼容的问题,通配符由此诞生.
4.3.1 通配符的使用
- 表示任意类型:可以使用“?”来表示任意类型,例如List<?>表示一个可以存储任意类型的列表。
- 表示任意类型的父类:可以使用“? extends T”来表示任意类型是T的子类或者T本身,例如List<? extends Number>表示一个可以存储Number或其子类的列表。
- 表示任意类型的子类:可以使用“? super T”来表示任意类型是T的父类或者T本身,例如List<? super Integer>表示一个可以存储Integer或其父类的列表。
下面我举例说明
1.
List<?> list = new ArrayList<>(); //这个示例中,使用通配符“?”来表示list可以存储任意类型的数据。 //但是由于通配符表示不确定的类型,所以在获取list中的元素时, //无法确定具体的类型,需要进行类型转换 //类型转换:先用instanceOf方法判断,再转换即可
2.
List<? extends Number> list = new ArrayList<>(); //这个示例中,使用通配符“? extends Number”来表示list可以存储Number或其子类的数据。 //在获取list中的元素时,可以确定元素是Number或其子类,无需进行类型转换。
3.
List<? super Integer> list = new ArrayList<>(); //这个示例中,使用通配符“? super Integer”来表示list可以存储Integer或其父类的数据。 //在获取list中的元素时,可以确定元素是Integer或其父类,无需进行类型转换。
注:在使用通配符时,不能对通配符进行写入操作,即不能向通配符表示的集合中添加元素。这是因为通配符表示不确定的类型,无法确定添加的元素是否符合集合的要求。如果需要进行写入操作,可以使用具体的类型来替代通配符。
例:
public class NumberBox<T extends Number> { private T item; public void set(T item) { this.item = item; } public T get() { return item; } } NumberBox<Integer> intBox = new NumberBox<>(); intBox.set(42); int value = intBox.get(); // 无需进行类型转换,因为T是Integer类型 System.out.println(value); // 输出 42
5. 泛型方法
问:使用了泛型参数的方法就是泛型方法吗?答案是否定的
public class NumberBox<T extends Number> { private T item; public void set(T item) { this.item = item; } public T get() { return item; } }
比如这里的get和set方法就不是泛型方法.
泛型方法举例:
//错误的,这里会将E当做某一个类的名字 public E(E e) { return null; } public E<E>(E e) { return null; }
所以,我们总结出泛型方法的格式
:权限修饰符 <T> 返回值类 方法名(形参列表){}
//通常在形参列表或返回值类型中会出现泛型参数T
说明:在声明泛型方法时,一定要加泛型参数
泛型方法在调用时.一定要指明其具体类型.
例:
public <E>ArrayList<E> fromArrayToList(E[] arr) { ArrayList<E> list = new ArrayList<>(); for(E e:arr) { list.add(e); } return list; }
这个方法是可以声明为静态的,因为无所谓谁来调用,只要调用时声明泛型即可.