💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# Java 面向对象的编程 原文:http://zetcode.com/lang/java/oop/ Java 教程的这一部分是 Java 面向对象编程的简介。 我们提到了 Java 对象,对象属性和方法,对象构造器以及访问修饰符。 此外,我们讨论了`super`关键字,构造器链接,类常量,继承,最终类和私有构造器。 共有三种广泛使用的编程示例:过程编程,函数编程和面向对象的编程。 Java 原则上是一种面向对象的编程语言。 从 Java8 开始,它对函数式编程提供了一些支持。 ## 面向对象编程 面向对象编程(OOP)是一种使用对象及其相互作用设计应用和计算机程序的编程示例。 以下是 OOP 中的基本编程概念: * 抽象 * 多态 * 封装 * 继承 抽象通过建模适合该问题的类来简化复杂的现实。 多态是将运算符或函数以不同方式用于不同数据输入的过程。 封装对其他对象隐藏了类的实现细节。 继承是一种使用已经定义的类形成新类的方法。 ## Java 对象 对象是 Java OOP 程序的基本构建块。 对象是数据和方法的组合。 在 OOP 程序中,我们创建对象。 这些对象通过方法进行通信。 每个对象都可以接收消息,发送消息和处理数据。 创建对象有两个步骤。 首先,我们定义一个类。 类是对象的模板。 它是一个蓝图,描述了类对象共享的状态和行为。 一个类可以用来创建许多对象。 在运行时从类创建的对象称为该特定类的实例。 `SimpleObject.java` ```java package com.zetcode; class Being {} public class SimpleObject { public static void main(String[] args) { Being b = new Being(); System.out.println(b); } } ``` 在第一个示例中,我们创建一个简单的对象。 ```java class Being {} ``` 这是一个简单的类定义。 模板的主体为空。 它没有任何数据或方法。 ```java Being b = new Being(); ``` 我们创建`Being`类的新实例。 为此,我们使用了`new`关键字。 `b`变量是创建对象的句柄。 ```java System.out.println(b); ``` 我们将对象打印到控制台以获取该对象的一些基本描述。 打印对象是什么意思? 实际上,当我们打印对象时,我们将其称为`toString()`方法。 但是我们还没有定义任何方法。 这是因为创建的每个对象都继承自基本`Object`。 它具有一些基本功能,可以在所有创建的对象之间共享。 其中之一是`toString()`方法。 ```java $ javac com/zetcode/SimpleObject.java $ ls com/zetcode/ Being.class SimpleObject.class SimpleObject.java ``` 编译器创建两个类文件。 `SimpleObject.class`是应用类,`Being.class`是我们在应用中使用的自定义类。 ```java $ java com.zetcode.SimpleObject com.zetcode.Being@125ee71 ``` 我们获得对象是实例的类的名称,@字符以及对象的哈希码的无符号十六进制表示形式。 ## Java 对象属性 对象属性是捆绑在类实例中的数据。 对象属性称为实例变量或成员字段。 实例变量是在类中定义的变量,该类中的每个对象都有一个单独的副本。 `ObjectAttributes.java` ```java package com.zetcode; class Person { public String name; } public class ObjectAttributes { public static void main(String[] args) { Person p1 = new Person(); p1.name = "Jane"; Person p2 = new Person(); p2.name = "Beky"; System.out.println(p1.name); System.out.println(p2.name); } } ``` 在上面的 Java 代码中,我们有一个带有一个成员字段的`Person`类。 ```java class Person { public String name; } ``` 我们声明一个名称成员字段。 `public`关键字指定可以在类块之外访问成员字段。 ```java Person p1 = new Person(); p1.name = "Jane"; ``` 我们创建`Person`类的实例,并将名称变量设置为`"Jane"`。 我们使用点运算符来访问对象的属性。 ```java Person p2 = new Person(); p2.name = "Beky"; ``` 我们创建`Person`类的另一个实例。 在这里,我们将变量设置为`"Beky"`。 ```java System.out.println(p1.name); System.out.println(p2.name); ``` 我们将变量的内容打印到控制台。 ```java $ java com.zetcode.ObjectAttributes Jane Beky ``` 我们看到了程序的输出。 `Person`类的每个实例都有一个单独的名称成员字段副本。 ## Java 方法 方法是在类主体内定义的函数。 它们用于通过对象的属性执行操作。 方法将模块化带入我们的程序。 在 OOP 范式的封装概念中,方法至关重要。 例如,我们的`AccessDatabase`类中可能有一个`connect()`方法。 我们无需知道方法`connect()`如何精确地连接到数据库。 我们只需要知道它用于连接数据库。 这对于划分编程中的职责至关重要,尤其是在大型应用中。 对象组的状态和行为。 方法代表对象的行为部分。 `Methods.java` ```java package com.zetcode; class Circle { private int radius; public void setRadius(int radius) { this.radius = radius; } public double area() { return this.radius * this.radius * Math.PI; } } public class Methods { public static void main(String[] args) { Circle c = new Circle(); c.setRadius(5); System.out.println(c.area()); } } ``` 在代码示例中,我们有一个`Circle`类。 在该类中,我们定义了两个方法。 `setRadius()`方法为`radius`成员分配一个值,`area()`方法根据类成员和常数计算圆的面积。 ```java private int radius; ``` 我们的类只有一个成员字段。 它是圆的半径。 `private`关键字是访问说明符。 它表明变量仅限于外部世界。 如果要从外部修改此变量,则必须使用公共可用的`setRadius()`方法。 这样我们可以保护我们的数据。 ```java public void setRadius(int radius) { this.radius = radius; } ``` 这是`setRadius()`方法。 `this`变量是一个特殊变量,我们用它来访问方法中的成员字段。 `this.radius`是实例变量,而`radius`是局部变量,仅在`setRadius()`方法内部有效。 ```java Circle c = new Circle(); c.setRadius(5); ``` 我们创建`Circle`类的实例,并通过在圆对象上调用`setRadius()`方法来设置其半径。 点运算符用于调用该方法。 ```java public double area() { return this.radius * this.radius * Math.PI; } ``` `area()`方法返回圆的面积。 `Math.PI`是内置常数。 ```java $ java com.zetcode.Methods 78.53981633974483 ``` 运行示例,我们得到上面的输出。 ## Java 访问修饰符 访问修饰符设置方法和成员字段的可见性。 Java 具有三个访问修饰符:`public`,`protected`和`private`。 可以从任何地方访问`public`成员。 `protected`成员只能在类本身内,被继承的类以及同一包中的其他类访问。 最后,`private`成员仅限于包含类型,例如仅在其类或接口内。 如果不指定访问修饰符,则将具有包专用的可见性。 在这种情况下,成员和方法可在同一包中访问。 访问修饰符可防止意外修改数据。 它们使程序更强大。 | | 类 | 包 | 子类(相同的包) | 子类(其他包) | 全局 | | --- | --- | --- | --- | --- | --- | | `public` | `+` | `+` | `+` | `+` | `+` | | `protected` | `+` | `+` | `+` | `+` | `o` | | 没有修饰符 | `+` | `+` | `+` | `o` | `o` | | `private` | `+` | `o` | `o` | `o` | `o` | 上表总结了 Java 访问修饰符(`+`是可访问的,`o`是不可访问的)。 `AccessModifiers.java` ```java package com.zetcode; class Person { public String name; private int age; public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } } public class AccessModifiers { public static void main(String[] args) { Person p = new Person(); p.name = "Jane"; p.setAge(17); System.out.println(String.format("%s is %d years old", p.name, p.getAge())); } } ``` 在上面的程序中,我们有两个成员字段:`public`和`private`。 ```java public int getAge() { return this.age; } ``` 如果成员字段是私有的,则访问它的唯一方法是通过方法。 如果要在类外部修改属性,则必须将方法声明为`public`。 这是数据保护的重要方面。 ```java public void setAge(int age) { this.age = age; } ``` `setAge()`方法使我们能够从类定义之外更改私有`age`变量。 ```java Person p = new Person(); p.name = "Jane"; ``` 我们创建`Person`类的新实例。 因为`name`属性是`public`,所以我们可以直接访问它。 但是,不建议这样做。 ```java p.setAge(17); ``` `setAge()`方法修改`age`成员字段。 由于已声明`private`,因此无法直接访问或修改。 ```java System.out.println(String.format("%s is %d years old", p.name, p.getAge())); ``` 最后,我们访问两个成员以构建一个字符串,该字符串将打印到控制台。 ```java $ java com.zetcode.AccessModifiers Jane is 17 years old ``` 运行示例,我们将获得以下输出。 以下程序显示访问修饰符如何影响子类继承成员的方式。 `ProtectedMember.java` ```java package com.zetcode; class Base { public String name = "Base"; protected int id = 5323; private boolean isDefined = true; } class Derived extends Base { public void info() { System.out.println("This is Derived class"); System.out.println("Members inherited:"); System.out.println(this.name); System.out.println(this.id); // System.out.println(this.isDefined); } } public class ProtectedMember { public static void main(String[] args) { Derived drv = new Derived(); drv.info(); } } ``` 在此程序中,我们有一个`Derived`类,该类继承自`Base`类。 `Base`类具有三个成员字段,所有成员字段均具有不同的访问修饰符。 `isDefined`成员未继承。 `private`修饰符可以防止这种情况。 ```java class Derived extends Base { ``` `Derived`类继承自`Base`类。 要从另一个类继承,我们使用`extends`关键字。 ```java System.out.println(this.name); System.out.println(this.id); // System.out.println(this.isDefined); ``` `public`和`protected`成员由`Derived`类继承。 可以访问它们。 `private`成员未继承。 访问成员字段的行被注释。 如果我们取消注释该行,则代码将无法编译。 ```java $ java com.zetcode.ProtectedMember This is Derived class Members inherited: Base 5323 ``` 运行程序,我们收到此输出。 ## Java 构造器 构造器是一种特殊的方法。 创建对象时会自动调用它。 构造器不返回值,也不使用`void`关键字。 构造器的目的是初始化对象的状态。 构造器与类具有相同的名称。 构造器是方法,因此它们也可以重载。 构造器不能直接调用。 `new`关键字调用它们。 构造器不能声明为同步,最终,抽象,本地或静态。 构造器不能被继承。 它们按继承顺序被调用。 如果我们不为类编写任何构造器,则 Java 提供隐式默认构造器。 如果提供任何类型的构造器,则不提供默认值。 `Constructor.java` ```java package com.zetcode; class Being { public Being() { System.out.println("Being is created"); } public Being(String being) { System.out.println(String.format("Being %s is created", being)); } } public class Constructor { @SuppressWarnings("ResultOfObjectAllocationIgnored") public static void main(String[] args) { new Being(); new Being("Tom"); } } ``` 我们有一个存在类。 此类具有两个构造器。 第一个不带参数,第二个不带参数。 ```java public Being() { System.out.println("Being is created"); } ``` 该构造器不接受任何参数。 ```java public Being(String being) { System.out.println(String.format("Being %s is created", being)); } ``` 此构造器采用一个字符串参数。 ```java @SuppressWarnings("ResultOfObjectAllocationIgnored") ``` 此注释将禁止警告我们不要将创建的对象分配给任何变量。 通常,这将是可疑的活动。 ```java new Being(); ``` 创建`Being`类的实例。 创建对象时将调用无参数构造器。 ```java new Being("Tom"); ``` 创建`Being`类的另一个实例。 这次,在创建对象时调用带有参数的构造器。 ```java $ java com.zetcode.Constructor Being is created Being Tom is created ``` 这是程序的输出。 在下一个示例中,我们初始化类的数据成员。 变量的初始化是构造器的典型工作。 `MemberInit.java` ```java package com.zetcode; import java.util.Calendar; import java.util.GregorianCalendar; class MyFriend { private GregorianCalendar born; private String name; public MyFriend(String name, GregorianCalendar born) { this.name = name; this.born = born; } public void info() { System.out.format("%s was born on %s/%s/%s\n", this.name, this.born.get(Calendar.DATE), this.born.get(Calendar.MONTH), this.born.get(Calendar.YEAR)); } } public class MemberInit { public static void main(String[] args) { String name = "Lenka"; GregorianCalendar born = new GregorianCalendar(1990, 3, 5); MyFriend fr = new MyFriend(name, born); fr.info(); } } ``` 我们有一个带有数据成员和方法的`MyFriend`类。 ```java private GregorianCalendar born; private String name; ``` 类定义中有两个私有变量。 ```java public MyFriend(String name, GregorianCalendar born) { this.name = name; this.born = born; } ``` 在构造器中,我们启动两个数据成员。 `this`变量是一个处理器,用于从方法中引用对象变量。 如果构造器参数的名称与成员的名称相等,则需要使用`this`关键字。 否则,用法是可选的。 ```java MyFriend fr = new MyFriend(name, born); fr.info(); ``` 我们创建带有两个参数的`MyFriend`对象。 然后我们调用对象的`info()`方法。 ```java $ java com.zetcode.MemberInit Lenka was born on 5/3/1990 ``` 这是`com.zetcode.MemberInit`程序的输出。 ## Java `super`关键字 `super`关键字是在子类中用于引用直接父类对象的引用变量。 它可以用来引用父对象的 a)实例变量,b)构造器,c)方法。 `SuperVariable.java` ```java package com.zetcode; class Shape { int x = 50; int y = 50; } class Rectangle extends Shape { int x = 100; int y = 100; public void info() { System.out.println(x); System.out.println(super.x); } } public class SuperVariable { public static void main(String[] args) { Rectangle r = new Rectangle(); r.info(); } } ``` 在示例中,我们使用`super`关键字引用了父变量。 ```java public void info() { System.out.println(x); System.out.println(super.x); } ``` 在`info()`方法内部,我们使用`super.x`语法引用父级的实例变量。 如果构造器未显式调用超类构造器,则 Java 将自动插入对超类的无参数构造器的调用。 如果超类没有无参数构造器,则会得到编译时错误。 `ImplicitSuper.java` ```java package com.zetcode; class Vehicle { public Vehicle() { System.out.println("Vehicle created"); } } class Bike extends Vehicle { public Bike() { // super(); System.out.println("Bike created"); } } public class ImplicitSuper { public static void main(String[] args) { Bike bike = new Bike(); System.out.println(bike); } } ``` 该示例演示了对父级构造器的隐式调用。 ```java public Bike() { // super(); System.out.println("Bike created"); } ``` 如果我们取消注释该行,则会得到相同的结果。 ```java $ java com.zetcode.ImplicitSuper Vehicle created Bike created com.zetcode.Bike@15db9742 ``` 创建`Bike`对象时,将调用两个构造器。 一个类中可以有多个构造器。 `SuperCalls.java` ```java package com.zetcode; class Vehicle { protected double price; public Vehicle() { System.out.println("Vehicle created"); } public Vehicle(double price) { this.price = price; System.out.printf("Vehicle created, price %.2f set%n", price); } } class Bike extends Vehicle { public Bike() { super(); System.out.println("Bike created"); } public Bike(double price) { super(price); System.out.printf("Bike created, its price is: %.2f %n", price); } } public class SuperCalls { public static void main(String[] args) { Bike bike1 = new Bike(); Bike bike2 = new Bike(45.90); } } ``` 该示例使用`super`的不同语法来调用不同的父构造器。 ```java super(); ``` 在这里,我们称为父级的无参数构造器。 ```java super(price); ``` 此语法调用具有一个参数的父级构造器:自行车的价格。 ```java $ java com.zetcode.SuperCalls Vehicle created Bike created Vehicle created, price 45.90 set Bike created, its price is: 45.90 ``` 这是示例输出。 ## Java 构造器链接 构造器链接是从构造器调用另一个构造器的能力。 要从同一类调用另一个构造器,我们使用`this`关键字。 要从父类中调用另一个构造器,我们使用`super`关键字。 `ConstructorChaining.java` ```java package com.zetcode; class Shape { private int x; private int y; public Shape(int x, int y) { this.x = x; this.y = y; } protected int getX() { return this.x; } protected int getY() { return this.y; } } class Circle extends Shape { private int r; public Circle(int r, int x, int y) { super(x, y); this.r = r; } public Circle() { this(1, 1, 1); } @Override public String toString() { return String.format("Circle: r:%d, x:%d, y:%d", r, getX(), getY()); } } public class ConstructorChaining { public static void main(String[] args) { Circle c1 = new Circle(5, 10, 10); Circle c2 = new Circle(); System.out.println(c1); System.out.println(c2); } } ``` 我们有一个`Circle`类。 该类具有两个构造器。 一种采用一个参数,一种不采用任何参数。 ```java class Shape { private int x; private int y; ... } ``` `Shape`类负责处理各种形状的`x`和`y`坐标。 ```java public Shape(int x, int y) { this.x = x; this.y = y; } ``` `Shape`类的构造器使用给定的参数启动`x`和`y`坐标。 ```java protected int getX() { return this.x; } protected int getY() { return this.y; } ``` 我们定义了两种方法来检索坐标值。 成员是私有的,因此唯一可能的访问是通过方法。 ```java class Circle extends Shape { private int r; ... } ``` `Circle`类继承自`Shape`类。 它定义了特定于此形状的`radius`成员。 ```java public Circle(int r, int x, int y) { super(x, y); this.r = r; } ``` `Circle`类的第一个构造器采用三个参数:`radius`以及`x`和`y`坐标。 使用`super`关键字,我们调用传递坐标的父级构造器。 请注意,`super`关键字必须是构造器中的第一条语句。 第二条语句启动`Circle`类的`radius`成员。 ```java public Circle() { this(1, 1, 1); } ``` 第二个构造器不带参数。 在这种情况下,我们提供一些默认值。 `this`关键字用于调用同一类的三参数构造器,并传递三个默认值。 ```java @Override public String toString() { return String.format("Circle: r:%d, x:%d, y:%d", r, getX(), getY()); } ``` 在`toString()`方法内部,我们提供`Circle`类的字符串表示形式。 要确定`x`和`y`坐标,我们使用继承的`getX()`和`getY()`方法。 ```java $ java com.zetcode.ConstructorChaining Circle: r:5, x:10, y:10 Circle: r:1, x:1, y:1 ``` 这是示例的输出。 ## Java 类常量 可以创建类常量。 这些常量不属于具体对象。 他们属于类。 按照约定,常量用大写字母表示。 `ClassConstant.java` ```java package com.zetcode; class Math { public static final double PI = 3.14159265359; } public class ClassConstant { public static void main(String[] args) { System.out.println(Math.PI); } } ``` 我们有一个带有`PI`常量的`Math`类。 ```java public static final double PI = 3.14159265359; ``` `final`关键字用于定义常数。 使用`static`关键字可以引用成员而无需创建类的实例。 `public`关键字使它可以在类的主体之外访问。 ```java $ java com.zetcode.ClassConstant 3.14159265359 ``` Running the example we get the above output. ## Java `toString`方法 每个对象都有`toString()`方法。 它返回人类可读的对象表示形式。 默认实现返回`Object`类型的标准名称。 当我们以对象作为参数调用`System.out.println()`方法时,将调用`toString()`。 `ThetoStringMethod.java` ```java package com.zetcode; class Being { @Override public String toString() { return "This is Being class"; } } public class ThetoStringMethod { public static void main(String[] args) { Being b = new Being(); Object o = new Object(); System.out.println(o.toString()); System.out.println(b.toString()); System.out.println(b); } } ``` 我们有一个`Being`类,其中我们重写了`toString()`方法的默认实现。 ```java @Override public String toString() { return "This is Being class"; } ``` 创建的每个类都从基`Object`继承。 `toString()`方法属于此对象类。 `@Override`注解通知编译器该元素旨在替代超类中声明的元素。 然后,编译器将检查我们是否未创建任何错误。 ```java Being b = new Being(); Object o = new Object(); ``` 我们创建两个对象:一个自定义对象和一个内置对象。 ```java System.out.println(o.toString()); System.out.println(b.toString()); ``` 我们在这两个对象上显式调用`toString()`方法。 ```java System.out.println(b); ``` 正如我们之前指定的,将对象作为`System.out.println()`的参数将调用其`toString()`方法。 这次,我们隐式调用了该方法。 ```java $ java com.zetcode.ThetoStringMethod java.lang.Object@125ee71 This is Being class This is Being class ``` 这是我们运行示例时得到的。 ## Java 中的继承 继承是一种使用已经定义的类形成新类的方法。 新形成的类称为派生的类,我们从中衍生的类称为基类。 继承的重要好处是代码重用和降低程序的复杂性。 派生类(后代)将覆盖或扩展基类(祖先)的功能。 `Inheritance.java` ```java package com.zetcode; class Being { public Being() { System.out.println("Being is created"); } } class Human extends Being { public Human() { System.out.println("Human is created"); } } public class Inheritance { @SuppressWarnings("ResultOfObjectAllocationIgnored") public static void main(String[] args) { new Human(); } } ``` 在此程序中,我们有两个类:基础`Being`类和派生的`Human`类。 派生类继承自基类。 ```java class Human extends Being { ``` 在 Java 中,我们使用`extends`关键字创建继承关系。 ```java new Human(); ``` 我们实例化派生的`Human`类。 ```java $ java com.zetcode.Inheritance Being is created Human is created ``` 我们可以看到两个构造器都被调用了。 首先,调用基类的构造器,然后调用派生类的构造器。 接下来是一个更复杂的示例。 `Inheritance2.java` ```java package com.zetcode; class Being { static int count = 0; public Being() { count++; System.out.println("Being is created"); } public void getCount() { System.out.format("There are %d Beings%n", count); } } class Human extends Being { public Human() { System.out.println("Human is created"); } } class Animal extends Being { public Animal() { System.out.println("Animal is created"); } } class Dog extends Animal { public Dog() { System.out.println("Dog is created"); } } public class Inheritance2 { @SuppressWarnings("ResultOfObjectAllocationIgnored") public static void main(String[] args) { new Human(); Dog dog = new Dog(); dog.getCount(); } } ``` 对于四个类,继承层次结构更加复杂。 `Human`和`Animal`类继承自`Being`类,`Dog`类直接继承自`Animal`类,间接继承自`Being`类。 ```java static int count = 0; ``` 我们定义一个`static`变量。 静态成员由类的所有实例共享。 ```java public Being() { count++; System.out.println("Being is created"); } ``` 每次实例化`Being`类时,我们将`count`变量增加一。 这样,我们就可以跟踪创建的实例数。 ```java class Animal extends Being { ... class Dog extends Animal { ... ``` `Animal`继承自`Being`,`Dog`继承自`Animal`。 `Dog`也间接继承自`Being`。 ```java new Human(); Dog dog = new Dog(); dog.getCount(); ``` 我们从`Human`和`Dog`类创建实例。 我们称为`Dog`对象的`getCount()`方法。 ```java $ java com.zetcode.Inheritance2 Being is created Human is created Being is created Animal is created Dog is created There are 2 Beings ``` `Human`对象调用两个构造器。 `Dog`对象调用三个构造器。 有两个实例化的`Beings`。 ## `final`类,`private`构造器 带有`final`修饰符的类不能被子类化。 带有带有`private`修饰符的构造器的类无法实例化。 `FinalClass.java` ```java package com.zetcode; final class MyMath { public static final double PI = 3.14159265358979323846; // other static members and methods } public class FinalClass { public static void main(String[] args) { System.out.println(MyMath.PI); } } ``` 我们有一个`MyMath`类。 此类具有一些静态成员和方法。 我们不希望任何人从我们的类继承; 因此,我们将其声明为`final`。 此外,我们也不想允许从我们的类中创建实例。 我们决定仅在静态上下文中使用它。 声明一个私有构造器,该类无法实例化。 `MyMath.java` ```java package com.zetcode; final class MyMath { private MyMath() {} public static final double PI = 3.14159265358979323846; // other static members and methods } public class PrivateConstructor { public static void main(String[] args) { System.out.println(MyMath.PI); } } ``` 我们的`MyMath`类无法实例化,也不能被子类化。 这就是`java.lang.Math`用 Java 语言设计的方式。 这是 Java 中 OOP 描述的第一部分。