9、抽象类与接口
9.1 抽象类
Java 语言提供了两种类,分别为具体类和抽象类。
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,那么这样的类称为抽象类。
在 Java 中抽象类的语法格式如下:
<abstract>class<class_name> {
<abstract><type><method_name>(parameter-iist);
}
其中,abstract 表示该类或该方法是抽象的;class_name 表示抽象类的名称;method_name 表示抽象方法名称,parameter-list 表示方法参数列表。
如果一个方法使用 abstract 来修饰,则说明该方法是抽象方法,抽象方法只有声明没有实现。(无论是在类还是接口)
需要注意的是 abstract 关键字只能用于普通方法,不能用于 static 方法或者构造方法中。
抽象方法的 3 个特征如下:
- 抽象方法没有方法体
- 抽象方法必须存在于抽象类中
- 子类重写父类时,必须重写父类所有的抽象方法
注意:在使用 abstract 关键字修饰抽象方法时不能使用 private 修饰,因为抽象方法必须被子类重写,而如果使用了 private 声明,则子类是无法重写的。
抽象类的定义和使用规则如下:
- 抽象类和抽象方法都要使用 abstract 关键字声明。
如果一个方法被声明为抽象的,那么这个类也必须声明为抽象的。而一个抽象类中,可以有 0~n 个抽象方法,以及 0~n 个具体方法。 - 抽象类不能实例化,也就是不能使用 new 关键字创建对象。
Demo
1)首先创建一个表示图形的抽象类 Shape,代码如下所示。
package Abstract_Interface;
public abstract class Shape {
public double height;
public double weight;
//构造方法
public Shape(double height,double weight){
this.height=height;
this.weight=weight;
}
//
public abstract double getArea();//抽象类才有抽象方法,而且没有函数体。
}
2)定义一个正方形类,该类继承自形状类 Shape,并重写了 area( ) 抽象方法。正方形类的代码如下:
package Abstract_Interface;
public class Square extends Shape {
public Square(double height,double weight){
super(height,weight);
}
//这个时候必须重写父类的抽象方法。否则报错,而且使用注解@Override标识,加不加注解都没问题,只是规范
@Override
public double getArea(){
//可以不使用this,隐式 return this.height*this.weight;
return height*weight;
}
}
3)定义一个三角形类,该类与正方形类一样,需要继承形状类 Shape,并重写父类中的抽象方法
package Abstract_Interface;
public class Triangle extends Shape {
public Triangle(double height,double weight){
super(height,weight);
}
//重写抽象方法
public double getArea(){
return 0.5*height*weight;
}
}
4)最后创建一个测试类,分别创建正方形类和三角形类的对象,并调用各类中的 area() 方法,打印出不同形状的几何图形的面积。测试类的代码如下:
package Abstract_Interface;
/**
* 定义一个抽象类,实现计算面积
* 抽象类可以实现如必须具备把方法都实现才能实例化
* 如线的长度,面积,体积——长方形的周长,面积,体积——长方体的周长,面积,体积
* 抽象类是对一种事物的抽象,即对类抽。.抽象类是对整个类整体进行抽象,包括属性、行为。
* Java抽象类和Java接口一样,都用来声明一个新的类型。并且作为一个类型的等级结构的起点。
*/
public class Abstract_Demo {
public static void main(String[]args){
//创建父类发现无法实例化
// Shape sp = new Shape(1,2);
//创建方形
Square sq = new Square(12,44);
System.out.println(sq.getArea());
//创建三角形
Triangle tr = new Triangle(23,45);
System.out.println(tr.getArea());
}
}
/*
注意事项:
A:抽象类中不一定有抽象方法,但是有抽象方法的类一定要定义为抽象类。
B:在Java语言中,类有两种,一种是具体类,另一种是抽象类。
具体类可以实例化,抽象类不可以实例化。
C:对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;
D:抽象方法只有声明,没有具体的实现。抽象类是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,就等于白白的创建了这个类。
E:对于一个父类,如果它的一个方法在父类中实现没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为了abstract抽象类。
*/
运行结果:
9.2 接口(Interface)
接口的用法简介
概念
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface)。
接口是 Java 中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。(java8后可以有普通方法,静态方法)
定义接口
Java 接口的定义方式与类基本相同,不过接口定义使用的关键字是 interface,接口定义的语法格式如下:
[public] interface interface_name [extends interface1_name[, interface2_name,…]] {
// 接口体,其中可以包含定义常量和声明方法
[public] [static] [final] type constant_name = value; // 定义常量
[public] [abstract] returnType method_name(parameter_list); // 声明方法
}
对以上语法的说明如下:
- public 表示接口的修饰符,当没有修饰符时,则使用默认的修饰符,此时该接口的访问权限仅局限于所属的包;
- interface_name 表示接口的名称。接口名应与类名采用相同的命名规则,即如果仅从语法角度来看,接口名只要是合法的标识符即可。如果要遵守 Java 可读性规范,则接口名应由多个有意义的单词连缀而成,每个单词首字母大写,单词与单词之间无需任何分隔符。
- extends 表示接口的继承关系,接口可以多继承;
- interface1_name 表示要继承的接口名称;
- constant_name 表示变量名称,一般是 static 和 final 型的;
- returnType 表示方法的返回值类型;
- parameter_list 表示参数列表,在接口中的方法是没有方法体的。
注意:一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
接口对于其声明、变量和方法都做了许多限制,这些限制作为接口的特征归纳如下:
- 具有 public 访问控制符的接口,允许任何类使用;没有指定 public 的接口,其访问将局限于所属的包。
- 方法的声明不需要其他修饰符,
在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。
- 在 Java 接口中声明的变量其实都是常量,
接口中的变量声明,将隐式地声明为 public、static 和 final,即常量,所以接口中定义的变量必须初始化。
- 接口没有构造方法,不能被实例化。
例如:
public interface A {
publicA(){…} // 编译出错,接口不允许定义构造方法
}
一个接口不能够实现另一个接口,但它可以继承多个其他接口。**子接口可以对父接口的方法和常量进行重写。**例如:
public interface StudentInterface extends PeopleInterface {
// 接口 StudentInterface 继承 PeopleInterface
int age = 25; // 常量age重写父接口中的age常量
void getInfo(); // 方法getInfo()重写父接口中的getInfo()方法
}
例如,定义一个接口 MyInterface,并在该接口中声明常量和方法,如下:
public interface MyInterface { // 接口myInterface
String name; // 不合法,变量name必须初始化
int age = 20; // 合法,等同于 public static final int age = 20;
void getInfo(); // 方法声明,等同于 public abstract void getInfo();
}
实现接口
接口的主要用途就是被实现类实现,一个类可以实现一个或多个接口,继承使用 extends 关键字,实现则使用 implements 关键字。因为一个类可以实现多个接口,这也是 Java 为单继承灵活性不足所作的补充。
类实现接口的语法格式如下:
<public> class <class_name> [extends superclass_name] [implements interface1_name[, interface2_name…]] {
// 主体
}
对以上语法的说明如下:
- public:类的修饰符;
- superclass_name:需要继承的父类名称;
- interface1_name:要实现的接口名称。
实现接口需要注意以下几点:
1)实现接口与继承父类相似,一样可以获得所实现接口里定义的常量和方法。如果一个类需要实现多个接口,则多个接口之间以逗号分隔。
2)一个类可以继承一个父类,并同时实现多个接口,implements 部分必须放在 extends 部分之后。
一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。
Demo
1.定义接口 Circle_Interface
package Abstract_Interface;
public interface Circle_Interface {
//必须实现的常量,默认属性 public static final
float pai = 3.14f;
//定义方法体,在之前是不允许的,后来才加上——java8环境下
// 方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。
//1.静态方法,可以有函数体,默认public
static double Volume(){
System.out.println("Circle_Interface的静态方法");
return 1;
}
//抽象方法
double getArea();//只要是抽象方法都无法拥有函数体,只能在继承的类或者实现该接口的类重写
//普通方法的定义,使用default
default double getLength(){
System.out.println("Circle_Interface的普通方法");
return 0;
}
}
2.实现接口
package Abstract_Interface;
public class InterFace_Demo implements Circle_Interface{
private double radius;
//定义一个构造方法
public InterFace_Demo(double radius){
this.radius = radius;
}
//应该加@Override声明为重写
@Override
public double getArea(){
return pai*radius*radius;
}
@Override
public double getLength(){
return pai *radius*2;
}
public static void main(String[]args){
InterFace_Demo circle = new InterFace_Demo(4);
//计算面积,接口的抽象方法必须重写
System.out.println(circle.getArea());
//计算周长,如果当前类没有重写接口中的默认方法,默认调用接口的该方法 0
System.out.println(circle.getLength());
//调用接口里边的静态方法,但是只能自己调用,通过接口名调用
System.out.println(Circle_Interface.Volume());
}
}
运行结果:
抽象类与接口的一些关系
- 接口没有构造方法,抽象类具有构造方法;
- 接口中的成员域默认是具有public、static、final属性的,而抽象类中的成员域要看自己是怎么定义的~
- 接口,多态,弥补单继承
- 现在接口和抽象类越来越相似了,现在不同点也就剩下:继承1个和实现多个的区别了。
- 此外,值得注意的是:面向对象中,java只是支持单继承,不支持多继承,一个类只能继承一个类
- 接口是对行为的抽象。接口是对类局部(行为)进行抽象。
如果多个接口定义了同样的静态方法,
- 即使存在实现类,同时实现多个接口,仍然是不可使用实现类名调用接口的方法
如果多个接口定义了同样的默认方法
- 实现类实现多个接口时,必须重写掉默认方法,否则编译失败。
- 在接口中可以定义实现的方法体是java8的一大特性,可以定义多个静态或者默认的方法,静态必须加上static,默认方法必须加上default关键字。
使用接口的原因
- 没有接口,可插入性就没有保证。因为Java是单继承的。
- 在一个类等级结构中的任何一个类都可以实现一个接口,如果这个类实现了这个接口那么将会影响到此类的所有子类,但是不会影响到此类的所有父类。
- 一个类最多有一个父类,但是可以同时实现几个接口。
如果一个抽象类不包含任何抽象方法,为何还要设计为抽象类?
个人理解:
- 抽象类目的是用来继承的,在定义上,可以没有抽象方法,只是用abstract修饰是没有语法错误的,但是没有的实际意义。抽象类中的抽象方法是让子类继承抽象类,来实现抽象类中的抽象方法,根据子类的需求分别来实现不同的功能。