4

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

 1 year ago
source link: https://www.cnblogs.com/TheMagicalRainbowSea/p/17093972.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

在这里插入图片描述

每博一文案

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

1. 泛型概述

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

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

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

  • 泛型的设计背景

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

  • 泛型的概述
    • 所谓的泛型,就是允许在定义类,接口时通过一个标识<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> { /* ... */ }

由尖括号(<>)分隔的类型参数部分在类名后面。它指定了类型参数(也称为类型变量)T1,T2,...,和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 ,MapList<?> 可以理解为是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 类型) id,age;(String 类型)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. 最后:

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

在这里插入图片描述

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK