(参考http://www.cnblogs.com/xdp-gacl/p/3648398.html 点击打开链接,以此为模板 自己做了整理、修改)
1.1 抽象类概念
先给出一个案例,深入理解抽象类
由以上的分析,只需要在父类Animal 中定义这个enjoy() 方法,而去不要去实现,此方法的实现留给继承它的子类去重写。使用关键字abstract 把enjoy() 方法定义成一个抽象方法,如:public abstract void enjoy();
从某种意义上来说,抽象方法就是被用来重写的,所以在父类声明的抽象方法一定要在子类里面重写。如果真的不想在子类里面重写这个方法,那么可以再在子类里面把这个方法再定义为抽象方法,因为子类觉得我去实现也不合适,应该让继承我的子类去实现比较合适,因此也可以在继承这个子类的下一个子类里面重写在父类里面声明的抽象方法,这是可以的。
这里有一个规则:既然父类里面的方法是抽象的,那么对于整个类来说,它就有一个没有实现的方法,这个方法不知道怎么去实现,那么这个类是就是残缺不全的,因此这个类应该被定义为一个抽象类。所以前面这样声明的class Animal应该要在class的前面加上abstract,即声明成这样:abstract class Animal,这样Animal类就成了一个抽象类了。
抽象类的案例,如下:
package javastudy.summary;
/**
* 父类Animal
* 在class的前面加上abstract,即声明成这样:abstract class Animal
* 这样Animal类就成了一个抽象类了
*/
abstract class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
/**
* 抽象方法
* 这里只有方法的定义,没有方法的实现。
*/
public abstract void enjoy(); //抽象方法是没有方法体的。在所有的普通方法上面都有“ {} ”,这部分就是方法体,
} //有方法体的方法一定可以被对象直接使用。而抽象方法是没有方法体的,必须用abstract 修饰
/**
* 这里的子类Cat从抽象类Animal继承下来,自然也继承了Animal类里面声明的抽象方法enjoy(),
* 但子类Cat觉得自己去实现这个enjoy()方法也不合适,因此它把它自己也声明成一个抽象的类,
* 那么,谁去实现这个抽象的enjoy方法,谁继承了子类,那谁就去实现这个抽象方法enjoy()。
* @author gacl
*
*/
abstract class Cat extends Animal {
/**
* Cat添加自己独有的属性
*/
public String eyeColor;
public Cat(String n, String c) {
super(n);//调用父类Animal的构造方法
this.eyeColor = c;
}
}
/**
* 子类BlueCat继承抽象类Cat,并且实现了从父类Cat继承下来的抽象方法enjoy
* @author gacl
*
*/
class BlueCat extends Cat {
public BlueCat(String n, String c) {
super(n, c);
}
/**
* 实现了抽象方法enjoy
*/
@Override //对方法重写的注解
public void enjoy() {
System.out.println("蓝猫叫...");
}
}
/**
* 子类Dog继承抽象类Animal,并且实现了抽象方法enjoy
* @author gacl
*
*/
class Dog extends Animal {
/**
* Dog类添加自己特有的属性
*/
public String furColor;
public Dog(String n, String c) {
super(n);//调用父类Animal的构造方法
this.furColor = c;
}
@Override //对方法重写的注解
public void enjoy() {
System.out.println("狗叫....");
}
}
public class TestAbstract {
/**
* @param args
*/
public static void main(String[] args) {
/**
* 把Cat类声明成一个抽象类以后,就不能再对Cat类进行实例化了,
* 因为抽象类是残缺不全的,缺胳膊少腿的,因此抽象类不能被实例化。
*/
//Cat c = new Cat("Catname","blue");
Dog d = new Dog("dogname","black");
d.enjoy();//调用自己实现了的enjoy方法
BlueCat c = new BlueCat("BlueCatname","blue");
c.enjoy();//调用自己实现了的enjoy方法
}
}
抽象方法是没有方法体的。在所有的普通方法上面都有“ {} ”,这部分就是方法体,
} //有方法体的方法一定可以被对象直接使用。而抽象方法是没有方法体的,必须用abstract 修饰
/**
* 这里的子类Cat从抽象类Animal继承下来,自然也继承了Animal类里面声明的抽象方法enjoy(),
* 但子类Cat觉得自己去实现这个enjoy()方法也不合适,因此它把它自己也声明成一个抽象的类,
* 那么,谁去实现这个抽象的enjoy方法,谁继承了子类,那谁就去实现这个抽象方法enjoy()。
* @author gacl
*
*/
abstract class Cat extends Animal {
/**
* Cat添加自己独有的属性
*/
public String eyeColor;
public Cat(String n, String c) {
super(n);//调用父类Animal的构造方法
this.eyeColor = c;
}
}
/**
* 子类BlueCat继承抽象类Cat,并且实现了从父类Cat继承下来的抽象方法enjoy
* @author gacl
*
*/
class BlueCat extends Cat {
public BlueCat(String n, String c) {
super(n, c);
}
/**
* 实现了抽象方法enjoy
*/
@Override //对方法重写的注解
public void enjoy() {
System.out.println("蓝猫叫...");
}
}
/**
* 子类Dog继承抽象类Animal,并且实现了抽象方法enjoy
* @author gacl
*
*/
class Dog extends Animal {
/**
* Dog类添加自己特有的属性
*/
public String furColor;
public Dog(String n, String c) {
super(n);//调用父类Animal的构造方法
this.furColor = c;
}
@Override //对方法重写的注解
public void enjoy() {
System.out.println("狗叫....");
}
}
public class TestAbstract {
/**
* @param args
*/
public static void main(String[] args) {
/**
* 把Cat类声明成一个抽象类以后,就不能再对Cat类进行实例化了,
* 因为抽象类是残缺不全的,缺胳膊少腿的,因此抽象类不能被实例化。
*/
//Cat c = new Cat("Catname","blue");
Dog d = new Dog("dogname","black");
d.enjoy();//调用自己实现了的enjoy方法
BlueCat c = new BlueCat("BlueCatname","blue");
c.enjoy();//调用自己实现了的enjoy方法
}
}
1.2 抽象类的使用
1.2.1 抽象类使用时的限制
先举个范例,如下:
package com.wz.abstractdemo;
abstract class A{//使用abstract关键字,定义一个抽象类
public void fun(){//普通方法
System.out.println("存在方法体的方法");
}
public abstract void print();//抽象方法,没有方法体,有abstract关键字做修饰
}
public class TestDemo {
public static void main(String[] args) {
A a = new A();
}
}
从上例可知,A是抽象的,无法直接进行实例化操作。为什么不能直接实例化呢?当一个类实例化之后,就意味着这个对象可以调用类中的属性和方法,但在抽象类里存在抽象方法,而抽象方法没有方法体,没有方法体就无法进行调用。既然无法进行方法调用的话,又怎么去产生实例化对象呢。
抽象类的使用原则如下:
(1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方 法),缺省情况下默认为public
(2)抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理,具体实现如下例
(3)抽象类必须有子类,使用extends继承,Java是单继承的,所以一个子类只能继承一个抽象类
(4)子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必 须将子类也定义为为abstract类)
package com.wz.abstractdemo;
abstract class A{//定义一个抽象类
public void fun(){//普通方法
System.out.println("存在方法体的方法");
}
public abstract void print();//抽象方法,没有方法体,有abstract关键字做修饰
}
//单继承
class B extends A{//B类是抽象类的子类,是一个普通类
@Override //对重写方法的注解
public void print() {//强制要求重写
System.out.println("Hello World !");
}
}
public class TestDemo {
public static void main(String[] args) {
A a = new B();//向上转型
a.fun();//被子类所覆写的过的方法
}
}
//向上转型
a.fun();//被子类所覆写的过的方法
}
}
通过以上例子就可以清楚的发现:
(1)抽象类继承子类里面有明确的方法覆写要求,而普通类可以有选择性的来决定是否需要覆写;
(2)抽象类实际上就比普通类多了一些抽象方法而已,其他组成部分和普通类完全一样;
(3)普通类对象可以直接实例化,而抽象类不能直接实例化,抽象类的对象必须经过向上转型之后才可以得到。
虽然一个类的子类可以去继承任意的一个普通类,可是从开发的实际要求来讲,普通类尽量不要去继承另外一个普通类,而是去继承抽象类。
1.2.2 抽象类相关的几个问题
(1)抽象类中有构造方法么?
由于抽象类里会存在一些属性,那么抽象类中一定存在构造方法。其存在目的是为了属性的初始化,并且子类对象实例化的时候,依然必须先执行父类构造,再执行子类构造的顺序。
(2)抽象类可以用final声明么?
不能,因为抽象类必须有子类,而final定义的类不能有子类;
(3)抽象类能否使用static关键字进行声明?
先看一个外部抽象类的范例,如下:
package com.wz.abstractdemo;
static abstract class A{ //定义一个抽象类,此抽象类为外部抽象类,在这个外部抽象类的前面加上static
public abstract void print();
}
class B extends A{
public void print(){
System.out.println("**********");
}
}
public class TestDemo {
public static void main(String[] args) {
A a = new B();//向上转型
a.print();
}
}
定义一个抽象类,此抽象类为外部抽象类,在这个外部抽象类的前面加上static
public abstract void print();
}
class B extends A{
public void print(){
System.out.println("**********");
}
}
public class TestDemo {
public static void main(String[] args) {
A a = new B();//向上转型
a.print();
}
}
再看一个内部抽象类的范例,如下:
package com.wz.abstractdemo;
abstract class A{//定义一个抽象类
static abstract class B{ //定义的内部类属于外部类,在内部抽象类前加上static
public abstract void print();
}
}
class C extends A.B{
public void print(){
System.out.println("**********");
}
}
public class TestDemo {
public static void main(String[] args) {
A.B ab = new C(); //向上转型
ab.print();
}
}
定义的内部类属于外部类,在内部抽象类前加上static
public abstract void print();
}
}
class C extends A.B{
public void print(){
System.out.println("**********");
}
}
public class TestDemo {
public static void main(String[] args) {
A.B ab = new C(); //向上转型
ab.print();
}
}
由此可见,外部抽象类不允许使用static声明,而内部的抽象类允许使用static声明。使用static声明的内部抽象类相当于一个外部抽象类,继承的时候使用“外部类.内部类”的形式表示类名称。
(4)可以直接调用抽象类中用static声明的方法么?
任何时候,如果要执行类中的static方法的时候,都可以在没有对象的情况下直接调用,对于抽象类也一样。
1.3 抽象类的应用----模板设计模式
模板设计模式,是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
例如,现在有三类事物,各个事物有着自己各自的行为
(1)机器人:充电,工作;
(2) 人 :吃饭,工作,睡觉;
(3) 猪 :进食,睡觉。
现要求实现一个程序,可以实现以上三种不同事物的行为。实现需求的代码如下:
abstract class Action{ //先定义一个抽象的行为类
public static final int EAT = 1 ;
public static final int SLEEP = 3 ;
public static final int WORK = 5 ;
public abstract void eat();
public abstract void sleep();
public abstract void work();
public void commond(int flags){
switch(flags){
case EAT:
this.eat();
break;
case SLEEP:
this.sleep();
break;
case WORK:
this.work();
break;
case EAT + SLEEP:
this.eat();
this.sleep();
break;
case SLEEP + WORK:
this.sleep();
this.work();
break;
default:
break;
}
}
}
class Robot extends Action{ //定义一个“机器人”的类,“机器人”类继承行为类Action
@Override
public void eat() {
System.out.println("机器人充电");
}
@Override
public void sleep() {
}
@Override
public void work() {
System.out.println("机器人工作");
}
}
class Human extends Action{ //定义一个“人”的类,“人”类继承行为类Action
@Override
public void eat() {
System.out.println("人吃饭");
}
@Override
public void sleep() {
System.out.println("人睡觉");
}
@Override
public void work() {
System.out.println("人工作");
}
}
class Pig extends Action{ //定义一个“猪”的类,“猪”类继承行为类Action
@Override
public void eat() {
System.out.println("猪进食");
}
@Override
public void sleep() {
System.out.println("猪睡觉");
}
@Override
public void work() {
}
}
public class AbstractDemo { //测试主类
public static void main(String[] args) {
fun(new Robot());
fun(new Human());
fun(new Pig());
}
public static void fun(Action act){
act.commond(Action.EAT);
act.commond(Action.SLEEP);
act.commond(Action.WORK);
}
}
2.1 接口概念
接口,严格的来讲属于一个特殊的类,而这个类里面只有抽象方法和全局常量,就连构造方法也没有。
Java是只支持单继承的,但现实之中存在多重继承这种现象。eg.如“金丝猴是一种动物”,金丝猴从“动物”这个类继承,同时“金丝猴是一种值钱的东西”,金丝猴从“值钱的东西”这个类继承,再同时“金丝猴是一种应该受到保护的东西”,金丝猴从“应该受到保护的东西”这个类继承。这样金丝猴可以同时从 “动物类”、“值钱的东西类”、“应该受到保护的东西” 这三个类继承,但由于Java只支持单继承,因此金丝猴只能从这三个类中的一个来继承,不能同时继承这三个类。因此为了封装现实生活中存在的多重继承现象,为了实现多继承,可以把其中的两个类封装成接口。使用接口可以帮助我们实现多重继承。
抽象类所具有的一些东西接口可以具有,假如一个抽象类里面所有的方法全都是抽象的,没有任何一个方法需要这个抽象类去实现,并且这个抽象类里面所有的变量都是静态(static)变量,且都是不能改变(final)的变量,即为静态常量(static final)。这时可以把这样的抽象类定义为一个接口(interface)。把符合这样条件的一个类定义成一个接口的格式是把声明类的关键字class用声明接口的关键字interface替换掉即可。
定义一个接口,范例:
interface A{ //定义一个接口
public static final String MSG = "hello"; //全局常量,用static final 定义
public abstract void print(); //抽象方法,没有方法体
}
2.2 接口的特征
接口(interface)是一种特殊的抽象类,在这种抽象类里面,所有的方法都是抽象方法,并且这个抽象类的属性(即成员变量)都是声明成“public static final 类型 属性名”这样的,默认也是声明成“public static final”即里面的成员变量都是公共的、静态的,不能改变的。因此在接口里面声明常量的时候,可以写成“public static final 类型 常量名=value(值)”这样的形式,也可以直接写成“类型 常量名=value(值)”如:“public static final int id=10”可以直接写成“int id=10”这样的形式,因为在接口里面默认的属性声明全部都是“public static final”的,因此“public static final”可以省略不写。在接口里面声明的抽象方法可以不写abstract关键字来标识,因为接口里面所有的方法都是抽象的,因此这个“abstract”关键字默认都是省略掉的,如在一个接口里面声明这样的三个方法:“public void start()”、“public void run()”、“public void stop()”这三个方法前面都没有使用abstract关键字来标识,但它们全是抽象方法,因为在接口里面的声明的方法都是抽象方法,因此在接口里面的抽象方法都会把abstract关键字省略掉,因为默认声明的方法都是抽象的,所以就没有必要再写“abstract”字了,这一点与在抽象类里面声明抽象方法时有所区别,在抽象类里面声明抽象方法是一定要使用“abstract”关键字的,而在接口里面声明抽象方法可以省略掉“abstract”。注意:在接口里面声明的抽象方法默认是“public(公共的)”的,也只能是“public(公共的)”之所以要这样声明是为了修正C++里面多重继承的时候容易出现问题的地方,C++的多继承容易出现问题,问题在于多继承的多个父类之间如果他们有相同的成员变量的时候,这个引用起来会相当地麻烦,并且运行的时候会产生各种各样的问题。Java为了修正这个问题,把接口里面所有的成员变量全都改成static final,成员变量是static类型,那么这个成员变量就是属于整个类里面的,而不是专属于某个对象。对于多重继承来说,在一个子类对象里面实际上包含有多个父类对象,而对于单继承来说,子类对象里面就只有一个父类对象。多继承子类对象就有多个父类对象,而这些父类对象之间可能又会存在有重复的成员变量,这就非常容易出现问题,因此在JAVA里面避免了这种问题的出现,采用了接口这种方式来实现多继承。作为接口来说,一个类可以从接口继承(或者叫实现接口),这也是多继承,接口里面的成员变量不专属于某个对象,都是静态的成员变量,是属于整个类的,因此一个类去实现多个接口也是无所谓的,不会存在对象之间互相冲突的问题。实现多个接口,也就实现了多重继承,而且又避免了多重继承容易出现问题的地方,这就是用接口实现多重继承的好处。
2.2.1 通过案例,进一步了解接口的特征
先给出一个例子,如下:
1 package javastudy.summary;
2
3 /**
4 * 这里定义了第一个接口:Painter,在Painter接口里面定义了paint()和eat()这两个抽象方法。
5 *
6 * @author gacl
7 *
8 */
9 interface Painter {
10 public void eat();
11
12 public void paint();
13 }
14
15 /**
16 * 这里定义了第二个接口:Singer,在Singer接口里面定义了sing()和sleep()这两个抽象方法。
17 *
18 * @author gacl
19 *
20 */
21 interface Singer {
22 public void sing();
23
24 public void sleep();
25 }
26
27 /**
28 * 类Student实现了Singer这个接口
29 *
30 * @author gacl
31 *
32 */
33 class Student implements Singer {
34
35 private String name;
36
37 public Student(String name) {
38 this.name = name;
39 }
40
41 public String getName() {
42 return name;
43 }
44
45 public void setName(String name) {
46 this.name = name;
47 }
48
49 /**
50 * 实现接口中定义的sing方法
51 */
52 @Override
53 public void sing() {
54 System.out.println("student is singing");
55 }
56
57 /**
58 * 实现接口中定义的sleep方法
59 */
60 @Override
61 public void sleep() {
62 System.out.println("student is sleeping");
63 }
64
65 public void study() {
66 System.out.println("Studying...");
67 }
68
69 }
70
71 /**
72 * Teacher这个类实现了两个接口:Singer和Painter。 这里Teacher这个类通过实现两个不相关的接口而实现了多重继承。
73 *
74 * @author gacl
75 *
76 */
77 class Teacher implements Singer, Painter {
78
79 private String name;
80
81 public Teacher(String name) {
82 this.name = name;
83 }
84
85 /**
86 * 在Teacher类里面重写了这两个接口里面的抽象方法,
87 * 通过重写抽象方法实现了这两个接口里面的抽象方法。
88 */
89 @Override
90 public void eat() {
91 System.out.println("teacher is eating");
92 }
93
94 public String getName() {
95 return name;
96 }
97
98 @Override
99 public void paint() {
100 System.out.println("teacher is painting");
101 }
102
103 public void setName(String name) {
104 this.name = name;
105 }
106
107 @Override
108 public void sing() {
109 System.out.println("teacher is singing");
110 }
111
112 @Override
113 public void sleep() {
114 System.out.println("teacher is sleeping");
115 }
116
117 public void teach() {
118 System.out.println("teaching...");
119 }
120 }
121
122 public class TestInterfaces {
123
124 public static void main(String[] args) {
125 /**
126 * 这里定义了一个接口类型的变量s1
127 */
128 Singer s1 = new Student("le"); //相当于一个父类对象s1 的引用指向子类对象Student
129 s1.sing();
130 s1.sleep();
131 Singer s2 = new Teacher("steven");
132 s2.sing();
133 s2.sleep();
134 Painter p1 = (Painter)s2; //强制类型转换
135 p1.paint();
136 p1.eat();
137 }
138 }
相当于一个父类对象s1 的引用指向子类对象Student
129 s1.sing();
130 s1.sleep();
131 Singer s2 = new Teacher("steven");
132 s2.sing();
133 s2.sleep();
134 Painter p1 = (Painter)s2; //强制类型转换
135 p1.paint();
136 p1.eat();
137 }
138 }
这里验证了两个规则,“一个类可以实现多个无关的接口”,Teacher类既实现了Singer接口,同时也实现了Painter接口,而Singer接口和Painter接口是无关系的两个接口。“多个无关的类可以实现同一接口”,Student类和Teacher类都实现了Singer接口,而Student类和Teacher类并不是关系很密切的两个类,可以说是无关的两个类。
2.2.2 结合内存图,分析接口和实现了之间的多态性
首先分析main方法的第一条语句 Singer s1 = new Student(“le”); 这里首先定义了一个接口类型的变量s1,接口Singer是Student类实现的,即相当于Student类从Singer接口继承,Singer接口的本质是一个特殊的抽象类,所以这里Singer接口就是Student类的父类,因此s1就是父类对象的一个引用,即这里这句话执行完后就是一个父类对象s1的引用指向子类对象Student。所以内存里面的布局应该是这样:栈空间里面有一个父类对象的引用s1,堆空间里面new出了一个Student对象,创造这个Student对象的时候调用了Student类的构造方法Student(String name),其定义如下:
Student(String name){
this.name = name;
}
通过调用构造方法使得这个Student对象有了一个自己的名字“le”,因此堆内存里面的Student对象的name属性值为“le”。
这个Student对象能够访问位于代码区里面的sleep()方法和sing()方法,因为Student类从父类Sing继承而来,因此自然可以访问到这两个方法,除此之外,还能访问Student类里面自定义的Study()方法。因此代码区里面存放着这三个方法等待着Student类的对象去访问,也就是去调用。一个正常的Student可以直接调用这三个方法。那么怎么找得到位于代码区的这三个方法呢?Student对象里面存在着能找得到这个三个方法的函数指针,引用对象通过这个指针的索引指向就能找到代码区里面的这三个方法。
s1是父类对象的索引,但此时s1指向的却是子类对象,即一个父类对象的索引指向了子类对象。这里很不幸的是,由于这个s1是一个父类对象的引用,站在s1的角度上,它就是只把你这个子类对象Student当成是一个Singer,s1只能看到Student对象里面的sing()和sleep这两个方法的方法指针,因此使用这个s1引用对象只能去访问从父类继承下来的sleep()和sing()这两个方法,但由于这两个方法在子类Student里面被重写了,那么现在就是这种情况了,子类Student从父类Singer继承,在子类里面重写了从父类继承下来的sing()和sleep()这两个方法,父类对象的引用指向了子类对象,这三种情况加在一起就使得多态可以存在了,这样调用位于代码区里面的方法时,会根据new出来的实际对象去调用代码区里面的方法,因此这里在s1眼里虽然是把这个new出的Student当成一个Singer,但这个对象实际上就是一个Student,因此使用父类对象的引用s1调用代码区里面的sleep()和sing()方法时,调用的是在子类里面重写过后的sing()和sleep()方法。
接着分析第二句话 Singer s2 = new Teacher(“steven”); Teacher这个类实现了Singer接口和Painter接口,即相当于从两个父类继承,一个父类是Singer,另一个父类是Painter。这里的s2也是父类对象Singer的引用,指向的却是子类对象Teacher,因此也是一个父类对象的引用指向子类对象。创造这个Teacher对象的时候,调用Teacher(String name)构造方法,其定义如下:
Teacher(String name){
this.name=name;
}
调用构造方法后,Teacher有了自己的名字steven,所以Teacher的name属性值为steven,由于这个Teacher实现了Painter接口和Singer接口,因此也继承这两个接口里面的方法,因此一个正常的Teacher可以访问的方法有:paint()、eat()和sing()、sleep。前面两个方法是从Painter类继承过来的,后面两个方法是从Singer类继承过来的。除了这四个方法外,还有自己定义的Teach()方法。可是很不幸的是,由于s2是一个Singer类对象的引用,因此站在s2的角度来看,它只把Teacher当成是一个普通的Singer,因此它看到的只是Teacher对象里面的sing()和sleep()这两方法,然后要调用时就通过Teacher对象里面的函数指针找到位于代码区的sleep()和sing()这两个方法。别的方法s2是看不到的,因此也调用不了。
再看第三句,Painter p1=(Painter)s2; 这里把s2强制转换成Painter,s2对象实际是指向Teacher的,把s2强制转换成Painter以后,就可以把Teacher当成Painter来用,所以p1会把Teacher当成Painter来看待,因此p1只能看到Teacher里面的painter()方法和eat()方法,因此能够访问到的也只有这两个方法。
所以接口对于我们实际当中的对象来说,每一个接口暴露了我们这个实际对象的一部分方法。你使用什么样的接口,就只能访问这个接口里面定义的方法,别的接口定义的方法就没办法访问得到。接口可以帮助我们实现多重继承这种逻辑,接口和它的实现类之间存在多态性。
2.2.3 金丝猴案例的代码实现
1 package javastudy.summary;
2
3 /**
4 * 把“值钱的东西”这个类定义成一个接口Valuable。在接口里面定义了一个抽象方法getMoney()
5 * @author gacl
6 *
7 */
8 interface Valuable {
9 public double getMoney();
10 }
11
12 /**
13 * 把“应该受到保护的东西”这个类定义成一个接口Protectable。
14 * 在接口里面定义了一个抽象方法beProtected();
15 * @author gacl
16 *
17 */
18 interface Protectable {
19 public void beProteced();
20 }
21
22 /**
23 * 这里是接口与接口之间的继承,接口A继承了接口Protectable,
24 * 因此自然而然地继承了接口Protectable里面的抽象方法beProtected()。
25 * 因此某一类去实现接口A时,除了要实现接口A里面定义的抽象方法m()以外,
26 * 还要实现接口A从它的父接口继承下来的抽象方法beProtected()。
27 * 只有把这两个抽象方法都实现了才算是实现了接口A。
28 * @author gacl
29 *
30 */
31 interface A extends Protectable {
32 void m();
33 }
34
35 /**
36 * 这里定义了一个抽象类Animal。
37 * @author gacl
38 *
39 */
40 abstract class Animal {
41 private String name;
42 /**
43 * 在Animal类里面声明了一个抽象方法enjoy()
44 */
45 abstract void enjoy();
46 }
47
48 /**
49 * 这里是为了实现了我们原来的语义:
50 * “金丝猴是一种动物”同时“他也是一种值钱的东西”同时“他也是应该受到保护的东西”。而定义的一个类GoldenMonKey。
51 * 为了实现上面的语义,这里把“值钱的东西”这个类定义成了一个接口Valuable,
52 * 把“应该受到保护的东西”这个类也定义成了一个接口Protectable。这样就可以实现多继承了。
53 * GoldenMonKey类首先从Animal类继承,然后GoldenMonKey类再去实现Valuable接口和Protectable接口,
54 * 这样就可以实现GoldenMonKey类同时从Animal类,Valuable类,Protectable类继承了,即实现了多重继承,
55 * 实现了原来的语义。
56 * @author gacl
57 *
58 */
59 class GoldenMonKey extends Animal implements Valuable,Protectable {
60
61 /**
62 * 在GoldenMoKey类里面重写了接口Protectable里面的beProtected()这个抽象方法,
63 * 实现了接口Protectable。
64 */
65 @Override
66 public void beProteced() {
67 System.out.println("live in the Room");
68 }
69
70 /**
71 * 在GoldenMoKey类里面重写了接口Valuable里面的getMoney()这个抽象方法,实现了接口Valuable。
72 */
73 @Override
74 public double getMoney() {
75 return 10000;
76 }
77
78 /**
79 * 这里重写了从抽象类Animal继承下来的抽象方法enjoy()。
80 * 实现了这抽象方法,不过这里是空实现,空实现也是一种实现。
81 */
82 @Override
83 void enjoy() {
84
85 }
86
87 public static void test() {
88 /**
89 * 实际当中在内存里面我们new的是金丝猴,在金丝猴里面有很多的方法,
90 * 但是接口的引用对象v能看到的就只有在接口Valuable里面声明的getMoney()方法,
91 * 因此可以使用v.getMoney()来调用方法。而别的方法v都看不到,自然也调用不到了。
92 */
93 Valuable v = new GoldenMonKey();
94 System.out.println(v.getMoney());
95 /**
96 * 把v强制转换成p,相当于换了一个窗口,通过这个窗口只能看得到接口Protectable里面的beProtected()方法
97 */
98 Protectable p = (Protectable)v;
99 p.beProteced();
100 }
101 }
102
103 /**
104 * 这里让Hen类去实现接口A,接口A又是从接口Protectable继承而来,接口A自己又定义了一个抽象方法m(),
105 * 所以此时相当于接口A里面有两个抽象方法:m()和beProtected()。
106 * 因此Hen类要去实现接口A,就要重写A里面的两个抽象方法,实现了这两个抽象方法后才算是实现了接口A。
107 * @author gacl
108 *
109 */
110 class Hen implements A {
111
112 @Override
113 public void beProteced() {
114
115 }
116
117 @Override
118 public void m() {
119
120 }
121
122 }
123
124 /**
125 * java中定义接口
126 */
127 public class JavaInterfacesTest {
128
129 public static void main(String[] args) {
130 GoldenMonKey.test();
131 }
132 }
由于接口里面存在抽象方法,所以接口对象不能直接使用关键字new进行实例化。接口的使用原则如下:
(1)接口必须要有子类,但此时一个子类可以使用implements关键字实现多个接口;
(2)接口的子类(如果不是抽象类),那么必须要覆写接口中的全部抽象方法;
(3)接口的对象可以利用子类对象的向上转型进行实例化。
归纳:接口和接口之间可以相互继承,类和类之间可以相互继承,类和接口之间,只能是类来实现接口。在Java中,一个抽象类只能继承一个抽象类,但一个接口却可以使用extends关键字同时继承多个接口,但接口不能继承抽象类
2.3 接口的应用----工厂设计模式
先引入一个例子:
interface Fruit{ //定义了一个接口
public void eat(); //接口中的方法,全部都是抽象方法
}
class Apple implements Fruit{
@Override //对重写方法的注解
public void eat() {
System.out.println("吃苹果。。。"); //重写接口中的抽象方法
}
}
public class Client {
public static void main(String[] args) {
Fruit f = new Apple(); //利用子类对象的向上转型,实现接口对象的实例化
f.eat();
}
}
利用子类对象的向上转型,实现接口对象的实例化
f.eat();
}
}
以上程序非常简单,就是通过接口的子类为接口对象实例化,但这样操作会存在什么样的问题呢?在软件开发中,我们强调以下两点:
(1)主方法或主类是一个客户端,客户端的操作应该越简单越好;
(2)客户端之外的代码修改,不影响用户的使用。也就是说,用户可以不用去关心代码是否由变更。
确实,以上范例没有任何语法错误,但关键的问题是客户端中出现的new关键字上。因为,一个接口会有多个子类,对于上面的Furit接口,也可能出现多个子类对象。
来看范例,我们多加上一个接口子类:
class Orange implements Fruit{
@Override
public void eat() {
System.out.println("吃橘子。。。");
}
}
客户端是若要得到这个新的子类对象,需要修改代码为:
public class Client {
public static void main(String[] args) {
//Fruit f = new Apple();
Fruit f = new Orange();
f.eat();
}
}
从上面我们发现,如果直接在客户端上产生一个实例化对象,那么我们每次要更换对象时,都需要修改客户端代码,这样的做法明显是不好的。而在整个代码中,我们最关心的是如何取得一个Fruit接口对象,然后进行方法的调用,至于这个接口对象时被谁实例化的,不是客户端关心的。这个问题就是代码耦合度太高!耦合度太高的产生的直接问题是代码不方便维护。
在本程序之中,最大的问题在于耦合上,发现在主方法中,一个接口和一个子类紧密耦合在一起,这种方式比较直接,可以简单的理解为由A —>B,但是这种紧密的方式不方便于维护,所以我们可以这样改:A—> C—>B,中间经历了一个过渡,这样一来,B改变,然后C去改变,但是A不需要改变。这可以参考Java中JVM的设计思想:程序—> JVM—>适应不同的操作系统。
于是,本程序我们这么修改,加上一个工厂类:
class Factory{
public static Fruit getInstance(String className){
if("apple".equals(className)){
return new Apple();
}else if("orange".equals(className)){
return new Orange();
}else{
return null;
}
}
}
然后修改客户端:
public class Client {
public static void main(String[] args) {
Fruit f = Factory.getInstance("apple");
f.eat();
}
}
这样的话,客户端不会看见具体的子类,客户端不再和一个具体的子类耦合在一起了,就算以后增加了新的子类,那么只需要修改Factory类即可实现,客户端的调用不会改变。
工厂模式的关系图如下:
从工厂模式关系图看出,客户端不和具体的子类耦合在一起,若要增加新的子类,只需要修改Factory类即可实现。
通过上面的分析可以得出结论:在开发之中,抽象类和接口实际上都是可以使用的,并且使用哪一个并没有明确的限制,可是抽象类有一个最大的缺点 : 一个子类只能够继承一个抽象类,存在单继承的局限。所以当遇到抽象类和接口都可以使用的情况下,优先考虑接口,避免单继承局限。
一些参考原则(根据自身情况参考):
(1)在进行某些公共操作的时候一定要定义出接口
(2)有了接口就需要利用子类完善方法
(3)如果是我们自己写的接口,尽量不要使用关键字new去直接实例化接口子类,要使用工厂类完成
------------------------------------------------ 我是低调的分隔线 ----------------------------------------------------
面向对象,面向卿;不负代码,不负心... ...