news 2026/10/21 8:13:10

Java Circle2D类设计:从封装到边界条件的完整实现指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Circle2D类设计:从封装到边界条件的完整实现指南

只要你系统学过Java,无论是课堂作业、实验报告还是面试手写代码,大概率都见过这道题:定义Circle2D类。题目通常长这样——让类包含圆心的x、y坐标和半径radius,提供无参构造、带参构造、getter方法,再实现getArea()、getPerimeter(),以及三个几何判定方法contains(double x, double y)、contains(Circle2D circle)、overlaps(Circle2D circle)。看起来就是一套标准答案唾手可得的入门练习,但真动手写的时候,很多人会在数学条件、边界情况、浮点比较上栽跟头。这篇文章就从一名Java开发者和带过课程设计的人的角度,把这题的每个设计决策掰开揉碎讲清楚,顺便把我踩过的坑和改代码时见过的典型错误一并写上。不管你是在做作业,还是想把这题扩展成项目里的基础几何工具类,都能直接参考。

1. 题目拆解:Circle2D到底在考你什么

1.1 一眼看穿的类设计考点

很多同学拿到这题的第一反应是“这不就是封装、构造方法、getter/setter吗”,然后唰唰写完交差。确实,题面里最明显的考点是面向对象基础:类的字段、构造方法重载、访问器方法。但你再往深处看一眼,这题其实在考三件事:数学建模能力、边界条件意识、代码规范习惯。

数学建模能力说的是,把一个“二维平面上的圆”抽象成三个double字段后,所有方法都是在用代数公式表达几何关系。比如判断点是否在圆内,本质是计算两点距离与半径的比较;判断两个圆是否重叠,本质是圆心距与半径之和、半径之差的比较。这里没有新知识,但能把几何关系翻译成正确的Java表达式,是区分“背代码”和“会写代码”的分水岭。

边界条件意识更关键。contains(x, y)里,点刚好在圆周上算不算在圆内?contains(Circle2D circle)里,两个圆完全重合算不算包含?overlaps里,两个圆内切、外切算不算重叠?不同教材对这些问题定义不同,代码必须明确体现你的选择并保持一致。

代码规范习惯则体现在命名、this的使用、是否提供不必要的setter、注释是不是在解释“怎么算”而不是“算什么”。这些细节在作业阶段不显眼,等代码量上来以后,能直接决定别人愿不愿意读你的代码。

1.2 字段选型:为什么坐标和半径都用double

Circle2D类里最常见的字段定义是这样的:

private double x; private double y; private double radius;

有人会问,坐标用int不行吗?反正测试用例里都是整数。我的建议是:除非题目明确限定坐标和半径是整数,否则一律用double。原因很简单,几何计算场景几乎不可能只碰整数。你自己写一个测试,判断点(0.5, 0.5)在不在圆内,如果用int字段,这个测试根本没法表达。而且double可以无损表示整数范围内的整数,用double不会丢整数精度,反过来用int表达小数却需要额外做转换。

还有一个细节:radius必须保证非负吗?从数学上讲,半径是距离,负半径没有意义。但从代码健壮性角度,我见过有人在构造方法里写if (radius < 0) throw new IllegalArgumentException("radius must be non-negative")。这个做法很稳,不过要注意一点:如果你要交作业,先看题目有没有要求做参数校验。没要求的情况下写不写都行,写了反而是加分项,因为老师一看就知道你有防御性编程意识。但别在作业里过度设计,比如用BigDecimal来存半径,那就偏离题目本身想考的东西了。

1.3 构造方法:无参构造的默认值该怎么定

Circle2D通常需要两个构造方法:一个无参构造,一个带三个参数的构造。无参构造的默认值,题目里大概率会写明,比如“创建一个圆心在(0,0)、半径为1的圆”。如果题目没写,你就得自己定一个合理值。我习惯默认(0, 0)为圆心,半径取1。为什么不取0?因为半径为0的圆面积是0,周长也是0,后续测试时会出现很多无意义的“点圆”,容易掩盖逻辑错误。

无参构造的实现要注意写法。最省事的方式是直接给字段赋值:

public Circle2D() { this(0, 0, 1); }

这样写而不是把this(0, 0, 1)换成三条赋值语句,原因是代码复用。如果后续你要给半径加上非负校验,只需要在带参构造里改一处,无参构造通过this调用也会自动带上校验逻辑。用this调用另一个构造方法必须放在构造方法的第一行,这一点初学者容易忘。

带参构造里的参数命名,我强烈建议写成x、y、radius,而不是px、py、r。参数和字段同名时用this区分,代码读起来最自然:

public Circle2D(double x, double y, double radius) { this.x = x; this.y = y; this.radius = radius; }

有同学为了省一个this,把参数命名为xCoordinate、yCoordinate,读起来绕,写起来也累。Java里this就是干这个用的,别怕用它。

2. 几何计算与几何判定:比想象中多一层的边界问题

2.1 面积与周长:Math.PI和浮点精度那些事

getArea()和getPerimeter()是所有方法里最简单的两个:

public double getArea() { return Math.PI * radius * radius; } public double getPerimeter() { return 2 * Math.PI * radius; }

这里有两个值得说的点。第一,一定要用Math.PI,不要自己写个final double PI = 3.14。作业里用3.14大概率也能过,但3.14和实际圆周率差距带来的误差在几何计算里会放大。比如计算半径为100的圆面积,3.14算出来是31400,Math.PI算出来约是31415.9265,差了15.9。如果后面还有依赖面积结果的计算,误差会继续传播。用Math.PI不丢人,反而说明你清楚浮点计算有精度问题。

第二,求平方我建议直接写radius * radius,而不是Math.pow(radius, 2)。从可读性上说两者都行,但从性能和代码意图上看,半径自乘更直观,也能避免Math.pow接受double参数时可能带来的微小的精度开销。这里不是让你做过早优化,只是乘法表达“平方”在几何公式里最自然。

有同学问getArea()和getPerimeter()要不要加final。我认为可以加,因为圆面积和周长的计算公式不会被子类改变。不过这是设计层面的选择,入门阶段不加也完全没问题。真正要注意的是,如果后续有人继承Circle2D做子类并重写getArea(),会导致circle的半径不变但面积变了的诡异情况,这违反里氏替换原则。所以如果你想让这个类作为公共基础类,用final修饰这两个方法是更严谨的做法。

2.2 点与圆的位置判定

contains(double x, double y)判断一个坐标点是否在圆内。数学条件是:点到圆心距离小于等于半径。写成代码:

public boolean contains(double x, double y) { double dx = this.x - x; double dy = this.y - y; return Math.hypot(dx, dy) <= radius; }

这里我用了Math.hypot而不是Math.sqrt(dx * dx + dy * dy)。Math.hypot是专门算直角边平方和开方的方法,底层实现会尽量避免中间值溢出或下溢。坐标值很大或很小时,dx * dx可能超出double能表示的范围,而Math.hypot正确处理这种情况。当然,作业里的测试数据通常不会那么极端,但既然是标准库提供的工具,直接使用它能让代码更简洁,也能少写一个临时变量。

边界情况是“点刚好在圆上”。我用的是<=,也就是点在圆周上算包含。这个选择要和你后面contains(Circle2D circle)的判断保持一致。如果两个圆半径相同且圆心重合,那么contains方法应该返回true,因为一个圆完全包含另一个圆。这时候对应的是distance + otherRadius <= radius,即圆心距为0,0 + r <= r成立。

2.3 圆与圆的包含判定:别漏了“内含”的数学条件

contains(Circle2D circle)判断另一个圆是否完全被当前圆包含。很多人一开始会把条件写成“两圆圆心距离小于等于this.radius”,但这显然不对,因为如果被包含的圆半径比当前圆还大,圆心距离再近,它也不可能被完全装进去。完整条件是:两圆圆心距离 + 另一个圆的半径 <= 当前圆的半径。

public boolean contains(Circle2D circle) { double distance = Math.hypot(this.x - circle.x, this.y - circle.y); return distance + circle.radius <= this.radius; }

这个公式很好理解:要让另一个圆完全装进自己里面,最多只能让另一个圆最远的那一点贴着自己圆周,也就是“圆心距 + 对方半径”不能超过自己半径。等号成立时,两个圆内切,我习惯把内切视为包含。

从设计上,这个方法可以复用前面算圆心距的逻辑。所以很多教材会在类里额外加一个私有方法:

private double distance(double x, double y) { return Math.hypot(this.x - x, this.y - y); }

然后用它来实现contains(double, double)和contains(Circle2D):

public boolean contains(double x, double y) { return distance(x, y) <= radius; } public boolean contains(Circle2D circle) { return distance(circle.x, circle.y) + circle.radius <= radius; }

这样可以避免三处重复写求距离的表达式。我给同学改作业时,经常看到把Math.hypot复制三遍的写法。功能没错,但后续如果要改距离计算方式,就得改三处。把公共计算抽成方法,是“方法封装”最直观的练习,也是这题除了题目要求之外真正值得做的加分项。

2.4 圆与圆的相交判定:overlaps的两种理解

overlaps(Circle2D circle)是这道题里争议最大的方法。不同教材对“重叠”的边界条件定义不一样。我见过的主要有两种:

第一种定义:两个圆只要有公共部分就算重叠,包含相切、包含内含。数学条件就是distance <= this.radius + circle.radius。这个定义把相切和包含都算进去,缺点是“两个圆一个包含另一个”也被归为重叠,语义上有点怪,因为包含而不是相交,但也确实有公共区域。

第二种定义:两个圆相交但不包含,也就是经典的“两圆相交”判定。数学条件是两个不等式同时满足:

distance < this.radius + circle.radius distance > Math.abs(this.radius - circle.radius)

第一个不等式排除了外离和外切,第二个不等式排除了内含和内切。为什么第二个不等式用Math.abs?因为我们不知道哪个圆大,半径差必须取绝对值,否则只判断distance > this.radius - circle.radius,当另一个圆更大时会得到错误结果。

我自己的实现偏向第二种,因为它更符合“overlaps”这个词在几何里通常表达的“相交且有重叠区域,但不完全包含”的意思:

public boolean overlaps(Circle2D circle) { double distance = Math.hypot(this.x - circle.x, this.y - circle.y); return distance < this.radius + circle.radius && distance > Math.abs(this.radius - circle.radius); }

这里我还有意识地把<写成严格小于而非小于等于,把相切排除在重叠之外。相切时两圆只有一个公共点,说“重叠”确实勉强。当然,如果你参考的教材把相切也算overlaps,请改成<=和>=。在实现时,我的建议是把这层语义选择写在注释里,比如:

// 两个圆相交(不包含相切与内含)时返回true

这样无论是老师看作业,还是同事看代码,都能立刻明白你的定义,避免后续因为理解不一致而出bug。

3. 完整实现与测试:直接把能跑的代码给你

3.1 Circle2D完整类实现

把前面讲的整理到一起,一个既能满足作业要求、又带一点工程习惯的Circle2D类可以长这样:

public class Circle2D { private double x; private double y; private double radius; public Circle2D() { this(0, 0, 1); } public Circle2D(double x, double y, double radius) { if (radius < 0) { throw new IllegalArgumentException("radius must be non-negative"); } this.x = x; this.y = y; this.radius = radius; } public double getX() { return x; } public double getY() { return y; } public double getRadius() { return radius; } public double getArea() { return Math.PI * radius * radius; } public double getPerimeter() { return 2 * Math.PI * radius; } public boolean contains(double x, double y) { return distance(x, y) <= radius; } public boolean contains(Circle2D circle) { return distance(circle.x, circle.y) + circle.radius <= radius; } public boolean overlaps(Circle2D circle) { double distance = distance(circle.x, circle.y); return distance < this.radius + circle.radius && distance > Math.abs(this.radius - circle.radius); } private double distance(double x, double y) { return Math.hypot(this.x - x, this.y - y); } }

注意几个细节。我没有写setter,因为radius一旦被构造后就不应该被随意修改,x和y也同理。如果题目明确要求提供setter,那就按题目来;如果只是说“提供访问器”,那么只给getter是更安全的设计。半径校验抛IllegalArgumentException是Java标准做法,不要自己写一个RuntimeException子类又没多大意义。distance方法是private,因为它是内部实现细节,外部调用者不需要关心。

3.2 测试程序与输出分析

光有类定义还不够,我习惯把测试程序写在同一个项目里单独的一个类中,用main函数直接跑。下面是针对上面的实现设计的测试:

public class TestCircle2D { public static void main(String[] args) { Circle2D c1 = new Circle2D(); Circle2D c2 = new Circle2D(3, 0, 2); Circle2D c3 = new Circle2D(0.5, 0, 0.2); System.out.println("c1圆心: (" + c1.getX() + ", " + c1.getY() + "), 半径: " + c1.getRadius()); System.out.println("c1面积: " + c1.getArea()); System.out.println("c1周长: " + c1.getPerimeter()); System.out.println("c1.contains(0.5, 0) = " + c1.contains(0.5, 0)); System.out.println("c1.contains(2, 0) = " + c1.contains(2, 0)); System.out.println("c1.contains(c2) = " + c1.contains(c2)); System.out.println("c1.contains(c3) = " + c1.contains(c3)); System.out.println("c1.overlaps(c2) = " + c1.overlaps(c2)); System.out.println("c1.overlaps(c3) = " + c1.overlaps(c3)); } }

我们来推一遍期望结果,这比直接看输出更重要。c1是圆心(0,0)、半径1的圆。它的面积是π,约3.141592653589793,周长是2π,约6.283185307179586。点(0.5,0)在圆内,所以contains返回true;点(2,0)到圆心距离是2,大于半径1,返回false。c2圆心(3,0)、半径2。c1和c2的圆心距是3,c2半径2,3 + 2 = 5 > 1,所以c2没有被c1包含,contains(c2)返回false。c3圆心(0.5,0)、半径0.2,圆心距0.5,0.5 + 0.2 = 0.7 <= 1,所以c1包含c3,返回true。

再说overlaps。c1和c2圆心距3,半径之和3,严格小于3不成立,所以返回false,因为两圆外切时只有交点没有重叠面积。c1和c3圆心距0.5,半径之和1.2,半径差0.8,不等式0.5 < 1.2且0.5 > 0.8不成立,说明c3被c1包含,不算重叠,返回false。这个测试虽然覆盖了几条常见路径,但还不够全,下一节补上真正的边界用例。

3.3 边界测试用例设计:考试和面试都爱问

我讲课时常说,写完功能代码只是完成了50%,剩下50%是设计和执行测试用例。对于Circle2D,至少要覆盖这样几类场景:

测试场景构造数据期望结果设计原因
点在圆周上圆心(0,0)半径1,点(1,0)contains(x, y) = true验证<=是否生效
点在外侧圆心(0,0)半径1,点(2,0)contains(x, y) = false基本否定路径
包含关系大圆(0,0)半径5,小圆(1,1)半径1contains(Circle2D) = true常规包含
完全重合两圆都圆心(0,0)半径3contains(Circle2D) = true边界中的边界
内切大圆(0,0)半径5,小圆(3,0)半径2contains(Circle2D) = true验证distance + r2 <= r1的等号
外切圆1圆心(0,0)半径2,圆2圆心(4,0)半径2overlaps(Circle2D) = false(按严格相交定义)验证严格小于
内含但不重叠圆1圆心(0,0)半径5,圆2圆心(1,0)半径1overlaps(Circle2D) = false验证半径差不等式
正常相交圆1圆心(0,0)半径2,圆2圆心(3,0)半径2overlaps(Circle2D) = true基本相交路径

把这组用例跑一边,你会发现contains和overlaps里那些<、<=、Math.abs全都不是白写的。很多同学代码里的bug就藏在等号场景下,比如把contains(Circle2D)写成distance <= radius,正好漏掉另一个圆更大的情况。测试用例设计到位后,这类问题会立刻暴露。

4. 新手最容易踩的坑:从代码风格到浮点比较

4.1 用==比较double,坑在哪

我在帮某同学调代码时见过这样的判断:

public boolean contains(Circle2D circle) { double distance = 0; // ... 计算距离 return distance + circle.radius == this.radius; }

他想表达的是内切时两圆边界刚好接触,所以用==判断。这在数学上没错,但在浮点数世界里非常危险。因为distance是通过开方运算得到的,而this.radius通常是用户传入的普通小数,两个来自不同计算路径的double几乎不可能精确相等,哪怕数学上应该相等,实际计算也可能得到1.9999999999999998这样的值。用==判断浮点相等,是Java初学者最常见的bug来源之一。

处理方式有两种。第一种是避开==,把内切并入>=或<=的分支。比如contains(Circle2D)用distance + circle.radius <= this.radius,当distance因为浮点误差略小于理论值时,判断依然成立,这在业务上更合理。第二种是引入误差容忍度epsilon:

private static final double EPSILON = 1e-12; private boolean almostEquals(double a, double b) { return Math.abs(a - b) < EPSILON; }

然后用almostEquals判断关键边界。但说实话,对这道作业题来讲,用<=、>=把相等归入包含或重叠,比手工做epsilon容错要简单得多,也不容易引入新错误。真正需要epsilon的场景是科学计算类的代码,作业阶段不用上纲上线。

4.2 构造方法里参数与字段重名,this到底要不要

有同学为了避免this,写出这样的构造方法:

public Circle2D(double xCoordinate, double yCoordinate, double r) { x = xCoordinate; y = yCoordinate; radius = r; }

功能上没问题,但读代码的人要停下来想一下xCoordinate和x的关系。更隐蔽的问题是,如果某天你重构字段名,比如把radius改成radiusValue,赋值语句里的x、y、radius散落各处,改起来容易漏。用this.radius = radius这种标准写法,左侧是字段、右侧是参数,含义一目了然。

还有一种更偷懒但实际常见的写法是给字段加前缀,比如mX、mY。这是从C++带来的习惯,在Java社区不主流,除非是团队规范要求,否则不建议用在作业里。Java本身就提供了this机制来消歧,用m前缀等于放弃了语言特性,还让代码看起来像旧时代的遗留代码。

4.3 只写getter不写setter,是偷懒还是设计

题目如果只说“提供访问器方法”,很多同学默认把getter和setter都写一遍。但Circle2D这个类里,真的需要setRadius吗?一旦允许外部随意修改radius,就可能出现radius为负的圆,或者一个已经被其他对象缓存了面积的圆突然变了半径的诡异状态。不可变对象在Java里是更安全的设计,尤其是这种只承载数值和计算逻辑的类。

如果你要交的作业明确要求写setter,那就按要求补上,但至少可以在setRadius里加上非负校验:

public void setRadius(double radius) { if (radius < 0) { throw new IllegalArgumentException("radius must be non-negative"); } this.radius = radius; }

这是两全的做法:满足需求,又不丢掉防御性编程意识。不过从我的个人习惯看,如果题目没有明确要求setter,我会主动不写,并在注释里说明这是为了保证对象不可变。这属于设计取舍,不是能力不足,面试时能讲清楚为什么少写几个方法,往往比把方法凑齐更让面试官欣赏。

5. 进阶玩法:把这道作业题做出含金量

5.1 用接口统一几何图形的行为

Circle2D写完之后,如果想让它更有扩展性,可以定义一个几何图形接口:

public interface GeometricShape { double getArea(); double getPerimeter(); boolean contains(double x, double y); }

然后让Circle2D实现这个接口。以后再定义Rectangle2D、Triangle2D,就能用同样的方式调用getArea()和getPerimeter(),写一个图形集合批量计算总面积也变得顺理成章:

List<GeometricShape> shapes = List.of( new Circle2D(0, 0, 2), new Circle2D(5, 5, 1) ); double totalArea = shapes.stream() .mapToDouble(GeometricShape::getArea) .sum();

这样重构之后,Circle2D就不再只是作业里的一个孤立类,而是融入了一个小型几何对象体系。理解接口的意义,比单纯多写一个类要重要得多。

5.2 用静态工厂方法代替重载构造

无参构造+带参构造是这题的常规要求。但如果我想创建一个以某点为圆心、半径为默认值的圆,就得写new Circle2D(x, y, 1),把魔法数字1写死在调用方。这时可以用静态工厂方法提供更清晰的语义:

public static Circle2D withRadius(double radius) { return new Circle2D(0, 0, radius); } public static Circle2D at(double x, double y) { return new Circle2D(x, y, 1); }

调用方式变成Circle2D.at(3, 4),读起来像自然语言。这是除了构造方法之外的另一种创建对象的思路,在Java标准库里也被大量使用,比如LocalDate.of()。作业里不一定要求你这么做,但写出来是明显的加分项。

5.3 给Circle2D加上集合与Lambda的配合

Circle2D的contains方法天然适合和Stream配合。比如给定一个圆心列表,找出所有包含某个点的圆:

List<Circle2D> circles = List.of( new Circle2D(0, 0, 1), new Circle2D(2, 2, 3) ); List<Circle2D> hitCircles = circles.stream() .filter(c -> c.contains(1, 1)) .toList();

这就是把几何类嵌入到实际业务逻辑里的一个例子。很多同学做完作业就把类文件扔了,其实只需要稍微扩展,就能体会到“写一个类”和“用一堆类解决问题”的区别。Circle2D虽然小,但它是理解对象建模、方法设计、边界处理这套流程的绝佳载体。

我个人在实际操作中的体会是,这类几何类题目最大的价值不在于面积和周长的公式,而在于“把数学条件转成程序条件”的精确性。见过太多代码把contains(Circle2D)写错、把Math.abs漏掉、把<和<=用混,最后测试跑不通。如果你正在写这道题,强烈建议把边界用例先列出来再动手实现,花10分钟设计case,能省下1小时调试。最后再分享一个小技巧:写完后把radius故意传负数试试,看代码是否抛出异常;把两个完全相同的圆传进contains和overlaps,看结果是否合乎你的定义。这两个小测试,几乎能检验出Circle2D实现里百分之八十的隐藏问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 8:12:54

Graphify实战:用代码知识图谱提升代码阅读效率

1. 一个让我重新理解代码阅读方式的工具第一次看到 Graphify 这个项目的时候&#xff0c;我正陷在一个五万行左右的老项目里出不来。那个项目是典型的“前人挖坑后人填”类型&#xff0c;模块之间调用关系错综复杂&#xff0c;光靠 IDE 的跳转功能&#xff0c;点着点着就迷路了…

作者头像 李华
网站建设 2026/10/21 8:12:29

C# WinForm 嵌入外部 EXE:Win32 API 窗口集成实战

简介&#xff1a;这是一份面向C#桌面开发者的WinForm窗体嵌入外部EXE程序实战示例包&#xff0c;聚焦进程管理、控件创建与窗口交互等核心难点&#xff0c;适合已掌握WinForm基础、希望实现多程序统一界面集成的中高级开发者参考。压缩包共25个文件&#xff0c;约48KB&#xff…

作者头像 李华
网站建设 2026/10/21 8:12:10

AI代码审查接入CI流水线:Balanced默认模式与API集成实践

1. 从"手动点按钮"到"流水线自动跑"&#xff1a;这次API开放到底改了什么过去一年里&#xff0c;代码审查这件事正在经历一次静默的迁移。以前我们团队的做法是&#xff1a;PR提上去&#xff0c;等同事有空了来review&#xff0c;运气好半天&#xff0c;运…

作者头像 李华
网站建设 2026/10/21 8:12:02

厂商AI能力对比:六维评估模型与选型实战指南

1. 厂商AI能力对比到底在比什么第一次看到“厂商AI能力对比”这个题目&#xff0c;很多人脑子里蹦出来的画面大概是跑分榜&#xff1a;谁家模型参数大、谁家榜单分数高、谁家发布会PPT更唬人。我早年也这么想&#xff0c;直到自己真正接手过几个需要选型的项目&#xff0c;才发…

作者头像 李华
网站建设 2026/10/21 8:11:41

AMD近似最小度排序:稀疏矩阵分解前的关键预处理

做仿真的和数值计算的工程师&#xff0c;打交道最多的矩阵之一就是“稀疏对称矩阵”。刚度矩阵、质量矩阵、电路方程里的节点导纳矩阵&#xff0c;几乎天天见。这类矩阵规模可以到几十万甚至上千万阶&#xff0c;但每行非零元往往只有几十个。直接做 Cholesky 或 LU 分解之前&a…

作者头像 李华
网站建设 2026/10/21 8:11:40

C#委托五步理解法:从方法地址到行为参数化

1. 五步法到底拆的是什么&#xff1a;先弄清楚委托为什么难1.1 难的不是语法&#xff0c;是"没见过这种思维"我带过的很多新人&#xff0c;在学到委托的时候都会经历一段非常类似的卡壳期。背概念倒还行&#xff1a;"委托是方法的引用""委托是类型安全…

作者头像 李华