- 接口和内部类为我们提供了一种将接口和实现分离的更加结构化的方法。
- Java很看重接口机制,因此提供了相比C++更直接的支持。抽象类是介于普通类和接口之间的。
- 抽象类和接口都是为了抽象、扩展等概念存在的。抽象类只不过约束比接口更强。
- 抽象可以说是一种基本形式,是一群类可操纵的部分,并且到底是final限定继承,可重写继承,添加删除公共接口,非常灵活。
- 在一个项目中,对类设计实际上就是画一棵树,而抽象类和接口就是能够操纵一群类的公共方法在继承层次结构移动的重构工具。
- 没有抽象方法也可以添加abstract关键字到class关键字前。
- 子类可以实现 0、部分、 全部 父类抽象方法。未全部实现则也是抽象类。全部实现也可以添加abstract关键字。
- 抽象是不能实例化的。所见即所得的道理。
- 抽象类可以有构造方法,静态成员等,除了不能实例化和可以提供抽象方法外其他和普通方法一样。
- [策略模式]: 抽象父类定义抽象类由不同实现类自己实现,然后提供一个或者几个final方法调用这些抽象方法。即将实现交给子类。或者定义一个类,有个接受这个抽象类为参数的方法。
abstract class Father {
Father(){
print();
}
abstract void print();
}
public class Son extends Father{
int i = 3;
@Override
void print() {
// TODO Auto-generated method stub
System.out.println(i);
}
public static void main(String[] args) {
Son s = new Son();
s.print();
}
}
Output: 0 3
这么看,抽象类只不过把一部分逻辑不提供默认实现,同时延迟到子类中实现罢了,是编程语言提供给程序结构设计者的工具。
- 接口提供: 接口协议 和 多态的向上向下转型。
- 接口在一个编译单元和一个类是一样的,即只能和类只有一个是public的,且和文件同名。
- 类实现接口时只能一次性全部实现,不能部分实现。并且无论该类是如何类型的类,所有实现的方法都要加
public
,否则就缩小了父类方法的权限控制范围。 - 接口可以随时添加新的方法声明,也可以使用
extends
进行接口多继承。 - 接口可以使方法和类完全解耦。一个接受接口为参数的方法比一个接受固定类的方法更灵活,后者只能使用这个类和其子类,而接口在类图中可以把限制放宽到以接口为根的整个子树。
此处有图
- 如果一个方法操作的是类,则就只能传入这个类及其子类。但是却无法应用到 ``` 不在此继承结构中的某个类 `` ,而使用接口就可以将这个方法和以这个接受的类及其子类解耦。例如
class Father{ // 基类类型
protected String getName(){
return this.getClass().getSimpleName();
}
void method(){ // 提供默认实现,可以由子类重写
System.out.println(getName());
}
}
class Son1 extends Father{
@Override
void method(){System.out.println(getName() + " - son1");}
}
class Son2 extends Father{
@Override
void method(){System.out.println(getName() + " - son2");}
}
class Son3 extends Father{
@Override
void method(){System.out.println(getName() + " - son3");}
}
public class Main1{
public static void apply(Father e){ // 策略模式,此处执行算法中不变的部分,根据传入的不同类型执行其实现的不同算法。
System.out.print(e.getName()+ " : ");
e.method();
}
public static void main(String[] args) {
Random rand = new Random();
Father[] f= new Father[5];
for(int i = 0; i < f.length; i ++){
switch(rand.nextInt(3)){ //0 - 2
case 0: f[i] = new Son1(); break;
case 1: f[i] = new Son2(); break;
case 2: f[i] = new Son3(); break;
}
}
for(int i = 0; i < f.length ; i++){
apply(f[i]); // 多态调用
}
}
}
若假设此时有一个以Mother类为基类的类似的继承体系,正好有void method()方法,要想复用 Main1.apply()
方法却无法复用。因为 使用了与类型耦合过紧的传入参数,从而无法实现方法复用
。 因此需要将共同方法单独做成接口,将apply方法改成接受接口的参数。 然后将原来的基类改成实现接口的抽象类,需要提供默认实现和固定算法的部分在这里实现,然后将需要子类实现的方法留在产生类中。但是这种方法需要修改大量的类,也可以使用 适配器模式
提供一个代理类,它实现公共接口,接受Mother类型的对象,并且提供转换方法。
使用适配器模式
interface PubMethod{
void method();
}
class MotherAdapter implements PubMethod{ // 对Mother实现代理访问。这个适配器类可以接受一个不含有在公共方法中的类型,然后因为适配器类实现了接口,所以它可以作为访问Mother类的代理。
Mother m = null;
MotherAdapter(Mother m){ this.m = m; }
void method(){ // 本适配器类只需实现公共接口中的方法就可以了。
if(m != null) return m.method();
}
}
public class Main1{
public static void apply(Father e){ // 注意,此处仍是接受Father 方法。
System.out.print(e.getName()+ " : ");
e.method();
}
public static void main(String[] args) {
Daughter d = new Daughter();
apply( new MotherAdapter (d) );
}
}
用一句话形容,就是适配器模式就是将一个不支持公共接口的类,由另一个类代理,并且提供公共接口的设计模式。
- 因为接口没有具体实现,即没有任何与接口相关的存储,所以可以实现多个接口的组合。Java中类的多重实现接口只能有一个类并且一次性完成。 实现接口的类可以转型成任一个接口类型。
- 一般来说,接口名都是‘定语’,例如 CanFly, CanSwim, Writable, Compareable, Serializable 等等。
- 多接口混合支持正常的重载,重写机制等,例如
interface i1{
void method();
void show();
}
class Father{
void show(){;;}
}
class Child extends Father implements i1{
void method(){;;;}
//没有关于 show 方法的声明。
//void show(){;;} 可去掉注释,此时为重写
}
但是在多重继承接口的过程中,出现命名冲突,则会出现错误。如下
interface i1 {void f();}
interface i2 {int f(int i);}
interface i3 {int f();}
class C { public int f() { return 1; } }
class C2 implements i1, i2 {
public void f(){}
public int f(int i) { return 1; } //重载
}
class C3 extends C implements i2 {
public int f(int i) { return 1; } //重载
}
class C4 extends C implenets i3 {
public int f() { return 1; } //重写
}
// class C5 extends C implements i1 {} //错误
// interface i4 extends i1, i3 {} //错误
- 使用接口的两大理由: 可以向上转型多个基类型的灵活性 和 防止直接创建该类型的对象。
- 可以在任何现有类上添加新的接口,即让方法接受接口类型,是让任何类都可以对该方法进行适配的方式。
- 接口只有
public和包默认
权限,其中的方法为public abstract
型, 变量为static final
型即常量值 。 变量不能是空final
值,但可以用非常量表达式初始化。
interface i1{
Random rand = new Random();
int i = rand.nextInt(1);
Double d = rand.nextDouble() * 100 ;
String s = new Scanner(System.in).nextLine();
int [] a = new int[10];
}
public class Main1{
public static void main(String[] args) {
System.out.println(i1.i);
System.out.println(i1.d);
System.out.println(i1.s);
System.out.println(i1.a[9]);
//a = new int[5]; goes wrong
}
}
//Input : 99
//Output:
0
20.713365652412374
99
0
可见,接口与类都在持久区的静态变量区初始化。无论对于基本数据类型还是引用变量,都是Static final型的。
- 接口可以写在类和接口中,并且支持更深入的嵌套。并且可以加
private
修饰。但是在嵌入到接口中时,因为默认所有接口成员都是public的,因此不可加私有修饰。
interface i1{
// private interface i11
interface i11
{
}
}
另外,作为私有成员的接口是不可被外部引用的,只可被类内的嵌套类所实现。
class c1{
private interface i1{
}
interface i2{
}
}
class t implements c1.i2
//class t implements c1.i1 //c1.i1 is not visiable
{
}
- 常用的
抽象工厂方法
,提供产品和工厂的抽象接口,然后可以根据各个产品,工厂的不同编写实现类。若不用工厂方法,就必须在某处指定将要创建的产品类的确切类型,然后调用合适的构造器。 添加这种额外级别的间接性,是因为创建框架的需要。 - 恰当的原则是优先选择类而不是选择接口。从类开始,若接口的必需性变得很明确,则要进行重构,接口是一种重要的重构工具,但是却容易被滥用。