七、复用类

1.组合语法

  在新的类中产生现有类的对象。由于新的类是由现有类的对象所组成,所以这种方法叫做组合。

  类中域为基本类型时能够自动被初始化为零。对象引用被初始化为null。

  编译器不是简单地为每一个引用都创建默认对象,如果想初始化这些引用,可以在代码中的下列位置进行:

  1.在定义对象的地方。这意味着它们总是能够在构造器被调用之前被初始化。

  2.在类的构造器中。

  3.就在正要使用这些对象之前,这种方式称为惰性初始化。

  4.使用实例初始化。

2.继承语法

  当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。

  为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法都指定为public

  Java用super关键字表示超类的意思,当前类就是从超类继承来的。

  ①初始化基类

  从外部看,导出类就像是一个与基类具有相同接口的新类,或许还会有一些额外的方法和域。但继承并不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。二者区别在于,后者来自于外部,而基类的子对象被包装在导出类的内部。

  对基类子对象的正确初始化也是至关重要的,而且仅有一种方法来保证这一点:构造器中调用基类构造器来执行初始化。Java会自动在导出类的构造器中插入对基类构造器的调用。

  但是,如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显式地编写调用基类构造器的语句,并且配以适当的参数列表。

  调用基类构造器必须是你在导出类构造器中要做的第一件事。

3.代理

  第三种关系称为代理,Java并没有提供对它的直接支持。这是继承和组合的中庸之道,因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。例如,太空船需要一个控制模块:

  1. public class SpaceShipControls {
  2. void up(int velocity) {}
  3. void down(int velocity) {}
  4. void left(int velocity) {}
  5. void right(int velocity) {}
  6. }

  构造太空船的一种方式是使用继承:

  1. public class SpaceShip extends SpaceShipControls {
  2. private String name;
  3. public SpaceShip(String name) {
  4. this.name = name;
  5. }
  6. public static void main(String[] args) {
  7. SpaceShip protector = new SpaceShip("NASA");
  8. protector.up();
  9. }
  10. }

  然而,SpaceShip并非真正的SpaceShipControls类型。更准确地说,SpaceShip包含了SpaceShipControls,与此同时,SpaceShipControls的所有方法在SpaceShip中都暴露了出来。代理解决了此难题:

  1. public class SpaceShipDelegation {
  2. private String name;
  3. private SpaceShipControls controls =
  4. new SpaceShipControls();
  5. public SpaceShipDelegation(String name) {
  6. this.name = name;
  7. }
  8. // Delegation methods
  9. public void back(int velocity) {
  10. controls.back(velocity);
  11. }
  12.  
  13. public void up(int velocity) {
  14. controls.up(velocity);
  15. }
  16. ...
  17.  
  18. public static void main(String[] args) {
  19. SpaceShipDelegation protector =
  20. new SpaceShipDelegation("NASA");
  21. protector.up();
  22. }
  23. }

4.结合使用组合和继承 

  名称屏蔽

  如果Java的基类拥有某个已被多次重载的方法的名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本。因此,无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作。

  Java SE5新增加了@Override注解,它并不是关键字,但是可以把它当作关键字使用。当你想要覆写某个方法时,可以选择添加这个注解,在你不留心重载而并非覆写了该方法时,编译器就会生成一条错误消息。

5.在组合与继承之间选择 

  组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。即,在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口。

  在继承的时候,使用某个现有类,并开发一个它的特殊版本。通常,这意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。

  思考一下,用一个“交通工具”对象来构成一部“车子”是毫无意义的,因为“车子”并不包含“交通工具”,它仅是一种交通工具(is-a关系)。“is-a"(是一个)关系是用继承来表达的,而“has-a”(有一个)的关系是用组合来表达的。

6.protected关键字

  protected,它指明“就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的。”

  尽管可以创建protected域,但是最好的方式还是将域保持为private;你应当一直保留“更改底层实现”的权利。然后通过protected方法来控制类的继承者的访问权限。

7.向上转型

   “向新的类提供方法”并不是继承技术中最重要的方面,其最重要的方面是用来表现新类和基类之间的关系。这种关系可以用“新类是现有类的一种类型”这句话加以概括。

  由于继承可以确保基类中所有的方法导出类中也同样有效,所以能够向基类发送的所以信息同样也可以向导出类发送。将导出类引用转换为基类引用的动作,我们称之为向上转型。

  由于向上转型是从一个较专用类型向较通用类型转换,所以总是很安全的。在向上转型的过程中,类接口唯一可能发生的事情是丢失方法。

  到底是该用组合还是继承,一个最清晰的判断方法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的。

8.final关键字 

  ①final数据

  许多编程语言都有某种方法,来向编译器告知一块数据是恒定不变的。有时数据的恒定不变是很有用的,例如:

    1.一个永不改变的编译时常量

    2.一个在运行时被初始化的值,而你不希望它被改变。

  在Java中,编译时常量必须时基本数据类型,并且以关键字final表示。在对这个常量进行定义的时候,必须对其进行赋值。

  一个既是static又是final的域只占据一段不能改变的存储空间。根据惯例,既是static又是final的域将用大写表示,并使用下划线分隔各个单词。

  对于对象引用,final使其引用恒定不变。然而对象本身却是可以改变的,Java并未提供使任何对象恒定不变的途径。

  不能因为某数据是final的就认为在编译时可以知道它的值。

  Java允许生成“空白final”,所谓空白final是指被声明为final但又未给定初值的域。无论什么情况,编译器都确保空白final在使用前都必须被初始化。但是,空白final在关键字final的使用上提供了更大的灵活性,为此,一个类中的final域就可以做到根据对象而有所不同,却又保持恒定不变的特性。

  Java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法在方法中更改参数引用所指向的对象。这一特性主要用来向匿名内部类传递数据。

  ②final方法

  使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义。这是出于设计的考虑:想要确保在继承中使方法行为保持不变,并且不会被覆盖。过去建议使用final方法的第二个原因是效率。在Java的早期实现中,如果将一个方法指明为final,就是同意编译器将针对该方法的所有调都转为内嵌调用。如今不需要用final来进行效率优化了。

  类中的所有的private方法都隐式地指定为是final的。

  “覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类接口的一部分。它仅是一些隐藏于类中的程序代码,只不过时具有相同的名称而已。但如果在导出类中以相同的名称生成一个publicprotected或包访问权限方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时你并没有覆盖该方法,仅是生成了一个新的方法。由于private方法无法触及而且能有效隐藏,所有除了把它所归属的类的组织结构的原因而存在外,其他任何事物都不需要考虑到它。

  ③final类

  当将某个类的整体定义为final时,就表明了你不打算继承该类,而且也不允许别人这样做。

  由于final类禁止继承,所以final类中所有的方法都隐式指定为是final的,因为无法覆盖它们。

9.初始化及类的加载 

  Java中所有事物都是对象。每个类的编译代码都存在于它自己的独立的文件中,该文件只在需要使用程序代码时才会被加载。一般来说,可以说:“类的代码在初次使用时才加载。”这通常是指加载发生于创建类的第一个对象之时,但是当访问static域或static方法时,也会发生加载。(构造器也是static方法。因此更准确地说,类是在任何static成员被访问时加载的。)

  继承与初始化

  了解包括继承在内的初始化全过程,以对所发生的一切有个全局的把握,是很有益的。看下例:

  1. class Insect {
  2. private int i = ;
  3. protected int j;
  4. Insect() {
  5. System.out.println("i = "+i+", j = "+j);
  6. j=;
  7. }
  8. private static int x1 = printInit("static Insect.x1 initialized");
  9. static int printInit(String s) {
  10. System.out.println(s);
  11. return ;
  12. }
  13. }
  14.  
  15. public class Beetle extends Insect {
  16. private int k = printInit("Beetle.k initialized");
  17. public Beetle() {
  18. System.out.println("k = "+k);
  19. System.out.println("j = "+j);
  20. }
  21.  
  22. private static int x2 = printInit("static Beetle.x2 initialized");
  23. public static void main(String[] args) {
  24. System.out.println("Beetle constrictor");
  25. Beetle b = new Beetle();
  26. }
  27. }
  28.  
  29. /**Output
  30. static Insect.x1 initialized
  31. static Beetle.x2 initialized
  32. Beetle constrictor
  33. i = 9, j = 0
  34. Beetle.k initialized
  35. k = 47
  36. j = 39
  37. */

  在Java上运行Beetle时,第一件事就是试图访问Bettle.main()(一个static方法),于是加载器开始启动并找出Beetle类的编译代码(在名为Beetle.class的文件中)。在加载过程中,编译器注意到它有一个基类(由extends),于是它继续进行加载。不管是否打算创建一个该基类的对象,这都要发生。

  如果该基类还有自身的基类,那么第二个基类就会被加载,如此类推。接下来,根基类中的static初始化(此例中是Insect)即会被执行,然后是下一个导出类,以此类推。这种方式很重要,因为导出类的static初始化可能会依赖于基类成员能否被正确初始化。

  至此为止,必要的类都已加载完毕,对象就可以创建了。首先,对象中的所有基本类型都会被设为默认值、对象引用被设为null——这是通过将对象内存设为二进制零值而一举生成的。然后,基类的构造器会被调用。在基类构造器完成以后,实例变量按其次序被初始化。最后,构造器的其余部分将执行。

10.总结

  继承和组合都能从现有类型生成新类型。组合一般是将现有类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口。

  尽管面向对象编程对继承极力强调,但在开始一个设计时,一般应优先选择使用组合(或者可能是代理),只在确实必要时才使用继承。

  

  

Java编程思想 学习笔记7的更多相关文章

  1. [Java编程思想-学习笔记]第3章 操作符

    3.1  更简单的打印语句 学习编程语言的通许遇到的第一个程序无非打印"Hello, world"了,然而在Java中要写成 System.out.println("He ...

  2. Java编程思想 学习笔记1

    一.对象导论 1.抽象过程 Alan Kay曾经总结了第一个成功的面向对象语言.同时也是Java所基于的语言之一的Smalltalk的五个基本特性,这些特性表现了纯粹的面向对象程序设计方式 1)万物皆 ...

  3. [Java编程思想-学习笔记]第1章 对象导论

    1.1  抽象过程 Java是一门面向对象的语言,它的一个优点在于只针对待解问题抽象,而不用为具体的计算机结构而烦心,这使得Java有完美的移植性,也即Java的口号"Write Once, ...

  4. Java编程思想 学习笔记11

    十一.持有对象  通常,程序总是根据运行时才知道的某些条件去创建新对象.在此之前,不会知道所需对象的数量,甚至不知道确切的类型. Java实用库还提供了一套相当完整的容器类来解决这个问题,其中基本的类 ...

  5. Java编程思想学习笔记——类型信息

    前言 运行时类型信息(RTTI:Runtime Type Information)使得我们可以在程序运行时发现和使用类型信息. Java在运行时识别对象和类的信息的方式: (1)一种是RTTI,它假定 ...

  6. Java编程思想 学习笔记12

    十二.通过异常处理错误  Java的基本理念是“结构不佳的代码不能运行”. Java中的异常处理的目的在于通过使用少于目前数量的代码来简化大型.可靠的程序的生成,并且通过这种方式可以使你更加自信:你的 ...

  7. Java编程思想 学习笔记10

    十.内部类  可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性.然而必须要了解,内部类和组合是 ...

  8. Java编程思想 学习笔记5

    五.初始化与清理 1.用构造器确保初始化  在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化.创建对象时,如果其类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造 ...

  9. Java编程思想 学习笔记4

    四.控制执行流程 1.true和false 所有条件语句都利用条件表达式的真或假来决定执行路径.注意Java不允许我们将一个数字作为布尔值使用. 2.if-else 3.迭代 while.do-whi ...

随机推荐

  1. Activiti启动某个流程失败,页面报500

    现象:Activiti启动某个流程失败,页面报500,错误日志如下. 2017-06-19 10:50:09 [org.activiti.engine.impl.interceptor.Command ...

  2. 项目引入android-support-v7-appcompat遇到的问题,no resource found that matches the given name 'android:Theme.AppCompat.Light'

    一.问题 今天准备使用v7包中的ToolBar来用,但是在styles.xml中引入Theme.AppCompat.Light的时候,报错“no resource found that matches ...

  3. BZOJ3626 LNOI2014LCA(树链剖分+主席树)

    开店简化版. #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> ...

  4. python中json.load()、json.loads()、json.dump()、json.dumps()的区别

    json.load()从文件中读取json字符串 json.loads()将json字符串转换为字典类型 json.dumps()将python中的字典类型转换为字符串类型 json.dump()将j ...

  5. day30 小面试题 去重 (考核 __eq__ 以及 __hash__ )

    # 小面试题,要求将一个类的多个对象进行去重 # 使用set方法去重,但是无法实现,因为set 需要依赖eq以及hash, # hash 哈希的是内存地址, 必然不一样 # eq 比较的也是内存地址, ...

  6. 【POI每日题解 #6】KRA-The Disks

    题目链接 : [POI2006]KRA-The Disks 好有既视感啊... 注意一下输入输出 输入是从上到下输入箱子的宽度 输出是最上面的积木停在哪一层 即 箱子高度 - 积木高度 + 1 在初始 ...

  7. BZOJ 2434 阿狸的打字机 | AC自动机

    题目戳这里 AC自动机上有神奇的东西叫做fail指针--所有fail指针连起来恰好构成一棵以1为根的树! 而这道题问x在y中出现过多少次,就是问Trie树上根到y的结束节点的路径上有多少节点能通过跳f ...

  8. shell(3)-mysql主从监控shell

    需要先明白数据库主从同步正常的标准是来查看两个线程Slave_IO和Slave_SQL两个线程的工作状态: #!/bin/bash #Check MySQL Slave's Runnning Stat ...

  9. py3+urllib+re,轻轻松松爬取双色球最近100期中奖号码

    通过页面源码,发现使用正则表达式可以很方便的获取到我们需要的数据,最后循环写入txt文件. (\d{2})表示两位数字 [\s\S]表示匹配包括“\r\n”在内的任何字符,匹配红球和蓝球之间的内容 具 ...

  10. 牛客练习赛40 C 小A与欧拉路(树的直径)

    链接:https://ac.nowcoder.com/acm/contest/369/C 题目描述 小A给你了一棵树,对于这棵树上的每一条边,你都可以将它复制任意(可以为0)次(即在这条边连接的两个点 ...