您的位置:首页 > 其它

抽象类、接口和内部类

2014-11-05 16:28 218 查看



作者:坛子


1.1. 使用抽象类


1.1.1. 抽象方法和抽象类

由abstract修饰的方法为抽象方法,抽象方法即只有方法的定义,没有方法体实现,用一个分号结尾。即方法五要素中,抽象方法缺少了一要素(即:方法体)。也可以将抽象方法理解为不完整的方法。

若将抽象方法包含在类中,则该类也应该为抽象的,可以理解为,该类也不完整。抽象类由abstract关键字声明。

抽象类是不能实例化对象的,而一个类不能实例化是没有意义的,所以,需要定义类来继承抽象类,而如果一个类继承了抽象类,则其必须重写其抽象方法(变不完整为完整),除非该类也声明为抽象类。看下面的代码,定义了抽象方法和抽象类:

<span style="background-color: rgb(255, 255, 255);">abstract class Shape {
private double c;
public Shape(double c) {
this.c = c;
}
public abstract double area();
} </span>


[/code]

通过上面的代码可以看到,area()方法没有方法体(连大括号也不存在),由abstract修饰,此为抽象方法。而Shape方法也由abstract修饰,即为抽象类。


1.1.2. 抽象类不可以实例化

抽象类不可以实例化,若Shape是抽象类的话,下面的代码是错误的:

<span style="background-color: rgb(255, 255, 255);">    Shape s1 = new Shape();</span>


[/code]

即使一个类中没有抽象方法,也可以将其定义为抽象类,同样,该类不可以实例化。

需要注意一点:abstract和final关键字不可以同时用于修饰一个类,因为final关键字使得类不可继承,而abstract修饰的类如果不可以继承将没有任何意义。两者放在一起,会起冲突。


1.1.3. 继承抽象类

一个类继承抽象类后,必须实现其抽象方法,不同的子类可以有不同的实现。看下面的代码,Square类与Circle类都继承自Shape类,并分别实现了重写的area()方法,只是其具体实现的代码不同:
class Square extends Shape {
private double c;
public Square(double c) {
super(c);
}
public double area() {
return 0.0625*c*c;
}
}
class Circle extends Shape{
private double c;
public Circle(double c) {
super(c);
}
public double area() {
return 0.0796*c*c;
}
}



1.1.4. 抽象类的意义

定义抽象类的意义在于:

为其子类提供一个公共的类型(父类引用指向子类对象);
封装子类中的重复内容(成员变量和方法);
定义有抽象方法,子类虽然有不同的实现,但该方法的定义是一致的。(子类需要实现此抽象方法)。

看如下的代码:
Shape[] sArr = {new Circle(100),  new Square(100),
new Circle(200), new Square(150) };
for(int i=0; i<sArr.length; i++) {
Shape shape = sArr[i];
System.out.println(shape.area());
}


通过上面的代码可以看出,以Shape类型的引用访问其子类,所有子类都会有area方法,只是其具体的实现各不一样。


1.2. 使用接口


1.2.1. 定义一个接口

接口可以看成是特殊的抽象类。即只包含抽象方法和常量的抽象类。可以通过interface关键字来定义接口。看如下代码:
interface Runner {
public static int DEFAULT_SPEED = 100
public void run();
}


注意,run()方法,此处可以省略public abstract。因其默认就是public abstract的。


1.2.2. 实现接口

与继承不同,一个类可以实现多个接口,实现的接口直接用逗号分隔。当然,该类需要实现这些接口中定义的所有方法;

一个类可以通过implements关键字”实现”接口。一个类实现了某个接口后必须实现该接口中定义的所有方法。看下面的代码,类实现了接口并实现了方法:
class AmericanCurl implements Runner , … {
public void run() {
System.out.println("run...");
}
}


另外需要说明的一点,接口可以作为一种类型声明变量,一个接口类型的变量可以引用实现了该接口的类的对象;通过该变量可以调用该接口中定义的方法(具体的实现类提供了方法的实现)。代码如下所示:
Runner runner = new AmericanCurl();


此句代码为,一个接口类型变量,引用了子类的对象。调用时,调用的是子类对象的具体的实现。


1.2.3. 接口的继承

接口间可以存在继承关系,一个接口可以通过extends关键字继承另外一个接口。子接口继承了父接口中定义的所有方法。代码如下所示:
interface Runner {
public void run();
}
interface Hunter  extends Runner {
public void hunt();
}
class AmericanCurl implements Hunter {
public void run() {… … …}
public void hunt() {… … …}
}


说明:AmericanCurl实现了Hunter,必须实现Hunter接口中的hunt方法以及其父接口Runner中的run方法。


1.2.4. 接口和抽象类的区别



图 -1

从图1中可以看出,达内职员类为抽象类,企业技术顾问、技术图书作者为两个接口。达内讲师类继承了抽象类达内职员,并实现了企业技术顾问和技术图书作者两个接口。而达内项目经理只继承了抽象类达内职员。通过分析,可以总结出如下几点抽象类和接口的区别:

一个类只能继承一个抽象类,但可以实现多个接口。
抽象类中可以包含抽象方法和非抽象方法,而接口中的所有方法均为抽象的。
子类继承抽象类必须实现抽象类中所有抽象方法,否则子类也必须是抽象类。而子类实现接口则必须实现接口中的所有抽象方法。


2. 抽象类、接口和内部类(下)


1.1. 多态


1.1.1. 多态的意义

前面所讲解的现象就是多态,多态即多种形态,主要有两个方面的表现。

首先,一个类型的引用在指向不同的对象时会有不同的实现,看如下的代码:
达内职员 emp1 = new 达内讲师();
达内职员 emp2 = new 达内项目经理();
emp1.完成工作();
emp2.完成工作();


同样是达内职员类型,当指向不同的对象时,可以有不同的表现。

其次,同样一个对象,造型成不同的类型时,会有不同的功能,看如下代码所示:
达内讲师 teacher = new 达内讲师();
企业技术顾问 consultant = teacher;
技术图书作者 author = teacher;
consultant.培训员工();
author.编辑稿件();


通过上面的代码,可以看出,同样的达内讲师对象,当将其造型为企业技术顾问及技术图书作者时,可以实现不同的功能。


1.1.2. 向上造型

在前面的课程中,介绍了一个现象,父类的引用指向子类的对象。这个现象就是下面要给大家介绍的现象,叫做向上造型。

一个类的对象可以向上造型的类型有:父类的类型及其实现的接口类型。当发生向上造型时,Java编译器会根据类型检查调用方法是否匹配。看如下的图 -1:



图 -1
实际应用中,可以使用如下的代码进行操作:

达内讲师 teacher = new 达内讲师();
达内职员 emp = teacher;
企业技术顾问 consultant = teacher;
技术图书作者 author = teacher;

emp.上班打卡();
emp.完成工作();
consultant.培训员工();
author.编辑稿件();



1.1.3. 强制转型

在实际应用中,还可以通过强制转换将父类型变量转换为子类型变量,前提是该变量指向的对象确实是该子类类型。也可通过强制转换将变量转换为某种接口类型,前提是该变量指向的对象确实实现了该接口。如果在强制转换过程中出现违背上述两个前提,将会抛出ClassCastException。看如下代码所示:
达内职员 emp1 = new 达内讲师();
达内讲师 teacher = (达内讲师) emp1;
技术图书作者 author = (技术图书作者) emp1;
达内项目经理 pm = (达内项目经理) emp1;


分析如下代码,声明达内职员类型emp1指向子类达内讲师对象,而后将emp1强制转换为达内讲师类,完全没有问题,而后将emp1再转换为技术图书作者类型,因为emp1实现了技术图书作者接口,所以转换也没有问题。但是最后一句,将emp1强制转换为达内项目经理,则会出现错误,抛出ClassCastException异常,意为,类型转换异常。因为emp1与达内项目经理没有父子关系及实现关系。


1.1.4. instanceof关键字

在强制转型中,为了避免出现ClassCastException,可以通过instanceof关键字判断某个引用指向的对象是否为指定类型。看如下代码所示:
达内职员 emp1 = new 达内讲师();
System.out.println(emp1 instanceof 达内讲师); // true
System.out.println(emp1 instanceof 技术图书作者); // true
System.out.println(emp1 instanceof 达内项目经理); // false
达内项目经理 pm = null;
if (emp1 instanceof 达内项目经理) {
pm = (达内项目经理) emp1;
}


分析如上代码,可以看出,因为emp1指向的为达内讲师,所以判断emp1是否是达内讲师类,返回结果为true,而达内讲师类又实现了技术图书作者接口,所以判断emp1是否为技术图书作者,返回结果也为御前。当想将emp1转换为达内项目经理时,先通过instanceof关键字判断,而后再转型。这样则可以避免发生ClassCastException,因为只要instanceof为true,则强制转换一定成功。


1.2. 内部类


1.2.1. 定义成员内部类

一个类可以定义在另外一个类的内部,定义在类内部的类称之为Inner,其所在的类称之为Outer;Inter定义在Outer的内部,通常只服务于Outer,对外不具备可见性,Inter可以直接调用Outer的成员及方法(包括私有的)。看如下代码:
class Outer{
private int time;
class Inner{
public void timeInc(){
time++;
}
}
}

[/code]

从上面的代码可以看出,在Outer类之中声明了Inner类,在Inner类中直接操作了Outer类的私有成员变量time。


1.2.2. 创建内部类对象

一般情况下,Inner对象会在Outer对象中创建(构造方法或其他方法);Inner对象中会有一个隐式的引用指向创建它的Outer类对象。看如下代码所示:
class Outer {
private int time;
private Inner inner;

Outer(int time) {
this.time = time;
inner = new Inner();
inner.timeInc();
}
public void printTime() {
System.out.println(time);
}
class Inner {
public void timeInc() {
time++;
}
}
}

[/code]

在main方法中,声明如下代码:
Outer outer = new Outer(100);
outer.printTime();

[/code]

上面代码的输出结果为:101。在Outer构造方法中创建的Inner对象会有一个隐式的引用指向创建它的Outer对象,调用Inner对象的timeInc方法,即会对Outer的time属性进行操作。


1.2.3. 定义匿名内部类

如果在一段程序中需要创建一个类的对象(通常这个类需要实现某个接口或者继承某个类),而且对象创建后,这个类的价值也就不存在了,这个类可以不必命名,称之为匿名内部类。看下面的代码:
SuperType obj = new SuperType (…) {
… … …
};

[/code]

其中,第一个SuperType为用匿名类所实现的接口或所继承的父类类型声明的引用;第二个SuperType为匿名类所要实现的接口或继承的父类;小括号()中为构造方法参数;大括号中为匿名中定义的成员变量或方法。

请参见下面的代码:
public interface Action {    	public void execute();
}
public class Main {
public static void main(String[] args) {
Action action = new Action(){
public void execute() {
System.out.println("Hello, World");
}
};
action.execute();
}
}

[/code]

如上的代码中,创建了实现了Action接口的内部匿名类对象,并调用其execute方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐