Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java编程思想第五版精粹(五)-初始化和清理(上)

Java编程思想第五版精粹(五)-初始化和清理(上)

原创
作者头像
JavaEdge
修改于 2020-05-06 07:32:25
修改于 2020-05-06 07:32:25
48800
代码可运行
举报
文章被收录于专栏:JavaEdgeJavaEdge
运行总次数:0
代码可运行

1 编程面临的主要安全问题

1.1

初始化

比如C语言,我写了整整半年,很多代码bug是因为程序员忘记初始化导致的,比如指针.

对于更高级的语言,现实中的很多调包侠不知道怎么才能初始化三方库包里的组件,甚至当侠客们必须得初始化这些三方组件时(而很多精简的掉包侠根本不会管初始化问题)

1.2

清理

当使用完一个元素后,因为再也用不到了嘛,就很容易忘了它。哦豁,那个元素很容易忘记清理它。这样就造成了元素使用的资源滞留不会被回收,直到程序消耗完所有的资源(特别是内存)。

2 构造器确保初始化

为解决问题 1.1,所以Java提供了构造器机制。类的设计者通过构造器保证每个对象的初始化。

那么问题随之而来了

2.1

怎么命名构造器

存在两个问题:

  1. 任何命名都可能与类中其他已有元素的名称冲突
  2. 调用构造器是编译器的职责,它必须知道该调用哪个方法

C++ 的解决方案看起来是最简单且最符合逻辑的,所以 Java 使用了同样的方式: 构造器名称与类名相同。冥冥之中就意味着在初始化过程中自动调用构造器。

2.2

怎么使用构造器

当创建一个对象时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
new MyObj()

分配存储空间,调用构造器。构造器保证了对象在被使用前执行了正确的初始化。

构造器方法名与类名相同,不需要符合首字母小写的编程风格

在 C++ 中,没有参数的构造器称为默认构造器。但是,出于某些原因,Java 设计者采用无参构造器这个名称,我(作者)认为这种叫法笨拙且没必要,所以我打算继续使用默认构造器。Java 8 引入了  default  关键字修饰方法,所以算了,还是用无参构造器的叫法吧。

2.3

构造器的好处

提高了代码可读性。从概念上讲,初始化与创建是相互独立的。而在前面的代码中,却看不到对初始化方法的显式调用。在 Java 中,对象的创建和初始化是捆绑在一起的概念,二者密不可分。

构造器是一种特殊的方法,因为它没有返回值。但它和返回类型为 void 的普通方法不同,普通方法可以返回空值,但还是能选择让它返回别的值。而构造器没有返回值,也没有给你选择的机会(虽然 new 表达式返回了刚创建的对象的引用,但构造器本身却是没有返回任何值的)。

试想一下,如果它真的有返回值,并且你也可以自己选择让它返回什么,那么编译器还得知道接下来该怎么处理那个返回值(这个返回值没有接收者)。

3 方法重载

名称是编程语言都具备的一个重要特性。当你创建一个对象时,就会给此对象分配的内存空间一个名称。一个方法就是一种行为的名称。通过名称引用所各种对象,属性和方法。良好的命名可以让系统易于理解和修改。

在将人类语言映射到编程语言时,拷问灵魂的问题就来了。因为通常来说,一个词可以表达多种不同的含义——它们被"重载"了!

3.1

人类语言场景

尤其是当含义差别很小时,这会很有用。就好像正常人会说"洗衬衫"、"洗车"和"洗狗"。而如果硬要这么说就会显得很愚蠢:"以洗衬衫的方式洗衬衫"、"以洗车的方式洗车"和"以洗狗的方式洗狗",因为听众根本不需要明确区分行所执行的动作。大多人类语言都具有这种"冗余"性,即使漏掉几个词,你也能明白含义。不需要对每个概念都使用不同的词汇——可以从上下文推断(基于大家都是智商正常的)。

3.2

编程语言场景

大多数编程语言(尤其是 C )要求为每个方法(在这些语言中经常称为函数)提供一个独一无二的标识符。所以,你可别指望有一个万金油 print() 函数能打印整型,也能打印浮点型——每个函数名都必须不同。

在 Java 和 C++ 中,还有一个因素促使了必须使用方法重载:构造器。因为构造器名肯定与类名相同,所以一个类中只会有一个构造器名。

那么问题又来了:怎么通过多种方式创建一个对象?

都是构造器,所以肯定名称相同——就是类名。因此,方法重载就很必要了:允许方法具有相同名称,但不同类型的参数。

3.3

区分方法重载

方法名相同,Java怎么知道你调用的是哪个?

最好最简单的实现只需遵循:每个被重载的方法必须有独一无二的参数类型列表。虽然也可以根据参数顺序来区分,但这会造成代码难以维护。

3.4

重载与基本类型

基本类型会自动从较小类型转型为较大类型。当这与重载结合时,有时令人迷糊。如果传入的参数类型(比如 int)大于方法期望接收的参数类型(byte),你必须首先做窄化转换,否则编译器就会报错。

3.5

返回值的重载

初学者经常搞不懂为什么就不能通过方法返回值区分呢?

看如下两个方法,它们有相同的命名和参数,但是很容易区分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void f(){}int f() {return 1;}

有时,编译器很容易从上下文推断出该调用哪个方法,如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int x = f()

但是,其实是可以操作调用一个方法且忽略返回值。这叫做调用一个函数的副作用,因为你不在乎返回值,只是想利用方法做些事。

所以如果你直接调用 f(),Java 编译器就不知你到底想调用谁,阅读者也不明所以。基于此,所以你不能根据返回值类型区分重载的方法。为了支持新特性,虽然 Java8 在一些具体情形下提高了猜测的准确度,但通常来说并无卵用。

4 无参构造器

一个无参构造器就是不接收参数的构造器,用来创建一个"默认的对象"。

  • 如果你创建一个类,类中没有构造器,那么编译器就会自动为你创建一个无参构造器
  • 但是,如果你显式定义了构造器(无论有参还是无参),编译器就不会再自动为你创建无参构造器 编译器认为你已经写了构造器,所以肯定知道你自己在做什么,如果你自己没有创建默认构造器,说明你本就不需要。

5 this 关键字

两个相同类型的对象 a 和 b,你可能在想,编译器是如何知道该为哪个对象调用方法的呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Banana {    void peel(int i) {        /*...*/    }}public class BananaPeel {    public static void main(String[] args) {        Banana a = new Banana(), b = new Banana();        a.peel(1);        b.peel(2);    }}

编译器做了优化,其实在方法中第一个参数,就已经隐密地传入了一个指向所操作对象的引用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Banana.peel(a, 1)Banana.peel(b, 1)

这是内部实现的,SE不可以直接这么写代码。

假设在方法内部,你想获得对当前对象的引用。但是,引用是被秘密传给编译器的,而并不在参数列表中。方便的是,有一个关键字: this 。

this 关键字只能在非static方法内使用。当你调用一个对象的方法时,this 生成了一个对象引用。你可以像对待其他引用一样对待这个引用。 如果你在一个类的方法里调用其他该类中的方法,不要使用 this,直接调用即可,this 自动地应用于其他方法上了。

5.1

适用场景

this 关键字只用在一些必须显式使用当前对象引用的特殊场合。例如:

  1. 在建造者模式中,在 return 语句中返回对当前对象的引用
  2. 参数列表中的变量名 s 和成员变量名相同,会引起混淆。可以通过 this.var
  3. 向其他方法传递当前对象 class Person { public void eat(Apple apple) { Apple peeled = apple.getPeeled(); System.out.println("Yummy"); }}​public class Peeler { static Apple peel(Apple apple) { // ... remove peel return apple; // Peeled }}​public class Apple { Apple getPeeled() { return Peeler.peel(this); }}​public class PassingThis { public static void main(String[] args) { new Person().eat(new Apple()); }}​ Apple 因为某些原因(比如说工具类中的方法在多个类中重复出现,你不想代码重复),必须调用一个外部工具方法 Peeler.peel() 做一些行为。必须使用 this 才能将自身传递给外部方法。
  4. 构造器中调用构造器 一个类中有多个构造器,为避免代码重复,想在一个构造器中调用另一个构造器来。可以使用 this。 通常 this,意味着"这个对象"或"当前对象",它本身生成对当前对象的引用。在构造器中,当给 this 一个参数列表时,它是另一层意思:显式调用构造器。

5.2

再谈static

之前我们就讨论过 static,现在已经知道了 this 关键字的作用,这有助于提高对 static 修饰方法的理解。

static 方法中不会存在 this。不能在static方法中调用非static方法(反之则是可以的)。

static方法是为类而创建,无需任何实例。这其实就是static方法的主要目的,static方法看起来就像全局方法,但是 Java 不允许全局方法,一个类中的静态方法可以被其他的静态方法和静态属性访问。

note:一些人认为static方法破坏面向对象,因为它们具有全局方法语义。使用静态方法,因为不存在 this,所以你没有向一个对象发送消息。的确,如果你发现代码中出现了大量的 static 方法,就该重新考虑自己的设计了。然而,static 的概念很实用,许多时候都要用到它。至于它是否真的"面向对象",就留给理论家讨论吧。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java编程思想第五版 第六章-初始化和清理
C 语言中很多的 bug 都是因为程序员忘记初始化导致的。尤其是很多类库的使用者不知道如何初始化类库组件,甚至当侠客们必须得初始化这些三方组件时(很多可怜的掉包侠根本不会管初始化问题)
JavaEdge
2020/05/27
7140
java编程思想第五章初始化与清理
    构造器是方法重载的一个重要原因,若想以多种方式创建构造器,就必须将方法进行重载,即使用有参构造。
用户1134788
2022/05/09
5970
java编程思想第五章初始化与清理
Java 小白成长记 · 第 4 篇《对象的初始化和清理》
这是一个技术疯狂迭代的时代,各种框架层出不穷,然而底层基础才是核心竞争力。博主(小牛肉)在现有的知识基础上,以上帝视角对 Java 语言基础进行复盘,汇总《Java 小白成长记》系列,力争从 0 到 1,全文无坑。
飞天小牛肉
2021/02/26
3380
Java 小白成长记 · 第 4 篇《对象的初始化和清理》
Java类的初始化和清理
C 语言中很多的 bug 都是因为程序员忘记初始化导致的。尤其是很多类库的使用者不知道如何初始化类库组件,甚至当侠客们必须得初始化这些三方组件时(很多可怜的掉包侠根本不会管初始化问题)
JavaEdge
2021/12/07
9670
Java编程思想第五版精粹(五)-初始化和清理(下)
以编译时错误的方式呈现。编译器可以为 i 赋一个默认值,但是未初始化的局部变量更可能是程序员的疏忽,所以强制程序员提供一个初始值,往往能帮助找出程序里的 bug。
JavaEdge
2020/05/27
4740
编程思想 之「初始化与清理」
与其他语言相比,Java 的一大特点就是其自动的初始化与清理功能。对于基本数据类型的全局变量,Java 自动将其初始化为对应的默认值,具体可以参考「对象漫谈」中的内容;对于对象,我们可以通过构造方法对其进行初始化;对于清理操作,Java 提供了垃圾回收机制,其可以帮我们自动清理不再使用的对象,释放资源。
CG国斌
2019/05/26
4040
Java编程思想学习录(连载之:初始化与清理)
Java编程思想学习录连载文章 关于构造器与初始化 无参构造器 = 默认构造器 = 自己未写编译器帮忙自动创建的 若自行定义了构造器(无论参数有否),编译器便停止默认创建动作 类里的对象引用默认初始化为null,基本类型初始化为0 四种常见初始化方式: 自动初始化:无法被阻止的,先于构造器,即所谓的基本类型赋空值(0),对象赋null 指定初始化:定义类成员的时候直接赋初始值 初始化子句:(匿名内部类的初始化的必需品!且一定先于构造器执行) 构造器初始化:在构造器中对成员赋上值 静态域的初始化: java中
CodeSheep
2018/05/22
5960
Java基础:五、this关键字、static含义(4)
如果只有一个peel()方法,如何知道是被a还是b所调用的呢?因为编译器会把“所操作对象的引用”作为第一次参数传递给peel()。所以上述两个方法的调用就变成了这样:
桑鱼
2020/03/17
3140
正确的初始化,在Java编程中至关重要!
有人说,你应该关注时事、财经,甚至流行的电影、电视剧,才有可能趁着热点写出爆文;有人说,你别再写“无聊”的技术文了,因为程序员的圈子真的很小,即便是像鸿祥那样的招牌大牛,文章是那么的干货,浏览量有多少?不到万吧;有人说,你别妄想在写作上面知识变现了,因为你写的文章真的很不优秀,我都不爱看!
沉默王二
2019/01/17
6550
java编程思想第四版第五章总结
1. 构造器 构造器的一个重要的作用: 保证对象被使用之前初始化了. 构造器是一种特殊类型的方法, 因为他没有返回值。这与返回值为空(void)明显不同。对于空返回值,尽管方法本身不会自动返回什么,
用户7798898
2020/09/27
3950
Java编程思想第五版精粹(五)-初始化和清理(中)
初始化很重要,而清理工作也同样重要。毕竟,谁会去清理一个 int?但使用完一个对象就不管了,这并非总是安全的操作。
JavaEdge
2020/05/27
5280
Java编程思想第五版第八章 -复用
对于像 C 语言等面向过程语言来说,“复用”通常指的就是“复制代码”。任何语言都可通过简单复制来达到代码复用的目的,但是这样做的效果并不好。Java 围绕“类”(Class)来解决问题。我们可以直接使用别人构建或调试过的代码,而非创建新类、重新开始。
JavaEdge
2020/05/26
8400
Java编程思想第五版(On Java8)(十二)-集合
通常,程序总是根据运行时才知道的某些条件去创建新的对象。在此之前,无法知道所需对象的数量甚至确切类型。为了解决这个普遍的编程问题,需要在任意时刻和任意位置创建任意数量的对象。因此,不能依靠创建命名的引用来持有每一个对象:
JavaEdge
2020/05/27
2.3K0
Java编程思想第五版(On Java8)(十一)-内部类
内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。
JavaEdge
2020/05/27
1.1K0
Java编程思想核心笔记
Java 编程思想为 Java 开发的圭臬, 是 Java 开发的经典手册. 作为一个开发人员还是建议多看一看. 从大学时起到现在已经开发多年, 也看过多遍, 随着年龄的增长和开发经验的增加, 每次重新阅读侯都会有新的理解, 所谓温故而知新. 但也存在问题, 一个是阅读时获得新的理解隔一段事件后容易遗忘. 二是每次阅读后做的纸质笔记容易丢失和难以拓展. 遂决定以电子版记之~~
芥末鱿鱼
2022/05/05
5870
Java编程思想核心笔记
Java为什么要支持方法重载?
任何编程语言中都具备的一项重要特性就是名称。当你创建一个对象时,就会给此对象分配的内存空间一个名称。一个方法就是一种行为的名称。通过名称引用所各种对象,属性和方法。良好的命名可以让系统易于理解和修改。
JavaEdge
2021/10/18
7700
Java编程思想第五版(OnJava8)第七章 - 封装
所有优秀的作者——包括那些编写软件的人——都知道一件好的作品都是经过反复打磨才变得优秀的。如果你把一段代码置于某个位置一段时间,过一会重新来看,你可能发现更好的实现方式。这是重构(refactoring)的原动力之一,重构就是重写可工作的代码,使之更加可读,易懂,因而更易维护。
JavaEdge
2020/05/27
8910
Java编程思想第五版(On Java8)(十)-接口
这种机制在编程语言中不常见,例如 C++ 只对这种概念有间接的支持。而在 Java 中存在这些关键字,说明这些思想很重要,Java 为它们提供了直接支持。
JavaEdge
2020/05/27
8210
15-初始化和清理
初始化和清理是涉及编程安全的两个重要问题,在C语言中一直采取由程序员直接控制变量的初始化和清理,极易导致内存耗尽等问题出现
Ywrby
2022/10/27
2620
Java编程思想第五版精粹(四)-运算符
接受参数并生成新值。与普通方法调用殊途同归。所有运算符都能根据自己的运算对象生成一个值。
JavaEdge
2020/05/26
8110
相关推荐
Java编程思想第五版 第六章-初始化和清理
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验