初识Java 18-5 泛型

目录

动态类型安全

异常

混型

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 Processor1implements Processor<String, Failure1> {static int count = 3;@Overridepublic void process(List<String> resultCollector)throws Failure1 {if (count-- > 1)resultCollector.add("哼!");elseresultCollector.add("哈!");if (count < 0)throw new Failure1();}
}class Failure2 extends Exception {
}class Processor2implements Processor<Integer, Failure2> {static int count = 2;@Overridepublic 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();}@Overridepublic long getStamp() {return timeStamp;}
}interface SerialNumbered {long getSerialNumber();
}class SerialNumberedImp implements SerialNumbered {private static long counter = 1;private final long serialNumber = counter++;@Overridepublic long getSerialNumber() {return serialNumber;}
}interface Basic {void set(String val);String get();
}class BasicImp implements Basic {private String value;@Overridepublic void set(String val) {value = val;}@Overridepublic String get() {return value;}
}// 混合多个类的能力
class Mixin extends BasicImpimplements TimeStamped, SerialNumbered {private TimeStamped timeStamp =new TimeStampedImp();private SerialNumbered serialNumber =new SerialNumberedImp();@Overridepublic long getStamp() {return timeStamp.getStamp();}@Overridepublic 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;}@Overridepublic void set(String val) {basic.set(val);}@Overridepublic 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值,则返回trueif (!delegateByMethod.containsKey(methodName))delegateByMethod.put(methodName, pair.a2);}}}@Overridepublic 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.mzph.cn/news/183392.shtml

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

相关文章

【计算机网络笔记】交换机

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

上海亚商投顾:沪指震荡下跌 成交量继续下破8000亿

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日震荡调整&#xff0c;深成指、创业板指午后跌超1%&#xff0c;北证50指数跌超7%&#xff0c;超百只北…

Windows11编译Hadoop3.3.6源码

由于https://github.com/kontext-tech/winutils还未发布3.3.6版本&#xff0c;因此尝试源码编译 目录 环境和安装包准备&#xff0c;见2zlib编译方法一&#xff1a;方法二&#xff1a; 配置文件更改1. maven阿里云镜像2. Node版本3. 越过Javadoc检查 编译HadoopError,其他报错…

JavaScript中的异步处理方法

JavaScript中的异步处理是开发者在日常开发过程中必须面对的一个重要问题。由于JavaScript是单线程的&#xff0c;因此对于一些可能需要长时间执行的操作&#xff0c;如网络请求、IO操作等&#xff0c;如果采用同步的方式&#xff0c;可能会导致应用程序的阻塞&#xff0c;降低…

科研学习|论文解读——Deep learning for anomaly detection in log data: a survey

摘要 自动日志文件分析能够及早发现系统故障等相关事件。特别是&#xff0c;自学习异常检测技术能够捕捉日志数据中的模式&#xff0c;然后向系统操作员报告意外的日志发生&#xff0c;而无需提前提供或手动建模异常场景。最近&#xff0c;越来越多的利用深度学习方法来实现此目…

ExoPlayer - Failed to initialize OMX.qcom.video.decoder.avc

人莫鉴于流水而鉴于止水&#xff0c;唯止能止众止 1. 背景 使用ExoPlayer&#xff0c;我不信你没遇到过这个问题&#xff1a; java.lang.IllegalArgumentException: Failed to initialize OMX.qcom.video.decoder.avc 详细内容如下图所示&#xff1a; 2. MediaCodec(解码器) …

FastApi接收不到Apifox发送的from-data字符串_解决方法

接收不到Apifox发送的from-data字符串_解决方法 问题描述解决方法弯路总结弯路描述纵观全局小结 问题描述 这里写了一个接口&#xff0c;功能是上传文件&#xff0c;接口参数是file文件和一个id字符串 gpt_router.post("/uploadfiles") async def create_upload_fi…

配置自动化部署Jenkins和Gitea

配置自动化部署 这里使用的是JenkinsGitea 如果不知道怎么安装Jenkins和Gitea可以参考下面文章 https://blog.csdn.net/weixin_46533577/article/details/134644144 我的另一篇文章 介绍 前端 先说下自己的情况&#xff0c;因为自己服务器原因&#xff0c;使用的服务器内…

Linux基础项目开发1:量产工具——输入系统(三)

前言&#xff1a; 前面我们已经实现了显示系统&#xff0c;现在我们来实现输入系统&#xff0c;与显示系统类似&#xff0c;下面让我们一起来对输入系统进行学习搭建吧 目录 一、数据结构抽象 1. 数据本身 2. 设备本身&#xff1a; 3. input_manager.h 二、触摸屏编程 to…

Mysq8l在Centos上安装后忘记root密码如何重新设置

场景 Mysql8在Windows上离线安装时忘记root密码&#xff1a; Mysql8在Windows上离线安装时忘记root密码-CSDN博客 如果是在Windows上忘记密码可以参考上面。 如果在Centos中安装mysql可以参考下面。 CentOS7中安装Mysql8并配置远程连接和修改密码等&#xff1a; CentOS7中…

BEVFormer【人工智能】

BEVFormer 是一篇今年中稿 ECCV 2022 的论文&#xff0c;其中提出了一种纯视觉&#xff08;camera&#xff09;感知任务的算法模型&#xff0c;用于实现3D目标检测和地图分割任务。该算法通过提取环视相机&#xff08;Bird’s Eye View Camera&#xff09;采集到的图像特征&…

阿里云Windows server2016 安装Docker

阿里云Windows server2016 安装Docker 1 软件环境介绍2 下载更新2.1 windowsR 输入sconfig2.2 下载最新版的安装包&#xff0c;安装并重启2.3 下载并安装更新2.4 以管理员方式运行powershell2.5 将Tls修改成二级2.6 安装NuGet服务2.7 安装docker模块2.7 安装 docker包 32.8 查看…

Reactor模式

Reactor模式有点类似事件驱动模式。在事件驱动模式中&#xff0c;当有事件触发时&#xff0c;事件源会将事件分发到Handler&#xff08;处理器&#xff09;&#xff0c;由Handler负责事件处理。Reactor模式中的反应器角色类似于事件驱动 模式中的事件分发器&#xff08;Dispatc…

解析Top-K问题及堆排序算法

Top-K问题是在海量数据中找到最大或最小的K个元素&#xff0c;它在实际应用中非常常见&#xff0c;例如专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。在面对大规模数据时&#xff0c;直接对数据进行排序可能效率低下&#xff0c;因为排序的时间复杂度通常为O(n lo…

lxd提权

lxd/lxc提权 漏洞介绍 lxd是一个root进程&#xff0c;它可以负责执行任意用户的lxd&#xff0c;unix套接字写入访问操作。而且在一些情况下&#xff0c;lxd不会调用它的用户权限进行检查和匹配 原理可以理解为用用户创建一个容器&#xff0c;再用容器挂载宿主机磁盘&#xf…

ZooKeeper的分布式锁---客户端命令行测试(实操课程)

本系列是zookeeper相关的实操课程&#xff0c;课程测试环环相扣&#xff0c;请按照顺序阅读测试来学习zookeeper。阅读本文之前&#xff0c;请先阅读----​​​​​​zookeeper 单机伪集群搭建简单记录&#xff08;实操课程系列&#xff09;。 阅读本文之前&#xff0c;请先阅读…

线性表——(2)线性表的顺序存储及其运算的实现

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 看到美好&#xff0c;感受美好&a…

GDPU 数据结构 天码行空12

文章目录 数据结构实验十二 图的遍历及应用一、【实验目的】二、【实验内容】三、实验源代码&#x1f37b; CPP&#x1f37b; C 数据结构实验十二 图的遍历及应用 一、【实验目的】 1、 理解图的存储结构与基本操作&#xff1b; 2、熟悉图的深度度优先遍历和广度优先遍历算法…

WEB渗透—反序列化(九)

Web渗透—反序列化 课程学习分享&#xff08;课程非本人制作&#xff0c;仅提供学习分享&#xff09; 靶场下载地址&#xff1a;GitHub - mcc0624/php_ser_Class: php反序列化靶场课程&#xff0c;基于课程制作的靶场 课程地址&#xff1a;PHP反序列化漏洞学习_哔哩哔_…