首页 > 编程 > Java > 正文

Java之泛型

2019-11-08 19:26:27
字体:
来源:转载
供稿:网友

为什么需要泛型?

首先,我们看下下面这段简短的代码:

public class GenericTest { public static void main(String[] args) { List list = new ArrayList(); list.add("QQyumidi"); list.add("corn"); list.add(100); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); //1 System.out.PRintln("name:" + name); } }}

定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值,这是完全允许的,因为此时list默认的类型为Object类型,在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误,因为编译阶段正常,而运行时会出现”java.lang.ClassCastException”异常,因此,导致此类错误编码过程中不易发现

那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现”java.lang.ClassCastException”异常呢?答案就是使用泛型

什么是泛型?

泛型,即”参数化类型”,一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参,那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)

看着好像有点复杂,首先我们看下上面那个例子采用泛型的写法:

public class GenericTest { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("qqyumidi"); list.add("corn"); list.add(100); //1:提示编译错误 for (int i = 0; i < list.size(); i++) { String name = list.get(i); //2 System.out.println("name:" + name); } }}

采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List<String>,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了

基本介绍和使用

泛型类:

容器类应该算得上最具重用性的类库之一,先来看一个没有泛型的情况下的容器类如何定义:

public class Container { private String key; private String value; public Container(String k, String v) { key = k; value = v; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; }}

Container类保存了一对key-value键值对,但是类型是定死的,也就说如果我想要创建一个键值对是String-Integer类型的,当前这个Container是做不到,必须再自定义,那么这明显重用性就非常低,所以使用泛型:

public class Container<K, V> { private K key; private V value; public Container(K k, V v) { key = k; value = v; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; }}

在编译期,是无法知道K和V具体是什么类型,只有在运行时才会真正根据类型来构造和分配内存,可以看一下现在Container类对于不同类型的支持情况

public class Main { public static void main(String[] args) { Container<String, String> c1 = new Container<String, String>("name", "findingsea"); Container<String, Integer> c2 = new Container<String, Integer>("age", 24); Container<Double, Double> c3 = new Container<Double, Double>(1.1, 2.2); System.out.println(c1.getKey() + " : " + c1.getValue()); System.out.println(c2.getKey() + " : " + c2.getValue()); System.out.println(c3.getKey() + " : " + c3.getValue()); }}

泛型接口:

在泛型接口中,生成器是一个很好的理解,看如下的生成器接口定义:

public interface Generator<T> { public T next();}

然后定义一个生成器类来实现这个接口:

public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; }}

调用:

public class Main { public static void main(String[] args) { FruitGenerator generator = new FruitGenerator(); System.out.println(generator.next()); System.out.println(generator.next()); System.out.println(generator.next()); System.out.println(generator.next()); }}

泛型方法:

一个基本的原则是:无论何时,只要你能做到,你就应该尽量使用泛型方法,也就是说,如果使用泛型方法可以取代将整个类泛化,那么应该有限采用泛型方法,下面来看一个简单的泛型方法的定义:

public class Main { public static <T> void out(T t) { System.out.println(t); } public static void main(String[] args) { out("findingsea"); out(123); out(11.11); out(true); }}

可以看到方法的参数彻底泛化了,这个过程涉及到编译器的类型推导自动打包,也就说原来需要我们自己对类型进行的判断和处理,现在编译器帮我们做了,这样在定义方法的时候不必考虑以后到底需要处理哪些类型的参数,大大增加了编程的灵活性

再看一个泛型方法和可变参数的例子:

public class Main { public static <T> void out(T... args) { for (T t : args) { System.out.println(t); } } public static void main(String[] args) { out("findingsea", 123, 11.11, true);//输出和前一段代码相同,可以看到泛型可以和可变参数非常完美的结合 }}

其他的使用参考博客:

泛型与通配符详解:http://www.cnblogs.com/fengmingyue/p/6087031.html

深入解读泛型:http://blog.csdn.net/basycia/article/details/50474774


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表