第七章:复用类
1.复用代码的两种方法
- 组合:在新的类中产生现有类的对象。
- 继承:按照现有类的类型来创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新的代码。
2.初始化基类
- 基类在导出类构造器可以访问它之前,就已经完成了初始化。(先初始化父类,再初始化子类)
- 类中含有默认的构造器,即构造器都不带参数,编译器可以轻松地调用它们是因为不必要考虑传递什么参数。但是,如果没有默认的基类构造器,或想调用一个带参数的基类构造器,就必须用关键字super显式地编写调用基类构造器的语句,并配以适当的参数列表。
- 调用基类构造器是在导出类构造器中要做的第一件事。
3.结合使用组合和继承
3.1 确保正确清理
- 有时类可能要在其生命周期内执行一些必须的清理活动,需要显式地编写一个特殊的方法来做这件事,并要确保客户端程序员知晓他们必须要调用这个方法。其首要任务就是必须将这一清理动作置于
finally
子句中,以预防异常的出现。
3.2 名称屏蔽
- 如果Java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽在基类中的任何版本。无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作。
- 当想覆写某个方法时,可添加
@Override
注解。
4.组合和继承之间选择
组合和继承都允许在新的类中放置子对象,组合是显式地这样做,继承是隐式地做。
组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。
继承表达is-a
关系,组合表达has-a
关系。
5.protected关键字
protected对任何继承于此类的导出类或其他任何位于同一个包内的类来说,都是可以访问的。
将域保持为private
,然后通过protected
方法来控制类的继承者的访问权限。
6.向上转型
向上转型是从一个较专用类型向较通用类型转换,所以总是很安全,也就是说,导出类是基类的一个超集。它可能比基类含有更多的方法,但它必须至少具备基类中所含有的方法,在向上转型的过程中,类接口唯一可能发生的事情就是丢失方法。考虑是否需要从新类向基类进行向上转型,如果必须向上转型,则继承是必要的。
7.final关键字
7.1 final数据
- Java允许在参数列表中以声明的方式将参数指明为final,这意味着无法在方法中更改参数引用所指向的对象。
- 一个既是static又是final的域只占据一段不能改变的存储空间(即编译期常量),将用大写表示,并使用下划线分隔单词。
- 对于基本类型,final使数值恒定不变;对于引用,final使引用恒定不变,即无法再指向另一个对象,但对象本身是可以被修改的。
- Java允许生成空白final,但必须在域的定义处或每个构造器中对空白final赋值。
7.2 final方法
- 把方法锁定,以防任何继承类修改它的含义,即该方法不会被继承的类覆盖。
- 效率,若一个方法指明为final,那么就同意编译器将针对该方法的所有调用转为内嵌调用。
- 类中所有的private方法都隐式地指定为final,由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但这并不会给该方法带来任何额外的意义。
- 若父类中某个方法被声明为final或者private,那么这个方法对子类来说是不可见的,就算在子类中创建了与父类一模一样的方法,这也是一个新的方法,而不是从父类中覆盖的方法。
7.3 final类
- 该类不能被继承,不需要任何子类,例如String类。
8.继承中的初始化顺序
父类的静态变量–>父类的静态代码块–>子类的静态变量–>子类的静态代码块–>父类的非静态变量(父类的非静态代码块)–>父类的构造函数–>子类的非静态变量(子类的非静态代码块)–>子类的构造函数
第八章:多态
多态通过分离做什么
和怎么做
,从另一个角度将接口和实现分离开来。**多态的作用是消除类型之间的耦合关系。**多态也称为动态绑定、后期绑定或运行时绑定。
多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是从同一基类导出而来的。这种区别是根据方法行为的不同而表示出来的,虽然这些方法都可以通过同一个基类来调用。
1.转机
1.1 方法调用绑定
- 将方法调 用同方法主体关联起来被称作绑定。
- 在程序执行前进行绑定,叫做前期绑定。如C语言中只有一种方法调用,那就是前期绑定。
- 在运行时根据对象的类型进行绑定,叫做后期绑定,也叫动态绑定或运行时绑定。如果一种语言想实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的类型,但是
方法调用机制
能找到正确的方法体,并加以调用。 - 即在编译时(在编译期,只是确保调用方法的存在,并对调用参数和返回值执行类型检查),方法调用和方法体没有关联起来,只要到了运行时,根据引用所指向的对象类型,将方法调用和方法体关联起来。
- Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。
- 将一个方法声明为final可有效关闭动态绑定。
1.2 产生正确的行为
导出类通过覆盖基类的公用接口,来为每种特殊类型的几何形状提供单独的行为。
1.3 缺陷:"覆盖"私有方法
**private方法被自动认为是final方法,而且对导出类是屏蔽的。**只有非private方法才可以被覆盖,但是还需要密切覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行。确切地说,在导出类中,对于基类中的private方法,最好采用不同的名字。
1.4 缺陷:域和静态方法
只有普通的方法调用可以是多态的。如果某个方法是静态的,它的行为就不具有多态性。静态方法与类相关联,而不是与单一的类的对象相关联。
2.向上转型
把某个对象引用视为对其基类型的引用的做法成为向上转型
。
3.构造器与多态
构造器默认是static方法
3.1 构造器的调用顺序
- 调用基类构造器
- 按声明顺序调用成员的初始化方法
- 调用导出类构造器的主体
3.2 构造器内部的多态方法的行为
初始化的实际过程:
- 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制零
- 调用基类构造器
- 按照声明的顺序调用成员的初始化方法
- 调用导出类的构造器主体
4.向下转型
在Java语言中,所有转型都会得到检查。在进入运行期时仍然会对其进行检查,以便保证它的确是我们希望的那种类型。如果不是就会返回一个 ClassCastException
。这种在运行期间对类型进行检查的行为称作“运行时类型识别”(RTTI)。
第九章:接口
接口和内部类为我们提供一种接口与实现分离的更加结构化的方法。
抽象类是普通的类与接口之间的一种中庸之道。
1.抽象类和抽象方法
- Java提供一种
抽象方法
机制,这种方法是不完整的,仅有声明而没有方法体 - 包含抽象方法的类叫做
抽象类
。如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。(否则编译器会报错) - 如果从一个抽象类继承,并想创建该类的对象,那么就必须为基类的所有抽象方法提供方法定义。如果不这样做(可以选择不做),那么导出类也是抽象的,却编译器将会强制我们用
abstract
关键字来限定这个类。 - 如果一个类,让其包含任何abstract方法都显得没有实际意义,而且我们也想要阻止产生这个类的任何对象,那么这个类可以是
一个没有任何抽象方法的抽象类
。
2.接口
接口被用来建立类与类之间的协议。
interface
关键字产生一个完全抽象的类,允许创建者确定方法名,参数列表和返回类型,但是不提供任何的具体实现。- 接口是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继变种的特性。
- 接口的域都是隐式地
static
和final
。 - 接口中的方法默认为
public
。
3.Java中的多重继承
Java 中可以组合多个接口,被称为多重继承。
在导出类中,不强制要求必须有一个是抽象的或者是“具体的”基类。继承类只能一次,但是实现接口可以有若干个。实现的多个接口,都可以向上转型为每个接口(因为每个接口都是一个独立类型)。
- 使用接口的核心原因:
- 为了能够向上转型为多个基类型
- 防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
- 事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使它成为一个接口。
4.通过继承来拓展接口
- 通过继承,可以很容易地在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口。
- 在打算组合的不同接口中使用相同的方法名通常会造成
代码可读性
的混乱。
5.接口中的域
- 接口中的任何域都是static和final的,因此接口成为了一种很便捷的用来创建常量组的工具。
- 接口中定义的域不能是
空final
,但是可以被非常量表达式初始化。 - 域是static的,它们就可以在类第一次被加载时初始化,这发生在任何域首次被访问时。这些域不是接口的一部分,它们的值被存储在该接口的静态存储区域内。
6.嵌套接口
接口也可以实现为private的,嵌套在类里面的private接口,只能被类自身所使用。实现一个private接口只是一种方式,它可以强制该接口中的方法定义不要添加任何类型信息,也就是说,不允许向上转型。
第十章:内部类
1.内部类与外部类
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体指明这个对象的类型:OuterClassName.InnerClassName
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。内部类还拥有其外围类的所有元素的访问权。
当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用,然后,在访问外围类的成员时,就是用那个引用来选择外围类的成员,编译器会帮我们处理所有的细节。即构建内部类对象时,需要一个指向外围类对象的引用,如果编译器访问不到这个引用就会报错。
在内部类中,外部类名+.this是生成外部类对象的引用。这一点会在编译器受检,没有任何运行时开销。
public class DotThis{
void f(){
System.out.println("DotThis.f()"); }
public class Inner{
public DotThis outer(){
return Dot