当前位置: 首页 > news >正文

十、接口(1)

本章概要

  • 抽象类和方法
  • 接口创建
    • 默认方法
    • 多继承
    • 接口中的静态方法
    • Instrument 作为接口

接口和抽象类提供了一种将接口与实现分离的更加结构化的方法。

这种机制在编程语言中不常见,例如 C++ 只对这种概念有间接的支持。而在 Java 中存在这些关键字,说明这些思想很重要,Java 为它们提供了直接支持。

首先,我们将学习抽象类,一种介于普通类和接口之间的折中手段。尽管你的第一想法是创建接口,但是对于构建具有属性和未实现方法的类来说,抽象类也是重要且必要的工具。你不可能总是使用纯粹的接口。

抽象类和方法

在上一章的乐器例子中,基类 Instrument 中的方法往往是“哑”方法。如果调用了这些方法,就会出现一些错误。这是因为接口的目的是为它的派生类创建一个通用接口。

在那些例子中,创建这个通用接口的唯一理由是,不同的子类可以用不同的方式表示此接口。通用接口建立了一个基本形式,以此表达所有派生类的共同部分。另一种说法把 Instrument 称为抽象基类,或简称抽象类。

对于像 Instrument 那样的抽象类来说,它的对象几乎总是没有意义的。创建一个抽象类是为了通过通用接口操纵一系列类。因此,Instrument 只是表示接口,不是具体实现,所以创建一个 Instrument 的对象毫无意义,我们可能希望阻止用户这么做。通过让 Instrument 所有的方法产生错误,就可以达到这个目的,但是这么做会延迟到运行时才能得知错误信息,并且需要用户进行可靠、详尽的测试。最好能在编译时捕捉问题。

Java 提供了一个叫做_抽象方法_的机制,这个方法是不完整的:它只有声明没有方法体。下面是抽象方法的声明语法:

abstract void f();

包含抽象方法的类叫做_抽象类_。如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的,否则,编译器会报错。

// interface/Basic.java
abstract class Basic {abstract void unimplemented();
}

如果一个抽象类是不完整的,当试图创建这个类的对象时,Java 会怎么做呢?它不会创建抽象类的对象,所以我们只会得到编译器的错误信息。这样保证了抽象类的纯粹性,我们不用担心误用它。

// interfaces/AttemptToUseBasic.java
// {WillNotCompile}
public class AttemptToUseBasic {Basic b = new Basic();// error: Basic is abstract; cannot be instantiated
}

如果创建一个继承抽象类的新类并为之创建对象,那么就必须为基类的所有抽象方法提供方法定义。如果不这么做(可以选择不做),新类仍然是一个抽象类,编译器会强制我们为新类加上 abstract 关键字。

// interfaces/Basic2.java
abstract class Basic2 extends Basic {int f() {return 111;}abstract void g() {// unimplemented() still not implemented}
}

可以将一个不包含任何抽象方法的类指明为 abstract,在类中的抽象方法没啥意义但想阻止创建类的对象时,这么做就很有用。

// interfaces/AbstractWithoutAbstracts.java
abstract class Basic3 {int f() {return 111;}// No abstract methods
}public class AbstractWithoutAbstracts {// Basic3 b3 = new Basic3();// error: Basic3 is abstract; cannot be instantiated
}

为了创建可初始化的类,就要继承抽象类,并提供所有抽象方法的定义:

// interfaces/Instantiable.java
abstract class Uninstantiable {abstract void f();abstract int g();
}public class Instantiable extends Uninstantiable {@Overridevoid f() {System.out.println("f()");}@Overrideint g() {return 22;}public static void main(String[] args) {Uninstantiable ui = new Instantiable();}
}

留意 @Override 的使用。没有这个注解的话,如果你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。因此,你可能认为这里的 @Override 是多余的。但是,@Override 还提示了这个方法被覆写——我认为这是有用的,所以我会使用 @Override,不仅仅是因为当没有这个注解时,编译器会告诉我出错。

记住,事实上的访问权限是“friendly”。你很快会看到接口自动将其方法指明为 public。事实上,接口只允许 public 方法,如果不加访问修饰符的话,接口的方法不是 friendly 而是 public。然而,抽象类允许每件事:

// interfaces/AbstractAccess.java
abstract class AbstractAccess {private void m1() {}// private abstract void m1a(); // illegalprotected void m2() {}protected abstract void m2a();void m3() {}abstract void m3a();public void m4() {}public abstract void m4a();
}

private abstract 被禁止了是有意义的,因为你不可能在 AbstractAccess 的任何子类中合法地定义它。

上一章的 Instrument 类可以很轻易地转换为一个抽象类。只需要部分方法是 abstract 即可。将一个类指明为 abstract 并不强制类中的所有方法必须都是抽象方法。如下图所示:

在这里插入图片描述

下面是修改成使用抽象类和抽象方法的管弦乐器的例子:

// interfaces/music4/Music4.java
// Abstract classes and methods
// {java interfaces.music4.Music4}
package interfaces.music4;
import polymorphism.music.Note;abstract class Instrument {private int i; // Storage allocated for eachpublic abstract void play(Note n);public String what() {return "Instrument";}public abstract void adjust();
}class Wind extends Instrument {@Overridepublic void play(Note n) {System.out.println("Wind.play() " + n);}@Overridepublic String what() {return "Wind";}@Overridepublic void adjust() {System.out.println("Adjusting Wind");}
}class Percussion extends Instrument {@Overridepublic void play(Note n) {System.out.println("Percussion.play() " + n);}@Overridepublic String what() {return "Percussion";}@Overridepublic void adjust() {System.out.println("Adjusting Percussion");}
}class Stringed extends Instrument {@Overridepublic void play(Note n) {System.out.println("Stringed.play() " + n);}@Overridepublic String what() {return "Stringed";}@Overridepublic void adjust() {System.out.println("Adjusting Stringed");}
}class Brass extends Wind {@Overridepublic void play(Note n) {System.out.println("Brass.play() " + n);}@Overridepublic void adjust() {System.out.println("Adjusting Brass");}
}class Woodwind extends Wind {@Overridepublic void play(Note n) {System.out.println("Woodwind.play() " + n);}@Overridepublic String what() {return "Woodwind";}
}public class Music4 {// Doesn't care about type, so new types// added to system still work right:static void tune(Instrument i) {// ...i.play(Note.MIDDLE_C);}static void tuneAll(Instrument[] e) {for (Instrument i: e) {tune(i);}}public static void main(String[] args) {// Upcasting during addition to the array:Instrument[] orchestra = {new Wind(),new Percussion(),new Stringed(),new Brass(),new Woodwind()};tuneAll(orchestra);}
}

输出:

Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C

除了 Instrument,基本没区别。

创建抽象类和抽象方法是有帮助的,因为它们使得类的抽象性很明确,并能告知用户和编译器使用意图。抽象类同时也是一种有用的重构工具,使用它们使得我们很容易地将沿着继承层级结构上移公共方法。

接口创建

使用 interface 关键字创建接口。在本书中,interface 和 class 一样随处常见,除非特指关键字 interface,其他情况下都采用正常字体书写 interface。

描述 Java 8 之前的接口更加容易,因为它们只允许抽象方法。像下面这样:

// interfaces/PureInterface.java
// Interface only looked like this before Java 8
public interface PureInterface {int m1(); void m2();double m3();
}

我们甚至不用为方法加上 abstract 关键字,因为方法在接口中。Java 知道这些方法不能有方法体(仍然可以为方法加上 abstract 关键字,但是看起来像是不明白接口,徒增难堪罢了)。

因此,在 Java 8之前我们可以这么说:interface 关键字产生一个完全抽象的类,没有提供任何实现。我们只能描述类应该像什么,做什么,但不能描述怎么做,即只能决定方法名、参数列表和返回类型,但是无法确定方法体。接口只提供形式,通常来说没有实现,尽管在某些受限制的情况下可以有实现。

一个接口表示:所有实现了该接口的类看起来都像这样。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。所以,接口被用来建立类之间的协议。(一些面向对象编程语言中,使用 protocol 关键字完成相同的功能。)

Java 8 中接口稍微有些变化,因为 Java 8 允许接口包含默认方法和静态方法——基于某些重要原因,看到后面你会理解。接口的基本概念仍然没变,介于类型之上、实现之下。接口与抽象类最明显的区别可能就是使用上的惯用方式。接口的典型使用是代表一个类的类型或一个形容词,如 Runnable 或 Serializable,而抽象类通常是类层次结构的一部分或一件事物的类型,如 String 或 ActionHero。

使用关键字 interface 而不是 class 来创建接口。和类一样,需要在关键字 interface 前加上 public 关键字(但只是在接口名与文件名相同的情况下),否则接口只有包访问权限,只能在接口相同的包下才能使用它。

接口同样可以包含属性,这些属性被隐式指明为 staticfinal

使用 implements 关键字使一个类遵循某个特定接口(或一组接口),它表示:接口只是外形,现在我要说明它是如何工作的。除此之外,它看起来像继承。

// interfaces/ImplementingAnInterface.java
interface Concept { // Package accessvoid idea1();void idea2();
}class Implementation implements Concept {@Overridepublic void idea1() {System.out.println("idea1");}@Overridepublic void idea2() {System.out.println("idea2");}
}

你可以选择显式地声明接口中的方法为 public,但是即使你不这么做,它们也是 public 的。所以当实现一个接口时,来自接口中的方法必须被定义为 public。否则,它们只有包访问权限,这样在继承时,它们的可访问权限就被降低了,这是 Java 编译器所不允许的。

默认方法

Java 8 为关键字 default 增加了一个新的用途(之前只用于 switch 语句和注解中)。当在接口中使用它时,任何实现接口却没有定义方法的时候可以使用 default 创建的方法体。默认方法比抽象类中的方法受到更多的限制,但是非常有用,我们将在“流式编程”一章中看到。现在让我们看下如何使用:

// interfaces/AnInterface.java
interface AnInterface {void firstMethod();void secondMethod();
}

我们可以像这样实现接口:

// interfaces/AnImplementation.java
public class AnImplementation implements AnInterface {public void firstMethod() {System.out.println("firstMethod");}public void secondMethod() {System.out.println("secondMethod");}public static void main(String[] args) {AnInterface i = new AnImplementation();i.firstMethod();i.secondMethod();}
}

输出:

firstMethod
secondMethod

如果我们在 AnInterface 中增加一个新方法 newMethod(),而在 AnImplementation 中没有实现它,编译器就会报错:

AnImplementation.java:3:error: AnImplementation is not abstract and does not override abstract method newMethod() in AnInterface
public class AnImplementation implements AnInterface {
^
1 error

如果我们使用关键字 defaultnewMethod() 方法提供默认的实现,那么所有与接口有关的代码能正常工作,不受影响,而且这些代码还可以调用新的方法 newMethod()

// interfaces/InterfaceWithDefault.java
interface InterfaceWithDefault {void firstMethod();void secondMethod();default void newMethod() {System.out.println("newMethod");}
}

关键字 default 允许在接口中提供方法实现——在 Java 8 之前被禁止。

// interfaces/Implementation2.java
public class Implementation2 implements InterfaceWithDefault {@Overridepublic void firstMethod() {System.out.println("firstMethod");}@Overridepublic void secondMethod() {System.out.println("secondMethod");}public static void main(String[] args) {InterfaceWithDefault i = new Implementation2();i.firstMethod();i.secondMethod();i.newMethod();}
}

输出:

firstMethod
secondMethod
newMethod

尽管 Implementation2 中未定义 newMethod(),但是可以使用 newMethod() 了。

增加默认方法的极具说服力的理由是它允许在不破坏已使用接口的代码的情况下,在接口中增加新的方法。默认方法有时也被称为_守卫方法_或_虚拟扩展方法_。

多继承

多继承意味着一个类可能从多个父类型中继承特征和特性。

Java 在设计之初,C++ 的多继承机制饱受诟病。Java 过去是一种严格要求单继承的语言:只能继承自一个类(或抽象类),但可以实现任意多个接口。在 Java 8 之前,接口没有包袱——它只是方法外貌的描述。

多年后的现在,Java 通过默认方法具有了某种多继承的特性。结合带有默认方法的接口意味着结合了多个基类中的行为。因为接口中仍然不允许存在属性(只有静态属性,不适用),所以属性仍然只会来自单个基类或抽象类,也就是说,不会存在状态的多继承。正如下面这样:

// interfaces/MultipleInheritance.java
import java.util.*;interface One {default void first() {System.out.println("first");}
}interface Two {default void second() {System.out.println("second");}
}interface Three {default void third() {System.out.println("third");}
}class MI implements One, Two, Three {}public class MultipleInheritance {public static void main(String[] args) {MI mi = new MI();mi.first();mi.second();mi.third();}
}

输出:

first
second
third

现在我们做些在 Java 8 之前不可能完成的事:结合多个源的实现。只要基类方法中的方法名和参数列表不同,就能工作得很好,否则会得到编译器错误:

// interface/MICollision.java
import java.util.*;interface Bob1 {default void bob() {System.out.println("Bob1::bob");}
}interface Bob2 {default void bob() {System.out.println("Bob2::bob");}
}// class Bob implements Bob1, Bob2 {}
/* Produces:
error: class Bob inherits unrelated defaults
for bob() from types Bob1 and Bob2
class Bob implements Bob1, Bob2 {}
^
1 error
*/interface Sam1 {default void sam() {System.out.println("Sam1::sam");}
}interface Sam2 {default void sam(int i) {System.out.println(i * 2);}
}// This works because the argument lists are distinct:
class Sam implements Sam1, Sam2 {}interface Max1 {default void max() {System.out.println("Max1::max");}
}interface Max2 {default int max() {return 47;}
}// class Max implements Max1, Max2 {}
/* Produces:
error: types Max2 and Max1 are imcompatible;
both define max(), but with unrelated return types
class Max implements Max1, Max2 {}
^
1 error
*/

Sam 类中的两个 sam() 方法有相同的方法名但是签名不同——方法签名包括方法名和参数类型,编译器也是用它来区分方法。但是从 Max 类可看出,返回类型不是方法签名的一部分,因此不能用来区分方法。为了解决这个问题,需要覆写冲突的方法:

// interfaces/Jim.java
import java.util.*;interface Jim1 {default void jim() {System.out.println("Jim1::jim");}
}interface Jim2 {default void jim() {System.out.println("Jim2::jim");}
}public class Jim implements Jim1, Jim2 {@Overridepublic void jim() {Jim2.super.jim();}public static void main(String[] args) {new Jim().jim();}
}

输出:

Jim2::jim

当然,你可以重定义 jim() 方法,但是也能像上例中那样使用 super 关键字选择基类实现中的一种。

接口中的静态方法

Java 8 允许在接口中添加静态方法。这么做能恰当地把工具功能置于接口中,从而操作接口,或者成为通用的工具:

// onjava/Operations.java
package onjava;
import java.util.*;public interface Operations {void execute();static void runOps(Operations... ops) {for (Operations op: ops) {op.execute();}}static void show(String msg) {System.out.println(msg);}
}

这是_模板方法_设计模式的一个版本(在“设计模式”一章中详细描述),runOps() 是一个模板方法。runOps() 使用可变参数列表,因而我们可以传入任意多的 Operation 参数并按顺序运行它们:

// interface/Machine.java
import java.util.*;
import onjava.Operations;class Bing implements Operations {@Overridepublic void execute() {Operations.show("Bing");}
}class Crack implements Operations {@Overridepublic void execute() {Operations.show("Crack");}
}class Twist implements Operations {@Overridepublic void execute() {Operations.show("Twist");}
}public class Machine {public static void main(String[] args) {Operations.runOps(new Bing(), new Crack(), new Twist());}
}

输出:

Bing
Crack
Twist

这里展示了创建 Operations 的不同方式:一个外部类(Bing),一个匿名类,一个方法引用和 lambda 表达式——毫无疑问用在这里是最好的解决方法。

这个特性是一项改善,因为它允许把静态方法放在更合适的地方。

Instrument 作为接口

回顾下乐器的例子,使用接口的话:

在这里插入图片描述

WoodwindBrass 说明一旦实现了某个接口,那么其实现就变成一个普通类,可以按常规方式扩展它。

接口的工作方式使得我们不需要显式声明其中的方法为 public,它们自动就是 public 的。play()adjust() 使用 default 关键字定义实现。在 Java 8 之前,这些定义要在每个实现中重复实现,显得多余且令人烦恼:

enum Note {MIDDLE_C, C_SHARP, B_FLAT; // Etc.
}interface Instrument {// Compile-time constant:int VALUE = 5; // static & finaldefault void play(Note n) {  // Automatically publicSystem.out.println(this + ".play() " + n);}default void adjust() {System.out.println("Adjusting " + this);}
}class Wind implements Instrument {@Overridepublic String toString() {return "Wind";}
}class Percussion implements Instrument {@Overridepublic String toString() {return "Percussion";}
}class Stringed implements Instrument {@Overridepublic String toString() {return "Stringed";}
}class Brass extends Wind {@Overridepublic String toString() {return "Brass";}
}class Woodwind extends Wind {@Overridepublic String toString() {return "Woodwind";}
}public class Music5 {// Doesn't care about type, so new types// added to the system still work right:static void tune(Instrument i) {// ...i.play(Note.MIDDLE_C);}static void tuneAll(Instrument[] e) {for (Instrument i : e) {tune(i);}}public static void main(String[] args) {// Upcasting during addition to the array:Instrument[] orchestra = {new Wind(),new Percussion(),new Stringed(),new Brass(),new Woodwind()};tuneAll(orchestra);}
}

输出:

Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C

这个版本的例子的另一个变化是:what() 被修改为 toString() 方法,因为 toString() 实现的正是 what() 方法要实现的逻辑。因为 toString() 是根基类 Object 的方法,所以它不需要出现在接口中。

注意到,无论是将其向上转型为称作 Instrument 的普通类,或称作 Instrument 的抽象类,还是叫作 Instrument 的接口,其行为都是相同的。事实上,从 tune() 方法上看不出来 Instrument 到底是一个普通类、抽象类,还是一个接口。

相关文章:

十、接口(1)

本章概要 抽象类和方法接口创建 默认方法多继承接口中的静态方法Instrument 作为接口 接口和抽象类提供了一种将接口与实现分离的更加结构化的方法。 这种机制在编程语言中不常见,例如 C 只对这种概念有间接的支持。而在 Java 中存在这些关键字,说明…...

percentile_approx 聚合函数

返回组内 expr 的百分位近似值。 此函数是 approx_percentile 聚合函数的同义词。 语法 percentile_approx ( [ALL | DISTINCT ] expr, percentile [, accuracy] ) [FILTER ( WHERE cond ) ] 还可以使用 OVER 子句将此函数作为窗口函数调用。 参数 expr:数值表达…...

面试热题(全排列)

给定一个不含重复数字的整数数组 nums ,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。 输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 先在这里说明一下排列和组合的区别? 组合:是指从一…...

一文走进时序数据库性能测试工具 TSBS

一、背景 在物联网、车联网等时序数据场景中,数据的高速写入能力至关重要,会对产品方案的可用性、可靠性和扩展性产生影响。 以物联网为例,当面临千万甚至上亿设备、平均每个设备采集几十个到几百个指标时,每秒生成的数据将达到…...

通俗讲解-动量梯度下降法原理与代码实例

本站原创文章,转载请说明来自《老饼讲解-BP神经网络》bp.bbbdata.com 目录 一.动量梯度下降法介绍 1.1 动量梯度下降法简介与思想 1.2 动量梯度下降法的算法流程 二.动量梯度下降法代码实例 2.1 动量梯度下降法实例代码 一.动量梯度下降法介绍…...

【【STM32-USART串口协议】】

STM32-USART串口协议 USART串口协议 •通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统 •通信协议:制定通信的规则,通信双方按照协议规则进行数据收发 就是我们并不能在芯片上设计完全部的一下子完成所有的设计&…...

vue3.0组件通信

1、props 没有加TS限制类型的时候 1. 数组写法 defineProps([count, changCount]) 2. 对象写法 defineProps({count: Number,changCount: Function }) 3. 配置对象 defineProps({count: {type: Number,default: 2},changCount: {type: Function,required: true} })注意: defi…...

费曼学习法

费曼学习法 费曼学习法(Feynman Technique)是一种学习和理解复杂概念的方法,以理查德费曼(Richard Feynman)这位著名的理论物理学家命名。该方法的核心思想是通过将学习内容简化并用自己的话解释给别人,来…...

Kubernetes介绍和部署,使用

1.k8s kubernetes来自希腊语舵手,google, 8是ubernete 1.管理docker容器 go写的(并发) 2.用于微服务 3.cncf云原生基金会 2.mater(管理节点)和nodes(微服务节点) 3.部署 1.minikube kind官网在线测试语句 2.kubeadm(官方)(安装比较方便 添加) 3.github下载二进制包 4.yum(老) …...

视频汇聚平台EasyCVR视频监控播放平台WebRTC流地址无法播放的问题解决方案

开源EasyDarwin视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,在视频监控播放上,TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放,可同时播放多…...

node.js 基础高并发案例

什么是高并发 高并发是指系统在同一时间段内需要处理大量的并发请求或同时进行大量的操作。在计算机领域中,高并发通常指的是在短时间内有大量的用户或客户端同时访问系统或进行操作,对系统的并发处理能力提出了较高的要求。 高并发的特点包括 大量的…...

OpenCV实例(八)车牌字符识别技术(二)字符识别

车牌字符识别技术(二)字符识别 1.字符识别原理及其发展阶段2.字符识别方法3.英文、数字识别4.车牌定位实例 1.字符识别原理及其发展阶段 匹配判别是字符识别的基本思想,与其他模式识别的应用非常类似。字符识别的基本原理就是对字符图像进行…...

svn文章五:问题排查与修复 - 出了问题怎么办?SVN故障排除与修复指南

文章五:问题排查与修复 - “出了问题怎么办?SVN故障排除与修复指南” 概述:在使用SVN时,难免会遇到一些问题和错误。在这篇文章中,我们将教您如何进行故障排查和修复,保护您的SVN仓库和数据安全。 1. 引言…...

国产开源ambari之DataSophon部署

介绍 DataSophon致力于快速实现部署、管理、监控以及自动化运维大数据云原生平台,帮助您快速构建起稳定、高效、可弹性伸缩的大数据云原生平台。 主要特性有: 快速部署,可快速完成300个节点的大数据集群部署兼容复杂环境,极少的依赖使其很容易适配各种复杂环境监控指标全面丰…...

面试之快速学习STL- vector

1. vector底层实现机制刨析: 简述:使用三个迭代器表示的:  这也就解释了,为什么 vector 容器在进行扩容后,与其相关的指针、引用以及迭代器可能会失效的原因。 insert 整体向后移 erase 整体向前移…...

LeetCode_03Java_1572. 矩阵对角线元素的和

给你一个正方形矩阵 mat,请你返回矩阵对角线元素的和。 请你返回在矩阵主对角线上的元素和副对角线上且不在主对角线上元素的和。 输入:mat [[1,2,3],[4,5,6],[7,8,9]] 输出:25 解释:对角线的和为:1 5 9 3 7 2…...

系统架构设计师---职责及与其他角色的关系区别

一. 系统架构设计师的职责如下: 系统架构设计师是系统或产品线的设计责任人,是一个负责理解和管理并最终确认和评估非功能性系统需求(比如软件的可维护性、性能、复用性、可靠性、有效性和可测试性等),给出 开发规范,搭建系统实现的核心构架,对整个软件架构、关键构件、…...

【Visual Studio Code】--- Win11 C盘爆满 修改 Code 插件数据和缓存的保存路径

Win11 C盘爆满 修改 Code 插件数据和缓存的保存路径 一、概述二、修改 Code 插件数据和缓存的保存路径 一、概述 一个好的文章能够帮助开发者完成更便捷、更快速的开发。书山有路勤为径,学海无涯苦作舟。我是秋知叶i、期望每一个阅读了我的文章的开发者都能够有所成…...

mapbox-gl中mvt、pbf 矢量切片 feature id bug

1.版本:mapbox-gl.js 2.13.0,pbf采用 postgis生成 2.调用矢量切片时,采用如下方法查询矢量切片要素,报错 map.on(mousemove, function(e) { var bbox = [ [e.point.x - 5, e.point.y - 5], [e.point…...

206、仿真-51单片机锂电池蓄电池电压电流加按键控制开关状态Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括: 需要完整的资料可以点击下面的名片加下我,找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&a…...

vue.js not detected解决方法

如果你在开发环境中遇到“Vue.js not detected”的错误,这通常意味着你的项目没有正确设置或者配置以识别Vue.js。下面是一些解决这个问题的步骤: 1. 确认Vue.js已正确安装 首先,确保你的项目中已经正确安装了Vue.js。你可以通过以下命令来…...

【系统架构设计师】绪论-系统架构概述

目录 绪论 系统架构概述 单选题 绪论 系统架构概述 单选题 1、软件方法学是以软件开发方法为研究对象的学科。其中,()是先对最高居次中的问题进行定义、设计、编程和测试,而将其中未解决的问题作为一个子任务放到下一层次中去…...

《视觉SLAM十四讲》自用笔记 第三讲:三维空间刚体运动

第三讲 三维空间刚体运动 3.0 目标 1.理解三维空间的刚体运动描述方式:旋转矩阵、变换矩阵、四元数和欧拉角。 2.掌握 Eigen 库的矩阵、几何模块使用方法。 3.1 旋转矩阵 3.1.1 点和向量,坐标系 三维空间中,刚体的运动可以用两个概念来…...

一.设计模式的基本概念

一.核心概念 对软件设计中重复出现问题的成熟解决方案,提供代码可重用性、可维护性和扩展性保障。核心原则包括: 1.1. 单一职责原则‌ ‌定义‌:一个类只承担一个职责,避免因职责过多导致的代码耦合。 1.2. 开闭原则‌ ‌定义‌&#xf…...

Three.js中AR实现详解并详细介绍基于图像标记模式AR生成的详细步骤

文档地址 Three.js中AR实现详解 以下是Three.js中实现AR功能的详细解析,涵盖技术原理、实现步骤、核心组件及优化策略: 🧩 一、技术基础 AR.js框架的核心作用 AR.js是Three.js实现AR的基石,提供以下核心能力: 多模…...

如何对Video视频进行SEO优化?

如何对Video视频进行SEO优化? 在现代互联网的海洋中,搜索引擎优化(SEO)无疑是每一个网站管理员和内容创作者必须掌握的技能。而今天,我要向大家介绍一个极为强大的工具——Schema.org。它不仅能提升你的网站在搜索引擎…...

【原创】基于视觉模型+FFmpeg+MoviePy实现短视频自动化二次编辑+多赛道

AI视频处理系统功能总览 🎯 系统概述 这是一个智能短视频自动化处理系统,专门用于视频搬运和二次创作。系统支持多赛道配置,可以根据不同的内容类型(如"外国人少系列"等)应用不同的处理策略。 &#x1f3d…...

natapp 内网穿透失败

连不上网络错误调试排查详解 - NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 如何将DNS服务器修改为114.114.114.114_百度知道 连不上/错误信息等问题解决汇总 - NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 nslookup auth.natapp.cnping auth.natapp.cn...

electron-vite串口通信

一、构建项目后,安装“串口通信库” npm install serialport二、设置 npm install --save-dev electron-rebuild ./node_modules/.bin/electron-rebuild 注意:如果执行报错以下问题 1、未配置python变量 2、没有Microsoft Visual Studio BuildTools 3…...

双面沉金线路板制作流程解析:高可靠性PCB的核心工艺

在高端电子制造领域,双面沉金(ENIG)线路板因其优异的焊接性能、抗氧化能力和信号完整性,已成为5G通信、医疗设备和汽车电子等领域的首选。本文将深入解析其制作流程的关键环节,帮助工程师更好地理解这一核心工艺。 一、…...