Java高级-泛型

12.泛型

1.泛型的概念

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参)。

12.1.为什么要用泛型
  • 泛型与File中使用泛型举例

泛型的使用
泛型即传入实际的参数类型,也称类型实参
1.jdk5.0新增的特性
2.在集合中使用泛型:
总结:
①集合接口或集合类在jdk5.0时都修改为带泛型的就够.
②在实例化集合类时,可以指明具体的泛型类型
③指明完以后,在集合类或接口中凡是定义类或接口的结构时,内部结构(比如: 方法,构造器,属性等) 用到类的泛型的位置,都指明为实例化的泛型类型.
比如: add(E e) 实例化后–> add(Integer e)
④注意点: 泛型的类型必须是类,不能是基本数据类型.要用到基本数据类型的位置,要拿包装类替换
⑤如果实例化时,没有指明泛型的类型.默认类型为java.lang.Object类型

// 不能让当前list中混进来其他类型变量,要在添加时候做类型检查,在集合中用泛型
public class GenericTest {
    // 在集合中用泛型之前的情况:
    @Test
    public void test1(){
        ArrayList list = new ArrayList();
        // 需求: 存放学生成绩
        list.add(78);
        list.add(76);
        list.add(89);
        list.add(88);
        // 类型不安全: 没有限制存放的类型
        // list.add("Tom");
        for (Object score : list) {
            // 问题二: 强转时,可能出现ClassCastException(类型转换异常)
            // 因为存放类型没有要求,其他类型可能混进来
            int stuScore = (Integer) score;// 向下强转并自动拆箱,可能会出异常
            System.out.println(stuScore);
        }
    }
    // 在集合中用泛型的情况: 以ArrayList
    @Test
    public void test2(){
        // 实例化泛型类
        // 泛型是个类型,不能是基本数据类型,要用他的包装类,因为泛型的参数要要求必须是对象
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(78);
        list.add(87);
        list.add(99);
        list.add(65);
        // 编译时,就会进行类型检查,保证数据的安全
        // list.add("Tom");
        // 方式一:
        /*for (Integer score : list) {
            // 避免了强转操作
            int stuScore = score;// 自动拆箱
            System.out.println(stuScore);
        }*/
        // 方式二: 类或者接口定义时有加泛型,实例对象时才能用泛型
        // 为什么自动识别为Integer类型,因为iterator方法在调的时候返回的就是带泛型的Iterator类型的结果
        // 由于在ArrayList实例化的时候指明泛型E就是Integer,紧接着Iterator的E就变成Integer了
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            // 此时调next方法,返回的数据就是Integer类型
            // 为什么知道是Integer,在接口Iiterator源码中因为在Iterator接口中造iterator对象指明的就是Integer
            // 所以next()方法返回的就是Integer类型
            int score = iterator.next();
            System.out.println(score);
        }
    }
    // 在集合中用泛型的情况: 以HashMap为例
    @Test
    public void test3(){
        // 泛型要写两个,因为HashMap类在声明泛型时就用了两个值
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        // jdk7新特性: 类型推断
        HashMap<String, Integer> map1 = new HashMap();
        map.put("Tom",12);
        map.put("Jerry",87);
        map.put("Jack", 67);
        // map.put(123,"abc"); 类型不符合,编译不通过
        // 泛型嵌套
        // Entry前要"Map.",是因为Entry是一个内部接口,不像Map接口一样暴露在外面,就像内部类的使用要外部类.内部类,想不用Map.要在顶部import Map接口
        Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
        // 造一个迭代器对象
        Iterator<Map.Entry<String, Integer>> iterator = entrySet.iterator();
        // 迭代器遍历
        while (iterator.hasNext()) {
            // 将集合中每个元素返回一个entry对象
            Map.Entry<String, Integer> entry = iterator.next();
            // 获取entry中的key和value,有可能要把key和value装到一个对象上,对象属性都是明确的所以不能泛泛写成Object类型
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println( key + "--" + value);
        }
    }
}
  • 使用泛型的练习

  • 自定义泛型类举例

如何自定义泛型结构: 泛型类,泛型接口; 泛型方法
1.关于自定义泛型类,泛型接口

// 第一种情况
    @Test
    public void test1(){
        // 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
        // 要求: 如果定义了类的带泛型的,建议在实例化时要指明类的泛型
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("AA");
        // 建议:实例化类时指明类的泛型
        Order<String> order1 = new Order<String>("orderAA",1001,"order:AA");
        order1.setOrderT("AA:hello");
    }
    @Test
    public void test2(){
        //由于子类在继承带泛型的父类时,指明了泛型类型.则实例化子类对象时,不再需要指明泛型.
        // 子类不再是泛型类,因为父类已经指明,子类就是普通类
        SubOrder sub = new SubOrder();
        sub.setOrderT(123);
        SubOrder1<String> sub1 = new SubOrder1<>();
        sub1.setOrderT("ordrer2....");
    }

自定义泛型类

// 属性类型不确定时可以在类声明时加泛型,把T称作类的泛型
public class Order<T>{ // T可看成是类型参数
    String orderName;
    int orderId;
    // 类的内部结构可以用类的泛型
    T orderT;
    // 构造器
    public Order(){
        // 编译不通过,因为这个时候T已经非常具体了,相当于是个实际操作了,只有一个类就叫T才能这样new,
        // 这里的T其实还是个变量,是个参数,实例化时指明的参数类型而已,只不过这个变量拿个类型来充当的
        // T[] arr = new T[10];
        // 虽然编译通过,但实际new的还是Object类型数组,数组声明是Object,具体放数据放T和他子类的对象,才能保证实际执行不会类型转换异常
        // 造T类型的数组
        T[] arr = (T[]) new Object[10];
    }

    public Order(String orderName, int orderId, T orderT) {
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }
}
// 第二种情况:在继承的时候父类指明具体的类型,子类处理的就是Integer类型,子类就没有泛型的说法了
    // SubOrder: 是普通类
public class SubOrder extends Order<Integer> {
}
// 若子类继承的泛型类没有指明类型,则子类也是泛型类,且类型必须与父类一直;
// 若子类是普通类,则父类的泛型必须明确
// SubOrder1: 仍然是泛型类
public class SubOrder1<T> extends Order<T>{
}
  • 自定义泛型类型类泛型接口的注意点

Java高级-泛型_第1张图片

Java高级-泛型_第2张图片

Java高级-泛型_第3张图片

Java高级-泛型_第4张图片

 @Test
    public void test3(){
        ArrayList<String> list1 = null;
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        // 泛型不同的引用不能相互赋值
        // list1 = list2;
        Person p1 = null;
        Person p2 = null;
        p1 = p2;
    }
// 属性类型不确定时可以在类声明时加泛型,把T称作类的泛型
public class Order<T>{ // T可看成是类型参数
    String orderName;
    int orderId;
    // 类的内部结构可以用类的泛型
    T orderT;
    // 构造器
    public Order(){
        // 编译不通过,因为这个时候T已经非常具体了,相当于是个实际操作了,只有一个类就叫T才能这样new,
        // 这里的T其实还是个变量,是个参数,实例化时指明的参数类型而已,只不过这个变量拿个类型来充当的
        // T[] arr = new T[10];
        // 虽然编译通过,但实际new的还是Object类型数组,数组声明是Object,具体放数据放T和他子类的对象,才能保证实际执行不会类型转换异常
        // 造T类型的数组
        T[] arr = (T[]) new Object[10];
    }
public Order(String orderName, int orderId, T orderT) {
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }
    // 如下三个方法都不能说是泛型方法
    // set,get方法
    public T getOrderT(){
        return this.orderT;
    }
    public void setOrderT(T orderT){
        this.orderT = orderT;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }
    // 静态方法可以使用泛型,只不过不能用类的泛型,自己单独用的没事
    // 类的泛型是在对象实例化指定的,而静态方法早于对象的创建,相当于类型还没指定,这里就要用了
    /*public static void show(){
        System.out.println(orderT);
    }*/
    public void show(){
        // 编译不通过,若泛型类型T不是异常类就不通过了
        /*try {

        } catch (T t) {

        }*/
    }
// 异常类不能声明为泛型类
/*
public class MyException extends Exception {
}
*/
  • 自定义泛型方法举例
// 属性类型不确定时可以在类声明时加泛型,把T称作类的泛型
public class Order<T>{ // T可看成是类型参数
    String orderName;
    int orderId;
    // 类的内部结构可以用类的泛型
    T orderT;
    // 构造器
    public Order(){
        // 编译不通过,因为这个时候T已经非常具体了,相当于是个实际操作了,只有一个类就叫T才能这样new,
        // 这里的T其实还是个变量,是个参数,实例化时指明的参数类型而已,只不过这个变量拿个类型来充当的
        // T[] arr = new T[10];
        // 虽然编译通过,但实际new的还是Object类型数组,数组声明是Object,具体放数据放T和他子类的对象,才能保证实际执行不会类型转换异常
        // 造T类型的数组
        T[] arr = (T[]) new Object[10];
    }

    public Order(String orderName, int orderId, T orderT) {
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }
    // 如下三个方法都不能说是泛型方法
    // set,get方法
    public T getOrderT(){
        return this.orderT;
    }
    public void setOrderT(T orderT){
        this.orderT = orderT;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }
    // 静态方法可以使用泛型,只不过不能用类的泛型,自己单独用的没事
    // 类的泛型是在对象实例化指定的,而静态方法早于对象的创建,相当于类型还没指定,这里就要用了
    /*public static void show(){
        System.out.println(orderT);
    }*/
    public void show(){
        // 编译不通过,若泛型类型T不是异常类就不通过了
        /*try {

        } catch (T t) {

        }*/
    }
    // 泛型方法: 在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
    // 泛型方法所属的类是不是泛型类跟方法都没有关系
    // 泛型方法,可以声明为静态的.原因: 泛型参数是在调用方法时确定该的.并非在实例化类时确定
    public static <E> List<E> copyArrayToList(E[] arr){
        // 创建一个指定泛型为E的ArrayList的对象
        ArrayList<E> list = new ArrayList();
        // 遍历数组中E类型的元素
        for (E e : arr){
            // 将arr的元素添加到list中
            list.add(e);
        }
        // 返回list集合
        return list;
    }
}
// 测试泛型方法
    @Test
    public void test4(){
        // 先创建泛型方法所在类的对象,指定为String类型
        Order<String> order = new Order<>();
        // 创建一个Integer类型的数组
        Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
        // 调用泛型方法,传入arr数组作为实参
        // 返回的list集合的Integer类型由传入的arr的类型决定的
        // 泛型方法在调用时,指明泛型参数的类型,该泛型和类的泛型没任何关系
        List<Integer> list = order.copyArrayToList(arr);
        System.out.println(list);
    } 
// SubOrder: 是普通类
// 泛型方法跟类是不是泛型没有关系
public class SubOrder extends Order<Integer> {
    // 重写父类的方法,要么都是非静态要么都是静态
    public static <E> List<E> copyArrayToList(E[] arr){
        // 创建一个指定泛型为E的ArrayList的对象
        ArrayList<E> list = new ArrayList();
        // 遍历数组中E类型的元素
        for (E e : arr){
            // 将arr的元素添加到list中
            list.add(e);
        }
        // 返回list集合
        return list;
    }
}
  • 举例泛型类和泛型方法的使用场景
// 表的共性操作的DAO
public class DAO<T> {
    // 添加一条记录
    public void add(T t){

    }
    // 删除一条记录,要是放在集合,用索引删
    public boolean remove(int index) {
        return false;
    }

    // 修改一条记录,将指定索引位置的元素改成新的
    public void update(int index, T t) {

    }

    // 查询一条记录
    public T getIndex(int index) {
        return null;
    }

    // 查询多条记录,查询小于index的所有数据,返回一个集合
    public List<T> getForList(int index) {
        return null;
    }
    // 泛型方法,指明为泛型E,返回值为E类型,有不确定性所以才写成E
    // 举例: 获取表中一共有多少条记录? 获取最大的员工入职时间?
    // 泛型方法的类型和泛型类类型没关系,方法所在类是不是泛型类也没关系
    // return什么类型,E就什么类型
    public <E> E getValue(){
        return null;
    }
}
// DAO的子类,此类对应数据库中的customers表,在表中添加记录就是造这个类的对象
public class Customer {
}
// 专门操作customers表的,继承DAO类并指明泛型
//只能操作某一个表的DAO
public class CustomerDAO extends DAO<Customer> {
}
// 表类中的属性可以参照数据库的表
public class Student {
}
// 创建一个指明操作Student泛型类
//只能操作某一个表的DAO
public class StudentDAO extends DAO<Student>{
}
public class DAOTest {
    public static void main(String[] args) {
        // 创建一个操作表的类对象
        CustomerDAO dao = new CustomerDAO();
        // 修改的数据一定是指明的泛型类型,dao对象一定是操作Customer的
        dao.add(new Customer());
        // 查询小于10的记录,返回的是Customer构成的list
        List<Customer> list = dao.getForList(10);
    }
}
  • 泛型在继承方面的体现
/*
    1.泛型在继承方面的体现
    类A是类B的父类,但是G 和 G二者不具备子父类关系,是并列关系
    补充: 类A是类B的父类, A 是 B 的父类
    泛型相同的子父类的引用能相互赋值
     */
    @Test
    public void test1(){
        Object obj = null;
        String str = null;
        obj = str;
        Object[] arr1 = null;
        String[] arr2 = null;
        arr1 = arr2;
        // 编译不通过
        // 此时的list1和list2的类型不具有子父类关系
        // 因为两个都是List接口类型
        List<Object> list1 = null;
        List<String> list2 = null;
        // list1 = list2;
        // String和Date是并列的两个类没有子父类关系
        /*Date date = new Date();
        str = date;*/
        /* 编译不通过反证法:
        假设list1 = list2;
        假设 List list2 = new ArrayList();
        意味着在堆空间new了一个ArrayList空间,在栈中声明的list2指向new的空间,
        然后把list2的地址给了list1,所以list1也指向了堆空间唯一的实体,实体里有数组结构
        如果是可以的,通过list1.add(123);相当于也放到了list2中,list2就混进了非String的数据了,出错
        所以两个是没有子父类关系的,而是并列关系
         */
        show(list1);
        // show(list2); 编译不通过,没有子父类关系
        show1(list2);
    }
    // 这里泛型方法不能重载,因为show(List list)方法在编译时擦除类型后的方法是show(List list),与另一个方法重复
    public void show1(List<String> list) {

    }
    public void show(List<Object> list){

    }
    @Test
    public void test2(){
        AbstractList<String> list1 = null;
        List<String> list2 = null;
        ArrayList list3 = null;

        list1 = list3;
        list2 = list3;
        // 相当于 list2 = list3;
        List<String> list4 = new ArrayList<>();
    }
 
  
  • 通配符的使用
  • 使用通配符后数据的读取和写入的要求

2.通配符的使用:
通配符: ?
类A是类B的父类,G 和 G是没有关系的,二者共同的父类是: G

@Test
    public void test3(){
        // 下面两是并列关系
        List<Object> list1 = null;
        List<String> list2 = null;
        List<?> list = null;
        // 相当于List这个结构作为了List和List的通用(公共)父类
        list = list1;
        list = list2;
        // 假如上面两种泛型都是创建的ArrayList集合对象,并且往里存放了数据
        // 此时就都可以调print方法实现通用的调用
        // 编译通过
        // print(list1);
        // print(list2);

        // 使用通配符后不能对集合修改
        List<String> list3 = new ArrayList<>();
        list3.add("AA");
        list3.add("BB");
        list3.add("CC");
        list = list3;// 此时list指向了list3
        // 添加(写入): 对于List就不能向其内部添加数据
        // list.add("AA");
        // list.add('?');
        // 因为add方法对于list集合来讲加的是对象,
        // 对象不管是什么类型的都能赋值为null,所以只允许加null
        list.add(null);
        // 获取(读取): 允许读取数据,虽然读的类型不确定,但一定是Ojbect类型或其子类对象,以多态方式赋值
        Object o = list.get(0);
        System.out.println(o);
    }
    // 遍历通用父类
    public void print(List<?> list){
        // 创建迭代器对象,泛型变成和形参一样的类型
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()) {
            // 不管问号是什么类型,next方法遍历出来的对象怎样都是Object类的实例
            // 因为Object是根父类
            Object obj = iterator.next();
            System.out.println(obj);
        }
    }
 
  
  • 有限制条件的通配符的使用

3.有限制条件的通配符的使用
? extends A: G 可以作为G(Person)和G(Student)的父类,其中B是A的子类
? super A: G 可以作为G
(Person)和G(Object)的父类,其中B是A的父类

@Test
    public void test4(){
        // ? 可理解为负无穷到正无穷
        // 要继承Person类,可理解为 ≤
        List<? extends Person> list1 = null;
        // Person类的父类,可理解为 ≥
        List<? super Person> list2 = null;
        List<Student> list3 = new ArrayList<>();
        List<Person> list4 = new ArrayList<>();
        List<Object> list5 = new ArrayList<>();
        list1 = list3;
        list1 = list4;
        // list1 = list5;编译不通过
        // list2 = list3;编译不通过
        list2 = list4;
        list2 = list5;
        // 读取数据:
        list1 = list3;
        // list1的上街通配符类型必须覆盖所有可能,所以最小要声明为Person类型,最大要Object类型
        Person p = list1.get(0);
        // 编译不通过,list1能接受<=Person类型的数据,有可能是Person,就不能用student接收了
         // Student s = list1.get(0);
         list2 = list4;
        // list2读取的值可能是Person的父类,所以不能返回Person类型
        Object obj = list2.get(0);
        // 编译不通过
        // Person obj1 = list2.get(0);
        // 写入数据:
        // 因为list1范围是(-∞,Person],list1类型不确定,所以最小只能添加null
        list1.add(null);
        // 因为list2范围是[Person,+∞),而子类可以添加父类里,list2的类型是不确定的,所以最大只能添加Person类型
        list2.add(new Person());
    }
  • 自定义泛型类的练习

你可能感兴趣的:(笔记,java,开发语言)