Java 中文官方教程 2022 版(三)(1)https://developer.aliyun.com/article/1486281
你可以使用Bicycle
构造函数来设置id
实例变量并递增numberOfBicycles
类变量:
public class Bicycle { private int cadence; private int gear; private int speed; private int id; private static int numberOfBicycles = 0; public Bicycle(int startCadence, int startSpeed, int startGear){ gear = startGear; cadence = startCadence; speed = startSpeed; // increment number of Bicycles // and assign ID number id = ++numberOfBicycles; } // new method to return the ID instance variable public int getID() { return id; } ... }
类方法
Java 编程语言支持静态方法以及静态变量。具有static
修饰符的静态方法应该使用类名调用,而无需创建类的实例,如
ClassName.methodName(args)
注意: 你也可以通过对象引用来引用静态方法
instanceName.methodName(args)
但这是不被推荐的,因为它没有清楚地表明它们是类方法。
静态方法的一个常见用途是访问静态字段。例如,我们可以向Bicycle
类添加一个静态方法来访问numberOfBicycles
静态字段:
public static int getNumberOfBicycles() { return numberOfBicycles; }
并非所有实例和类变量和方法的组合都被允许:
- 实例方法可以直接访问实例变量和实例方法。
- 实例方法可以直接访问类变量和类方法。
- 类方法可以直接访问类变量和类方法。
- 类方法不能直接访问实例变量或实例方法—它们必须使用对象引用。此外,类方法不能使用
this
关键字,因为this
没有实例可供参考。
常量
static
修饰符与final
修饰符结合使用,也用于定义常量。final
修饰符表示此字段的值不能更改。
例如,以下变量声明定义了一个名为PI
的常量,其值是圆周率的近似值(圆的周长与直径的比值):
static final double PI = 3.141592653589793;
以这种方式定义的常量不能被重新分配,如果程序尝试这样做,将在编译时出现错误。按照惯例,常量值的名称以大写字母拼写。如果名称由多个单词组成,则单词之间用下划线(_)分隔。
**注意:**如果原始类型或字符串被定义为常量,并且在编译时已知其值,则编译器会在代码中的所有位置用其值替换常量名称。这被称为编译时常量。如果常量在外部世界中的值发生变化(例如,如果立法规定 pi 实际上应该是 3.975),则需要重新编译使用此常量的任何类以获取当前值。
Bicycle
类
在本节中进行的所有修改后,Bicycle
类现在是:
public class Bicycle { private int cadence; private int gear; private int speed; private int id; private static int numberOfBicycles = 0; public Bicycle(int startCadence, int startSpeed, int startGear) { gear = startGear; cadence = startCadence; speed = startSpeed; id = ++numberOfBicycles; } public int getID() { return id; } public static int getNumberOfBicycles() { return numberOfBicycles; } public int getCadence() { return cadence; } public void setCadence(int newValue) { cadence = newValue; } public int getGear(){ return gear; } public void setGear(int newValue) { gear = newValue; } public int getSpeed() { return speed; } public void applyBrake(int decrement) { speed -= decrement; } public void speedUp(int increment) { speed += increment; } }
初始化字段
正如你所见,你通常可以在声明中为字段提供初始值:
public class BedAndBreakfast { // initialize to 10 public static int capacity = 10; // initialize to false private boolean full = false; }
当初始化值可用且初始化可以放在一行时,这种方式效果很好。然而,这种初始化方式由于其简单性而有一些限制。如果初始化需要一些逻辑(例如,错误处理或使用for
循环填充复杂数组),简单赋值是不够的。实例变量可以在构造函数中初始化,可以在那里使用错误处理或其他逻辑。为了为类变量提供相同的功能,Java 编程语言包括静态初始化块。
**注意:**在类定义的开头声明字段并不是必需的,尽管这是最常见的做法。只需要在使用之前声明和初始化它们即可。
静态初始化块
静态初始化块是一个普通的代码块,用大括号{ }
括起来,并在static
关键字之前。这里是一个示例:
static { // whatever code is needed for initialization goes here }
一个类可以有任意数量的静态初始化块,并且它们可以出现在类体的任何位置。运行时系统保证静态初始化块按照它们在源代码中出现的顺序调用。
还有一种替代静态块的方法 — 你可以编写一个私有静态方法:
class Whatever { public static varType myVar = initializeClassVariable(); private static varType initializeClassVariable() { // initialization code goes here } }
私有静态方法的优势在于,如果需要重新初始化类变量,它们可以在以后被重用。
初始化实例成员
通常,你会将代码放在构造函数中初始化实例变量。有两种替代方法可以用来初始化实例变量:初始化块和 final 方法。
实例变量的初始化块看起来就像静态初始化块,但没有static
关键字:
{ // whatever code is needed for initialization goes here }
Java 编译器将初始化块复制到每个构造函数中。因此,这种方法可以用于在多个构造函数之间共享一段代码。
final 方法不能在子类中被重写。这在接口和继承的课程中有讨论。这里是使用 final 方法初始化实例变量的示例:
class Whatever { private varType myVar = initializeInstanceVariable(); protected final varType initializeInstanceVariable() { // initialization code goes here } }
如果子类可能希望重用初始化方法,则这是特别有用的。该方法是 final 的,因为在实例初始化期间调用非 final 方法可能会导致问题。
创建和使用类和对象摘要
原文:
docs.oracle.com/javase/tutorial/java/javaOO/summaryclasses.html
类声明命名类并在大括号之间封装类体。类名可以由修饰符前置。类体包含类的字段、方法和构造函数。类使用字段来包含状态信息,并使用方法来实现行为。初始化类的新实例的构造函数使用类的名称,并且看起来像没有返回类型的方法。
您可以通过在声明中使用访问修饰符(如public
)来以相同的方式控制对类和成员的访问。
通过在成员声明中使用static
关键字来指定类变量或类方法。未声明为static
的成员隐式地是实例成员。类变量由类的所有实例共享,并且可以通过类名以及实例引用访问。类的实例会获得每个实例变量的自己的副本,必须通过实例引用访问。
通过使用new
运算符和构造函数,您可以从类创建对象。new
运算符返回一个对创建的对象的引用。您可以将引用分配给变量或直接使用它。
可以通过使用限定名称来引用在声明它们的类之外的代码可访问的实例变量和方法。实例变量的限定名称如下所示:
*objectReference.variableName*
方法的限定名称如下所示:
*objectReference.methodName(argumentList)*
或:
*objectReference.methodName()*
垃圾收集器会自动清理未使用的对象。如果程序不再持有对对象的引用,则该对象将被视为未使用。您可以通过将持有引用的变量设置为null
来显式丢弃引用。
问题和练习:类
原文:
docs.oracle.com/javase/tutorial/java/javaOO/QandE/creating-questions.html
问题
- 考虑以下类:
public class IdentifyMyParts { public static int x = 7; public int y = 3; }
- 类变量是什么?
- 实例变量是什么?
- 以下代码的输出是什么:
IdentifyMyParts a = new IdentifyMyParts(); IdentifyMyParts b = new IdentifyMyParts(); a.y = 5; b.y = 6; a.x = 1; b.x = 2; System.out.println("a.y = " + a.y); System.out.println("b.y = " + b.y); System.out.println("a.x = " + a.x); System.out.println("b.x = " + b.x); System.out.println("IdentifyMyParts.x = " + IdentifyMyParts.x);
练习
- 编写一个类,其实例代表一副扑克牌中的一张牌。扑克牌有两个独特的属性:等级和花色。确保保留你的解决方案,因为你将被要求在枚举类型中重新编写它。
- 提示:
你可以使用assert
语句来检查你的赋值。你可以写:
assert (boolean expression to test);
- 如果布尔表达式为假,你将收到一个错误消息。例如,
assert toString(ACE) == "Ace";
- 应该返回
true
,这样就不会有错误消息。
如果你使用assert
语句,你必须用ea
标志运行你的程序:
java -ea YourProgram.class
- 编写一个类,其实例代表一副完整的扑克牌。你也应该保留这个解决方案。
- 3. 编写一个小程序来测试你的牌组和卡片类。这个程序可以简单到创建一副牌并显示其卡片。
检查你的答案。
问题和练习:对象
原文:
docs.oracle.com/javase/tutorial/java/javaOO/QandE/objects-questions.html
问题
- 以下程序有什么问题?
public class SomethingIsWrong { public static void main(String[] args) { Rectangle myRect; myRect.width = 40; myRect.height = 50; System.out.println("myRect's area is " + myRect.area()); } }
- 以下代码创建了一个数组和一个字符串对象。在代码执行后,这些对象有多少个引用?这两个对象是否有资格进行垃圾回收?
... String[] students = new String[10]; String studentName = "Peter Parker"; students[0] = studentName; studentName = null; ...
- 程序如何销毁它创建的对象?
练习
- 修复问题 1 中显示的名为
SomethingIsWrong
的程序。 - 给定以下名为
NumberHolder
的类,编写一些代码来创建该类的一个实例,初始化其两个成员变量,然后显示每个成员变量的值。
public class NumberHolder { public int anInt; public float aFloat; }
检查你的答案。
嵌套类
Java 编程语言允许您在另一个类中定义一个类。这样的类称为嵌套类,如下所示:
class OuterClass { ... class NestedClass { ... } }
术语: 嵌套类分为两类:非静态和静态。非静态嵌套类称为内部类。声明为static
的嵌套类称为静态嵌套类。
class OuterClass { ... class InnerClass { ... } static class StaticNestedClass { ... } }
嵌套类是其封闭类的成员。非静态嵌套类(内部类)可以访问封闭类的其他成员,即使它们被声明为 private。静态嵌套类无法访问封闭类的其他成员。作为OuterClass
的成员,嵌套类可以声明为private
、public
、protected
或包私有。(请记住,外部类只能声明为public
或包私有。)
为什么使用嵌套类?
使用嵌套类的引人注目的原因包括以下内容:
- 它是一种逻辑上将仅在一个地方使用的类分组的方法:如果一个类仅对另一个类有用,则将其嵌入该类并将两者保持在一起是合乎逻辑的。嵌套这样的“辅助类”使其包更加简洁。
- 它增加了封装性:考虑两个顶层类 A 和 B,其中 B 需要访问 A 的成员,否则这些成员将被声明为
private
。通过将类 B 隐藏在类 A 中,A 的成员可以被声明为 private,并且 B 可以访问它们。此外,B 本身可以对外部世界隐藏。 - 它可以导致更易读和易维护的代码:将小类嵌套在顶层类中可以使代码更接近其使用位置。
内部类
与实例方法和变量一样,内部类与其封闭类的实例相关联,并且可以直接访问该对象的方法和字段。此外,因为内部类与实例相关联,它本身不能定义任何静态成员。
作为内部类的实例存在于外部类的实例内部。考虑以下类:
class OuterClass { ... class InnerClass { ... } }
InnerClass
的实例只能存在于OuterClass
的实例中,并且可以直接访问其封闭实例的方法和字段。
要实例化内部类,必须首先实例化外部类。然后,使用以下语法在外部对象中创建内部对象:
OuterClass outerObject = new OuterClass(); OuterClass.InnerClass innerObject = outerObject.new InnerClass();
有两种特殊类型的内部类:局部类和匿名类。
静态嵌套类
与类方法和变量一样,静态嵌套类与其外部类相关联。并且像静态类方法一样,静态嵌套类不能直接引用其封闭类中定义的实例变量或方法:它只能通过对象引用使用它们。内部类和嵌套静态类示例演示了这一点。
注意: 静态嵌套类与其外部类(以及其他类)的实例成员交互方式与任何其他顶层类相同。实际上,静态嵌套类在行为上就像是为了包装方便而嵌套在另一个顶层类中的顶层类。内部类和嵌套静态类示例也演示了这一点。
您可以像实例化顶层类一样实例化静态嵌套类:
StaticNestedClass staticNestedObject = new StaticNestedClass();
内部类和嵌套静态类示例
以下示例,OuterClass
,以及TopLevelClass
,演示了内部类(InnerClass
)、嵌套静态类(StaticNestedClass
)和顶层类(TopLevelClass
)可以访问OuterClass
的哪些类成员:
OuterClass.java
public class OuterClass { String outerField = "Outer field"; static String staticOuterField = "Static outer field"; class InnerClass { void accessMembers() { System.out.println(outerField); System.out.println(staticOuterField); } } static class StaticNestedClass { void accessMembers(OuterClass outer) { // Compiler error: Cannot make a static reference to the non-static // field outerField // System.out.println(outerField); System.out.println(outer.outerField); System.out.println(staticOuterField); } } public static void main(String[] args) { System.out.println("Inner class:"); System.out.println("------------"); OuterClass outerObject = new OuterClass(); OuterClass.InnerClass innerObject = outerObject.new InnerClass(); innerObject.accessMembers(); System.out.println("\nStatic nested class:"); System.out.println("--------------------"); StaticNestedClass staticNestedObject = new StaticNestedClass(); staticNestedObject.accessMembers(outerObject); System.out.println("\nTop-level class:"); System.out.println("--------------------"); TopLevelClass topLevelObject = new TopLevelClass(); topLevelObject.accessMembers(outerObject); } }
TopLevelClass.java
public class TopLevelClass { void accessMembers(OuterClass outer) { // Compiler error: Cannot make a static reference to the non-static // field OuterClass.outerField // System.out.println(OuterClass.outerField); System.out.println(outer.outerField); System.out.println(OuterClass.staticOuterField); } }
此示例打印以下输出:
Inner class: ------------ Outer field Static outer field Static nested class: -------------------- Outer field Static outer field Top-level class: -------------------- Outer field Static outer field
请注意,静态嵌套类与其外部类的实例成员交互方式与任何其他顶层类相同。静态嵌套类StaticNestedClass
无法直接访问outerField
,因为它是封闭类OuterClass
的实例变量。Java 编译器会在突出显示的语句处生成错误:
static class StaticNestedClass { void accessMembers(OuterClass outer) { // Compiler error: Cannot make a static reference to the non-static // field outerField System.out.println(outerField); } }
要修复此错误,请通过对象引用访问outerField
:
System.out.println(outer.outerField);
同样,顶层类TopLevelClass
也无法直接访问outerField
。
遮蔽
如果特定范围(如内部类或方法定义)中的类型声明(如成员变量或参数名)与封闭范围中的另一个声明具有相同的名称,则声明会遮蔽封闭范围的声明。您不能仅通过名称引用被遮蔽的声明。以下示例,ShadowTest
,演示了这一点:
public class ShadowTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { System.out.println("x = " + x); System.out.println("this.x = " + this.x); System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); } } public static void main(String... args) { ShadowTest st = new ShadowTest(); ShadowTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
以下是此示例的输出:
x = 23 this.x = 1 ShadowTest.this.x = 0
此示例定义了三个名为x
的变量:类ShadowTest
的成员变量,内部类FirstLevel
的成员变量以及方法methodInFirstLevel
中的参数。方法methodInFirstLevel
中定义的变量x
会遮蔽内部类FirstLevel
的变量。因此,当您在方法methodInFirstLevel
中使用变量x
时,它指的是方法参数。要引用内部类FirstLevel
的成员变量,请使用关键字this
表示封闭范围:
System.out.println("this.x = " + this.x);
通过类名引用封装更大范围的成员变量。例如,以下语句从方法methodInFirstLevel
中访问类ShadowTest
的成员变量:
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
序列化
内部类的序列化,包括局部和匿名类,是强烈不建议的。当 Java 编译器编译某些结构(如内部类)时,它会创建合成结构;这些是在源代码中没有对应构造的类、方法、字段和其他结构。合成结构使 Java 编译器能够实现新的 Java 语言特性,而无需更改 JVM。然而,合成结构在不同的 Java 编译器实现之间可能会有所不同,这意味着.class
文件在不同的实现之间也可能会有所不同。因此,如果您序列化一个内部类,然后在不同的 JRE 实现中反序列化它,可能会出现兼容性问题。有关在编译内部类时生成的合成结构的更多信息,请参见隐式和合成参数部分中的获取方法参数名称部分。
内部类示例
原文:
docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html
要查看内部类的使用,请首先考虑一个数组。在以下示例中,您创建一个数组,填充它的整数值,然后仅按升序输出数组的偶数索引值。
接下来的DataStructure.java
示例包括:
- 包含构造函数以创建包含连续整数值(0、1、2、3 等)的数组的实例的
DataStructure
外部类,并且包含一个打印具有偶数索引值的数组元素的方法。 EvenIterator
内部类,实现了DataStructureIterator
接口,该接口扩展了Iterator
<
Integer
>
接口。迭代器用于遍历数据结构,通常具有用于测试最后一个元素、检索当前元素和移动到下一个元素的方法。- 一个
main
方法,实例化一个DataStructure
对象(ds
),然后调用printEven
方法来打印具有偶数索引值的数组arrayOfInts
的元素。
public class DataStructure { // Create an array private final static int SIZE = 15; private int[] arrayOfInts = new int[SIZE]; public DataStructure() { // fill the array with ascending integer values for (int i = 0; i < SIZE; i++) { arrayOfInts[i] = i; } } public void printEven() { // Print out values of even indices of the array DataStructureIterator iterator = this.new EvenIterator(); while (iterator.hasNext()) { System.out.print(iterator.next() + " "); } System.out.println(); } interface DataStructureIterator extends java.util.Iterator<Integer> { } // Inner class implements the DataStructureIterator interface, // which extends the Iterator<Integer> interface private class EvenIterator implements DataStructureIterator { // Start stepping through the array from the beginning private int nextIndex = 0; public boolean hasNext() { // Check if the current element is the last in the array return (nextIndex <= SIZE - 1); } public Integer next() { // Record a value of an even index of the array Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]); // Get the next even element nextIndex += 2; return retValue; } } public static void main(String s[]) { // Fill the array with integer values and print out only // values of even indices DataStructure ds = new DataStructure(); ds.printEven(); } }
输出为:
0 2 4 6 8 10 12 14
请注意,EvenIterator
类直接引用了DataStructure
对象的arrayOfInts
实例变量。
您可以使用内部类来实现辅助类,例如在此示例中所示的类。要处理用户界面事件,您必须知道如何使用内部类,因为事件处理机制广泛使用它们。
Java 中文官方教程 2022 版(三)(3)https://developer.aliyun.com/article/1486283