泛型的使用

一,泛型概述

​ 关于泛型,先来说几句集合。都知道集合是可以存储任意对象,当我们创建一个集合时如果没有声明它的存储类型,那该集合便自动提升为Object类型。请参看如下代码:

public class GenericDemo {
	public static void main(String[] args) {
		Collection coll = new ArrayList();
		coll.add("abc");
		coll.add("hello");
		coll.add(5);//由于集合没有做任何限定,任何类型都可以给其中存放
        // 使用迭代器遍历集合
		Iterator it = coll.iterator();
		while(it.hasNext()){
	//需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
			String str = (String) it.next();
			System.out.println(str.length());
		}
	}
}

​ 毫无疑问,以上代码会报错,首先从代码上看在集合中存储了数值类型,字符串类型。但是在使用迭代器遍历时,取出的是String类型的,那么对于数值类型来说就必然会报错。

​ 那么以上问题该怎么解决呢?通常对于Collection集合来说,同一个集合对象只保存一种数据类型。如下代码所示:

 		// 存储字符类型
        Collection<String> stringCollection = new ArrayList<>();
        // 存储数值类型
        Collection<Integer> integerCollection = new ArrayList<>();
        // 存储精度类型
        Collection<Double> doubleCollection = new ArrayList<>();

​ 也就是说在我们初始化集合的时候,就已经将存储存储类型固定。但是按照Java的灵活性来说,这是显然不够的,比如预先并不确定要存储的类型。因此在JDK5之后新增了泛型的语法,使我们的程序显得更为灵活。

泛型:在方法或者类(接口)中预先的使用某种未知的数据类型。

提示:在我们创建对象的时候,如果没有明确指出一种数据类型,那么编译器会默认为Object类。

1.1,泛型的意义

​ 那说了这么多,泛型又能解决什么问题呢?针对上面的案例分析:

​ 1,可以将ClassCastException异常由运行期转到编译期异常。

​ 2,避免强制类型的转换。

​ 请看如上代码,在我们初始化集合时指定String数据类型,此时再存储数值类型就会在编译期报错,那么泛型又将如何运用呢。先别急,我们先看看集合中是如何定义的。

​ 在Array List集合源码中看出,它的存储类型是一个E,这是什么呢?

  • E,Elements:元素
  • T,Type:类型

这就是泛型,因为我们对于集合来说它们并不知道我们要存储是什么类型,因此这就是泛型的意义。

提示:泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。

​ 泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。

二,泛型类

​ 编写格式:

修饰符 class 类名称<泛型变量>{}

​ 我们还是以集合为例,请看如下集合源代码实现,以add方法作为参考。

​ 可以看出add方法接收的数据类型为E,与我们上一节图片中所示Array List源码实现是一致的,因为Array List接收的参数类型就是E。说这么多有些啰嗦,现在先定义一个泛型类。

public class GenericClass<T> {
    private T key;
   public void setKey(T key){
       this.key = key;
   }
   public T getKey(){
       return key;
   }
}
public class GenericDemo {
    public static void main(String[] args) {

       GenericClass<String> genericString = new GenericClass<>();
       // 存储字符类型
        genericString.setKey("这是一个泛型类");
       // 获取数据
        String stringKey = genericString.getKey();
        System.out.println(stringKey);
    
        // 存储数值类型
        GenericClass<Integer> genericInteger = new GenericClass<>();
        // 存储数值1
        genericInteger.setKey(1);
        // 获取数值
        Integer integerKey = genericInteger.getKey();
        System.out.println(integerKey);
    }
}

三,泛型方法

​ 编写格式:

修饰符 <泛型变量> 返回值类型 方法名称(参数){}

​ 例如定义泛型方法:

public class GenericMethod {
    // 定义泛型方法
    public <T> void menthod(T t) {
        System.out.println(t.getClass());
    }
    // 返回实例化对象
    public <T> T getGenericMethod(Class<T> t) throws IllegalAccessException, InstantiationException {
        return t.newInstance();
    }
    public static void main(String[] args) throws Exception {
        GenericMethod genericMethod = new GenericMethod();
        genericMethod.menthod("abc");
        Object method = genericMethod.getGenericMethod(Class.forName("com.api.generic.User"));
        System.out.println(method);
    }
}

四,泛型接口

​ 编写格式:

修饰符 interface 接口名称<泛型变量>{}

​ 定义泛型接口:

public interface GenericInterface<T> {

    void add(T t);

    T getT();
}

​ 如果泛型接口的实现类仍不确定,则还可以接着使用泛型。

public class GenericInterfaceImpl<T> implements GenericInterface<T> {
    @Override
    public void add(T t) {
    }

    @Override
    public T getT() {
        return null;
    }
}

​ 只有在创建对象的时候,要确定泛型的数据类型。

GenericInterfaceImpl<String> genericInterface = new GenericInterfaceImpl<>();
        genericInterface.add("Hello");

五,泛型通配符

​ 当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

通配符基本使用

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。

此时只能接收数据,不能往该集合中存储数据。

举个例子大家理解使用即可:

public static void main(String[] args) {
    Collection<Intger> list1 = new ArrayList<Integer>();
    getElement(list1);
    Collection<String> list2 = new ArrayList<String>();
    getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型
注意:泛型不存在继承关系 Collection<Object> list = new ArrayList<String>();这种是错误的。

六,泛型上下限

​ 之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限下限

泛型的上限

  • 格式类型名称 <? extends 类 > 对象名称
  • 意义只能接收该类型及其子类

泛型的下限

  • 格式类型名称 <? super 类 > 对象名称
  • 意义只能接收该类型及其父类型

比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类

public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    Collection<String> list2 = new ArrayList<String>();
    Collection<Number> list3 = new ArrayList<Number>();
    Collection<Object> list4 = new ArrayList<Object>();
    
    getElement(list1);
    getElement(list2);//报错
    getElement(list3);
    getElement(list4);//报错
  
    getElement2(list1);//报错
    getElement2(list2);//报错
    getElement2(list3);
    getElement2(list4);
  
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}

七,泛型擦除

​ 在泛型的使用中还有一项重要的概念,就是泛型擦除。

​ 这里就不再分享了,有一篇写的很好的博客关于泛型擦除的,有兴趣的可以去看看。

​ 转载自:https://blog.csdn.net/briblue/article/details/76736356

八,总结

​ 总体来说泛型的出现为我们的代码带来很多便利,使程序更加灵活。并且在官方文档中提出如果能使用泛型就尽量使用泛型,提高代码的可读性。

​ 这次关于泛型的总结相对来说并不深入,只是一些简单的描述。最后,在本文中的内容如有不适之处,欢迎多多指点。

感谢阅读!

posted @ 2019-09-06 14:35  奋进的小样  阅读(1557)  评论(0编辑  收藏  举报