Java编程思想—第7章 复用类

  • 第一种方法非常直观:只需在新的类中产生现有类的对象。由于新的类是由现有类的对象所组成,所以这种方法称为组合,该方法知识复用了现有程序代码的功能,而非它的形式。
  • 第二种方法则更细致一些,他按照现有类的类型来创建新类。无需改变现有类的形式,采用现有类的形式并在其中添加新代码。这种神奇的方式称为继承,而且编译器可以完成其中大部分工作。

7.1 组合语法

  1. 只需将对象引用置于新类中即可
  2. 每一个非基本类型的对象都有一个toString()方法,而且在编译器需要一个String而你却只有一个对象时,该方法便会被调用

7.2 继承语法

  1. 当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承
class Cleanser{
    //内容
}

public class Detergent extends Cleanser{
    //内容
}
  1. 可以为每个类都创建一个main()方法,可使每个类的单元测试都变得简便易行。
  2. 即使是一个程序中含有多个类,也只有命令行所调用的那个类的main()方法会被调用。即使一个类只具有包访问权限,其public main()仍然是可访问的。
  3. 为了继承一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public
  4. Java用super关键字表示超类的意思,当前类就是从超类继承来的。为此,表达式super.scrub()就是调用基类中的scrub()
  5. 初始化基类
    a. 当创建了一个导出类的对象时,该对象包含了一个基类的子对象。基类的子对象被包装在导出类对象内部
    b. 在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。Java会自动在导出类的构造器中插入对基类构造器的调用。
    c. 构建过程是从基类“向外”扩散的,所以基类导出类构造器可以访问它之前,就已经完成了初始化。
  6. 带参数的构造器
    a. 如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显示地编写调用基类构造器的语句,并且配以适当的参数列表。
    b. 调用基类构造器必须是你在导出类构造器中要做的第一件事(如果你做错了,编译器会提醒你)

7.3 代理

  1. 第三种关系称为代理,Java并没有提供对它的直接支持。这是继承与组合之间的中庸之道,因为我们将一个成员置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该对象的所有方法(就像继承)。
public class SpaceShipControls{
    void up(int velocity){}
    void down(int velocity){}
    void left(int velocity){}
    void right(int velocity){}
}

//继承模式
public class SpaceShip extends SpaceShipControls{
    private String name;
    public SpaceShip(String name){
        this.name = name;
    }
}

//代理模式
public class SpaceShipDelegation{
    private String name;
    private SpaceShipControls controls = new SpaceShipControls();
    public SpaceShipDelegation(String name){
        this.name = name;
    }
    
    //代理模式,想实现多少实现多少方法
    public void up(int velocity){
        controls.up(velocity);
    }
    
    public void down(int velocity){
        controls.down(velocity);
    }
}
  1. 可以看到,上面的方法是如何传递给了底层的controls对象,而其接口由此也就与使用继承得到的接口相同了。但是我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中的方法的某个子集。

7.4 结合使用组合和继承

  1. 虽然编译器强制你去初始化基类,并且要求你要在构造器起始处就要这么做,但是它并不监督你必须即将成员对象也初始化,因此在这一点上你自己必须时刻注意。
  2. 确保正确清理
    a. 在清理方法中,还必须注意对基类清理方法和成员对象清理方法的调用顺序,以防某个子对象依赖于另一个子对象情形的发生
    b. 最好的办法是除了内存以外,不能依赖垃圾回收器去做任何事。如果需要进行清理,最好是编写你自己的清理方法,但不要使用finalize()
  3. 名称屏蔽
    a. 如果Java的基类拥有某个已被多次重载的方法名称,那么在导出类总重新定义该方法名称并不会屏蔽其在基类中的任何版本
    b. 使用与基类完全相同的特征签名返回类型覆盖具有相同名称的方法,是一件极其平常的事。
    c. @Override注解可以防止你在不想重载时而意外进行了重载

7.5 在组合和继承之间选择

  1. 组合和继承都允许在新的类中放置子对象,组合是显示地这样做,而继承则是隐式地做。
  2. 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。
  3. 在继承的时候,使用某个现有类,并开发一个它的特殊版本

7.6 protected关键字

  1. 在实际的项目中,经常会想要将某些事物尽可能对这个世界隐藏起来,但仍然允许导出类的成员访问它们
  2. protected,它指明”就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的“(protected也提供了包内访问权限
  3. 尽管可以创建protected域,但是最好的方式还是将域保持为private;你应当一直保留“更改底层实现”的权利。然后通过protected方法来控制类的继承者的访问权限

7.7 向上转型

  1. “为新的类提供方法”并不是继承技术中最重要的方面,其最重要的方面是用来表现新类和基类之间的关系。这种关系可以用”新类是现有类的一种类型“这句话加以概括。
  2. 为什么称为向上转型:向上转型是从一个较专用类型向较通用类型转换,所以总是很安全的。
  3. 再论组合与继承:到底是该用组合还是继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型

7.8 final关键字

  1. final数据
    a. 一个既是static又是final的域只占据一段不能改变的存储空间,用大写表示
    b. 用于对象引用,final使引用恒定不变,但对象内容可以被改变
    c. Java允许生成“空白final”,所谓空白final是指被声明为final但又未给定初值的域
    d. 必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是finanl域在使用前总是被初化的原因所在
    e. Java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法在方法中更改参数引用所指向的对象:你可以读参数,但却无法修改参数。这一特性主要用来向匿名内部类传递数据。
  2. final方法
    a. 使用final方法的原因有两个。
    b. 第一个原因是把方法锁定,以防止任何继承类修改它的含义。想要确保在继承中使方法行为保持不变,并且不会被覆盖
    c. 第二个原因是效率,现在已经不用的,效率问题交给Java虚拟机
    d. 类中所有的private方法都隐试指定为final的,对private方法添加final修饰词,没有任何意义
    e. “覆盖”只有在某方法是基类的接口的一部分时才会出现
  3. final类
    a. 当将某个类的整体定义为final时(通过将关键字final置于它的定义之前),就表明了你不打算继承该类,而且也不允许别人这样做。
  4. 有关final的忠告
    a. 要预见类是如何被复用的一般是很困难的,特别是对于一个通用类而言更是如此
    b. 在设计类时,将方法指明是final,应该说是明智的

7.9 初始化及类的加载

  1. 类的代码在初次使用时才加载
  2. 初次使用之处也是static初始化发生之处。所有的static对象和static代码段都会在加载时依程序中的顺序而依次初始化。
  3. 继承与初始化:在对它进行加载的过程中,编译器注意到它有一个基类,于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生

7.10 总结

  1. 继承和组合都能从现有类型生成新类型。
  2. 组合一般是将现有类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口
发布了77 篇原创文章 · 获赞 20 · 访问量 5760

猜你喜欢

转载自blog.csdn.net/qq_42396168/article/details/105053771
今日推荐