0
点赞
收藏
分享

微信扫一扫

Java中的泛型

以下为本人观看尚硅谷Java学习视频所做的笔记

目录

为什么要有泛型

泛型(Generic)可以理解为标签,是jdk5.0新增的特性
类比:中药店中每个抽屉外边贴着标签,抽屉里只能存放标签代表的药物。我们可以把Java中的容器类比为抽屉,那么泛型就是标签,意味着这个容器只能存放泛型所指定类型的对象

泛型的设计背景:
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。collection<E>,List<E>,ArrayList<E>这个<E>就是类型参数,即泛型。
 

泛型的概念

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

从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如: List<String>,这表明该List只能保存字符串类型的对象。

JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参

 

使用泛型的好处

那么为什么要有泛型呢,直接object不是也可以存储数据吗?
1.解决元素存储的安全性问题,好比商品、药品标签,不会弄错。
2.解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药
品都要辨别。
在这里插入图片描述
泛型示例代码:

import java.util.*;

public class Sample {
    public static void main(String[] args) {
        //以ArrayList为例
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(123);
        //编译时就会进行类型检查,保证数据的安全
        //list.add("Tom");

        //遍历
        //方式一:
        for (Integer integer : list) {
            //避免了强制转换操作
            int tmp = integer;
            System.out.println(tmp);
        }
        //方式二:
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            int tmp = iterator.next();
            System.out.println(tmp);
        }

        //以HashMap为例
        Map<String, Integer> map = new HashMap<String, Integer>();
        map.put("Tom", 87);
        //类型检查,编译报错
        //map.put(123, "Tom");

        //泛型的嵌套
        Set<Map.Entry<String, Integer>> entry = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator1 = entry.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> e = iterator1.next();
            String key = e.getKey();
            Integer value = e.getValue();
            System.out.println(key + " --> " + value);
        }
    }
}

总结:
1、集合接口或集合类在jdk5.0时都修改为带泛型的结构
2、在实例化集合类时,可以指明具体的泛型类型
3、指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型,比如:add(E e) → 实例化后:add(Integer e)
4、注意点:泛型的类型必须是类,不能是基本数据类型,需要用到基本类型的位置,使用包装类替换
5、如果实例化时,没有指明泛型的类型,默认类型为java.lang.Object类型
 

jdk7.0泛型新特性:类型推断

//jdk7.0之前声明对象时候后面语句也需要注明数据类型
ArrayList<Integer> list = new ArrayList<Integer>();
//jdk7.0之后可以省略
ArrayList<Integer> list = new ArrayList<>();

 

自定义泛型结构

泛型类、泛型接口

1、泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
2、泛型类的构造器如下: public GenericClass(),而这样是错误的: public GenericClass()
3、实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致
4、泛型不同的引用不能相互赋值

ArrayList<String> list1 = null;
ArrayList<Integer> list2 = null;
list1 = list2;//编译报错

  · 尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
5、泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验: 泛型要使用一路都用。要不用,一路都不要用
6、如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象
7、jdk1.7,泛型的简化操作: ArrayList flist = new ArrayList<>();
8、泛型的指定中不能使用基本数据类型,可以使用包装类替换
9、在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型
10、异常类不能是泛型的
11、设泛型参数是E,注意不能使用new E[]。但是可以这样写: E[] elements =(E[]) new Object[capacity]; (参考: ArrayList源码中声明: Object[] elementData,而非泛型参数类型数组)
12、父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
  · 子类不保留父类的泛型: 按需实现
    · 没有类型擦除
    · 具体类型
  · 子类保留父类的泛型: 泛型子类
    · 全部保留
    · 部分保留
  · 结论: 子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型

继承泛型类

1、在继承时就指明了类型

class SubOrder extends Order<Integer>

由于子类在继承带泛型的父类时,指明了泛型类型,则实例化子类对象时,不再需要指明泛型。这时候SubOrder不是泛型类

//由于子类在继承带泛型的父类时,指明了泛型类型,则实例化子类对象时,不再需要指明泛型
SubOrder subOrder = new SubOrder();

2、在继承时不指明类型

class SubOrder<T> extends Order<T>

这时候SubOrder类仍然是泛型类

SubOrder<String> subOrder = new SubOrder<>();

自定义泛型类、泛型接口(以泛型类为例)

//自定义泛型类
class Order<T> {
    String orderName;
    int orderId;

    //类的内部结构可以使用类的泛型
    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 orderT;
    }

    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }
}

public class Sample {
    public static void main(String[] args) {
        //如果定义了泛型类,实例化没有指定类的泛型,则认为此泛型类型为Object类型
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("ABC");

        //要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型
        Order<String> order1 = new Order<String>("orderAA", 1001, "order:AA");
        order1.setOrderT("AA:hello");
    }
}

 

泛型方法

在方法中出现了泛型的结构,泛型方法的泛型参数与类的泛型参数没有任何关系(换句话说,泛型方法所属的类是不是泛型类都没有关系)

泛型方法在调用时,指明泛型参数的类型

泛型方法可以声明为静态的,因为泛型参数是在调用方法时确定的,并非在实例化类的时候确定

示例:注意方法声明处第一个<E>是用于说明这个方法是泛型方法,如果不加这个<E>,会导致编译器误认为E是一个类而报错

public class Sample {
    public static void main(String[] args) {
        Integer[] arr = new Integer[]{1, 2, 3};
        List<Integer> list = new Sample().copy(arr);//这时候list对象的类型就由arr类型来决定
    }
	//泛型方法
    public <E> List<E> copy(E[] arr) {
        ArrayList<E> list = new ArrayList<>();
        for (E e : arr) {
            list.add(e);
        }
        return list;
    }
}

 

泛型在继承上的体现

设类A是类B的父类,注意G<A>和G<B>二者不具备子父类关系,二者是并列关系
如:
设obj是Object类的对象,str是String类的对象,那么我们可以赋值obj = str
但是设list1是List<Object>的对象,list2是List<String>的对象,那么list1 = list2是编译不通过的,因为List<Object>和List<String>是并列关系

设类A是类B的父类,注意A<T>是B<T>的父类
如:List<String>是ArrayList<String>的父类,所以才有 List<String> list = new ArrayList<String>(); 这样的赋值语句

 

通配符的使用

通配符:?

上述文中说到G<A>和G<B>是并列关系,那么如果想要使用多态来使得它们都能被赋值的话就应该找到它们的父类,它们的父类是G<?>。通配符是用来解决这个问题的。

通配符示例代码:

import java.util.Iterator;
import java.util.List;

public class Sample {
    public static void main(String[] args) {
        List<Object> list1 = null;
        List<String> list2 = null;
        List<?> list = null;

        //可以赋值,说明List<?>是父类
        list = list1;
        list = list2;

        new Sample().print(list1);//编译通过

        //添加(写入):对于List<?>就不能向其内部添加数据,除了添加null之外
        list2.add("AA");
        //list.add("AA");//编译报错
        list.add(null);

        //获取(读取):允许读取数据,读取的数据类型为Object
        Object obj = list.get(0);

    }

    public void print(List<?> list) {
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println(obj);
        }
    }
}

有限制的通配符

  • 通配符指定上限
    上限extends: 使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
  • 通配符指定下限
    下限super: 使用时指定的类型不能小于操作的类,即>=
  • 举例:
    • <?extends Number>(无穷小,Number]:只允许泛型为Number及Number子类的引用调用
    • <?super Number>[Number ,无穷大):只允许泛型为Number及Number父类的引用调用
    • <? extends Comparable>:只允许泛型为实现Comparable接口的实现类的引用调用

示例代码(其中Student类是Person类的子类):
? extands A:
  G<? extends A>可以作为G<A>和G<B>的父类,其中B是A的子类
? super A:
  G<? super A>可以作为G<A>和G<B>的子类,其中B是A的父类

import java.util.ArrayList;
import java.util.List;

public class Sample {
    public static void main(String[] args) {
        List<? extends Person> list1 = null;
        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;
        Person p = list1.get(0);//编译通过
        //Student s = list1.get(0);//编译不通过

        list2 = list4;
        Object obj = list2.get(0);//编译通过
        //Person person = list2.get(0);//编译不通过

        //写入数据:
        //list1.add(new Student());//编译不通过
        list2.add(new Person());//编译通过
        list2.add(new Student());//编译通过
    }
}

class Person {
}

class Student extends Person {
}
举报

相关推荐

0 条评论