第5章 类的扩展
第5章主要介绍接口、抽象类、内部类和枚举。
接口的本质
很多时候,我们关心的并不是对象的类型,而是对象的能力,我们要求某个对象提供某种能力,而不管其类型。接口就是用来描述对象可以提供的能力。
接口声明了一组能力,但它自己并没有实现这个能力,它只是一个约定。接口涉及交互两方对象,一方需要实现这个接口,另一方使用这个接口,但双方对象并不直接相互依赖,他们只是通过接口间接交互。
接口的定义使用interface
关键字。如下所示
public interface MyComparable {
int compareTo(Object other);
}
在Java 8之前,接口内不可以实现方法,接口方法不需要加修饰符,默认为public abstract。
类可以通过实现接口来表示类的对象具有接口所表示的能力。实现接口使用implements
关键字。
public class Point implements MyComparable{
...
@Override
public int compareTo(Object other) {
...
}
}
在类中需要重写接口定义的方法来实现接口。在使用泛型的时候需要使用instanceof
来检查对象的类型,以免造成不必要的错误。
一个类可以实现多个接口来表示类的对象更具备多种能力。
public class Test implements interface1, interface2{
}
接口不能new
来创建接口对象,使用接口的唯一方法是通过类来实现接口,然后创建这个类的对象,调用对象的接口方法。而实现接口的对象可以赋值给接口类型的变量,类似于父类和子类的关系。
这就是接口的威力所在,我们可以脱离对象的类型来统一考虑对象的能力,即面向接口编程的思想。
接口更重要的是降低了耦合,提高灵活性。使用接口的代码依赖的是接口本身,而非实现接口的具体类型,程序可以根据情况替换接口的实现,而不影响解耦的使用者。
接口内可以定义变量,但变量类型是public static final
public interface Interface1{
public static final int a = 0;
}
接口可以继承,不同于类的继承,接口的继承支持多继承。
public interface IBase1{
void method1();
}
public interface IBase2{
void method2();
}
public interface IChild extends IBase1, IBase2{
}
类的继承和实现接口可以并存。
public class Child extends Base implements IChild{
}
接口也可以使用instanceof
关键字,用来判断一个对象是否实现了某个接口。
Point p = new Point();
if( p instanceof MyComparable) {
}
可以使用接口和组合来有效代替继承。
抽象类
抽象类就是抽象的类。抽象是相对于具体而言的,一般而言,具体类有直接对应的对象,而抽象类没有,它表达的是抽象概念,例如图形类Shape。抽象概念的方法一般定义为抽象方法,例如抽象类的draw
方法就是抽象方法。因为抽象类不知道方法如何实现,只有子类(具体类如圆类Circle)才知道如何实现。抽象类和抽象方法都使用abstract
关键字来声明。
public abstract class Shape{
public abstract void draw();
}
定义了抽象方法的类必须被声明为抽象类,但抽象类可以没有抽象方法。抽象类不可以使用new
来创建对象,要创建对象必须使用其子类。一个类在继承抽象类后,必须实现抽象类中定义的所有抽象方法,除非它自己也声明为抽象类。
public class Circle extends Shape{
@Override
public void draw(){
}
}
抽象类虽然不能创建对象,但可以声明抽象类的变量,引用抽象类具体子类的对象。
Shape shape = new Shape();
shape.draw();
抽象类的引入和以引导使用者正确使用它们,减少无用。使用抽象方法而非空方法体,子类就必须要实现该方法,而不可能忽略,若忽略则会发生编译错误。
抽象类和接口在根本上是不同的,接口中不能定义实例变量而抽象类可以。一个类可以实现多个接口但只能继承一个类。
抽象类和接口是配合而非替代关系,它们常常一起使用、接口声明能力,抽象类提供默认实现,实现全部或部分方法。一个接口经常有一个对应的抽象类。例如Collection
接口对应的AbstractCollection
抽象类。
对于需要具备某种能力的具体类来说,有两个选择,一是实现接口,自己实现全部方法,另一个则是继承抽象类,根据需要重写方法。继承的好处是复用代码,只需要重写需要的部分即可,需要编写的代码比较少容易实现。不过如果这个具体了已经有父类了,那么只能选择实现接口。
内部类的本质
一般来说,每个类都对应一个独立的Java源文件。但一个类还可以放在另一个类的内部,称之为内部类,相对而言,包含它的类称为外部类。
内部类只是Java编译器的概念,对于Java虚拟机而言,它并不知道内部类,每个内部类最后都会被编译为一个独立的类,生成一个独立的字节码文件。
内部类可以方便地访问外部类的私有变量。
在Java中,根据定义的位置和方式不同,内部类的类型主要有4种。
- 静态内部类
- 成员内部类
- 方法内部类
- 匿名内部类
方法内部类是在一个方法内定义和使用的,匿名内部类作用范围更小,这两种类都不能在外部使用。成员内部类和静态内部类可以被外部使用,但它们都可以被声明为private
静态内部类和静态变量和静态方法的定义方式一样,都带有static
关键字。
public class Outer{
public static class StaticInner{
public void innerMethod() {
...
}
}
}
静态内部类可以直接访问外部类的静态变量和方法,但不能访问实例变量。
public
的静态内部类可以被外部使用,只不过需要通过外部类.静态内部类
的方式来使用。
Outer.StaticInner si = new Outer.StaticInner();
si.innerMethod();
成员内部类和静态内部类的定义相似,但没有static
修饰符。
public class Outer{
private int a = 100;
public class Inner{
public void innerMethod() {
System.out.prinln("outer a = " + a);
Outer.this.action();
}
}
private void action() {
System.out.println("action");
}
public void test() {
Inner inner = new Inner();
inner.innnerMethod();
}
}
成员内部类还可以直接访问外部类的实例变量和方法,也可以使用外部类.this.xxx
的方式来引用外部类的实例变量和方法。但后者一般应用在成员内部类的方法和变量与外部类重名的情况
枚举的本质
枚举enum
是一种特殊的数据类型,它的取值是有限的,是可以枚举出来的,因此称为枚举类型。
枚举类的定义示例如下。
public enum Size {
SMALL, MEDIUM, LARGE
}
枚举使用关键字enum
来定义,枚举类型的每个值以逗号,
分割。枚举类型可以定义为一个单独的文件,也可以定义在其他类内部。
枚举类型变量的定义如下。
Size size = Size.MEDIUM;
枚举变量的toString()
和name()
方法返回枚举变量的字面值,size.toString()
返回的是MEDIUM。
枚举变量可以使用==
和equals()
进行比较。因为枚举值是有顺序的,可以比较大小。枚举值的顺序是在定义枚举类型时确定的,从0开始。枚举值通过方法ordinal()
返回。
枚举类型都实现了Java API的Comparable
接口,可以通过方法compareTo
来和其他枚举值进行比较,实际上是比较ordinal
的大小。
枚举类型可以用在switch
的判断条件中,但是在case
中的标签不可以加上枚举类型的前缀。
switch(size) {
case SMALL:
...
break;
case MEDIUM:
...
break;
case LARGE:
...
break;
}
枚举类型都有一个静态的valueOf(String)
方法,可以返回字符串对应的枚举值。
System.out.println(Size.valueOf("SMALL"));
枚举类型也都有一个静态的values
方法,返回一个包括所有枚举类型变量的数组,顺序与声明时的顺序相同。
for(Size size : Size.values()) {
System.out.println(size);
}
实际上在类中定义静态整型变量也可以实现枚举的功能,但枚举类型有如下优点。
- 定义枚举的语法更为简洁。
- 枚举类型更为安全。一个枚举类型的变量,它们的值要么为null,要么为枚举值之一,不可能为其他值。如果使用整型变量,它的值就没有办法被限制。
- 枚举类型有许多易于使用的自带方法。
枚举类型实际上会被Java编译器编译成一个对应的final
类,这个类继承了Java API中的java.lang.Enum类。
一般枚举类型变量会被转换成对应的类变量,在switch语句中,枚举值会被转换成其对应的ordinal值。枚举类型实际上也是类,但由于编译器自动做了许多工作,使得枚举类型的使用更为简洁、安全和方便。
枚举类型也可以有实例变量和方法。枚举值的定义需要放在类的定义的内部的开头处,以;
结尾。
public enum Size {
SMALL("S", "小号"),
MEDIUM("M","中号"),
LARGE("L","大号");
private String attribute;
private String title;
private Size(String attribute, String title) {
this.attribute = attribute;
this.title = title;
}
public String getAttribute() {
return this.attribute;
}
public String getTitle() {
return this.title;
}
}