抽象类与接口的比较

引言

  这篇博客,是用来说明抽象类和接口的。但是本人有强迫症,索性就把抽象类的演化过程来捋一遍。首先介绍的是继java特性封装之后的“继承”、“多态的表现”,然后再逐步演化到抽象类和接口,最后再把抽象类和接口放在一起做个比较。这样可以复习好多基础,有莫有?有的话,记得点赞哟~


继承

继承的概述

  继承是将某一类事物所共有的部分抽象出来,并且实现多次使用的过程.该过程让某2类事物之间发生关系:is a。
  就是说,谁是谁的什么。就比如下边这个例子:我们通过分析发现,Cat和Dog都是动物,有名字,有重量,还都有相同的名字的方法say()。那么这种情况就可以对这两种动物进行抽取其共性!
  共性就是他们俩都拥有的部分。在下边的例子中也加上了封装特性。

  将他们都有的部分,抽取出来放到单独的一个类中,叫做Animal类。在Animal类中,定义了这些属性和方法。那么,现在就只需要使用extends关键字让Animal和Dog、Cat发生关系即可。


  通过一段代码来引出继承的概念!

/**
 * 首先我定义一个狗类和一个猫类.(为了清晰,没使用封装)
 * 然后发现以下特点:
 * 1、Cat中的属性,Dog中也有。
 * 2、它们都有say()方法。
 * 3、它们都是动物!
 * @author Feng
 * 2018年3月3日上午9:41:23
 */
//class Dog {
//  private String name;
//  private double weight;
//  private String color;
//  
//  public void say() {
//      System.out.println("我是Dog");
//  }
//  public void watchHome() {
//      System.out.println("我能看家!");
//  }
//}
//class Cat{
//  private String name;
//  private double weight;
//  
//  public void say() {
//      System.out.println("我是Cat");
//  }
//  public void catchMouse() {
//      System.out.println("我能抓老鼠!");
//  }
//}
/**
 * 建立动物类:
 * 对Dog和Cat抽取相同的部分。
 * 就拥有了Dog和Cat的相同的属性和方法。
 * 这个时候就可以节省代码的书写量。想办法让他们三者有一种联系。
 * 让Dog和Cat可以不用再次定义、声明这些属性和方法。
 * 或者说是Dog和Cat拥有一个同样的规则。
 * 加上私有和set,get方法!
 * @author Feng
 * 2018年3月3日上午9:50:56
 */
class Animal{
    private String name;
    private double weight;
    //两个构造方法!
    public Animal() {}
    public Animal(String name,double weight) {
        this.setName(name);
        this.setWeight(weight);
    }
    //set、get
    public void setName(String name) {
        this.name = name;
    }
    public void setWeight(double weight) {
        this.weight = weight;
    }
    public String getName() {
        return name;
    }
    public double getWight() {
        return weight;
    }
    //say方法!
    public void say() {
        System.out.println("我是一个动物!");
    }
}
class Dog extends Animal{
    private String color;
    public Dog() {}
    public Dog(String color,String name,double weight ) {
        super(name,weight);
        this.setColor(color);
    }
    //set、get
    public void setColor(String color) {
        this.color = color;
    }
    public String getColor() {
        return color;
    }
    public void say() {
        super.say();//调用Animal类的say方法。
        System.out.println("我是Dog");
    }
    //Dog自己特有的方法!
    public void watchHome() {
        System.out.println("我能看家!");
    }
}
class Cat extends Animal{
    //两个构造方法!
    public Cat() {}
    public Cat(String name,double weight) {
        super(name,weight);
    }
    public void say() {
        super.say();
        System.out.println("我是Cat");
    }
    public void catchMouse() {
        System.out.println("我会抓老鼠!");
    }
}

public class AnimalDemo{
    public static void main(String[] args) {
        //向上转型
        Animal dog = new Dog("black","jason",11.2);
        Animal cat = new Cat("jerry",22.3);
        //调用复写父类的方法
        dog.say();
        cat.say();
        //无法调用子类特有的方法。
//      dog.watchHome();
//      cat.catchMouse();
        //向下转型,可以调用子类特有的方法了。
        Dog doggy = (Dog)dog;
        Cat catty = (Cat)cat;
        doggy.watchHome();
        catty.catchMouse();
    }
}

  那么现在满足了继承的关系,会有什么地方得到优化了呢?

继承带来了什么好处?

1. 提高了代码的复用性。在下边的例子中,Animal中定义的东西,可以在其继承类中被拿来使用,这就是代码的复用。
2. 继承是让类与类之间发生了关系而这层关系就是多态的基石。
3. 注意一点:千万不雅为了获取其他类的功能,简化代码就去使用继承,一般我们使用继承是因为类之间有所属关系,就比如,猫是动物。

继承的注意事项

  在java中,只支持单继承,一个类只能继承一个类。其可以理解成,多继承存在安全隐患,假如在多个父类中定义了相同功能,当功能内容不同时,但名字又是相同的,就会不确定去执行哪一个!
  比如:在父类A中有一个show方法,父类B中有一个show方法,当C同时继承A、B时,调用了show方法,就会不安全。但是java中保留了这种机制,并用另一种体现形式来完成表示,即多实现。

  java中支持多层继承,也就是一个继承体系。可以出现B继承A,C继承B,D继承C等继承关系。

如何使用一个继承体系中的功能?

  想要使用体系,先查阅体系中关于父类的描述,因为父类中定义的是该体系中的共性功能,通过了解共性功能,就可以知道该体系的基本功能,那么这个体系就已经可以进行使用了。

  在具体调用时,要创建最子类的对象,因为有可能父类不能创建对象(比如抽象类),其次创建子类对象可以使用更多的功能,包括最基本的和特有的。也就是说,查阅父类功能,创建子类对象使用功能。

子、父类出现后,类成员的特点

1. 变量方面的变化:
  如果子类中出现非私有成员了变量时,子类要访问本类中的变量就要用this;子类要访问父类中的同名变量时用super。this和super的使用几乎一致,this代表的是本类中的引用,super代表的是父类对象的引用。而现实中,这种出现同名变量的情况并不多见,因为如果在父类中已经定义了,那子类就可以去获取并使用它,没有必要再自己定义了。
2. 方法上的变化:
  当子类出现和父类一模一样的函数时,当子类对象调用该函数,会运行子类函数的内容,这就是重写(覆盖)。
  当子类继承了父类,沿袭了父类的功能到子类中,但是子类虽具备该功能,但该功能的内容却和父类不一致,这时没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。
  覆盖要注意的:其一,子类覆盖父类,必须保证子类权限大于等于父类权限才可以覆盖,否则编译失败。其二,静态只能覆盖静态。
  重载,只看同名函数的参数列表。重写,子、父类方法要一模一样,只有方法体不同。下边有一个简单的例子。
3. 构造方法的变化:
  子类中构造函数,在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句super();,它会访问父类中空参数的构造函数,而且子类中所有的构造函数默认第一行都是super();
4. 继承打破了封装性,因为子类可以重写父类的方法。因此就要用到关键字final。final可以修饰类、方法、变量:
  final修饰的类不能被继承,修饰的方法不能被覆盖(重写),修饰的变量是一个常量,只能被赋值一次。内部类只能访问被final修饰的局部变量。final刚好可以解决这个问题,可以避免被子类重写功能,是不能有子类的。同样的思想,为了更加灵活、严谨,final可以去修饰方法,达到的效果是该方法不能被重写。

继承中对构造方法的理解

  为什么子类一定要访问父类中的构造函数?
  这是因为,父类中的数据子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的,所以子类在对象初始化时,要先访问一下父类中的构造函数。
  如果,要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。还需要注意,super语句一定要定义在子类构造方法第一行,并且不能和this同时出现。子类中至少有一个构造函数会去访问父类。

结论

  结论:子类中的所有的构造函数,默认都会访问父类中空参数的构造函数,因为子类每一个构造函数内的第一行都有一句隐式super语句。
 当父类中没有空参数的构造函数时,子类必须手动通过super或者this语句形式来指定要访问的构造函数。当然,子类的构造函数的第一行,也可以使用this语句来访问本类中的构造函数,子类中至少会有一个构造函数会访问父类中的构造函数。这实际就是子类的实例化过程。java中所有类都有一个隐式的父类,就是Object类。

抽象类

  引入:当多个类中出现相同功能,但是功能主体不同,这时可以进行向上抽取,这时,只抽取功能定义,不去抽取功能主体,这时就不需要写功能的主体了。这时的方法就是一个抽象方法(笼统的、模糊的、看不懂的),就需要去标识一下,使用abstract关键字,叫做抽象方法。

抽象方法

  没有方法体,只有修饰符、返回值、方法名和参数列表的方法!使用abstract关键字修饰。

//抽象方法必须在抽象类或接口中。
public abstract class AbstractMethod {
    //可以用默认修饰符,protected,public修饰。
    abstract void demo1();
    protected abstract void name() ;
    public abstract void demo2();
    //抽象方法不能私有
//  private abstract void demo3();
    //抽象方法不能被final修饰。
//  final abstract void demo4();
    //抽象方法不能被静态修饰。
//  static abstract void demo5();
}

抽象类的基本特点

1. 抽象方法必须放在抽象类中。
2. 抽象方法和抽象类都必须用abstract关键字修饰。
3. 抽象类不可以用new关键字创建对象,因为调用抽象方法没有意义。
4. 抽象类中的方法要被使用,必须由其子类复写其所有的抽象方法后,建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还必须是一个抽象类。
5. 抽象类中可以定义非抽象的方法。只是比普通类多了抽象方法。当抽象类中没有定义抽象方法时,只是为了不让该类建立对象。
6. 抽象类和一般类每有太大不同,该如何去描述事物就如何去描述事物,只不过该事物中出现了一些不确定的部分,这些不确定的部分也是事物的功能,需要明确的出现。但是无法定义主体。所以就通过抽象方法来表示。

   第二个例子:

/**
 * 通过上一个例子,我们发现:
 * Animal类中,有一个say方法!
 * 这个方法看着很多余,子类还会复写它!
 * 当然你要是想留着它,也是可以的。
 * 这个时候,对于不想要这个方法实现的人来说,就可以让这个方法变成一个抽象方法!
 * 但是,抽象方法又必须在抽象类或接口中,所以这个例子就来看看抽象类。
 * @author Feng
 * 2018年3月3日上午10:29:12
 */
public class AnimalAbstractDemo {
    public static void main(String[] args) {
        //注意:抽象类不能实例化。
//      Animal animal = new animal();
        //只能实例化其子类。
        Animal dogAnimal = new Dog();
        dogAnimal.say();
        //想要调用其子类的特有方法的话,就必须去向下转型。
        //注意转换时可能会出现类转换异常。所以在这里使用了instanceof
        //目的是判断dogAnimal是不是Dog的一个实例,如果是,才能转换。
        if(dogAnimal instanceof Dog) {
            Dog dog = (Dog)dogAnimal;
            //调用Dog特有的方法。
            dog.watchHome();
        }
        //出现异常!
        if(dogAnimal instanceof Cat) {
            //dogAnimal不是Cat的实例。
            Cat cat = (Cat)dogAnimal;
        }else {
            System.out.println("dogAnimal不是Cat的实例,无法向下转型。");
        }

    }
}
abstract class Animal{//抽象类用abstract关键字修饰。
    //抽象方法中可以定义属性。
    private String name;
    private double weight;
    //抽象方法
    public abstract void say();
    //抽象方法不能是私有的。因为它需要被子类重写!
//  private abstract void say();
    //抽象类中是可以写构造方法的。
    Animal() {}
    Animal(String name,double weight) {
        this.setAttribute(name, weight);
    }
    //抽象类中也可以有非抽象方法。
    public void setAttribute(String name,double weight) {
        this.name = name;
        this.weight = weight;
    }
    public String getName() {
        return name;
    }
    public double getWeight() {
        return weight;
    }

}
class Dog extends Animal{
    private String color;
    //构造方法
    public Dog() {
        super();
    }
    public Dog(String name,double weight,String color) {
        super(name,weight);
        this.setColor(color);
    }
    //set get
    public void setColor(String color) {
        this.color = color;
    }
    public String getColor() {
        return color;
    }
    //一个类继承了抽象类,这个类就必须重写它的父类的所有的抽象方法。
    @Override
    public void say() {
        // TODO Auto-generated method stub
        System.out.println("我是狗");
    }
    public void watchHome() {
        System.out.println("我能看家!");
    }
}
class Cat extends Animal{
    //简单的只写了一个从父类继承到的say()
    @Override
    public void say() {
        // TODO Auto-generated method stub
        System.out.println("我是Cat");
    }

}
abstract class DOG extends Animal{
    //如果不想重写父类的抽象方法,可以让这个类也变成抽象类。
    //那么,其中的抽象方法就由它的子类去重写。
    abstract void method();
}
class DOGS extends DOG{
    //该类必须重写其父类,以及其父类的父类等的抽象方法!
    //否则,也必须让其成为抽象类。
    @Override
    public void say() {
        // TODO Auto-generated method stub

    }
    @Override
    void method() {
        // TODO Auto-generated method stub

    }

}

接口

  可以认为是一个特殊的抽象类(初期的理解),当抽象类中的方法全是抽象方法时,就可以把它定义成另外一种表现形式,叫做接口。接口用interface定义。
  接口不能创建对象,需要被子类实现(必须实现接口中的所有方法)之后,才能创建对象。

定义接口

  1. 接口中常见定义常量、抽象方法。
  2. 接口中的成员都有固定的修饰符。
  3. 修饰常量(可省略,建议写全):public static final
  4. 修饰方法(可省略,建议写全):public abstract

接口为什么是实现关系而不是继承关系?

  因为父类中有非抽象内容,可以直接拿过来用,这是继承。而接口里边全是抽象方法,子类如果要继承,就得全部复写,所以用一种更加确切的方式来表示,就是子类要实现接口中所有的方法之后才能去使用。
  接口可以被类多实现!也是多继承不支持的转换形式,java支持多实现。一个类在继承另一个类的时候还可以去实现多个接口,从而增加自己的功能。接口与接口之间可以存在继承关系,接口之间可以多继承(可以继承多个接口)。

接口的特点

1. 接口是对外暴露的规则。
2. 接口是程序的功能扩展。降低了耦合性(模块式开发)。
3. 接口可以用来多实现。
4. 类与接口之间是实现关系,而且类可以继承一个类的同时去实现多个接口。
5. 重点内容,接口与接口之间可以有继承关系。

  注意:当一个类实现了多个接口时,这些接口中如果有相同的方法,那么只需要重写一次。
接口的例子:

package 继承;
/**
 * 从抽象类的例子看出:
 * 写抽象类的缺点
 * 1.在继承关系上有限制!一个类只能有一个父类,如果想使用多个类中的抽象方法,
 * 就必须让他们有多层继承关系,这样是不符合继承的用法的(属于强制发生关系)。
 * 2.写抽象类,就是为了让其子类去重写它的抽象方法,所以,我们能把抽象方法单独写出来吗?
 * 写抽象方法,就是为了让需要用到它的类去重写!
 * 为了弥补这些缺点,接口的初期理解就可以是一个特殊的抽象类,它就是当抽象类
 * 中全是抽象方法时,所用到的定义方式。
 * @author Feng
 * 2018年3月3日下午12:06:32
 */
public class InterfaceDemo {
    public static void main(String[] args) {
        //使用时,实例化实现接口的那个类。
        AnimalSay say = new Dog();
        say.say();
    }

}
/**
 * 定义动物说话的规则。
 * 使用interface关键字定义接口。
 * @author Feng
 * 2018年3月3日下午12:17:34
 */
interface AnimalSay{
    //接口中定义静态常量。接口中的成员都有固定的修饰符
    //修饰常量(可省略,建议写全):public static final
    //动物寿命。
    public static final int LIFE_TIME = 120;
    //修饰方法(可省略,建议写全):public abstract
    public abstract void say();
    //接口中只能定义抽象方法,不能定义非抽象方法!
//  void show() {}
}
//接口中也存在继承关系!
//一个接口可以继承多个接口。一个类也可以实现多个接口。
//接口与接口之间是多继承的关系,类与接口间是多实现的关系。
//接口关系可以理解成是一条链式的,如果你实现的是链尾的接口,那么该接口继承的所有接口中的抽象方法都必须要实现。
//如果不想实现,你必须定义成一个抽象类。
interface AnimalSpeak extends AnimalSay,AnimalShow{}
interface AnimalShow extends AnimalSay{}

//Animal做为父类。
class Animal{
    private String name;
    private int age;

    Animal(){
        super();//可以省略。
    }
    Animal(String name,int age){
        super();//可以省略。
        this.setAttributes(name, age);
    }
    public void setAttributes(String name,int age) {
        this.name = name;
        if(age >= 0 && age <= AnimalSay.LIFE_TIME)
            this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
} 
class Dog extends Animal implements AnimalSay,AnimalSpeak{
    //重写接口中的抽象方法。
    @Override
    public void say() {
        // TODO Auto-generated method stub
        System.out.println("name = "+this.getName() +",age = "+this.getAge());
    }
}

目前就先写到这。现在再说说接口与抽象类之间到底有什么区别!

接口与抽象类的比较

相同点:

1、都可以定义或者不定义抽象方法。
2、都可以定义常量。
3、都不能用final修饰。
4、都不能用static修饰。
5、都不能用private修饰。
6、抽象类可以被多个类所继承,接口也可以被多个类所实现。
7、抽象类之间可以存在继承关系,接口之间也可以存在继承关系。
8、都可以提高程序的复用性。
9、都不能new自己的对象,即使抽象类中可以定义构造方法也不行。

不同点:

1、定义方式不同:接口使用interface关键字定义;抽象类则是需要用abstract关键字定义。
2、非抽象方法定义的不同:接口中只能定义抽象方法;抽象类中既可以定义抽象方法,还可以定义非抽象方法!
3、定义常量时的格式不同:接口中格式是固定的,都必须是public static final修饰的;而抽象类中则不做限制。
4、在构造方法上:抽象类中可以定义构造方法;接口中则不能定义构造方法。
5、一个类继承了抽象类,就必须重写其父类中的所有抽象方法,否则就必须把这个类也定义成抽象类;一个类实现了接口,就必须重写这个接口中的所有抽象方法,否则就得把这个类定义成抽象类。
6、抽象类为了被其子类或者子类的子类重写其抽象方法,就必须被继承;接口则需要被实现(implements)。
7、这条应该和第2条相对应的看,想要用接口中的方法就必须去实现其抽象方法;而抽象类中有非抽象的方法,其功能可以直接拿来用!
8、抽象类只能是单继承,换句话说,就是抽象类的父类只能有1个(当然还可以多层继承);接口可以继承多个接口(或者说成有多个父接口,这样能好理解点!)。总结成一句话就是,一个类可以实现多个接口,但是只能继承一个类。

注意事项

当一个类实现了多个接口时,这些接口中如果有相同的方法,那么只需要重写一次。

以上只是我的认识,哪位大神若觉得什么地方不妥,还请批评指教!感谢万分,内个看完有感悟的话,请点个赞什么的,谢谢啦。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你家宝宝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值