《Java编程逻辑》第5章 类的扩展

第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;
    }
}

猜你喜欢

转载自blog.csdn.net/NelsonCheung/article/details/110358505
今日推荐