java 泛型编程_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<String> 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> {

T obj;

public T getObj() {

return obj;

}

public void setObj(T obj) {

this.obj = obj;

}

public static void main(String[] args) {

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

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

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

public interface List <E> {

void add(E x);

Iterator<E> iterator();

}

public interface Iterator<E> {

E next();

boolean hasNext();

}

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

3.泛型类的使用

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

public static void main(String[] args) {

ArrayList<String> 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<E> implements List<E>{

}

4.泛型的命名规范

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

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

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 <T> void fromArrayToCollection(T[] a,Collection<T> c){

for (T t : a) {

c.add(t);

}

}

public static void main(String[] args) {

Object[] oa = new Object[100];

Collection<Object> co = new ArrayList<>();

GenMethod.<Object>fromArrayToCollection(oa, co);

}

}

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

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

for (T t : a) {

c.add(t);

c.add(new Object());

}

}

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

public class Gen {

public <T> Gen(T t){

}

}

6.泛型的继承与子类型

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

String str = new String();

Object obj = new Object();

obj = str;

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

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

list.add(new Integer(1));

list.add(new Float(1.0));

list.add(new Double(1.0));

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

public void someMethod(ArrayList<Number> n) {

}

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

ArrayList<Nunber>?

ArrayList<Integer>?

ArrayList<Double>?

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

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

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

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

<T extends ParentType>

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

public class Box<T> {

private T t;

public void set(T t) {

this.t = t;

}

public T get() {

return t;

}

public <U 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<String> integerBox = new Box<>();

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

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

integerBox.inspect(new Integer(10));

}

}

6ba9be44c0abee161bf1864e7b0f06ae.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值