Java中的泛型编程

引言

什么是泛型?

泛型的意思是  类型参数化。

到底什么是类型参数化呢?通过这一节内容,我们希望大家能够彻底弄懂什么是泛型,以及如何在开发中使用泛型。

 

Java泛型应用是java核心基础之一,从java5开始引入泛型概念。如果你曾经使用过java中的collection相关的类,那么就算你已经接触过泛型了。在java的Collection中使用泛型是一件很简单的事情,可泛型还具有许多你想不到的作用。在深入了解泛型之前,首先来了解一下泛型的一些基本概念与原理。

1.泛型的引入

    Java泛型的应用可以提高代码的复用性,同时泛型提供了类型检查,减少了数据的类型转换,同时保证了类型的安全。下面来看一下,泛型如何保证了类型的安全:

  List list = new ArrayList();

    list.add("abc");

    list.add(new Integer(1)); //可以通过编译

    for (Object object : list) {

           System.out.println((String)object);//抛出ClassCastException异常

    }

 上面的代码会在运行时抛出ClassCastException,因为它尝试将一个Integer转换为String。接着,来看一下从java5开始,Collection的用法:

 List list = new ArrayList<>();

 list.add("abc");

 //list.add(new Integer(1)); //编译错误

 for (String string : list) {

      System.out.println(string);//无需任何强制类型转换

 }

注意到,List的创建增加了类型参数String,因此只能向list添加String类型对象,添加其他对象会抛出编译异常;同样可以注意到,foreach循环不需要再添加任何强制类型转换,也就移除了运行时的ClassCastException异常。

2.泛型类与接口

既然是学泛型,自然就要知道如何去使用泛型定义自己的类和接口。同时为了加深理解泛型的作用,我们先定义一个不适用泛型的类:

public class Gen {

     private Object obj;

     public Object getObj() {

        return obj;

     }

     public void setObj(Object obj) {

        this.obj = obj;

     }

     public static void main(String[] args) {

        Gen gen = new Gen();

        gen.setObj("abc");

        String str = (String) gen.getObj();//类型转换,可能会引起运行时ClassCastException

       }

}

原始类的定义,容易引发ClassCastException,因为在使用的时候我们无法知道具体的类型到底是什么。现在来看一下泛型类来重新定义Gen — 使用<>指定泛型参数,如下

public class Gen {

T obj;

public T getObj() {

return obj;

}

public void setObj(T obj) {

this.obj = obj;

}

public static void main(String[] args) {

Gen gen = new Gen<>();

gen.setObj("abc");

// gen.setObj(10); //无法通过编译

String str = gen.getObj(); //无需类型转换

//-----------------------------

Gen gen2 = new Gen();//raw type原始类型

gen2.setObj("abc");

gen2.setObj(10); //可以通过编译,自动装箱将10转化为Integer对象

Integer num = (Integer) gen2.getObj();//使用了强制类型转换

}

}

细心的你会发现在main()方法里是使用泛型类型Gen,便不再需要强制类型转换,也就移除了运行时的ClassCastException。同时为了区别,在此也定义了一个没有使用泛型类型的gen2,这时,编译器会弹出一个警告“Gen is a raw type,References to generic type Gen should be parameterized”。当我们不提供泛型类型时,会默认使用Object会代替,也是因此这样,gen2可以设置String和Integer类型,不过,我们应尽量去避免这种这种情况的出现,如此,便又需要用到强制类型转换,也伴随着运行时的ClassCastException异常。

ps:可以使用@SuppressWarnings("rawtypes")来忽略编译器弹出警告。

接口的泛型应用和类的泛型应用非常类似:

public interface List  {

 void add(E x);

 Iterator iterator();

}

 

public interface Iterator {

 E next();

 boolean hasNext();

}

另外,在定义泛型类和泛型接口的时候,我们也可以定义多个泛型化参数。例如Java中的Map

3.泛型类的使用

在使用泛型类的时候,我们就需要将泛型参数具体化,例如:

public static void main(String[] args) {

ArrayList list = new ArrayList<>();

list.add("ABC");

list.add(123);//报错!由于泛型已经具体化成String类型,就不能使用整数类型了

}

需要注意的是,当我们将泛型参数具体化成String类型之后,原本ArrayList中的add(E e)方法的参数类型就会变成String类型,这时候在调用add()方法的时候,就只能传入String类型的参数了。

另外,在Java中明确的规定了泛型参数在具体化的时候只能使用引用类型,所以是有的泛型参数必须使用Object类型及其子类类型,如果使用基本类型,就会出错:

//这里不能使用基本类型来具体化泛型参数

ArrayList<int> list = new ArrayList<>();

泛型类的继承和泛型接口的实现

我们在实现泛型接口的时候,也必须要定义泛型类去实现泛型接口。例如:

public class ArrayList implements List{

 

}

4.泛型的命名规范

  为了更好地去理解泛型,我们也需要去理解java泛型的命名规范。为了与java关键字区别开来,java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:

    E — Element,常用在java Collection里,如:List,Iterator,Set

    K,V — Key,Value,代表Map的键值对

    N — Number,数字

    T — Type,类型,如String,Integer等等

    S,U,V etc. - 2nd, 3rd, 4th 类型,和T的用法一样。

    当然,你如果硬是要标新立异,使用其它的字母或单词,也是可以使用的,只是这样,代码的规范度就大大下降了。

5.泛型方法与构造方法

    有些时候,我们可能并不希望将整个类都泛型化。这个时候我们就可以只在某个方法上定义泛型,构造方法也是一样。例如:

public class GenMethod {

    public static  void fromArrayToCollection(T[] a,Collection c){

        for (T t : a) {

              c.add(t);

        }

    }

    public static void main(String[] args) {

        Object[] oa = new Object[100];

        Collection co = new ArrayList<>();

        GenMethod.fromArrayToCollection(oa, co);

   }

}

 

 GenMethod 的代码不多,不过需要注意的地方却不少。第一、定义方法所用的泛型参数需要在修饰符之后添加,如上面的,public static ,如果有多个泛型参数,可如此定义或者。第二,不建议在泛型变量里添加其他类型,如下面的代码,将会引起编译错误(或隐含错误),如下:

public static > void fromArrayToCollection(T[] a,Collection c){

    for (T t : a) {

        c.add(t);

        c.add(new Object());

    }

}

泛型构造方法的定义和泛型方法类似:

public class Gen {

    public  Gen(T t){

 

    }

}

6.泛型的继承与子类型

如果两个类之间有继承被被继承的关系,那么我们就可以将一个类的对象赋值类另外一个类的对象,比如:

String str = new String();

Object obj = new Object();

obj = str;

这种关系同样适用于泛型。比如我们将泛型参数设置为Number,那么在随后的调用中,就只需要传入一个Number类型或者是Number的子类类型的对象就行了,比如Integer,Float,Double都可以:

ArrayList list = new ArrayList<>();

list.add(new Integer(1));

list.add(new Float(1.0));

list.add(new Double(1.0));

但是有一种情况是我们需要特别注意的,比如我们定义一个如下的方法:

public void someMethod(ArrayList n) {

}

这个方法能接受什么样类型的参数类?

ArrayList?

ArrayList?

ArrayList?

显然这个方法接受ArrayList类型的参数是没有问题的?而ArrayList或者ArrayList类型都不行,原因是:虽然Integer和Double是Number类型的子类,但是ArrayList和ArrayList类型并不是ArrayList类型的子类。

        Java中的泛型编程_第1张图片

在泛型里也存在子类型,前提是其泛型参数的限制并没有发生改变,或者说泛型没有改变,其实就是从原来的类或接口来判断泛型的子类型。比如ArrayLIst implements List,而List extends Collection,那么ArrayList就是List的子类型,而List又是Collection的子类型。

        Java中的泛型编程_第2张图片

 

7.泛型参数界限与通配符

有时候,你会希望泛型类型只能是某一部分类型,比如在操作数据的时候,你希望是Number或其子类类型。这个通常的做法就是给泛型参数添加一个参数。其定义的形式为:

extends ParentType>

这个定义表示T应该是Numner的子类型,T和Parant可以是类,也可以是接口,注意此处的extends表示的是ParentType的子类型,和继承是有区别的。

public class Box {

    private T t;

    public void set(T t) {

        this.t = t;

    }

    public T get() {

        return t;

    }

    public extends Number> void inspect(U u) {

        System.out.println("T: " + t.getClass().getName());

        System.out.println("U: " + u.getClass().getName());

    }

    public static void main(String[] args) {

        Box integerBox = new Box<>();

        integerBox.set("abc"); //能通过编译,因为T指定为String类型

        // integerBox.inspect("abc");//不能通过编译,因为U必须是Number类型或其子类

        integerBox.inspect(new Integer(10));

    }

}

你可能感兴趣的:(JavaSE,Java,Java基础,泛型)