Java抽象类与接口

AI摘要:

对应《疯狂Java讲义(第5版)》6.5-6.6 章节

抽象类(abstract)

从多个类中抽象出来的模板,避免子类设计的随意性。

某种情况下,父类只能知道子类应该具备一个怎样的方法,但是不能够明确知道如何实现该方法,只能在子类中才能确定如何去实现方法体。例如:所有几何图形都应该具备一个计算面积的方法,但是不同的几何图形计算面积的方式不同。

抽象方法(abstract修饰)没有方法主体,表明这个方法必须由子类重写实现。

public abstract void test( ); 后面无{ }
public void test( ){ } 已经定义方法体,只是方法体为空,不可使用abstract修饰

Java语法规定,包含抽象方法的类只能定义为抽象类。其中,包含抽象方法指:

直接定义一个抽象方法
继承抽象类或接口,但没有完全实现全部抽象方法

抽象类无法实例化,无法使用new创建抽象类的实例,抽象类只能被继承,其构造器只能在创建子类实例时通过子类调用。

抽象类普通方法可依赖于抽象方法,而抽象方法推迟到子类中实现。

Shape.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public abstract class Shape
{
{
System.out.println("执行Shape的初始化块...");
}
private String color;
// 定义一个计算周长的抽象方法
public abstract double calPerimeter();
// 定义一个返回形状的抽象方法
public abstract String getType();
// 定义Shape的构造器,该构造器并不是用于创建Shape对象,
// 而是用于被子类调用
public Shape(){}
public Shape(String color)
{
System.out.println("执行Shape的构造器...");
this.color = color;
}
// 省略color的setter和getter方法
public void setColor(String color)
{
this.color = color;
}
public String getColor()
{
return this.color;
}
}

Triangle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Triangle extends Shape
{
// 定义三角形的三边
private double a;
private double b;
private double c;
public Triangle(String color , double a
, double b , double c)
{
super(color);
this.setSides(a , b , c);
}
public void setSides(double a , double b , double c)
{
if (a >= b + c || b >= a + c || c >= a + b)
{
System.out.println("三角形两边之和必须大于第三边");
return;
}
this.a = a;
this.b = b;
this.c = c;
}
// 重写Shape类的的计算周长的抽象方法
public double calPerimeter()
{
return a + b + c;
}
// 重写Shape类的的返回形状的抽象方法
public String getType()
{
return "三角形";
}
}

Circle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Circle extends Shape
{
private double radius;
public Circle(String color , double radius)
{
super(color);
this.radius = radius;
}
public void setRadius(double radius)
{
this.radius = radius;
}
// 重写Shape类的的计算周长的抽象方法
public double calPerimeter()
{
return 2 * Math.PI * radius;
}
// 重写Shape类的的返回形状的抽象方法
public String getType()
{
return getColor() + "圆形";
}
public static void main(String[] args)
{
Shape s1 = new Triangle("黑色" , 3 , 4, 5);
Shape s2 = new Circle("黄色" , 3);
System.out.println(s1.getType());
System.out.println(s1.calPerimeter());
System.out.println(s2.getType());
System.out.println(s2.calPerimeter());
}
}

Shape类型引用变量可直接指向Triangle和Circle类型的对象,调用相应方法,无需转换为子类类型,更好发挥多态的优势。

接口(interface)

定义多个类应遵守的规范,规定这批类必须提供某些方法,而不关心实现细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[public] interface 接口名 extends 父接口1, 父接口2...
{
成员变量(静态常量)定义
//自动添加public static final修饰符,必须定义时指定默认值
抽象方法(普通方法)定义
//自动使用public abstract修饰
内部类、接口、枚举定义
//自动使用public abstract修饰
私有方法、默认方法或类方法定义(Java9新增)
//默认方法 default
//类方法 static
//私有方法 private
/*
不能包含构造器和初始化块
*/
}

修饰符只能为public省略(包权限访问);

接口只能继承接口,不能继承类,可有多个父接口,获得所有抽象方法、常量;

一个Java源文件只能有一个Public接口,且文件名与接口名相同;

接口可以用于声明引用变量类型(可引用接口实现类的对象),或通过接口直接调用常量(均为public static final)。

Output.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface Output
{
// 接口里定义的成员变量只能是常量
int MAX_CACHE_LINE = 50;
// 接口里定义的普通方法只能是public的抽象方法
void out();
void getData(String msg);
// 在接口中定义默认方法,需要使用default修饰
default void print(String... msgs)
{
for (String msg : msgs)
{
System.out.println(msg);
}
}
// 在接口中定义默认方法,需要使用default修饰
default void test()
{
System.out.println("默认的test()方法");
}
// 在接口中定义类方法,需要使用static修饰
static String staticTest()
{
return "接口里的类方法";
}
// 定义私有方法
private void foo()
{
System.out.println("foo私有方法");
}
// 定义私有静态方法
private static void bar()
{
System.out.println("bar私有静态方法");
}
}
  • 默认方法 default,自动添加public,只能由接口的实现类的实例调用默认方法,相当于有方法体的实例方法
  • 类方法 static,自动添加public,可以由接口直接调用
  • 私有方法 private,私有类方法 private static ,用于实现隐藏的工具方法,为默认方法或类方法提供支持

接口被类实现(implements)

1
2
3
4
[修饰符] class 类名 extends 父类 implements 接口1, 接口2...
{

}

类必须实现接口定义的全部抽象方法,否则将保留抽象方法,且必须定义为抽象类

实现接口方法,必须使用public控制符,权限只能更大(接口抽象方法默认public abstract);

可同时实现多个接口,弥补单继承的不足。

Printer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 定义一个Product接口
interface Product
{
int getProduceTime();
}
// 让Printer类实现Output和Product接口
public class Printer implements Output , Product
{
private String[] printData
= new String[MAX_CACHE_LINE];
// 用以记录当前需打印的作业数
private int dataNum = 0;
public void out()
{
// 只要还有作业,继续打印
while(dataNum > 0)
{
System.out.println("打印机打印:" + printData[0]);
// 把作业队列整体前移一位,并将剩下的作业数减1
System.arraycopy(printData , 1
, printData, 0, --dataNum);
}
}
public void getData(String msg)
{
if (dataNum >= MAX_CACHE_LINE)
{
System.out.println("输出队列已满,添加失败");
}
else
{
// 把打印数据添加到队列里,已保存数据的数量加1。
printData[dataNum++] = msg;
}
}
public int getProduceTime()
{
return 45;
}
public static void main(String[] args)
{
// 创建一个Printer对象,当成Output使用
Output o = new Printer();
o.getData("轻量级Java EE企业应用实战");
o.getData("疯狂Java讲义");
o.out();
o.getData("疯狂Android讲义");
o.getData("疯狂Ajax讲义");
o.out();
// 调用Output接口中定义的默认方法
o.print("孙悟空" , "猪八戒" , "白骨精");
o.test();
// 创建一个Printer对象,当成Product使用
Product p = new Printer();
System.out.println(p.getProduceTime());
// 所有接口类型的引用变量都可直接赋给Object类型的变量
Object obj = p;
}
}

面向接口编程

简单工厂模式

具体场景:Computer类需要组合一个输出设备,如前文提到的Printer类,在系统重构时,需要用BetterPrinter代替Printer。

对于如下Computer,完全与Printer类分离,只与Output接口耦合,且不负责创建Output对象。

Computer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Computer
{
private Output out;
public Computer(Output out)
{
this.out = out;
}
// 定义一个模拟获取字符串输入的方法
public void keyIn(String msg)
{
out.getData(msg);
}
// 定义一个模拟打印的方法
public void print()
{
out.out();
}
}

提供Output工厂负责生成Output对象,改变getOutput()方法即可更改Output具体的实现类。

OutputFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OutputFactory
{
public Output getOutput()
{
// return new Printer();
return new BetterPrinter();
}
public static void main(String[] args)
{
OutputFactory of = new OutputFactory();
Computer c = new Computer(of.getOutput());
c.keyIn("轻量级Java EE企业应用实战");
c.keyIn("疯狂Java讲义");
c.print();
}
}

重构后的BetterPrinter,修改return new BetterPrinter()即可运行新的BetterPrinter对象

BetterPrinter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class BetterPrinter implements Output
{
private String[] printData
= new String[MAX_CACHE_LINE * 2];
// 用以记录当前需打印的作业数
private int dataNum = 0;
public void out()
{
// 只要还有作业,继续打印
while(dataNum > 0)
{
System.out.println("高速打印机正在打印:" + printData[0]);
// 把作业队列整体前移一位,并将剩下的作业数减1
System.arraycopy(printData , 1, printData, 0, --dataNum);
}
}
public void getData(String msg)
{
if (dataNum >= MAX_CACHE_LINE * 2)
{
System.out.println("输出队列已满,添加失败");
}
else
{
// 把打印数据添加到队列里,已保存数据的数量加1。
printData[dataNum++] = msg;
}
}
}

命令模式

适用场景:某个方法需要完成某一行为,但具体实现无法确定,必须等到执行该方法时才可以确定

具体一点:某个方法需要遍历某个数组的数组元素,但无法确定在遍历时如何处理这些元素,需要在调用该方法时指定具体的处理行为

使用Command接口定义方法,封装处理行为,但具体行为尚未定义。

1
2
3
4
5
public interface Command
{
// 接口里定义的process()方法用于封装“处理行为”
void process(int[] target);
}

数组的处理类中,Command参数负责具体的处理行为。

1
2
3
4
5
6
7
public class ProcessArray
{
public void process(int[] target , Command cmd)
{
cmd.process(target);
}
}

传入Command对象,确定数组的处理行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CommandTest
{
public static void main(String[] args)
{
ProcessArray pa = new ProcessArray();
int[] target = {3, -4, 6, 4};
// 第一次处理数组,具体处理行为取决于PrintCommand
pa.process(target , new PrintCommand());
System.out.println("------------------");
// 第二次处理数组,具体处理行为取决于SquareCommand
pa.process(target , new SquareCommand());
}
}

两次不同的处理行为通过PrintCommandSquareCommand类提供

1
2
3
4
5
6
7
8
9
10
public class PrintCommand implements Command
{
public void process(int[] target)
{
for (int tmp : target )
{
System.out.println("迭代输出目标数组的元素:" + tmp);
}
}
}
1
2
3
4
5
6
7
8
9
10
public class SquareCommand implements Command
{
public void process(int[] target)
{
for (int tmp : target )
{
System.out.println("数组元素的平方是:" + tmp*tmp);
}
}
}

Java抽象类与接口
https://blog.cngo.rr.nu/posts/1229.html
作者
cngo
发布于
2024年6月20日
许可协议