前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java编程思想第五版精粹(五)-初始化和清理(上)

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

原创
作者头像
JavaEdge
修改2020-05-06 15:32:25
4500
修改2020-05-06 15:32:25
举报
文章被收录于专栏:JavaEdgeJavaEdge

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

1.1

初始化

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

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

1.2

清理

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

2 构造器确保初始化

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

那么问题随之而来了

2.1

怎么命名构造器

存在两个问题:

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

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

2.2

怎么使用构造器

当创建一个对象时:

代码语言:javascript
复制
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
复制
void f(){}int f() {return 1;}

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

代码语言:javascript
复制
int x = f()

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

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

4 无参构造器

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

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

5 this 关键字

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

代码语言:javascript
复制
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
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com