# Java 面向对象编程 II

原文：http://zetcode.com/lang/java/oop2/

在 Java 教程的这一章中，我们将继续对 Java OOP 的描述。 我们提到了抽象类和方法，接口，多态以及各种嵌套类。

## Java 抽象类和方法

在设计应用时，我们经常发现我们的类具有很多通用功能。 这些共同点可以被提取并放入父类中。 这样，我们可以减少代码的大小，并使我们的应用更紧凑。 我们可能会发现父类是无形的，虚幻的实体-一个想法。 在桌子上，我们有一支笔，一本书，一支铅笔或一杯茶。 一个项目可能被视为所有这些事情的父类。 该类将包含这些项目的一些常见特质。 例如 id，权重或颜色。 我们可以实现`getId()`方法，但是不能在此类中实现`getWeight()`或`getColor()`方法。 物品没有重量或颜色。 这些方法只能在`Item`类的子类中实现。 对于这些情况，我们有抽象的方法和类。 `Item`类是抽象类的候选人-抽象类不能创建，并且其某些或所有方法不能实现。

使用`abstract`关键字创建抽象类或方法。 抽象类不能被实例化，但是可以被子类化。 如果一个类至少包含一个抽象方法，则也必须将其声明为抽象方法。 抽象方法无法实现； 他们只是声明方法的签名。 当我们从抽象类继承时，所有抽象方法都必须由派生类实现，或者该类本身必须是抽象的。

单个抽象类由相似类的子类继承，这些相似类具有很多共同点（抽象类的实现部分），但也有一些区别（抽象方法）。

抽象类可能具有完全实现的方法，也可能具有定义的成员字段。 因此，抽象类可以提供部分实现。 程序员经常将一些通用功能放入抽象类中。 这些抽象类随后会被子类化以提供更具体的实现。 通用功能在抽象类中实现，不同之处由抽象方法提示。 例如，Qt 图形库具有`QAbstractButton`，它是按钮小部件的抽象基类，提供按钮所共有的功能。 按钮`Q3Button`，`QCheckBox`，`QPushButton`，`QRadioButton`和`QToolButton`从此基本抽象类继承并提供其特定功能。

`static`，`private`和`final`方法不能是抽象的，因为这些类型的方法不能被子类覆盖。 同样，`final`类不能具有任何抽象方法。

正式地说，抽象类用于强制执行协议。 协议是所有实现对象都必须支持的一组操作。

`AbstractClass.java`

```java
package com.zetcode;

abstract class Drawing {

    protected int x = 0;
    protected int y = 0;

    public abstract double area();

    public String getCoordinates() {

        return String.format("x: %d, y: %d", this.x, this.y);
    }
}

class Circle extends Drawing {

    private int r;

    public Circle(int x, int y, int r) {

        this.x = x;
        this.y = y;
        this.r = r;
    }

    @Override
    public double area() {

        return this.r * this.r * Math.PI;
    }

    @Override
    public String toString() {

        return String.format("Circle at x: %d, y: %d, radius: %d",
                this.x, this.y, this.r);
    }
}

public class AbstractClass {

    public static void main(String[] args) {

        Circle c = new Circle(12, 45, 22);

        System.out.println(c);
        System.out.format("Area of circle: %f%n", c.area());
        System.out.println(c.getCoordinates());
    }
}

```

我们有一个抽象基类`Drawing`。 该类定义两个成员字段，定义一个方法并声明一个方法。 一种方法是抽象的，另一种是完全实现的。 `Drawing`类是抽象的，因为我们无法绘制它。 我们可以画一个圆，一个点或一个正方形，但是我们不能画一个`Drawing`。 `Drawing`类对我们可以绘制的对象具有一些通用功能。

```java
abstract class Drawing {

```

我们使用`abstract`关键字定义一个抽象类。

```java
public abstract double area();

```

抽象方法之前还带有`abstract`关键字。 `Drawing`类是一个想法。 这是不真实的，我们无法为其实现`area()`方法。 在这种情况下，我们使用抽象方法。 该方法将在更具体的实体（例如圆圈）中实现。

```java
class Circle extends Drawing {

```

`Circle`是`Drawing`类的子类。 因此，它必须实现抽象的`area()`方法。

```java
@Override
public double area() {

    return this.r * this.r * Math.PI;
}

```

在这里，我们正在实现`area()`方法。

```java
$ java com.zetcode.AbstractClass
Circle at x: 12, y: 45, radius: 22
Area of circle: 1520.530844
x: 12, y: 45

```

我们创建一个`Circle`对象并打印其面积和坐标。

## Java 接口

遥控器是观众和电视之间的接口。 它是此电子设备的接口。 外交礼仪指导外交领域的所有活动。 道路规则是驾车者，骑自行车的人和行人必须遵守的规则。 编程中的接口类似于前面的示例。

接口是：

*   API
*   合约

对象通过其公开的方法与外界交互。 实际的实现对程序员而言并不重要，或者也可能是秘密的。 公司可能会出售图书馆，但它不想透露实际的实现情况。 程序员可能会在 GUI 工具箱的窗口上调用`maximize()`方法，但对如何实现此方法一无所知。 从这个角度来看，接口是对象与外界交互的方式，而又不会过多地暴露其内部功能。

从第二个角度来看，接口就是契约。 如果达成协议，则必须遵循。 它们用于设计应用的架构。 他们帮助组织代码。

接口是完全抽象的类型。 它们使用`interface`关键字声明。 在 Java 中，接口是引用类型，类似于只能包含常量，方法签名和嵌套类型的类。 没有方法主体。 接口无法实例化-它们只能由类实现或由其他接口扩展。 所有接口成员都隐式具有公共访问权限。 接口不能具有完全实现的方法。 Java 类可以实现任何数量的接口。 接口扫描还可以扩展任何数量的接口。 实现接口的类必须实现接口的所有方法签名。

接口用于模拟多重继承。 Java 类只能从一个类继承，但可以实现多个接口。 具有接口的多重继承与继承方法和变量无关，而与继承接口所描述的思想或契约有关。

接口的主体包含抽象方法，但是根据定义，由于接口中的所有方法都是抽象的，因此不需要`abstract`关键字。 由于接口指定了一组公开的行为，因此所有方法都是隐式公共的。 接口除了方法声明外，还可以包含常量成员声明。 接口中定义的所有常数值都是`public`，`static`和`final`隐式。 这些修饰符可以省略。

接口和抽象类之间有一个重要的区别。 抽象类为继承层次结构中相关的类提供部分实现。 另一方面，可以通过彼此不相关的类来实现接口。 例如，我们有两个按钮。 经典按钮和圆形按钮。 两者都继承自抽象按钮类，该类为所有按钮提供了一些通用功能。 实现类是相关的，因为它们都是按钮。 而类别`Database`和`SignIn`彼此不相关。 我们可以应用`ILoggable`接口，该接口将迫使他们创建执行日志记录的方法。

`SimpleInterface.java`

```java
package com.zetcode;

interface IInfo {

    void doInform();
}

class Some implements IInfo {

    @Override
    public void doInform() {

        System.out.println("This is Some Class");
    }
}

public class SimpleInterface {

    public static void main(String[] args) {

        Some sm = new Some();
        sm.doInform();
    }
}

```

这是一个演示接口的简单 Java 程序。

```java
interface IInfo {

    void doInform();
}

```

这是接口`IInfo`。 它具有`doInform()`方法签名。

```java
class Some implements IInfo {

```

我们实现了`IInfo`接口。 要实现特定的接口，我们使用`implements`关键字。

```java
@Override
public void doInform() {

    System.out.println("This is Some Class");
}

```

该类提供了`doInform()`方法的实现。 `@Override`注解告诉编译器我们正在重写方法。

Java 不允许直接从多个类中继承。 它允许实现多个接口。 下一个示例显示了一个类如何实现多个接口。

`MultipleInterfaces.java`

```java
package com.zetcode;

interface Device {

    void switchOn();

    void switchOff();
}

interface Volume {

    void volumeUp();

    void volumeDown();
}

interface Pluggable {

    void plugIn();

    void plugOff();
}

class CellPhone implements Device, Volume, Pluggable {

    @Override
    public void switchOn() {

        System.out.println("Switching on");
    }

    @Override
    public void switchOff() {

        System.out.println("Switching on");
    }

    @Override
    public void volumeUp() {

        System.out.println("Volume up");
    }

    @Override
    public void volumeDown() {

        System.out.println("Volume down");
    }

    @Override
    public void plugIn() {

        System.out.println("Plugging in");
    }

    @Override
    public void plugOff() {

        System.out.println("Plugging off");
    }
}

public class MultipleInterfaces {

    public static void main(String[] args) {

        CellPhone cp = new CellPhone();
        cp.switchOn();
        cp.volumeUp();
        cp.plugIn();
    }
}

```

我们有一个`CellPhone`类，它从三个接口继承。

```java
class CellPhone implements Device, Volume, Pluggable {

```

该类实现所有三个用逗号分隔的接口。 `CellPhone`类必须实现来自所有三个接口的所有方法签名。

```java
$ java com.zetcode.MultipleInterfaces 
Switching on
Volume up
Plugging in

```

运行程序，我们得到此输出。

下一个示例显示接口如何形成层次结构。 接口可以使用`extends`关键字从其他接口继承。

`InterfaceHierarchy.java`

```java
package com.zetcode;

interface IInfo {

    void doInform();
}

interface IVersion {

    void getVersion();
}

interface ILog extends IInfo, IVersion {

    void doLog();
}

class DBConnect implements ILog {

    @Override
    public void doInform() {

        System.out.println("This is DBConnect class");
    }

    @Override
    public void getVersion() {

        System.out.println("Version 1.02");
    }

    @Override
    public void doLog() {

        System.out.println("Logging");
    }

    public void connect() {

        System.out.println("Connecting to the database");
    }
}

public class InterfaceHierarchy {

    public static void main(String[] args) {

        DBConnect db = new DBConnect();
        db.doInform();
        db.getVersion();
        db.doLog();
        db.connect();
    }
}

```

我们定义了三个接口。 接口按层次结构组织。

```java
interface ILog extends IInfo, IVersion {

```

`ILog`接口从两个接口继承。

```java
class DBConnect implements ILog {

```

`DBConnect`类实现`ILog`接口。 因此，它必须实现所有三个接口的方法。

```java
@Override
public void doInform() {

    System.out.println("This is DBConnect class");
}

```

`DBConnect`类实现`doInform()`方法。 该方法由该类实现的`ILog`接口继承。

```java
$ java com.zetcode.InterfaceHierarchy 
This is DBConnect class
Version 1.02
Logging
Connecting to the database

```

这是示例输出。

## Java 多态

多态是对不同的数据输入以不同方式使用运算符或函数的过程。 实际上，多态意味着如果类 B 从类 A 继承，则不必继承关于类 A 的所有内容； 它可以完成 A 类所做的某些事情。

通常，多态是以不同形式出现的能力。 从技术上讲，它是重新定义派生类的方法的能力。 多态与将特定实现应用于接口或更通用的基类有关。

简而言之，多态是重新定义派生类的方法的能力。

`Polymorphism.java`

```java
package com.zetcode;

abstract class Shape {

    protected int x;
    protected int y;

    public abstract int area();
}

class Rectangle extends Shape {

    public Rectangle(int x, int y) {

        this.x = x;
        this.y = y;
    }

    @Override
    public int area() {

        return this.x * this.y;
    }
}

class Square extends Shape {

    public Square(int x) {

        this.x = x;
    }

    @Override
    public int area() {

        return this.x * this.x;
    }
}

public class Polymorphism {

    public static void main(String[] args) {

        Shape[] shapes = { new Square(5),
            new Rectangle(9, 4), new Square(12) };

        for (Shape shape : shapes) {

            System.out.println(shape.area());
        }
    }
}

```

在上面的程序中，我们有一个抽象的`Shape`类。 此类演变为两个后代类别：`Rectangle`和`Square`。 两者都提供了自己的`area()`方法实现。 多态为 OOP 系统带来了灵活性和可伸缩性。

```java
@Override
public int area() {

    return this.x * this.y;
}
...
@Override
public int area() {

    return this.x * this.x;
}

```

`Rectangle`和`Square`类具有`area()`方法的自己的实现。

```java
Shape[] shapes = { new Square(5),
    new Rectangle(9, 4), new Square(12) };

```

我们创建三个形状的数组。

```java
for (Shape shape : shapes) {

    System.out.println(shape.area());
}

```

我们遍历每个形状，并在其上调用`area()`方法。 编译器为每种形状调用正确的方法。 这就是多态的本质。

## Java 嵌套类

可以在另一个类中定义一个类。 这种类在 Java 术语中称为嵌套类。 非嵌套类的类称为顶级类。

Java 有四种类型的嵌套类：

*   静态嵌套类
*   内部类
*   本地类
*   匿名类

使用嵌套类可以提高代码的可读性并改善代码的组织。 内部类通常在 GUI 中用作回调。 例如在 Java Swing 工具箱中。

## Java 静态嵌套类

静态嵌套类是可以在没有封闭类实例的情况下创建的嵌套类。 它可以访问封闭类的静态变量和方法。

`SNCTest.java`

```java
package com.zetcode;

public class SNCTest {

    private static int x = 5;

    static class Nested {

        @Override
        public String toString() {
            return "This is a static nested class; x:" + x;
        }
    }

    public static void main(String[] args) {

        SNCTest.Nested sn = new SNCTest.Nested();
        System.out.println(sn);
    }
}

```

该示例展示了一个静态的嵌套类。

```java
private static int x = 5;

```

这是`SNCTest`类的私有静态变量。 可以通过静态嵌套类访问它。

```java
static class Nested {

    @Override
    public String toString() {
        return "This is a static nested class; x:" + x;
    }
}

```

定义了一个静态的嵌套类。 它具有一种打印消息并引用静态`x`变量的方法。

```java
SNCTest.Nested sn = new SNCTest.Nested();

```

点运算符用于引用嵌套类。

```java
$ java com.zetcode.SNCTest 
This is a static nested class; x:5

```

这是`com.zetcode.SNCTest`程序的输出。

## Java 内部类

普通或顶级类的实例可以单独存在。 相比之下，内部类的实例必须绑定到顶级类才能实例化。 内部类也称为成员类。 它们属于封闭类的实例。 内部类可以访问封闭类的成员。

`InnerClassTest.java`

```java
package com.zetcode;

public class InnerClassTest {

    private int x = 5;

    class Inner {

        @Override
        public String toString() {
            return "This is Inner class; x:" + x;
        }
    }

    public static void main(String[] args) {

        InnerClassTest nc = new InnerClassTest();
        InnerClassTest.Inner inner = nc.new Inner();

        System.out.println(inner);
    }
}

```

在`InnerClassTest`类中定义了一个嵌套类。 它可以访问成员`x`变量。

```java
class Inner {

    @Override
    public String toString() {
        return "This is Inner class; x:" + x;
    }
}

```

`InnerClassTest`类的主体中定义了`Inner`类。

```java
InnerClassTest nc = new InnerClassTest();

```

首先，我们需要创建顶级类的实例。 没有封闭类的实例，内部类将不存在。

```java
InnerClassTest.Inner inner = nc.new Inner();

```

一旦实例化了顶级类，就可以创建内部类的实例。

```java
$ java com.zetcode.InnerClassTest 
This is Inner class; x:5

```

这是`com.zetcode.InnerClassTest`程序的输出。

## Java 变量隐藏

如果内部作用域中的变量与外部作用域中的变量具有相同的名称，则将其隐藏。 仍然可以在外部范围中引用该变量。

`Shadowing.java`

```java
package com.zetcode;

public class Shadowing {

    private int x = 0;

    class Inner {

        private int x = 5;

        void method1(int x) {

            System.out.println(x);
            System.out.println(this.x);
            System.out.println(Shadowing.this.x);            
        }
    }

    public static void main(String[] args) {

        Shadowing sh = new Shadowing();
        Shadowing.Inner si = sh.new Inner();

        si.method1(10);    
    }
}

```

我们在顶级类，内部类和方法内部定义一个`x`变量。

```java
System.out.println(x);

```

该行引用在方法的本地范围内定义的`x`变量。

```java
System.out.println(this.x);

```

使用`this`关键字，我们引用`Inner`类中定义的`x`变量。

```java
System.out.println(Shadowing.this.x);

```

在这里，我们指的是`Shadowing`顶级类的`x`变量。

```java
$ java com.zetcode.Shadowing 
10
5
0

```

这是示例输出。

## Java 本地类

本地类是内部类的特例。 本地类是在块中定义的类。 （块是括号之间的零个或多个语句的组。）本地类可以访问其封闭类的成员。 此外，如果声明了`final`，则本地类可以访问本地变量。 原因是技术上的。 本地类实例的生存期可能比定义该类的方法的执行时间更长。 为了解决这个问题，将局部变量复制到局部类中。 为了确保以后不会更改它们，必须将它们声明为`final`。

本地类别不能为`public`，`private`，`protected`或`static`。 不允许将它们用于局部变量声明或局部类声明。 除了声明为`static`和`final`的常量外，局部类不能包含静态字段，方法或类。

`LocalClassTest.java`

```java
package com.zetcode;

public class LocalClassTest {

    public static void main(String[] args) {

        final int x = 5;       

        class Local {

            @Override
            public String toString() {
                return "This is Local class; x:" + x;
            }
        }

        Local loc = new Local();
        System.out.println(loc);        
    }
}

```

本地类在`main()`方法的主体中定义。

```java
@Override
public String toString() {
    return "This is Local class; x:" + x;
}

```

如果局部类声明为`final`，则可以访问它们。

## Java 匿名类

匿名类是没有名称的本地类。 它们使我们能够同时声明和实例化一个类。 如果我们只想使用匿名类，则可以使用匿名类。 匿名类在单个表达式中定义和实例化。 当事件处理代码仅由一个组件使用，因此不需要命名引用时，也可以使用匿名内部类。

匿名类必须实现接口或从类继承。 但是不使用`implements`和`extends`关键字。 如果`new`关键字后面的名称是类的名称，则匿名类是命名类的子类。 如果在`new`之后的名称指定了接口，则匿名类将实现该接口并扩展`Object`。

由于匿名类没有名称，因此无法为匿名类定义构造器。 在匿名类的主体内部，我们无法定义任何语句； 仅方法或成员。

`AnonymousClass.java`

```java
package com.zetcode;

public class AnonymousClass {

   interface Message {
        public void send();
    }    

    public void createMessage() {

        Message msg = new Message() {

            @Override
            public void send() {
                System.out.println("This is a message");
            }
        };

        msg.send();
    }

    public static void main(String[] args) {

        AnonymousClass ac = new AnonymousClass();
        ac.createMessage();
    }
}

```

在此代码示例中，我们创建一个匿名类。

```java
interface Message {
    public void send();
}  

```

匿名类必须是子类或必须实现接口。 我们的匿名类将实现`Message`接口。 否则，编译器将无法识别类型。

```java
public void createMessage() {

    Message msg = new Message() {

        @Override
        public void send() {
            System.out.println("This is a message");
        }
    };

    msg.send();
}

```

匿名类是本地类，因此它是在方法主体中定义的。 表达式中定义了一个匿名类。 因此，右括号后面是一个分号。

在 Java 教程的这一部分中，我们继续介绍 Java 中的面向对象编程。