[学习笔记][Java编程思想]第7章:复用类

来源:互联网 发布:焦大seo自媒体 编辑:程序博客网 时间:2024/06/03 02:26
  • 使用类而不破坏现有程序代码。
    • 在新的类中产生现有类的对象,由于新的类是由现有的类的对象所组成,所以这种方法称为组合。
    • 按照现有类的类型来创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新代码,这种方式称为继承。

1 组合语法

  • 组合只需将对象引用置于新类中即可。
  • 每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法会被调用。
  • 编译器并不是简单地为每一个引用都创建默认对象。
    • 在定义对象的地方,能够在构造器被调用之前初始化。
    • 在类的构造器中。
    • 在正在使用这些对象之前,称为惰性初始化。
    • 实例初始化。

2 继承语法

  • 除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。
  • 继承过程中,需要先声明“新类与旧类类似”,通过在类主体的左边花括号之前,书写后面紧随基类名称的关键字extends而实现的,会自动得到基类中所有的域和方法。
  • 在每个类中都设置一个main()方法的技术可使每个类的单元测试都变得简单易行。
  • 即时是一个程序中含有多个类,也只有命令行所调用的那个类的main()方法会被调用。
  • 为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public。
  • 可以将继承视作是类的复用。
  • 使用基类中定义的方法对它进行修改是可行的。Java用super关键字表示超类的意思,当前类就是从超类继承来的。
  • 在继承的过程中,并不一定非得使用基类的方法。可以在导出类中添加新方法,其添加方式与在类中添加任意方法一样。

2.1 初始化基类

  • 继承并不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。
  • 在构造器中调用基类构造器来执行初始化,Java会自动在导出类的构造器中插入对基类构造器的调用。
  • 构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。
  • 初始化顺序:基类静态字段→导出类静态字段→基类域初始化→基类构造器→导出类域初始化→导出类构造器。
  • 如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显示地编写调用基类构造器的语句,并且配以适当的参数列表。
  • 调用基类构造器必须是你在导出类构造器中要做的第一件事。

3 代理

  • 代理是继承与组合之间的中庸之道,Java并没有提供对它的直接支持。
  • 代理将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。
  • 使用代理可以选择只提供在成员对象中的方法的某个子集。

4 结合使用组合和继承

  • 虽然编译器强制你去初始化基类,并且要求你要在构造器起始处就要这么做,但是它并不监督你必须将对象成员也初始化。

4.1 确保正确清理

  • 执行类的所有特定的清理动作,其顺序同生成顺序相反(通常这就要求基类元素仍旧存活)。
  • 除了内存以外,不能依赖垃圾回收器去做任何事。
  • 如果需要进行清理,最好是编写你自己的清理方法,但不要使用finalize();

4.2 名称屏蔽

  • 如果Java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本。
  • 使用与基类完全相同的特征签名及返回类型来覆盖具有相同名称的方法,是一件极其平常的事。
  • @Override注解,在重载而并非重写了该方法时,编译器就会生成一条错误消息,可以防止你在不想重载时而意外地进行了重载。

5 在组合与继承之间选择

  • 组合和继承都允许在新的类中放置子对象,组合是显式地这样做,而继承则是隐式地做。
  • 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。即在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为了新类所定义的接口,而非所嵌入对象的接口。
  • 在继承的时候,使用某个现有类,并开发一个它的特殊版本。
  • “is-a”(是一个)的关系是用继承来表达的,而“has-a”(有一个)的关系则是用组合来表达的。

6 protected关键字

  • 关键字protected,指明“就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的。”(protected也提供了包内访问权限。)
  • 尽管可以创建protected域,但是最好的方式还是将域保持为private。
  • 应当一直保留“更改底层实现”的权利,然后通过protected方法控制类的继承者的访问权限。

7 向上转型

  • “为新的类提供方法”并不是继承技术最重要的方面,其最重要的方面是用来表现新类和基类之间的关系。
  • 能够向基类发送的所有信息同样也可以向导出类发送。
  • 将导出类引用转换为基类引用的动作,称为向上转型。

7.1 为什么称为向上转型

  • 传统的类继承图的绘制方法:将根置于页面的顶端,然后逐渐向下。由导出类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。
  • 导出类是基类的一个超集,在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法。

7.2 再论组合与继承

  • 在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类中,并使用该类的对象。
  • 尽管在教授OOP的过程中我们多次强调继承,但这并不意味着要尽可能使用它。
  • 继承的使用场合仅限于你确信使用该技术确实有效的情况,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型。

8 final关键字

  • 关键字final,通常指的是“这是无法改变的”、
  • 不想做改变可能出于两种理由:设计或效率。

8.1 final数据

  • 常量
    • 一个永不改变的编译时常量。
    • 一个在运行时被初始化的值,而你不希望它被改变。
  • Java编译期常量必须是基本数据类型,以关键字final表示。在对这个变量进行定义时,必须进行赋值。
  • 一个既是static又是final的域只占据一段不能改变的存储空间。
  • 对于基本类型,final使数值恒定不变;而用于对象引用,final使引用恒定不变。
  • Java并未提供使任何对象(包括数组)恒定不变的途径。
  • 带有恒定初始值(编译期常量)的final static基本类型全用大写字母命名,并且字与字之间用下划线隔开。
  • 编译器对编译时数值一视同仁。
  • 空白final是指被声明为final但又未给定初值的域。
  • 必须在域的定义处或者每个构造器中用表达式对final进行赋值。
  • Java允许在参数列表中以声明的方式将参数指明为final。这一特性主要用来向匿名内部类传递数据。

8.2 final方法

  • final把方法锁定,以防任何继承类修改它的含义。
  • 类中所有的private方法都隐式地指定为是final的。
  • “覆盖”只有在某方法是基类的接口的一部分时才会出现。

8.3 final类

  • final类的域可以根据个人的意愿选择为是或不是final。
  • final类禁止继承,所以final类中所有的方法都隐式指定为final。

8.4 有关final的忠告

  • 要预见类是如何被复用的一般是很困难的,特别是对于一个通用类而言更是如此。

9 初始化及类的加载

  • 每个类的编译代码都存在于它自己的独立文件中。该文件只在需要使用程序代买时才会被加载。
  • 类是在其任何static成员被访问时加载的。

9.1 继承与初始化

  • 导出类的static初始化可能会依赖于基类成员能否被正确初始化。

10 总结

  • 一般应优先选择使用组合(或者可能是代理),只在确实必要时才使用继承。

疑惑和总结

1 一个类继承外部包的类,类内部可以访问基类的包访问权限成员吗?

  • 不能。

2 protected或者包访问权限,在包外调用可以实现多态吗?

  • 不能,在包外的基类对象,protected和包访问权限接口不可视。
原创粉丝点击