十、接口(3)

news2024/9/25 1:15:41

本章概要

  • 接口适配
  • 接口字段
    • 初始化接口中的字段
  • 接口嵌套
  • 接口和工厂方法模式

接口适配

接口最吸引人的原因之一是相同的接口可以有多个实现。在简单情况下体现在一个方法接受接口作为参数,该接口的实现和传递对象则取决于方法的使用者。

因此,接口的一种常见用法是前面提到的_策略_设计模式。编写一个方法执行某些操作并接受一个指定的接口作为参数。可以说:“只要对象遵循接口,就可以调用方法” ,这使得方法更加灵活,通用,并更具可复用性。

例如,类 Scanner 的构造器接受的是一个 Readable 接口(在“字符串”一章中学习更多相关内容)。你会发现 Readable 没有用作 Java 标准库中其他任何方法的参数——它是单独为 Scanner 创建的,因此 Scanner 没有将其参数限制为某个特定类。通过这种方式,Scanner 可以与更多的类型协作。如果你创建了一个新类并想让 Scanner 作用于它,就让它实现 Readable 接口,像这样:

// interfaces/RandomStrings.java
// Implementing an interface to conform to a method
import java.nio.*;
import java.util.*;

public class RandomStrings implements Readable {
    private static Random rand = new Random(47);
    private static final char[] CAPITALS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    private static final char[] LOWERS = "abcdefghijklmnopqrstuvwxyz".toCharArray();
    private static final char[] VOWELS = "aeiou".toCharArray();
    private int count;
    
    public RandomStrings(int count) {
        this.count = count;
    }
    
    @Override
    public int read(CharBuffer cb) {
        if (count-- == 0) {
            return -1; // indicates end of input
        }
        cb.append(CAPITALS[rand.nextInt(CAPITALS.length)]);
        for (int i = 0; i < 4; i++) {
            cb.append(VOWELS[rand.nextInt(VOWELS.length)]);
            cb.append(LOWERS[rand.nextInt(LOWERS.length)]);
        }
        cb.append(" ");
        return 10; // Number of characters appended
    }
    
    public static void main(String[] args) {
        Scanner s = new Scanner(new RandomStrings(10));
        while (s.hasNext()) {
            System.out.println(s.next());
        }
    }
}

输出:

在这里插入图片描述

Readable 接口只需要实现 read() 方法(注意 @Override 注解的突出方法)。在 read() 方法里,将输入内容添加到 CharBuffer 参数中(有多种方法可以实现,查看 CharBuffer 文档),或在没有输入时返回 -1

假设你有一个类没有实现 Readable 接口,怎样才能让 Scanner 作用于它呢?下面是一个产生随机浮点数的例子:

// interfaces/RandomDoubles.java
import java.util.*;

public interface RandomDoubles {
    Random RAND = new Random(47);
    
    default double next() {
        return RAND.nextDouble();
    }
    
    static void main(String[] args) {
        RandomDoubles rd = new RandomDoubles(){};
        for (int i = 0; i < 7; i++) {
            System.out.println(rd.next() + " ");
        }
    }
}

输出:

0.7271157860730044 
0.5309454508634242 
0.16020656493302599 
0.18847866977771732 
0.5166020801268457 
0.2678662084200585 
0.2613610344283964

我们可以再次使用适配器模式,但这里适配器类可以实现两个接口。因此,通过关键字 interface 提供的多继承,我们可以创建一个既是 RandomDoubles,又是 Readable 的类:

// interfaces/AdaptedRandomDoubles.java
// creating an adapter with inheritance
import java.nio.*;
import java.util.*;

public class AdaptedRandomDoubles implements RandomDoubles, Readable {
    private int count;
    
    public AdaptedRandomDoubles(int count) {
        this.count = count;
    }
    
    @Override
    public int read(CharBuffer cb) {
        if (count-- == 0) {
            return -1;
        }
        String result = Double.toString(next()) + " ";
        cb.append(result);
        return result.length();
    }
    
    public static void main(String[] args) {
        Scanner s = new Scanner(new AdaptedRandomDoubles(7));
        while (s.hasNextDouble()) {
            System.out.print(s.nextDouble() + " ");
        }
    }
}

输出:

0.7271157860730044 0.5309454508634242 
0.16020656493302599 0.18847866977771732 
0.5166020801268457 0.2678662084200585 
0.2613610344283964

因为你可以以这种方式在已有类中增加新接口,所以这就意味着一个接受接口类型的方法提供了一种让任何类都可以与该方法进行适配的方式。这就是使用接口而不是类的强大之处。

接口字段

因为接口中的字段都自动是 staticfinal 的,所以接口就成为了创建一组常量的方便的工具。在 Java 5 之前,这是产生与 C 或 C++ 中的 enum (枚举类型) 具有相同效果的唯一方式。所以你可能在 Java 5 之前的代码中看到:

// interfaces/Months.java
// Using interfaces to create groups of constants
public interface Months {
    int 
    JANUARY = 1, FEBRUARY = 2, MARCH = 3,
    APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,
    AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
    NOVEMBER = 11, DECEMBER = 12;
}

注意 Java 中使用大写字母的风格定义具有初始化值的 static final 变量。接口中的字段自动是 public 的,所以没有显式指明这点。

自 Java 5 开始,我们有了更加强大和灵活的关键字 enum,那么在接口中定义常量组就显得没什么意义了。然而当你阅读遗留的代码时,在很多场合你还会碰到这种旧的习惯用法。在“枚举”一章中你会学习到更多关于枚举的内容。

初始化接口中的字段

接口中定义的字段不能是“空 final",但是可以用非常量表达式初始化。例如:

// interfaces/RandVals.java
// Initializing interface fields with
// non-constant initializers
import java.util.*;

public interface RandVals {
    Random RAND = new Random(47);
    int RANDOM_INT = RAND.nextInt(10);
    long RANDOM_LONG = RAND.nextLong() * 10;
    float RANDOM_FLOAT = RAND.nextLong() * 10;
    double RANDOM_DOUBLE = RAND.nextDouble() * 10;
}

因为字段是 static 的,所以它们在类第一次被加载时初始化,这发生在任何字段首次被访问时。下面是个简单的测试:

// interfaces/TestRandVals.java
public class TestRandVals {
    public static void main(String[] args) {
        System.out.println(RandVals.RANDOM_INT);
        System.out.println(RandVals.RANDOM_LONG);
        System.out.println(RandVals.RANDOM_FLOAT);
        System.out.println(RandVals.RANDOM_DOUBLE);
    }
}

输出:

8
-32032247016559954
-8.5939291E18
5.779976127815049

这些字段不是接口的一部分,它们的值被存储在接口的静态存储区域中。

接口嵌套

接口可以嵌套在类或其他接口中。下面揭示一些有趣的特性:

// interfaces/nesting/NestingInterfaces.java
// {java interfaces.nesting.NestingInterfaces}
package interfaces.nesting;

class A {
    interface B {
        void f();
    }
    
    public class BImp implements B {
        @Override
        public void f() {}
    }
    
    public class BImp2 implements B {
        @Override
        public void f() {}
    }
    
    public interface C {
        void f();
    }
    
    class CImp implements C {
        @Override
        public void f() {}
    }
    
    private class CImp2 implements C {
        @Override
        public void f() {}
    }
    
    private interface D {
        void f();
    }
    
    private class DImp implements D {
        @Override
        public void f() {}
    }
    
    public class DImp2 implements D {
        @Override
        public void f() {}
    }
    
    public D getD() {
        return new DImp2();
    }
    
    private D dRef;
    
    public void receiveD(D d) {
        dRef = d;
        dRef.f();
    }
}

interface E {
    interface G {
        void f();
    }
    // Redundant "public"
    public interface H {
        void f();
    }
    
    void g();
    // Cannot be private within an interface
    //- private interface I {}
}

public class NestingInterfaces {
    public class BImp implements A.B {
        @Override
        public void f() {}
    }
    
    class CImp implements A.C {
        @Override
        public void f() {}
    }
    // Cannot implements a private interface except
    // within that interface's defining class:
    //- class DImp implements A.D {
    //- public void f() {}
    //- }
    class EImp implements E {
        @Override
        public void g() {}
    }
    
    class EGImp implements E.G {
        @Override
        public void f() {}
    }
    
    class EImp2 implements E {
        @Override
        public void g() {}
        
        class EG implements E.G {
            @Override
            public void f() {}
        }
    }
    
    public static void main(String[] args) {
        A a = new A();
        // Can't access to A.D:
        //- A.D ad = a.getD();
        // Doesn't return anything but A.D:
        //- A.DImp2 di2 = a.getD();
        // cannot access a member of the interface:
        //- a.getD().f();
        // Only another A can do anything with getD():
        A a2 = new A();
        a2.receiveD(a.getD());
    }
}

在类中嵌套接口的语法是相当显而易见的。就像非嵌套接口一样,它们具有 public 或包访问权限的可见性。

作为一种新添加的方式,接口也可以是 private 的,例如 A.D(同样的语法同时适用于嵌套接口和嵌套类)。那么 private 嵌套接口有什么好处呢?你可能猜测它只是被用来实现一个 private 内部类,就像 DImp。然而 A.DImp2 展示了它可以被实现为 public 类,但是 A.DImp2 只能被自己使用,你无法说它实现了 private 接口 D,所以实现 private 接口是一种可以强制该接口中的方法定义不会添加任何类型信息(即不可以向上转型)的方式。

getD() 方法产生了一个与 private 接口有关的窘境。它是一个 public 方法却返回了对 private 接口的引用。能对这个返回值做些什么呢?main() 方法里进行了一些使用返回值的尝试但都失败了。返回值必须交给有权使用它的对象,本例中另一个 A 通过 receiveD() 方法接受了它。

接口 E 说明了接口之间也能嵌套。然而,作用于接口的规则——尤其是,接口中的元素必须是 public 的——在此都会被严格执行,所以嵌套在另一个接口中的接口自动就是 public 的,不能指明为 private

NestingInterfaces 展示了嵌套接口的不同实现方式。尤其是当实现某个接口时,并不需要实现嵌套在其内部的接口。同时,private 接口不能在定义它的类之外被实现。

添加这些特性的最初原因看起来像是出于对严格的语法一致性的考虑,但是我通常认为,一旦你了解了某种特性,就总能找到其用武之地。

接口和工厂方法模式

接口是多实现的途径,而生成符合某个接口的对象的典型方式是_工厂方法_设计模式。不同于直接调用构造器,只需调用工厂对象中的创建方法就能生成对象的实现——理论上,通过这种方式可以将接口与实现的代码完全分离,使得可以透明地将某个实现替换为另一个实现。这里是一个展示工厂方法结构的例子:

// interfaces/Factories.java
interface Service {
    void method1();
    void method2();
}

interface ServiceFactory {
    Service getService();
}

class Service1 implements Service {
    Service1() {} // Package access
    
    @Override
    public void method1() {
        System.out.println("Service1 method1");
    }
    
    @Override
    public void method2() {
        System.out.println("Service1 method2");
    }
}

class Service1Factory implements ServiceFactory {
    @Override
    public Service getService() {
        return new Service1();
    }
}

class Service2 implements Service {
    Service2() {} // Package access
    
    @Override
    public void method1() {
        System.out.println("Service2 method1");
    }
    
    @Override
    public void method2() {
        System.out.println("Service2 method2");
    }
}

class Service2Factory implements ServiceFactory {
    @Override
    public Service getService() {
        return new Service2();
    }
}

public class Factories {
    public static void serviceConsumer(ServiceFactory fact) {
        Service s = fact.getService();
        s.method1();
        s.method2();
    }
    
    public static void main(String[] args) {
        serviceConsumer(new Service1Factory());
        // Services are completely interchangeable:
        serviceConsumer(new Service2Factory());
    }
}

输出:

Service1 method1
Service1 method2
Service2 method1
Service2 method2

如果没有工厂方法,代码就必须在某处指定将要创建的 Service 的确切类型,从而调用恰当的构造器。

为什么要添加额外的间接层呢?一个常见的原因是创建框架。假设你正在创建一个游戏系统;例如,在相同的棋盘下国际象棋和西洋跳棋:

// interfaces/Games.java
// A Game framework using Factory Methods
interface Game {
    boolean move();
}

interface GameFactory {
    Game getGame();
}

class Checkers implements Game {
    private int moves = 0;
    private static final int MOVES = 3;
    
    @Override
    public boolean move() {
        System.out.println("Checkers move " + moves);
        return ++moves != MOVES;
    }
}

class CheckersFactory implements GameFactory {
    @Override
    public Game getGame() {
        return new Checkers();
    }
}

class Chess implements Game {
    private int moves = 0;
    private static final int MOVES = 4;
    
    @Override
    public boolean move() {
        System.out.println("Chess move " + moves);
        return ++moves != MOVES;
    }
}

class ChessFactory implements GameFactory {
    @Override
    public Game getGame() {
        return new Chess();
    }
}

public class Games {
    public static void playGame(GameFactory factory) {
        Game s = factory.getGame();
        while (s.move()) {
            ;
        }
    }
    
    public static void main(String[] args) {
        playGame(new CheckersFactory());
        playGame(new ChessFactory());
    }
}

输出:

Checkers move 0
Checkers move 1
Checkers move 2
Chess move 0
Chess move 1
Chess move 2
Chess move 3

如果类 Games 表示一段很复杂的代码,那么这种方式意味着你可以在不同类型的游戏里复用这段代码。你可以再想象一些能够从这个模式中受益的更加精巧的游戏。

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

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

相关文章

Mac发现有的软件不能上网的破解之法

1、Mac上打开终端 terminal &#xff0c;获取 root 权限。 sudo -i 2、编辑 hosts 文件 vim /private/etc/hosts 3、找到被禁止软件的数据请求域名&#xff0c;然后删除相关行&#xff0c;快捷件dd&#xff0c;然后:wq保存退出 比如百度 127.0.0.1 pan.baidu.com ##sec 印…

两个字符串的删除操作——力扣583

class Solution {public:int minDistance(string word1, string word2) {int m = word1.length(), n=word2

内网穿透实战应用-windows搭建WebDAV服务,并内网穿透公网访问【无公网IP】

windows搭建WebDAV服务&#xff0c;并内网穿透公网访问【无公网IP】 文章目录 windows搭建WebDAV服务&#xff0c;并内网穿透公网访问【无公网IP】1. 安装IIS必要WebDav组件2. 客户端测试3. cpolar内网穿透3.1 打开Web-UI管理界面3.2 创建隧道3.3 查看在线隧道列表3.4 浏览器访…

opencv-gpu版本编译(添加java支持,可选)实现硬解码

目录 opencv gpu版本编译&#xff0c;实现硬解码&#xff0c;加速rtsp视频流读取1、准备文件2、复制 NVCUVID 头文件到 cuda 安装目录 include3、安装相关依赖4、 执行cmake5、编译安装6、测试 opencv gpu版本编译&#xff0c;实现硬解码&#xff0c;加速rtsp视频流读取 前置条…

卷积神经网络——上篇【深度学习】【PyTorch】

文章目录 5、卷积神经网络5.1、卷积5.1.1、理论部分5.1.2、代码实现5.1.3、边缘检测 5.2、填充和步幅5.2.1、理论部分5.2.2、代码实现 5.3、多输入多输出通道5.3.1、理论部分5.3.2、代码实现 5.4、池化层 | 汇聚层5.4.1、理论部分5.4.2、代码实现 5、卷积神经网络 5.1、卷积 …

PSP - 基于开源框架 OpenFold 训练的 Finetuning 模型与推理逻辑评估

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132410296 AlphaFold2 以其能够以极高的准确度预测蛋白质结构的能力&#xff0c;彻底改变了结构生物学。然而&#xff0c;AlphaFold2 的实现&…

Linux面试笔试题(5)

79、下列工具中可以直接连接mysql的工具有【c 】。 A.xsellB.plsqlC.navicatD.以上都不是 80、Linux系统最少的挂载点有两个【B 】 A.一个是根挂载点 home&#xff0c;另一个是swap B.一个是根挂载点/&#xff0c;另一个是swap C.一个是根挂载点 boot&#xff0c;另一个是sw…

多维时序 | MATLAB实现SCNGO-CNN-Attention多变量时间序列预测

多维时序 | MATLAB实现SCNGO-CNN-Attention多变量时间序列预测 目录 多维时序 | MATLAB实现SCNGO-CNN-Attention多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.SCNGO-CNN-Attention超前24步多变量回归预测算法。 程序平台&#xff1a;无Attention适…

vue 弹出框 引入另一个vue页面

为什么要这么做,适用于在一个页面逻辑比较多的时候,可以搞多个页面,防止出错 index页面点击解约按钮,弹出框 进入jieyue.vue 核心代码 <el-buttonsize"mini"type"text"icon"el-icon-edit"v-if"scope.row.delFlag 0"click"j…

openpnp - 日常使用的零碎记录

文章目录 openpnp - 日常使用的零碎记录概述抓偏贴偏的问题END openpnp - 日常使用的零碎记录 概述 设备标定已经妥妥的了(随时有需求从头要设备标定, 都是一次通过:) ), 现在主要是使用openpnp正常干活. 使用过程中, 发现了一些问题, 尝试解决并记录. 抓偏贴偏的问题 软件…

探索人工智能 | 模型训练 使用算法和数据对机器学习模型进行参数调整和优化

前言 模型训练是指使用算法和数据对机器学习模型进行参数调整和优化的过程。模型训练一般包含以下步骤&#xff1a;数据收集、数据预处理、模型选择、模型训练、模型评估、超参数调优、模型部署、持续优化。 文章目录 前言数据收集数据预处理模型选择模型训练模型评估超参数调…

基于MATLAB开发AUTOSAR软件应用层Code mapping专题-part 3 Paramter标签页介绍

这页是参数设置的界面,那首先要知道什么是参数,参数就是算法中的系数这些可以更改的变量,接下来就是要学习如何创建参数,如下图: 打开模型资源管理器 选择model Workspace标签,点击上边工具栏里的创建参数的按钮(红色箭头指向的按钮),添加一个新的参数K,值设置为4,数…

摄影预约小程序制作的技术要点与难点解析

随着移动互联网的发展&#xff0c;小程序成为了很多企业和个人推广自己的产品和服务的有效工具。对于摄影师来说&#xff0c;一个功能完善、用户友好的摄影预约小程序可以方便客户预约拍摄时间&#xff0c;提升工作效率。那么&#xff0c;如何制作开发摄影预约小程序呢&#xf…

字幕翻译难吗,如何做好影视字幕翻译?

你是否曾经遇到过观看外国影视作品时&#xff0c;因为字幕翻译不准确而影响观影体验的情况&#xff1f; 专业的字幕翻译员不仅需要具备丰富的知识储备和语言组织能力&#xff0c;还要了解国内外文化风俗的差异。那么&#xff0c;如何才能做好影视字幕翻译呢&#xff1f;北京哪家…

CouchDB Erlang 分布式协议代码执行

漏洞描述 在CouchDB 3.2.1及以前版本中,使用了默认Cookie,值为“monster”,由于Erlang的特性,其支持分布式计算,分布式节点之间通过Erlang/OTP Distribution协议进行通信。攻击者如果知道通信时使用的Cookie,即可在握手包通过认证并执行任意命令。 免责声明 技术文章…

正中优配:降息利好什么股票?降息利好什么板块?

降息通常是指央行下降银行的存款、贷款利率&#xff0c;它是一种宽松的货币政策&#xff0c;会导致资金从银行流出&#xff0c;存款变为出资或消费&#xff0c;结果是资金流动性添加&#xff0c;给股市带来更多的资金&#xff0c;整体上会影响股市的上涨&#xff0c;那么&#…

2023-08-21 Unity Shader 开发入门1 —— 渲染管线

文章目录 一、概述二、应用阶段三、几何阶段四、光栅化阶段 一、概述 ​ Unity 中的渲染管线和图形学中的渲染管线基本上指的是相同的概念&#xff0c;但是具体实现和细节方面可能存在一些差异。 ​ Unity 的渲染管线建立在图形学的基础上&#xff0c;但具有自己的实现和拓展。…

windows安装使用RocketMQ常见问题,及springboot整合

win安装rocketmq 官网下载二进制包&#xff1a;https://rocketmq.apache.org/download 解压到不包含中文及空格的目录&#xff0c;配置环境变量 ROCKETMQ_HOME4. 修改runbroker.cmd和runserver.cmd文件 文件地址在rocketmq安装目录下的bin文件夹中。 如果不修改可能会遇见以…

CSS伪类:where和:is

CSS伪类:where和:is 1 :where1.1 概述1.2 组合与叠加1.3 优先级1.4 安全性1.5 兼容性 2 :is兼容性 1 :where 1.1 概述 :where()接受选择器列表作为它的参数&#xff0c;将会选择所有能被该选择器列表中任何一条规则选中的元素。 例如&#xff0c;在以下代码中&#xff0c;a标…

linux安装 jdk

1.下载 jdk 网盘资源&#xff1a; 链接: https://pan.baidu.com/s/1Z-fyHGDyj9b_km6ymR6mZg?pwdwd42 提取码: wd42 2.上传至服务器并解压 这里是上传至/opt 文件夹&#xff0c;并创建了/opt/soft 文件夹&#xff0c;解压在此 cd /opt tar -zxvf ./jdk-8u321-linux-x64.t…