初识Java 18-5 泛型

news2025/1/19 8:23:32

目录

动态类型安全

异常

混型

C++中的混型

替代方案

与接口混合

使用装饰器模式

与动态代理混合


本笔记参考自: 《On Java 中文版》


动态类型安全

        在Java 5引入泛型前,老版本的Java程序中就已经存在了List等原生集合类型。这意味着,我们可以向这些老版本的代码中传递泛型集合,这无疑是存在风险的。为此,Java 5在java.util.Collections中添加了一组实用工具checked*()

  • checkedCollection()
  • checkList()
  • checkedMap()
  • checkedSet()
  • checkedSortedMap()
  • checkedSortSet()

        这些方法的第一个参数都是被检查的集合,之后的参数会传入需要强制确保的类型。若这些方法检测到插入了不匹配的类型,就会发出异常。

    这一点和Java 5之前的原生集合不同,它们只会在我们从中取出对象时报告问题。此时,我们难以找出发生问题的代码。

        下面的例子展示了check*()的用法:

【例子:check*()的使用】

        假设我们有一个Pet类,它有两个子类CatDog。 则代码如下所示:

import reflection.pets.Cat;
import reflection.pets.Dog;
import reflection.pets.Pet;

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

public class CheckedList {
    // 一个Java 5之前遗留的方法:
    @SuppressWarnings("unchecked")
    static void oldStyleMethod(List probablyDogs) {
        // 向probablyDogs集合中传入错误的Cat类型:
        probablyDogs.add(new Cat());
    }

    public static void main(String[] args) {
        List<Dog> dogs1 = new ArrayList<>();
        // 安静地放入了Cat类型:
        oldStyleMethod(dogs1);

        List<Dog> dogs2 = Collections.checkedList(
                new ArrayList<>(), Dog.class);
        try {
            oldStyleMethod(dogs2);
        } catch (Exception e) {
            System.out.println("发生异常:" + e);
        }

        // 使用基类集合不会存在问题:
        List<Pet> pets = Collections.checkedList(
                new ArrayList<>(), Pet.class);
        pets.add(new Dog());
        pets.add(new Cat());
    }
}

        程序执行的结果是:

        可以发现,oldStyleMethod(dogs1)并没有受到编译器的质疑,尽管方法内部会往dogs1集合中插入一个不匹配的Cat对象。而dogs2立刻抛出异常。

异常

        因为类型擦除,catch子句无法捕获泛型类型的异常,因为无法获知异常的确切类型。也因此,泛型类无法直接或间接地继承Throwable

        但类型参数可以用于方法声明中的throws子句。根据这个特点,我们可以编写随(受检查的)异常类型变化而变化的泛型代码:

【例子:可变化的异常】

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

interface Processor<T, E extends Exception> {
    void process(List<T> resultCollector) throws E;
}

class ProcessRunner<T, E extends Exception>
        extends ArrayList<Processor<T, E>> {
    List<T> processAll() throws E {
        List<T> resultCollector = new ArrayList<>();
        for (Processor<T, E> processor : this)
            processor.process(resultCollector);
        return resultCollector;
    }
}

class Failure1 extends Exception {
}

class Processor1
        implements Processor<String, Failure1> {
    static int count = 3;

    @Override
    public void process(List<String> resultCollector)
            throws Failure1 {
        if (count-- > 1)
            resultCollector.add("哼!");
        else
            resultCollector.add("哈!");

        if (count < 0)
            throw new Failure1();
    }
}

class Failure2 extends Exception {
}

class Processor2
        implements Processor<Integer, Failure2> {
    static int count = 2;

    @Override
    public void process(List<Integer> resultCollector)
            throws Failure2 {
        if (count-- == 0)
            resultCollector.add(47);
        else {
            resultCollector.add(11);
        }

        if (count < 0)
            throw new Failure2();
    }
}

public class ThrowGenericException {
    public static void main(String[] args) {
        ProcessRunner<String, Failure1> runner1 =
                new ProcessRunner<>();
        for (int i = 0; i < 3; i++)
            runner1.add(new Processor1());
        try {
            System.out.println(runner1.processAll());
        } catch (Failure1 e) {
            System.out.println(e);
        }

        ProcessRunner<Integer, Failure2> runner2 =
                new ProcessRunner<>();
        for (int i = 0; i < 3; i++)
            runner2.add(new Processor2());
        try {
            System.out.println(runner2.processAll());
        } catch (Failure2 e) {
            System.out.println(e);
        }
    }
}

        程序执行的结果是:

        Processor规定了会抛出异常E的方法process()process()的结果被保存在了List<T> resultCollector中(这个参数也被称为采集参数)。

    因为检查型异常的缘故,如果不使用参数化的异常,我们就无法泛化地进行如上的代码编写。

混型

        混型最基本的概念是,混合多个类的能力,生成一个可以代表混型中所有类型的类(不过,这通常是我们最后做的一件事)。

        对混型的更改会应用于所有使用了该混型的类中。可以说,混型更加接近面向切面编程。

C++中的混型

        依旧是先看看混型在C++中的表现。C++会通过多重继承实现混型,除此之外,参数化类型也是一个不错的实现手段。

【例子:在C++中使用混型】

#include<string>
#include<ctime>
#include<iostream>

using namespace std;

template<class T> class TimeStamped : public T {
    long timeStamp;
public:
    TimeStamped() {
        timeStamp = time(0);
    }
    long getStamp() {
        return timeStamp;
    }
};

template<class T> class SerialNumbered : public T {
    long serialNumber;
    static long counter;
public:
    SerialNumbered() {
        serialNumber = counter++;
    }
    long getSerialNumber() {
        return serialNumber;
    }
};

// 定义静态存储,并进行初始化
template<class T> long SerialNumbered<T>::counter = 1;

class Basic {
    string value;
public:
    void set(string val) {
        value = val;
    }
    string get() {
        return value;
    }
};

int main() {
    // 使用混型:
    TimeStamped<SerialNumbered<Basic>> mixin1, mixin2;
    mixin1.set("test 1");
    mixin2.set("test 2");
    cout << mixin1.get() << ": " << mixin1.getStamp() <<
        " " << mixin1.getSerialNumber() << endl;
    cout << mixin2.get() << ": " << mixin2.getStamp() <<
        " " << mixin2.getSerialNumber() << endl;
}

        程序执行的结果是:

        mixin1mixin2具有所有混入类型的方法,这就相当于将已有的类映射到新的子类上一样。

        不幸的是,由于类型擦除会丢弃基类的类型,因此在Java中,泛型类无法直接继承自泛型参数


替代方案

        为了在Java中使用混型,下面将会给出一些替代的方案。

与接口混合

        一种常见的方式是通过接口来实现泛型的效果:

【例子:使用接口实现混型的效果】

import java.util.Date;

interface TimeStamped {
    long getStamp();
}

class TimeStampedImp implements TimeStamped {
    private final long timeStamp;

    TimeStampedImp() {
        timeStamp = new Date().getTime();
    }

    @Override
    public long getStamp() {
        return timeStamp;
    }
}

interface SerialNumbered {
    long getSerialNumber();
}

class SerialNumberedImp implements SerialNumbered {
    private static long counter = 1;
    private final long serialNumber = counter++;

    @Override
    public long getSerialNumber() {
        return serialNumber;
    }
}

interface Basic {
    void set(String val);

    String get();
}

class BasicImp implements Basic {
    private String value;

    @Override
    public void set(String val) {
        value = val;
    }

    @Override
    public String get() {
        return value;
    }
}

// 混合多个类的能力
class Mixin extends BasicImp
        implements TimeStamped, SerialNumbered {
    private TimeStamped timeStamp =
            new TimeStampedImp();
    private SerialNumbered serialNumber =
            new SerialNumberedImp();

    @Override
    public long getStamp() {
        return timeStamp.getStamp();
    }

    @Override
    public long getSerialNumber() {
        return serialNumber.getSerialNumber();
    }
}

public class Mixins {
    public static void main(String[] args) {
        Mixin mixin1 = new Mixin(),
                mixin2 = new Mixin();
        mixin1.set("Test 1");
        mixin2.set("Test 2");
        System.out.println(mixin1.get() + ": " +
                mixin1.getStamp() + " " +
                mixin1.getSerialNumber());
        System.out.println(mixin2.get() + ": " +
                mixin2.getStamp() + " " +
                mixin2.getSerialNumber());
    }
}

        程序执行的结果是:

        在这里,Mixin类使用的是委托模式,这种模式要求每个被混入其中的类在Mixin中都有一个字段,Mixin负责把对应的任务委托给字段所代表的类。

        然而,这种做法在面对复杂的混型时会导致代码量的急剧增加。

----------

使用装饰器模式

||| 装饰器模式:用其他的类装饰一个可包装的类,分层叠加功能。

        可以发现,装饰器模式和混型的概念有相似之处。装饰器通过组合和规范的结构(这个结构就是可装饰物和装饰器的层次结构)进行实现,而混型的实现基于继承。

    装饰器是透明的,可以通过一个公共的信息集向其传递信息。

(可以将混型看做一种不要求装饰器继承结构的泛型装饰器机制)

【例子:使用装饰器重写上一个例子】

import java.util.Date;

class Basic {
    private String value;

    public void set(String val) {
        val = value;
    }

    public String get() {
        return value;
    }
}

class Decorator extends Basic {
    protected Basic basic;

    Decorator(Basic basic) {
        this.basic = basic;
    }

    @Override
    public void set(String val) {
        basic.set(val);
    }

    @Override
    public String get() {
        return basic.get();
    }
}

class TimeStamped extends Decorator {
    private final long timeStamp;

    TimeStamped(Basic basic) {
        super(basic);
        timeStamp = new Date().getTime();
    }

    public long getStamp() {
        return timeStamp;
    }
}

class SerialNumbered extends Decorator {
    private static long counter = 1;
    private final long serialNumber = counter++;

    SerialNumbered(Basic basic) {
        super(basic);
    }

    public long getSerialNumber() {
        return serialNumber;
    }
}

public class Decoration {
    public static void main(String[] args) {
        TimeStamped t1 = new TimeStamped(new Basic());
        TimeStamped t2 = new TimeStamped(
                new SerialNumbered(new Basic()));
        // 该方法不可用:
        // t2.getSerialNumber();
        t2.getStamp();

        SerialNumbered s1 = new SerialNumbered(new Basic());
        SerialNumbered s2 = new SerialNumbered(
                new TimeStamped(new Basic()));
        // 同样不可用:
        // s2.getStamp();
        s2.getSerialNumber();
    }
}

        通过这种方式创建的类也会包含所有所需的方法,但是使用装饰器产生的对象类型是其层次结构上的最后一层包装。换言之,因为只有最后一层是实际的类型,因此只有最后一层的方法是可见的

----------

与动态代理混合

        通过动态代理(可见笔记17-3),可以创建一种更捷径混型的机制。使用了动态代理,获得的结果类的动态类型将会是混合后的合并类型。

        需要注意的是,在动态代理中每个被混入的类都必须是某个接口的实现

【例子:使用混合代理实现混型】

import onjava.Tuple2;

import static onjava.Tuple.*;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

class MixinProxy implements InvocationHandler {
    Map<String, Object> delegateByMethod;

    @SuppressWarnings("unchecked")
    MixinProxy(Tuple2<Object, Class<?>>... pairs) {
        delegateByMethod = new HashMap<>();
        for (Tuple2<Object, Class<?>> pair : pairs) {
            for (Method method : pair.b2.getMethods()) {
                String methodName = method.getName();

                // containKey()来自Map类:如果包含key值,则返回true
                if (!delegateByMethod.containsKey(methodName))
                    delegateByMethod.put(methodName, pair.a2);
            }
        }
    }

    @Override
    public Object invoke(
            Object proxy, Method method, Object[] args)
            throws Throwable {
        String methodName = method.getName();
        Object delegate = delegateByMethod.get(methodName);
        return method.invoke(delegate, args);
    }

    @SuppressWarnings("unchecked")
    public static Object newInstance(Tuple2... pairs) {
        Class[] interfaces = new Class[pairs.length];
        for (int i = 0; i < pairs.length; i++) {
            interfaces[i] = (Class) pairs[i].b2;
        }
        ClassLoader cl =
                pairs[0].a2.getClass().getClassLoader();
        return Proxy.newProxyInstance(
                cl, interfaces, new MixinProxy(pairs));
    }
}

public class DynamicProxyMixin {
    public static void main(String[] args) {
        // BasicImp来自于Mixins.java的那个例子:
        @SuppressWarnings("unchecked")
        Object mixin = MixinProxy.newInstance(
                tuple(new BasicImp(), Basic.class),
                tuple(new TimeStampedImp(), TimeStamped.class),
                tuple(new SerialNumberedImp(),
                        SerialNumbered.class));
        Basic b = (Basic) mixin;
        TimeStamped t = (TimeStamped) mixin;
        SerialNumbered s = (SerialNumbered) mixin;

        b.set("Hello");
        System.out.println(b.get());
        System.out.println(t.getStamp());
        System.out.println(s.getSerialNumber());
    }
}

        程序执行的结果是:

        然而,这种实现只对动态类型有效,而不会包括静态类型。除此之外,如main()中所展示的:

在使用方法之前,我们还需要强制向下转型,这也会带来多余的麻烦。

    为了支持Java的混型,业界开发了不止一个用于支持泛型的附加语言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1269438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode(38)生命游戏【矩阵】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 生命游戏 1.题目 根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是…

Linux(fork+exec创建进程)

1.进程创建 内核设计与实现43页; 执行了3次ps -f ,ps -f的父进程的ID(PPID)都是一样的,即bash. 实际上Linux上这个bash就是不断的复制自身,然后把复制出来的用exec替换成想要执行的程序(比如ps); 运行ps,发现ps是bash的一个子进程;原因就是bash把自己复制一份,然后替换成ps;…

深度学习-模型调试经验总结

1、 这句话的意思是&#xff1a;期望张量的后端处理是在cpu上&#xff0c;但是实际是在cuda上。排查代码发现&#xff0c;数据还在cpu上&#xff0c;但是模型已经转到cuda上&#xff0c;所以可以通过把数据转到cuda上解决。 解决代码&#xff1a; tensor.to("cuda")…

vuepress-----7、发布在GitHub

# 7、发布在GitHub 在你的项目中&#xff0c;创建一个如下的 deploy.sh 文件&#xff08;请自行判断去掉高亮行的注释&#xff09;: #!/usr/bin/env sh# 确保脚本抛出遇到的错误 set -e# 生成静态文件 npm run docs:build# 进入生成的文件夹 cd docs/.vuepress/dist# 如果是发…

attention中Q,K,V的理解

第一种 1.首先定义三个线性变换矩阵&#xff0c;query&#xff0c;key&#xff0c;value&#xff1a; class BertSelfAttention(nn.Module):self.query nn.Linear(config.hidden_size, self.all_head_size) # 输入768&#xff0c; 输出768self.key nn.Linear(config.hidde…

python实验3 石头剪刀布游戏

实验3&#xff1a;石头剪刀布游戏 一、实验目的二、知识要点图三、实验1. 石头剪刀布2. 实现大侠个人信息 一、实验目的 了解3类基本组合数据类型。理解列表概念并掌握Python中列表的使用。理解字典概念并掌握Python中字典的使用。运用jieba库进行中文分词并进行文本词频统计。…

COGVLM论文解读(COGVLM:VISUAL EXPERT FOR LARGE LANGUAGE MODELS)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、摘要二、引言三、模型方法1、模型思路2、融合公式 四、训练方法总结 前言 2023年5月18日清华&智谱AI发布并开源VisualGLM-6B以来&#xff0c;清华KEG&…

竞赛选题 题目:基于深度学习的中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…

BUUCTF [GXYCTF2019]BabyUpload 1详解(.htaccess配置文件特性)

题目环境&#xff1a;查看题目源码 SetHandler application/x-httpd-php 通过源码可以看出这道文件上传题目主要还是考察.htaccess配置文件的特性 倘若不先上传.htaccess配置文件&#xff0c;那么后台服务器就无法解析php代码 这个是需要注意的 .htaccess配置文件特性 概述来说…

函数学习 PTA 1使用函数输出一个整数的逆序数;3判断满足条件的三位数;5使用函数求余弦函数的近似值

其实一共有五道题&#xff0c;但那两道实在太过简单&#xff0c;也不好意思打出来给大家看&#xff0c;那么这篇博客&#xff0c;就让我一次性写三道题吧&#xff01;也当是个小总结&#xff0c;睡前深思。 6-1 使用函数输出一个整数的逆序数 本题要求实现一个求整数的逆序数的…

STM32F407-14.3.6-01输入捕获模式

输入捕获模式 在输入捕获模式下&#xff0c;当相应的 ICx⑦ 信号检测到跳变沿后&#xff0c;将使用捕获/比较寄存器 (TIMx_CCRx⑪) 来锁存计数器的值。发生捕获事件时&#xff0c;会将相应的 CCXIF⑬ 标志&#xff08;TIMx_SR 寄存器&#xff09;置 1&#xff0c; 并可发送中断…

云时空社会化商业 ERP 系统 Shiro 反序列化漏洞复现

0x01 产品简介 时空云社会化商业ERP&#xff08;简称时空云ERP&#xff09; &#xff0c;该产品采用JAVA语言和Oracle数据库&#xff0c; 融合用友软件的先进管理理念&#xff0c;汇集各医药企业特色管理需求&#xff0c;通过规范各个流通环节从而提高企业竞争力、降低人员成本…

SpringMVC多种类型数据响应

SpringMVC多种类型数据响应入门 1.概念 RequestMapping 作用&#xff1a;用于建立请求URL和处理请求方法之间的对应关系 位置&#xff1a; 类上&#xff0c;请求URL的第一级访问目录。此处不写的话&#xff0c;就相当于应用的根目录 方法上&#xff0c;请求URL的第二级访问目…

【二叉树】Leetcode 199. 二叉树的右视图

力扣题目链接 解题思路 一开始&#xff0c;我以为只需要依次遍历最右边一列所有数即可&#xff0c;写出来的代码也通过了样例&#xff1a; class Solution { public:vector<int> rightSideView(TreeNode* root) {vector<int> ans;TreeNode* temp root;while(tem…

游戏缺少d3dx9_43.dll修复方法分享,快速解决dll缺失问题

在计算机使用过程中&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是“找不到d3dx9_43.dll文件”。这个错误通常出现在运行某些游戏或应用程序时&#xff0c;d3dx9_43.dll是一个动态链接库文件&#xff0c;它是DirectX 9的一部分&#xff0c;用于支持游戏中的3…

LeetCode 7 整数反转

题目描述 整数反转 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] &#xff0c;就返回 0。 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;。 示例…

U-boot(七):U-boot移植

本文主要探讨基于210官方U-boot源码移植。 移植基础 tar -jxvf android_uboot_smdkv210.tar.bz2cd u-boot-samsung-devrm -rf onenand_ipl onenand_bl1 lib_avr32 lib_blackfin lib_i386 lib_m68k lib_mips lib_microblaze lib_nios lib_nios2 lib_ppc lib_sh lib_sparccd bo…

Ubuntu防止休眠和挂起(笔记)

目录 1 动机2 禁用休眠3 解除休眠 1 动机 我要将 饿啊人制作成 noah-mp 的区域运行强迫&#xff0c;但是跑的慢&#xff0c;一晚上两天。后来发现是因为电脑自动 supend 了。Ubuntu 在电源那里最多只能设置 2 小时的防止休眠&#xff0c;2小时候又自动休眠&#xff0c;严重影响…

mongodb基本操作命令

mongodb快速搭建及使用 1.mongodb安装1.1 docker安装启动mongodb 2.mongo shell常用命令2.1 插入文档2.1.1 插入单个文档2.1.2 插入多个文档2.1.3 用脚本批量插入 2.2 查询文档 前言&#xff1a;本篇默认你是对nongodb的基础概念有了了解&#xff0c;操作是非常基础的。但是与关…

Opencv手势控制音量!附源码!

效果演示&#xff1a; 废话不多说&#xff01;直接上源码&#xff01;下面写有所有代码注释&#xff01;&#xff01; import cv2 import mediapipe as mp #它包含了各种预训练的机器学习模型&#xff0c;可以用于姿势估计、手势识别等任务 from ctypes import cast, POINTE…