目录
一、抽象类
(一)抽象类的引出
(二)抽象类基本介绍
(三)注意事项和使用细节
(四)抽象类的最佳实践——模板设计模式
二、接口
(一)接口快速入门
(二)基本介绍
(三)注意事项与使用细节
(四)接口VS继承
(五)接口的多态性
1.多态参数
2.多态数组
3.接口存在多态传递现象
(六)练习题
一、抽象类
(一)抽象类的引出
当父类的某些方法,需要声明,但是又不确定如何实现时,就可以将其声明为抽象方法,那么这个就是抽象类。
class Animal{
private String name;
public Animal(String name) {
this.name = name;
}
// 这里的eat实现了没有什么意义
// 即:父类方法的不确定性问题
public void eat(){
System.out.println("这时一只动物,但是不知道吃什么");
}
}
父类方法有不确定性,所以可以将该方法设计为抽象(abstract)方法,所谓抽象方法就是没有实现的方法,即没有方法体。
改写后的代码:
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public abstract void eat();
}
当父类的一些方法不确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类。即:当一个类中存在抽象方法时,需要将该类声明为abstract类。一般来说,抽象类会被继承,由其子类来实现抽象方法。
(二)抽象类基本介绍
- 用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{ }
- 用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表); // 没有方法体
抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类,在框架和设计模式中使用比较多
(三)注意事项和使用细节
- 抽象类不能被实例化
- 抽象类不一定要包含abstract方法,也就是说,抽象类可以没有abstract方法,还可以有实现的方法
- 一旦类包含了abstract方法,则这个类必须声明为abstract
- abstract只能修饰类和方法,不能修饰属性和其他的。
- 抽象类可以有任意成员(因为抽象类仍然是类),比如:非抽象方法、构造器、静态属性等等
abstract class A { private String name; public static int age = 10; public static final int FEED_TIMES = 3; public A() { System.out.println("无参构造器"); } public A(String name) { System.out.println("有参构造器"); this.name = name; } public static void ok() { System.out.println("静态方法"); } public void hello() { System.out.println("成员方法"); } public abstract void m1(); static { System.out.println("static代码块"); } { System.out.println("普通代码块"); } }
- 抽象方法不能有方法体
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
abstract class E { public abstract void hi(); } abstract class F extends E { } class G extends E { /** * 这里相当于子类G实现了父类E的抽象方法, * 所谓实现方法,就是有方法体 */ @Override public void hi() { } }
抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
(四)抽象类的最佳实践——模板设计模式
模板设计模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,而将一些步骤的实现延迟到子类中。
例如子类AA和子类BB中都有job方法,想要查看每个类的job方法的运行时间,就需要在每个job方法中进行时间差的计算,会造成代码重复,将计算时间差的重复代码抽取到父类中,形成一个模板,并且父类定义job的抽象方法,模板中调用job抽象方法。
此时子类可以先重写各自的job方法,在main方法中调用模板即可,根据Java的动态绑定机制,哪个对象调用模板,最后就运行哪个对象的job方法。
public class TestTemplate {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime(); // 执行时间:4
BB bb = new BB();
bb.calculateTime(); // 执行时间:7
}
}
abstract class Template {
// 抽象方法
public abstract void job();
// 普通父类方法,调用抽象方法
// 下面的方法时一个模板,可以用final修饰,防止被其他类重写
public final void calculateTime() {
long start = System.currentTimeMillis();
job(); // 动态绑定机制,子类可以自定义job方法的内部逻辑
long end = System.currentTimeMillis();
System.out.println("执行时间:" + (end - start));
}
}
class AA extends Template {
@Override
public void job() {
long sum = 0;
for (long i = 1; i <= 8000000; i++) {
sum += i;
}
}
}
class BB extends Template {
@Override
public void job() {
long sum = 0;
for (long i = 1; i <= 8000000; i++) {
sum *= i;
}
}
}
二、接口
(一)接口快速入门
现实生活中我们常说的接口例如USB接口,你可以把手机、相机、U盘都插在USB插槽上,而不用担心哪个插槽是专门插哪个的,原因是做USB插槽的厂家,和做各种设备的厂家都遵守了同意的规定,包括尺寸、排线等等。
下面我们先看用代码实现接口的案例:
首先定义一个USB接口:
public interface UsbInterface { //接口
//规定接口的相关方法
public void start();
public void stop();
}
再定义一个Phone类,实现这个接口,并实现接口中的全部方法:
// 即 Phone类需要实现 UsbInterface接口 规定/声明的方法
public class Phone implements UsbInterface {
@Override
public void start() {
System.out.println("手机开始工作...");
}
@Override
public void stop() {
System.out.println("手机停止工作.....");
}
}
再定义一个Camera类,也实现这个接口,并实现接口中的全部方法:
public class Camera implements UsbInterface {
// 实现接口,就是把接口方法实现
@Override
public void start() {
System.out.println("相机开始工作...");
}
@Override
public void stop() {
System.out.println("相机停止工作....");
}
}
定义一个Computer类,能够接收接口:
public class Computer {
//编写一个方法, 计算机工作:
//1. UsbInterface usbInterface 形参是接口类型 UsbInterface
//2. 看到 接收 实现了 UsbInterface接口的类的对象实例
public void work(UsbInterface usbInterface) {
// 通过接口,来调用方法
usbInterface.start();
usbInterface.stop();
}
}
main方法中创建对象运行:
public class Interface01 {
public static void main(String[] args) {
//创建手机,相机对象
//Camera 实现了 UsbInterface
Camera camera = new Camera();
//Phone 实现了 UsbInterface
Phone phone = new Phone();
//创建计算机
Computer computer = new Computer();
computer.work(phone);// 把手机接入到计算机
System.out.println("===============");
computer.work(camera);// 把相机接入到计算机
}
}
运行结果:
(二)基本介绍
接口就是给出一些没有实现的方法,封装到一起,直到某个类要使用的时候,再根据具体情况把这些方法实现了、
基本语法:
interface 接口名{
// 属性
// 方法(1.抽象方法; 2.默认实现方法 3.静态方法)
}
class 类名 implements 接口{
自己的属性;
自己的方法;
必须实现的接口的抽象方法;
}
小结:
- 在JDK7.0以前,接口里的所有方法都没有方法体,即都是抽象方法。
- JDK8.0及以后,接口类可以有静态方法、默认方法,也就是说,接口中可以有方法的具体实现。即(接口类中可以定义:1.抽象方法; 2.默认实现方法 3.静态方法)
public interface AInterface { //属性: public int n1 = 10; // 方法: // 在接口中,抽象方法,可以省略abstract关键字 public void hi(); // 在jdk8后,接口中可以有默认实现方法,需要使用default关键字修饰 default public void ok() { System.out.println("ok ..."); } // 在jdk8后, 接口中可以有静态方法 public static void cry() { System.out.println("cry ...."); } } // 1.如果一个类 implements实现 接口 // 2.需要将该接口的所有抽象方法都实现 class A implements AInterface { @Override public void hi() { System.out.println("hi()...."); } }
(三)注意事项与使用细节
接口不能被实例化
接口中所有的方法是 public方法,接口中抽象方法,可以不用abstract修饰
一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用alt+enter来解决
抽象类去实现接口时,可以不实现接口的抽象方法
一个类同时可以实现多个接口:class Pig implements IB, IC {}
接口中的属性,只能是final的,而且是 public static final 修饰符,并且必须被初始化:
interface IB {int n1 = 10;} // public static final 部分可以省略
接口中属性的访问形式:接口名.属性名
接口不能继承其它的类,但是可以继承多个别的接口:interface ID extends IB, IC {}
接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的:interface IE {}
public class InterfaceDetail01 {
public static void main(String[] args) {
// 1.接口不能被实例化
// new IA();
}
}
interface IA {
// 2.接口中所有的方法是 public方法, 接口中抽象方法,可以不用abstract修饰
void say();// 修饰符 public protected 默认 private
// 相当于 public abstract void say();
void hi();
}
// 3.一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用alt+enter来解决
class Cat implements IA {
@Override
public void say() {
System.out.println("实现接口IA的say方法");
}
@Override
public void hi() {
System.out.println("实现接口IA的hi方法");
}
}
// 4.抽象类去实现接口时,可以不实现接口的抽象方法
abstract class Tiger implements IA {}
public class InterfaceDetail02 {
public static void main(String[] args) {
// 接口中的属性,是 public static final
// 7.接口中属性的访问形式:接口名.属性名
System.out.println(IB.n1);// 说明n1 就是static
// IB.n1 = 30; // 无法重新赋值,说明n1是final
}
}
interface IB {
// 6.接口中的属性,只能是final的,而且是 public static final 修饰符,并且必须被初始化
int n1 = 10; // 等价于 public static final int n1 = 10;
void hi();
}
interface IC {
void say();
}
// 5.一个类同时可以实现多个接口
class Pig implements IB, IC {
@Override
public void hi() {
System.out.println("实现IB的hi方法");
}
@Override
public void say() {
System.out.println("实现IC的say方法");
}
}
// 8.接口不能继承其它的类,但是可以继承多个别的接口
interface ID extends IB, IC {}
// 9.接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的
interface IE {}
小练习:分析下面代码是否有错误与输出结果:
public class InterfaceExercise01 {
public static void main(String[] args) {
B b = new B();//ok
System.out.println(b.a); //23 因为a是public,所以可以访问
System.out.println(AA.a); //23 因为a是static类型,所以可以直接调用
System.out.println(B.a); //23 因为B实现了AA接口,所以AA的属性和方法也能访问到
}
}
interface AA {
int a = 23; //等价 public static final int a = 23;
}
class B implements AA {//正确
}
(四)接口VS继承
- 接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计好各种规范(方法),让其它类去实现这些方法。
- 接口比继承更加灵活
继承是满足is-a的关系,比如Cat可以继承Animal,而Computer不能;
接口只需满足like-a的关系,比如,通过实现接口中的方法,Cat也可以像Computer一样具有上网的功能。
- 接口在一定程度上实现代码的解耦[即:接口规范性+动态绑定机制]
public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey littleMonkey = new LittleMonkey("悟空");
// 子类可以调用父类的属性和方法
littleMonkey.climbing(); // 悟空可以爬树...
// 子类可以调用重写的接口方法
littleMonkey.swimming(); // 悟空通过学习,可以像鱼儿一样游泳...
littleMonkey.flying(); // 悟空通过学习,可以像鸟儿一样飞翔...
}
}
// 如果LittleMonkey想要实现父类Monkey没有的功能,就需要实现对应的接口
// 定义接口Fishable
interface Fishable {
// 编写接口中的抽象方法,swimming
void swimming();
}
// 另一个接口Birdable
interface Birdable {
void flying();
}
// 编写父类Monkey
class Monkey {
private String name;
public void climbing() {
System.out.println(name + "可以爬树...");
}
public Monkey(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 编写子类LittleMonkey继承父类Monkey
class LittleMonkey extends Monkey implements Fishable, Birdable {
// 因为父类没有无参构造,只有有参构造,所以子类必须实现父类指定的有参构造
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName() + "通过学习,可以像鱼儿一样游泳...");
}
@Override
public void flying() {
System.out.println(getName() + "通过学习,可以像鸟儿一样飞翔...");
}
}
小结:
- 当子类继承了父类,就自动的拥有父类的功能
- 如果子类需要扩展功能,可以通过实现接口的方式扩展
- 可以理解为:实现接口 是 对Java单继承机制的一种补充
(五)接口的多态性
1.多态参数
在前面的USB接口案例,UsbInterface既可以接收手机Phone对象,又可以接收相机Camera对象,就体现了接口的多态:
(1)接口类型的变量 可以指向 实现接口类的对象实例
(2)父类类型的变量 可以指向 实现父类的子类的对象实例
public class InterfacePloyParameter {
public static void main(String[] args) {
// 接口体现的多态:
// 接口类型的变量 if01 可以指向 实现了IF接口类的对象实例
IF if01 = new Monster();
if01 = new Fish();
// 继承体现的多态:
// 父类类型的变量 aaa 可以指向 继承了AAA父类的子类的对象实例
AAA aaa = new BBB();
aaa = new CCC();
}
}
interface IF{}
class Monster implements IF{}
class Fish implements IF{}
class AAA{}
class BBB extends AAA{}
class CCC extends AAA{}
2.多态数组
案例:
在Usb接口数组中,存放Phone和Camera对象,Phone类还有一个特有的方法call(),Camera类还有一个特有的方法photo()。
遍历Usb数组:如果是Phone对象,除了调用Usb 接口定义的方法外,还需要调用Phone特有方法call();如果是Camera对象,除了调用Usb接口定义的方法外,还需要调用Camera特有方法photo()
代码实现:
public class InterfacePolyArr {
public static void main(String[] args) {
//多态数组 -> 接口类型数组
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
for (int i = 0; i < usbs.length; i++) {
usbs[i].work();//动态绑定..
// 和前面一样,我们仍然需要进行类型的 向下转型
if (usbs[i] instanceof Phone_) {// 判断usbs[i]的运行类型是 Phone_
((Phone_) usbs[i]).call();
} else if (usbs[i] instanceof Camera_) {
((Camera_) usbs[i]).photo();
}
}
}
}
interface Usb {
void work();
}
class Phone_ implements Usb {
// Phone_特有的方法
public void call() {
System.out.println("手机可以打电话...");
}
@Override
public void work() {
System.out.println("手机工作中...");
}
}
class Camera_ implements Usb {
// Camera_特有的方法
public void photo() {
System.out.println("相机拍照更清晰...");
}
@Override
public void work() {
System.out.println("相机工作中...");
}
}
3.接口存在多态传递现象
如果类A实现了接口IN1,类A必须要实现接口IN1的抽象方法;如果接口IN1还继承了接口IN2,那么类A也必须要实现接口IN2的抽象方法。这就是接口的多态传递现象。
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了该接口的类的对象实例
IG ig = new Teacher();
//如果IG 继承了 IH 接口,而Teacher 类实现了 IG接口
//那么,实际上就相当于 Teacher 类也实现了 IH接口.
//这就是所谓的 接口多态传递现象.
IH ih = new Teacher();
}
}
interface IH {
void hi();
}
interface IG extends IH {
}
class Teacher implements IG {
@Override
public void hi() {
}
}
(六)练习题
System.out.println(x);是否正确?
public class InterfaceExercise02 {
public static void main(String[] args) {
new C().pX(); // 0 1
}
}
interface AB {
int x = 0;
} // 等价 public static final int x = 0;
class BB {
int x = 1;
} //普通属性
class C extends BB implements AB {
public void pX() {
//System.out.println(x); //错误,原因不明确x
//可以明确指定x
//访问接口的 x 就使用 AB.x
//访问父类的 x 就使用 super.x
System.out.println(AB.x + " " + super.x);
}
}