抽象类
什么是抽象类?
只包含抽象的概念,比如只给出动物类,具体是哪个动物不做具体解释,其中包含抽象方法,比如只包含睡觉这个方法,具体什么时间睡,睡多长时间不指明,等到在子类中继承这个抽象类之后再根据子类对象的不同来设定。
抽象方法和抽象类
抽象方法和抽象类必须使用abstract修饰符来修饰,有抽象方法的类一定是抽象类,但是抽象类中可以不包含抽象方法。
格式
抽象类格式:abstract class 类名{}
抽象方法格式:public abstract void eat();
抽象方法和抽象类的规则
- 抽象类和抽象方法都必须用absrtact来修饰,抽象方法中不能有方法体;
- 抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。但可以通过子类来实例化它,也是多态的体现。
- 抽象类可以包含成员变量、成员方法、构造器、初始化块、内部类5种成分抽象类的构造器不能用于创建实例,主要是为了用于被其他子类调用。
- 按照多态的方式,有具体的子类实例化抽象类,这也是多态的一种。
- 抽象类的子类,要么是抽象类,要么重写抽象类中的所有抽象方法。
- 抽象类中的抽象方法必须重写。
抽象类可以用“有得有失”四个字来总结,得:抽象类可以包含抽象方法;失:抽象类不能用来创建实例;
实例:
Person.class抽象类
package org.westos.practice;
public abstract class Person {
//抽象类中可以定义抽象方法也可以定义非抽象方法
public abstract void work();
public abstract void drink();
public void learning(){}
}
Teacher.class
package org.westos.practice;
public class Teacher extends Person {
@Override
public void work() {
System.out.println("The work of teacher is teaching");
}
@Override
public void drink() {
System.out.println("Teacher drunk white water");
}
}
Student.class
package org.westos.practice;
public class Student extends Person {
@Override
public void work() {
System.out.println("The work of student is learning");
}
@Override
public void drink() {
System.out.println("Student drink jury");
}
}
Mytest.class
package org.westos.practice;
public class MyTest {
public static void main(String[] args) {
//在子类中来具体实现抽象方法
Student student = new Student();
student.work();
student.drink();
System.out.println("-------------");
Teacher teacher = new Teacher();
teacher.work();
teacher.drink();
}
}
Teacher和Student都是Person的子类,他们将Person中定义的抽象方法重写,具体实现。
abstract不能用于修饰成员变量,不能用修饰局部变量,不能用于修饰构造器,抽象类中定义的构造器只是普通构造器。
抽象类可以通过多态的方式用子类实例化
// Person person = new Person();
// Error:(15, 25) java: org.westos.practice.Person是抽象的; 无法实例化
Person p=new Teacher();
p.work();
注意:
- abstract不能与private共存,他们是矛盾的,abstract要求方法必须被重写,而private则让外界访问不到被修饰的方法和类;
- abstract和final不能共存,final要求被修饰的类不能被继承,修饰的方法不能被重写,这与abstract恰恰相反。
- abstract和static没有意义,被static修饰的方法为内部方法,而被abstratic修饰的方法没有方法体,通过类来调用一个没有方法体的方法会报错。不过他们可以同事修饰内部类。
抽象类的意义
从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础在进行扩展、改造,但子类总体上会大致保留抽象类的行为方式。
模板模式在面向对象的软件中很常用,其原理简单,实现也很简单。
模板模式的简单规则:
- 抽象父类可以只定义需要使用的某些方法,把不能实现的部分方法抽象成抽象方法,留给子类去实现。
- 父类中可能包含需要调用其他系列方法的方法,这些被调用方法既可以由父类实现也可以由其子类实现。父类里提供的方法只是定义一个通用算法,其实现也许并不完全由自身实现,而需要依赖其子类的辅助。
接口
接口的概念
我们在生活中可能经常听到PCI接口、AGP结构等,我之前以为这里的说的接口就是插槽,其实这是错误的。当说PCI接口时,指的是主板及上那个插槽遵守了PCI规范,而具有的PCI插槽其实只是PCI接口的实例。
对于不同型号的主板机而言,它们各自的PCI插槽都遵循一个规范,遵守这个规范就可以保证插入该插槽里的板卡与主板机进行正常通信。
同一类的内部状态数据、各种方法的实现细节完全相同,类是一种具体实现体。而接口定义了一种规范,接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。可见,接口是从多个相似类中抽象出来的规范,接口不提供任何实现,只体现规范和实现分离的设计哲学。
接口存在的意义
让规范和实现分离,让软件系统的各组件之间面向接口耦合,是一种松耦合的设计。例如主板机上提供了一个PCI插槽 ,只要一块显卡遵循PCI接口规范,就可以插入PCI插槽内,与该主板机正常通信。至于这块显卡是哪个厂家造的,内部如何实现,主板机无需关心。
所以接口的意义为系统提供了更好的扩展性和可维护性
Java中的接口
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出来一种更加特殊的“抽象类”——接口,接口里不能包含普通方法,接口里的所有方法都是抽象方法。JDK1.8对接口进行了改进,允许在接口中定义默认方法,默认方法可以提供方法实现。
格式
接口关键字 interface 接口名{}
类实现接口用implements class 类名 implements 接口名{}
定义的基本语法
[修饰符] interface 接口名 extends 父接口1 父接口2 …
{ 零到多个常量定义
零到多个抽象方法定义
零到多个内部类、接口、枚举定义
零到多个默认方法或类方法定义
}
- public可以省略。省略之后按默认修饰符default处理;
- 接口名与类名采用相同命名规则;
- 一个接口可以有多个直接父接口,但接口只能继承接口不能继承类;
在8版本以上才允许在接口中定义默认方法,类方法。
接口成员的特点
- 接口定义的是一种规范,因此接口里不能包含构造器合初始化代码块;
- 接口里的成员变量只能是静态变量,它是与相关的,因此系统会自动为这些成员变量增加static 和final两个修饰符。因此接口里的成员变量只能在定义是指定默认值;
- 接口定义的方法只能是抽象方法、类方法或默认方法,因此如果不能定义默认方法,系统将自动为普通方法增加abstract修饰符。接口里的普通方法不能有方法实现,但类方法、默认方法都必须有方法实现(方法体)。
- 接口的子类可以是抽象类
- 接口和接口之间可以是多继承;
案例
定义两个接口
package org.westos.practice;
public interface MyInterface1 {
//接口定义的成员变量只能是公共的静态常量
//public static final可以省略,系统会自动为成员变量分配
public static final int num=50;
//定义普通方法只能是public修饰的抽象方法
//public abstract也可以省略
public abstract void jumpHigh();
void fireDtill();
//定义默认方法需要用default修饰
default void print(){
System.out.println("我是接口1中的默认方法");
}
//定义类方法需要用static修饰
static void staticMethod(){
System.out.println("我是接口1中的类方法");
}
}
package org.westos.practice;
public interface MyInterface2 {
int num=100;
void swimming();
void playBall();
static void interStaticMethod(){
System.out.println("我是接口2中的类方法");
}
}
接口的继承
接口的继承和类继承不一样,接口完全支持多继承。
定义第三个接口并继承前两个接口
package org.westos.practice;
public interface SonInterface extends MyInterface1,MyInterface2 {
int num3=150;
void runing();
}
测试类中
package org.westos.practice;
public class MyTest {
public static void main(String[] args) {
System.out.println(SonInterface.num1);
System.out.println(SonInterface.num2);
System.out.println(SonInterface.num3);
}
}
使用接口(在类中实现接口)
接口不能用于创建实例,但接口可以用于声明引用类型变量,当使用接口来声明引用变量类型时,这个引用变量必须引用到其实现类的对象。除此之外接口的主要用途是被实现类实现。
一个类可以实现一个或多个接口。
格式:
[修饰符] class 类名 extends 父类 implements 接口1,接口2…
{
类体部分
}
这个类必须完全实现这些接口中所定义的全部抽象方法,除非这个类是抽象类,可以保留父接口中的抽象方法不实现。
案例:
定义Animal类,Cat类,Tom类多层继承,在Tom类中实现上面两个接口的抽象方法,调用默认方法和接口中的常量:
Animal.class
package org.westos.practice;
public class Animal {
int name;
int age;
public void eat(){
System.out.println("动物吃食");
}
public void sleep(){
System.out.println("动物睡觉");
}
}
Cat.class
package org.westos.practice;
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫爱吃鱼");
}
@Override
public void sleep() {
System.out.println("猫爱白天睡觉");
}
}
Tom.class
package org.westos.practice;
public class Tom extends Cat implements MyInterface1,MyInterface2 {
//实现接口中的方法
@Override
public void jumpHigh() {
System.out.println("Tom会跳高");
}
@Override
public void fireDtill() {
System.out.println("Tom会钻火圈");
}
@Override
public void swimming() {
System.out.println("Tom会游泳");
}
@Override
public void playBall() {
System.out.println("Tom会玩球");
}
//重写父类的方法
@Override
public void eat() {
System.out.println("Tom爱吃带鱼");
}
@Override
public void sleep() {
System.out.println("Tom爱在白天抱着球睡觉");
}
}
测试类
package org.westos.practice;
public class MyTest {
public static void main(String[] args) {
Tom tom = new Tom();
//继承父类的方法
tom.eat();
tom.sleep();
//实现接口抽象方法
tom.jumpHigh();
tom.fireDtill();
tom.playBall();
tom.swimming();
//实现接口默认方法,不用重写直接调用
tom.print();
//调用接口中的常量
System.out.println(tom.num1);
System.out.println(tom.num2);
}
}
可以体会到在接口定义了一些杂技,这些杂技并不适合于每个动物,但是现在Tom会这些杂技,就可以实现这方接口中的抽象方法来表示。
Tom对象还可以直接调用接口中的默认方法和常量。
对接口和抽象类的区别
相同点:
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端用于被其他类实现和继承;
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
不同点:
- 接口里只能包含抽象方法、静态方法和默认方法,不能为普通方法提供方法实现;抽象类可以包含普通方法。
- 接口只能定义静态常量,不能定义普通成员变量;抽象类中既可以定义普通成员变量又可以定义静态常量;
- 接口里不包含构造器;抽象类里可以包含构造器,抽象类中的构造器并不是用来创建对象,而是让其子类来调用,完成属于抽象类的初始化操作;
- 接口里不能包含初始化块;抽象类中可以;
- 类不能实现多继承;接口可以实现;
最大的差别是在设计目的上:
接口体现的是一种规范,当在一个程序中使用接口时,接口是多个模块间耦合的标准;当在多个程序之间使用接口时,借口就是多个程序之间的通信标准。
抽象类体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品。这个中间产品已经实现了部分功能,但这个产品却不能当成最终产品,必须有更一步的完善。