专栏首页JavaEdgeJava编程思想第五版精粹(五)-初始化和清理(上)

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

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

1.1

初始化

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

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

1.2

清理

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

2 构造器确保初始化

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

那么问题随之而来了

2.1

怎么命名构造器

存在两个问题:

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

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

2.2

怎么使用构造器

当创建一个对象时:

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

返回值的重载

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

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

void f(){}
int f() {return 1;}

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

int x = f()

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

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

4 无参构造器

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

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

5 this 关键字

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

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);
    }
}

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

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 的概念很实用,许多时候都要用到它。至于它是否真的"面向对象",就留给理论家讨论吧。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

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

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

    公众号-JavaEdge
  • Filter如何工作

    公众号-JavaEdge
  • 掌握 @transactional 注解@Transactional 注解管理事务的实现步骤Spring 的注解方式的事务实现机制

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

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

    公众号-JavaEdge
  • SpringBoot源码分析 顶

    一:创建SpringApplication对象过程:new SpringApplication(primarySources)

    须臾之余
  • 匿名对象和object的转换

    参考http://www.2cto.com/kf/201207/139227.html

    跟着阿笨一起玩NET
  • 哪里才是中国最热的火炉城市?

    对于全球国土面积居世界前列、拥有至少五种气候类型(18种细分类型)的中国而言,这个问题确实不好回答。

    华章科技
  • 高效读取大数据文本文件(上亿行数据)

    我是攻城师
  • 挑战一张图,三分钟,调出四季

    一年有四季,每个季节都有不同的风景与韵味,今天就用Lightroom(Lr),使用一张照片,调出四个季节的风格照片,足不出户,看遍四季好风光。

    罗罗攀
  • Filter如何工作

    公众号-JavaEdge

扫码关注云+社区

领取腾讯云代金券