15-面向对象7

1. 抽象类和抽象方法

1.1 概述

「父类」的目的是为它的所有导出类创建一个通用接口。建立这个通用接口的唯一理由是,不同的子类可以用不同的方式表示此接口。通用接口建立起一种基本形式,以此表示所有导出类的共同部分。即将「父类」称作“抽象基类”,或简称“抽象类”。

如果我们只有一个抽象基类,那么该类的对象几乎没有任何意义。我们创建抽象类是希望通过这个通用接口操纵一系列类。因此,「父类」只是表示了一个接口,没有具体的实现内容;也就是说,创建一个「父类」对象没有什么意义,并且我们可能还想阻止使用者这么做。通过让父类中所有方法都产生错误,就可以实现这个目的。但是这样做会将错误信息延迟到运行时才获得,所以最好是在编译时捕获这些问题。

1.2 抽象类的应用

public abstract class Vehicle {
    public abstract double calcFuelEfficiency(); // 计算燃料效率的抽象方法
    public abstract double calcTripDistance(); // 计算行驶距离的抽象方法
}

public class Truck extends Vehicle {
    public double calcFuelEfficiency() {
        // 写出计算卡车的燃料效率的具体方法
    }
    public double calcTripDistance() {
        // 写出计算卡车行驶距离的具体方法
    }
}

public class RiverBarge extends Vehicle {
    public double calcFuelEfficiency() {
        // 写出计算驳船的燃料效率的具体方法
    }
    public double calcTripDistance() {
        // 写出计算驳船行驶距离的具体方法
    }
}

1.3 注意事项

  • 不能用 abstract 修饰 变量、代码块、构造器
  • 不能用 abstract 修饰 private 方法、static 方法、final 方法以及 final 类

1.4 抽象类的匿名子类

public class AnonymousClass {
    public static void main(String[] args) {
        Fish f = new Fish();
        func(f); // → 非匿名类 ~ 非匿名对象
        func(new Fish()); // → 非匿名类 ~ 匿名对象

        // → 父类为Animal[A]的匿名子类 ~ 非匿名对象
        Animal an = new Animal() {
            @Override
            public void move() {
                // super.move(); 父类是抽象类, 故编译报错!
                System.out.println("fly in the sky ~");
            }
        };
        func(an);

        // → 父类为Fish[C]的匿名子类 ~ 匿名对象
        func(new Fish() {
            @Override
            public void move() {
                super.move();
                System.out.println("also can walk in the water ~");
            }
        });
    }

    public static void func(Animal an) {
        an.move();
    }
}

abstract class Animal {
    public abstract void move();
}

class Fish extends Animal {
    @Override
    public void move() {
        System.out.println("swim in the water ~");
    }
}

1.5 模板方法的设计模式

public class TemplateMethodTest {
    public static void main(String[] args) {
        BankTemplate btm = new DrawMoney();
        btm.process();

        BankTemplate btm2 = new ManageMoney();
        btm2.process();
    }
}

abstract class BankTemplate {
    // 具体方法
    public void takeNumber() {
        System.out.println("取号排队");
    }

    public abstract void transact(); // 办理具体的业务(钩子方法)

    public void evaluate() {
        System.out.println("反馈评分");
    }

    // 模板方法,把基本操作组合到一起,子类一般不能重写
    public final void process() {
        this.takeNumber();
        // 像个钩子,具体执行时,挂哪个子类上,就执行哪个子类的实现代码
        this.transact();
        this.evaluate();
    }
}

class DrawMoney extends BankTemplate {
    public void transact() {
        System.out.println("我要取款!!!");
    }
}

class ManageMoney extends BankTemplate {
    public void transact() {
        System.out.println("我要理财!!!");
    }
}

2. 接口

接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。

interface 关键字使抽象的概念更向前迈进了一步。abstract 关键字允许人们在类中创建一个或多个没有任何定义的方法 —— 提供了接口部分,但是没有提供任何相应的具体实现,这些实现是由此类的继承者创建的。interface 这个关键字产生一个完全抽象的类,他根本就没有提供任何具体实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口只提供了形式,而未提供任何具体实现。

一个接口表示:"所有实现了该特定接口的类看起来都像这样"。因此,任何使用某特定接口的代码都知道调用该接口的哪些方法,而且仅需知道这些。因此,接口被用来建立类与类之间的协议。

但是,interface 不仅仅是一个极度抽象的类(接口和类是并列的两种结构),因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继承的特性。

有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3、手机、数码相机、移动硬盘等都支持 USB 连接。

接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要 ...,则必须能 ...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能" 的关系

2.1 使用规则

接口(interface)是抽象方法和常量值定义的集合。

  • 接口中的所有成员变量都默认是由 public static final 修饰
  • 接口中的所有抽象方法都默认是由 public abstract 修饰
  • 接口中没有构造器
  • 接口采用“多继承”机制 // 弥补了类的单继承性

举例:

注意!以上定义都是 JDK7 及之前的;从 JDK8 开始,除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法。

接口通过让类实现 (implements) 的方式来使用:

  • 如果实现类实现了接口中所有的方法,则此实现类就可以实例化。
  • 如果实现类没有实现接口中所有的方法,则此实现类必为一个抽象类。

一个类可以实现多个接口,接口也可以继承其它接口;定义 Java 类的语法格式:class SubClass [extends SuperClass] [implements InterfaceA [, ..., InterfaceN]] {...}

2.2 接口是一种规范

不变应万变, 不变的是"规范"。因此,开发往往都是「面向接口编程」。

// 与继承关系类似,接口与实现类之间存在多态性
public class USBTest {
    public static void main(String[] args) {
        Flash f = new Flash();
        Mouse m = new Mouse();
        Computer c = new Computer();
        c.transferData(f);
        c.transferData(m);
    }
}

class Computer {
    public void transferData(USB usb) {
        usb.start();
        System.out.println("--- transferData ---");
        usb.stop();
    }
}

interface USB { // 规范
    void start();
    void stop();
}

// 接口的主要用途就是被实现类实现
class Flash implements USB {

    @Override
    public void start() {
        System.out.println("U盘开始工作");
    }

    @Override
    public void stop() {
        System.out.println("U盘停止工作");
    }
}

class Mouse implements USB {

    @Override
    public void start() {
        System.out.println("鼠标开始工作");
    }

    @Override
    public void stop() {
        System.out.println("鼠标停止工作");
    }
}

2.3 接口的匿名实现类

public class USBTest {
    public static void main(String[] args) {
        // 匿名实现类 ~ 非匿名对象
        USB printer = new USB() {
            @Override
            public void stop() {
                System.out.println("打印机停止工作");
            }

            @Override
            public void start() {
                System.out.println("打印机开始工作");
            }
        };

        c.transferData(printer);

        // 匿名实现类 ~ 匿名对象
        c.transferData(new USB() {

            @Override
            public void stop() {
                System.out.println("音响停止工作");
            }

            @Override
            public void start() {
                System.out.println("音响开始工作");
            }
        });
    }
}

2.4 应用:代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

通俗的来讲代理模式就是我们生活中常见的中介。假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。

a. 代理模式的通用类图

  • [Subject]:抽象主题角色,可以是抽象类,也可以是接口;抽象主题是一个普通的业务类型,无特殊要求。
  • [RealSubject]:具体主题角色,也叫做被委托角色或被代理角色,是业务逻辑的具体执行者。
  • [Proxy]:代理主题角色,也叫做委托类或代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在具体主题角色处理完毕前后做预处理和善后处理工作。

b. 代理模式的优点

  • 职责清晰;具体角色是实现具体的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,代码清晰。在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口
  • 高扩展性;具体主题角色随时会发生变化,但是只要实现了接口,接口不变,代理类就可以不做任何修改继续使用,符合"开闭原则(对扩展开放、对修改关闭)"。另外,代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,同样符合开闭原则。

c. 使用场景

代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

d. 代理模式通用示例

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

更通俗的说,代理解决的问题是:当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理。但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

代理模式有多种不同的实现方式。如果按照代理创建的时期来进行分类:静态代理、动态代理

  • 静态代理:由程序员创建或特定工具自动生成源代码,再对其进行编译。在程序运行之前,代理类 .class 文件就已经被创建,代理类和委托类的关系在运行前就确定。
  • 动态代理:动态代理类的源码是在程序运行期间由 JVM 根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

2.5 排错题

2.6 JDK8 的接口

JDK8 中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。

  • 【静态方法】使用 static 关键字修饰。通过接口直接调用。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像 Collection/Collections 或者 Path/Paths 这样成对的接口和类
  • 【默认方法】使用 default 关键字修饰。通过实现类对象来调用。 我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。 比如:Java 8 API 中对 CollectionListComparator 等接口提供了丰富的默认方法。

有几个注意点要提一下:

  1. 如果实现类实现的多个接口中,具有同名同参数的默认方法,若子类不重写该方法,会编译报错 → [接口冲突] → 解决办法:重写该同名同参的方法
  2. 如果子类(实现类)继承的父类和实现的接口中声明了同名同参数的成员(默认)方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略
  3. 如果父类和接口有同名同参方法、或者实现类重写了接口中的默认方法,此时如何调用接口中的默认方法?

3. 内部类

可以将一个类的定义放在另一个类的定义内部,这就是内部类。

内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。然后必须要了解,内部类和组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信。

3.1 内部类在类中的位置

  • 成员内部类(静态、非静态)
  • 局部内部类(构造器、代码块、方法内)
class Person {
    // 成员内部类1:静态
    static class Dog {}

    // 成员内部类2:非静态
    class Bird {}

    // 局部内部类1:静态代码块内
    static {
        class AA {}
    }

    // 局部内部类2:非静态代码块内
    {
        class AA {}
    }

    // 局部内部类3:构造器内
    public Person() {
        class AA {}
    }

    // 局部内部类4:方法内
    public void method() {
        class AA {}
    }
}

3.2 链接到外部类

到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些事很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素(方法和字段)的访问权,就像自己拥有它们似的。

一个内部类被嵌套多少层并不重要 —— 它能透明地访问所有它所嵌入的外围类的所有成员。

如何做到的呢?

当某个外围类的对象创建了一个内部类对象时,此内部类对象有一个隐式的对外围类对象的引用,这个引用在内部类的定义中是不可见的。然后,当你在内部类中访问此外围类的成员时,就是用那个引用来选择外围类的成员(包括 private 成员)。幸运的是,编译器会帮你处理所有的细节(外围类的引用在构造器中设置;编译器修改了所有的内部类的构造器,添加一个外围类引用的参数),但你现在可以看到:内部类的对象只能在与外围类的对象相关联的情况下才能被创建(针对内部类是 !static 类时,若是 static 内部类则没有这种附加指针)。创建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。

3.3 生成一个成员内部类对象

  • 非静态的成员内部类 // 必须要用外部类的对象来创建该内部类对象
    • Person p = new Person(); Person.Bird b1 = p.new Bird();
    • Person.Bird b2 = new Person().new Bird();
  • 静态的成员内部类
    • Person.Dog dog = new Person.Dog();
    • 在拥有外部类对象之前是不可能创建非静态内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外部类对象上。但是,如果创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。

3.4 嵌套类

3.5 非静态成员内部类中调用外部类的结构

// Person的非静态成员内部类Bird
class Bird {
    int speed;

    public void fly(int speed) {
        System.out.println(speed); // 方法形参
        System.out.println(this.speed); // 内部类属性
        System.out.println(Person.this.speed); // 外部类属性!
    }
}

3.6 局部内部类

只能在声明它的方法或代码块中使用,而且是先声明后使用;除此之外的任何地方都不能使用该类。

它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型

局部内部类的使用举例:

// 返回一个实现了Comparable<I>的类的对象
public Comparable geComparable() {
    // Plan-1: 创建一个实现了Comparable<I>的局部内部类
    class MyComparable implements Comparable{
        @Override
        public int compareTo(Object o) {
            return 0;
        }
    }
    // 返回该局部内部类的匿名对象
    return new MyComparable();
    
    // Plan-2: 返回一个实现了该接口的匿名实现类的匿名对象
}

局部内部类的特点:

  1. 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的 .class 文件
    • 成员内部类:[外部类名]$[内部类名].class
    • 局部内部类:[外部类名]$[数字][内部类名].class
  2. 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
  3. 局部内部类可以使用外部类的成员,包括私有的。
  4. 局部内部类可以使用外部方法的局部变量,但是局部变量必须是 final 的(JDK8 之前必须要显式声明,之后可省);由局部内部类和局部变量的声明周期不同所致
    public void method() {
        int num = 13; // 就算没给final,但内部类里调num了,那编译器就会自动给num加final
        class C {
            public void show() {
                // Local variable num defined in an enclosing scope
                // must be final or effectively final
                System.out.println(num); // 错
            }
        }
        num = 1101; // final就不能再改了,不是这行就是上面syso(num)那行,总得有一个报错
    }
    
  5. 局部内部类和局部变量地位类似,因此不能使用 public,protected,缺省,private
  6. 局部内部类不能使用 static 修饰,因此也不能包含静态成员

3.7 匿名内部类

  • 匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在 new 的后面,用其隐含实现一个接口或实现一个类
  • 格式
    new 父类构造器(实参列表) | 实现接口 () {
        // 匿名内部类的类体部分
    }
    
  • 特点
    • 匿名内部类必须继承父类或实现接口
    • 匿名内部类只能有一个对象
    • 匿名内部类对象只能使用多态形式引用

3.8 为什么需要内部类

虽然看不懂,但我还是截下来了...

原文地址:https://www.cnblogs.com/liujiaqi1101/p/13115393.html