首页 > 编程笔记

Java泛型用法详解

泛型(Generics)是指在类定义时不指定类中信息的具体数据类型,而是用一个标识符来代替,当外部实例化对象时来指定具体的数据类型。

有了泛型,我们就可以在定义类或者接口时不明确指定类中信息的具体数据类型,在实例化时再来指定具体的数据类型。这样极大地提高了类的扩展性,一个类可以装载各种不同的数据类型,泛型可以指代类中的成员变量数据类型,方法的返回值数据类型以及方法的参数数据类型。

为什么要使用泛型呢?来看下面这个例子,我们知道一个集合可以存储不同数据类型的数据,实例化一个 ArrayList 集合对象,并向该集合中保存两个数据,int 类型的 1 和 String 类型的 "Hello"。
public class Test {
   public static void main(String[] args) {
      ArrayList<Integer> list = new ArrayList<Integer>();
      list.add(1);
      list.add("Hello");
      for(int i = 0; i < list.size(); i++) {
         int num = (int) list.get(i);
         System.out.println(num+1);
      }
   }
}
代码出现编译错误,如下图所示。


因为此时 list 指定了泛型 Integer,所以 String 类型的"Hello"无法存入集合,尝试修改程序:
public class Test {
   public static void main(String[] args) {
      ArrayList<Integer> list = new ArrayList<Integer>();
      list.add(1);
      list.add(2);
      for(int i = 0; i < list.size(); i++) {
         int num = (int) list.get(i);
         System.out.println(num+1);
      }
   }
}
运行结果为:

2
3

泛型的应用

我们除了可以在实例化集合时指定泛型外,自定义的类也可以添加泛型,基本语法如下:
访问权限修饰符 class 类名<泛型标识1, 泛型标识2...>{
    访问权限修饰符 泛型标识 属性名;
    访问权限修饰符 泛型标识 方法名(泛型标识 参数名...){}
}
例如我们自定义一个表示时间的类 Time,代码为:
public class Time<T> {
   private T value;
   public T getValue() {
      return value;
   }
   public void setValue(T value) {
      this.value = value;
   }
}

public class Test {
   public static void main(String[] args) {
      Time<Integer> time1 = new Time<Integer>();
      time1.setValue(10);
      System.out.println("现在的时间是:"+time1.getValue());
      Time<String> time2 = new Time<String>();
      time2.setValue("十点整");
      System.out.println("现在的时间是:"+time2.getValue());
   }
}
运行结果为:

现在的时间是:10
现在的时间是:十点整


在定义一个类时可以同时指定多个泛型标识,例如:
public class Time<H,M,S> {
   private H hour;
   private M minute;
   private S second;
   //getter、setter方法
}

public class Test {
   public static void main(String[] args) {
      Time<String,Integer,Float> time = new Time<String,Integer,Float>();
      time.setHour("十点");
      time.setMinute(10);
      time.setSecond(10.0f);
      System.out.println("现在的时间是:"+time.getHour()+":"+time.getMinute()+ ":"+time.getSecond());
   }
}
运行结果为:

现在的时间是:十点:10:10.0

泛型通配符

如果我们在定义一个参数为 ArrayList 类型的方法时,希望该方法既可以接收泛型为 String 的集合参数,也可以接收泛型为 Integer 的集合参数,那应该怎么处理呢?

可以用多态的思想来定义该方法,即使用 Object 来定义参数泛型,代码如下:
public class Test {
   public static void main(String[] args) {
      ArrayList<String> list1 = new ArrayList<String>();
      ArrayList<Integer> list2 = new ArrayList<Integer>();
      test(list1);
      test(list2);
   }
   public static void test(ArrayList<Object> list) {
      System.out.println(list);
   }
}
此时,代码出现了编译错误,如下图所示。


list1 和 list2 无法匹配 test() 方法的参数类型,String 和 Integer 在泛型引用中不能转换为 Object。

所以 test() 方法的参数泛型不能设置为 Object,如何解决呢?这个问题可以通过泛型通配符来处理,用 ? 表示当前未知的泛型类型,例如:
public class Test {
   public static void main(String[] args) {
      ArrayList<String> list1 = new ArrayList<String>();
      ArrayList<Integer> list2 = new ArrayList<Integer>();
      test(list1);
      test(list2);
   }
   public static void test(ArrayList<?> list) {
      System.out.println(list);
   }
}
ArrayList<?> 表示可以使用任意的泛型类型对象,这样 test() 方法就具有通用性了。

泛型上限和下限

我们在使用泛型时,往往数据类型会有限制,只能使用一种具体的数据类型。如果希望在此基础之上进行适量扩容,可以通过泛型上限和下限来完成。

泛型上限表示实例化时的具体数据类型,可以是上限类型的子类或者是上限类型本身,用 extends 关键字来修饰。泛型下限表示实例化时的具体数据类型可以是下限类型的父类或者是下限类型本身,用super关键字来修饰,基本语法如下:
// 泛型上限
类名<泛型标识 extends 上限类名>
// 泛型下限
类名<泛型标识 super 下限类名>
例如:
public class Time<T> {
   public static void main(String[] args) {
      test(new Time<Integer>());
      test(new Time<String>());
      test2(new Time<String>());
      test2(new Time<Integer>());
   }
   /*
    * 参数的泛型只能是Number或者其子类,即Number,Byte,Short
    * Long,Integer,Float,Double
    */
   public static void test(Time<? extends Number> time) {

   }
   /*
    * 参数的泛型只能是String或者其父类,即String和Object
    */
   public static void test2(Time<? super String> time) {

   }
}
代码出现编译错误,如下图所示:

泛型接口

我们在定义类时可以添加泛型,在定义接口时也可以添加泛型。

声明泛型接口的语法和声明泛型类很相似,在接口名后加上 <T> 即可,基本语法如下:
访问权限修饰符 interface 接口名<泛型标识>
具体实现如下:
public interface MyInterface<T> {
   public T getValue();
}
实现泛型接口有两种方式,一种是实现类在定义时继续使用泛型标识,另一种是实现类在定义时直接给出具体的数据类型,实现代码如下:
public class MyInterfaceImpl<T> implements MyInterface<T>{
   private T obj;
   //getter、setter方法

   public MyInterfaceImpl(T obj) {
      this.obj = obj;
   }
   @Override
   public T getValue() {
      // TODO Auto-generated method stub
      return this.obj;
   }
}

public class MyInterfaceImpl2 implements MyInterface<String>{
   private String obj;
   //getter、setter方法  

   public MyInterfaceImpl2(String obj) {
      this.obj = obj;
   }
   @Override
   public String getValue() {
      // TODO Auto-generated method stub
      return this.obj;
   }
}

两种不同实现类的实例化方式也不同,一种需要在实例化时指定具体的数据类型,另一种在实例化时不需要指定具体的数据类型,如代码所示:
public class Test {
   public static void main(String[] args) {
      MyInterface<String> myInterface = new MyInterfaceImpl<String>("接口");
      String value1 = myInterface.getValue();
      MyInterface myInterface2 = new MyInterfaceImpl2("接口");
      String value2 = myInterface2.getValue();
   }
}

推荐阅读