目录
泛型是一个未知的数据类型,当我们不确定什么什么数据类型的时候,可以使用泛型泛型可以接收任意的数据类型。
定义使用泛型的类:
class 类名<泛型名称> extends .... implements ....{
}
这样就可以将泛型当作一个数据类型来使用。
示例:
class Demo<E>{
E name;
}
该类可定义普通的不含泛型的方法,也可定义使用泛型的静态方法或实例方法。
当创建该类的对象时,可以确定泛型的数据类型:
class Main {
public static void main(String[] args) {
Demo<String> a1 = new Demo<String>(); // 这样name变量就是String类型的
Demo<Student> b1 = new Demo<>(); // 第二个尖括号的类型可省略
// Demo<> b1 = new Demo<Student>(); 但是这样不行
}
}
class Demo<E>{
E name;
}
定义使用泛型的方法:
如果类没有定义泛型:
修饰符列表 <泛型> 返回值类型 方法名 ( 参数列表 ){
方法体;
}
但如果类定义了泛型,那么方法可以之间使用该泛型,因为该泛型现在已经看成是一个具体的类型了,如:
public E kk(E e){
}
参数列表可以使用泛型来定义变量。
在调用方法的时候,传递什么类型的参数,泛型就是什么类型。
注意:静态方法不可以访问类上定义的泛型,所以定义泛型的静态方法和定义泛型的实例方法有所区别:
class Demo<E>{ public static <E> E ff1(E e){ return e; } public static <SuiBian> SuiBian ff2(SuiBian e){ return e; } public E ff3(E e){ // 使用类定义的泛型 return e; } public <T> T ff4(T e){ // 使用自定义的泛型 return e; } }
在调用静态方法时,我们可以指定方法的泛型:
class Demo{
public static void main(String[] args) {
System.out.println(A.<String>get("haha","nihao"));
}
}
class A{
public static <T> T get(T...a){
return a[a.length/2];
}
}
大多数情况下,编译器可以通过所提供的实参来判断泛型应该取什么类型,但在某些特殊情况下我们也需要指定某个类型。
如:
class Demo{
public static void main(String[] args) {
System.out.println(A.<Number>get(1.1,100));
}
}
class A{
public static <T> T get(T...a){
return a[a.length/2];
}
}
因为如果不指定泛型,编辑器发现这个代码有两种解释方式,且都是合法的。简单来说,编译器将参数自动装箱为一个Integer类型和一个Double类型,为了兼容,它会去查找这些类的共同父类,他找到了两个父类,一个是Number抽象类,一个是Comparable接口,在这种情况下,如果我们不想改变我们所传入的值,那么我们就要指定一个他们的共同父类。
同样的,实例方法也可以这样做:
class Demo{
public static void main(String[] args) {
System.out.println(new A().<Number>get(1.1,100));
System.out.println(new B().<Number>get(1.1,100));
}
}
class A{
public <T> T get(T...a){
return a[a.length/2];
}
}
class B<T>{
public T get(T...a){
return a[a.length/2];
}
}
传递给泛型的类型:
1、传递给泛型类型时必须使用构造数据类型,不能传递基本数据类型给泛型。所以这时候就要用到我们的包装类,int类型可以传递Integer类型,有无自定义的类也是构造数据类型,所以也可以传递类名,如student等。
2、当在调用具有泛型的类或方法时,可以选择不传递任何类型,这样泛型默认为Object类型。
定义使用泛型的接口:
interface 接口名<泛型名称> {
}
实现类可以指定泛型的类型,这样所有使用了该泛型的抽象方法都有了具体的类型,如:
class 类名 implement 接口名<String> {
}
实现类也可以不指定泛型的类型,等创建类对象或调用方法的时候再指定,如:
class 类名<泛型名称> implement 接口名<泛型名称> {
}
当然如果是函数式接口,那么还可以使用lambda来实现:
class Main{
public static void main(String[] args) {
Demo<String> d1 = (s)->{ // 指定类型
return s;
};
Demo d2 = (s)->{ // 不指定类型
return s;
};
}
}
interface Demo<E>{
E ff3(E e);
}
同时定义多个泛型的类型变量:
class Demo<E,T>{
E name;
T age;
}
可在尖括号内定义多个泛型的类型变量。
泛型通配符:
假设我们要定义一个可以遍历所有ArrayList集合的方法,且该方法可以对存储任何类型的集合使用,那么这时候就要用到泛型的通配符:
public static <T> void printArray(T arrayList){
Iterator iterator = arrayList.iterator(); // 报错。找不到iterator方法
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
在上述代码中,因为Java不知道 T 是什么类型的,所以不知道 T 类中有无iterator这个方法,所以报错。但如果为ArrayList<String>类型的形参定义一个方法,那就“写死了”,不能对ArrayList<Integer>类型的参数进行操作,但如果使用方法重载又要浪费大量的代码得不偿失。这时候就可以用到我们的通配符来表示泛型的未知类型了:
import java.util.ArrayList;
import java.util.Iterator;
class Main{
public static void main(String[] args) {
ArrayList<String> s = new ArrayList<>();
s.add("AA");
s.add("BB");
ArrayList<Integer> i = new ArrayList<>();
i.add(100);
i.add(200);
printArray(s);
printArray(i);
}
public static void printArray(ArrayList<?> arrayList){
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
/*
public static void printArray(ArrayList<Integer> arrayList){ 只能遍历Integer类型的集合
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
public static void printArray(ArrayList<String> arrayList){ 只能遍历String类型的集合
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
*/
}
那么为什么不能把 printArray 方法的参数类型设置成 ArrayList<Object> 呢?因为在泛型中,是没有继承这个概念的。
当然我们也可以使用这样的形式:
public static <E> void Demo(Collection<E> c){
}
但这样就必须在方法中定义使用的泛型了。一般如果在方法中不使用Collection的<>中的类型的话,例如调用变量c的方法,一般使用通配符 ? 即可。
传递类型给泛型时是不允许使用通配符的,即不能创建对象时使用,只能作为方法的参数使用,表示未知的泛型类型。
ArrayList<?> unKnow = new ArrayList<>();
unKnow.add("aa"); // 报错,'java.util.ArrayList' 中的 'add(capture<?>)' 无法应用于 '(java.lang.String)'
通配符不是类型变量,所以也不能作为一种类型使用:
? t = p.getFirst(); // 报错
泛型的限定:
如果需要的话,通配符还可以这样定义:
public static void printArray(ArrayList<? extends Number> arrayList){
Iterator iterator = arrayList.iterator(); 限定泛型所属的类必须是Number的子类
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
public static void printArray(ArrayList<? super Number> arrayList){
Iterator iterator = arrayList.iterator(); 限定泛型所属的类必须是Number的父类
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
public static <T extends Comparable> Pair<T> maxmin(){
...... // 限定泛型必须是Comparable的子类,即实现了Comparable接口
return new Pair<>(min,max)
}
这样会对泛型的类型加以限定。
注意:在Java的限定中,无论是不是接口,都是用extends关键词表示,不会用implement关键词。
代码示例:
import java.util.ArrayList;
class Main{
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
ArrayList<Number> list3 = new ArrayList<>();
ArrayList<Object> list4 = new ArrayList<>();
// Integer extends Number extends Object
// String extends Object
Extend(list1);
Extend(list2); // 报错
Extend(list3);
Extend(list4); // 报错
Super(list1); // 报错
Super(list2); // 报错
Super(list3);
Super(list4);
}
public static void Extend(ArrayList<? extends Number> a){
}
public static void Super(ArrayList<? super Number> a){
}
}
不只是通配符,其余泛型类型也可以使用限定:
类:
class Demo<T extends List> {
}
方法:
public static <T extends Number> void Demo(T c){
}
接口:
interface demo<T extends Number>{
}
我们可以对一个泛型类型or通配符进行多个限制:
public static <T extends ArrayList & Comparable> Pair<T> maxmin(){
...... // 限定泛型必须是ArrayList的子类,还实现了Comparable接口
return new Pair<>(min,max)
}
注意:在对泛型的限定中,我们最多只能限定一个类,但可以限定多个接口。且一个类作为限定,那么这个类必须放在限定的首位。
public static <T extends Comparable & ArrayList> Pair<T> maxmin(){
...... // 报错!
return new Pair<>(min,max)
}