你好!您已经熟悉“修饰符”一词。至少,您遇到过访问修饰符(public、private)和 static 修饰符。今天我们将讨论一个名为final的特殊修饰符。您可以说 final 修饰符“巩固”了我们程序中需要恒定、明确、不变行为的部分。您可以在程序中的三个地方使用它:类、方法和变量。
让我们按顺序浏览它们。如果类声明中使用了final修饰符,则意味着该类不能被继承。在之前的课程中,我们使用了一个简单的继承示例:我们有一个
让我们试试别的。例如,
没有任何改变。它不应该有。正如我们之前所说,字符串是不可变的。那么,类中的所有方法是什么

Animal
父类和两个子类:Cat
和Dog
public class Animal {
}
public class Cat extends Animal {
// Fields and methods of the Cat class
}
public class Dog extends Animal {
// Fields and methods of the Dog class
}
但是,如果我们在类上使用finalAnimal
修饰符,则类Cat
和Dog
类不能继承它。
public final class Animal {
}
public class Cat extends Animal {
// Error! Cannot inherit from final Animal
}
编译器立即生成错误。在 Java 中,许多最终类已经实现。在您经常使用的那些中,String
是最著名的。此外,如果一个类被声明为final,则该类的所有方法也将变为final。这意味着什么?如果使用final修饰符声明方法,则不能重写该方法。例如,这里我们有一个Animal
声明speak()
方法的类。但是,狗和猫肯定以不同的方式“说话”。因此,我们将在Cat
和Dog
类中声明 speak() 方法,但我们将以不同方式实现它们。
public class Animal {
public void speak() {
System.out.println("Hello!");
}
}
public class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Woof!");
}
}
我们使Cat
和Dog
类覆盖了在父类中声明的方法。现在,动物会说不同的话,这取决于它是什么类型的物体:
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
cat.speak();
dog.speak();
}
}
输出: 喵!纬! 但是,如果我们将Animal
类的speak()
方法声明为final,那么我们就不能在其他类中重写它:
public class Animal {
public final void speak() {
System.out.println("Hello!");
}
}
public class Cat extends Animal {
@Override
public void speak() {// Error! A final method can't be overridden!
System.out.println("Meow!");
}
}
我们的对象将被迫使用speak()
父类中定义的方法:
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
cat.speak();
dog.speak();
}
输出: 你好!你好! 现在,关于最终变量。它们也称为常量。首先(也是最重要的),不能更改分配给常量值的初始值。它是一次性分配的。
public class Main {
private static final int CONSTANT_EXAMPLE = 333;
public static void main(String[] args) {
CONSTANT_EXAMPLE = 999;// Error! You can't assign a new value to a final variable!
}
}
常量不需要立即初始化。那可以稍后再做。但是,最初分配给它的值将永远保持不变。
public static void main(String[] args) {
final int CONSTANT_EXAMPLE;
CONSTANT_EXAMPLE = 999;// This is allowed
}
其次,记下我们变量的名称。Java 对常量有不同的命名约定。它不是通常的驼峰命名法。如果它是一个普通变量,我们会称它为constantExample. 但是,常量的名称全部大写,单词之间有下划线(如果有多个单词),例如“CONSTANT_EXAMPLE”。为什么我们需要常量?它们非常有用,例如,如果您经常在程序中使用固定值。例如,您决定创造历史并亲手编写游戏“巫师 4”。游戏显然会经常使用主角的名字:“利维亚的杰洛特”。这个字符串(以及其他英雄的名字)最好声明为常量:它的值将存储在一个地方,你输入一百万次绝对不会错字。
public class TheWitcher4 {
private static final String GERALT_NAME = "Geralt of Rivia";
private static final String YENNEFER_NAME = "Yennefer of Wengerberg";
private static final String TRISS_NAME = "Triss Merigold";
public static void main(String[] args) {
System.out.println("The Witcher 4");
System.out.println("It's already the fourth Witcher game, but " + GERALT_NAME + " still can't decide who" +
" he likes more: " + YENNEFER_NAME + " or " + TRISS_NAME);
System.out.println("But, if you've never played The Witcher before, we'll start from the beginning.");
System.out.println("The protagonist's name is " + GERALT_NAME);
System.out.println(GERALT_NAME + " is a witcher, a monster hunter");
}
}
输出: The Witcher 4 已经是巫师的第四场比赛了,但是 Rivia 的 Geralt 仍然无法决定他更喜欢谁:Wengerberg 的 Yennefer 还是 Triss Merigold 但是,如果你以前从未玩过 The Witcher,我们将从开始。主角的名字是利维亚的杰洛特 利维亚的杰洛特是猎魔人,怪物猎人 我们已经将英雄的名字声明为常量。现在肯定不会打错了,也不用每次都手写了。另一个优点:如果我们需要在整个程序中更改变量的值,您可以在一个地方完成,而不是在整个代码库中手动修改它。:)
不可变类型
当您使用过 Java 时,您可能已经习惯了这样的想法,即程序员几乎可以完全控制所有对象的状态。如果你想创建一个Cat
对象,你可以。如果你想重命名它,你可以。如果你想改变它的年龄或其他东西,你可以。但是 Java 有几种具有特殊属性的数据类型。它们是不可变的。如果一个类是不可变的,那么它的对象的状态就不能改变。想要一些例子吗?您可能会感到惊讶,但最著名的不可变类是 String!那么,我们真的不能改变一个字符串的值吗?好吧,让我们试试看:
public static void main(String[] args) {
String str1 = "I love Java";
String str2 = str1;// Both reference variables point to the same string.
System.out.println(str2);
str1 = "I love Python";// but changing str1 has no impact on str2
System.out.println(str2);// str2 continues to point to the "I love Java" string, but str1 now points to a different object
}
输出: 我爱 Java 我爱 Java 我们写完之后
str1 = "I love Python";
字符串"I love Java"
对象没有改变或去任何地方。它仍然愉快地存在并且具有与以前完全相同的文本。代码
str1 = "I love Python";
只是创建了另一个对象,str1现在指向它。但是,我们似乎无法对“I love Java”字符串对象产生任何影响。好吧,让我们试试别的吧!这个String
类充满了方法,其中一些似乎可以改变对象的状态!例如,有一个replace()
方法。让我们将字符串中的单词“Java”更改为“Python”!
public static void main(String[] args) {
String str1 = "I love Java";
String str2 = str1;// Both reference variables point to the same string.
System.out.println(str2);
str1.replace("Java", "Python");// We try to change the state of str1 by swapping the word "Java" with "Python"
System.out.println(str2);
}
输出: I love Java I love Java 它又没用了!也许替换方法不起作用? substring()
。它根据作为参数传递的字符索引返回一个子字符串。让我们切断字符串的前 10 个字符:
public static void main(String[] args) {
String str1 = "I love Java";
String str2 = str1;// Both reference variables point to the same string.
System.out.println(str2);
str1.substring(10);// Truncate the original String
System.out.println(str2);
}
输出: 我爱 Java 我爱 Java 
String
?毕竟,它们可以截断字符串、更改字符等等。如果什么都没发生,那又有什么意义呢?他们居然可以做这些事!但是,他们每次都返回一个新的字符串。写的没意义
str1.replace("Java", "Python");
因为您无法更改原始对象。但是,如果将方法的结果写入新的引用变量,您会立即看到不同之处!
public static void main(String[] args) {
String str1 = "I love Java";
String str2 = str1;// Both reference variables point to the same string.
System.out.println(str2);
String str1AfterReplacement = str1.replace("Java", "Python");
System.out.println(str2);
System.out.println(str1AfterReplacement);
}
所有String
方法都以这种方式工作。无法对该"I love Java"
对象进行任何操作。您可以创建一个新对象并写入:“<new object> = 操作的结果"I love Java" object "
。还有哪些类型是不可变的?您肯定需要立即记住的一些是原始类型的所有包装类。 Integer
,Byte
, Character
, Short
, Boolean
, Long
, Double
, Float
:所有这些类都创建immutable
对象(我们将在接下来的课程中讨论它们)。这包括用于创建大数字的类,例如BigInteger
和BigDecimal
。我们最近介绍了异常并触及了堆栈跟踪。好吧,猜猜是什么,java.lang.StackTraceElement对象也是不可变的。这是有道理的:如果有人可以更改我们堆栈的数据,那么整个事情就毫无意义了。想象一下有人通过堆栈跟踪并将OutOfMemoryError更改为FileNotFoundException。然后你使用那个堆栈来找出错误的原因。但是该程序甚至不使用文件。:) 所以,他们让这些对象不可变,以防万一。好的,所以它或多或少对StackTraceElement有意义。但是,为什么有人需要让字符串不可变呢?为什么改变他们的价值观会成为一个问题?它可能会更方便。:/ 有几个原因。首先,它节省内存。不可变字符串可以放在字符串池中,允许重复使用字符串而不是创建新字符串。第二,为了安全。例如,几乎每个程序中的用户名和密码都是字符串。使更改它们成为可能可能会导致授权问题。还有其他原因,但我们对 Java 的学习还没有涉及到它们,所以我们稍后再回过头来。
更多阅读: |
---|
GO TO FULL VERSION