java基础学习(六)组合与继承

使用组合和继承,可以利用现有类型生成新类型来复用代码,而不必再从头开始编写,能够达到使用类而不破坏现有程序代码。

一、组合
只需在新的类中产生现有类的对象,新的类是由这些现有类的对象所组成,只是复用了现有程序代码的功能,而非它的形式。
只需将对象引用置于新类中即可。对于非基本类型的对象,必须将其引用置于新的类中;但是可以直接定义基本类型数据。

public class Springkler {
//将各种现有类的对象组合在一起,产生新类
    private String value;
    //可以在定义对象的时候,直接初始化
    private WaterSource waterSource=new WaterSource();
    //基本类型可以直接定义,有默认值
    private int i;
    public String toString(){
        return "String value="+value+"   "+
                "Object Value="+waterSource+"   "+
                "Integer value="+i+"   ";
    }

    public static void main(String[] args) {
        Springkler sk=new Springkler();
        System.out.println(sk.toString());
    }
}

class WaterSource{
    private String str;
    WaterSource(){
        str="Constructor";
    }
    public String toString(){
        return  str;
    }
}
结果:
String value=null   Object Value=Constructor   
Integer value=0   

每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你只有一个对象时,该方法会被调用。

为了避免增加不必要的负担,编译器并不是为每个引用都创建默认对象,如果想初始化这些引用,可以:
1、在对象定义的时候。总能够在构造器被调用之前被初始化。
2、在类的构造器中。
3、惰性初始化,在使用这些对象之前进行初始化,可以减少额外的负担。
4、使用实例初始化。

public class Bath {
    //在对象定义的地方初始化
    private  String value="This is a Test";
    private    Soap soup=new Soap();

    private  int i;
    Bath(int i){
        //在类的构造器中初始化
        this.i=i;
    }

    public static void main(String[] args) {
        //在使用这些对象之前进行初始化
        String str="test only ";
        System.out.println(str);
    }
}

class Soap{
    Soap(){
        System.out.println("Soup Constructor");
    }
}

二、继承
当创建一个类时,总是在继承,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。

public class Detergent extends Cleanser{
    //覆写scrub()方法
    @Override
    public void scrub() {
        append("  Detergent.scrub()方法 ");
        //调用基类的方法
        super.scrub();
    }

    public void form(){
        append("  form()  ");
    }

    public static void main(String[] args) {
        Detergent x=new Detergent();
        x.dilute();
        x.apply();
        x.scrub();
        x.form();
        System.out.println(x);
        //直接调用基类中的main方法
        Cleanser.main(args);
    }
}

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()");
    }
    public String toString(){
        return s;
    }

    public static void main(String[] args) {
        Cleanser x=new Cleanser();
        x.dilute();
        x.apply();
        x.scrub();
        System.out.println(x);

    }
}

上例中,子类从基类导出,子类可以自动获得这些public方法,尽管并不能看到这些方法在子类中的显式定义。因此,继承可以视作是对类的复用。

导出类就像是一个与基类具有相同接口的新类,或许还会有一些额外的方法和域。但是继承并不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。区别在于,后者直接通过基类创建对象,而前者是被包装在导出类对象内部。

在构造器中调用基类构造器来对基类子对象进行初始化操作。
Java会自动在导出类的构造器中插入对基类构造器的调用:

public class Cartoon extends Drawing {
    public Cartoon(){
        System.out.println("Cartoon Constructor");
    }

    public static void main(String[] args) {
        Cartoon cartoon=new Cartoon();
    }
}
class Drawing extends Art{
    Drawing(){
        System.out.println("Drawing Constructor");
    }
}
class Art{
    Art(){
        System.out.println("Art Constructor");
    }
}
结果:
Art Constructor
Drawing Constructor
Cartoon Constructor

构建过程是从基类向外扩散的,就像在导出类的构造函数中存在一个隐式的super()方法。基类在导出类构造器可以访问它之前,就已经完成了初始化。即使导出类使用默认的无参构造器,该构造器还是会调用基类的构造器。

如果想调用一个带参数的基类构造器,则必须用关键字super显式编写调用基类构造器的语句,并且配以适当的参数列表:
必须要将super()方法放在构造器的第一句。

public class Cartoon extends Drawing {

    public Cartoon(){
        super("test");
        System.out.println("Cartoon Constructor");
    }

    public static void main(String[] args) {
        System.out.println("进入main方法");
        Cartoon cartoon=new Cartoon();
    }
}
class Drawing extends Art{
    Drawing(String str){
        super(str);
        System.out.println("Drawing Constructor");
    }
}
class Art{
    Art(String str){
        System.out.println("str = [" + str + "]");
        System.out.println("Art Constructor");
    }
}
结果:
进入main方法
str = [test]
Art Constructor
Drawing Constructor
Cartoon Constructor

说明:基类的构造器与导出类的构造器要保持一致,也就是,当在导出类构造器中使用隐式或显式无参的super()方法时,基类中必须要有无参的构造器;当在导出类构造器中使用显式的super(Object o),那基类中必须要提供该有参构造器。

三、代理
Java并没有提供对代理的直接支持。可以将一个成员对象置于所要构造的新类中,但是通过选择只提供在该成员对象中的方法的某个子集,而不是在新类中暴露该成员对象的所有方法。

public class SpaceShipDelegation {
    private String name;
    private SpaceshipControl sc=new SpaceshipControl();
    public SpaceShipDelegation(String name){
        this.name=name;
    }
    //只提供成员对象sc中的方法的子集,而不是所有的方法
    public void up(int velocity){
        sc.up(velocity);
    }
    public void down(int velocity){
        sc.down(velocity);
    }
    public void forward(int velocity){
        sc.forward(velocity);
    }
}

class SpaceshipControl{
    void up(int velocity){};
    void down(int velocity){};
    void forward(int velocity){}
    void back(int velocity){}
}

在java中,习惯只是忘掉而不是销毁对象,并且让垃圾回收器在必要时释放其内存。当不知道垃圾回收器何时将会被调用,或者是否将被调用。需要在其生命周期内执行一些必需的清理活动。

public class CADSystem extends Shape{
    Circle c;
    CADSystem(int i){
        super(i);
        c=new Circle(i);
        System.out.println("CADSystem Constructor");
    }
    void dispose(){
        //与构造器的顺序相反
        System.out.println("CADSystem dispose");
        c.dispose();
        super.dispose();
    }

    public static void main(String[] args) {
        CADSystem cad=new CADSystem(43);
        try{
            //do something
        }finally{
            cad.dispose();
        }
    }
}

class Circle extends Shape{
    Circle(int i){
        super(i);
        System.out.println("Circle Constructor");
    }
    void dispose(){
        //清理的顺序与构造器相反
        System.out.println("Circle dispose");
        super.dispose();
    }
}

class Shape{
    Shape(int i){
        System.out.println("Shape Constructor");
    }
    //自定义的清理方法
    void dispose(){
        System.out.println("Shape dispose");
    }
}
结果:
//基类Shape构造器方法
Shape Constructor
//类Circle中构造器方法
Shape Constructor
Circle Constructor
//类CADSyatem中构造器方法
CADSystem Constructor
//类CADSystem中清除方法
CADSystem dispose
//类Circle中清除方法
Circle dispose
Shape dispose
//基类Shape中清除方法
Shape dispose

将清理方法的调用放在finally子句中,表示该方法一定要被调用。
为了防止某个子对象依赖于另一个子对象的情形发生,执行清理顺序时:首先,执行类的所有特定的清理动作,其顺序同生成顺序相反;然后,调用基类的清理方法。

垃圾回收器可能永远也无法被调用,即使被调用,也可能以任何它想要的顺序来回收对象。最好的办法是除了内存以外,不能依赖垃圾回收器去做任何事。如果需要清理,最好编写自己的清理方法,但是不要使用finalize()方法。

名称屏蔽
基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称,并不会屏蔽其在基类中的任何版本。无论是在该层或者它的基类中对方法的定义,重载机制都可以正常工作:

public class Hide extends Home{
    void doh(long l){
        System.out.println("doh(long)");
    }

    public static void main(String[] args) {
        Hide hide=new Hide();
        //所有重载方法都是可用的
        hide.doh(999l);
        hide.doh('w');
        hide.doh(1);
    }
}
class Home{
    void doh(char c){
        System.out.println("doh(char)");
    }
    void doh(int i){
        System.out.println("doh(int)");
    }
}
结果:
doh(long)
doh(char)
doh(int)

新增加的@Override注解,它并不是关键字,但可以把它当做关键字使用。当想要覆写某个方法时,可以选择添加这个注解,但是不小心重载而非覆写该方法时,就会报错:

public class Hide extends Home{
    void doh(long l){
        System.out.println("doh(long)");
    }
    @Override
    void doh(char c){
        System.out.println("override the 方法");
    }

    public static void main(String[] args) {
        Hide hide=new Hide();
        hide.doh(999l);
        hide.doh('w');
        hide.doh(1);
    }
}
//结果:
doh(long)
override the 方法
doh(int)

组合与继承之间的选择
组合和继承都允许在新的类中放置子对象,组合是显式地这样做,而继承是隐式的做。

组合技术通常用于想在新类中使用现有类的功能而非它的接口。即在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口。

继承意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。

is-a(是一个)的关系是用继承来表达的;
has-a(有一个)的关系是用组合来表达的。

到底是使用组合还是继承的一个清晰的判断方法是:是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必须的。

protected关键字
就普通类用户而言,这是private的;但是对于任何继承于此类的导出类其他位于同一个包内的类来说,它却是可以访问的。

向上转型
传统继承图的绘制如下:将根置于页面的顶端,然后逐渐向下:
这里写图片描述
由导出类转型为基类,在继承图上是向上移动,一次称为向上转型。
向上转型是从一个比较专用的类型向通用类型的转换,所以是安全的,也就是说导出类是基类的一个超集。它可能比基类含有更多的方法,但它必须至少具备基类中所含有的方法。它们之间的关系可以用”新类是现有类的一种类型”加以概括。

向上转型是安全的,因为基类不会具有大于导出类的接口,通过基类接口发送消息保证都能被接受。但是对向下转型,避免发出该对象无法接受的消息。

final关键字
1、final数据
向编译器告知一块数据是恒定不断的。
一个永不改变的编译期常量,编译器可以将该常量值代入到可能用到它的计算式中,也就是在编译时执行计算式,减轻运行时的负担。
java中这类常量必须是基本数据类型,并且以关键字final表示,对这个常量进行定义时,必须对其进行赋值。但是不能因为某数据是final,就认为在编译时可以知道它的值。可以在运行时知道它的值也行。

一个即使static又是fianl的域,只占据一段不能改变的存储空间。用大写字母命名,并且字与字之间用下划线隔开。
对于只有final的域,使用驼峰命名法。

对对象引用运用final,会使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。但是对象自身却是可以被修改的。数组也是对象,同样适用。

public class FinalData {
    private int valueOne=9;
    //final和static final都是带有编译时数值的final基本类型,都可以作为编译期常量,没有重大区别
    private final int valueTwo=99;
    private static final int VALUE_THREE=999;
    //在运行时知道数值,而不是在编译时
    private static final int VALUE_Four= new Random(23).nextInt(10);
    private final int valueFive= new Random(53).nextInt(15);

    //final使引用恒定不变
    private Value obj=new Value(11);
    private final Value obj2=new Value(22);
    private static final Value OBJ_3=new Value(33);
    FinalData(){

    }
    public static void main(String[] args) {
        FinalData fd=new FinalData();
        fd.valueOne++;
        //final基本类型值不可改变
        /**
        fd.valueTwo++;
        fd.VALUE_THREE++;
        fd.VALUE_Four++;
        fd.valueFive++;
         */
        //final非基本类型,引用恒定,但是对象值可变。
        fd.obj=new Value(666);
        //引用不可以改变
        /**
        fd.obj2=new Value(7777);
        fd.OBJ_3=new Value(8888);
        */
        //对象的值可以改变
        fd.OBJ_3.setI(787878);
        int obj3Value=fd.OBJ_3.getI();
        System.out.println("Value类型值="+obj3Value);

    }
}
class Value{
    int i;
    public Value(int i){
        this.i=i;
    }
    public void setI(int j){
        i=j;
    }
    public int getI(){
        return i;
    }
}
结果:
Value类型值=787878

空白final
java允许生成空白final,空白final是指被声明为final但是又未给定初值的域。无论什么情况下,编译器都确保空白final在使用前必须被初始化。因此,必须在域的定义处或者每个构造器中对final进行赋值。

public class BlankFinal {
    private final int i=0;
    private final int j;
    private final Poppet p;
    public BlankFinal(){
        j=1;
        p=new Poppet();
    }
}

class Poppet{
    Poppet(){}
}

final参数
java允许在参数列表中以声明的方式将参数指明为final。意味着无法在方法中更改参数引用所指向的对象。

public class FinalAgs {
    void with(final Game g){
        //不能更改参数所指向的对象
        //g=new Game();
    }
    void without(Game g){
        g=new Game();
        g.spin();
    }
    //可以读参数,但是无法修改参数
    int find(final int i){
        //无法修改
       // i++;
        //可以读参数
        return i+1;
    }
}
class Game{
    public  void spin(){}
}

final方法
想要确保在继承中使方法行为保持不变,并且不会被覆盖。把方法锁定,以防任何继承类修改它的含义。

final和private关键字
类中所有的private方法都是隐式的指定为final的。由于无法取用private方法,所以也就无法覆盖它。

final 类
当将某个类整体定义为final时,不打算继承该类,而且也不允许其他类继承。不希望它有子类。
final类的域可以自定义为是否是final。不论类是否被定义为final,相同的规则适用于final域。

初始化加载
每个类的编译代码都存在于它自己的独立的文件中,该文件只在需要使用程序代码时才会被加载。

类的代码在初次使用时才加载,是指加载发生于创建类的第一个对象之时,但是当访问static域或static方法时,也会发生加载。
构造器也是static方法,类是在其任何static成员被访问时加载的。

所有的static对象和static代码段都会在加载时以程序中的顺序而依次初始化。定义为static的东西只会被初始化一次。

public class Beetle extends Insect{
    private int k=printInit("Beetle.k Initialized");
    public Beetle(){
        System.out.println("Beetle Constructor");
    }
    private  static int x2=printInit("static Beetle.x2 initialized");

    public static void main(String[] args) {
        System.out.println("进入 main 方法");
        Beetle b=new Beetle();
        b.test();
        b.free();
        b.find();
    }
    public void test(){
        System.out.println("test()");
    }
    public static void find(){
        System.out.println("find()");
    }
}

class Insect{
    private int i=printInit("Insect.i initialized");
    protected int j;
    Insect(){
        j=39;
        System.out.println("Insect Constructor");
    }
    private static int x1=printInit("static Insect.x1 initialized");
    static int printInit(String s){
        System.out.println(s);
        return 47;
    }

    public void free(){
        System.out.println("free()");
    }
}
//结果
//基类 static 域初始化,并且 类开始加载
static Insect.x1 initialized
//导出类 static 域初始化,并且 类开始加载
static Beetle.x2 initialized
//导出类main方法
进入 main 方法
//基类 域 初始化
Insect.i initialized
//基类构造器
Insect Constructor
//导出类 域 初始化
Beetle.k Initialized
//导出类构造器
Beetle Constructor
//按定义的顺序调用方法
test()
free()
find()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值