原文: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
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
类对我们可以绘制的对象具有一些通用功能。
abstract class Drawing {
我们使用abstract
关键字定义一个抽象类。
public abstract double area();
抽象方法之前还带有abstract
关键字。 Drawing
类是一个想法。 这是不真实的,我们无法为其实现area()
方法。 在这种情况下,我们使用抽象方法。 该方法将在更具体的实体(例如圆圈)中实现。
class Circle extends Drawing {
Circle
是Drawing
类的子类。 因此,它必须实现抽象的area()
方法。
@Override
public double area() {
return this.r * this.r * Math.PI;
}
在这里,我们正在实现area()
方法。
$ 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
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 程序。
interface IInfo {
void doInform();
}
这是接口IInfo
。 它具有doInform()
方法签名。
class Some implements IInfo {
我们实现了IInfo
接口。 要实现特定的接口,我们使用implements
关键字。
@Override
public void doInform() {
System.out.println("This is Some Class");
}
该类提供了doInform()
方法的实现。 @Override
注解告诉编译器我们正在重写方法。
Java 不允许直接从多个类中继承。 它允许实现多个接口。 下一个示例显示了一个类如何实现多个接口。
MultipleInterfaces.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
类,它从三个接口继承。
class CellPhone implements Device, Volume, Pluggable {
该类实现所有三个用逗号分隔的接口。 CellPhone
类必须实现来自所有三个接口的所有方法签名。
$ java com.zetcode.MultipleInterfaces
Switching on
Volume up
Plugging in
运行程序,我们得到此输出。
下一个示例显示接口如何形成层次结构。 接口可以使用extends
关键字从其他接口继承。
InterfaceHierarchy.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();
}
}
我们定义了三个接口。 接口按层次结构组织。
interface ILog extends IInfo, IVersion {
ILog
接口从两个接口继承。
class DBConnect implements ILog {
DBConnect
类实现ILog
接口。 因此,它必须实现所有三个接口的方法。
@Override
public void doInform() {
System.out.println("This is DBConnect class");
}
DBConnect
类实现doInform()
方法。 该方法由该类实现的ILog
接口继承。
$ java com.zetcode.InterfaceHierarchy
This is DBConnect class
Version 1.02
Logging
Connecting to the database
这是示例输出。
Java 多态
多态是对不同的数据输入以不同方式使用运算符或函数的过程。 实际上,多态意味着如果类 B 从类 A 继承,则不必继承关于类 A 的所有内容; 它可以完成 A 类所做的某些事情。
通常,多态是以不同形式出现的能力。 从技术上讲,它是重新定义派生类的方法的能力。 多态与将特定实现应用于接口或更通用的基类有关。
简而言之,多态是重新定义派生类的方法的能力。
Polymorphism.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 系统带来了灵活性和可伸缩性。
@Override
public int area() {
return this.x * this.y;
}
...
@Override
public int area() {
return this.x * this.x;
}
Rectangle
和Square
类具有area()
方法的自己的实现。
Shape[] shapes = { new Square(5),
new Rectangle(9, 4), new Square(12) };
我们创建三个形状的数组。
for (Shape shape : shapes) {
System.out.println(shape.area());
}
我们遍历每个形状,并在其上调用area()
方法。 编译器为每种形状调用正确的方法。 这就是多态的本质。
Java 嵌套类
可以在另一个类中定义一个类。 这种类在 Java 术语中称为嵌套类。 非嵌套类的类称为顶级类。
Java 有四种类型的嵌套类:
- 静态嵌套类
- 内部类
- 本地类
- 匿名类
使用嵌套类可以提高代码的可读性并改善代码的组织。 内部类通常在 GUI 中用作回调。 例如在 Java Swing 工具箱中。
Java 静态嵌套类
静态嵌套类是可以在没有封闭类实例的情况下创建的嵌套类。 它可以访问封闭类的静态变量和方法。
SNCTest.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);
}
}
该示例展示了一个静态的嵌套类。
private static int x = 5;
这是SNCTest
类的私有静态变量。 可以通过静态嵌套类访问它。
static class Nested {
@Override
public String toString() {
return "This is a static nested class; x:" + x;
}
}
定义了一个静态的嵌套类。 它具有一种打印消息并引用静态x
变量的方法。
SNCTest.Nested sn = new SNCTest.Nested();
点运算符用于引用嵌套类。
$ java com.zetcode.SNCTest
This is a static nested class; x:5
这是com.zetcode.SNCTest
程序的输出。
Java 内部类
普通或顶级类的实例可以单独存在。 相比之下,内部类的实例必须绑定到顶级类才能实例化。 内部类也称为成员类。 它们属于封闭类的实例。 内部类可以访问封闭类的成员。
InnerClassTest.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
变量。
class Inner {
@Override
public String toString() {
return "This is Inner class; x:" + x;
}
}
InnerClassTest
类的主体中定义了Inner
类。
InnerClassTest nc = new InnerClassTest();
首先,我们需要创建顶级类的实例。 没有封闭类的实例,内部类将不存在。
InnerClassTest.Inner inner = nc.new Inner();
一旦实例化了顶级类,就可以创建内部类的实例。
$ java com.zetcode.InnerClassTest
This is Inner class; x:5
这是com.zetcode.InnerClassTest
程序的输出。
Java 变量隐藏
如果内部作用域中的变量与外部作用域中的变量具有相同的名称,则将其隐藏。 仍然可以在外部范围中引用该变量。
Shadowing.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
变量。
System.out.println(x);
该行引用在方法的本地范围内定义的x
变量。
System.out.println(this.x);
使用this
关键字,我们引用Inner
类中定义的x
变量。
System.out.println(Shadowing.this.x);
在这里,我们指的是Shadowing
顶级类的x
变量。
$ java com.zetcode.Shadowing
10
5
0
这是示例输出。
Java 本地类
本地类是内部类的特例。 本地类是在块中定义的类。 (块是括号之间的零个或多个语句的组。)本地类可以访问其封闭类的成员。 此外,如果声明了final
,则本地类可以访问本地变量。 原因是技术上的。 本地类实例的生存期可能比定义该类的方法的执行时间更长。 为了解决这个问题,将局部变量复制到局部类中。 为了确保以后不会更改它们,必须将它们声明为final
。
本地类别不能为public
,private
,protected
或static
。 不允许将它们用于局部变量声明或局部类声明。 除了声明为static
和final
的常量外,局部类不能包含静态字段,方法或类。
LocalClassTest.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()
方法的主体中定义。
@Override
public String toString() {
return "This is Local class; x:" + x;
}
如果局部类声明为final
,则可以访问它们。
Java 匿名类
匿名类是没有名称的本地类。 它们使我们能够同时声明和实例化一个类。 如果我们只想使用匿名类,则可以使用匿名类。 匿名类在单个表达式中定义和实例化。 当事件处理代码仅由一个组件使用,因此不需要命名引用时,也可以使用匿名内部类。
匿名类必须实现接口或从类继承。 但是不使用implements
和extends
关键字。 如果new
关键字后面的名称是类的名称,则匿名类是命名类的子类。 如果在new
之后的名称指定了接口,则匿名类将实现该接口并扩展Object
。
由于匿名类没有名称,因此无法为匿名类定义构造器。 在匿名类的主体内部,我们无法定义任何语句; 仅方法或成员。
AnonymousClass.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();
}
}
在此代码示例中,我们创建一个匿名类。
interface Message {
public void send();
}
匿名类必须是子类或必须实现接口。 我们的匿名类将实现Message
接口。 否则,编译器将无法识别类型。
public void createMessage() {
Message msg = new Message() {
@Override
public void send() {
System.out.println("This is a message");
}
};
msg.send();
}
匿名类是本地类,因此它是在方法主体中定义的。 表达式中定义了一个匿名类。 因此,右括号后面是一个分号。
在 Java 教程的这一部分中,我们继续介绍 Java 中的面向对象编程。