第七章 复用类
初始化这些引用,可以在代码中的下列位置进行:1、在定义对象的地方。这意味着它们总是能够在构造器被调用之前被初始化。2、在类的构造器中。3、就在正要使用这些对象之前。这种方式成为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下。
7.2继承类 129页
在继承过程中,需要先声明“新类与旧类相似”。这种声明是通过类的主体的左边花括号之前,书写后面紧随基类名称的关键字extends而实现的。当这么做时,会自动得到基类中所有的域和方法。
为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public。在此例中,你可能想要在新版本中调用从基类继承而来的方法。但是在scrub()中,并不能直接调用scrub(),因为这样做将会产生递归,而这并不是你所期望的。为解决此问题,java用super关键字表示超类的意思,当前类就是从超类继承来的。为此,表达式super.scrub()将调用基类版本的scrub()方法。
super是用在子类中,目的是访问直接父类中被屏蔽的成员,注意是直接父类(就是类之上最近的超类)
7.2.1 初始化基类
从外部来看,它就像是一个与基类具有相同接口的新类,或许还会有一些额外的方法和域。但继承并不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。二者区别在于,后者来自于外部,而基类的子对象被包装在导出类对象内部(因为构造方法中的super)。
对基类的子对象的正确初始化也是至关重要的,而且也仅有一种方法来保证这一点:在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。
带参数的构造器
上例中各个类均含有默认的构造器,即这些构造器都不带参数。编译器可以轻松调用它们是因为不必考虑要传递什么样的参数问题。但是,如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显式地编写调用基类构造器的语句。
调用基类构造器必须是你在导出类构造器中要做的第一件事。
代理
第三种关系成为代理。Java并没有提供对它的直接支持。这是继承与组合之间的中庸之道。因为我们将一个成员对象置于锁妖构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。
7.4.2 名称屏蔽
如果java的基类拥有某个已多次被重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在类中的任何版本。因此,无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作。
7.5 在组合与继承之间的选择
组合和继承都允许在新的类中放置子对象,组合是显式地这样做,而继承则是隐式地做。
组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形,即在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户所看到的只是为新类所定义的接口,而非所嵌入对象的接口。
有时,允许类的用户直接访问新类中的组合成分是极具意义的;也就是说,将成员对象声明为public。如果成员对象自身都隐藏了具体实现,那么这种做法是安全的。
is-a是一个的关系是用继承来表达的。has-a有一个的关系则是用组合来表达的。
7.6 protected关键字
关键字
protected就是起这个作用的。它指明“就类用户而言,这是private,但对于任何继承于此类的导出类或其他任何位于同一包内的类来说,它却是可以访问的。”
7.7向上转型
7.7.1为什么称向上转型
在向上转型的过程中,类接口唯一可能发生的事情就是丢失方法,而不是获取它们。
7.8 final关键字
根据上下文环境,java的关键字final的含义存在着细微的区别,但通常它指的是“这是无法改变的。”
7.8.1final 数据
在编译期常量这种情况,编译器可以将该常量带入任何可能用到它的计算式中。在java中,这类常量必须是基本数据类型,并且以关键字fianl表示。在对这个常量进行定义的时候,必须对其进行赋值。
一个既是static又是fianl的域只占据一段不能改变的存储空间。
对于基本类型,final使数值恒定不变,而用于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。
带有恒定初始值(即编译器常量)的fianl static基本类型全用大写字母命名,并且字与字之间用下划线隔开。
空白final
java允许生成“空白final”,所谓空白final是指被声明为final但又未给定初值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。
final参数
java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法在方法中更改参数引用所指向的对象:
当基本类型的参数被指明为final时所出现的结果:你可以读参数,但却无法修改参数,这一特性,主要用来向匿名内部类传递数据。
7.8.2 final方法
7.8.3 final 类
当将某个类的整体定义为final时,就表明了你不打算继承该类,而且也不允许别人这样做,换句话说,出于某种考虑,你对该类的设计永不需要做任何变动。
7.9 初始化及类的加载
类的代码在初次使用时才加载。这通常要指加载发生于创建类的第一个对象之时。但是当访问static域或static方法时,也会发生加载。
第8章 多态
在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。
多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。封装通过合并特性和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”将接口和实现分离开来。多态的作用则是消除类型之间的耦合关系。继承允许将对象视为它自己本身的类型或基类型来处理。
8.1
从导出类向上转型到基类可能会”缩小“接口,但不会比基类的全部接口更窄。
8.1.1
如果我们只写这样一个简单方法,它仅接受基类作为参数,而不是那些特殊的导出类,这样做情况会变得更好吗?也就是说,如果我们不管导出类的存在,编写的代码只是与基类打交道,会不会更好呢?
如果我们只写这样一个简单方法,它仅接受基类作为参数,而不是那些特殊的导出类。
8.2转机
8.2.1方法调用绑定
将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定(如果有的话,由编译器和连接器程序实现),叫做前期绑定。
后期绑定,它的含义就是运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行时时绑定。如果一种语言想实现后期绑定,就必须有某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法。
8.2.2产生正确的行为
shape基类为自它那里继承而来的所有导出类建立了一个共用接口——也就是说,所有形状都可以描绘和擦除。导出类通过覆盖这些定义,来为每种特殊类型的几何形状提供单独的行为。
8.2.3可扩展性
多态是一项让程序员”将改变的事务与未变的事务分离开来“的重要技术。
8.2.4缺陷"覆盖"私有方法
只有非private 方法才可以被覆盖,但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是不会按照我们所期望的来执行,确切的说,在导出类中,对于基类中的private方法,最好采用不同的名字。
8.2.5缺陷:域与静态方法
静态方法是与类,而非单个的对象相关联的。
8.3构造器和多态
8.3.1构造器的调用顺序
构造器具有一项特殊任务;检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类的成员(基类成员通常是private)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不可能正确构造完整对象。
一个复杂对象调用构造器要遵照下面的顺序:
1)调用基类构造器。2)按声明顺序调用成员的初始化方法 3)调用导出类构造器的主体。
初始化的实际过程是:
1)在其他任何事物发生之前,将分配对象的存储控件初始化成二进制的零
2)如前所述那样调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用)
3)按照声明的顺序调用成员的初始化方法。
4)调用导出类的构造器主体。
8.5 用继承进行设计
用继承表达行为间的差异,并用字段表达状态上的变化。
8.5.2向下转型与运行时类型识别
第9章 接口
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
在这些示例中,建立这个通用接口的唯一理由是,不同的子类可以用不同的方式表示此接口。通用接口建立起一种基本形式,以此表示所有导出类的共同部分。
java提供一个叫做抽象方法的机制,这种方法是不完整的;仅有声明而没有方法体。
包含抽象方法的类叫做抽象类。如果一个类包含一个多多个抽象方法,该类被限定为抽象的。
9.2接口
Interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口只提供了形式,而未提供任何具体实现。
一个接口表示:所有实现了该特定接口的类看起来都像这样。因此,任何使用某特定接口的代码都知道可以用该接口的哪些方法,而且仅需知道这些。因此,接口被用来建立类与类之间的协议。
接口也可以包含域,但这些域隐式地是static和final的。在接口中的每一个方法确实都只是一个声明,是编译器所允许的在接口中唯一能够存在的事物。
9.3完全解耦
只要一个方法操作的是类而非接口,那么你就只能使用这个类及其子类。创建一个能够根据所传递的参数不同而具有不同行为的方法,被成为策略设计模式。
9.4 java中的多重继承
接口是根本没有任何具体实现的,也就是说,没有任何与接口相关的存储,因此,也就无法阻止多个接口的组合。
使用接口的核心原因:为了能够向上转型为多个类型。第二个原因,防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
9.5适配接口
接口最吸引人的原因之一就是允许同一接口具有多个不同的具体实现。我们可以在任何现有类之上添加新的接口,所以这意味着让方法接受接口类型,是一种让任何类都可以对该方法进行适配的方式。这就是使用接口而不是类的强大之处。
9.7 初始化接口中的域
9.8 嵌套的接口
实现一个private接口只是一种方式,它可以强制该接口的方法定义不要添加任何类型信息。也就是说,不允许向上转型。
9.9接口与工厂
接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式。这与直接构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离。
总结
任何抽象性都应该是应真正的需求而产生的。当必需时,你应该重构接口而不是导出添加额外级别的间接性,并由此带来的额外的复杂性。
恰当的原则应该是优先选择类而不是接口。从类开始,如果接口的必须性变得非常明确了,那么就进行重构。
第10章 内部类
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
10.1 创建内部类
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在main()方法中那样,具体地指明这个对象的类型:OuterClassName.InnerClassName。
10.2链接到外部类
内部类还拥有其外围类的所有元素的访问权。
当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。
10.3使用.this与.new
如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this。这样产生的引用自动具有正确的类型。
有时你可能需要告知某些其他对象,去创建某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部类对象的引用,这就需要使用new语法。
在拥有外部累对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外部类对象上。
10.4内部类与向上转型
当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的)。这是因为次内部类——某个接口的实现——呢能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用。
private内部类给类的设计者提供了一种途径,通过这种像在TestParcel类中看到的那样,于是,private内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。
10.6匿名内部类 197页
contents()方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类的是匿名,它没有名字。更糟的是,看起来似乎是你正要创建一个Contents对象,但是然后(到达语句结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义”。
这种奇怪的语法指的是:“创建一个继承自Contents的匿名类的对象”。通过new类表示式返回的引用被自动向上转型为contents的引用。
在匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上,它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。
如果定义一个匿名内部类。并且希望它使用一个在其外部定义的对象,那么编译器就会要求其参数引用是final,就像你在destnation()的参数中看到的那样。
在匿名内部类中不可能有命名构造器,因为它根本没有名字,但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果。
在实例初始化操作的内部,它们不能作为字段初始化动作的一部分来执行,所以对于匿名类而言,实例初始化的实际效果就是构造器。
匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口。
10.6.1再访工厂方法
此处的Runnable是接口,而我们却new了Runnable,那不就是直接实例化接口啦?其实不然,此处代码的意思就是new了一个实现Runnable接口的匿名内部类,然后new得到匿名内部类的对象再向上转型为它实现的接口
10.7嵌套类
如果不需要内部类对象与外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。普通的内部类对隐式地保存了一个引用,指向创建它的外围类对象。
嵌套类意味着
1)要创建嵌套类的对象,并不需要其外围类的对象
2)不能从嵌套类的对象中访问非静态的外围类对象
10.7.1接口内部的类
正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是public 和static 的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。
10.7.2从外层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员。
10.8为什么需要内部类
内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。
内部类实现一个接口与外围类实现这个接口有什么区别呢?后者不是总能享用到接口带来的方便,有时需要用到接口的实现。
使用内部类最吸引人的原因是:
每个内部类都能独立继承自一个(接口)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与变成问题就很难解决。从这个角度看,内部类使得多重继承的解决方法变得完整,接口解决了部分问题,而内部类有效地实现了多重继承。也就说内部类允许继承多个非接口类型(译注:类或抽象类)。
如果使用内部类,还可以获得其他一些特性:
1)内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类对象的信息相互独立。
2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
回调的价值在于它的灵活性,可以在运行时动态地决定需要调用什么方法。
10.8.2内部类与控制框架
应用程序框架就是被设计用以解决某类特定问题的一个类或一组类。
设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。
控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求,主要用来响应事件的系统被称作事件驱动系统。
内部类允许
1)控制框架的完整实现是由单个类创建,从而使得实现的细节都被封装了起来。内部类用来表示解决问题所必须的各种不同的action()
2)内部类能够很容易地访问外围类的任意成员。
10.9内部类的继承
因为内部类的构造器必须链接到指向其外围类对象的引用,所以在继承内部类的时候,事情就变得有点复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在导出类中不再存在可链接的默认对象。
10.11 局部内部类
可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及次外围类的所有成员。
我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化。
这些类的文件的命名有严格的规则:外围类的名字,加上“$”再加上内部类的名字