引言
这篇博客,是用来说明抽象类和接口的。但是本人有强迫症,索性就把抽象类的演化过程来捋一遍。首先介绍的是继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定义。
接口不能创建对象,需要被子类实现(必须实现接口中的所有方法)之后,才能创建对象。
定义接口
- 接口中常见定义常量、抽象方法。
- 接口中的成员都有固定的修饰符。
- 修饰常量(可省略,建议写全):public static final
- 修饰方法(可省略,建议写全):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个(当然还可以多层继承);接口可以继承多个接口(或者说成有多个父接口,这样能好理解点!)。总结成一句话就是,一个类可以实现多个接口,但是只能继承一个类。
注意事项
当一个类实现了多个接口时,这些接口中如果有相同的方法,那么只需要重写一次。
以上只是我的认识,哪位大神若觉得什么地方不妥,还请批评指教!感谢万分,内个看完有感悟的话,请点个赞什么的,谢谢啦。