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

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

Java 抽象类和方法

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

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

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

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

staticprivatefinal方法不能是抽象的,因为这些类型的方法不能被子类覆盖。 同样,final类不能具有任何抽象方法。

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

AbstractClass.java

  1. package com.zetcode;
  2. abstract class Drawing {
  3. protected int x = 0;
  4. protected int y = 0;
  5. public abstract double area();
  6. public String getCoordinates() {
  7. return String.format("x: %d, y: %d", this.x, this.y);
  8. }
  9. }
  10. class Circle extends Drawing {
  11. private int r;
  12. public Circle(int x, int y, int r) {
  13. this.x = x;
  14. this.y = y;
  15. this.r = r;
  16. }
  17. @Override
  18. public double area() {
  19. return this.r * this.r * Math.PI;
  20. }
  21. @Override
  22. public String toString() {
  23. return String.format("Circle at x: %d, y: %d, radius: %d",
  24. this.x, this.y, this.r);
  25. }
  26. }
  27. public class AbstractClass {
  28. public static void main(String[] args) {
  29. Circle c = new Circle(12, 45, 22);
  30. System.out.println(c);
  31. System.out.format("Area of circle: %f%n", c.area());
  32. System.out.println(c.getCoordinates());
  33. }
  34. }

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

  1. abstract class Drawing {

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

  1. public abstract double area();

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

  1. class Circle extends Drawing {

CircleDrawing类的子类。 因此,它必须实现抽象的area()方法。

  1. @Override
  2. public double area() {
  3. return this.r * this.r * Math.PI;
  4. }

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

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

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

Java 接口

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

接口是:

  • API
  • 合约

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

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

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

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

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

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

SimpleInterface.java

  1. package com.zetcode;
  2. interface IInfo {
  3. void doInform();
  4. }
  5. class Some implements IInfo {
  6. @Override
  7. public void doInform() {
  8. System.out.println("This is Some Class");
  9. }
  10. }
  11. public class SimpleInterface {
  12. public static void main(String[] args) {
  13. Some sm = new Some();
  14. sm.doInform();
  15. }
  16. }

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

  1. interface IInfo {
  2. void doInform();
  3. }

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

  1. class Some implements IInfo {

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

  1. @Override
  2. public void doInform() {
  3. System.out.println("This is Some Class");
  4. }

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

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

MultipleInterfaces.java

  1. package com.zetcode;
  2. interface Device {
  3. void switchOn();
  4. void switchOff();
  5. }
  6. interface Volume {
  7. void volumeUp();
  8. void volumeDown();
  9. }
  10. interface Pluggable {
  11. void plugIn();
  12. void plugOff();
  13. }
  14. class CellPhone implements Device, Volume, Pluggable {
  15. @Override
  16. public void switchOn() {
  17. System.out.println("Switching on");
  18. }
  19. @Override
  20. public void switchOff() {
  21. System.out.println("Switching on");
  22. }
  23. @Override
  24. public void volumeUp() {
  25. System.out.println("Volume up");
  26. }
  27. @Override
  28. public void volumeDown() {
  29. System.out.println("Volume down");
  30. }
  31. @Override
  32. public void plugIn() {
  33. System.out.println("Plugging in");
  34. }
  35. @Override
  36. public void plugOff() {
  37. System.out.println("Plugging off");
  38. }
  39. }
  40. public class MultipleInterfaces {
  41. public static void main(String[] args) {
  42. CellPhone cp = new CellPhone();
  43. cp.switchOn();
  44. cp.volumeUp();
  45. cp.plugIn();
  46. }
  47. }

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

  1. class CellPhone implements Device, Volume, Pluggable {

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

  1. $ java com.zetcode.MultipleInterfaces
  2. Switching on
  3. Volume up
  4. Plugging in

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

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

InterfaceHierarchy.java

  1. package com.zetcode;
  2. interface IInfo {
  3. void doInform();
  4. }
  5. interface IVersion {
  6. void getVersion();
  7. }
  8. interface ILog extends IInfo, IVersion {
  9. void doLog();
  10. }
  11. class DBConnect implements ILog {
  12. @Override
  13. public void doInform() {
  14. System.out.println("This is DBConnect class");
  15. }
  16. @Override
  17. public void getVersion() {
  18. System.out.println("Version 1.02");
  19. }
  20. @Override
  21. public void doLog() {
  22. System.out.println("Logging");
  23. }
  24. public void connect() {
  25. System.out.println("Connecting to the database");
  26. }
  27. }
  28. public class InterfaceHierarchy {
  29. public static void main(String[] args) {
  30. DBConnect db = new DBConnect();
  31. db.doInform();
  32. db.getVersion();
  33. db.doLog();
  34. db.connect();
  35. }
  36. }

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

  1. interface ILog extends IInfo, IVersion {

ILog接口从两个接口继承。

  1. class DBConnect implements ILog {

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

  1. @Override
  2. public void doInform() {
  3. System.out.println("This is DBConnect class");
  4. }

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

  1. $ java com.zetcode.InterfaceHierarchy
  2. This is DBConnect class
  3. Version 1.02
  4. Logging
  5. Connecting to the database

这是示例输出。

Java 多态

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

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

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

Polymorphism.java

  1. package com.zetcode;
  2. abstract class Shape {
  3. protected int x;
  4. protected int y;
  5. public abstract int area();
  6. }
  7. class Rectangle extends Shape {
  8. public Rectangle(int x, int y) {
  9. this.x = x;
  10. this.y = y;
  11. }
  12. @Override
  13. public int area() {
  14. return this.x * this.y;
  15. }
  16. }
  17. class Square extends Shape {
  18. public Square(int x) {
  19. this.x = x;
  20. }
  21. @Override
  22. public int area() {
  23. return this.x * this.x;
  24. }
  25. }
  26. public class Polymorphism {
  27. public static void main(String[] args) {
  28. Shape[] shapes = { new Square(5),
  29. new Rectangle(9, 4), new Square(12) };
  30. for (Shape shape : shapes) {
  31. System.out.println(shape.area());
  32. }
  33. }
  34. }

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

  1. @Override
  2. public int area() {
  3. return this.x * this.y;
  4. }
  5. ...
  6. @Override
  7. public int area() {
  8. return this.x * this.x;
  9. }

RectangleSquare类具有area()方法的自己的实现。

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

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

  1. for (Shape shape : shapes) {
  2. System.out.println(shape.area());
  3. }

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

Java 嵌套类

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

Java 有四种类型的嵌套类:

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

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

Java 静态嵌套类

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

SNCTest.java

  1. package com.zetcode;
  2. public class SNCTest {
  3. private static int x = 5;
  4. static class Nested {
  5. @Override
  6. public String toString() {
  7. return "This is a static nested class; x:" + x;
  8. }
  9. }
  10. public static void main(String[] args) {
  11. SNCTest.Nested sn = new SNCTest.Nested();
  12. System.out.println(sn);
  13. }
  14. }

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

  1. private static int x = 5;

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

  1. static class Nested {
  2. @Override
  3. public String toString() {
  4. return "This is a static nested class; x:" + x;
  5. }
  6. }

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

  1. SNCTest.Nested sn = new SNCTest.Nested();

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

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

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

Java 内部类

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

InnerClassTest.java

  1. package com.zetcode;
  2. public class InnerClassTest {
  3. private int x = 5;
  4. class Inner {
  5. @Override
  6. public String toString() {
  7. return "This is Inner class; x:" + x;
  8. }
  9. }
  10. public static void main(String[] args) {
  11. InnerClassTest nc = new InnerClassTest();
  12. InnerClassTest.Inner inner = nc.new Inner();
  13. System.out.println(inner);
  14. }
  15. }

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

  1. class Inner {
  2. @Override
  3. public String toString() {
  4. return "This is Inner class; x:" + x;
  5. }
  6. }

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

  1. InnerClassTest nc = new InnerClassTest();

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

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

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

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

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

Java 变量隐藏

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

Shadowing.java

  1. package com.zetcode;
  2. public class Shadowing {
  3. private int x = 0;
  4. class Inner {
  5. private int x = 5;
  6. void method1(int x) {
  7. System.out.println(x);
  8. System.out.println(this.x);
  9. System.out.println(Shadowing.this.x);
  10. }
  11. }
  12. public static void main(String[] args) {
  13. Shadowing sh = new Shadowing();
  14. Shadowing.Inner si = sh.new Inner();
  15. si.method1(10);
  16. }
  17. }

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

  1. System.out.println(x);

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

  1. System.out.println(this.x);

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

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

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

  1. $ java com.zetcode.Shadowing
  2. 10
  3. 5
  4. 0

这是示例输出。

Java 本地类

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

本地类别不能为publicprivateprotectedstatic。 不允许将它们用于局部变量声明或局部类声明。 除了声明为staticfinal的常量外,局部类不能包含静态字段,方法或类。

LocalClassTest.java

  1. package com.zetcode;
  2. public class LocalClassTest {
  3. public static void main(String[] args) {
  4. final int x = 5;
  5. class Local {
  6. @Override
  7. public String toString() {
  8. return "This is Local class; x:" + x;
  9. }
  10. }
  11. Local loc = new Local();
  12. System.out.println(loc);
  13. }
  14. }

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

  1. @Override
  2. public String toString() {
  3. return "This is Local class; x:" + x;
  4. }

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

Java 匿名类

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

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

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

AnonymousClass.java

  1. package com.zetcode;
  2. public class AnonymousClass {
  3. interface Message {
  4. public void send();
  5. }
  6. public void createMessage() {
  7. Message msg = new Message() {
  8. @Override
  9. public void send() {
  10. System.out.println("This is a message");
  11. }
  12. };
  13. msg.send();
  14. }
  15. public static void main(String[] args) {
  16. AnonymousClass ac = new AnonymousClass();
  17. ac.createMessage();
  18. }
  19. }

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

  1. interface Message {
  2. public void send();
  3. }

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

  1. public void createMessage() {
  2. Message msg = new Message() {
  3. @Override
  4. public void send() {
  5. System.out.println("This is a message");
  6. }
  7. };
  8. msg.send();
  9. }

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

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