Java JDK1.5: 泛型 新特性的讲解说明

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

Java JDK1.5 泛型 新特性的讲解说明

在这里插入图片描述

每博一文案

听到过这样一句话“三观没有标准。在乌鸦的世界里天鹅也有罪。”
环境、阅历的不同造就了每个人独有的世界观、人生观、价值观。
三观并无对错高下只有同与不同。恰如飞鸟不用和游鱼同行高山不必同流水相逢。
总用自己的尺子去度量别人无疑是一种狭隘。面对不同时只有懂得尊重对方才能跳出固有的认知看得更高更远。
这个世界上没有标准答案人不是只有一种活法。

文章目录

1. 泛型概述

在任何不重要的软件项目中错误都只是生活中的事实。 仔细的计划编程和测试可以帮助减少他们的普遍性但不知何故在某个地方他们总是会找到一种方法来进入你的代码。 随着新功能的推出以及您的代码库规模和复杂性的增加这一点变得尤为明显。

幸运的是一些错误比其他错误更容易被发现。例如编译时错误可以在早期发现; 你可以使用编译器的错误信息来找出问题所在然后修正它。运行时错误然而可能是更多的问题; 它们并不总是立即出现而且当它们这样做时它可能在程序中的某一点远离问题的实际原因。

泛型通过在编译时检测更多的错误来增加代码的稳定性。

  • 泛型的设计背景

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

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

2. 为什么要使用泛型

那么为什么要有泛型呢直接Object 不是也可以存储数据吗

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

如下举例

没有使用泛型

我们创建一个 ArrayList 集合不使用泛型默认存储的是 Object 类型用来存储 学生的成绩 int 类型添加成绩时不小心添加了

学生的姓名因为该集合没有使用泛型默认是Object 类型什么都可以存储所以也把这个输入错误的 学生姓名给存储进去了。

当我们把 ArrayList 集合当中的存储的数据取出 (强制转换为 int 类型的数据成绩时)报异常java.lang.ClassCastException 类型转换异常。因为你其中集合当中存储了一个学生的姓名String 是无法强制转换成 int 类型的。

在这里插入图片描述


import java.util.ArrayList;

public class GenericTest {
    // 没有使用泛型
    public static void main(String[] args) {
        // 定义了泛型没有使用的话默认是 Object 类型存储
        ArrayList arrayList = new ArrayList();

        // 添加成绩
        arrayList.add(99);
        arrayList.add(89);
        arrayList.add(79);

        // 问题一:存储的类型不安全
        // 不小心添加了一个学生的姓名
        arrayList.add("Tom");

        for (Object o : arrayList) {
            // 问题二: 强转时可能出现ClassCastException 异常
            int stuScore = (Integer)o;  // 因为你存储的类型可能与强制转换的类型没有继承关键实例关系
            // 导致转换失败.
            System.out.println(stuScore);
        }
    }
}

在这里插入图片描述

使用了泛型

将 ArrayLsit 集合定义为 ArrayList<Integer> 使用上泛型限定了该集合只能存储 Integer 类型的数据其它类型的无法存入进到集合当中编译的时候就会报错。

在这里插入图片描述

在这里插入图片描述

import java.util.ArrayList;

public class GenericTest {
    // 使用上泛型
    public static void main(String[] args) {
        // 泛型限定了存储类型泛型指定定义引用类型基本数据类型不行
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        // 使用了泛型: 就会进行类型检查保证数据的安全
        arrayList.add(99);  // 包装类自动装箱
        arrayList.add(78);
        arrayList.add(76);
        arrayList.add(89);
        arrayList.add(88);

        // arrayList.add("Tom"); // 存储不符合泛型的数据编译无法通过。
        for (Integer integer : arrayList) {
            int stuScore = integer;  // 不需要强制转换自动拆箱

            System.out.println(stuScore);
        }
    }

}

在这里插入图片描述

Java泛型可以保证如果程序在编译时没有发出警告运行时就不会产生 java.lang.ClassCastException异常。同时代码更加简洁健壮

简而言之在定义类接口和方法时泛型使 类型类和接口成为参数。 就像方法声明中使用的更熟悉的 形式参数 一样类型参数为您提供了一种方法 让您在不同的输入中重用相同的代码。区别在于形式参数的输入是值而类型参数的输入是类型。

使用泛型的代码比非泛型代码有许多优点

  • 编译时更强大的类型检查。

    Java 编译器将强类型检查应用于通用代码并在代码违反类型安全性时发出错误。修复编译时错误比修复运行时错误要容易得多。

  • 消除强制转换 。

3. 集合中使用泛型

在 Java SE 7 和更高版本中只要编译器可以根据上下文确定或推断类型参数就可以用一组空类型参数<>替换调用泛型类的构造函数所需的类型参数。 这一对尖括号<>非正式地称为钻石。例如您可以使用以下语句创建 Box <Integer> 的实例

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

在这里插入图片描述

在这里插入图片描述

在 List集合中使用泛型存取数据



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

public class GenericTest2 {
    // List 集合中使用泛型存取数据
    public static void main(String[] args) {
        // 使用泛型<String> 限定 List 集合存储的类型对象
        // 注意泛型中只能存储引用类型的基本数据类型不可以(int,double)
        List<String> list = new ArrayList<>();

        list.add("Tom");
        list.add("李华");
        list.add("张三");

        Iterator<String> iterator = list.iterator();

        while(iterator.hasNext()) {
            String s = iterator.next();
            System.out.println(s);
        }

    }
}

在这里插入图片描述


在Set集合中使用泛型存取数据

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class GenericTest2 {
    public static void main(String[] args) {
        // Set 集合中使用泛型存取数据
        // 使用泛型<Integer> 限定Set 集合存储的类型对象
        // 注意泛型中只能存储引用类型的基本数据类型不可以(int,double)
        Set<Integer> set = new HashSet<>();

        set.add(1);
        set.add(2);
        set.add(3);

        Iterator<Integer> iterator = set.iterator();
        while(iterator.hasNext()) {
            Integer next = iterator.next();
            System.out.println(next);
        }
    }

在这里插入图片描述

在 Map 集合中使用泛型存取数据


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class GenericTest2 {
    // Map 集合中使用泛型存取数据
    public static void main(String[] args) {
        // 使用泛型<String,Integer> 限定 Map 集合存储的类型对象
        // 注意泛型中只能存储引用类型的基本数据类型不可以(int,double) 
        Map<String, Integer> map = new HashMap<>();
        map.put("Tom", 99);
        map.put("李华", 89);
        map.put("张三", 79);

        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            System.out.println(entry.getKey() + "--->" + entry.getValue());

        }
    }
}

在这里插入图片描述

泛型是可以被嵌套的多层嵌套泛型Set<Map.Entry<T>>

Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
// 这里泛型嵌套了两层: Set<Map.Entry<>> 这是一层
//      内部还有一层: Map.Entry<String,Integer> 这是第二层

4. 自定义泛型结构

4.1 输入参数命名约定

按照惯例类型参数名称是单个大写字母。这与你已经知道的变量命名约定形成了鲜明的对比 并且有很好的理由没有这个约定很难区分类型变量和普通类或接口名称。

最常用的类型参数名称是

  • E - 元素由 Java 集合框架广泛使用
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

4.2 自定义泛型结构的接口

一个泛型接口的定义格式如下

接口中的泛型可以定义一个也可以定义多个多个泛型 T 使用逗号, 分隔开来。

public interface MyGeneric<T1, T2, ..., Tn> {
}

接口中的泛型 T 可以在抽象方法中应用起来

在抽象方法中作为 方法值 T

public interface MyGeneric<T> {

    // 定义含有泛型的 T 抽象方法泛型作为返回值;
    public T fun();
}

在抽象方法中作为 参数 T

public interface MyGeneric<T> {
    // 定义含有泛型的 T 抽象方法 泛型作为参数
    public void fun2(T t);

}

既作为抽象方法中的返回值 T 又作为抽象方法中的 参数 T

public interface MyGeneric<T> {
    
    // 定义含有泛型的 T 抽象方法 T 泛型作为返回值参数
    public T fun3(T t);

}
public interface MyGeneric<T> {

    // 定义含有泛型的 T 抽象方法泛型作为返回值;
    public T fun();

    // 定义含有泛型的 T 抽象方法 泛型作为参数
    public void fun2(T t);

    // 定义含有泛型的 T 抽象方法 T 泛型作为返回值参数
    public T fun3(T t);

}

4.3 自定义泛型结构的类

一个泛型类的定义格式如下

class name<T1, T2, ..., Tn> { /* ... */ }

由尖括号<>分隔的类型参数部分在类名后面。它指定了类型参数也称为类型变量T1T2…和Tn。

要更新 Box 类以使用泛型可以通过将代码 public class Box 更改为 public class Box <T> 来创建泛型类型声明。 这引入了类型变量 T可以在类中的任何地方(非静态方法属性参数返回值)使用

把一个集合中的内容限制为一个特定的数据类型这就是泛型背后的核心思想

注意含有泛型的类的构造器的创建和没有使用泛型一样创建构造器就可以了不要附加你的奇思妙想

如下

public class Box<T> {
    
    // 泛型<T> 应用类属性当中
    T t;

    
    // 无参构造器
    public Box() {
        
    }
    
    // 带泛型参数构造器
    public Box(T t) {
        this.t = t;
    }
}

具体如下代码


public class Box<T> {

    // 泛型<T> 应用类属性当中
    T t;


    // 无参构造器
    public Box() {

    }

    // 带泛型参数构造器
    public Box(T t) {
        this.t = t;
    }


    // 泛型<T> 应用到方法返回值中
    public T fun() {
       return null;
    }

    // 泛型<T> 应用到参数当中
    public void fun2(T t) {

    }

    // 泛型<T> 应用到返回值参数当中
    public T set(T t) {
        return null;
    }


}



注意异常类中不可以使用泛型<T> 编译无法通过

在这里插入图片描述

不可以使用泛型创建数组编译无法通过

在这里插入图片描述

但是我们可以用特殊方法实现如下通过创建一个 new Object[] 的数组再强制转换为 T[] 泛型数组,因为泛型默认没有使用的话是 Object 类型。

在这里插入图片描述

泛型不可以作为实例化对象出现因为泛型是在实例化的时候才确定该泛型具体的类型是什么的如果直接对泛型实例化你都不知道实例化成什么类型的对象的。 所以直接编译无法通过。

如下

在这里插入图片描述

4.3.1 含有泛型的类实例化对象

带有泛型的实例化一定要在类名/接口后面指定类型参数的值(类型)。如下:

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

**JDK 7 ** 版本以后可以省略 = 等号右边的 <>泛型声明了只要声明左边的就可以了。就算你右边附加上了<>泛型声明 默认也是会被省略的。

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

注意泛型和集合一样只能存储引用类型的数据泛型不能用基本数据类型填充必须使用引用类型填充这里包装类就起到了非常重要的作用了。

在这里插入图片描述

4.4 自定义泛型结构的方法

泛型方法 是引入自己的类型参数的方法。这与声明泛型类型相似但是类型参数的作用域仅限于声明它的方法。允许使用静态和非静态泛型方法以及泛型类构造函数。

泛型方法的语法包括一个类型参数列表里面的尖括号出现在方法的返回类型之前。对于静态泛型方法类型参数部分必须出现在方法的返回类型之前。

泛型方法与该类是不是含有泛型类无关 换句话说泛型方法所属的类是不是泛型类都没有关系同样可以定义泛型方法。

定义非静态泛型方法格式如下

访问权限修饰符 <泛型>(表示泛型方法不可省略) 返回类型 方法名(参数类型 参数名) 抛出的异常 

public <E> E fun3(E e) {
    return e;
}

定义静态泛型方法格式如下在附加上一个 static 静态修饰

访问权限修饰符 static  <泛型>(表示泛型方法不可省略) 返回类型 方法名(参数类型 参数名) 抛出的异常 
public static <E> E fun4(E e) {
        System.out.println("静态泛型方法泛型作为返回值参数"+e);
        return e;
}

package blogs.blog8;

public class GenericTest4 {
    public static void main(String[] args) {
        // 泛型方法的调用:


    }

}

class MyClass {

    // 泛型方法无返回值的
    public <E> void fun() {
        System.out.println("泛型方法无返回值,无参数的");
    }

    // 泛型方法: 泛型作为参数传入
    public <E> void fun2(E e) {
        System.out.println("泛型方法无返回值有泛型参数"+e);
    }

    // 泛型方法: 泛型作为返回值和参数
    public <E> E fun3(E e) {
        System.out.println("泛型方法泛型作为返回值参数"+e);
        return e;
    }

    public static <E> E fun4(E e) {
        System.out.println("静态泛型方法泛型作为返回值参数"+e);
        return e;
    }
}

泛型方法的调用和没有普通的方法一样的方式调用没有什么区别区别是在 JVM 运行编译的时候的不同。调用是一样的方法如下

package blogs.blog8;



public class GenericTest4 {
    public static void main(String[] args) {
        // 泛型方法的调用:
        MyClass myClass = new MyClass();
        myClass.fun();
        myClass.fun2(new String("Hello"));
        String s = myClass.fun3("你好世界");
        System.out.println(s);

        System.out.println("**********");
        String s2 = MyClass.fun4("Hello Wrold");
        System.out.println(s2);


    }

}

class MyClass {

    // 泛型方法无返回值的
    public <E> void fun() {
        System.out.println("泛型方法无返回值,无参数的");
    }

    // 泛型方法: 泛型作为参数传入
    public <E> void fun2(E e) {
        System.out.println("泛型方法无返回值有泛型参数"+e);
    }

    // 泛型方法: 泛型作为返回值和参数
    public <E> E fun3(E e) {
        System.out.println("泛型方法泛型作为返回值参数"+e);
        return e;
    }

    public static <E> E fun4(E e) {
        System.out.println("静态泛型方法泛型作为返回值参数"+e);
        return e;
    }
}

在这里插入图片描述

泛型方法在你调用的时候就会推断出你要 <E> 泛型的具体的类型了。 如下

在这里插入图片描述

5. 泛型在继承上的体现

关于父类中含有泛型<> 对应的子类的对父类泛型的处理情况如下

  • 父类有泛型子类继承父类不保留父类中的泛型擦除了父类中的泛型(默认是 Object)
// 父类
class Father<T1,T2> {

}


// 子类没有保留父类的泛型擦除了: 等价于class Son extends Father<Object,Object>{}
class Son1 extends Father{

}

  • 父类有泛型子类继承父类并指明了父类的泛型(具体类型)

注意: 由于子类在继承泛型的父类/实现的接口时指明了泛型具体是什么类型所以实例化子类对象时不再需要指明泛型了。

但是单独实例化父类还是要指明其泛型的具体类型的。

// 父类
class Father<T1,T2> {

}

// 子类保留了父类的泛型并指明了父类中泛型的具体类型
class Son2 extends Father<String,Integer> {
    
}

  • 父类有泛型子类继承父类并保留了父类的泛型(并没有指明具体类型)

注意: 因为子类并没有指明父类泛型的具体类型所以子类要沿用上父类的泛型**<>**从而对父类上的泛型(赋予具体类型),不然编译无法通过。

// 父类
class Father<T1,T2> {

}

// 子类保留了父类的泛型并没有指明父类的具体类型
// 注意因为没有指明父类泛型的具体类型所以子类要沿用上父类的泛型<>从而对父类上的泛型(赋予具体类型)
class Son3<T1,T2> extends Father<T1,T2> {

}

  • 父类有泛型子类继承父类并保留了父类的泛型(并没有指明具体类型)外加子类定义自己独有的泛型

注意: 因为子类并没有指明父类泛型的具体类型所以子类要沿用上父类的泛型**<>**从而对父类上的泛型(赋予具体类型),不然编译无法通过。

// 父类
class Father<T1,T2> {

}

// 子类继承父类并保留了父类的泛型(并没有指明具体类型)外加子类定义自己独有的泛型
class Son4<T1,T2,E,E2> extends Father<T1,T2> {
    
}

  • 父类有泛型子类继承父类并保留了父类的部分泛型(部分指明了父类的泛型具体类型部分没有指明父类的泛型具体类型)外加子类定义自己独有的泛型

注意: 因为子类并没有指明父类泛型的具体类型所以子类要沿用上父类的泛型**<>**从而对父类上的泛型(赋予具体类型),不然编译无法通过。

// 父类
class Father<T1,T2> {

}

// 父类有泛型子类继承父类并保留了父类的`部分`泛型(部分指明了父类的泛型具体类型部分没有指明父类的泛型具体类型)外加子类定义自己独有的泛型
class Son4K<T2,E,E2> extends Father<String,T2> {
    
}

6. <泛型> 中的 通配符

泛型的多态性上的使用

  • 注意: ArrayList<String> 和 ArrqayList<Intger> 是两种不同的类型虽然它们整体上是都是 ArrayList 集合类但是所指定的泛型(指明的类型不同导致存储的类型不同) 编译时被鉴定为了两种不同的类型无法相互引用赋值。但是在运行时只有一个 ArrayList 被加载到 JVM 中因为类一样的所存储的类型不同而已类仅仅只会加载一次到i内存当中。
  • 简单的说就是泛型不同的不可以相互引用赋值 编译无法通过。

如下代码

在这里插入图片描述

两个泛型相同的类型可以引用赋值如下

在这里插入图片描述

根据上述情况我们对不同的泛型(具体指明的类型) 需要定义不同的方法了。注意: 如果是的泛型<指明的具体类型>不同是无法重载方法的。

在这里插入图片描述

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

public class GenericTest6 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        ArrayList<Integer> arrayList2 = new ArrayList<>();
        
        fun1(arrayList);  // <String>
        fun2(arrayList2); // <Integer>
    }


    public static void fun1(List<String> list) {

    }

    public static void fun2(List<Integer> list) {

    }
}

为了解决上述因为泛型(指明的具体类型)的不同而导致的繁琐操作。Java为程序员提供了 通配符?

在泛型代码中被称为通配符的是 一个问号? 表示未知类型。 通配符可用于多种情况作为参数的类型、字段或局部变量;

有时作为返回类型尽管更好的编程实践更具体。比如List<?> Map<??>List<?> 可以理解为是Lis<String>t、List<Object>等各种泛型List的父类。

通配符永远不会用作泛型方法调用泛型类实例创建或超类型的类型参数。

举例:

在这里插入图片描述

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

public class GenericTest6 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        ArrayList<Integer> arrayList2 = new ArrayList<>();
        ArrayList<?> arrayList3 = new ArrayList<>();
        arrayList3 = arrayList;  // 尽管 <String> 不同都可以赋值给 <?> 通配符
        arrayList3 = arrayList2; // 尽管 <Integer> 不同都可以赋值给 <?> 通配符
    }
}

举例:

在这里插入图片描述

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

public class GenericTest6 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        ArrayList<Integer> arrayList2 = new ArrayList<>();

        fun(arrayList);  // <String>
        fun(arrayList2); // <Integer>
    }

    public static void fun(List<?> list) {

    }
}

对于 List<?>Map<?,?>Set<?> 等等对象**读取(添加)**数据元素时永远时可以添加成功的因为不管 list<泛型> 中的泛型具体指明的是什么类型都它们都是包含了 Object 都可以被 ? 接受住。

我们可以调用 get() 方法并使用其返回值。返回值是一个未知的类型但是我们知道它总是一个Object

如下代码:

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

public class GenericTest6 {
    public static void main(String[] args) {
        List<?> list3 = null;
        List<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("World");

        // 将 List<String> 引用赋值给 List<?>
        list3 = list;
        // 获取值
        Object o = list3.get(0);
        System.out.println(o);
        Object o2 = list3.get(1);
        System.out.println(o2);

        System.out.println("*******************************");

        List<Integer> list2 = new ArrayList<>();
        list2.add(99);
        list2.add(999);

        // 将 List<Integer> 引用赋值给 List<?>
        list3 = list2;
        // 获取值
        Object o3 = list3.get(0);
        System.out.println(o3);
        Object o4 = list3.get(1);
        System.out.println(o4);


    }
}

在这里插入图片描述

对于 List<?>Map<?,?>Set<?> 等等对象**读取(添加)**数据元素时报编译无法通过。因为我们不知道 的元素类型我们不能向其中添加对象。唯一的例外是null它是所有类型的成员。

将任意元素加入到其中不是类型安全的

Collection c = new ArrayList();

c.add(new Object()); // 编译时错误

因为我们不知道c的元素类型我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型所以我们无法传任何东西进去。

举例:

在这里插入图片描述

在这里插入图片描述

6.1 通配符的使用注意点

  • 注意点1编译错误通配符不能用在泛型方法声明上返回值类型前面也<>不能使用 。

在这里插入图片描述

  • 注意点2编译错误通配符不能用在泛型类的声明上。

在这里插入图片描述

  • 注意点3编译错误不能用在创建对象上右边属于创建集合对象。

在这里插入图片描述

6.2 有限制的通配符

6.2.1 无界通配符

<?> 允许所以泛型的引用调用。称为 无界通配符 。上面介绍了就这里就不多介绍了。

6.2.2 上界通配符

< ? extends XXX> 上限 extends 使用时指定的类型必须是继承某个类(XXX)或者实现了某个接口(X

xX)。 即 <=

比如:

< ? extends Person>;  // (无穷小, Person] 只允许泛型为 Person 以及 Person 子类的引用调用
< ? extends Comparable>;  // 只允许泛型为实现 Comparable 接口的实现类的引用调用

举例:

在这里插入图片描述

package blogs.blog8;

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

public class GenericTest7 {
    public static void main(String[] args) {
        List< ? extends Person> list = null;  // <=
        List<Person> list2 = new ArrayList<>();
        List<Student> list3 = new ArrayList<>();
        List<Object> list4 = new ArrayList<>();

        // list 可以存取 <= Person 的类型
        list = list2;
        list = list3;
        list = list4; // 这就Object > Person 不行

    }
}


class Person {

}

class Student extends Person {

}

注意 同样 <? extends XXX> 下界通配符的引用不可以添加数据(因为是未知的类型)但是可以获取起其中的数据返回 XXX 最大的。

“写入” 添加数据无法写入

在这里插入图片描述

“读”获取数据: 返回对应最大的 XXX

package blogs.blog8;

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

public class GenericTest7 {
    public static void main(String[] args) {
        List< ? extends Person> list = null;  // <=
        List<Person> list2 = new ArrayList<>();
        List<Student> list3 = new ArrayList<>();
        List<Object> list4 = new ArrayList<>();
        list3.add(new Student() );

        // list 可以存取 <= Person 的类型
        list = list2;
        list = list3;

        // 获取数据 “读”
        Person person = list.get(0);
        System.out.println(person);

    }
}


class Person {

}

class Student extends Person {

}

在这里插入图片描述

6.2.3 下界通配符

< ? super XXX> 下限 super使用时指定的类型不能小于操作的类 XXX>=

比如:

< ? super Person>;  // [Person, 无穷大] 只允许泛型为 Person 及  Person父类的引用调用

举例:

在这里插入图片描述

注意 同样 <? superXXX> 上界通配符的引用不可以添加数据(因为是未知的类型)但是可以获取起其中的数据返回 XXX 最大的。

“写入” 添加数据无法写入

在这里插入图片描述

“读”获取数据: 返回对应最大的 Object

package blogs.blog8;

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

public class GenericTest7 {
    public static void main(String[] args) {
        List< ? super Person> list = null;  // >= Person
        List<Person> list2 = new ArrayList<>();
        List<Student> list3 = new ArrayList<>();
        List<Object> list4 = new ArrayList<>();
        list3.add(new Student() );

        // list 可以存取 >= Person 的类型
        list = list2;
        list = list4;
        list.add(new Person());  // 小于的可以添加上
        list.add(new Student());

        Object object = list.get(0);
        System.out.println(object);


    }


}


class Person {

}

class Student extends Person {

}

在这里插入图片描述

7. 对泛型的限制(泛型的使用上的注意事项)

要有效地使用 Java 泛型您必须考虑以下限制

  • 注意泛型不能只能填充引用类型不可填充基本数据类型。使用包装类

在这里插入图片描述

  • 注意泛型不可以无法创建类型参数的实例 E new () 不可以 编译无法通过

在这里插入图片描述

  • 注意不能声明类型是类型参数的静态字段/静态方法中(编译无法通过)但是可以创建静态泛型方法。 因为泛型是实例化对象的时候才确定其指明具体类型而 静态是在实例化之前的操作。静态泛型方法是在调用静态泛型方法的时候泛型才确定指明具体类型的。所以没问题。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 注意不能使用带有参数化类型的 cast 或 instanceof

在这里插入图片描述

  • 注意泛型不能创建参数化类型的数组

在这里插入图片描述

但是我们可以用特殊方法实现如下通过创建一个 new Object[] 的数组再强制转换为 T[] 泛型数组,因为泛型默认没有使用的话是 Object 类型。

在这里插入图片描述

  • 注意泛型可以用于创建捕捉或抛出参数化类型的对象 自定义异常类中不可以用泛型类

在这里插入图片描述

在这里插入图片描述

  • 不能重载每个过载的形式参数类型擦除到相同的原始类型的方法简单的说就是不能通过指明的泛型的不同实现重载的不满足重载的要求的

在这里插入图片描述

8. 泛型应用举例

  • 定义个泛型类 DAO在其中定义一个 Map 成员变量Map 的键 为 String 类型值为 T 类型。

分别创建以下方法

public void save(String id,T entity) 保存 T 类型的对象到 Map 成员 变量中 。

public T get(String id)从 map 中获取 id 对应的对象。

public void update(String id,T entity)替换 map 中 key 为 id 的内容,改为 entity 对象 。

public List list()返回 map 中存放的所有 T 对象 。

public void delete(String id)删除指定 id 对象 。

定义一个 User

该类包含private 成员变量int 类型 idageString 类型name。

定义一个测试类

创建 DAO 类的对象 分别调用其 save、get、update、list、delete 方 法来操作 User 对象

使用 Junit 单元测试类进行测试。

DAO类和 User类

package blogs.blog8;


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;


public class DAO<T> {
    private Map<String, T> map = null;

    public DAO() {
        // 构造器为其Map集合初始化
        this.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)) {  // 首先判断该修改的 key 是否存在
            // 存在通过 put()添加覆盖
            map.put(id, entity);
        }
    }

    // 返回map中存放的所以 T 对象
    public List<T> list() {
        Collection<T> values = map.values();
        List<T> list = new ArrayList<T>();
        // 注意了: Collection 是 List 的父类接口如果List 对象不是 Collection 的实例
        // 是无法将一个父类强制(向下)为子类的(这里两个都是接口不可能有实例的)
        // 通过取出所以的values 值赋值到一个新创建的 List 对象当中再返回。
        for (T t : values) {
            list.add(t);
        }
        return list;

    }

    // 删除指定id对象
    public void delete(String id) {
        map.remove(id);
    }
}

class User {
    private int id;
    private int age;
    private String name;

    public User() {

    }

    public User(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    // 因为存储的是在Map当中所以Map当中的Key 存储对象需要重写 equals() 和 hashCode() 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return getId() == user.getId() &&
                getAge() == user.getAge() &&
                Objects.equals(getName(), user.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId(), getAge(), getName());
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

DAOTest

package blogs.blog8;

import day25.DAOS;
import org.junit.Test;

import java.util.List;

public class DAOTest {
    @Test
    public void test() {
        DAOS<User> dao = new DAOS<User>();

        dao.save("1001",new User(1001,34,"周杰伦"));
        dao.save("1002",new User(1002,20,"昆菱"));
        dao.save("1003",new User(1003,25,"蔡依林"));

        dao.update("1003",new User(1003,30,"万文山"));

        dao.delete("1002");

        List<User> list = dao.list();
        list.forEach(System.out::println);


    }
}


在这里插入图片描述

9. 总结

  1. 泛型是 JDK5.0 的新特性。
  2. Java泛型可以保证如果程序在编译时没有发出警告运行时就不会产生ClassCastException异常。同时代码更加简洁、健壮。
  3. 把一个集合中的内容限制为一个特定的数据类型这就是generics背后的核心思想
  4. 泛型只能填充引用类型基本数据类型不可填充泛型使用包装类。
  5. 使用泛型的主要优点是能够在编译时而不是在运行时检测错误。
  6. 泛型如果不指定将被擦除泛型对应的类型均按照Object处理但不等价 于Object。经验 泛型要使用一路都用。要不用一路都不要用。
  7. 自定义泛型类泛型接口泛型方法。
  8. 泛型类在父类上的继承变化上的使用。
  9. 泛型中的通配符上的使用无界通配符<?>上界通配符< ? extends XXX> (<=)下界通配符 <? super XXX> (>= )
  10. 泛型在使用上的限制以及注意事项。

10. 最后

限于自身水平其中存在的错误希望大家给予指教韩信点兵——多多益善。谢谢大家江湖再见后会有期

在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java

“Java JDK1.5: 泛型 新特性的讲解说明” 的相关文章