Java面向对象(三)

一. 组合 vs 继承

继承

  1. is-a 的关系,具有共同数据成员和方法,派生类在父类基础上有一定的扩展或区别
  2. 优缺点:
    • 优点:
      • 子类能自动继承父类的接口
      • 创建子类的对象时,无须创建父类的对象
    • 缺点:
      • 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
      • 支持扩展,但是往往以增加系统结构的复杂度为代价 - 不支持动态继承。在运行时,子类无法选择不同的父类
      • 子类不能改变父类的接口
  3. 使用场景:
    • 父类只是给子类提供服务,并不涉及子类的业务逻辑
    • 层级关系明显,功能划分清晰,父类和子类各做各的。
    • 父类的所有变化,都需要在子类中体现,也就是说此时耦合已经成为需求
    • 新类需要向基类进行向上转型

组合

  1. has-a 的关系,强调一种包含关系,类与类构成整体和部分
  2. 优缺点:
    • 优点:
      • 不破坏封装,整体类与局部类之间松耦合,彼此相对独立
      • 具有较好的可扩展性 - 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象
      • 整体类可以对局部类进行包装,封装局部类的接口,提供新的接口
    • 缺点:
      • 整体类不能自动获得和局部类同样的接口
      • 创建整体类的对象时,需要创建所有局部类的对象
  3. 使用场景:
    • 优先考虑组合
    • 对象间需要互发消息
    • 一个对象需要使用另一个对象的功能或数据

二. 两个对象间互发消息

消息的实质:引用向对象发出的服务请求,是对数据成员和方法的调用

  1. 方式:类的组合,对象通过成为另一个对象的数据成员从而具备发送消息的条件,就可以向另一个对象发送消息
  2. 举例:
class FighterPlane {
    String name;
    int missileNum;
    public Message(String _name,int _missileNum){
        name = _name;
        missileNum = _missileNum;
    }
    public void fire(){
        if(missileNum > 0){
            System.out.println("now fire a missile!");
            missileNum -= 1;
        }else{
            System.out.println("No missile left!");
        }
    }

}
class A{
    FighterPlane fp;
    public A(FighterPlane  fpp){
        this.fp = fpp; // A对象中有了 FighterPlane 对象的引用
    }
    public void invoke(){
        //A 对象发送消息给 FighterPlane 的对象
        System.out.println(fp.name);
    }
}
public class Run{
    public static void main(String[] args){
        FighterPlane ftp = new FighterPlane ("su35",10);
        // 产生A对象,并将 ftp 作为对象引用传入
        A a = new A(ftp);
        // 发送消息,产生调用关系
        a.invoke();
    }

三. 多态

运行时多态

一种动态绑定,使用父类引用指向子类对象,再调用某一父类中的方法时,不同子类会表现出不同结果

实际上就是方法覆盖

  1. 作用:具有极好的扩展性,可维护性高
  2. 举例:
// 父类
class Bird{
    public void moo(){
        System.out.println("鸟叫声");
    }
}
// 子类,鹦鹉类
class Parrot extends Bird{
    public void moo(){
        System.out.println("鹦鹉的叫声");
    }
}
// 子类,麻雀类
class Sparrow extends Bird{
    public void moo(){
        System.out.println("麻雀的叫声");
    }
}
public class Test {
    public static void main(String[] args) {
        Bird b1 = new Parrot(); // 父类引用指向子类对象
        b1.moo(); // 调用父类的方法,实际上调用子类覆写的方法
        Bird b2 = new Sparrow();// 父类引用指向子类对象
        b2.moo(); // 调用父类的方法,实际上调用子类覆写的方法
    }
}
/*
运行结果:
	鹦鹉的叫声
	麻雀的叫声
*/

编译时多态

一种静态绑定,根据参数的数据类型、个数和次序,在编译时就能够确定执行重载方法中的哪一个

实际上就是方法重载

四. 抽象类 vs 接口

抽象类

  1. 概念:abstract 修饰符修饰的类,不包含足够的信息来描绘具体的对象
  2. 特点:
    • 可以有零个或多个抽象方法,也可以包含非抽象方法
    • 不能被实例化,即不能创建对象,但可以有声明,声明能引用所有具体子类对象
    • 抽象类和具体子类的关系只能是被继承与继承关系
    • 抽象方法没有方法体,抽象类子类可以不实现抽象类的所有抽象方法,但此时必须也作为抽象类,且不能和父类有同名的抽象方法
    • 在抽象类中,非抽象方法可以调用抽象方法
    • abstract 不能和 final 修饰同一个类,也不能和 staticprivatenative 修饰同一个方法
  3. 使用场景
    • 在继承方面应用:简化子类定义,增加系统结构关系的清晰度
    • 在引用具体子类对象方面应用:抽象类的声明可以引用具体子类对象

接口

  1. 概念:一种是可以被引用调用的方法;另一种是功能方法说明的集合 interface

  2. 特点:

    • 接口中数据成员全是 public fianl static,即静态常量
    • 接口没有构造方法,所有成员都是抽象方法,默认是public abstract修饰
    • 接口也具有继承性,可以多重继承,用关键字extends声明
    • 在类中,用implements关键字来实现接口。一个类可以实现多个接口,在implements后用逗号隔开多个接口的名字。一个接口也可被多个类来实现
    • 接口和子接口都不能有自己的实例对象,但可以有声明,可以引用实现类对象
    • 如果实现某接口的类不是abstract修饰的抽象类,则在类的定义部分必须实现接口的所有抽象方法,而且方法头部分应该与接口中的定义完全一致
    • 如果实现接口的类是abstract类,则它可以不实现该接口的所有方法。但对于抽象类的任何一个非抽象的子类而言,接口中的所有抽象方法都必须实现
    • l类在实现接口的抽象方法时,必须显式使用public修饰符,否则将被警告为缩小了接口中定义的方法的访问控制范围。

    注意:在JDK8中,接口也可以定义静态方法和默认非静态方法,可以直接用接口名调用静态方法,实现类可以调用默认非静态方法。如果同时实现两个接口,接口中定义了一样的默认方法,则实现类必须重写默认方法,不然会报错。

  3. 使用场景:

    • 约束多个实现类具有统一的行为,但是不在乎每个实现类如何具体实现
    • 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识
    • 实现类需要具备很多不同的功能,但各个功能之间可能没有任何联系
    • 使用接口的引用调用具体实现类中实现的方法(多态)

异同

  1. 共同点:二者都可具有抽象方法,都不能实例化,但都可以有自己的声明,并能引用子类或实现类对象

  2. 不同点:

    特性接口抽象类
    组合新类可以组合多个接口只能继承单一抽象类
    状态不能包含属性(除了静态属性,不支持对象状态)可以包含属性,非抽象方法可能引用这些属性
    默认方法和抽象方法不需要在子类中实现默认方法,默认方法可以引用其他接口的方法必须在子类中实现抽象方法
    构造器没有构造器可以有构造器
    可见性隐式 public可以是protected 或 友元

五. 接口应用

求矩形、三角形、圆面积和周长

import java.applet.Applet;
import java.awt.*;

// Shapes 接口
interface Shapes {
    public abstract double getArea();
    public abstract double getPerimeter();
}
// 矩形
class Rect implements Shapes{
    int x,y;
    double width,height;
    public Rect(int x,int y,double width,double height) {
        this.x=x;
        this.y=y;
        this.width=width;
        this.height=height;
    }

    public double getArea() {
        return width*height;
    }

    public double getPerimeter() {
        return 2*(width+height);
    }
}
// 三角形
class Triangle implements Shapes {
    int baseA,baseB,baseC;
    double m;

    public Triangle(int x,int y,int z) {
        baseA=x;
        baseB=y;
        baseC=z;
        m=(baseA+baseB+baseC)/2.0;
    }

    public double getArea() {
        return (Math.sqrt(m*(m-baseA)*(m-baseB)*(m-baseC)));
    }

    public double getPerimeter() {
        return (double)(baseA+baseB+baseC);
    }
}
// 圆
class Circle implements Shapes{
    int x,y;
    double d,r;
    public Circle(int x,int y,int width) {
        this.x=x;
        this.y=y;
        r=width/2.0;
        d=(double)width;
    }

    public double getArea() {
        return (r*r*Math.PI);
    }

    public double getPerimeter() {
        return (d*Math.PI);
    }
}
public class RunShape extends Applet {
    Rect rect=new Rect(5,15,25,25);
    Triangle tri=new Triangle(5,5,8);
    Circle cir=new Circle(13,90,25);

    private void drawArea(Graphics g,Shapes s,int a,int b) {
        g.drawString(s.getClass().getName()+"Area"+s.getArea(), a,b);
    }

    private void drawPerimeter(Graphics g,Shapes s,int a,int b) {
        g.drawString(s.getClass().getName()+"Perimeter"+s.getPerimeter(),a,b);
    }

    public void paint(Graphics g) {
        g.drawRect(rect.x, rect.y,(int)rect.width, (int)rect.height);
        drawArea(g,rect, 50, 35);
        drawPerimeter(g,rect,50,55);
        drawArea(g,tri,50,75);
        drawPerimeter(g,tri,50,95);
        g.drawOval(cir.x-(int)cir.r,cir.y-(int)cir.r ,(int)cir.d,(int)cir.d);
        drawArea(g,cir,50,115);
        drawPerimeter(g,cir,50,135);
    }
}

六. 运算符instanceof

用于确定一个实例对象是否属于一个特定的类,返回 Boolean 值

二目运算符,左边操作元为对象,右边操作元为类

class Uncle{}
class Pare{}
class Pare1 extends Pare{}
class Pare2 extends Pare1{}
public class Test {
    public static void main(String[] args) {
        Uncle  u  = new Uncle();
        Pare   p  = new Pare();
        Pare1  p1 = new Pare1();
        Pare2  p2 = new Pare2();
        
        //本类对象是本类的实例
        if ( p instanceof Pare) {
            System.out.println("p instanceof Pare");
        }
        //子类对象是父类的实例
        if (!( p1 instanceof Pare)) {
            System.out.println("p1 not instanceof Pare");
        } else {
            System.out.println("p1 instanceof Pare");
        }
        //父类对象不是子类的实例
        if (p1 instanceof Pare2) {
            System.out.println("p1 instanceof Pare2");
        } else {
            System.out.println("p1 not instanceof Pare2");
        }
        /* 报错:
        	java: 不兼容的类型: Pare无法转换为Uncle
        if (p instanceof Uncle) {
            System.out.println("p instanceof Uncle");
        } else {
            System.out.println("p not instanceof Uncle");
        }
        */
 
        if (null instanceof String){
            System.out.println("null instanceof String");
        }else{
            System.out.println("null not instanceof String");
        }
    }
}
/*
	运行结果:
	p instanceof Pare
	p1 instanceof Pare
	p1 not instanceof Pare2
	null not instanceof String
*/

七. equals 方法覆写应用

Object类的方法,比较本引用和参数指明的某个引用是否相等,即是否指向同一对象,返回Boolean值

class Student{
    private int id;
    private String name;
    public Student(int _id, String _name){
        id = _id;
        name = _name;
    }
    public int getId(){
        return id;
    }
    @Override
    public boolean equals(Object o) {
        return this.getId()==((Student)o).getId();
    }
}

public class Test {
    public static void main(String[] args) {
        Student s1 = new Student(2020001, "小明");
        Student s2 = new Student(2020001,"小王");
        System.out.println(s1.equals(s2)); // true
    }
}

八. debug调试

例6.4

class AddClass {
    public int x = 0, y = 0, z = 0; 
    AddClass(int x) { 
        this.x = x;
    }
    AddClass(int x, int y) {
        this(x); 
        this.y = y;
    }

    AddClass(int x, int y, int z) {
        this(x, y); 
        this.z = z;
    }

    public int add() {
        return x + y + z;
    }
}

public class SonAddClass extends AddClass {
    int a = 0, b = 0, c = 0; 
    SonAddClass(int x) {
        super(x);
        a = x + 7;
    }
    SonAddClass(int x, int y) {
        super(x, y);
        a = x + 5;
        b = y + 5;
    }

    SonAddClass(int x, int y, int z) {
        super(x, y, z); 
        a = x + 4;
        b = y + 4; 
        c = z + 4;
    }

    public int add() {
        System.out.println("super:x+y+z=" + super.add());
        return a + b + c;
    }

    public static void main(String[] args) {
        SonAddClass p1 = new SonAddClass(2, 3, 5); // debug调试处
        SonAddClass p2 = new SonAddClass(10, 20); 
        SonAddClass p3 = new SonAddClass(1);
        System.out.println("a+b+c=" + p1.add());
        System.out.println("a+b=" + p2.add());
        System.out.println("a=" + p3.add());
    }
}

步骤总结

  1. 先声明父类的x,y,z并赋初值0
  2. 通过super()方法调用父类构造函数 AddClass(int x, int y, int z),再通过this()方法调用构造函数 AddClass(int x, int y) ,最后调用 AddClass(int x),然后执行 this.x = x 语句,完成x的赋值;接着回退执行this.y = ythis.z = z,完成父类构造函数的全部调用
  3. 接着声明本类的成员a,b,c并赋初值0
  4. 执行本类构造函数内部代码,完成a,b,c的赋值,对象初始化结束

例6.5

class Pare {
  int i;
  Pare() { 
   i = 6;
  }
}
public class Construct extends Pare {
  Construct() {}
  Construct(int num) {} 
  public static void main(String[] args) {
    Construct ct = new Construct(9); // debug调试处
    System.out.println(ct.i); // 6
  }
}

步骤总结

  1. 执行子类构造函数 Construct(int num) 前先对父类的 i 声明并赋默认初值0,然后隐式调用父类构造函数,i 赋值为6
  2. 然后执行子类构造函数,完成对象初始化

总结

​ 这一次的整理侧重于明晰一些具体理论知识的使用场景,以及重点知识的应用,从而能使抽象的概念更生动具体,而唯有自己亲身体验过了才能加深印象,帮助自己更好理解和掌握

​ 在整理博客的过程中参考了一些资料以及许多他人优秀的文章,就不一一列举,在此表示感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值