抽象类
抽象:即不具体、或无法具体
例如:当我们声明一个几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又无法给出具体的实现(父类只知道你是一个矩形、三角形或者圆,但是并不知道你具体得长宽高面积等数据,无法具体实现),而是应该交给子类各自具体实现。那么父类在声明这些方法时,就只有方法签名,没有方法体,而没有方法体的方法就称为抽象方法。Java语法规定,包含抽象方法的类必须是抽象类。
语法格式
- 抽象方法 : 没有方法体的方法。
- 抽象类:被abstract所修饰的类。
抽象类的语法格式
【权限修饰符】 abstract class 类名{
}
【权限修饰符】 abstract class 类名 extends 父类{
}
抽象方法的语法格式
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
注意:抽象方法没有方法体
代码举例:
public abstract class Animal {
public abstract void run();
}
public class Cat extends Animal {
public void run (){
System.out.println("小猫在墙头走~~~");
}
}
public class CatTest {
public static void main(String[] args) {
// 创建子类对象
Cat c = new Cat();
// 调用run方法
c.run();
}
}
输出结果:
小猫在墙头走~~~
abstract class HelloA{//创建一个抽象类
//抽象类成员变量
String name;
int age;
//抽象方法
abstract void run();
abstract void sleep();
public HelloA(String name,int age){
//抽象类构造器
this.age = age;
this.name=name;
}
}
/**
抽象类内抽象方法必须加上abstract关键字否则报错
* 抽象注意事项:
* 子类必须实现父类所有抽象方法,除非该子类也是抽象类
* 抽象类不能创建对象,但是其非抽象的子类可以
*抽象类有构造方法,供子类创建对象初始化父类成员变量使用
*抽象类不一定包含抽象方法,包含抽象方法的类一定是抽象类
*/
class HelloB extends HelloA{
int high;//子类成员变量
//子类构造器
public HelloB(String name, int high) {
//继承的父类属性
//父类有成员变量时写法:
super(name,age);
//父类无成员变量时:
//super();可写可不写
this.high = high;
}
//实现父类抽象方法 重写
public void run(){
System.out.println("跑步");
}
public void sleep(){
System.out.println("睡觉");
}
}
class Test{
public static void main(String[] args) {
//创建子类对象同时使用子类和父类的成员属性
HelloB b1 = new HelloB("沈阳",32);
b1.run();
b1.sleep();
}
}
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
注意事项
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
-
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
-
抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
接口
电脑边上提供了USB插槽,这个插槽遵循了USB的规范,只要其他设备也是遵循USB规范的,那么就可以互联,并正常通信。至于这个电脑、以及其他设备是哪个厂家制造的,内部是如何实现的,我们都无需关心。
**这种设计是将规范和实现分离,这也正是Java接口的好处。Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面向接口的低耦合,为系统提供更好的可扩展性和可维护性。**
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。
- 可以理解成是一个特殊的类,在接口中的定义必须是全局常量以及全部的公共抽象方法。
- 例如:你能不能用USB进行连接,或是否具备USB通信功能,就看你是否遵循USB接口规范
- 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范
定义格式
接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,接口。
接口的声明格式
【修饰符】 interface 接口名{
//接口的成员列表:
// 静态常量 static final修饰
接口中的变量声明,将隐式地声明为public static final 变量(并且只能是 public,用 private 修饰会报编译错误
// 抽象方法 public void run();没有方法体的方法
// 默认方法 default修饰
// 静态方法 static方法
// 私有方法 private方法
}
示例代码:
interface Usb3{
//静态常量
long MAX_SPEED = 500*1024*1024;//500MB/s
//抽象方法
void read();
void write();
//默认方法
public default void start(){
System.out.println("开始");
}
public default void stop(){
System.out.println("结束");
}
//静态方法
public static void show(){
System.out.println("USB 3.0可以同步全速地进行读写操作");
}
}
接口的成员说明
接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。
在JDK8之前,接口中只允许出现:静态变量和抽象方法
(1)公共的静态的常量:其中public static final可以省略
(2)公共的抽象的方法:其中public abstract可以省略
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
在JDK1.8时,接口中允许声明默认方法和静态方法:
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
在JDK1.9时,接口又增加了:私有方法
(5)私有方法
除此之外,接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要初始化。
实现接口
接口的使用,它不能创建对象,但是可以被实现(implements
,类似于被继承)。
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
1、实现接口语法格式
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
注意:
-
如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法。
-
默认方法可以选择保留,也可以重写。
重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
-
不能重写静态方法
示例代码:
class MobileHDD implements Usb3{
//重写/实现接口的抽象方法,【必选】
public void read() {
System.out.println("读数据");
}
public void write(){
System.out.println("写数据");
}
//重写接口的默认方法,【可选】
//重写默认方法时,default单词去掉
public void end(){
System.out.println("清理硬盘中的隐藏回收站中的东西,再结束");
}
}
如何调用对应的方法
- 对于接口的静态方法,直接使用“接口名.”进行调用即可
- 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
- 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
- 接口不能直接创建对象,只能创建实现类的对象
public class TestInteface {
public static void main(String[] args) {
//创建实现类对象
MobileHDD b = new MobileHDD();
//通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
b.start();
b.read();
b.stop();
//通过接口名调用接口的静态方法
MobileHDD.show();
}
}
/**
* 接口就是一种规范,一种特殊的类,可以理解为100%抽象类
* 接口中成员属性必须初始化------>全局常量
*接口中的所有方法都默认是由public abstract修饰的(抽象方法)
* 只能是 public abstract,其他修饰符都会报错,可以省略public和abstract
* 接口不可实例化,即不能创建对象,也就没有构造器可言
* 实现类:
* 实现类为非抽象类必须重写所有抽象方法,实现类是抽象类可以不重写
* 默认方法可重写也可不重写 默认方法重写就暴怒用再写default关键字了
* 不能重写静态方法
*/
interface A{
//接口成员
String name ="12";
int age = 0;
//抽象方法
void breath();
void see();
//默认方法---->default修饰
public default void walk(){
System.out.println("默认都会走路");
}
//静态方法
public static void black(){
System.out.println("黑色头发");
}
}
class RealA implements A{//实现接口A
@Override
public void breath() {
System.out.println("重写 会呼吸");
}
@Override
public void see() {
System.out.println("重写 有眼睛能看见");
}
}
接口引用
/**
* 静态方法只能直接通过接口名调用:
* 抽象方法、默认方法只能通过实现类对象调用
* 接口不能创建对象
*/
interface A{
String name = "张三";
void breath();
public static void run(){
System.out.println("会走路");
}
public default void black(){
System.out.println("黑色头发");
}
}
class B implements A{
@Override
public void breath() {
System.out.println("会呼吸");
}
}
class Test00{
public static void main(String[] args) {
B b1 = new B();
//默认方法
b1.breath();
b1.black();
//静态方法
A.run();
}
}
接口的多实现
之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
【修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
定义多个接口:
interface A {
public abstract void showA();
public abstract void show();
}
interface B {
public abstract void showB();
public abstract void show();
}
定义实现类:
public class C implements A,B{
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
@Override
public void show() {
System.out.println("show");
}
}
interface A{
String name = "1";
void breath();
static void black(){
System.out.println("黑色头发");
}
default void body(){
System.out.println("默认四肢健全");
}
}
interface B{
int age = 0;
void see();
void breath();
}
class Test implements A,B{
@Override
public void breath() {// 两接口重名方法重写一次即可
System.out.println("都会喘气");
}
@Override
public void see() {
System.out.println("都能看见东西");
}
}
class TestLast{
public static void main(String[] args) {
Test t1 = new Test();
t1.body();//实现类未重写,调用接口该默认方法
t1.breath();//调用实现类重写接口抽象方法
}
}
默认方法冲突问题
亲爹优先原则
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。代码如下:
定义接口:
interface A {
public default void methodA(){
System.out.println("AAAAAAAAAAAA");
}
}
定义父类:
class D {
public void methodA(){
System.out.println("DDDDDDDDDDDD");
}
}
定义子类:
class C extends D implements A {
// 未重写methodA方法
}
class B extends D implements A{
//当然也可以选择重写
public void methodA(){
System.out.println("BBBBBBBBBBBB");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
C c = new C();
c.methodA(); //调用父类的重名方法
B b = new B();
b.methodA();
}
}
输出结果:
DDDDDDDDDDDD
BBBBBBBBBBBB
必须做出选择
当一个类同时实现了多个接口,而多个接口中包含方法签名相同的默认方法时,怎么办呢?
无论你多难抉择,最终都是要做出选择的。代码如下:
声明接口:
interface A{
public default void d(){
System.out.println("今晚7点-8点陪我吃饭看电影");
}
}
interface B{
public default void d(){
System.out.println("今晚7点-8点陪我逛街吃饭");
}
}
选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法。
class C implements A,B{
@Override
public void d() {
A.super.d();
}
}
选择自己完全重写:
class D implements A,B{
@Override
public void d() {
System.out.println("自己待着");
}
}
接口的多继承
一个接口能继承另一个或者多个接口,接口的继承也使用 extends
关键字,子接口继承父接口的方法。
定义父接口:
interface A {
void a();
public default void methodA(){
System.out.println("AAAAAAAAAAAAAAAAAAA");
}
}
interface B {
void b();
public default void methodB(){
System.out.println("BBBBBBBBBBBBBBBBBBB");
}
}
定义子接口:
interface C extends A,B{
@Override
public default void methodB() {
System.out.println("CCCCCCCCCCCCCCCCCCCC");
}
}
注意:
子接口重写默认方法时,default关键字可以保留。
子类重写默认方法时,default关键字不可以保留。
class D implements C{
/**
* 接口多继承
* 写法一:interface C extends A,B{} class TX implements C{}
* 写法二:class TX implements A,B,C
*/
@Override
public void a() {
System.out.println("xxxxx");
}
@Override
public void b() {
System.out.println("yyyyy");
}
}
class E implements A,B,C{//效果和上面的D是等价的
@Override
public void b() {
System.out.println("xxxxx");
}
@Override
public void a() {
System.out.println("yyyyy");
}
}
接口与实现类对象的多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
public class TestInterface {
public static void main(String[] args) {
Flyable b = new Bird();
b.fly();
Flyable k = new Kite();
k.fly();
}
}
interface Flyable{
//抽象方法
void fly();
}
class Bird implements Flyable{
@Override
public void fly() {
System.out.println("展翅高飞");
}
}
class Kite implements Flyable{
@Override
public void fly() {
System.out.println("别拽我,我要飞");
}
}