Java SE 学习笔记 :泛型篇(下)

简介: Java SE 学习笔记 :泛型篇(下)

5.2  自定义泛型方法

附注:如何在IDEA中找出一个类的所有子类?


六、类型变量的上限

我们在声明<T>等类型变量时,可以给它限定“上限”。

语法格式:

<T extends 上限> : 表示T的类型必须是<=上限,即要么是上限本身,要是继承上限类或实现了上限接口的类型。

举例如下:

学生类,包含姓名和成绩。

现在成绩类型不确定,但是要求成绩必须是Number或Number的子类对象。

java.Lang.Number表示数值类型,例如它的子类有。

代码如下:

在上面的案例需求中再加一个要求,成绩不仅要求是Number类或它的子类,还要求实现Comparable接口

一个类型变量的上限可以是1个,也可以是多个。但是要求如果有多个的话,类只能有一个,其他的都是接口类型。而且类在左边,其他的类型在右边。

如果自定义的方法实现了类型变量的下限,那么传参给方法的实参中的类也必须实现该接口。此举意在编译前提醒检测类型变量的类型转换问题

6.1 定义泛型类的类型变量时指定上限

代码举例如下:

6.2  定义泛型方法的类型变量时指定上限

编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。要求数组的元素类型必须是java.lang.Comparable<T>接口类型。

代码举例如下:

public class MyArrays {
    public static <T extends Comparable<T>> void sort(T[] arr){
        for (int i = 1; i < arr.length ; i++) {
            for (int j = 0; j < arr.length-i ; j++) {
                if (((Comparable)(arr[j])).compareTo(arr[j+1])>0){
                    T temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
            }
        }
    }
}
import org.junit.Test;
public class TestArrays {
    @Test
    public void test01(){
        Integer[] nums={4,7,1,2,89,34,67};
        System.out.println("排序前:");
        for (Integer num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
        MyArrays.sort(nums);
        System.out.println("排序后:");
        for (Integer num : nums) {
            System.out.print(num+"\t");
        }
    }
}

运行效果:


七、泛型擦除

如果用户在使用泛型类或泛型接口时,没有主动指定泛型的类型,就会发生泛型的擦除。

泛型擦除后,类型变量按照哪个类型处理? 是统一按照object处理吗?

不是

答案:泛型擦除后,自动按照类型变量声明时的第一个上限处理,如果类型变量上面时没有指定上限,那么按照Object处理

代码举例如下:

 


八、泛型通配符

8.1  为什么要使用泛型通配符?

当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator<T>类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量<T>的具体类型,此时我们考虑使用类型通配符 ?

代码举例如下:

import java.util.ArrayList;
import java.util.Collection;
public class TestWildcard {
    public static void m4(Collection<?> coll){
        for (Object o : coll) {
            System.out.println(o);
        }
    }
    public static void main(String[] args) {
        //右边泛型指定为任意类型或不指定都可以
        m4(new ArrayList<Object>());//Collection<?> coll = new ArrayList<Object>();
        m4(new ArrayList<>());//Collection<?> coll = new ArrayList<>();
        m4(new ArrayList());//Collection<?> coll = new ArrayList();
        m4(new ArrayList<String>());//Collection<?> coll = new ArrayList<String>();
    }
}

在Java中,泛型的指定是必须左右两边一致的

8.2  类型通配符的三种使用形式

  • <?>:完整形式为:类名<?> 或接口名<?>,此时?代表任意类型
  • <? extends 上限>:完整形式为:类名<? extends 上限类型> 或接口名<? extends 上限类型>,此时?代表上限类型本身或者上限的子类,即?代表 <= 上限的类型。
  • <? super 下限>:完整形式为:类名<? super 下限类型> 或接口名<? super 下限类型>,此时?代表下限类型本身或者下限的父类,即?代表>= 下限的类型。

案例:

声明一个集合工具类MyCollections,要求包含:

  • public static boolean different(Collection<?> c1, Collection<?> c2):比较两个Collection集合,此时两个Collection集合的泛型可以是任意类型,如果两个集合中没有相同的元素,则返回true,否则返回false。
  • public static <T> void addAll(Collection<? super T> c1, T... args):可以将任意类型的多个对象添加到一个Collection集合中,此时要求Collection集合的泛型指定必须>=元素类型。
  • public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src):可以将一个Collection集合的元素复制到另一个Collection集合中,此时要求原Collection泛型的类型<=目标Collection的泛型类型。

代码演示如下:

import java.util.Collection;
public class MyCollections {
    public static boolean different(Collection<?> c1, Collection<?> c2){
        return c1.containsAll(c2) && c2.containsAll(c1);
    }
    public static <T> void addAll(Collection<? super T> c1, T... args){
        for (int i = 0; i < args.length; i++) {
            c1.add(args[i]);
        }
    }
    public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src){
        for (T t : src) {
            dest.add(t);
        }
    }
}
public class MyCollectionsTest {
    public static void main(String[] args) {
        Collection<Integer> c1 = new ArrayList<Integer>();
        MyCollections.addAll(c1,1,2,3,4,5);
        System.out.println("c1 = " + c1);
        Collection<String> c2 = new ArrayList<String>();
        MyCollections.addAll(c2,"hello","java","world");
        System.out.println("c2 = " + c2);
        System.out.println("c1 != c2 " + MyCollections.different(c1, c2));
        Collection<Object> c3 = new ArrayList<>();
        MyCollections.copy(c3,c1);
        MyCollections.copy(c3,c2);
        System.out.println("c3 = " + c3);
    }
}

运行效果如下:

8.3  使用类型通配符来指定类型参数的问题

(1)如果把"泛型类<T>"指定为"泛型类<?>";那么该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。

代码举例如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class TestProblem {
    @Test
    public void test01(){
        Collection<?> coll = new ArrayList<>();
//        coll.add("hello");
//        coll.add(1);
//        coll.add(1.0);
        /*
        上面所有添加操作都报错。
        为什么?
        因为<?>表示未知的类型,集合的元素是不确定的,那么添加任意类型对象都有风险。
        void add(E t)方法无法正常使用
        因为此时E由?表示,即表示直到add方法被调用时,E的类型仍然不确定,所以该方法无法正常使用
         */
        Collection<?> coll2 = Arrays.asList("hello","java","world");
        for (Object o : coll2) {
            System.out.println(o);
        }
    }
}

(2)如果把"泛型类<T>"指定为"泛型类<? extends 上限>":那么该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。

代码举例如下:

个人理解:如果把"泛型类<T>"指定为"泛型类<? extends 上限>",之所以无法正常使用,是因为

Java编译器在编译时无法确定添加到集合coll中的元素的类型是否<= Number,泛型类<? extends 上限> 中只说了要<=Number[上限],Number类以下的部分或者说它的子类,编译器是不知道的。

(3)如果把"泛型类<Tdd>"指定为"泛型类<? super 下限>":那么该泛型类中所有参数是T类型的方法或成员都可以使用,但是有要求。参数类型不是T类型的方法照常使用。

相关文章
|
6月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
426 10
|
6月前
|
Java
Java 数组学习笔记
本文整理Java数组常用操作:遍历、求和、查找、最值及二维数组行求和等典型练习,涵盖静态初始化、元素翻倍、去极值求平均等实例,帮助掌握数组基础与应用。
|
6月前
|
小程序 Java 知识图谱
Java 学习笔记 —— BMI & BMR 计算器
这是一个使用 Java 编写的 BMI 与 BMR 计算器小程序,可输入年龄、性别、身高和体重,计算身体质量指数(BMI)和基础代谢率(BMR),并输出健康评估结果。通过该项目,掌握了 Java 的输入处理、数据验证、条件判断、数学运算及格式化输出等基础知识,是 Java 初学者的理想练习项目。
|
6月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
1101 1
|
8月前
|
安全 Java API
在Java中识别泛型信息
以上步骤和示例代码展示了怎样在Java中获取泛型类、泛型方法和泛型字段的类型参数信息。这些方法利用Java的反射API来绕过类型擦除的限制并访问运行时的类型信息。这对于在运行时进行类型安全的操作是很有帮助的,比如在创建类型安全的集合或者其他复杂数据结构时处理泛型。注意,过度使用反射可能会导致代码难以理解和维护,因此应该在确有必要时才使用反射来获取泛型信息。
290 11
|
9月前
|
Java API 微服务
2025 年 Java 从入门到精通学习笔记全新版
《Java学习笔记:从入门到精通(2025更新版)》是一本全面覆盖Java开发核心技能的指南,适合零基础到高级开发者。内容包括Java基础(如开发环境配置、核心语法增强)、面向对象编程(密封类、接口增强)、进阶技术(虚拟线程、结构化并发、向量API)、实用类库与框架(HTTP客户端、Spring Boot)、微服务与云原生(容器化、Kubernetes)、响应式编程(Reactor、WebFlux)、函数式编程(Stream API)、测试技术(JUnit 5、Mockito)、数据持久化(JPA、R2DBC)以及实战项目(Todo应用)。
474 5
|
9月前
|
设计模式 算法 Java
Java SE 与 Java EE 组件封装使用方法及实践指南
本指南详细介绍了Java SE与Java EE的核心技术使用方法及组件封装策略。涵盖集合框架、文件操作、Servlet、JPA、EJB和RESTful API的使用示例,提供通用工具类与基础组件封装建议,如集合工具类、文件工具类、基础Servlet、实体基类和服务基类等。同时,通过分层架构集成示例展示Servlet、EJB和JPA的协同工作,并总结组件封装的最佳实践,包括单一职责原则、接口抽象、依赖注入、事务管理和异常处理等。适合希望提升代码可维护性和扩展性的开发者参考。
297 0
|
12月前
|
存储 Java
# 【Java全栈学习笔记-U1-day02】变量+数据类型+运算符
本篇笔记主要围绕Java全栈学习的第二天内容展开,涵盖了变量、数据类型、运算符以及Scanner类的应用。首先介绍了变量的概念与命名规范,以及如何定义和使用变量;接着详细讲解了Java中的基本数据类型,包括整型、浮点型、字符型、布尔型等,并通过实例演示了数据类型的运用。随后,深入探讨了各类运算符(赋值、算术、关系、逻辑)及其优先级,帮助理解表达式的构成。最后,介绍了如何利用Scanner类实现用户输入功能,并通过多个综合示例(如计算圆面积、购物打折、变量交换及银行利息计算)巩固所学知识。完成相关作业将进一步加深对这些基础概念的理解与实践能力。
228 13
|
开发框架 Java 开发工具
【Java全栈学习笔记-U1-day01】Java介绍
本笔记整理了Java学习的基础内容,涵盖程序理解、Java语言特性、JDK安装与配置、Java程序开发工具及编写步骤。重点介绍了Java程序的基本结构、编译和运行过程,以及输出语句的使用。通过实例演示了IDEA创建Java程序的方法,并强调了编码规范和注意事项。适合初学者复习和交流学习。 主要内容: 1. 理解程序:计算机组成、程序定义。 2. 简介:Java语言特点、技术平台、JDK作用。 3. 编写Java程序:编写、编译、运行步骤,基本结构。 4. 输出语句 5. DEA使用:新建工程、保存位置、文件介绍、新建类。 6. 扩展:注释、代码规范、大小写敏感、缩进等。
|
5月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
274 2