【读书笔记】On Java 8 - 封装、复用、继承与初始化、多态

  • 摘录来源 - On Java 8,如有侵权请联系删除

  • 笔记中只记录关键知识点,详细知识点推荐阅读原书

文章目录

    • 封装
    • 复用
    • 继承与初始化
    • 多态
    • 构造器内部多态方法的行为

封装

  1. 包的概念
    • 包内包含一组类,它们被组织在一个单独的命名空间(namespace)下。
    • 通过 import 导入特定的包或者类。
    • 通过包名限定,为每个类创建一个唯一的标识符组合。
  2. 访问权限修饰符
    • private, protected, public, default
  3. 接口和实现
    • 接口和实现分离有利于解耦。
  4. 类访问权限
    • 每一个编译单元仅能有一个 public 的类,其它的非public 类具有包内可见性。

复用

  1. 代码复用是面向对象编程(OOP)最具魅力的原因之一。
  2. 第一种方式:在新类中创建现有类的对象。这种方式叫做“组合”(Composition),通过这种方式复用代码的功能,而非其形式。
  3. 第二种方式:创建现有类类型的新类。“继承”(Inheritance),编译器会做大部分的工作。继承是面向对象编程(OOP)的重要基础之一。
  4. 构造从基类“向外”进行,因此基类在派生类构造函数能够访问它之前进行初始化。
  5. 使用 super 关键字和适当的参数列表显式地编写对基类构造函数的调用。
  6. Java不直接支持的第三种重用关系称为委托。这介于继承和组合之间,因为你将一个成员对象放在正在构建的类中(比如组合),但同时又在新类中公开来自成员对象的所有方法(比如继承)。
  7. 一个被 staticfinal 同时修饰的属性只会占用一段不能改变的存储空间。
  8. 对于对象引用,final 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供将任意对象设为常量的方法。
  9. 类中所有的 private 方法都隐式地指定为 final。因为不能访问 private 方法,所以不能覆写它。
  10. 如果一个方法是 private 的,它就不是基类接口的一部分。

继承与初始化

// reuse/Beetle.java
// The full process of initialization
class Insect {
    private int i = 9;
    protected int j;

    Insect() {
        System.out.println("i = " + i + ", j = " + j);
        j = 39;
    }

    private static int x1 = printInit("static Insect.x1 initialized");

    static int printInit(String s) {
        System.out.println(s);
        return 47;
    }
}

public class Beetle extends Insect {
    private int k = printInit("Beetle.k.initialized");

    public Beetle() {
        System.out.println("k = " + k);
        System.out.println("j = " + j);
    }

    private static int x2 = printInit("static Beetle.x2 initialized");

    public static void main(String[] args) {
        System.out.println("Beetle constructor");
        Beetle b = new Beetle();
    }
}

输出:

static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39

分析:

  • 当执行 java Beetle,首先会试图访问 Beetle 类的 main() 方法(一个静态方法),加载器启动并找出 Beetle 类的编译代码(在名为 Beetle.class 的文件中)。在加载过程中,编译器注意到有一个基类,于是继续加载基类。不论是否创建了基类的对象,基类都会被加载。(可以尝试把创建基类对象的代码注释掉证明这点。)

  • 如果基类还存在自身的基类,那么第二个基类也将被加载,以此类推。接下来,根基类(例子中根基类是 Insect)的 static 的初始化开始执行,接着是派生类,以此类推。这点很重要,因为派生类中 static 的初始化可能依赖基类成员是否被正确地初始化。

  • 至此,必要的类都加载完毕,可以创建对象了。首先,对象中的所有基本类型变量都被置为默认值,对象引用被设为 null —— 这是通过将对象内存设为二进制零值一举生成的。接着会调用基类的构造器。本例中是自动调用的,但是你也可以使用 super 调用指定的基类构造器(在 Beetle 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。

多态

  • 多态是面向对象编程语言中,继数据抽象和继承之外的第三个重要特性。
  • 多态是消除类型之间的耦合。
  • Java 中除了 staticfinal 方法(private 方法也是隐式的 final)外,其他所有方法都是后期绑定。这意味着通常情况下,我们不需要判断后期绑定是否会发生——它自动发生。
  • Java 中所有方法都是通过后期绑定来实现多态。
  • 只有非 private 方法才能被重写,但是得小心重写 private 方法的现象,编译器不报错,但不会按我们所预期的执行。如果使用了 @Override 注解,就能检测出问题。
  • 如果一个方法是静态(static)的,它的行为就不具有多态性。
  • 在派生类的构造过程中总会调用基类的构造器。初始化会自动按继承层次结构上移,因此每个基类的构造器都会被调用到。
  • 如果在派生类的构造器主体中没有显式地调用基类构造器,编译器就会默默地调用无参构造器。如果没有无参构造器,编译器就会报错。
  • 对象的构造器调用顺序:
    • 基类构造器被调用。这个步骤被递归地重复,这样一来类层次的顶级父类会被最先构造,然后是它的派生类,以此类推,直到最底层的派生类。
    • 按声明顺序初始化成员。
    • 调用派生类构造器的方法体。

构造器内部多态方法的行为

// polymorphism/PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect
class Glyph {
    void draw() {
        System.out.println("Glyph.draw()");
    }

    Glyph() {
        System.out.println("Glyph() before draw()");
        draw();
        System.out.println("Glyph() after draw()");
    }
}

class RoundGlyph extends Glyph {
    private int radius = 1;

    RoundGlyph(int r) {
        radius = r;
        System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
    }

    @Override
    void draw() {
        System.out.println("RoundGlyph.draw(), radius = " + radius);
    }
}

public class PolyConstructors {
    public static void main(String[] args) {
        new RoundGlyph(5);
    }

输出:

Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
  • 如果在构造器中调用了动态绑定方法,就会用到那个方法的重写定义。然而,调用的结果难以预料因为被重写的方法在对象被完全构造出来之前已经被调用。
  • 在构造器内部,整个对象可能只是部分形成——只知道基类对象已经初始化。如果构造器只是构造对象过程中的一个步骤,且构造的对象所属的类是从构造器所属的类派生出的,那么派生部分在当前构造器被调用时还没有初始化。然而,一个动态绑定的方法调用向外深入到继承层次结构中,它可以调用派生类的方法。如果你在构造器中这么做,就可能调用一个方法,该方法操纵的成员可能还没有初始化——这肯定会带来灾难。
  • 编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在基类的构造器中能安全调用的只有基类的 final 方法(这也适用于可被看作是 finalprivate 方法)。这些方法不能被重写,因此不会产生意想不到的结果。
  • RTTI(Run Time Type Identification)即通过运行时类型识别,程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型。
  • Java 系列笔记 (1) - Java 类加载与初始化
  • Java RTTI和反射机制

你可能感兴趣的:(java基础,on,Java,8,java,多态,编程语言)