模式切换
关键字
instanceof
instanceof
操作符用于检查一个对象是否是某个类的实例或其子类的实例。它在向下转型之前通常用于类型检查,以避免 ClassCastException
。
在下面的例子中,instanceof
操作符用于检查 myAnimal
是否是 Dog
类的实例,如果是,则进行向下转型。
java
Animal myAnimal = new Dog();
if (myAnimal instanceof Dog) {
Dog myDog = (Dog) myAnimal;
myDog.bark();
} else {
System.out.println("The object is not an instance of Dog.");
}
super
super
关键字在 Java 中用于访问父类的内容,主要包括:
- 访问父类的属性:当子类与父类有同名的属性时,可以使用
super
来访问父类的属性。javapublic class Parent { public int x = 10; } public class Child extends Parent { public int x = 20; public void printX() { System.out.println(super.x); // 访问父类的 x 属性 } }
- 调用父类的方法:当子类需要调用父类的方法时,可以使用
super
。javapublic class Parent { public void method() { // ... } } public class Child extends Parent { public void anotherMethod() { super.method(); // 调用父类的 method 方法 } }
- 在构造方法中调用父类的构造方法:使用
super(...)
可以在子类的构造方法中调用父类的构造方法。这是 Java 中创建对象时初始化父类部分的标准方式。javapublic class Parent { public Parent(int x) { // ... } } public class Child extends Parent { public Child(int x) { super(x); // 调用父类的构造器 } }
需要注意的是,super(...)
和 this(...)
都必须在构造方法的第一行使用,并且它们不能同时出现在一个构造方法中。
this
this
关键字在 Java 中主要用于以下情况:
- 引用当前对象的属性:当局部变量和类的成员变量同名时,可以使用
this
来区分。javapublic class MyClass { private int x; public MyClass(int x) { this.x = x; // 使用 this 来引用类的成员变量 } }
- 调用当前对象的方法:有时,你可能需要在方法内部调用同一个类的另一个方法。java
public class MyClass { public void method1() { // ... method2(); // 直接调用 this.method2(); // 使用 this 来调用,与直接调用效果相同 } public void method2() { // ... } }
- 在构造方法中调用另一个构造方法:从 Java 11 开始,你可以在构造器中使用
this(...)
来调用同一类中的另一个构造方法。注意,这种调用必须是构造方法中的第一条语句。javapublic class MyClass { public MyClass() { this(10); // 调用带有参数的构造器 } public MyClass(int x) { // ... } }
- 返回当前对象的引用:在某些情况下,你可能希望方法返回调用它的对象本身。java
public class MyClass { public MyClass someMethod() { // ... return this; // 返回当前对象的引用 } }
final
- 修饰变量
final
修饰基本数据类型的变量,表示该变量的值不能被修改。final
修饰引用类型的变量,表示该变量的引用不能被修改,但是对象的内容可以被修改。例如数组引用不变,但是数组元素可以改变。
javapublic class Main { public static void main(String[] args) { final int MY_CONSTANT = 10; // MY_CONSTANT = 20; // 错误:不能修改 final 变量 System.out.println(MY_CONSTANT); final int[] arr = {1, 2, 3}; // arr = new int[] {4, 5, 6}; // 错误:不能修改 final 引用 arr[0] = 4; // 可以修改数组元素 } }
- 修饰方法:
final
修饰的方法不能被子类重写。javaclass Parent { final void display() { System.out.println("This is a final method."); } } class Child extends Parent { // void display() { // 错误:不能重写 final 方法 // System.out.println("Trying to override final method."); // } }
- 修饰类:
final
修饰的类不能被继承。javafinal class FinalClass { void display() { System.out.println("This is a final class."); } } // class Child extends FinalClass { // 错误:不能继承 final 类 // }
static
static
关键字修饰的变量、常量和方法称为静态变量、静态常量和静态方法。
例如下面的场景中:在球类和圆类中都需要使用到 PI
常量,此时就不建议在两个类中分别定义,因为这样会导致虽然是同一个名称的常量,但是分配到了不同的内存空间里。为了解决这个问题,可以使用 static
关键字定义静态变量和静态常量。
调用的语法:
java
类名.静态变量名
类名.静态方法名
示例:
java
public class Constants {
public static final double PI = 3.14159;
}
public class Circle {
public double getArea(double radius) {
return Constants.PI * radius * radius;
}
}
public class Ball {
public double getVolume(double radius) {
return 4.0 / 3.0 * Constants.PI * radius * radius * radius;
}
}
图 PI 常量在内存中被共享的情况
需要注意的是,在静态方法中不能直接访问非静态成员,因为静态方法在类加载时就已经存在,而非静态成员是在对象创建时才会被初始化。并且在静态方法中不能使用 this
关键字。
例如下面的示例中,getArea()
方法是静态方法,不能直接访问 radius
成员变量:
java
public class Circle {
public static final double PI = 3.14159;
double radius;
public static double getArea() {
return Constants.PI * radius * radius;
}
}
如果需要在执行类时,希望先执行类的初始化动作,则可以使用 static
代码块。static
代码块在类加载时执行,只执行一次。
java
public class Circle {
static {
// 在这里编写初始化代码
}
}
transient
transient
关键字用于修饰类的字段,指示垃圾收集器在序列化对象时忽略这些字段。这意味着当对象被序列化(通常是为了保存到文件或通过网络传输)时,transient
修饰的字段不会被包含在序列化过程中。
这种机制对一些特定情况非常有用,例如:
- 敏感数据的保护:如果对象中包含敏感信息(如密码),而不希望在序列化时保存这些信息,可以使用
transient
关键字。 - 非持久性数据:如果某个字段的数据是临时的,或者在重建对象时可以通过其他方法恢复,不需要序列化它。
- 避免循环引用问题:在对象图中,如果序列化字段可能导致循环引用(即对象相互引用形成闭环),可以使用
transient
避免这些字段的序列化。
需要注意的是,使用 transient
关键字的字段在反序列化对象时,其值将会是其类型的默认值(如对象类型为 null
,整数类型为 0
,布尔类型为 false
)。因此,如果需要在反序列化后恢复这些字段的值,需要额外的逻辑来处理。
在下面的代码中,password
字段被标记为 transient
,因此在序列化时不会被保存。序列化和反序列化后,password
字段的值将会变为 null
。
java
import java.io.*;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // 不希望序列化的字段
public User(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "'}";
}
public static void main(String[] args) {
User user = new User("john_doe", "secret123");
System.out.println("Before serialization: " + user);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
User deserializedUser = (User) ois.readObject();
System.out.println("After serialization: " + deserializedUser);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}