初识Java 18-6 泛型

目录

潜在类型机制

支持潜在类型机制的语言

Python的潜在类型机制

C++的潜在类型机制

Java中的直接潜在类型机制

潜在类型机制的替代方案

反射

将方法应用于序列中的每个元素

Java 8的潜在类型机制(间接实现)

潜在类型机制的使用例(Suppliers)

总结


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


潜在类型机制

        通过泛型,我们应该可以向“将代码写得更通用一点”这一理念更进一步。特别是在编写简单的Java泛型时,泛型可以在不了解具体类型的情况下执行方法。然而,随着对Java泛型的了解逐渐深入,类型擦除无疑使泛型的作用打了折扣,并限制了“泛型”这一概念。

        一些语言会提供潜在类型机制(又称结构化类型机制),它还有一个更有意思的名称:鸭子类型机制。比方说,“如果某件事物走路像鸭子,说话也像鸭子,那么就可以把它看做鸭子”。

    与泛型不同,潜在类型机制只对方法本身有所要求,而不需要实现特别的类或接口。

        潜在类型机制可以超越类的层次结构,调用不属于某个公共接口的方法。

支持潜在类型机制的语言

        有许多语言支持潜在类型机制,例如Python、C++和Go等。

Python的潜在类型机制

        先看看Python中的潜在类型机制:

【例子:Python中的潜在类型机制】

class Dog:def speak(self):print("汪!")def sit(self):print("坐着")def reproduce(self):passclass Robot:def speak(self):print("锵!")def sit(self):print("咔")def oilChange(self):passdef perform(anything):anything.speak()anything.sit()a = Dog()
b = Robot()
perform(a)
perform(b)

        程序执行的结果是:

        在perform(anything)中并不包含任何关于anything的类型信息,anything只是一个标识符。在后台,anything相当于一个被隐藏的接口,它包含了perform()要求的操作。但我们无需显式地表明它,因为它是潜在的。

        而如果向perform()传入不支持操作的对象,就会报错:

----------

C++的潜在类型机制

        同样可以用C++来实现上面的例子:

【例子:C++中的潜在类型机制】

#include<iostream>
using namespace std;class Dog {
public:void speak() {cout << "汪!" << endl;}void sit() {cout << "坐着" << endl;}
};class Robot {
public:void speak() {cout << "锵!" << endl;}void sit() {cout << "咔" << endl;}void oilChange() {}
};template<class T> void perform(T anything) {anything.speak();anything.sit();
}int main() {Dog d;Robot r;perform(d);perform(r);
}

        程序执行的结果相同:

        若试图传入错误的类型,编译器也会报错。不同与Python,C++会在运行时抛出错误(尽管C++的错误信息出了名的冗长)。但这两门语言都保证了类型不会错用。

----------

        也可以使用Go实现这个例子:

【例子:Go中的潜在类型机制】

package main
import "fmt"type Dog struct{}
func (this Dog) speak() {fmt.Printf("汪!\n")
}
func (this Dog) sit(){fmt.Printf("坐着\n")
}
func (this Dog) reproduce(){
}type Robot struct{}
func (this Robot) speak() {fmt.Printf("锵!\n")
}
func (this Robot) sit(){fmt.Printf("咔\n")
}
func (this Robot) oilChange(){
}func perform(speaker interface {speak(); sit()}){speaker.speak()speaker.sit()
}func main(){perform(Dog{})perform(Robot{})
}

        程序会得到相同的结果:


Java中的直接潜在类型机制

        Java的泛型加入得较晚,因此没有支持潜在类型机制。因此,若要在Java中实现上面所述的效果,通常会需要使用接口(并且使用边界):

【例子:通过接口模拟潜在类型机制】

import reflection.pets.Dog;class PerformingDog extends Dog implements Performs {@Overridepublic void speak() {System.out.println("汪!");}@Overridepublic void sit() {System.out.println("坐下");}public void reproduce() {}
}class Robot implements Performs {@Overridepublic void speak() {System.out.println("锵!");}@Overridepublic void sit() {System.out.println("咔");}public void oilChange() {}
}class Communicate {// 通过边界,调用接口的方法:public static <T extends Performs>void perform(T performer) {performer.speak();performer.sit();}
}public class DogsAndRobots {public static void main(String[] args) {Communicate.perform(new PerformingDog());Communicate.perform(new Robot());}
}

        程序执行的结果是:

        然而,仔细考虑这种做法会发现,Communicate.perform()并不需要使用到泛型,它可以直接使用Performs接口:

class Communicate {public static void perform(Performs performer) {performer.speak();performer.sit();}
}

说到底,无论是PerformingDog还是Robot都已经强制实现了Performs接口。

潜在类型机制的替代方案

        尽管Java并没有(直接)支持潜在类型机制,但我们依旧可以想办法创建出真正意义上的泛型代码,实现方法的跨层次应用。

反射

        一个方案是使用反射:

【例子:使用反射创建泛型代码】

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;// 这个类没有实现Perform接口:
class Mime {public void walkAgainstTheWind() {}public void sit() {System.out.println("假装坐着");}public void pushInvisibleWalls() {}@Overridepublic String toString() {return "哑剧";}
}class SmartDog {public void speak() {System.out.println("汪!");}public void sit() {System.out.println("坐下");}public void reproduce() {}
}class CommunicateReflectively {public static void perform(Object speaker) {Class<?> spkr = speaker.getClass();try {try {Method speak = spkr.getMethod("speak");speak.invoke(speaker);} catch (NoSuchMethodException e) {System.out.println(speaker + "没法说话");}try {Method sit = spkr.getMethod("sit");sit.invoke(speaker);} catch (NoSuchMethodException e) {System.out.println(speaker + "无法坐下");}} catch (SecurityException |IllegalAccessException |IllegalArgumentException |InvocationTargetException e) {throw new RuntimeException(speaker.toString(), e);}}
}public class LatentReflection {public static void main(String[] args) {CommunicateReflectively.perform(new SmartDog());CommunicateReflectively.perform(new Robot());CommunicateReflectively.perform(new Mime());}
}

        程序执行的结果是:

        在这里,SmartDogRobotMime之间没有任何的直接联系,我们直接通过反射动态调用speak()sit()


将方法应用于序列中的每个元素

        还可以进一步地开发反射。假设我们需要为一个序列中的每个元素应用方法,只使用接口仍是不够的,因此我们可以这样做:

【例子:将一个方法应用于序列】

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class Apply {public static <T, S extends Iterable<T>>void apply(S seq, Method f, Object... args) {try {for (T t : seq)f.invoke(t, args);} catch (IllegalAccessException |IllegalArgumentException |InvocationTargetException e) {throw new RuntimeException(e); // 可能会因为不正确的使用该方法导致异常}}
}

        apply()方法可以接受任意数量的序列元素,并将方法f()应用于所有元素。这种做法有一个好处,f.invoke()可以接受任意长度的序列,因此可以认为apply()也可以做到这点。

    除此之外,apply()方法使用了for-in语法。 这表示着S可以是任何实现了Iterable接口的类。

        接下来就对apply()方法进行测试:

【例子:apply()方法的使用例】

        首先创建一个简单的继承结构,这个结构包含一个父类Shape和一个子类Square

===父类Shape

public class Shape {private static long counter = 0;private final long id = counter++;@Overridepublic String toString() {return getClass().getSimpleName() + " " + id;}public void rotate() {System.out.println(this + " rotate");}public void resize(int newSize) {System.out.println(this + " resize " + newSize);}
}

==子类Square

public class Square extends Shape {}

        接下来的就是测试类ApplyTest

===ApplyTest

import onjava.Suppliers;import java.util.ArrayList;
import java.util.List;public class ApplyTest {public static void main(String[] args)throws Exception {List<Shape> shapes =Suppliers.create(ArrayList::new, Shape::new, 3);Apply.apply(shapes,Shape.class.getMethod("rotate"));Apply.apply(shapes,Shape.class.getMethod("resize", int.class), 7);System.out.println();List<Square> squares =Suppliers.create(ArrayList::new, Square::new, 3);Apply.apply(squares,Shape.class.getMethod("rotate"));Apply.apply(squares,Shape.class.getMethod("resize", int.class), 7);System.out.println();Apply.apply(new FilledList<>(Shape::new, 3),Shape.class.getMethod("rotate"));Apply.apply(new FilledList<>(Square::new, 3),Shape.class.getMethod("rotate"));}
}

        程序执行的结果是:

        首先解释Suppliers.create(),看如下代码:

这行代码等价于,将Shape类的构造器作为生成器,生成3个对象,并将结果放入一个ArrayList对象中。

        尽管反射可以使代码看起来很优雅,但要注意,反射的运行速度通常会慢于非反射的实现。究其原因,反射在运行时处理了太多东西。尽管我们不应该只因为这个理由放弃使用反射,但这无疑是我们必须考虑的一点。

        现在考虑Java 8加入的函数式方式,下面的例子通过它重写ApplyTest

【例子:使用函数式方式重写ApplyTest

import java.util.stream.Stream;public class ApplyFunctional {public static void main(String[] args) {Stream.of(Stream.generate(Shape::new).limit(2),Stream.generate(Square::new).limit(2)).flatMap(c -> c) // 将所有元素扁平化,合成一条流.peek(Shape::rotate).forEach(s -> s.resize(7));System.out.println();new FilledList<>(Shape::new, 2).forEach(Shape::rotate);new FilledList<>(Square::new, 2).forEach(Shape::rotate);}
}

        程序执行的结果是:

        这种重写方式已经抛弃了Apply.apply()了。

        这么做的好处是,它更加简洁、可读性高并且不会抛出异常。因此现在可以这么说,我们只需要在某些只能使用反射来解决的场景中使用反射就好了。

Java 8的潜在类型机制(间接实现)

        Java 8带来的未绑定方法引用可以在某种程度上实现潜在类型机制。

【例子:使用方法引用实现潜在类型机制】

import reflection.pets.Dog;import java.util.function.Consumer;class PerformingDogA extends Dog {public void speak() {System.out.println("汪!");}public void sit() {System.out.println("坐下");}public void reproduce() {}
}class RobotA {public void speak() {System.out.println("锵!");}public void sit() {System.out.println("咔");}public void oilChange() {}
}class CommunicateA {public static <P> void perform(P performer, Consumer<P> action1, Consumer<P> action2) {action1.accept(performer);action2.accept(performer);}
}public class DogsAndRobotMethodReferences {public static void main(String[] args) {CommunicateA.perform(new PerformingDogA(),PerformingDogA::speak, PerformingDogA::sit);CommunicateA.perform(new RobotA(),RobotA::speak, RobotA::sit);CommunicateA.perform(new Mime(),Mime::walkAgainstTheWind, Mime::pushInvisibleWalls);}
}

        程序执行结果是:

        因为CommunicateA.perform()没有对P做出限制,因此它可以是任何类型。perform()只要求提供可供Consumer<P>使用的方法。因此我们可以向其中传入任何与其签名一致的方法

    然而,和真正的潜在类型机制相比,这种做法需要我们显式地提供perform()会用到的方法引用。

        但这种方式也有更好的一点,它其实不会对传入其中的方法名做出要求。在这个意义上,这种方法要更加通用。

潜在类型机制的使用例(Suppliers)

        现在可以通过潜在类型机制创建Suppliers(这个类在之前的笔记中也曾出现)。这个类的方法都用于填充集合:

【例子:潜在类型机制的使用例】

import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Stream;public class Suppliers {// 根据factory创建一个新的集合,并且填充它:public static <T, C extends Collection<T>> Ccreate(Supplier<C> factory, Supplier<T> gen, int n) {return Stream.generate(gen).limit(n).collect(factory, C::add, C::addAll);}// 填充已存在的集合coll:public static <T, C extends Collection<T>>C fill(C coll, Supplier<T> gen, int n) {Stream.generate(gen).limit(n).forEach(coll::add);return coll;}// 使用到未绑定方法引用adder,可以生成更加通用的方法:public static <H, A> H fill(H holder, BiConsumer<H, A> adder,Supplier<A> gen, int n) {Stream.generate(gen).limit(n).forEach(a -> adder.accept(holder, a));return holder;}
}

        在第一个fill()方法中,我们返回了coll(即传入的容器的类型信息),这样就能保证不会丢失类型信息了。

        第二个fill()方法使用了未绑定的方法引用adder。通过adder.accept(),我们可以将操作a应用于对象holder

        接下来可以尝试使用Suppliers了。

【例子:使用Suppliers

import onjava.Suppliers;import java.util.ArrayList;
import java.util.List;class Customer {private static long counter = 1;private final long id = counter++;@Overridepublic String toString() {return "Customer " + id;}
}class Teller {private static long counter = 1;private final long id = counter++;@Overridepublic String toString() {return "Teller " + id;}
}class Bank {private List<BankTeller> tellers =new ArrayList<>();public void put(BankTeller bt) {tellers.add(bt);}
}public class BankTeller {public static void serve(Teller t, Customer c) {System.out.println(t + " 服务于 " + c);}public static void main(String[] args) {// 使用create():RandomList<Teller> tellers =Suppliers.create(RandomList::new, Teller::new, 4);// 演示第一个fill():List<Customer> customers = Suppliers.fill(new ArrayList<>(), Customer::new, 12);customers.forEach(c ->serve(tellers.select(), c));// 演示潜在类型机制:Bank bank = Suppliers.fill(new Bank(), Bank::put, BankTeller::new, 3);// 或使用第二个fill():List<Customer> customers2 = Suppliers.fill(new ArrayList<>(), List::add, Customer::new, 12);}
}

        程序执行的结果是:

        注意第二个fill()的两个使用,我们不仅可以将有所关联的Bank类型,还可以将其用于List

总结

        尽管Java中加入的泛型存在着一些问题,但在此之前我们需要注意一点:无论一门语言多么强大,都有可能被用来编写一个无比糟糕的程序。泛型常常被用于集合之类的场景,但除此之外,泛型还能做到什么?

        泛型的概念应该就像它的名字一样,追求的是一种更加“泛型(泛化)”的代码,使一种代码能够适应更多的场景。

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

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

相关文章

Android 架构实战MVI进阶

MVI架构的原理和流程 MVI架构是一种基于响应式编程的架构模式&#xff0c;它将应用程序分为四个核心组件&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;、意图&#xff08;Intent&#xff09;和状态&#xff08;State&#xff09;。 原理&…

【AntDB数据库】国产数据库崛起之狂飙猛进的互联网技术

中国作为人口大国&#xff0c;也是重要的人才资源大国&#xff0c;人口大国带来了国外数据库厂商在世界其他国家不曾出现的高复杂高密度的需求场景&#xff0c;而人才资源大国则让我们在互联网尤其是移动互联网方面奋起直追&#xff0c;甚至达到了全球领先的水平&#xff0c;进…

通过navicat工具将excel文件导入数据库的表中

文章目录 1.navicat可视化工具2. 导入文件 1.navicat可视化工具 这里使用的是navicat数据库可视化工具&#xff0c;不是直接通过数据库指令导入的 前提是连接好数据库&#xff0c;建立好表&#xff0c;如下图&#xff0c;test为连接名&#xff0c;随便起&#xff0c;data为数据…

学习程序员必知必会的基础算法(收藏)

近年来学习python的程序员愈来愈多&#xff0c;有的同学选择了python培训机构&#xff0c;也有的人觉得自己天赋好选择了自学不管大家怎么去学习&#xff0c;在学习python基础的过程中&#xff0c;肯定离不开的就是基础算法&#xff0c;今天就为大家介绍几大学习中的基础算法。…

LLM面面观之Prefix LM vs Causal LM

1. 背景 关于Prefix LM和Causal LM的区别&#xff0c;本qiang在网上逛了一翻&#xff0c;发现多数客官只给出了结论&#xff0c;但对于懵懵的本qiang&#xff0c;结果仍是懵懵... 因此&#xff0c;消遣了多半天&#xff0c;从原理及出处&#xff0c;交出了Prefix LM和Causal …

Python requests请求响应以流stream的方式打印输出

如果你使用的请求库是requests&#xff0c;那么你必须了解的大模型里的请求怎么响应式的接收并打印出来的。 这里给大家写一下正式的书写方式: import requestsurl "http://localhost:8080/stream"payload {} headers {}response requests.request("GET&q…

回文链表,剑指offer 27,力扣 61

目录 题目&#xff1a; 我们直接看题解吧&#xff1a; 解题方法&#xff1a; 难度分析&#xff1a; 审题目事例提示&#xff1a; 解题分析&#xff1a; 解题思路&#xff08;数组列表双指针&#xff09;&#xff1a; 代码说明补充&#xff1a; 代码实现&#xff1a; 代码实现&a…

智安网络|发现未知风险,探索渗透测试的奥秘与技巧

在当今信息时代&#xff0c;网络安全已成为组织和个人面临的重大挑战。为了保护网络系统的安全&#xff0c;渗透测试成为一种重要的手段。 一、渗透测试的基本原理 渗透测试是通过模拟黑客攻击的方式&#xff0c;对目标系统进行安全评估。其基本原理是模拟真实攻击者的思维和行…

openGauss学习笔记-136 openGauss 数据库运维-例行维护-检查数据库性能

文章目录 openGauss学习笔记-136 openGauss 数据库运维-例行维护-检查数据库性能136.1 检查办法136.2 异常处理 openGauss学习笔记-136 openGauss 数据库运维-例行维护-检查数据库性能 136.1 检查办法 通过openGauss提供的性能统计工具gs_checkperf可以对硬件性能进行检查。 …

一个软件测试练手项目——学生信息管理系统测试,卷起来啊

免费分享一个练手项目&#xff0c;学生信息管理系统&#xff0c;获取方式在文末 1.引言 1.1项目目的 软件测试是为了在软件投入生产性运行之前&#xff0c;尽可能多地发现软件的错误。该项目的目的是给学习软件测试的朋友练手用 1.2 项目背景 随着学校的规模不断扩大&…

【JUC】十九、volatile与内存屏障

文章目录 1、volatile的两大特性2、volatile的四大内存屏障3、分类4、happens-before之volatile变量重排规则5、读写屏障插入策略 1、volatile的两大特性 被volatile修饰的变量有两大特点&#xff1a; 可见性有序性 关于volatile的可见性&#xff0c;也即volatile的内存语义…

Linux介绍

文章目录 前言一、概述 前言 Linux学习笔记。 一、概述 linux怎么读,不下10种 linux是一个开源、免费的操作系统&#xff0c;其稳定性、安全性、处理多并发已经得到业界的认可&#xff0c;目前很多企业级的项目(c/c/php/python/java/go)都会部署到Linux/unix系统上。 常见的…

联软 IT 安全运维管理软件反序列化漏洞复现

0x01 产品简介 联软科技持续十多年研发的联软IT安全运维管理软件&#xff0c;集网络准入控制、终端安全管理、BYOD设备管理、杀毒管理、服务器安全管理、数据防泄密、反APT攻击等系统于一体&#xff0c;通过一个平台&#xff0c;统一框架&#xff0c;数据集中&#xff0c;实现更…

Android中在google Map 上绘制历史路径

很多的App都会有这种需求&#xff0c;需要把自己的轨迹绘制在地图上来加标一段行踪&#xff0c;使得自己的行程展现出来&#xff0c;通过地图的展示&#xff0c;自己的行程也就一目了然了。 这里利用Google Map 把自己的行程展现出来&#xff0c;注意这里用到了上一章的基础&a…

C语言——写一个简单函数,找两个数中最大者

#include <stdio.h>int max( int a, int b ) { return a>b ? a:b; }int main() { int a, b;printf("输入两个数:\n");scanf("%d %d", &a, &b);printf("max %d\n", max(a, b));return 0; }输出结果&#xff1a;

csdn最新最全面的Jmeter接口测试:jmeter_逻辑控制器_循环控制器

循环控制器 循环次数&#xff1a;设置该控制器下的请求的循环执行次数 永远&#xff1a;勾选上的话&#xff0c;会一直循环&#xff0c;即所谓死循环 注意&#xff1a;如果线程组本身已经设置了循环次数的话&#xff0c;那循环控制元件控制的子节点 的循环次数为线程组设置的…

Rust的Vec优化

本篇是对Rust编程语言17_Rust的Vec优化[1]学习与记录 MiniVec https://crates.io/crates/minivec enum DataWithVec { // tag,uint64,8字节 I32(i32), // 4字节,但需内存对齐到8字节? F64(f64), // 8字节 Bytes(Vec<u8>), // 24字节}fn main()…

浅聊代理(应用部署)

以前很少接触过项目的上线部署&#xff0c; 我对前后端交互的认知还停留在前端一个请求 对应后端一个API 比如后端提供: /api/backend/categories -GET 前端则通过使用ajax或者axios组件去构建http请求&#xff0c; 发送到: https://host:port/api/backend/categories -GET 一、…

安全高效的PostgreSQL数据库迁移解决方案

PostgreSQL数据库是一款高度可扩展的开源数据库系统&#xff0c;支持复杂的查询、事务完整性和多种数据类型&#xff0c;这使得它成为企业中处理大规模和多样化数据需求的理想选择。在很多企业中&#xff0c;PostgreSQL不仅处理大量的交易数据&#xff0c;还支持复杂的数据分析…

Django二转Day03 04

0 cbv执行流程&#xff0c;self问题 path(index/, Myview.as_view()),Myview.as_view() 实例化后返回 变成return Myview.dispatch(request, *args, **kwargs)但是视图函数Myview中没有 dispatch 方法 所以去 父类View中寻找return View.dispatch(request, *args, **kwargs)调用…