泛型就这么简单

前言

从今天开始进入Java基础的复习,可能一个星期会有一篇的<十道简单算法>,我写博文的未必都是正确的~若是有写错的地方请你们多多包涵并指正~java

今天要复习的是泛型,泛型在Java中也是个很重要的知识点,本文主要讲解基础的概念,并非高深的知识,若是基础好的同窗能够当复习看看~算法

1、什么是泛型?

Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.安全

泛型:把类型明确的工做推迟到建立对象或调用方法的时候才去明确的特殊的类型微信

参数化类型:session

  • 把类型看成是参数同样传递
  • <数据类型> 只能是引用类型

相关术语:ide

  • ArrayList<E>中的E称为类型参数变量
  • ArrayList<Integer>中的Integer称为实际类型参数
  • 整个称为ArrayList<E>泛型类型
  • 整个ArrayList<Integer>称为参数化的类型ParameterizedType

2、为何须要泛型

早期Java是使用Object来表明任意类型的,可是向下转型有强转的问题,这样程序就不太安全学习

首先,咱们来试想一下:没有泛型,集合会怎么样测试

  • Collection、Map集合对元素的类型是没有任何限制的。原本个人Collection集合装载的是所有的Dog对象,可是外边把Cat对象存储到集合中,是没有任何语法错误的。
  • 把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。所以在get()的时候,返回的是Object。外边获取该对象,还须要强制转换

有了泛型之后:this

  • 代码更加简洁【不用强制转换】
  • 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
  • 可读性和稳定性【在编写集合的时候,就限定了类型】

2.1有了泛型后使用加强for遍历集合

在建立集合的时候,咱们明确了集合的类型了,因此咱们可使用加强for来遍历集合!spa

//建立集合对象
        ArrayList<String> list = new ArrayList<>();

        list.add("hello");
        list.add("world");
        list.add("java");

        //遍历,因为明确了类型.咱们能够加强for
        for (String s : list) {
            System.out.println(s);
        }

复制代码

3、泛型基础

3.1泛型类

泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来....这样的话,用户明确了什么类型,该类就表明着什么类型...用户在使用的时候就不用担忧强转的问题,运行时转换异常的问题了。

  • 在类上定义的泛型,在类的方法中也可使用!
/* 1:把泛型定义在类上 2:类型变量定义在类上,方法中也可使用 */
public class ObjectTool<T> {
    private T obj;

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}
复制代码
  • 测试代码:

用户想要使用哪一种类型,就在建立的时候指定类型。使用的时候,该类就会自动转换成用户想要使用的类型了。

public static void main(String[] args) {
        //建立对象并指定元素类型
        ObjectTool<String> tool = new ObjectTool<>();

        tool.setObj(new String("钟福成"));
        String s = tool.getObj();
        System.out.println(s);


        //建立对象并指定元素类型
        ObjectTool<Integer> objectTool = new ObjectTool<>();
        /** * 若是我在这个对象里传入的是String类型的,它在编译时期就经过不了了. */
        objectTool.setObj(10);
        int i = objectTool.getObj();
        System.out.println(i);
    }
复制代码

3.2泛型方法

前面已经介绍了泛型类了,在类上定义的泛型,在方法中也可使用.....

如今呢,咱们可能就仅仅在某一个方法上须要使用泛型....外界仅仅是关心该方法,不关心类其余的属性...这样的话,咱们在整个类上定义泛型,未免就有些大题小做了。

  • 定义泛型方法....泛型是先定义后使用的
//定义泛型方法..
    public <T> void show(T t) {
        System.out.println(t);

    }

复制代码
  • 测试代码:

用户传递进来的是什么类型,返回值就是什么类型了

public static void main(String[] args) {
        //建立对象
        ObjectTool tool = new ObjectTool();

        //调用方法,传入的参数是什么类型,返回值就是什么类型
        tool.show("hello");
        tool.show(12);
        tool.show(12.5);

    }

复制代码

3.3泛型类派生出的子类

前面咱们已经定义了泛型类,泛型类是拥有泛型这个特性的类,它本质上仍是一个Java类,那么它就能够被继承

那它是怎么被继承的呢??这里分两种状况

  1. 子类明确泛型类的类型参数变量
  2. 子类不明确泛型类的类型参数变量

3.3.1子类明确泛型类的类型参数变量

  • 泛型接口
/* 把泛型定义在接口上 */
public interface Inter<T> {
    public abstract void show(T t);

}

复制代码
  • 实现泛型接口的类.....
/** * 子类明确泛型类的类型参数变量: */

public class InterImpl implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);

    }
}

复制代码

3.3.2子类不明确泛型类的类型参数变量

  • 当子类不明确泛型类的类型参数变量时,外界使用子类的时候,也须要传递类型参数变量进来,在实现类上须要定义出类型参数变量
/** * 子类不明确泛型类的类型参数变量: * 实现类也要定义出<T>类型的 * */
public class InterImpl<T> implements Inter<T> {

    @Override
    public void show(T t) {
        System.out.println(t);

    }
}

复制代码

测试代码:

public static void main(String[] args) {
        //测试第一种状况
        //Inter<String> i = new InterImpl();
        //i.show("hello");

        //第二种状况测试
        Inter<String> ii = new InterImpl<>();
        ii.show("100");

    }
复制代码

值得注意的是:

  • 实现类的要是重写父类的方法,返回值的类型是要和父类同样的!
  • 类上声明的泛形只对非静态成员有效

3.4类型通配符

为何须要类型通配符????咱们来看一个需求.......

如今有个需求:方法接收一个集合参数,遍历集合并把集合元素打印出来,怎么办?

  • 按照咱们没有学习泛型以前,咱们可能会这样作:
public void test(List list){


	for(int i=0;i<list.size();i++){
		
		System.out.println(list.get(i));
	
	}
}


复制代码

上面的代码是正确的,只不过在编译的时候会出现警告,说没有肯定集合元素的类型....这样是不优雅的...

  • 那咱们学习了泛型了,如今要怎么作呢??有的人可能会这样作:
public void test(List<Object> list){


	for(int i=0;i<list.size();i++){
		
		System.out.println(list.get(i));
	
	}
}

复制代码

这样作语法是没毛病的,可是这里十分值得注意的是:该test()方法只能遍历装载着Object的集合!!!

强调:泛型中的<Object>并非像之前那样有继承关系的,也就是说List<Object>List<String>是毫无关系的!!!!

那如今咋办???咱们是不清楚List集合装载的元素是什么类型的,List<Objcet>这样是行不通的........因而Java泛型提供了类型通配符 ?

因此代码应该改为这样:

public void test(List<?> list){


	for(int i=0;i<list.size();i++){
		
		System.out.println(list.get(i));
	
	}
}

复制代码

?号通配符表示能够匹配任意类型,任意的Java类均可以匹配.....

如今很是值得注意的是,当咱们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。

记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。由于直到外界使用才知道具体的类型是什么。也就是说,在上面的List集合,我是不能使用add()方法的。由于add()方法是把对象丢进集合中,而如今我是不知道对象的类型是什么。


3.4.1设定通配符上限

首先,咱们来看一下设定通配符上限用在哪里....

如今,我想接收一个List集合,它只能操做数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么作???

咱们学习了通配符,可是若是直接使用通配符的话,该集合就不是只能操做数字了。所以咱们须要用到设定通配符上限

List<? extends Number>
复制代码

上面的代码表示的是:List集合装载的元素只能是Number的子类或自身

public static void main(String[] args) {


        //List集合装载的是Integer,能够调用该方法
        List<Integer> integer = new ArrayList<>();
        test(integer);

        //List集合装载的是String,在编译时期就报错了
        List<String> strings = new ArrayList<>();
        test(strings);

    }


    public static void test(List<? extends Number> list) {
        
    }
复制代码

3.4.2设定通配符下限

既然上面咱们已经说了如何设定通配符的上限,那么设定通配符的下限也不是陌生的事了。直接来看语法吧

//传递进来的只能是Type或Type的父类
	<? super Type>
复制代码

设定通配符的下限这并很多见,在TreeSet集合中就有....咱们来看一下

public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

复制代码

那它有什么用呢??咱们来想一下,当咱们想要建立一个TreeSet<String>类型的变量的时候,并传入一个能够比较String大小的Comparator。

那么这个Comparator的选择就有不少了,它能够是Comparator<String>,还能够是类型参数是String的父类,好比说Comparator<Objcet>....

这样作,就很是灵活了。也就是说,只要它可以比较字符串大小,就好了

值得注意的是:不管是设定通配符上限仍是下限,都是不能操做与对象有关的方法,只要涉及到了通配符,它的类型都是不肯定的!

3.5通配符和泛型方法##

大多时候,咱们均可以使用泛型方法来代替通配符的.....

//使用通配符
    public static void test(List<?> list) {

    }

    //使用泛型方法
    public <T> void test2(List<T> t) {
        
    }


复制代码

上面这两个方法都是能够的.....那么如今问题来了,咱们使用通配符仍是使用泛型方法呢??

原则:

  • 若是参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法
  • 若是没有依赖关系的,就使用通配符,通配符会灵活一些.

3.6泛型擦除

泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将再也不带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。

3.6.1兼容性

JDK5提出了泛型这个概念,可是JDK5之前是没有泛型的。也就是泛型是须要兼容JDK5如下的集合的。

当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。

值得注意的是:它保留的就类型参数的上限。

List<String> list = new ArrayList<>();

        //类型被擦除了,保留的是类型的上限,String的上限就是Object
        List list1 = list;

复制代码

若是我把没有类型参数的集合赋值给带有类型参数的集合赋值,这又会怎么样??

List list = new ArrayList();
        List<String> list2 = list;
复制代码

它也不会报错,仅仅是提示“未经检查的转换”


4、泛型的应用

当咱们写网页的时候,经常会有多个DAO,咱们要写每次都要写好几个DAO,这样会有点麻烦。

这里写图片描述

那么咱们想要的效果是什么呢??只写一个抽象DAO,别的DAO只要继承该抽象DAO,就有对应的方法了。

要实现这样的效果,确定是要用到泛型的。由于在抽象DAO中,是不可能知道哪个DAO会继承它本身,因此是不知道其具体的类型的。而泛型就是在建立的时候才指定其具体的类型。

  • 抽象DAO
public abstract class BaseDao<T> {

	//模拟hibernate....
	private Session session;
	private Class clazz;
	
	
	//哪一个子类调的这个方法,获得的class就是子类处理的类型(很是重要)
	public BaseDao(){
		Class clazz = this.getClass();  //拿到的是子类
		ParameterizedType  pt = (ParameterizedType) clazz.getGenericSuperclass();  //BaseDao<Category>
		clazz = (Class) pt.getActualTypeArguments()[0];
		System.out.println(clazz);
		
	}
	

	public void add(T t){
		session.save(t);
	}
	
	public T find(String id){
		return (T) session.get(clazz, id);
	}
	
	public void update(T t){
		session.update(t);
	}
	
	public void delete(String id){
		T t = (T) session.get(clazz, id);
		session.delete(t);
	}
	
}
复制代码
  • 继承抽象DAO,该实现类就有对应的增删改查的方法了。

CategoryDao

public class CategoryDao extends BaseDao<Category> {

}

复制代码

BookDao

public class BookDao extends BaseDao<Book> {


}

复制代码

5、最后

泛型的基础就介绍到这里了,若是之后有须要的话再进行深刻研究吧~若是以为该文章帮助到你,不妨点个赞,关注公众号一波~

参考资料:

  • Core Java

若是文章有错的地方欢迎指正,你们互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同窗,能够关注微信公众号:Java3y

相关文章
相关标签/搜索