Java运算符详解:从基础到高级应用

简介

运算符由一个或多个符号组合而成,例如著名的算术运算符减号(-)和加号(+),或者更高级的instanceof。当你将运算符应用于值或变量时,会得到一个运算结果。这些运算在编程中是基础的,因为它们的结果可以被赋给一个变量,或者通过进一步的计算直到程序达到最终目标。

对于本教程,你必须熟悉操作数,即被运算符应用的值或变量。根据操作数的数量,运算符可以分为三组。首先,当操作中只有一个操作数时,运算符被称为一元运算符。同样地,二元运算符涉及两个操作数。最后,当有三个操作数时,运算符为三元运算符。根据这种分类,本教程针对每种类型的运算符分为三个主要部分进行组织。

在本教程中,您将使用三种类型的运算符来操作原始数据类型,例如数学方程。您还将在更高级的场景中使用运算符与引用类型,并探索一些运算符优先级规则。

先决条件

为了跟随本教程,你需要准备:

  • 一个可以执行Java程序的环境,以便跟随示例。要在本地机器上进行设置,你需要安装Java开发工具包(JDK)提供的Java(版本11或更高版本)。对于Ubuntu和Debian,请遵循我们教程《如何在Ubuntu 22.04上使用Apt安装Java》中选项1的步骤。对于包括Mac和Windows在内的其他操作系统,请参阅Java安装的下载选项。为了编译和运行代码示例,本教程使用Java Shell,它是一个从命令行运行的读取-求值-打印循环(REPL)。要开始使用JShell,请查阅《JShell入门指南》。
  • 熟悉Java和面向对象编程,你可以在我们的教程《如何编写你的第一个Java程序》中找到相关内容。
  • 理解Java数据类型,这在我们的教程《理解Java中的数据类型》中有所讨论。

一元运算符

一元运算符应用于一个操作数,这使它们变得最直接。一元运算符常被使用,因为它们使代码更简洁、易读。它们代替了明确描述增加和减少值等操作的需要。然而,当与其他运算符结合时,一元运算符也可能变得难以使用,稍后在本节中您将发现这一点。

接下来,您将使用一元运算符来增加和减少数值,以及翻转布尔值。

递增和递减运算符

增量和减量运算符,正如它们的名字所表明的那样,用于增加和减少数字。增量运算符由两个加号(++)组成,而减量运算符由两个减号(--)组成。这些运算符在操作数之前和之后使用。

前增量和前减量

当你在操作数之前使用运算符时,根据使用的是++还是--,你进行的是预增量或预减量操作。当你使用预操作符时,在使用操作数之前你会改变它的值。因此,当你实际使用这个值时,它已经发生了改变。

信息:要按照本教程中的示例代码操作,请在本地系统上打开Java Shell工具,运行jshell命令。然后,您可以通过在jshell>提示后添加它们并按ENTER键来复制、粘贴或编辑示例。要退出jshell,输入/exit。要使用前增量运算符,将以下内容输入到jshell中:

  1. int theAnswer = 42;
  2. System.out.println(“Preincrementing: “ + ++theAnswer);

 

在第一行,你用值为42来定义一个变量theAnswer。在第二行,你使用println()方法来打印出它,并展示它是如何改变的。

在上面的例子中,前缀自增运算符是++,它位于theAnswer之前。通过以这种方式使用前缀自增运算符,您首先将theAnswer的值增加到43。在此之后,当println()对其进行处理时,它已经是43,因此您看到输出的是43。

输出

theAnswer ==> 42 Preincrementing: 43

预递减工作方式类似,但您是通过减少操作数的值来递减。作为练习,将上述示例修改为使用预递减运算符--而不是预递增运算符++

后递增和后递减

与前置运算符相比,后置运算符是在使用操作数后更改其值的。有一些特定情况下常常使用后置或前置运算符,但整体而言,这是个人偏好的问题。

为了演示后置运算符的工作原理,您将对theAnswer的值进行后增操作,并检查其值的变化。在jshell中添加以下行:

  1. int theAnswer = 42;
  2. System.out.println(“Postincrementing: “ + theAnswer++);
  3. System.out.println(“Final value: “ + theAnswer);

 

变量theAnswer首先等于42。然后,它被打印出来并进行后自增操作。在最后一行,你再次打印它以查看最终值。

你应该输出:

输出

theAnswer ==> 42 Postincrementing: 42 Final value: 43

正如你所见,在后自增的过程中,答案仍然是42。只有在后自增后再次打印时,答案才变成了43(最终值:43)。

后递减的工作方式相同。首先获取并使用值,然后才进行递减操作。作为练习,请尝试将后递增运算符++替换为后递减运算符--,甚至可以包含其中一种前运算符。

非运算符

这是文章《如何在Java中使用运算符》的第2部分(共8部分)。

内容片段:

**逻辑非运算符(NOT)**

逻辑非运算符,通常用感叹号 `!` 表示,用于翻转布尔操作数的值。当你有一个布尔变量或值,并且希望使用其相反的值时,`NOT` 运算符非常有用,它避免了不必要地创建另一个具有相反值的变量。

以下是一个 `NOT` 运算符的工作示例。为了简化说明,我们将反转 `true` 的值:

  1. boolean isJavaFun = !true;
  2. System.out.println(isJavaFun);

你定义了一个布尔变量 `isJavaFun` 为 `true`。然而,`NOT` 运算符 `!` 位于 `true` 之前,因此 `true` 的值被翻转为 `false`。当你运行上述代码时,将打印以下输出:

输出

isJavaFun ==> false

这就是 `NOT` 运算符的工作原理。有时它可能会让人感到困惑且难以察觉,因此在使用时应谨慎。

在上述示例中,你可以直接使用 `false` 而不是 `!true`。这样做更清晰、更直观,通常是更好的实践。一般来说,最好使用直接的字面量或方法,而不是需要额外操作的替代方案。然而,在某些情况下,这种做法可能并不总是合理或可行。例如,`NOT` 运算符通常用于翻转布尔方法的结果。

举例来说,要检查一个字符串是否包含另一个字符串,你可以使用 `contains()` 方法。但是,如果你想检查相反的情况(即一个字符串不包含另一个字符串),则没有其他内置方法可用。你需要将 `contains()` 方法与 `NOT` 运算符结合使用来实现。

假设你有一个字符串 “Java is smart”,并且你想检查以下内容:

    1. 该字符串包含“智能”(smart)。
    该字符串不包含“困难”(hard)。

要检查这些,你将使用以下代码:

  1. String javaIsSmart = “Java is smart.”;
  2. boolean isSmartPartOfJava = javaIsSmart.contains(“smart”);
  3. boolean isHardNotPartOfJava = !javaIsSmart.contains(“hard”);

第一行,你定义了一个 `String` 变量 `javaIsSmart`。第二行,你定义了一个布尔变量 `isSmartPartOfJava`,它的值由 `contains()` 方法的操作结果决定——在这种情况下,即字符串 “smart” 是否是 `javaIsSmart` 字符串的一部分。同样地,在第三行,你定义了一个布尔变量 `isHardNotPartOfJava`,它的值是 `true`,因为在 `javaIsSmart` 中找不到 “hard”。

当你在 `jshell` 中运行这段代码时,你将会得到以下输出:

输出

javaIsSmart ==> “Java is smart.”
isSmartPartOfJava ==> true
isHardNotPartOfJava ==> true

根据上述输出:

  • `isSmartPartOfJava` 为 `true`,因为在 `javaIsSmart` 中找到了 “smart”。
  • `isHardNotPartOfJava` 也为 `true`,因为在 `javaIsSmart` 中没有找到 “hard”。

在本节中,你探索了使用一个操作数进行自增、自减和逻辑非操作符。尽管这些操作符只有一个操作数,但它们可能难以使用,正如逻辑非操作符所示。在下一步中,你将通过使用两个操作数的运算符来进一步巩固这些知识。

二元运算符

二元运算符作用于两个操作数,通常与算术运算(例如加法和减法)相关联。还有其他非数学相关的二元运算符,例如逻辑运算符和特殊的关系运算符 `instanceof`。在本节中,你将首先学习算术二元运算符,这可能更为熟悉。

算术二元运算符

以下是用于算术运算的常见操作符,如加法(`+`)和减法(`-`)。以下是加法示例:

  1. int theAnswer = 40 + 2;
  2. System.out.println(“The result is: “ + theAnswer);

在第一行,你将 `2` 加到 `40` 上,并将结果赋值给 `theAnswer` 变量。当你打印它时,你得到最终值 `42`。

输出

The result is: 42

**注意:**除了用于算术运算外,加号(`+`)还用于连接字符串。你在我们展示打印值的大多数例子中都见过它的作用,就像上面那个例子一样。在那里,你使用加号把“结果是:”和变量 `theAnswer` 连接起来。然而,这种用法是一个例外,其他算术运算符不能用于引用类型。所以,举个例子,你不能使用减号来删除字符串的一部分。为了进一步练习,尝试使用其他算术运算符,你可以在Java文档中找到。

赋值运算符

赋值运算符将右操作数的值赋给左操作数。通常情况下,左操作数是一个变量,右操作数是一个值或指向对象的引用。这可能听起来很熟悉,因为你在所有的例子中都使用过这样的赋值。在本节中,你将练习使用基本的赋值运算符、一些复合赋值运算符和强制类型转换运算符。

基本的赋值运算符(`=`)是一个众所周知且经常使用的运算符。

  1. int x = 1;

在这个例子中,你声明一个整数变量 `x`,并赋值为 `1`。使用等号(`=`)是给变量赋值的方法。

复合赋值运算符

这是文章《如何在Java中使用运算符》的第3部分(共8部分)。

复合赋值运算符+=-=*=/=)将赋值与附加的算术运算(如加法或减法)结合起来。这些运算符可以避免冗余代码,特别适用于简单易懂的算术运算。

例如,使用复合 += 赋值运算符来将加法和赋值结合在一起,就像这样:

  1. int x = 1;
  2. int y = 1;
  3. x += y;
  4. System.out.println(“x is: “ + x);

在前两行中,你声明了两个整数变量xy,它们的值都为1。接下来,你使用+=运算符重新为x赋值,这意味着x先与y相加,然后再赋值给x

以上代码将返回类似于这样的输出。

输出

x ==> 1 y ==> 1 $11 ==> 2 x is: 2

根据上述输出,xy的值都为1。在第三行,有一个临时变量,它的名称是随机分配的($11)。它通过复合赋值操作保存了x的值。在最后一行,打印出的x的值为2。

这段代码可以不使用复合赋值运算符进行重写,如下所示:

  1. int x = 1;
  2. int y = 1;
  3. x = x + y;
  4. System.out.println(“x is: “ + x);

与之前的例子相比,你需要在第3行编写额外的代码来明确描述xy的相加过程。

运行这段代码将返回以下输出:

输出

x ==> 1 y ==> 1 x ==> 2 x is: 2

最终,无论是第一个还是第二个例子中,x都等于2。然而,在第二个例子中,jshell 没有打印一个临时变量名称,比如$11。相反,它直接使用了x来显示它的值已经改变(x ==> 2)。这样详细的输出对学习非常有帮助,而且只有在jshell中才能使用。

其余的复合运算符包括减法(-=)、乘法(*=)和除法(/=),与赋值一起结合使用。尝试更改上述示例来查看它们的工作方式。

了解复合运算符是很好的,因为它们经常被使用。然而,使用复合运算符并不会提高性能,因此使用复合运算符是个人选择的问题。如果它们看起来令人困惑,则可以选择不使用。

转型运算符

你将要回顾的最后一个赋值操作符是转型操作符,它是一个由括号括起来的数据类型:(数据类型)。转型操作符用于类型转换,即将一个数据类型解释为另一个数据类型。

数据类型必须是兼容的。数据类型之间是否兼容取决于它们之间的关系,比如一个类是否是另一个类的父类或同级类。例如,你可以将整数转换为短整型,因为这两种数据类型都用于存储整数。然而,你不能将整数转换为布尔型,因为这两种数据类型不兼容。

在这一节中,你将探索一些常见的类型转换示例和问题。为了教育目的,你将从一个错误和不匹配的转换开始。

  1. boolean y = (boolean) 1;

使用这行代码,你试图将整数1转换为布尔值,并赋值给变量y。当你将其粘贴到JShell中时,你会得到以下错误。

输出

| Error: | incompatible types: int cannot be converted to boolean |
| boolean y = (boolean) 1; |

根据错误信息所述,你不能将一个整数值转换为布尔值。布尔值只能是truefalse,无法确定1应该是哪个布尔值。

现在,你将尝试一个具有兼容数据类型的例子。你将使用两种原始类型来存储整数:intshort。它们的区别在于它们的容量,也就是存储信息可用的内存量。int具有更大的容量,因此可以存储更大的数字。

将以下行添加到jshell中:

  1. int prize = 32767;
  2. short wonPrize = (short) prize;
  3. System.out.println(“You won: “ + wonPrize);

在第一行中,你将彩票奖品定义为一个值为32767的int原始类型。然而,在第二行中,你决定使用short原始类型更合适来储存wonPrize的值,并使用(short)prize转换为short类型。

在jshell中运行上述代码时,输出结果是:

输出

这是文章《如何在Java中使用运算符》的第4部分(共8部分)。

prize ==> 32767
wonPrize ==> 32767
You won: 32767

上面的输出确认了奖品(prize)和赢得奖品(wonPrize)的值都被正确设置为32767。最后一行使用wonPrize变量来显示您赢得了多少奖品。

intshort而言,强制类型转换可能显得没有必要,实际情况下可能不会看到这种转换。然而,这个例子对于展示强制类型转换的概念是有用的。

强制类型转换似乎很简单,但有一个注意事项。当您从具有较大容量的数据类型转换为具有较小容量的数据类型时,可能会超过较小容量的限制,这被称为溢出。为了展示这个问题,可以重用之前的例子,并将奖励从32767增加到32768,像这样:

  1. int prize = 32768;
  2. short wonPrize = (short) prize;
  3. System.out.println("You won: " + wonPrize);

 

当您在jshell中运行上述命令时,将会得到如下输出:

Output
prize ==> 32768
wonPrize ==> -32768
You won: -32768

在这种情况下,您会丢失信息并得到意想不到的结果。当将值32768转换为short类型时,其值变为-32768。这是因为short类型的存储容量范围是从-32768到32767。当您试图存储一个大于最大值的数时,会发生溢出并从最小值重新开始。在这个案例中,当您尝试存储32768时,超过了最大容量(32767)1个单位。因此,下一个值会从最小可能值开始分配。在这里,最小值为-32768。

这就是为什么上述的输出看起来出乎意料——最终的奖品成为了一个负数。这种问题并不总是容易发现,所以您应该谨慎使用类型转换。您可能会在更复杂的情境中使用类型转换,这将在Java系列的日后教程中介绍。

关系运算符

关系运算符比较两个操作数并返回一个布尔值结果。如果关系成立,则结果为真(true)。如果不成立,则结果为假(false)。

第一类关系运算符是等于(==)和不等于(!=)。它们用于判断值和对象的相等性。对于原始值和字面量,它们的使用类似于数学。

为了演示等于(==)操作符,我们比较两个整数常量。实际上,它们将是同一个数字:1。您将比较它是否等于自己,以便您可以得到一个真(true)的结果。将以下代码粘贴到jshell中:

  1. System.out.println(1==1);

 

在上述代码中,您断言1是否等于1。由于这两个数字相等,该表达式得出的结果为true。因此,println()会打印出true

Output
true

以练习为目的,尝试更改其中一个值,以获得错误的结果。

注意:确保区分等于操作符“==”和赋值操作符“=”。即使您知道它们不同,也很容易混淆它们。您的代码并不总是会出现语法错误,这可能导致难以调试的问题。

与比较原始值相比,比较对象的相等性更加复杂,因为您要断定两个变量是否指向同一个对象。尝试以比较两个整数(Integer)的例子来说明。

  1. Integer myAnswer = Integer.valueOf(42);
  2. Integer yourAnswer = Integer.valueOf(42);
  3. System.out.println(myAnswer == yourAnswer);

 

在以上代码中,您创建了两个整数变量,每个变量的值都为42。在最后一行,您比较它们是否相等,并且如果它们相等则打印true。根据我们之前的Java数据类型教程,您可能知道Integer.valueOf()首先在缓存中检查是否有具有相同值的对象,并且如果已经存在一个具有该值的对象,则返回同一个对象。这就是为什么myAnsweryourAnswer都得到了相同的对象。

当您把以上的代码粘贴到jshell中时,您将会得到以下输出结果:

Output

这是文章《如何在Java中使用运算符》的第5部分(共8部分)。

myAnswer ==> 42
yourAnswer ==> 42
true

这种对象比较看起来很简单,但有时候会有挑战。其中最令人困惑的例子是字符串比较。尝试比较两个具有相同值的字符串。

  1. String answer1 = new String(“yes”);
  2. String answer2 = new String(“yes”);
  3. System.out.println(answer1 == answer2);

首先,你声明了两个新的String变量(answer1和answer2),它们的值都为”yes”。然而,你使用了new关键字来创建新的String对象。因此,这两个变量并不指向同一个对象——它们实际上指向两个不同的对象(但值相同)。

当你将以上代码粘贴到jshell中时,你将得到以下输出结果。

Output
answer1 ==> "yes" answer2 ==> "yes" false

即使answer1和answer2的值相同(都是“yes”),它们的相等性评估结果却是false,这意味着它们是不相等的。如果您打算比较值,例如判断两个答案是否都是肯定的,而不关心底层对象,那么这可能令人困惑。为此,许多类(包括字符串)都有专门的方法用于断言相等性。

在字符串的情况下,此方法是equals()。尝试更改代码以使用equals()(而不是==),像这样:

  1. String answer1 = new String(“yes”);
  2. String answer2 = new String(“yes”);
  3. System.out.println(answer1.equals(answer2));

equals()方法用于验证比较对象中包含的字符串是否相等。

当你将这段代码粘贴到JShell中时,你将得到以下输出结果:

Output
answer1 ==> "yes" answer2 ==> "yes" true

以上输出与上一个输出相似,但它以true结尾,确认了两个对象的值是相等的。这个例子证明了在比较引用类型时,你必须小心,并在可用时使用相应的方法。

一个选择的等于操作符,不是等于!=,用法类似,但它断言两个变量或值是否不相同(或不等)。作为练习,在之前的一些示例中尝试使用!=替换==

类似于==!=,下面的四个关系运算符也源自数学:小于<,小于等于<=,大于>,大于等于>=

这是一个使用大于操作符的示例。

  1. System.out.println(4 > 5);

当你在jshell中运行这段代码时,你将会得到以下输出:

Output
false

以上的代码首先比较了4是否大于5。由于不是,表达式的结果为false。然后,println()方法打印了比较的结果(false)。

在 Java 中,最后一个关系运算符是instanceof,它用于评估一个变量是否是给定类(或子类)的实例或接口的实现。如我们在《了解 Java 中的数据类型》教程中所解释的那样,接口是一个具有一组要求的抽象实体。

使用以下示例来探索instanceof的工作原理:

  1. String greeting = “hello”;
  2. System.out.println(greeting instanceof String);

首先,你创建一个名为greeting的字符串变量。然后,在括号中评估greeting是否是一个字符串实例。

当你在jshell中粘贴代码时,你将获得以下输出:

Output
greeting ==> "hello" true

由于greeting是一个字符串实例,所以表达式的值为true,通过println()函数在屏幕上打印。

逻辑运算符

这是文章《如何在Java中使用运算符》的第6部分(共8部分)。

内容片段: 逻辑运算符包括逻辑与(&)、逻辑或(|)和异或(^)。它们都按照以下方式对两个值进行评估:

  • 逻辑与:当两个值都为真时,结果为真。
  • 逻辑或:当至少一个值为真时,结果为真。
  • 异或:当一个值为真而另一个值为假时,结果为真。

当逻辑运算符不满足上述条件时,它们的结果为假。

要使用逻辑与(&)运算符,请将以下示例粘贴到jshell中:

  1. boolean isJavaFun = true;
  2. boolean isJavaPowerful = true;
  3. System.out.println(isJavaFun & isJavaPowerful);

 

前两行定义了两个布尔变量isJavaFunisJavaPowerful,都赋值为true。在第三行,你在括号中对isJavaFunisJavaPowerful执行了逻辑与操作,并且结果由println()函数输出。

当你将这段代码粘贴到jshell中时,你将会得到以下输出:

输出

isJavaFun ==> true isJavaPowerful ==> true true

两个变量都被设置为真,并通过逻辑与运算的结果打印出最后的真值。

要增加你的技能,尝试使用前面的代码示例并进行一些变化。你可以尝试在变量之间切换truefalse的值。你也可以尝试将逻辑运算符改为逻辑或(|)和异或(^)。

逻辑运算符的扩展版本是所谓的短路逻辑运算符:短路与(&&)和短路或(||)。它们与常规的逻辑与和逻辑或运算符类似,但有一个重要的区别:如果第一个操作数的评估结果足以确定整个表达式的值,那么第二个操作数将不会被评估。因此,为了使短路与为真,它两边的条件都必须为真。然而,如果左边为假,则不会评估右边。类似地,对于短路或,如果左边为真,则不会评估右边。

这里举一个使用短路逻辑或的例子。

  1. boolean isJavaFun = true;
  2. Boolean isNullFun = null;
  3. System.out.println(isJavaFun || isNullFun);

 

首先,你将变量isJavaFun赋值为true。为了与之前的示例保持一致性,这个变量是基本类型boolean。然而,对于下一个变量isNullFun,你使用Boolean引用类型,以便可以将其赋值为null。在这个示例中,你有一个指向null的变量很重要,但是正如你可能还记得的,在理解Java数据类型的教程中,基本类型不能为null,这就是为什么你使用了引用类型的原因。

当短路或运算符发生在括号中时,由于左侧为真,isNullFun被忽略,这就足以使整个表达式为真。因此,当您在jshell中运行代码时,将打印以下输出:

输出

isJavaFun ==> true isNullFun ==> null true

第一行和第二行确认变量被赋值为真和空值。第三行打印真,因为短路或操作返回了真。

以上的例子是选择特定的空值来展示短路运算符的工作原理,并且isNullFun不会被执行。要看到isNullFun被执行,请尝试将短路或运算符更改为普通的或运算符,像这样:

  1. boolean isJavaFun = true;
  2. Boolean isNullFun = null;
  3. System.out.println(isJavaFun | isNullFun);

 

常规的逻辑或运算符会同时对表达式的两侧进行求值。

当您在jshell中运行此代码时,您将获得以下输出:

isJavaFun ==> true
isNullFun ==> null
|  Exception java.lang.NullPointerException
|        at (#3:1)

当逻辑或尝试评估null时,你会得到java.lang.NullPointerException。因此,短路运算符更受青睐,几乎总是使用而不是常规逻辑运算符。

作为练习,尝试使用短路运算符&&||,它们是最受欢迎和最有用的运算符。

在本节中,你在一系列的例子中使用了二进制运算符,从基本算术到涉及强制类型转换和比较对象相等性的更具挑战性的操作。在下一节中,你将使用三个操作数来进行操作。

三元运算符

Java运算符系列教程:三元运算符详解

这是文章《如何在Java中使用运算符》的第7部分(共8部分)。

在之前的章节中,您已经练习了使用一个或两个操作数的运算符。在本节中,您将深入了解三元运算符,它是Java中唯一一个需要三个操作数的运算符。其基本语法如下:

第一个操作数 ? 第二个操作数 : 第三个操作数;

其中,第一个操作数必须是一个布尔表达式。如果该布尔表达式评估为true,则整个表达式的结果将是第二个操作数的值;反之,如果评估为false,则返回第三个操作数的值。

三元运算符因其能够有效避免编写冗长的if-else条件语句,并减少对临时变量的依赖而广受欢迎和频繁使用。

三元运算符示例

以下示例展示了如何在Java中使用三元运算符:

  1. boolean isJavaFun = true;
  2. String shouldILearnJava = isJavaFun ? “yes” : “no”;
  3. System.out.println(“Should I learn Java: ” + shouldILearnJava);

在此示例中,isJavaFun的值被初始化为true,并且变量shouldILearnJava通过三元运算符进行赋值。由于第一个操作数isJavaFuntrue,因此表达式返回第二个操作数,即字符串"yes"。为了验证这一点,第三行代码打印了shouldILearnJava变量的值,预期输出为yes

当您运行以上代码时,将得到以下输出:

输出
Should I learn Java: yes

为了进一步练习,请尝试在isJavaFun前面使用逻辑非(!)运算符:

  1. boolean isJavaFun = true;
  2. String shouldILearnJava = !isJavaFun ? “yes” : “no”;
  3. System.out.println(“Should I learn java: ” + shouldILearnJava);

通过在isJavaFun前添加逻辑非运算符,其布尔值从true反转为false。因此,三元表达式将返回第三个操作数,即字符串"no"

尽管三元表达式,特别是当与逻辑非等附加运算符结合使用时,可能会让人感到有些困惑,但它们在减少代码冗余方面表现出色,这也是它们备受欢迎的原因。

运算符优先级

这是文章《如何在Java中使用运算符》的第8部分(共8部分)。

一旦你了解了重要的运算符,你就能够使用甚至组合它们。但在你开始组合之前,你需要了解运算符优先级的规则。

运算符优先级决定了运算符的评估顺序。由于你可能会使用多个运算符,因此了解运算符优先级非常重要。虽然规则并不总是直观的,但在实践中,你只需要了解一些基本规则即可。

理解运算符优先级有助于编写整洁的代码,这是现代编程的既定标准。编写整洁的代码意味着编写易于理解和维护的代码。在运算符方面,整洁代码的范式意味着尽量少使用运算符,而是创建单独的语句,而不是嵌套和组合运算符。

例如,应避免使用像下面这样的陈述,因为它们太令人困惑。

  1. boolean isThisCleanCode = !true || false && true;

 

即使你查阅操作手册,也可能无法猜测最终结果(isThisCleanCode为假)。

以下是一些最重要和常用的优先规则,从具有最高优先级的规则开始:

  • 前缀和后缀增量与减量运算符:它们具有最高的优先级,并在任何其他运算符之前生效。
  • 数学运算规则:所有在数学中有效的规则在Java中也有效。例如,除法 / 具有比加法 + 更高的优先级。括号可以覆盖优先级;也就是说,你可以用括号将操作分组,它们将优先被评估。
  • 相等和关系运算符:由于它们评估相等性或关系,它们也作用于其操作数的最终值,因此所有其他操作都必须完成。
  • 逻辑或和与运算符(包括短路):类似于三元运算符,它们都需要其操作数的最终值,因此它们的优先级较低。
  • 三元运算符:三元运算符需要第一个操作数的最终值。为了实现这一点,所有其他操作都必须已经完成。这就是为什么三元运算符的优先级如此之低。
  • 赋值运算符:它们最后被评估,以便所有其他操作都已完成,并且最终结果可以赋值给左侧的操作数。例如,想想你迄今为止用等号赋值的每个变量。

现在,你将使用一道数学问题来探索运算符优先级。

  1. int x = 1 + 10 / 2;

 

当你将以上代码粘贴到jshell中时,你将会得到下面的输出结果:

输出

x ==> 6

根据上述输出,x 的值为6。这是因为已经完成了以下操作,按照递减的优先级排列:

    1. 首先计算10除以2,结果为5。

 

    1. 然后将第一步操作的结果(5)加1,得到6。

 

  1. 接着赋值操作符开始执行,使用最终结果(6)将其赋给变量 x

尽管这个数学方程相对来说很简单,但你可以通过增加冗余内容并使用括号来确保运算的优先级更容易理解。考虑把它改写成这样:

  1. int x = 1 + (10 / 2);

 

当你将上述内容粘贴到jshell中时,将打印出与之前相同的结果。

输出

x ==> 6

在上一个示例中,你没有通过在 10 / 2 周围使用括号来更改优先级。相反,它们的目的只是使括号内的操作的优先级更明显。像这样使用括号有助于使你的代码更加清晰和易懂,尤其是在操作更加复杂的情况下。

优先规则很有趣,花点时间学习它们是个好主意。牢记代码的整洁范式,不要进行不必要的嵌套和操作符组合,因为这被视为代码上的弱点。

结论

在本教程中,你学习了Java中的主要运算符。你编写了一些测试代码片段,其中你看到了与运算符相关的一些最有用和有趣的场景。你还学习了简洁代码的原则,意识到运算符不应该被不必要地过度使用。

想了解更多关于Java的内容,请访问我们的《Java编程指南》系列。

bannerAds