复用类的两种方法:1.组合:在新类中产生现有类的对象。2.继承:采用现有类的形式,并在其中添加新代码
7.1组合语法
7.2继承语法
为了继承,一般的规则是将所有的成员变量声明为private,将所有的方法声明为public。
super关键字表示超类的意思。
导出类会在构造器中调用基类构造器来执行初始化。
想调用一个基类的参数构造器,必须用super关键字显式编写调用基类构造器的方法。
7.3代理
将一个成员对象至于要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员变量的所有方法(就像继承)。
7.4结合使用组合和继承
7.5组合与继承之间的选择
组合和继承都允许在新的类中放置子对象,结合是显式这样做,而继承则是隐式这样做。
7.6protected关键字
protected关键字作用:只对对于任何继承于此类的导出类或任何其他位于同一包内的类是可以访问的。
7.7向上转型
向上转型
是否需要从导出类向基类向上转型,如果需要继承是必须的,否则就可以考虑使用组合
7.8final关键字(这是无法改变的)
可能用到final的三种情况:数据、方法和类。
数据:1.一个永不改变的编译时常量(这些常量必须是基本数据类型)2.一个运行时被初始化的值,而你不希望它改变
一个既是static又是final的域只占据一段不能改变的空间。将用大写表示,单词之间下划线分开
而对于对象引用,final使引用恒定不变,一旦 引用被初始化指向一个对象,则无法再把它指向另外一个对象。但对象自身是可以被修改的。
空白final,通过构造器初始化来根据对象而有所不同,却又保持恒定不变的特性。
必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是能初始化的原因。
7.9初始化及类加载器
第一个类的实例化对象的创建或者static成员的访问都会引起类的加载。
复用类这标题刚开始很难懂,后面专门去看了书的英文原版,其实标题是reusing classes,重新使用类,其实复用就是“利用现成的东西”的意思,其实实现的两种方法就是java中经常听到的——组合和继承。
(1)组合
has-a的作用。
public class TV {
Show show;
public String toString(){
return "showgirl";
}
}
class Show{
}
提一下toString方法,当你需要String而你是一个对象的时候,编译器会调用对象的toString方法。
TV里有Show,现在的show没有初始化,为null,不能调用show的方法。
组合的作用强大,以面向对象看,假如你在造一个Car类,那么你可以用组合轻易的将Glass,Light,Engine等等的Car这些部件组合起来。
(2)继承
is-a
package com.myown.iaiti;
public class Father {
public int i;
void get(){
System.out.println("father");
}
}
package son;
import com.myown.iaiti.*;
public class Son extends Father{
Father f = new Father();
int j = f.i;
Son son = new Son();
son.get();
}
public void get(){
super.get();
System.out.println("son");
}
}
这里有个包访问权限的问题,假如没有加public的时候,默认是包内成员访问,不同包访问,即Son中的Father成员访问get方法是不可见的。而public的话是可见的,所以i访问得到。
private部分是不能继承,属于父类私有,而public的部分,将继承,需要修改的方法,可以进行重写。要添加的属性可以单独添加。
而且继承的方法,如果原本的father的public方法重写之后没将public加上,会有Cannot reduce the visibility of the inherited method from Father,也就是不能减少父类中继承方法的可见性。super指的是父类,即Father。
还有一点是,其实java中所有的类都隐式地继承了Object类。Object是父类,其他类是子类
老外喜欢讲为基类。子类也叫导出类或者派生类。
(3)代理
设计模式里面有个比较难懂的——代理模式,作者讲的很有趣,代理是组合和继承的中庸之道。
package son;
class Father{
public void get(){
System.out.println("father");
}
}
public class Son extends Father{
public static void main(String[] args) {
Father f = new Father();
f.get();
}
}
class FatherProxy{
private Father f = new Father();
public void get(){
f.get();
}
}
像直接把Father当做成员,那么father的方法就暴露给这个类了,那我们可以使用FatherProxy这样的代理类,我自己定义好get方法是怎么拿的,我自己知道是调用father的get方法,但是使用我这个代理的人不知道,我只告诉他你要用就用代理的get的方法就可以了。封装性就体现出来了。上面只是随便敲的一个简单例子。
(4)重写和重载
class Father{
public void get(String s){
System.out.println("father");
}
public void get(boolean b){
System.out.println("boolean");
}
}
public class Son extends Father{
@Override
public void get(String s){
System.out.println("father");
}
// @Override //会有错误提示 因为父类没有该方法,不是重写
public void get(int i ){
System.out.println("sonint");
}
public static void main(String[] args) {
Son s = new Son();
s.get("d");
s.get(false);
s.get(1);
}
}
重写是重新覆盖父类的方法,如果没有重写或者重载,那么子类调用一个子类没有的方法时,其实是调用父类。
重载是同样的方法名,但参数名称不同,为了防止你错误的进行重载可以加上@Override标签,那样会提示你并没有重写方法。
(5)protected
Java编程思想(三) —— 访问权限的控制
在前面一篇提前写了,因为之前没讲继承的东西。
可以简单将protected看成父类给儿子继承的遗产,其他非继承类不能访问。
(6)final关键字
加上final关键字的基本类型,表示这个变量初始化后不会改变。类似c的define,你希望一个变量在这个程序里就是这个值不需要改变。就可以用final。
public class Son{
int age = 2;
public static void main(String[] args) {
final int i = 1;
// i = 2; 值不能再改变
final Son son = new Son();
// son = new Son();
//The final local variable son cannot be assigned.
//It must be blank and not using a compound assignment
//final修饰的局部变量son不能被分配,必须为空或者不要再次分配
son.age = 4;
//虽然引用恒定不变,但是,对象本身却可以改变。
}
void change(final int c){
// c= this.age; 无法赋予新值 因为值只有在方法传参决定 对象引用和这个类似
//age ++; 无法改变
}
}
static本来是静态初始化,和final一起用就是占据了一块不能改变的存储空间。
static final即编译期常量,常量名按照c的常量命名传统,全部用大写字母,单词之间用下划线分开。
static final VALUE_ONE = 1;
final修饰方法时
public class Print {
final void cannotprint(){
System.out.println(1);
}
}
public class PrintSon extends Print{
//void cannotprint(){}
//无法重写 因为被final修饰了
public static void main(String[] args) {
PrintSon ps = new PrintSon();
ps.cannotprint();
}
}
可以看成父类要求子类必须继承的不可修改财产(祖传)。private隐式地指定为final,因为private根本就不给你继承。这比给你继承但不能修改还更私有。
顺便将权限理清。
public,公共财产,不止是子类,其他类也可以用。
final,祖传珍宝,留给子类,但不允许修改。
private,父类私有财产,不会给子类继承。
protected,父类专门留给子类的财产,其他人不能用它。
当final修饰的是类的时候,是为了让这个类不会被继承。
(7)继承和初始化
这里的顺序问题是一个很有趣的问题。看例子。
class GrandFather{
private static int i = print();
private static int print(){
System.out.println("g");
return 1;
}
}
class Father extends GrandFather{
private static int i = print();
private static int print(){
System.out.println("f");
return 1;
}
}
public class Son extends Father{
private static int i = print();
private static int print(){
System.out.println("s");
return 1;
}
public static void main(String[] args) {
System.out.println("first");
}
}
打印的结果是first吗?错了。
虽然执行的是main方法,但是看到son这个需要静态初始化的i没有,结果是s,first吗?
这还有初始化的问题,son是继承father,那么编译器会加载father,并初始化i,那father继承grandfather,那么编译器会去加载grandfather,类似递归。
那最后最先初始化的是grandfather的i。
所以最后的结果是:g,f,s,first。
至于这章提到的向上转型,是和多态联系着的,所以放到下一篇来讲。
继承与组合
复用类的方法有两种:继承与组合。继承就不多说了,组合就是直接在类中new一个对象。
数组也是对象,使用数组也是组合的一种。
2、初始化基类
当创建一个导出类的对象时,该对象包含一个基类的子对象。这个子对象跟直接new一个基类的对象完全相同,唯一的区别是:一个在外部,一个被包装在导出类对象内部。
在导出类构造器中,通过调用基类构造器来执行初始化。【在Java中,所有导出类的所有构造器内部都必须调用父类的某一个构造器或所有导出类的所有构造器内部都必须调用一个其他构造器(既可以是本类构造器也可以是父类的构造器),但是最终必须调用到父类的某一个构造器。也就是说,必须确保能够正确初始化父类对象。如果没有显式调用其它构造器,JVM会默认为你添加调用父类无参构造的代码,如果父类已经显式定义其他有参构造而没有定义无参构造,则会报找不到的编译期异常。】
3、代理:介于继承与组合的“中庸之道”
1)将一个成员对象置于所需构建的类中(类似组合)
2)在新类中暴露该成员对象的所有方法(类似继承)
4、继承与组合的商榷
组合比继承常用得多;
应该慎用继承技术,多用组合。因为组合的方式比继承灵活得多。
那么应该何时使用继承更加适合呢?下面是判断依据:
是否需要从新类向基类进行向上转型?
1)如果必须向上转型,则继承是必须的;
2)如果不需要,则应好好考虑继承与组合的取舍。
5、final关键字
用来修饰数据、方法和类。
final状态表示最终状态:(如果是变量)不可修改、(如果是方法)不可覆盖、(如果是类)不可继承。
static final 【】:表示编译器常量;
private 方法,隐含着final作用(因为他同样不可覆盖和修改等)。
1、继承与组合
复用类的方法有两种:继承与组合。继承就不多说了,组合就是直接在类中new一个对象。
数组也是对象,使用数组也是组合的一种。
2、初始化基类
当创建一个导出类的对象时,该对象包含一个基类的子对象。这个子对象跟直接new一个基类的对象完全相同,唯一的区别是:一个在外部,一个被包装在导出类对象内部。
在导出类构造器中,通过调用基类构造器来执行初始化。【在Java中,所有导出类的所有构造器内部都必须调用父类的某一个构造器或所有导出类的所有构造器内部都必须调用一个其他构造器(既可以是本类构造器也可以是父类的构造器),但是最终必须调用到父类的某一个构造器。也就是说,必须确保能够正确初始化父类对象。如果没有显式调用其它构造器,JVM会默认为你添加调用父类无参构造的代码,如果父类已经显式定义其他有参构造而没有定义无参构造,则会报找不到的编译期异常。】
3、代理:介于继承与组合的“中庸之道”
1)将一个成员对象置于所需构建的类中(类似组合)
2)在新类中暴露该成员对象的所有方法(类似继承)
4、继承与组合的商榷
组合比继承常用得多;
应该慎用继承技术,多用组合。因为组合的方式比继承灵活得多。
那么应该何时使用继承更加适合呢?下面是判断依据:
是否需要从新类向基类进行向上转型?
1)如果必须向上转型,则继承是必须的;
2)如果不需要,则应好好考虑继承与组合的取舍。
5、final关键字
用来修饰数据、方法和类。
final状态表示最终状态:(如果是变量)不可修改、(如果是方法)不可覆盖、(如果是类)不可继承。
static final 【】:表示编译器常量;
private 方法,隐含着final作用(因为他同样不可覆盖和修改等)。
2.复用类
(1)组合:将已有的类增加到新类中。已有类就像普通基本数据类型一样被使用,但是类中的基本数据类型能够自动被初始化,对象引用会被初始化为null,因此应确保在对象使用之前能够被创建。例如,即使没有被初始化,也会利用toString()输出null,toString()帮助字符串操作与输出。可以重载这一方法,输出需要的更详细的信息。
(2)继承:每个类中都可以有一个main方法,但是pubulic的类只有一个,可使每个类的单元测试都变得简单易行。main方法的调用受到命令行的限制,调用谁的(类),就会执行谁的main方法,即使该类不是public的。
当基类显示的定义了带参数的构造器,则再继承的子类中需要使用super显示调用;
代理:是在组合的基础上,在新类中有选择地调用(实现)了其中(旧类)的方法。
正确的清理:在除内存回收(可由垃圾回收)外,其余的清理工作,从基类到子类都应显示地在清理函数中调用,基类的清理在子类的清理函数中,super调用,类似c++的函数调用;
覆盖:在Java中基类的某个方法进行了重载,在继承的子类对该方法的覆盖同样可以使用,可以使用@overide来注解帮助,说明为覆盖了基类的方法。
向上转型:由于继承机制,使得子类具备了基类所能够处理的所有信息能力,因此在java的类型检查中,在参数等传递过程中子类就看成向上转型为其基类使用了。
final与static:final可以在运行时确定不变,但是同一个类的不同对象可拥有不同的值,例如有final int a=rand.nextInt(),而static则所有对象仅拥有一份拷贝
空final变量:在类的定义中,使用空白final(如 final int a;),可以通过构造带参数的构造器,在构造器中进行初始化(用其他方式不行,直接编译不过去),从而实现灵活的设置,不同的对象在初始化时根据需要设置不同的final对象的值;
final参数:类似于c++中的const,在方法中无法修改所引用的对象;
final方法、类:final可以将方法、类锁定,防止继承、覆盖。
复用代码的两种形式:继承和组合
1. 组合语法(composition):将对象的引用置于新类中,编译器不是简单地为每个引用创建缺省对象,初始化这些引用:在以下位置进行:1)在定义对象的地方,(在构造器吧被调用之前被初始化)2)类的构造器中3)需要使用这些对象之前,(惰性初始化lazy initialization)
2. 继承语法(inheritance):为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public,调用基类继承而来的方法,使用关键字super
3. 初始化基类:基类/导出类,当创建一个导出类的对象时,该对象包含了一个基类的子对象(subobject),在对基类子对象初始化,java会自动在导出类的构造器中插入对基类构造器的调用。
编译器合成的缺省构造器,也会自动调用基类的构造器。
带参数的构造器:必须用super显式地编写调用基类构造器的语句,配以适当的参数列表。(在有参数的导出类构造器中,必须调用基类构造器,做错了,编译器会提醒你)。
调用基类构造器,可以防止导出类构造器捕捉任何来自基类的异常。
4. 结合使用组合(composition)和继承(inheritance):编译器强制初始化基类,并不监督必须将成员对象也初始化。
5. 确保正确清除:若某个类要清除一些东西,必须显式地编写一个特殊方法来做这件事,并且这一方法必须调用。像try catch中的finally字句一样。
回收对象的最好办法:是除了内存以外,不要依赖垃圾回收器去做任何事,如果你需要进行清除,最好编写自己的清除方法,不要依赖finalize()。
6. 名称屏蔽(Name hiding):如果java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称,并不会屏蔽其在基类中的任何版本(这一点与C++不同)。在java中,使用与基类完全相同的签名(signature)及返回类型来重载具有相同名称的方法是一件及其平常的事。
7. 组合与继承之间选择:is-a,has-a
通过protected方法来控制类的继承者的访问权限。
8. 增量开发(incremental development):程序开发是一种增量过程,将项目视作是一种有机的、进化着的生命体去培养。通过生成和操作各种类型的对象,用来自问题空间(problem space)中的术语来表现一个模型(model)。
9. 向上转型(upcasting):理解新类是现有类的一种类型。由导出类转型成基类,在继承图上是向上移动的。因此称为“向上转型”。
10. Final数据:1)它可以是一个永不改变的“编译期常量(compile-time constant),2)它可以是一个在运行期被初始化的值,而你不希望它被改变。
Java中,这类常量必须是原始的并且以关键字final表示,对这个常量进行定义的时候,必须对其进行赋值。
一个既是static又是final的域只占有一份不能改变的存储空间。
对于原始类型,final使数值恒定不变,对于对象引用,final使引用恒定不变。
定义static强调只有一份,定义为final来说明它是一个常量。带有恒定初始值(即,编译期常量)的final static原始类型全用大写字母命名,并且字与字之间用下划线来隔开。
11. 空白final:声明为final但又未给定初值的数据成员。
对final进行赋值被强制在数据成员的定义处或者是每个构造器中用表达式对final进行赋值,保证了final数据成员在使用前总是被初始化。
12. final参数:将参数声明为final,意味着无法在方法中更改参数引用所指向的对象。
13. final方法:1)锁定方法,不会被重载,2)效率
14. final类:禁止继承,final类中的所有方法都被隐含是final的,因为它们是不会被重载的。
优先使用组合,在必要时选择继承(需要向上转型)
复用代码是Java众多引人注目的功能之一,但Java要乡成为极具革命性的语言,仅能够复制代码并对之加以改变还不够,还必须能够做更多的事情。
为了使用类而不破坏现有代码,方法之一只需在新的类中产生现有类的对象,由于新的类是由现有类的对象组成,所以这种方法称为组合,此法只复用现有程序代码的功能,而非它的形式。
为了使用类而不破坏现有代码,方法之二是按照现有类来创建新类,无需改变现有形式,采用现有类的形式并在其中添加代码,此法称为继承,继承是面向对象基石之一。
编译器不是简单地为每一个引用都创建默认对象,如果想初始化这些引用,可以将初始化定义于:在定义对象的地方;在类的构造器中;在使用这些对象之前;使用实例初始化。
继承是所有OOP语言和Java语言不可缺少的组成部分,创建一个类时,总是在继承,因此,除非明确指出要继承于其他类,否则就是在隐式地extends Java的标准根类Object。
“+=”操作符是被Java设计者重载用以处理String对象的操作符之一,另一个是“+”。
Java当中,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public。
Java中的代理是继承与组合之间的中庸之道,因为将一个成员对象置于所要构造的类中,这就像组合一样,但与此同时再将新类中暴露成员对象的所有方法,这就像继承。
Java中如果想要某个类清理一些东西,必须显示得编写一个特殊方法做着件事,并要确保客户端程序员知晓他们必须调用这一方法,既将这一清理动作置于finally字句之中。
关键字try表示,它下面的块是所谓的保护区,这意味着它需要被特殊处理,其中一项特殊处理就是说无论try是怎样退出的,保护区后的finally字句中的代码总是要被执行。
如果Java的基类中拥有某个已被多次重载的方法名称,那么在导出类中定义该方法名称并不会屏蔽其在基类中的任何版本,因此,无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作。
@Override注解可以防止你在不想重载时而意外地进行了重载。
组合和继承都允许在新类中放置子对象,组合是显示地这样做,而继承则是隐式地做,组合技术通常用于想在新类中使用现有的功能而非它的接口的情形,组合表达的关系是”has-a”既“有一个”的关系,而继承表达的是”is-a”既“是一个”的关系。
关键字protected起的作用就是,就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,是可以访问的。
子类引用转换为父类引用的动作,称为向上转型,由于向上转型是从一个较为专用的类型向一个较为通用的类型转换,总体来说,此动作很安全。
在很多情况下,到底是采用组合还是继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型,如果必须向上转型,则继承是必要的,如果不需要的话,那么就要好好考虑是否需要继承来解决。
Java当中的final关键字通常指的是“这是无法改变的”,可能使用到final关键字有三种情况:数据,方法和类。
基本数据类型被final关键字修饰时,必须对该常量进行赋值,一个既是static又是final的域只占据一段不能改变的存储空间。
带有恒定初始值的final static基本类型全用大写字母命名,并且字与字之间用下滑线隔开。
使用final方法的原因之一是把方法锁定,以防任何继承类修改她的含义,想要确保在继承中使方法行为保持不变,并且不会被覆盖;原因之二是效率(以不常用)。
类中所有的private方法都被隐式地指定为final。
当将某个类的整体定义为final时,表明不打算继承该类,而且也不允许别人继承,也就是说对类的设计永不需要做任何变动,final类中所有的成员都是final的。
尽管面向对象编程对继承极力强调,但在开始一个设计时,一般优先选择组合(或代理),只有在确实必要时才使用继承,因为组合更加灵活,此外,通过对成员类型使用继承技术的添加技巧,可以在运行时改变那些成员对象的类型和行为。
复用代码是Java众多引人注目的功能之一。
达到复用代码的方法有:
组合:新的类由现有类的对象所组成。(复用现有代码的功能,而非它的形式)
继承:按照现有类的类型组建新类。(不改变现有类的形式,复用现有类的形式并在其中添加新代码)。面向对象程序设计的基石
正文
1.组合语法
将对象引用置于新类中即可(非基本类型:在新类中放置引用,基本类型:直接添加就可以了)
/**
* 引擎
*/
class Engine{
/**
* 功率
*/
double power;
/**
* 品牌
*/
String type;
/**
* 无参构造方法
*/
Engine(){
}
/**
* 构造方法
* @param power
* @param type
*/
Engine(double power,String type){
this.power = power;
this.type = type;
}
@Override
public String toString() {
return "Engine:power="+power+" type="+type;
}
}
/**
*火炮
*/
class Artillery{
/**
* 口径
*/
double caliber;
/**
* 构造方法
*/
Artillery(){
}
Artillery(double caliber){
this.caliber = caliber;
}
@Override
public String toString() {
return "Artillery: caliber="+caliber;
}
}
/**
* 坦克
*/
public class Tank {
private Engine engine;
private Artillery artillery;
public Tank(Engine engine,Artillery artillery){
this.artillery = artillery;
this.engine = engine;
}
@Override
public String toString() {
return "Tank:"+this.engine+""+this.artillery;
}
public static void main(String[] args) {
Engine engine = new Engine(10000.0,"ABC");
Artillery artillery = new Artillery(1000);
Tank tank = new Tank(engine,artillery);
System.out.println(tank);
}
}
每一个非基本类型的对象都有一个toString()方法。可以打印对象,重写toString()方法能自定义打印的内容。
对于类中的域,若是基本类型则能自动初始化为0。但是对象引用会被初始化为null。
编译器不会简单的为每一个引用创建默认对象(如果创建默认对象的话,在许多情况下增加不必要的负担)
初始化引用的方法:
1.在定义对象的地方。(在构造器被调用前被初始化)
2.在类的构造器中。
3.正要使用这些对象之前(惰性初始化,可减少额外负担)
4.使用实例初始化。
class Soap{
private String s;
/**
* 构造器初始化
*/
Soap(){
System.out.println("Soap()");
s = "Constructed";
}
@Override
public String toString() {
return s;
}
}
public class Bath {
/**
* 在定义对象的地方(在构造器被调用前初始化)
*/
private String s1 = "Happy",
s2 = "Happy",
s3,
s4;
private Soap castille;
private int i;
private float toy;
public Bath(){
System.out.println("Inside Bath()");
s3 = "Joy";
toy = 3.14f;
castille = new Soap();
}
/**
* 实例初始化
*/
{
i=47;
}
@Override
public String toString() {
/**
* 惰性初始化
*/
if(s4==null)
s4="Joy";
return "s1 = "+s1+
"\ns2 ="+s2+
"\ns3 ="+s3+
"\ns4 ="+s4+
"\ni ="+i+
"\ntoy ="+toy
+"\ncastille ="+castille;
}
public static void main(String[] args) {
Bath b = new Bath();
System.out.println(b);
}
}
继承语法
继承是所有OOP(面向对象的编程语言)语言,包括Java语言不可缺少的组成部分。
当创建一个类时,总是在继承。(除非已明确指出从其他类中继承,否则就是隐式的继承自Java的标准根类Object)
关键字:extends(子类会自动得到父类中所有的域和方法)
class Cleanser{
private String s = "cleanser";
public void append(String a){
s += a;
}
public void dilute(){
append("dilute()");
}
public void apply(){
append("apply()");
}
public void scrub(){
append("scrub()");
}
@Override
public String toString() {
return s;
}
/**
* 可以为每个类都创建一个main方法,使每个类的单元测试变得容易。
* @param args
*/
public static void main(String[] args) {
Cleanser cleaner = new Cleanser();
cleaner.dilute();
cleaner.apply();
cleaner.scrub();
System.out.println(cleaner);
}
}
public class Detergent extends Cleanser{
/**
* 重写Cleanser的scrub方法
*/
public void scrub() {
append("Detergent.scrub()");
super.scrub();
}
/**
* 在子类中添加新的方法
*/
public void foam(){
append("foam()");
}
public static void main(String[] args) {
Detergent detergent = new Detergent();
detergent.dilute();
detergent.apply();
detergent.scrub();
detergent.foam();
System.out.println(detergent);
System.out.println("Test base classes:");
//调用Cleanser的main方法
Cleanser.main(args);
}
}
初始化基类(父类)
class Art{
Art(){
System.out.println("Art Constructor");
}
}
class Drawing extends Art{
Drawing(){
//Java会自动在子类的构造器中插入对父类构造器的调用(即super()方法的调用)
// super();
System.out.println("Drawing Constructor");
}
}
public class Cartoon extends Drawing {
public Cartoon(){
// super();
System.out.println("Cartoon Constructor");
}
public static void main(String[] args) {
Cartoon cartoon = new Cartoon();
}
}
结果:
Art Constructor
Drawing Constructor
Cartoon Constructor
带参数的构造器
若没有默认构造器,或想调用一个带参数的基类构造器,就必须使用关键字super显式地编写调用基类构造器的语句,并且配以适当的参数列表:
class Art{
Art(int i){
System.out.println("Art Constructor");
}
}
class Drawing extends Art{
Drawing(int i){
//super(i)显式调用基类(父类)的构造器方法
super(i);
System.out.println("Drawing Constructor");
}
}
public class Cartoon extends Drawing {
public Cartoon(int i){
//super(i)显式调用基类(父类)的构造器方法
super(i);
System.out.println("Cartoon Constructor");
}
public static void main(String[] args) {
Cartoon cartoon = new Cartoon(1);
}
}
用代码的两种形式:
1.组合,新类中产生现有类对象
2.继承,用现有类型创建新类型
7.1组合语法
7.2继承语法
7.2.1初始化基类
当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的(Java会自动在导出类构造器里插入对基类构造器的调用,基类只含带参构造需要自己用super调用,不可省略),二者区别在于继承时基类的子对象包装在导出类对象内部,而直接创建来与外部。
7.3代理
代理:第三种关系,Java没有对它的直接支持,继承和组合之间中庸之道。
实现代理:把一个成员对象放在代理类中(就像组合),同时在新类中暴露了这个对象的所有方法(就像继承)。
7.4结合使用组合和继承
7.4.1确保正确清理
除了内存之外,不能依赖垃圾回收器去做任何事。如果需要清理,最好是编写自己的清理方法(子类清理方法和父类清理方法调用顺序按照创建反序来执行),在该对象使用结束之后,用finally{}子句调用清理方法,不要使用finalize()。
7.4.2名称屏蔽
子类可以对基类方法进行覆盖,重载。利用@override注解可以防止在想覆盖时意外写成了重载
7.5在组合与基础之间选择
继承:is-a
组合:has-a
7.6protected关键字
提供子类和同包的访问权限
7.7向上转型
为新类型提供方法,并不是继承技术中最重要的方面,最重要的是新类和基类之间的关系,这种关系可以用”新类是现有类的一种类型“来表示。
7.7.1为什么称为向上转型
7.7.2再论如何选择组合与继承
问一问自己是否需要从新类向基类转型,需要用继承,否则尽可能不要用
7.8final关键字
参照 Java final 关键字 & 参数传递特点
7.9初始化及类的加载
Java中类的加载在初次使用时发生,通常是创建对象或者访问static方法(构造器也算是static方法,只是隐式的)。
7.9.1继承与初始化
参照 Java 类加载、调用构造器、执行方法的过程
7.10总结
组合和继承都可以产生新类型。组合一般是把现有类型作为新类型实现的一部分加以复用,而继承是复用已有类型的接口。
、组合语法
编译器并不是简单的为每一个引用都创建默认对象,这一点是很有意义的,因为若真的那样做的话,就会在许多情况下增加不必要的负担。如果想要初始化这些引用,可以在代码的下列位置进行:
(1)在定义对象的地方。这意味着它们总能在构造器调用之前完成初始化
(2)在类的构造器中
(3)就在正要使用这些对象之前,这种方式成为惰性初始化。在声称对象不值得或者不必要每次都生成对象的情况下,这种方式可以减少额外的负担
(4)使用实例初始化
二、继承语法
1、初始化基类
当创建了一个导出类的对象的时候,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象一样。二者的区别在于,后者来自于外部,而基类的子对象被包装在导出类的对象内部。
在构造器中调用基类的构造器来执行初始化,二基类构造器具有执行基类初始化所需要的所有知识和能力。java会自动在导出类的构造器中插入对基类构造器的调用
所有的基类在导出类的构造器可以访问它之前,就已经完成了初始化:
基类构造器:(a)总是会被调用 (b)在导出类的构造器之前被调用
2、带参数的构造器
(1)如果没有默认的基类构造器,或者想用一个带参数的基类构造器,就必须用关键字super显式的编写调用基类构造器的语句,并且配以适当的参数列表。
(2)调用基类的构造器必须是你在导出类构造器中要做的第一件事。
三、代理
(1)将一个对象置于索要构造的类中(类似组合)
(2)在新类中暴露了该成员对象的所有方法(类似继承)
四、结合使用组合和继承
1、确保正确清理
2、名称屏蔽
(1)如果java的基类拥有某个已经多次被重载的方法名称,那么在导出类中重新定义该方法名称不会屏蔽在基类中的任何版本(这一点和c++不同)
(2)java SE5新增加了@Override注解,它不是关键字,但是可以把它当做关键字使用。当想要覆写某个方法时候,可以选择添加该注解,当你不留心重载而非覆写了该方法的时候,编译器就好生成一条错误消息:
五、在组合与继承之间选择
(1) “is-a”--->继承
(2) “has-a”---->组合
六、protected关键字
七、向上转型
八、final关键字
1、final数据
1)一个永不改变的编译时常量
2)一个在运行时被初始化的值,而你不希望它改变
对于基本类型,final使数组恒定不变,而用于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。然而,对象其自身却是可以被修改的。
我们不能因为某数据是final的就认为编译时就可以知道它的值。
(1)空白final
java允许生成“空白final”,所谓空白final是指被声明为final但又未给定初值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。
(2) final参数
java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法再方法中更改参数引用所指向的对象。(这一特性主要用来向匿名内部类传递数据)
2、final方法
使用final方法的原因有两个:
1)把方法锁定,以防止任何继承类修改它的含义
2)效率
(1)final和private关键字
类中所有的private方法都隐式的指定为final的。由于无法取得private方法,所以也就无法覆盖它。
“覆盖”只有在某方法是基类的接口的一部分时候才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类的接口的一部分。它仅是一些隐藏于类中的程序代码,只不过是具有相同的名称而已。
3、final类
1)当将某个类的整体定义为final的时候,就表明了你不打算继承该类,二期也不允许别人这么做。
2)由于final类禁止继承,所以final类中所有的方法都会隐式指定为final的,因为无法覆盖它们。在final类中科院给方法添加final修饰词,蛋这不会增添任何意义。
九、初始化及类加载
一般来说,类的代码在初次使用时候才加载。这通常是指加载发生于创建类的第一个对象之时,但是当访问static域或static方法的时候,也会发生加载。
构造器也是static方法,尽管static关键字没有显式的写出来。更准确的讲,类是在任何static成员被访问的时候被加载的。
1. 每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用。
2. 构造方法的过程是从基类“向外”扩散的,所以基类在导出类构造方法可以访问它之前,就已经完成了初始化。
调用父类构造方法必须是你在导出类构造方法中要做的第一件事。
3. 子类要想覆盖基类的方法,必须方法名,参数和返回值3个都一直才可以覆盖,否则编译不通过。
4. 由导出类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。由于向上转型是从一个较专用类型向较通用类型转换,所有总是很安全的,也就是说,导出类是基类的一个超级。它可能比基类含有更多的方法,但它必须至少具备基类中的所含有的方法。在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法,而不是获取它们。这就是为什么编译器在“未曾明确表示转型”或“未曾指定特殊标记”的情况下,仍然允许向上转型。
5. 根据上下文环境,Java的关键字final的含义存在着细微的区别,但通常它指的是”这是无法改变的。”不想做改变可能出于两种理由:设计或效率,由于这两个原因相差很远,所以关键字final有可能被误用。使用到final的三种情况:数据、方法和类。
在Java中,当以关键字final修饰时,必须对其进行赋值(或者在构造方法中赋值),否则编译不通过。必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是被初始化的原因所在。
一个既是static又是final的域只占据一段不能改变的存储空间。
当对对象引用而不是基本类型运用final时,其含义会有一点令人迷惑。对于基本类型,final使数值恒定不变;而用于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。然而,对象其自身却是可以被修改的,Java并未提供使任何对象恒定不变的途径(但可以自己编写类以取得使对象恒定不变的效果)。这一限制同样适用数组,它也是对象。
我们不能因为某数据是final的就认为在编译时可以知道它的值。在运行时使用随机生成的数值来初始化就说明了这一点。
public class TestFinal {
private static Random rand = new Random(47);
private String id;
public TestFinal(String id) {
this.id = id;
}
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
public static final int VALUE_THREE = 39;
public static int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() {
return id + ":" + "i4 = " + i4 + ",INT_5 = " + INT_5;
}
public static void main(String[] args) {
TestFinal tf1 = new TestFinal("tf1");
// fd1.valueOne++; // Error: can't change value
tf1.v2.i++; // Object isn't constant !
tf1.v1 = new Value(9); // ok -- not final
for (int i = 0; i < tf1.a.length; i++) {
tf1.a[i]++; // Object isn't constant !
}
// tf1.v2 = new Value(0); // Error: can't;
// tf1.VAL_3 = new Value(1); // change reference.
// tf1.a = new int[3];
System.out.println(tf1);
System.out.println("Creating new TestFinal");
TestFinal tf2 = new TestFinal("tf2");
System.out.println(tf1);
System.out.println(tf2);
}
}
class Value {
int i;
public Value(int i) {
this.i = i;
}
}
也可以传递null当参数给方法:
public class TestFinalMethod {
void with(Gizmo g) {
g = new Gizmo();
System.out.println("创建好了!" + g.i);
}
public static void main(String[] args) {
TestFinalMethod tfm = new TestFinalMethod();
tfm.with(null);
}
}
class Gizmo {
int i = 4;
}
如果with的参数是final Gizmo g,那么就会在方法第一行报错了。
使用final方法的原因有两个:第一个原因是把方法锁定,以防止任何继承类修改它的含义。过去使用final的第二个原因就是效率。
类中所有的private方法都隐式地指定为是final的,由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但这并不能给该方法增加任何额外的意义。
“覆盖”只有在某方法是基类接口的一部分时才会出现,即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类的接口的一部分,它仅是一些隐藏于类中的程序代码,只不过是具有相同名称而已。如果在导出类中以相同的名称生成一个public、pretected或宝访问权限方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时你并没有覆盖该方法,仅是生成了一个新的方法。
final类的域可以根据个人的意愿选择是或不是final,不论类是否被定义为final,相同的规则都适用于定义为final的域,然而,由于final类禁止继承,所以final类中所有的方法都是隐式指定为是final的。
6. 初始化过程
父类静态方法(父类静态代码块)-->子类静态方法(子类静态代码快)-->父类成员(父类代码块)-->父类构造方法-->子类成员(子类代码块)-->子类构造方法
7.1组合语法
7.2继承语法
为了继承,一般的规则是将所有的成员变量声明为private,将所有的方法声明为public。
super关键字表示超类的意思。
导出类会在构造器中调用基类构造器来执行初始化。
想调用一个基类的参数构造器,必须用super关键字显式编写调用基类构造器的方法。
7.3代理
将一个成员对象至于要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员变量的所有方法(就像继承)。
7.4结合使用组合和继承
7.5组合与继承之间的选择
组合和继承都允许在新的类中放置子对象,结合是显式这样做,而继承则是隐式这样做。
7.6protected关键字
protected关键字作用:只对对于任何继承于此类的导出类或任何其他位于同一包内的类是可以访问的。
7.7向上转型
向上转型
是否需要从导出类向基类向上转型,如果需要继承是必须的,否则就可以考虑使用组合
7.8final关键字(这是无法改变的)
可能用到final的三种情况:数据、方法和类。
数据:1.一个永不改变的编译时常量(这些常量必须是基本数据类型)2.一个运行时被初始化的值,而你不希望它改变
一个既是static又是final的域只占据一段不能改变的空间。将用大写表示,单词之间下划线分开
而对于对象引用,final使引用恒定不变,一旦 引用被初始化指向一个对象,则无法再把它指向另外一个对象。但对象自身是可以被修改的。
空白final,通过构造器初始化来根据对象而有所不同,却又保持恒定不变的特性。
必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是能初始化的原因。
7.9初始化及类加载器
第一个类的实例化对象的创建或者static成员的访问都会引起类的加载。
复用类这标题刚开始很难懂,后面专门去看了书的英文原版,其实标题是reusing classes,重新使用类,其实复用就是“利用现成的东西”的意思,其实实现的两种方法就是java中经常听到的——组合和继承。
(1)组合
has-a的作用。
public class TV {
Show show;
public String toString(){
return "showgirl";
}
}
class Show{
}
提一下toString方法,当你需要String而你是一个对象的时候,编译器会调用对象的toString方法。
TV里有Show,现在的show没有初始化,为null,不能调用show的方法。
组合的作用强大,以面向对象看,假如你在造一个Car类,那么你可以用组合轻易的将Glass,Light,Engine等等的Car这些部件组合起来。
(2)继承
is-a
package com.myown.iaiti;
public class Father {
public int i;
void get(){
System.out.println("father");
}
}
package son;
import com.myown.iaiti.*;
public class Son extends Father{
Father f = new Father();
int j = f.i;
Son son = new Son();
son.get();
}
public void get(){
super.get();
System.out.println("son");
}
}
这里有个包访问权限的问题,假如没有加public的时候,默认是包内成员访问,不同包访问,即Son中的Father成员访问get方法是不可见的。而public的话是可见的,所以i访问得到。
private部分是不能继承,属于父类私有,而public的部分,将继承,需要修改的方法,可以进行重写。要添加的属性可以单独添加。
而且继承的方法,如果原本的father的public方法重写之后没将public加上,会有Cannot reduce the visibility of the inherited method from Father,也就是不能减少父类中继承方法的可见性。super指的是父类,即Father。
还有一点是,其实java中所有的类都隐式地继承了Object类。Object是父类,其他类是子类
老外喜欢讲为基类。子类也叫导出类或者派生类。
(3)代理
设计模式里面有个比较难懂的——代理模式,作者讲的很有趣,代理是组合和继承的中庸之道。
package son;
class Father{
public void get(){
System.out.println("father");
}
}
public class Son extends Father{
public static void main(String[] args) {
Father f = new Father();
f.get();
}
}
class FatherProxy{
private Father f = new Father();
public void get(){
f.get();
}
}
像直接把Father当做成员,那么father的方法就暴露给这个类了,那我们可以使用FatherProxy这样的代理类,我自己定义好get方法是怎么拿的,我自己知道是调用father的get方法,但是使用我这个代理的人不知道,我只告诉他你要用就用代理的get的方法就可以了。封装性就体现出来了。上面只是随便敲的一个简单例子。
(4)重写和重载
class Father{
public void get(String s){
System.out.println("father");
}
public void get(boolean b){
System.out.println("boolean");
}
}
public class Son extends Father{
@Override
public void get(String s){
System.out.println("father");
}
// @Override //会有错误提示 因为父类没有该方法,不是重写
public void get(int i ){
System.out.println("sonint");
}
public static void main(String[] args) {
Son s = new Son();
s.get("d");
s.get(false);
s.get(1);
}
}
重写是重新覆盖父类的方法,如果没有重写或者重载,那么子类调用一个子类没有的方法时,其实是调用父类。
重载是同样的方法名,但参数名称不同,为了防止你错误的进行重载可以加上@Override标签,那样会提示你并没有重写方法。
(5)protected
Java编程思想(三) —— 访问权限的控制
在前面一篇提前写了,因为之前没讲继承的东西。
可以简单将protected看成父类给儿子继承的遗产,其他非继承类不能访问。
(6)final关键字
加上final关键字的基本类型,表示这个变量初始化后不会改变。类似c的define,你希望一个变量在这个程序里就是这个值不需要改变。就可以用final。
public class Son{
int age = 2;
public static void main(String[] args) {
final int i = 1;
// i = 2; 值不能再改变
final Son son = new Son();
// son = new Son();
//The final local variable son cannot be assigned.
//It must be blank and not using a compound assignment
//final修饰的局部变量son不能被分配,必须为空或者不要再次分配
son.age = 4;
//虽然引用恒定不变,但是,对象本身却可以改变。
}
void change(final int c){
// c= this.age; 无法赋予新值 因为值只有在方法传参决定 对象引用和这个类似
//age ++; 无法改变
}
}
static本来是静态初始化,和final一起用就是占据了一块不能改变的存储空间。
static final即编译期常量,常量名按照c的常量命名传统,全部用大写字母,单词之间用下划线分开。
static final VALUE_ONE = 1;
final修饰方法时
public class Print {
final void cannotprint(){
System.out.println(1);
}
}
public class PrintSon extends Print{
//void cannotprint(){}
//无法重写 因为被final修饰了
public static void main(String[] args) {
PrintSon ps = new PrintSon();
ps.cannotprint();
}
}
可以看成父类要求子类必须继承的不可修改财产(祖传)。private隐式地指定为final,因为private根本就不给你继承。这比给你继承但不能修改还更私有。
顺便将权限理清。
public,公共财产,不止是子类,其他类也可以用。
final,祖传珍宝,留给子类,但不允许修改。
private,父类私有财产,不会给子类继承。
protected,父类专门留给子类的财产,其他人不能用它。
当final修饰的是类的时候,是为了让这个类不会被继承。
(7)继承和初始化
这里的顺序问题是一个很有趣的问题。看例子。
class GrandFather{
private static int i = print();
private static int print(){
System.out.println("g");
return 1;
}
}
class Father extends GrandFather{
private static int i = print();
private static int print(){
System.out.println("f");
return 1;
}
}
public class Son extends Father{
private static int i = print();
private static int print(){
System.out.println("s");
return 1;
}
public static void main(String[] args) {
System.out.println("first");
}
}
打印的结果是first吗?错了。
虽然执行的是main方法,但是看到son这个需要静态初始化的i没有,结果是s,first吗?
这还有初始化的问题,son是继承father,那么编译器会加载father,并初始化i,那father继承grandfather,那么编译器会去加载grandfather,类似递归。
那最后最先初始化的是grandfather的i。
所以最后的结果是:g,f,s,first。
至于这章提到的向上转型,是和多态联系着的,所以放到下一篇来讲。
继承与组合
复用类的方法有两种:继承与组合。继承就不多说了,组合就是直接在类中new一个对象。
数组也是对象,使用数组也是组合的一种。
2、初始化基类
当创建一个导出类的对象时,该对象包含一个基类的子对象。这个子对象跟直接new一个基类的对象完全相同,唯一的区别是:一个在外部,一个被包装在导出类对象内部。
在导出类构造器中,通过调用基类构造器来执行初始化。【在Java中,所有导出类的所有构造器内部都必须调用父类的某一个构造器或所有导出类的所有构造器内部都必须调用一个其他构造器(既可以是本类构造器也可以是父类的构造器),但是最终必须调用到父类的某一个构造器。也就是说,必须确保能够正确初始化父类对象。如果没有显式调用其它构造器,JVM会默认为你添加调用父类无参构造的代码,如果父类已经显式定义其他有参构造而没有定义无参构造,则会报找不到的编译期异常。】
3、代理:介于继承与组合的“中庸之道”
1)将一个成员对象置于所需构建的类中(类似组合)
2)在新类中暴露该成员对象的所有方法(类似继承)
4、继承与组合的商榷
组合比继承常用得多;
应该慎用继承技术,多用组合。因为组合的方式比继承灵活得多。
那么应该何时使用继承更加适合呢?下面是判断依据:
是否需要从新类向基类进行向上转型?
1)如果必须向上转型,则继承是必须的;
2)如果不需要,则应好好考虑继承与组合的取舍。
5、final关键字
用来修饰数据、方法和类。
final状态表示最终状态:(如果是变量)不可修改、(如果是方法)不可覆盖、(如果是类)不可继承。
static final 【】:表示编译器常量;
private 方法,隐含着final作用(因为他同样不可覆盖和修改等)。
1、继承与组合
复用类的方法有两种:继承与组合。继承就不多说了,组合就是直接在类中new一个对象。
数组也是对象,使用数组也是组合的一种。
2、初始化基类
当创建一个导出类的对象时,该对象包含一个基类的子对象。这个子对象跟直接new一个基类的对象完全相同,唯一的区别是:一个在外部,一个被包装在导出类对象内部。
在导出类构造器中,通过调用基类构造器来执行初始化。【在Java中,所有导出类的所有构造器内部都必须调用父类的某一个构造器或所有导出类的所有构造器内部都必须调用一个其他构造器(既可以是本类构造器也可以是父类的构造器),但是最终必须调用到父类的某一个构造器。也就是说,必须确保能够正确初始化父类对象。如果没有显式调用其它构造器,JVM会默认为你添加调用父类无参构造的代码,如果父类已经显式定义其他有参构造而没有定义无参构造,则会报找不到的编译期异常。】
3、代理:介于继承与组合的“中庸之道”
1)将一个成员对象置于所需构建的类中(类似组合)
2)在新类中暴露该成员对象的所有方法(类似继承)
4、继承与组合的商榷
组合比继承常用得多;
应该慎用继承技术,多用组合。因为组合的方式比继承灵活得多。
那么应该何时使用继承更加适合呢?下面是判断依据:
是否需要从新类向基类进行向上转型?
1)如果必须向上转型,则继承是必须的;
2)如果不需要,则应好好考虑继承与组合的取舍。
5、final关键字
用来修饰数据、方法和类。
final状态表示最终状态:(如果是变量)不可修改、(如果是方法)不可覆盖、(如果是类)不可继承。
static final 【】:表示编译器常量;
private 方法,隐含着final作用(因为他同样不可覆盖和修改等)。
2.复用类
(1)组合:将已有的类增加到新类中。已有类就像普通基本数据类型一样被使用,但是类中的基本数据类型能够自动被初始化,对象引用会被初始化为null,因此应确保在对象使用之前能够被创建。例如,即使没有被初始化,也会利用toString()输出null,toString()帮助字符串操作与输出。可以重载这一方法,输出需要的更详细的信息。
(2)继承:每个类中都可以有一个main方法,但是pubulic的类只有一个,可使每个类的单元测试都变得简单易行。main方法的调用受到命令行的限制,调用谁的(类),就会执行谁的main方法,即使该类不是public的。
当基类显示的定义了带参数的构造器,则再继承的子类中需要使用super显示调用;
代理:是在组合的基础上,在新类中有选择地调用(实现)了其中(旧类)的方法。
正确的清理:在除内存回收(可由垃圾回收)外,其余的清理工作,从基类到子类都应显示地在清理函数中调用,基类的清理在子类的清理函数中,super调用,类似c++的函数调用;
覆盖:在Java中基类的某个方法进行了重载,在继承的子类对该方法的覆盖同样可以使用,可以使用@overide来注解帮助,说明为覆盖了基类的方法。
向上转型:由于继承机制,使得子类具备了基类所能够处理的所有信息能力,因此在java的类型检查中,在参数等传递过程中子类就看成向上转型为其基类使用了。
final与static:final可以在运行时确定不变,但是同一个类的不同对象可拥有不同的值,例如有final int a=rand.nextInt(),而static则所有对象仅拥有一份拷贝
空final变量:在类的定义中,使用空白final(如 final int a;),可以通过构造带参数的构造器,在构造器中进行初始化(用其他方式不行,直接编译不过去),从而实现灵活的设置,不同的对象在初始化时根据需要设置不同的final对象的值;
final参数:类似于c++中的const,在方法中无法修改所引用的对象;
final方法、类:final可以将方法、类锁定,防止继承、覆盖。
复用代码的两种形式:继承和组合
1. 组合语法(composition):将对象的引用置于新类中,编译器不是简单地为每个引用创建缺省对象,初始化这些引用:在以下位置进行:1)在定义对象的地方,(在构造器吧被调用之前被初始化)2)类的构造器中3)需要使用这些对象之前,(惰性初始化lazy initialization)
2. 继承语法(inheritance):为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public,调用基类继承而来的方法,使用关键字super
3. 初始化基类:基类/导出类,当创建一个导出类的对象时,该对象包含了一个基类的子对象(subobject),在对基类子对象初始化,java会自动在导出类的构造器中插入对基类构造器的调用。
编译器合成的缺省构造器,也会自动调用基类的构造器。
带参数的构造器:必须用super显式地编写调用基类构造器的语句,配以适当的参数列表。(在有参数的导出类构造器中,必须调用基类构造器,做错了,编译器会提醒你)。
调用基类构造器,可以防止导出类构造器捕捉任何来自基类的异常。
4. 结合使用组合(composition)和继承(inheritance):编译器强制初始化基类,并不监督必须将成员对象也初始化。
5. 确保正确清除:若某个类要清除一些东西,必须显式地编写一个特殊方法来做这件事,并且这一方法必须调用。像try catch中的finally字句一样。
回收对象的最好办法:是除了内存以外,不要依赖垃圾回收器去做任何事,如果你需要进行清除,最好编写自己的清除方法,不要依赖finalize()。
6. 名称屏蔽(Name hiding):如果java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称,并不会屏蔽其在基类中的任何版本(这一点与C++不同)。在java中,使用与基类完全相同的签名(signature)及返回类型来重载具有相同名称的方法是一件及其平常的事。
7. 组合与继承之间选择:is-a,has-a
通过protected方法来控制类的继承者的访问权限。
8. 增量开发(incremental development):程序开发是一种增量过程,将项目视作是一种有机的、进化着的生命体去培养。通过生成和操作各种类型的对象,用来自问题空间(problem space)中的术语来表现一个模型(model)。
9. 向上转型(upcasting):理解新类是现有类的一种类型。由导出类转型成基类,在继承图上是向上移动的。因此称为“向上转型”。
10. Final数据:1)它可以是一个永不改变的“编译期常量(compile-time constant),2)它可以是一个在运行期被初始化的值,而你不希望它被改变。
Java中,这类常量必须是原始的并且以关键字final表示,对这个常量进行定义的时候,必须对其进行赋值。
一个既是static又是final的域只占有一份不能改变的存储空间。
对于原始类型,final使数值恒定不变,对于对象引用,final使引用恒定不变。
定义static强调只有一份,定义为final来说明它是一个常量。带有恒定初始值(即,编译期常量)的final static原始类型全用大写字母命名,并且字与字之间用下划线来隔开。
11. 空白final:声明为final但又未给定初值的数据成员。
对final进行赋值被强制在数据成员的定义处或者是每个构造器中用表达式对final进行赋值,保证了final数据成员在使用前总是被初始化。
12. final参数:将参数声明为final,意味着无法在方法中更改参数引用所指向的对象。
13. final方法:1)锁定方法,不会被重载,2)效率
14. final类:禁止继承,final类中的所有方法都被隐含是final的,因为它们是不会被重载的。
优先使用组合,在必要时选择继承(需要向上转型)
复用代码是Java众多引人注目的功能之一,但Java要乡成为极具革命性的语言,仅能够复制代码并对之加以改变还不够,还必须能够做更多的事情。
为了使用类而不破坏现有代码,方法之一只需在新的类中产生现有类的对象,由于新的类是由现有类的对象组成,所以这种方法称为组合,此法只复用现有程序代码的功能,而非它的形式。
为了使用类而不破坏现有代码,方法之二是按照现有类来创建新类,无需改变现有形式,采用现有类的形式并在其中添加代码,此法称为继承,继承是面向对象基石之一。
编译器不是简单地为每一个引用都创建默认对象,如果想初始化这些引用,可以将初始化定义于:在定义对象的地方;在类的构造器中;在使用这些对象之前;使用实例初始化。
继承是所有OOP语言和Java语言不可缺少的组成部分,创建一个类时,总是在继承,因此,除非明确指出要继承于其他类,否则就是在隐式地extends Java的标准根类Object。
“+=”操作符是被Java设计者重载用以处理String对象的操作符之一,另一个是“+”。
Java当中,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public。
Java中的代理是继承与组合之间的中庸之道,因为将一个成员对象置于所要构造的类中,这就像组合一样,但与此同时再将新类中暴露成员对象的所有方法,这就像继承。
Java中如果想要某个类清理一些东西,必须显示得编写一个特殊方法做着件事,并要确保客户端程序员知晓他们必须调用这一方法,既将这一清理动作置于finally字句之中。
关键字try表示,它下面的块是所谓的保护区,这意味着它需要被特殊处理,其中一项特殊处理就是说无论try是怎样退出的,保护区后的finally字句中的代码总是要被执行。
如果Java的基类中拥有某个已被多次重载的方法名称,那么在导出类中定义该方法名称并不会屏蔽其在基类中的任何版本,因此,无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作。
@Override注解可以防止你在不想重载时而意外地进行了重载。
组合和继承都允许在新类中放置子对象,组合是显示地这样做,而继承则是隐式地做,组合技术通常用于想在新类中使用现有的功能而非它的接口的情形,组合表达的关系是”has-a”既“有一个”的关系,而继承表达的是”is-a”既“是一个”的关系。
关键字protected起的作用就是,就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,是可以访问的。
子类引用转换为父类引用的动作,称为向上转型,由于向上转型是从一个较为专用的类型向一个较为通用的类型转换,总体来说,此动作很安全。
在很多情况下,到底是采用组合还是继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型,如果必须向上转型,则继承是必要的,如果不需要的话,那么就要好好考虑是否需要继承来解决。
Java当中的final关键字通常指的是“这是无法改变的”,可能使用到final关键字有三种情况:数据,方法和类。
基本数据类型被final关键字修饰时,必须对该常量进行赋值,一个既是static又是final的域只占据一段不能改变的存储空间。
带有恒定初始值的final static基本类型全用大写字母命名,并且字与字之间用下滑线隔开。
使用final方法的原因之一是把方法锁定,以防任何继承类修改她的含义,想要确保在继承中使方法行为保持不变,并且不会被覆盖;原因之二是效率(以不常用)。
类中所有的private方法都被隐式地指定为final。
当将某个类的整体定义为final时,表明不打算继承该类,而且也不允许别人继承,也就是说对类的设计永不需要做任何变动,final类中所有的成员都是final的。
尽管面向对象编程对继承极力强调,但在开始一个设计时,一般优先选择组合(或代理),只有在确实必要时才使用继承,因为组合更加灵活,此外,通过对成员类型使用继承技术的添加技巧,可以在运行时改变那些成员对象的类型和行为。
复用代码是Java众多引人注目的功能之一。
达到复用代码的方法有:
组合:新的类由现有类的对象所组成。(复用现有代码的功能,而非它的形式)
继承:按照现有类的类型组建新类。(不改变现有类的形式,复用现有类的形式并在其中添加新代码)。面向对象程序设计的基石
正文
1.组合语法
将对象引用置于新类中即可(非基本类型:在新类中放置引用,基本类型:直接添加就可以了)
/**
* 引擎
*/
class Engine{
/**
* 功率
*/
double power;
/**
* 品牌
*/
String type;
/**
* 无参构造方法
*/
Engine(){
}
/**
* 构造方法
* @param power
* @param type
*/
Engine(double power,String type){
this.power = power;
this.type = type;
}
@Override
public String toString() {
return "Engine:power="+power+" type="+type;
}
}
/**
*火炮
*/
class Artillery{
/**
* 口径
*/
double caliber;
/**
* 构造方法
*/
Artillery(){
}
Artillery(double caliber){
this.caliber = caliber;
}
@Override
public String toString() {
return "Artillery: caliber="+caliber;
}
}
/**
* 坦克
*/
public class Tank {
private Engine engine;
private Artillery artillery;
public Tank(Engine engine,Artillery artillery){
this.artillery = artillery;
this.engine = engine;
}
@Override
public String toString() {
return "Tank:"+this.engine+""+this.artillery;
}
public static void main(String[] args) {
Engine engine = new Engine(10000.0,"ABC");
Artillery artillery = new Artillery(1000);
Tank tank = new Tank(engine,artillery);
System.out.println(tank);
}
}
每一个非基本类型的对象都有一个toString()方法。可以打印对象,重写toString()方法能自定义打印的内容。
对于类中的域,若是基本类型则能自动初始化为0。但是对象引用会被初始化为null。
编译器不会简单的为每一个引用创建默认对象(如果创建默认对象的话,在许多情况下增加不必要的负担)
初始化引用的方法:
1.在定义对象的地方。(在构造器被调用前被初始化)
2.在类的构造器中。
3.正要使用这些对象之前(惰性初始化,可减少额外负担)
4.使用实例初始化。
class Soap{
private String s;
/**
* 构造器初始化
*/
Soap(){
System.out.println("Soap()");
s = "Constructed";
}
@Override
public String toString() {
return s;
}
}
public class Bath {
/**
* 在定义对象的地方(在构造器被调用前初始化)
*/
private String s1 = "Happy",
s2 = "Happy",
s3,
s4;
private Soap castille;
private int i;
private float toy;
public Bath(){
System.out.println("Inside Bath()");
s3 = "Joy";
toy = 3.14f;
castille = new Soap();
}
/**
* 实例初始化
*/
{
i=47;
}
@Override
public String toString() {
/**
* 惰性初始化
*/
if(s4==null)
s4="Joy";
return "s1 = "+s1+
"\ns2 ="+s2+
"\ns3 ="+s3+
"\ns4 ="+s4+
"\ni ="+i+
"\ntoy ="+toy
+"\ncastille ="+castille;
}
public static void main(String[] args) {
Bath b = new Bath();
System.out.println(b);
}
}
继承语法
继承是所有OOP(面向对象的编程语言)语言,包括Java语言不可缺少的组成部分。
当创建一个类时,总是在继承。(除非已明确指出从其他类中继承,否则就是隐式的继承自Java的标准根类Object)
关键字:extends(子类会自动得到父类中所有的域和方法)
class Cleanser{
private String s = "cleanser";
public void append(String a){
s += a;
}
public void dilute(){
append("dilute()");
}
public void apply(){
append("apply()");
}
public void scrub(){
append("scrub()");
}
@Override
public String toString() {
return s;
}
/**
* 可以为每个类都创建一个main方法,使每个类的单元测试变得容易。
* @param args
*/
public static void main(String[] args) {
Cleanser cleaner = new Cleanser();
cleaner.dilute();
cleaner.apply();
cleaner.scrub();
System.out.println(cleaner);
}
}
public class Detergent extends Cleanser{
/**
* 重写Cleanser的scrub方法
*/
public void scrub() {
append("Detergent.scrub()");
super.scrub();
}
/**
* 在子类中添加新的方法
*/
public void foam(){
append("foam()");
}
public static void main(String[] args) {
Detergent detergent = new Detergent();
detergent.dilute();
detergent.apply();
detergent.scrub();
detergent.foam();
System.out.println(detergent);
System.out.println("Test base classes:");
//调用Cleanser的main方法
Cleanser.main(args);
}
}
初始化基类(父类)
class Art{
Art(){
System.out.println("Art Constructor");
}
}
class Drawing extends Art{
Drawing(){
//Java会自动在子类的构造器中插入对父类构造器的调用(即super()方法的调用)
// super();
System.out.println("Drawing Constructor");
}
}
public class Cartoon extends Drawing {
public Cartoon(){
// super();
System.out.println("Cartoon Constructor");
}
public static void main(String[] args) {
Cartoon cartoon = new Cartoon();
}
}
结果:
Art Constructor
Drawing Constructor
Cartoon Constructor
带参数的构造器
若没有默认构造器,或想调用一个带参数的基类构造器,就必须使用关键字super显式地编写调用基类构造器的语句,并且配以适当的参数列表:
class Art{
Art(int i){
System.out.println("Art Constructor");
}
}
class Drawing extends Art{
Drawing(int i){
//super(i)显式调用基类(父类)的构造器方法
super(i);
System.out.println("Drawing Constructor");
}
}
public class Cartoon extends Drawing {
public Cartoon(int i){
//super(i)显式调用基类(父类)的构造器方法
super(i);
System.out.println("Cartoon Constructor");
}
public static void main(String[] args) {
Cartoon cartoon = new Cartoon(1);
}
}
用代码的两种形式:
1.组合,新类中产生现有类对象
2.继承,用现有类型创建新类型
7.1组合语法
7.2继承语法
7.2.1初始化基类
当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的(Java会自动在导出类构造器里插入对基类构造器的调用,基类只含带参构造需要自己用super调用,不可省略),二者区别在于继承时基类的子对象包装在导出类对象内部,而直接创建来与外部。
7.3代理
代理:第三种关系,Java没有对它的直接支持,继承和组合之间中庸之道。
实现代理:把一个成员对象放在代理类中(就像组合),同时在新类中暴露了这个对象的所有方法(就像继承)。
7.4结合使用组合和继承
7.4.1确保正确清理
除了内存之外,不能依赖垃圾回收器去做任何事。如果需要清理,最好是编写自己的清理方法(子类清理方法和父类清理方法调用顺序按照创建反序来执行),在该对象使用结束之后,用finally{}子句调用清理方法,不要使用finalize()。
7.4.2名称屏蔽
子类可以对基类方法进行覆盖,重载。利用@override注解可以防止在想覆盖时意外写成了重载
7.5在组合与基础之间选择
继承:is-a
组合:has-a
7.6protected关键字
提供子类和同包的访问权限
7.7向上转型
为新类型提供方法,并不是继承技术中最重要的方面,最重要的是新类和基类之间的关系,这种关系可以用”新类是现有类的一种类型“来表示。
7.7.1为什么称为向上转型
7.7.2再论如何选择组合与继承
问一问自己是否需要从新类向基类转型,需要用继承,否则尽可能不要用
7.8final关键字
参照 Java final 关键字 & 参数传递特点
7.9初始化及类的加载
Java中类的加载在初次使用时发生,通常是创建对象或者访问static方法(构造器也算是static方法,只是隐式的)。
7.9.1继承与初始化
参照 Java 类加载、调用构造器、执行方法的过程
7.10总结
组合和继承都可以产生新类型。组合一般是把现有类型作为新类型实现的一部分加以复用,而继承是复用已有类型的接口。
、组合语法
编译器并不是简单的为每一个引用都创建默认对象,这一点是很有意义的,因为若真的那样做的话,就会在许多情况下增加不必要的负担。如果想要初始化这些引用,可以在代码的下列位置进行:
(1)在定义对象的地方。这意味着它们总能在构造器调用之前完成初始化
(2)在类的构造器中
(3)就在正要使用这些对象之前,这种方式成为惰性初始化。在声称对象不值得或者不必要每次都生成对象的情况下,这种方式可以减少额外的负担
(4)使用实例初始化
二、继承语法
1、初始化基类
当创建了一个导出类的对象的时候,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象一样。二者的区别在于,后者来自于外部,而基类的子对象被包装在导出类的对象内部。
在构造器中调用基类的构造器来执行初始化,二基类构造器具有执行基类初始化所需要的所有知识和能力。java会自动在导出类的构造器中插入对基类构造器的调用
所有的基类在导出类的构造器可以访问它之前,就已经完成了初始化:
基类构造器:(a)总是会被调用 (b)在导出类的构造器之前被调用
2、带参数的构造器
(1)如果没有默认的基类构造器,或者想用一个带参数的基类构造器,就必须用关键字super显式的编写调用基类构造器的语句,并且配以适当的参数列表。
(2)调用基类的构造器必须是你在导出类构造器中要做的第一件事。
三、代理
(1)将一个对象置于索要构造的类中(类似组合)
(2)在新类中暴露了该成员对象的所有方法(类似继承)
四、结合使用组合和继承
1、确保正确清理
2、名称屏蔽
(1)如果java的基类拥有某个已经多次被重载的方法名称,那么在导出类中重新定义该方法名称不会屏蔽在基类中的任何版本(这一点和c++不同)
(2)java SE5新增加了@Override注解,它不是关键字,但是可以把它当做关键字使用。当想要覆写某个方法时候,可以选择添加该注解,当你不留心重载而非覆写了该方法的时候,编译器就好生成一条错误消息:
五、在组合与继承之间选择
(1) “is-a”--->继承
(2) “has-a”---->组合
六、protected关键字
七、向上转型
八、final关键字
1、final数据
1)一个永不改变的编译时常量
2)一个在运行时被初始化的值,而你不希望它改变
对于基本类型,final使数组恒定不变,而用于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。然而,对象其自身却是可以被修改的。
我们不能因为某数据是final的就认为编译时就可以知道它的值。
(1)空白final
java允许生成“空白final”,所谓空白final是指被声明为final但又未给定初值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。
(2) final参数
java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法再方法中更改参数引用所指向的对象。(这一特性主要用来向匿名内部类传递数据)
2、final方法
使用final方法的原因有两个:
1)把方法锁定,以防止任何继承类修改它的含义
2)效率
(1)final和private关键字
类中所有的private方法都隐式的指定为final的。由于无法取得private方法,所以也就无法覆盖它。
“覆盖”只有在某方法是基类的接口的一部分时候才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类的接口的一部分。它仅是一些隐藏于类中的程序代码,只不过是具有相同的名称而已。
3、final类
1)当将某个类的整体定义为final的时候,就表明了你不打算继承该类,二期也不允许别人这么做。
2)由于final类禁止继承,所以final类中所有的方法都会隐式指定为final的,因为无法覆盖它们。在final类中科院给方法添加final修饰词,蛋这不会增添任何意义。
九、初始化及类加载
一般来说,类的代码在初次使用时候才加载。这通常是指加载发生于创建类的第一个对象之时,但是当访问static域或static方法的时候,也会发生加载。
构造器也是static方法,尽管static关键字没有显式的写出来。更准确的讲,类是在任何static成员被访问的时候被加载的。
1. 每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用。
2. 构造方法的过程是从基类“向外”扩散的,所以基类在导出类构造方法可以访问它之前,就已经完成了初始化。
调用父类构造方法必须是你在导出类构造方法中要做的第一件事。
3. 子类要想覆盖基类的方法,必须方法名,参数和返回值3个都一直才可以覆盖,否则编译不通过。
4. 由导出类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。由于向上转型是从一个较专用类型向较通用类型转换,所有总是很安全的,也就是说,导出类是基类的一个超级。它可能比基类含有更多的方法,但它必须至少具备基类中的所含有的方法。在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法,而不是获取它们。这就是为什么编译器在“未曾明确表示转型”或“未曾指定特殊标记”的情况下,仍然允许向上转型。
5. 根据上下文环境,Java的关键字final的含义存在着细微的区别,但通常它指的是”这是无法改变的。”不想做改变可能出于两种理由:设计或效率,由于这两个原因相差很远,所以关键字final有可能被误用。使用到final的三种情况:数据、方法和类。
在Java中,当以关键字final修饰时,必须对其进行赋值(或者在构造方法中赋值),否则编译不通过。必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是被初始化的原因所在。
一个既是static又是final的域只占据一段不能改变的存储空间。
当对对象引用而不是基本类型运用final时,其含义会有一点令人迷惑。对于基本类型,final使数值恒定不变;而用于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。然而,对象其自身却是可以被修改的,Java并未提供使任何对象恒定不变的途径(但可以自己编写类以取得使对象恒定不变的效果)。这一限制同样适用数组,它也是对象。
我们不能因为某数据是final的就认为在编译时可以知道它的值。在运行时使用随机生成的数值来初始化就说明了这一点。
public class TestFinal {
private static Random rand = new Random(47);
private String id;
public TestFinal(String id) {
this.id = id;
}
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
public static final int VALUE_THREE = 39;
public static int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() {
return id + ":" + "i4 = " + i4 + ",INT_5 = " + INT_5;
}
public static void main(String[] args) {
TestFinal tf1 = new TestFinal("tf1");
// fd1.valueOne++; // Error: can't change value
tf1.v2.i++; // Object isn't constant !
tf1.v1 = new Value(9); // ok -- not final
for (int i = 0; i < tf1.a.length; i++) {
tf1.a[i]++; // Object isn't constant !
}
// tf1.v2 = new Value(0); // Error: can't;
// tf1.VAL_3 = new Value(1); // change reference.
// tf1.a = new int[3];
System.out.println(tf1);
System.out.println("Creating new TestFinal");
TestFinal tf2 = new TestFinal("tf2");
System.out.println(tf1);
System.out.println(tf2);
}
}
class Value {
int i;
public Value(int i) {
this.i = i;
}
}
也可以传递null当参数给方法:
public class TestFinalMethod {
void with(Gizmo g) {
g = new Gizmo();
System.out.println("创建好了!" + g.i);
}
public static void main(String[] args) {
TestFinalMethod tfm = new TestFinalMethod();
tfm.with(null);
}
}
class Gizmo {
int i = 4;
}
如果with的参数是final Gizmo g,那么就会在方法第一行报错了。
使用final方法的原因有两个:第一个原因是把方法锁定,以防止任何继承类修改它的含义。过去使用final的第二个原因就是效率。
类中所有的private方法都隐式地指定为是final的,由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但这并不能给该方法增加任何额外的意义。
“覆盖”只有在某方法是基类接口的一部分时才会出现,即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类的接口的一部分,它仅是一些隐藏于类中的程序代码,只不过是具有相同名称而已。如果在导出类中以相同的名称生成一个public、pretected或宝访问权限方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时你并没有覆盖该方法,仅是生成了一个新的方法。
final类的域可以根据个人的意愿选择是或不是final,不论类是否被定义为final,相同的规则都适用于定义为final的域,然而,由于final类禁止继承,所以final类中所有的方法都是隐式指定为是final的。
6. 初始化过程
父类静态方法(父类静态代码块)-->子类静态方法(子类静态代码快)-->父类成员(父类代码块)-->父类构造方法-->子类成员(子类代码块)-->子类构造方法