7、复用类
复用代码是Java众多引人注目的功能之一。
组合和继承
不需要重新写,只需要在新类中调用其他的类即可,这样就能保证原有的程序不被破坏掉。这种方法称为组合。
第二种方法则通过新类继承以前的类,来达到扩展的目的。
7.1 组合语法
本书之前已经使用了多次组合技术。
class Door{
int height;
String color;
}
class Window{
int height;
int width;
}
class House{
Door d = new Door();
Window w1 = new Window();
Window w2 = new Window();
}
构造了三个类,分别是Door门类,Window窗户类,House房子类。
一个房子有一个Door门,2个窗户。
7.2 继承语法
继承是所有面向对象(OOP)语言不可缺少的部分。
当创建一个类时,总是在继承,如果不写,则默认继承Object类。
继承使用extends 实现。
7.2.1 一个编译单元(java文件)中可以有多个main方法
并且可以指定需要允许的main方法。
这方便测试
class GrandFather{
static protected int age=18;
public static int a = 12;
public static void main(String[] args) {
System.out.println("hello");
}
}
public class TestString extends GrandFather{
public static void main(String[] args){
System.out.println(GrandFather.a);
}
}
7.2.2 初始化基类
初始化基类的顺序如下代码展示
class Art{
Art(){System.out.println("Art");}
}
class Drawing extends Art{
Drawing(){System.out.println("Drawing");}
}
public class Cartoon extends Drawing{
Cartoon(){System.out.println("Cartoon");}
public static void main(String[] args){
Cartoon x = new Cartoon();
}
}
//输出结果
// Art
// Drawing
// Cartoon
先执行父类的父类,再执行父类,最后执行子类。
带参数的构造器
class Game{
Game(int i){ System.out.println("Game"); }
}
class BoardGame extends Game{
BoardGame(int i){
super(i);
System.out.println("BoardGame");
}
}
public class Chess extends BoardGame{
Chess(){
super(11);
System.out.println("Chess");
}
public static void main(String[] args){
Chess x = new Chess();
}
}
//输出结果
// Game
// BoardGame
// Chess
7.3 代理
第三种关系称为代理,Java并没有提供对它的支持。这是基础与组合之间的中庸之道。
例如一个挂式空调有开和关,但是不能直接操作空调,所以需要一个遥控器类,这个遥控器就是代理。
class AirCondi(){
int state = 0;
void open(){
this.state = 1;
}
void close(){
this.state = 0;
}
}
class Controller(){
AirCondi airCondi = new AriCondi();
void open(){
AirCondi.open();
}
void open(){
AirCondi.close();
}
}
尽管Java语言比支持代理,但是有很多开发工具却支持代理,比如说JetBrains IDEA
7.4 结合使用组合和继承
同时使用组合和继承是很常见的事情。
7.4.1 确保正确清理
作者再次强调,清理内存的重要性。但是Java并没有析构函数,所以需要自己手动编写。
7.4.2 名称屏蔽,继承中的重载
如果Java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本。
因此,继承在当前类和子类中都可以起作用
class Homer{
void doh(char c){
System.out.println("char");
}
void doh(float f){
System.out.println("float");
}
}
class Milhouse{ }
class Bart extends Homer{
void doh(Milhouse m){
System.out.println("Milhouse")
}
}
public class Hide{
public static void main(String[] args){
Bart b = new Bart();
b.doh(1);
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
}
// 输出结果
// float
// char
// float
// Milhouse
7.5 在组合与继承之间选择
组合和继承都是在新的类中放置子对象,组合是显示的,继承是隐式的。
组合:新类中使用现有的类,而非他的接口的形式。
继承:使用某个现有的类,并开发一个它的特殊版本。
组成:有 has-a 的概念
继承:有 is-a 的概念
7.6 protected关键字
具体地说明protected关键字,protected可以在本包中和其他包的子类中使用。
在讲述完继承之后,protected就有了其具体的意义。
7.7 向上转型
新类是现有类的一种类型。
即中国人是地球人,其中中国人类继承了地球人类
class EarthPerson{
void speak(){
System.out.print("Hello");
}
}
class Chinese extends EarthPerson{}
public class Test{
public static void main(String[] args){
EarthPerson p = new Chinese();
p.speak();
}
}
// 输出结果
// Hello
父类的指针,可以指向其子类。
7.7.1 为什么称为向上转型
该术语的使用是有其历史原因的,之前的继承图都是子类指向父类 子类 -> 父类
,所以用父类的指针指向子类就称为向上转型。
7.7.2 再论组合与继承
在面向对象编程中,尽可能将属性和方法放入类中。
但是继承技术需要慎用,判断是否使用继承的标准就是,我需要进行向上转型吗
7.8 final关键字
final意味着无法改变。
提出final的主要目的是为了设计和效率。
可能使用到final的三种情况(1)数据 (2)方法 (3)类
7.8.1 final数据
有些数据不变是很有用的,如(1)一个永不改变的编译时常量 (2)在运行时被初始化的值,你不希望它被改变。
-
对于基本数据类型,使用final意味着无法改变
-
如果既是static又是final,则只占据一段不能改变的存储空间。
-
对于对象的引用,使用final意味着这个指向无法改变,但是对象本身是可以变的,这一点作者也确实感到疑惑。
-
如果需要让对象不变,则需要自己编写类以取得恒定不变的效果。
空白final
Java允许生成"空白final",空白final指的是声明为final但又没有给定初值的域。
class Poppet{
private final int j; //一个空白final数据
}
final 参数
final参数意味着你无法在方法中改变参数所指向的对象。
7.8.2 final方法
使用final方法的原因
(1)不允许子类修改(覆盖)方法
(2)提高效率,但是随着Java虚拟机的升级,提高效率的效果并不明显。
final和private关键字
类中的所有private方法都是final的。因为private方法无法被子类覆盖。所以private方法并不需要增加final关键字,因为这毫无意义。
7.8.3 final类
final类意味着该类无法被继承。
final类意味着类中的所有方法都是final的。
7.8.4 有关final的忠告
要预见一个类是否被复用是很困难的,特别是对于一个通用的类更是如此。如果将一个方法指定为final,防止其他程序员在项目中通过继承来复用你的类。
7.9 初始化及类的加载
Java中的所有事物都是对象,这使得加载变得容易,每个类的编译代码都存在于它自己的独立文件中。该文件只在需要使用程序代码时才会被加载(如新建一个对象,或者使用static域的static方法)
7.9.1 继承与初始化
了解包括继承在内的初始化全过程,是很有必要的。
class Insect{
Insect(){
}
static int printInit(){
}
}
public class Beetle extends Insect{
public static void main(String[] args){
// ...
}
}
(1)**加载主类:**运行Java时,第一件事就是访问Beetle的main方法,于是加载器启动找出Beetle类的编译代码。
(2)**加载父类:**在加载时发现存在父类,于是继续加载Insect类。如果Insect类还存在父类,那么就继续加载,以此类推。
(3)从上往下初始化类所有类加载完毕后,就开始创建对象。 所有基本类型设置为默认值,对象设置为null。从上往下调用构造方法,即先调用父类的构造方法,然后再调用子类的。
7.10 总结
继承和组合都能从现有的类型生成新的类型。组合一般时将现有类型作为新类型底层实现的一部分加以复用,而继承复用的是接口。
在使用继承时,可以向上转型为父类。这对多态来说至关重要。
在开始设计时,一般优先使用组合,只有在必要时才用继承。