【JAVA高级&泛型】知识点整理

【JAVA高级&泛型】知识点整理

概述

通过本篇笔记的整理,复盘和巩固 JAVASE体系中 “泛型” 的相关知识点及其使用场景等。

目录

主要内容

0x01:泛型(generics)的概述

泛型:标签

举例:

  • 中药店,每个抽屉外面贴着标签
  • 超市购物架上很多瓶子,每个瓶子装的是什么,有标签

泛型的设计背景

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

泛型的概念

  • 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
  • JDK1.5 以后,Java 引入了 “参数化类” 的概念,允许我们在创建集合时再指定集合元素的类型,正如:List<String> ,这表明该List只能保存字符串类型的对象。
  • JDK1.5 改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

为什么要有泛型?如何使用?

  • 解决元素存储的安全性问题, 好比商品、 药品标签, 不会弄错。
  • 解决获取数据元素时, 需要类型强制转换的问题, 好比不用每回拿商品、 药品都要辨别。

在集合中没有泛型时

//在集合中使用泛型之前的情况:
@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 为例

//在集合中使用泛型的情况:以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);
    //
    //        }
    //方式二:
    Iterator<Integer> iterator = list.iterator();
    while(iterator.hasNext()){
        int stuScore = iterator.next();
        System.out.println(stuScore);
    }
}

HashMap 为例

//在集合中使用泛型的情况:以HashMap为例
@Test
public void test3(){
    //Map<String,Integer> map = new HashMap<String,Integer>();
    //jdk7新特性:类型推断
    Map<String,Integer> map = new HashMap<>();

    map.put("Tom",87);
    map.put("Jerry",87);
    map.put("Jack",67);

    //        map.put(123,"ABC");
    //泛型的嵌套
    Set<Map.Entry<String,Integer>> entry = map.entrySet();
    Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();

    while(iterator.hasNext()){
        Map.Entry<String, Integer> e = iterator.next();
        String key = e.getKey();
        Integer value = e.getValue();
        System.out.println(key + "----" + value);
    }

}

小结

1、集合接口或集合类在jdk5.0时都修改为带泛型的结构。

2、在实例化集合类时,可以指明具体的泛型类型,可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException 异常。使得代码更加简洁、健壮。

3、指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。比如:add(E e) 实例化以后:add(Integer e)

4、注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换

5、如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object 类型。

0x02:自定义泛型结构

泛型的声明

interface List<T>class GenTest<K,V>
其中, T,K,V 不代表值,而是表示类型。 这里使用 "任意字母" 都可以。常用 T表示,是 Type 的缩写。

泛型的实例化

一定要在类名后面指定类型参数的值(类型)。如:

List<String> strList = new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator();

T 只能是类,不能用 “基本数据类型” 填充。但可以使用 “包装类” 填充把一个集合中的内容限制为一个 "特定的数据类型" ,这就是泛型(generics)背后的核心思想 。

泛型类

public class Order<T> {

    String orderName;
    int orderId;

    //类的内部结构就可以使用类的泛型
    T orderT;
    public Order(){
        //编译不通过
        //T[] arr = new T[10];
        //编译通过
        T[] arr = (T[]) new Object[10];
    }

    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 +
            '}';
    }
}

如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object 类型

  • 要求:如果定义了类是带泛型的,建议在实例化时要指明类的泛型。
@Test
public void test1(){
    Order order = new Order();
    order.setOrderT(123);
    order.setOrderT("ABC");

    //建议:实例化时指明类的泛型
    Order<String> order1 = new Order<String>("orderAA",1001,"order:AA");
    order1.setOrderT("AA:hello");
}

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

public class SubOrder extends Order<Integer>{//SubOrder:不是泛型类

}

实例化

SubOrder sub1 = new SubOrder();
sub1.setOrderT(1122);

如果子类未指定泛型,则其仍是一个泛型类

public class SubOrder1<T> extends Order<T> {//SubOrder1<T>:仍然是泛型类
}

实例化

SubOrder1<String> sub2 = new SubOrder1<>();
sub2.setOrderT("order2...");

一些细节

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

  2. 泛型类的构造器如下: public GenericClass(){}。而下面是错误的: public GenericClass<E>(){}

  3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

  4. 泛型不同的引用不能相互赋值。

    尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。

  5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于 Object。 经验: 泛型要使用一路都用。要不用,一路都不要用。

  6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

  7. jdk1.7,泛型的简化操作:

    ArrayList<Fruit> flist = new ArrayList<>();
    
  8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。

  9. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型,因为静态成员创建于对象之前,如下例子

并且使用 try/catch 捕获异常时 catch中 也不能定义异常,如下

//编译不通过
try{


}catch(T t){

}
  1. 异常类不能是泛型的,错误信息如下

  2. 不能使用 new E[]。但是可以: E[] elements = (E[])new Object[capacity]; 使用 (E[]) 进行强制转换

    • 参考: ArrayList 源码中声明: Object[] elementData, 而非泛型参数类型数组。
  3. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

    • 子类不保留父类的泛型:按需实现

      • 没有类型 擦除
      • 具体类型
    • 子类保留父类的泛型:泛型子类

      • 全部保留
      • 部分保留

结论:子类必须是 “富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。

泛型方法

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

并且,泛型方法可以是静态的,原因为:泛型参数是在调用方法时确定的。并非在实例化类时确定。如下代码如下

public static <E>  List<E> copyFromArrayToList(E[] arr){

    ArrayList<E> list = new ArrayList<>();

    for(E e : arr){
        list.add(e);
    }
    return list;
}

0x03:泛型的应用举例

泛型类与泛型方法的使用情景

以操作数据库举例,在实际开发中,每一个数据库表对应一个 JAVA 实体类,对数据库表的一些操作行为的类,我们称作为 DAO ,现在我们需要编写一个通用于多个表的 DAO 类,它具有一些对多个表的共性操作,所以需要使用到泛型类,如下代码

public class DAO<T> {//表的共性操作的DAO

    //添加一条记录
    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;
    }

    //查询多条记录
    public List<T> getForList(int index){
        return null;
    }
}

定义具体到某个表的 DAO ,例如操作 Student

public class StudentDAO extends DAO<Student> {//只能操作某一个表的DAO
}

实体类

public class Student {
    
}

泛型方法的使用情景:

  • 具有通用性
  • 返回值不确定
  • 例如这个方法提供了多个功能:获取表中一共有多少条记录?获取最大的员工入职时间?此时我们需要将该方法的返回值写为 “泛型”
//泛型方法
//举例:获取表中一共有多少条记录?获取最大的员工入职时间?
public <E> E getValue(){
    return null;
}

泛型类在继承中的体现

虽然类 A 是类 B 的父类,但是 G<A>G<B>二者不具备子父类关系,二者是并列关系。

A 是类 B 的父类,A<G>B<G> 的父类

AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;

list1 = list3;
list2 = list3;

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

0x04:通配符的使用

通配符:?

类A是类 B 的父类,G<A>G<B> 是没有关系的,二者共同的父类是:G<?> ,如下例子

@Test
public void test3(){
    List<Object> list1 = null;
    List<String> list2 = null;
    List<?> list = null;
    list = list1;
    list = list2;
    //编译通过
    this.print(list1);
    this.print(list2);
}

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

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

  • 添加(写入):对于 List<?> ,不能向其内部添加数据。

  • 获取(读取):允许读取数据,读取的数据类型为 Object

    如下例子

    List<String> list3 = new ArrayList<>();
    list3.add("AA");
    list3.add("BB");
    list3.add("CC");
    list = list3;
    //添加(写入):对于List<?>就不能向其内部添加数据。
    //除了添加null之外。
    //list.add("DD");
    //list.add('?');
    
    list.add(null);
    
    //获取(读取):允许读取数据,读取的数据类型为Object。
    Object o = list.get(0);
    System.out.println(o);
    

有限制条件的通配符的使用

  • ? extends A:
    • G<? extends A> 可以作为 G<A> 和G <B> 的父类,其中BA 的子类
  • ? super A:
    • G<? super A> 可以作为 G<A>G<B> 的父类,其中 BA 的父类

使用案例

interface Info{		// 只有此接口的子类才是表示人的信息
}
class Contact implements Info{	// 表示联系方式
    private String address ;	// 联系地址
    private String telephone ;	// 联系方式
    private String zipcode ;	// 邮政编码
    public Contact(String address,String telephone,String zipcode){
        this.address = address;
        this.telephone = telephone;
        this.zipcode = zipcode;
    }
    public void setAddress(String address){
        this.address = address ;
    }
    public void setTelephone(String telephone){
        this.telephone = telephone ;
    }
    public void setZipcode(String zipcode){
        this.zipcode = zipcode;
    }
    public String getAddress(){
        return this.address ;
    }
    public String getTelephone(){
        return this.telephone ;
    }
    public String getZipcode(){
        return this.zipcode;
    }
    @Override
    public String toString() {
        return "Contact [address=" + address + ", telephone=" + telephone
            + ", zipcode=" + zipcode + "]";
    }
}
class Introduction implements Info{
    private String name ;		// 姓名
    private String sex ;		// 性别
    private int age ;			// 年龄
    public Introduction(String name,String sex,int age){
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    public void setName(String name){
        this.name = name ;
    }
    public void setSex(String sex){
        this.sex = sex ;
    }
    public void setAge(int age){
        this.age = age ;
    }
    public String getName(){
        return this.name ;
    }
    public String getSex(){
        return this.sex ;
    }
    public int getAge(){
        return this.age ;
    }
    @Override
    public String toString() {
        return "Introduction [name=" + name + ", sex=" + sex + ", age=" + age
            + "]";
    }
}
class Person<T extends Info>{
    private T info ;
    public Person(T info){		// 通过构造器设置信息属性内容
        this.info = info;
    }
    public void setInfo(T info){
        this.info = info ;
    }
    public T getInfo(){
        return info ;
    }
    @Override
    public String toString() {
        return "Person [info=" + info + "]";
    }

}
public class GenericPerson{
    public static void main(String args[]){
        Person<Contact> per = null ;		// 声明Person对象
        per = new Person<Contact>(new Contact("北京市","01088888888","102206")) ;
        System.out.println(per);

        Person<Introduction> per2 = null ;		// 声明Person对象
        per2 = new Person<Introduction>(new Introduction("李雷","男",24));
        System.out.println(per2) ;
    }
}

总结

通过本篇笔记的整理加深了对“泛型”的概念的理解,以及其使用场景等。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://codeyee.com/archives/java-generics.html