【设计模式】访问者模式
访问者模式
访问者模式被称为是最复杂的设计模式,比较难理解并且使用频率不高。
在 GoF 的《设计模式》⼀书中,访问者者模式(Visitor Design Pattern)是这么定义的:
Allows for one or more operation to be applied to a set of objects at runtime, decoupling the operations from the object structure.
允许⼀个或者多个操作应⽤到⼀组对象上,解耦操作和对象本⾝。
访问者模式是一种将数据结构和数据操作分离的设计模式,属于行为型模式。
访问者模式的基本思想是: 假设系统中有一个由许多对象构成的对象结构(元素),这些对象的类都提供一个accept()
方法用来接受访问者对象的访问,不同的访问者访问同一对象可以产生不同的数据结果(访问者其实就是一个拥有visit()方法的接口) 。accept()
方法可以接收不同的访问者对象,然后在其内部将自己(元素)转发到访问者对象的visit()
方法内。
访问者模式的核心是解耦数据结构和数据操作,使得对元素的操作具备良好的扩展性。可以通过扩展不同的访问者来实现对同一元素集的不同操作。
如果你的系统中只是对单个对象(类)进行操作或者对多个类进行一种操作,那么就没必要使用访问者模式了。运用访问者模式是为了方便后续扩展操作类型,在对对象集(多个类对象)扩展操作的时候可以不需要修改所有类的代码。
当系统中存在类型数目稳定(固定)的一类数据结构时,可以通过访问者模式方便地实现对该类型所有数据结构的不同操作。
访问者模式类图
主要包含四个角色 :
Visitor 抽象访问者:接口或者抽象类,它定义了对每一个可访问元素(Visitable Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变(不能改变的意思是说,如果元素类的个数经常改变,则说明不适合使用访问者模式)。
ConcreteVisitor:具体的访问者,实现对每一个元素的具体操作。
Element 抽象元素:元素接口,它定义了一个接受访问者(accept)的方法,其意义是指,每一个元素都要可以被访问者访问。
ConcreteElement:具体的元素类,它提供接受访问者的具体实现,而这个具体的实现,通常情况下是调用访问者提供的访问该元素类的方法。
代码示例
我们使用访问者模式模拟一个处理不同类型文件的场景
假设我们有三类文件,PDF,EXCEL,WORD,我们需要从三种文件中提取信息存到到自己的系统里(假设需要导入到自己的一个txt文件),然后还需要对三类文件都进行压缩等一系列功能
将不同文件类型定义为抽象元素(这是一个稳定的数据结构), 对文件的操作定义为访问者,代码如下 :
public abstract class ResourceFile {private String name;protected ResourceFile(String name) {this.name = name;}abstract void accept(Vistor vistor);}
public class PDFFile extends ResourceFile {protected PDFFile(String name) {super(name);}@Overridevoid accept(Vistor vistor) {vistor.visit(this);}
}public class ExcelFile extends ResourceFile{protected ExcelFile(String name) {super(name);}@Overridevoid accept(Vistor vistor) {vistor.visit(this);}
}public class WordFile extends ResourceFile{public WordFile(String name) {super(name);}@Overridevoid accept(Vistor vistor) {vistor.visit(this);}
}
public interface Vistor {void visit(PDFFile file);void visit(ExcelFile file);void visit(WordFile file);}
public class CompressionVistor implements Vistor{@Overridepublic void visit(PDFFile file) {System.out.println("压缩pdf文件");}@Overridepublic void visit(ExcelFile file) {System.out.println("压缩excel文件");}@Overridepublic void visit(WordFile file) {System.out.println("压缩word文件");}
}
public class ExtractVistor implements Vistor{@Overridepublic void visit(PDFFile file) {System.out.println("提取pdf文字内容");}@Overridepublic void visit(ExcelFile file) {System.out.println("提取excel文字内容");}@Overridepublic void visit(WordFile file) {System.out.println("提取word文字内容");}
}
public class Test {private static final List<ResourceFile> resourceFileList = new ArrayList<>();static {resourceFileList.add(new PDFFile("设计模式.pdf"));resourceFileList.add(new ExcelFile("Data.excel"));resourceFileList.add(new WordFile("笔记.doc"));}public static void main(String[] args) {for (ResourceFile resourceFile: resourceFileList) {resourceFile.accept(new CompressionVistor());}for (ResourceFile resourceFile: resourceFileList) {resourceFile.accept(new ExtractVistor());}}}
Double Dispatch
静态分派
静态分派(Static Dispatch)就是按照变量的静态类型(变量被声明时的类型)进行分派,从而确定方法的执行版本,静态分派在编译时就可以确定方法的版本,典型例子就是java的方法重载
java在静态分派的时候,我们可以根据多个判断依据(即参数个数和参数类型)判断使用哪个方法,所以java是静态多分派的语言
动态分派
动态分派,不是在编译期确定方法版本,而是在运行时才能确定
Single Dispatch,指的是我们仅仅需要根据对象运行时的类型来决定执行哪个对象的方法
Double Dispatch,指的是我们需要根据对象的运行时类型和参数的运行时类型来决定执行哪个对象的哪个方法 (二者区别主要在于是否可以根据方法参数运行时的类型来判断执行对象的哪个方法)
当前主流的⾯向对象编程语⾔(⽐如,Java、C++、C#)都只⽀持 Single Dispatch,不⽀持 Double Dispatch。
以Java为例,Java⽀持多态,代码可以在运⾏时获得对象的实际类型,然后根据实际类型决定调⽤哪个对象的方法。 Java 也⽀持方法重载,但 Java 设计的方法重载的语法规则是在编译时,根据传递进函数的参数的声明类型,来决定调⽤哪个重载方法。也就是说,具体执⾏哪个对象的哪个⽅法,只跟对象的运⾏时类型有关,跟参数的运⾏时类型⽆关。所以,Java 语⾔是 动态单分派的语言。
我们可以看下具体的例子 :
public class ParentClass {public void method() {System.out.println("ParentClass 执行method方法");}}public class SonClass extends ParentClass{@Overridepublic void method() {System.out.println("SonClass 执行method方法");}
}
public class SingleDispatch {public void method(ParentClass parentClass) {parentClass.method();}public void print(ParentClass parentClass) {System.out.println("打印parentClass");}public void print(SonClass sonClass) {System.out.println("打印sonClass");}
}
public class Test {public static void main(String[] args) {ParentClass s = new SonClass();SingleDispatch singleDispatch = new SingleDispatch();singleDispatch.method(s);//执⾏哪个对象的⽅法,由对象的实际类型决定(多态)singleDispatch.print(s);//执⾏对象的哪个⽅法,由参数对象的声明类型决定,这里声明的时ParentClass类型}}
动态双分派的语言不需要访问者模式
假设 Java 语⾔⽀持 动态双分派,那么下面的代码就可以编译通过,正常执行了。
public class ExtractExecutor {public void extract(PDFFile file) {System.out.println("提取pdf文字内容");}public void extract(WordFile file) {System.out.println("提取word文字内容");}public void extract(ExcelFile file) {System.out.println("提取excel文字内容");}}
public static void main(String[] args) {ExtractExecutor extractExecutor = new ExtractExecutor();for (ResourceFile resourceFile: resourceFileList) {//这里会编译报错: Cannot resolve method 'extract(ResourceFile)'extractExecutor.extract(resourceFile);}}
代码会在运⾏时,根据参数(resourceFile)的实际类型(PDFFile、ExcelFile、WordFile),来决定调用extract()
的三个重载方法中的哪⼀个,也就不需要访问者模式了。
访问者模式中的伪动态双分派
所谓的动态双分派就是在运行时根据对象和参数的运行时类型去判断调用哪个一个对象的哪个方法。访问者模式通过进行两次动态单分派来达到这个效果。
for (ResourceFile resourceFile: resourceFileList) {resourceFile.accept(new ExtractVistor());}
@Overridevoid accept(Vistor vistor) {vistor.visit(this);}
当调用accept()
方法的时候, 根据resourceFile的实际类型决定调用哪个文件的accept()
方法;
在执行accept()
方法的时候,根据vistor的示例类型来决定调用哪个Vistor的visist方法,此时的this的类型就是这个类的静态类型,这是在编译期就确定的,所以也可以确定是调用的哪个重载方法
通过工厂模式实现上述功能
上述的例子,如果对文件的操作也比较固定,也可以使用工厂模式来实现,定义⼀个包含 extract()
接⼝的Executor接⼝。PdfExtractExecutor、ExcelExtractExecutor、WordExtractExecutor 类实现 Executor接⼝,完成对各自⽂件的⽂本内容抽取。然后再提供一个ExtractExecutorFactory ⼯⼚类根据不同的⽂件类型,返回不同的 Executor。
public abstract class ResourceFile {private String name;protected ResourceFile(String name) {this.name = name;}abstract String getType();
}
public class PDFFile extends ResourceFile {protected PDFFile(String name) {super(name);}@OverrideString getType() {return "PDF";}}
public interface Executor {void extract(ResourceFile file);
}
//省略了WordExtractExecutor,ExcelExtractExecutor的代码
public class PDFExtractExecutor implements Executor{@Overridepublic void extract(ResourceFile file) {System.out.println("提取pdf的内容");}
}
public class ExtractExecutorFactory {private static Map<String, Executor> map = new HashMap<>();static {map.put("PDF", new PDFExtractExecutor());
// map.put("EXCEL", new ExeclExtractExecutor());
// map.put("WORD", new WordExtractExecutor());}public static Executor getExecutor(ResourceFile file) {return map.get(file.getType());}}
访问者模式在源码中的应用
Java 7 版本后,Files 类提供了 walkFileTree() 方法,该方法可以很容易的对目录下的所有文件进行遍历,需要 Path、FileVisitor 两个参数。其中,Path 是要遍历文件的路径,FileVisitor 则可以看成一个文件访问器。源码如下。
package java.nio.file;
public final class Files {...public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor)throws IOException{return walkFileTree(start,EnumSet.noneOf(FileVisitOption.class),Integer.MAX_VALUE,visitor);}...
}
FileVisitor 提供了递归遍历文件树的支持,这个接口的方法表示了遍历过程中的关键过程,允许在文件被访问、目录将被访问、目录已被访问、发生错误等过程中进行控制。换句话说,这个接口在文件被访问前、访问中和访问后,以及产生错误的时候都有相应的钩子程序进行处理。
FileVisitor 主要提供了 4 个方法,且返回结果的都是 FileVisitResult 对象值,用于决定当前操作完成后接下来该如何处理。FileVisitResult 是一个枚举类,代表返回之后的一些后续操作。
package java.nio.file;import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
public interface FileVisitor<T> {FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)throws IOException;FileVisitResult visitFile(T file, BasicFileAttributes attrs)throws IOException;FileVisitResult visitFileFailed(T file, IOException exc)throws IOException;FileVisitResult postVisitDirectory(T dir, IOException exc)throws IOException;
}package java.nio.file;public enum FileVisitResult {CONTINUE,TERMINATE,SKIP_SUBTREE,SKIP_SIBLINGS;
}
FileVisitResult 主要包含 4 个常见的操作。
- FileVisitResult.CONTINUE:这个访问结果表示当前的遍历过程将会继续。
- FileVisitResult.SKIP_SIBLINGS:这个访问结果表示当前的遍历过程将会继续,但是要忽略当前文件/目录的兄弟节点。
- FileVisitResult.SKIP_SUBTREE:这个访问结果表示当前的遍历过程将会继续,但是要忽略当前目录下的所有节点。
- FileVisitResult.TERMINATE:这个访问结果表示当前的遍历过程将会停止。
通过访问者去遍历文件树会比较方便,比如查找文件夹内符合某个条件的文件或者某一天内所创建的文件,这个类中都提供了相对应的方法。它的实现也非常简单,代码如下
public class SimpleFileVisitor<T> implements FileVisitor<T> {protected SimpleFileVisitor() {}@Overridepublic FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)throws IOException{Objects.requireNonNull(dir);Objects.requireNonNull(attrs);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFile(T file, BasicFileAttributes attrs)throws IOException{Objects.requireNonNull(file);Objects.requireNonNull(attrs);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFileFailed(T file, IOException exc)throws IOException{Objects.requireNonNull(file);throw exc;}@Overridepublic FileVisitResult postVisitDirectory(T dir, IOException exc)throws IOException{Objects.requireNonNull(dir);if (exc != null)throw exc;return FileVisitResult.CONTINUE;}
}
一开始觉得这里的设计比较多余,后来仔细想了下,在不同场景下我们对文件树的遍历要求是不一样的,通过访问者模式,用户可以方便的定义自己的遍历操作
比方说在JavacPathFileManager里就重写了preVisitDirectory
和visitFile
方法
Files.walkFileTree(packageDir, opts, maxDepth,new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {Path name = dir.getFileName();if (name == null || SourceVersion.isIdentifier(name.toString())) // JSR 292?return FileVisitResult.CONTINUE;elsereturn FileVisitResult.SKIP_SUBTREE;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {if (attrs.isRegularFile() && kinds.contains(getKind(file.getFileName().toString()))) {JavaFileObject fe =PathFileObject.createDirectoryPathFileObject(JavacPathFileManager.this, file, pathDir);results.append(fe);}return FileVisitResult.CONTINUE;}});
总结
简单来说,访问者模式就是封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作。
访问者模式适用场景 :
- 数据结构稳定,但是作用于数据结构的操作经常变化
- 需要数据结构与数据操作分离
- 需要对不同数据类型(元素)进行操作,但是有不使用
if.. else ..
判断具体类型
优点:
- 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
- 扩展性好,添加新的操作或者说访问者会非常容易。
缺点:
- 增加新的元素类型会非常困难,每次新增元素类型,则访问者类必须增加对应元素类型的操作
- 违反了依赖倒置原则,访问者依赖的是具体元素类型,而不是抽象
- 变更元素的属性可能会导致对应的访问者类也需要修改
相关文章:

【设计模式】访问者模式
访问者模式 访问者模式被称为是最复杂的设计模式,比较难理解并且使用频率不高。 在 GoF 的《设计模式》⼀书中,访问者者模式(Visitor Design Pattern)是这么定义的: Allows for one or more operation to be applied to a set o…...

蓝桥杯刷题冲刺 | 倒计时27天
作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录1.递增序列2.等差素数列3.七段码4.亲戚5.连通块中点的数量1.递增序列 题目 链接:&am…...
RV1126_python人脸识别Retinaface+MobilefaceNet
RV1126_python人脸识别Retinaface+MobilefaceNet RV1126 具备RKNN 模块支持大部分如Pytorch、MXNet、Caffe、tensorflow、keras、onnx等常见框架,而且量化部署使用RKNN-toolkit非常方便。以下介绍通过RV1126实现的人脸识别过程。 首先人脸识别需要先做人脸检测>>人脸校正…...

HBase---HBase基础语法
HBase基础语法 文章目录HBase基础语法基本操作进入 HBase 客户端命令行查看命名空间查看命名空间下的表创建命名空间创建表查看表描述禁用/启用删除表新增列族删除列族更改列族存储版本的限制put 增加数据get 查看数据get条件查询删除指定列族下的指定列删除指定行全表扫描全表…...

2023年,PMP有多少含金量呢?
其实围绕以PMP含金量为中心的这个类似的小问题我好像也已经写了不少文章了。首先我肯定PMP的含金量,不管有多少质疑,这的确是事实。因为就是看中了他的价值考的,并且在项目的执行上收获了很多。 具体的可以看我接下来谈的PMP的价值&#x…...
vue动态路由
import Vue from vue import Router from vue-router import layout from ../components/layout Vue.use(Router) // 动态路由 export const asyncRouterMap = [ { path: /home, component: layout, name: home, meta: { title: 首页, icon: el-ic…...

被骗进一个很隐蔽的外包公司,入职一个月才发现,已经有了社保记录,简历污了,以后面试有影响吗?...
职场的套路防不胜防,一不留神就会掉坑,一位网友就被“骗”进了外包公司,他说公司非常隐蔽,入职一个月才发现是外包,但已经有了社保记录,简历污了,不知道对以后面试有影响吗?楼主说&a…...
华为OD机试 -租车骑绿岛(Java) | 机试题+算法思路+考点+代码解析 【2023】
租车骑绿岛 题目 部门组织绿岛骑行团建活动。租用公共双人自行车骑行,每辆自行车最多坐两人、做大载重M。 给出部门每个人的体重,请问最多需要租用多少双人自行车。 输入 第一行两个数字m、n,自行车限重m,代表部门总人数n。 第二行,n个数字,代表每个人的体重。体重都…...

【Java|基础篇】用思维导图理解逻辑控制
文章目录顺序结构分支结构if单分支语句if else双分支语句if else if else多分支语句switch语句循环语句for循环while循环do while循环continuebreak总结顺序结构 顺序结构是指代码按照从上往下的顺序依次执行 分支结构 选择语句是条件成立时,才会执行的语句.共有三种.分为是if…...

Go单元测试基础
Go单元测试基础1.go test工具2.单元测试函数3.go test -v/go test -run4.跳过某些测试用例5.子测试6.表格驱动测试7.并行测试8.使用工具生成测试代码9.测试覆盖率1.go test工具 Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需…...
华为OD机试 -执行时长(Java) | 机试题+算法思路+考点+代码解析 【2023】
执行时长 题目 为了充分发挥GPU算力,需要尽可能多的将任务交给GPU执行,现在有一个任务数组,数组元素表示在这1秒内新增的任务个数且每秒都有新增任务,假设GPU最多一次执行n个任务,一次执行耗时1秒,在保证GPU不空闲情况下,最少需要多长时间执行完成 输入描述: 第一个…...

互联网检测服务器
互联网检测服务器 1. 题目要求2. 试题解析1. 题目要求 题目: 为了模拟 Internet 访问测试,请搭建网卡互联网检测服务。 2. 试题解析 根据windows的官方文档,互联网检测服务有专门的域名,通过注册表可以找到检测域名字符串的写法(字符串为www.msftconnecttest.com),具体位…...
YOLO系列模型改进指南
YOLO系列模型改进指南 目前包含yolov5,yolov7,yolov8模型的众多改进方案,效果因数据集和参数而定,仅供参考。 如果需要改进模型,建议baseline和改进模型也不要载入预训练权重,不然的话,他们的起…...
QML- 在QML定义JavaScript资源
在QML定义JavaScript资源一、概述二、后台代码实现文件三、共享JavaScript资源(库)一、概述 QML应用程序的一部分程序逻辑可以用 JavaScript 定义。JavaScript代码可以在QML文档中内联定义,也可以分离到单独的 JavaScript 文件中(在QML中称为JavaScript资源)。 QML…...

php(tp框架)使用七牛云对象存储
图片文件存服务器非常占用存储带宽资源,且用户访问体验也不佳,因此使用一些第三方oss存储就很有必要了。之前lz发布了一篇tp使用阿里云oss的博文。不过阿里oss是收费的。而七牛云提供了一些免费使用额度。所以,这里额外补充一篇。 1.前提准备…...

八大排序算法之插入排序+希尔排序
目录 一.前言(总体简介) 关于插入排序 关于希尔排序: 二.插入排序 函数首部: 算法思路: 算法分析 插入排序代码实现: 插入排序算法的优化前奏: 三.希尔排序(缩小增量排序) 1.算法思想: 2.算法拆分解析 序列分组 分组预排序: 分组预排序的另一种实现方式: 希尔…...

蓝桥杯第十四届蓝桥杯模拟赛第三期考场应对攻略(C/C++)
这里把我的想法和思路写出来,恳请批评指正! 目录 考前准备 试题1: 试题2: 试题3: 试题4: 试题5: 试题6: 试题7: 试题8: 试题9: 试题1…...

【数论】最大公约数、约数的个数与约数之和定理
Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接 我会一直往里填充内容哒! &…...
第28篇:Java日期Calendar类总结(二)
目录 1、获取系统当前时间 2、获取指定日期 3、对象字段类型 4 、对象信息设置 4.1 Set设置...

【Python】字符串 - 集大成篇
目录 1. 不同语言的字符串比较 1.1 C 语言 1.2 C 语言 1.2.1 C 风格字符串 1.2.2 C 风格字符串 1.3 JAVA 1.4 Python 2. Python 字符串 2.1 方法 2.2.1 title () 2.2.2 lower () 2.2.3 upper () 2.2.4 rstrip () 2.2.5 lstrip …...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...