javaSE 集合框架—— 泛型

Posted 玛丽莲茼蒿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javaSE 集合框架—— 泛型相关的知识,希望对你有一定的参考价值。

目录

一、为什么要有泛型

二、在集合中使用泛型

2.1 集合使用泛型

2.2 Comparable类和Comparator比较器使用泛型 

1)Comprable类使用泛型

2)Comparator使用泛型

三、自定义泛型(泛型类、泛型接口;泛型方法)

3.1 泛型类/泛型接口

3.2 泛型的继承

 3.3 泛型方法

1)什么是泛型方法

2)泛型方法和如何调用举例

3.4 什么情景下使用泛型类;什么情景下使用泛型方法 

四、泛型在继承上的体现

4.1 <>为父子类关系

4.2 <> 相同,泛型类是父子类关系

五、通配符的使用

5.1 通配符的使用

5.2 使用通配符后对数据读取和写入的要求

5.3 有限制条件的通配符

 5.4 有限制条件的通配符对于读取和写入的要求

六、练习


一、为什么要有泛型

泛型在之前就接触过,例如C++的vector容器

vector<int> v;

 里面的“<int>”就是泛型。

定义:泛型, 就是将数据类型作为参数进行传递。而并非一个类

泛型是JDK 5.0的新特性。主要是为了解决集合中元素类型不确定的问题。在JDK 5.0之前,集合只能设计成Object类,什么元素都能装进来,容易误装不想要的类型。所以,先通过一个例子看一下,如果不使用泛型会发生什么问题:

二、在集合中使用泛型

2.1 集合使用泛型

 那加上泛型以后呢?有两点好处(好处二和好处三其实是一处)

 

上面以ArrayList为例(单列数据如何使用泛型?),下面再以HashMap为例(双列数据如何使用泛型?)

2.2 Comparable类和Comparator比较器使用泛型 

1)Comprable类使用泛型

2)Comparator使用泛型

特点01:所有类都可以使用泛型吗?

漏!必须在定义这个类时加上<E>,这个类实例化的时候才能使用泛型。也就是说我们定义一个普普通通的Person类,是无法使用泛型的

 特点02:泛型的类型必须是类,不能是int这样的基本数据类型(but可以使用包装类替换)。

 特点03:没学泛型之前,我们是这样new的:

HashSet set = new HashSet();

 此时,默认添加的元素都是Object类型。 相当于:

HashSet<Object> set = new HashSet<Object>();

特点04:JDK 1.7以后,实例化泛型类的时候,第二个<>可以空着(自动推断)。

HashSet<String> set = new HashSet<空>();

 特点05:泛型不同的引用不能相互赋值(后面继承的时候也会讲到)

 特点06:static方法中不能用泛型,但【泛型方法】可以声明为static。前者还是那个原因,static方法编译时就被加载进内存,但那个时候泛型的具体类型还没指定。

后者因为:泛型参数是在调用方法时通过参数指定的,并非在实例化类时确定。

特点07:异常类不能是泛型的

 :在泛型类中如果想要定义【泛型数组】

三、自定义泛型(泛型类、泛型接口;泛型方法)

3.1 泛型类/泛型接口

我们定义一个泛型类Order:

package Generics;

public class Order<T> 
    private String orderName;
    private int orderId;
    private T orderT; //泛型

    public Order()

    public Order(String orderName, int orderId,T orderT)  //泛型
        this.orderName=orderName;
        this.orderId=orderId;
        this.orderT=orderT;//泛型
    

    public T getOrderT() //泛型
        return this.orderT;
    

    public void setOrderT(T orderT)//泛型
        this.orderT=orderT;
    

 泛型类实例化时,将泛型指定为String类型:

        Order<String> order = new Order<String>();
        order.setOrderT("现在泛型被指定为String类型");

3.2 泛型的继承

1)继承时指定泛型的类型 

此时子类变为普通类,不是泛型类

public class SubOrder1 extends Order<String> //SubOrder1是普通类,不是泛型类了

实例化和普通类一样: 

        SubOrder1 subOrder1 = new SubOrder1();
        subOrder1.setOrderT("泛型在父类中被指定为String类型");

 2)继承时仍然保留泛型(参照ArrayList<E> extends List<E>)

此时子类是泛型类

public class SubOrder2<T> extends Order<T> //SubOrder2<T>是泛型类

实例化时需要指定泛型到底是什么类型: 

        SubOrder2<String> subOrder2 = new SubOrder2<String>();
        subOrder2.setOrderT("泛型在子类实例化时才被指定为String类型");

还有很多种情况,总结一下:

 

 3.3 泛型方法

1)什么是泛型方法

首先要明确什么是泛型方法。其实在C++中也接触过,只不过C++中叫模板template

#include <iostream>
using namespace std;
template <typename T>
T add(T a,T b)  //注意形参和返回类型
   
 return a+b;
 
void main()

    int num1, num2, sum; 
    cin>>num1>>num2;
    sum=add(num1,num2); //用int匹配模版参数T,若sum,num1,num2类型不一致则无法匹配。
    cout<<sum;

并不是说泛型类中用到泛型的方法就是泛型方法,拿Order<T>类来说:

 普通类当中也可以有泛型方法,只要我这个方法的类型不确定,我就可以设置成泛型方法。

所以泛型方法和泛型类没有任何关系,如果在一个泛型类Order<T>中我们想要申明一个泛型方法,为了区分开,就不能用<T>了,可以用<E>

2)泛型方法和如何调用举例

泛型方法举例,作用是将【某类型】的数组放入相应类型的List

    public <E> List<E> copyFromArrayToList(E[] arr)
        ArrayList<E> arrayList = new ArrayList<>();
        for(E e:arr)
            arrayList.add(e);
        
        return arrayList;
    

注意

 泛型方法的调用:

Order<String> order = new Order<String>();
Integer arr[] = new Integer[]1,2,3,4;
order.copyFromArrayToList(arr); //调用【泛型方法】的时候,参数arr决定了泛型类型

3.4 什么情景下使用泛型类;什么情景下使用泛型方法 

        我们定义一个对数据库中表进行操作的泛型类,由于数据库中有各种类型的表,所以该类是泛型类。

/**
 * 泛型类:对数据库中【所有类型】表的操作
 */
public class DaoGenerics<T>  //泛型类

    //表中增加一条数据
    public void add(T t)
        //操作省略
    

    //表中删除一条数据
    public void remove(T t)

    

    //表中查询一条数据
    public T query()
        return null;
    

    //泛型方法

 假设数据库中有一张员工表Employee:

/**
 * 一个Empolyee类代表数据库内的一张【员工表】
 */
public class Employee 

那么我就要写一个对员工表进行操作的类,继承泛型类,并且指明数据类型为Employee

/**
 * 精确到对数据库中Employee表的操作
 */
public class EmployeeDao extends DaoGenerics<Employee>  //指定泛型为【Employee】类型

 测试:在Employee表中添加一行数据

        EmployeeDao employeeDao = new EmployeeDao(); //方式(一)
        employeeDao.add(new Employee());  //直接添加Employee类型的数据

        DaoGenerics<Employee> employeeDao2 = new DaoGenerics<Employee>(); //方式(二)
        employeeDao2.add(new Employee());

四、泛型在继承上的体现

4.1 <>为父子类关系

        Object obj = null;
        String str = null;
        obj = str; //√,对象上转型

        ArrayList<Object> list1 = null;
        ArrayList<String> list2 = null;
        list1 = list2; //×,错误

不能互相赋值!!

虽然Object和String是父子类的关系,但list1和list2都是ArrayList类的,list1和list2是并列关系。主要想表达的呢,是不能在实际开发中向下面这样写(一打眼看去,貌似是“多态”):

4.2 <> 相同,泛型类是父子类关系

        List<String> list3 = null;
        ArrayList<String> list4 = null;
        list3 = list4;

上面这种多态是可以的,因为list3和list4才是“父子接口”的关系 

五、通配符的使用

5.1 通配符的使用

前面(下图)提到的这种“类似多态”是错误的,但是确实我们开发需求的一种。为了解决这种问题,提出了通配符“?”。

<?>作为公共父类,可以实现如下的赋值

        ArrayList<?> list = null;
        ArrayList<Object> list1 = null;
        ArrayList<String> list2 = null;
        
        list = list1;
        list = list2;

进而,我们就可以这样进行“多态”的方法调用了:

@Test
    public void test07()
        ArrayList<Object> list1 = null;
        ArrayList<String> list2 = null;

        print(list1);  //多态思想
        print(list2);  //多态思想
    

    public void print(ArrayList<?> list)
        Iterator<?> iterator = list.iterator();
        while(iterator.hasNext())
            Object next = iterator.next(); //注意这里的返回类型是Object,而不是通配符“?”
            System.out.println(next);
        
    

5.2 使用通配符后对数据读取和写入的要求

读取:允许读取数据。读取的数据类型为Object

写入:不能添加数据。null除外 

理解:为什么使用通配符(注意,这里更严格的说应该是“无限制”的通配符)后不能添加数据呢?

首先要明确,list和list3是同一个类的对象,并不是父子类关系。

因为<?>可以匹配所有类型,话句话说,?可以add所有类型的数据,以下面的例子为例,list3本来规定了只能添加String类型的数据,list=list3以后,list再去添加一个Integer类型的数据,这样list3不就白限制类型了吗,不就违背了使用泛型的意义了吗,所以直接不让?添加数据。

 了解了使用通配符后的读取和写入要求有什么用呢?

在我们上面写的print个方法中,只能用list读取数据,不能去改动或者添加数据。

5.3 有限制条件的通配符

其实就是限制通配符的“通配”范围。

<? extends A>,可以“通配”A类,或者A类的子类

<? superA>,可以“通配”A类,或者A类的父类

 5.4 有限制条件的通配符对于读取和写入的要求

理解记忆!

有一点需要注意的是,有限制条件的通配符可以添加数据了!!原因就在于,有了限制条件(自己看下面的例子去悟吧)

        ArrayList<? extends Person> list1 = null;
        ArrayList<? super Person> list2 = null;

        ArrayList<Student> list3 = new ArrayList<>();
        ArrayList<Person> list4 = new ArrayList<>();
        ArrayList<Object> list5 = new ArrayList<>();

        list1 = list3;  //√
        list1 = list4;  //√
        //list1 = list5;  //×

        //list2 = list3;  //×
        list2 = list4;  //√
        list2 = list5;  //√

        //1) 读取,extends -------> Person和Person的子类,可以用Person或者Object去接收
        Object obj = list1.get(0);
        Person person = list1.get(0);
        //Student stu = list1.get(0);  //×,和对象不能【下转】是一个道理

        //2) 读取,super -------> Person和Person的父类,只能用Object接收
        Object obj1 = list2.get(0);
        //Person per = list2.get(0); //×,和对象不能【下转】是一个道理

        //3)写入,extends ------->
        //list1.add(new Person());  //×,Person不能赋给Student,和对象不能【下转】是一个道理
        //list1.add(new Student()); //×,Student不能赋给Person的其他子类,和对象不能【下转】是一个道理
        
        //4) 写入,super
        list2.add(new Person());
        list2.add(new Student());

六、练习

1. 体会泛型的嵌套

三层嵌套。

注意ArrayList<Citizen>一旦带上<Citizen>,就要从头到尾带着<Citizen>。

ArrayList<Citizen> familyList = new ArrayList<Citizen>();
        familyList.add(new Citizen("父亲"));
        familyList.add(new Citizen("母亲"));
        familyList.add(new Citizen("女儿"));
        familyList.add(new Citizen("儿子"));

        HashMap<String, ArrayList<Citizen>> houseHoldRegister = new HashMap<>(); //map<户主,家庭成员>
        houseHoldRegister.put("父亲",familyList);

        /**
         * 遍历
         */
        Set<Map.Entry<String, ArrayList<Citizen>>> set = houseHoldRegister.entrySet();
        Iterator<Map.Entry<String, ArrayList<Citizen>>> it = set.iterator();
        while(it.hasNext())
            Map.Entry<String,ArrayList<Citizen>> entry= it.next();
            System.out.println("1)户主:"+entry.getKey());
            System.out.println("2)家庭成员:");
            ArrayList<Citizen> value = entry.getValue();
            for(Citizen c:value)
                System.out.println(c.getName());
            
        
public class Citizen 
    String name;

    public Citizen()

    

    public Citizen(String name)
        this.name = name;
    

    public String getName() 
        return this.name;
    

2. 有限制的泛型类Person<T extends Info>

 一个【人的信息】接口

/**
 * 只有实现此接口的子类才表示【人的信息
 */
public interface Info 

一个ContactInfo类实现Info接口,存储联系方式

public class ContactInfo implements Info
    private String address; //联系地址
    private String telephone; //联系电话

    public ContactInfo()

    
    public ContactInfo(String address,String telephone)
        this.address = address;
        this.telephone = telephone;
    
    public void setAddress(String address)
        this.address = address;
    
    public void setTelephone(String telephone)
        this.telephone = telephone;
    
    public String getAddress()
        return this.address;
    
    public String getTelephone()
        return this.telephone;
    
    @Override
    public String toString()
        return "ContactInfo [address="+this.address+",telephone"+this.telephone+"]";
    

一个PersonalInfo类实现Info接口,存储基本信息

public class PersonalInfo implements Info
    private String name; //姓名
    private int age; //年龄

    public PersonalInfo()
    public PersonalInfo(String name, int age)
        this.name = name;
        this.age = age;
    
    public void setName(String name)
        this.name = name;
    
    public void setAge(int age)
        this.age = age;
    
    public String getName()
        return this.name;
    
    public int getAge()
        return this.age;
    
    @Override
    public String toString()
        return "PersonalInfo [name="+this.name+",age="+this.age+"]";
    

Person类作为一个泛型类,其泛型还是有限制的泛型,只能是实现Info接口的子类(注意,这里Info虽然是个接口不是个类,依然可以使用extends关键字)。

public class Person <T extends Info>
    private T info;
    public Person()
    public Person(T info)
        this.info = info;
    
    public void setInfo(T info)
        this.info = info;
    
    public T getInfo()
        return this.info;
    
    @Override
    public String toString()
        return "Person [info=" + info +"]";
    

测试类:

public class GeneticsTest 
    public static void main(String[] args) 
        Person<ContactInfo> per = new Person<ContactInfo>(new ContactInfo("北京市","22225555"));
        System.out.println(per); //使用我们自己重写的toString方法
        //输出:
        //Person [info=ContactInfo [address=北京市,telephone22225555]]

        Person<PersonalInfo> per2 = new Person<PersonalInfo>(new PersonalInfo("LN",18));
        System.out.println(per2);
        //输出:
        //Person [info=PersonalInfo [name=LN,age=18]]
    

3. 自定义泛型类例题

 但是这里的User类并不能说明这个题的用意(用意就是泛型类DAO可以操作所有类型的用户),所以我给他改成一个歌手类Singer和一个演员类Actor。

DAO.java

public class DAO<T>
    private Map<String, T> map = new HashMap<>();

    //保存 T 类型的对象到map中
    public void save(String id, T entity)
        map.put(id,entity);
    
    //从map中获取id对应的对象
    public T get(String id)
        return map.get(id);
    
    //替换 map中key为id的内容,改为entity对象
    public void update(String id, T entity)
        if(map.containsKey(id))
            map.put(id,entity);
        
    
    //返回 map中存放的所有T对象
    public List<T> list()
        ArrayList<T> arrayList = new ArrayList<>();
        Collection<T> values = map.values();
        for(T t: values)
            arrayList.add(t);
        
        return arrayList;
    
    //删除指定id对象
    public void delete(String id)
        map.remove(id);
    

Singer.java

public class Singer 
    private String name;

    public Singer() 
    

    public Singer(String name) 
        this.name = name;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    @Override
    public String toString() 
        return "Singer" +
                "name='" + name + '\\'' +
                '';
    
    //因为Singer类要作为map的value,所以Singer类要重写equals方法

    @Override
    public boolean equals(Object o) 
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Singer singer = (Singer) o;

        return name != null ? name.equals(singer.name) : singer.name == null;
    

//    @Override
//    public int hashCode() 
//        return name != null ? name.hashCode() : 0;
//    

演员类和歌手类一样,省略。

DAOTest.java

public void test01()
        /**
         * 对歌手的操作:new DAO<Singer>()
         */
        DAO<Singer> singerDAO = new DAO<Singer>();
        singerDAO.save("001", new Singer("刘德华"));
        singerDAO.save("002", new Singer("林俊杰"));
        singerDAO.save("003", new Singer("汪苏泷"));
        singerDAO.save("004", new Singer("蔡健雅"));
        List<Singer> singerList = singerDAO.list();
        System.out.println(singerList);
        //输出:[Singername='刘德华', Singername='林俊杰', Singername='汪苏泷', Singername='蔡健雅']
        /**
         * 对演员的操作
         */
        DAO<Actor> actorDAO = new DAO<Actor>();
        actorDAO.save("001",new Actor("沈腾"));
        actorDAO.save("002",new Actor("吴京"));
        actorDAO.save("003",new Actor("黄渤"));
        actorDAO.delete("002");
        List<Actor> actorList = actorDAO.list();
        System.out.println(actorList);
        //输出:[Actorname='沈腾', Actorname='黄渤']
    

以上是关于javaSE 集合框架—— 泛型的主要内容,如果未能解决你的问题,请参考以下文章

JavaSE| 泛型

JavaSE-16 集合框架

集合框架和泛型编程

JavaSE基础八----<集合>泛型集合体系Collection接口中的方法

JavaSE-集合

JavaSE-泛型