corejava11(3.5 操作符)

本文深入探讨Java中的各种运算符,包括算术、逻辑、位运算符及数学函数,详细讲解了它们的使用场景和注意事项,同时介绍了类型转换和运算符优先级规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3.5 操作符

运算符用于组合值。正如您将在下面的章节中看到的,Java有丰富的算术和逻辑运算符和数学函数集。

3.5.1 算术运算符

常用的算术运算符+-*/用于Java中的加法、减法、乘法和除法。如果两个参数都是整数,/运算符表示整数除法,否则表示浮点除法。整数余数(有时称为模)用%表示。例如,15/2是7,15%2是1,15.0/2是7.5。

请注意,整数被0除会引发异常,而浮点被0除会产生无穷大或NaN结果。

注意

Java编程语言的一个明确目标是可移植性。无论哪个虚拟机执行计算,计算都应该产生相同的结果。对于浮点数的算术计算,实现这种可移植性是非常困难的。double类型使用64位存储数值,但有些处理器使用80位浮点寄存器。这些寄存器在计算的中间步骤中产生额外的精度。例如,考虑以下计算:

double w = x * y / z;

许多英特尔处理器计算x*y,将结果保留在80位寄存器中,然后除以z,最后将结果截断为64位。这样可以得到更精确的结果,并且可以避免指数溢出。但是结果可能与使用64位的计算不同。出于这个原因,Java虚拟机的初始规范要求所有中间计算必须被截断。数字社会对此深恶痛绝。被截断的计算不仅会导致溢出,而且实际上它们比更精确的计算慢,因为截断操作需要时间。出于这个原因,Java编程语言被更新以识别对于最佳性能和完美再现性的冲突需求。默认情况下,现在允许虚拟机设计者使用扩展精度进行中间计算。但是,使用strictfp关键字标记的方法必须使用严格的浮点操作,以产生可重复的结果。

例如,你能标记main

public static strictfp void main(String[] args)

然后,main方法中的所有指令都将使用严格的浮点计算。如果将类标记为strictfp,则其所有方法都必须使用严格的浮点计算。

这些血淋淋的细节与英特尔处理器的行为密切相关。在默认模式下,中间结果可以使用扩展的指数,但不能使用扩展的尾数。(Intel芯片支持尾数的截断而不会损失性能。)因此,默认模式和严格模式之间的唯一区别是,当默认计算不存在时,严格计算可能溢出。

如果你看这张纸条时目光呆滞,别担心。对于大多数常见的程序来说,浮点溢出不是一个问题。我们在这本书中没有使用strictfp关键字。

3.5.2 数学函数和常量

Math类包含您偶尔需要的各种数学函数,这取决于您所做的编程类型。

要获取数字的平方根,请使用sqrt方法:

double x = 4;
double y = Math.sqrt(x);
System.out.println(y); // prints 2.0

注意

println方法和sqrt方法之间有细微的区别。println方法对System.out对象进行操作。但是数学类中的sqrt方法不在任何对象上操作。这种方法称为静态方法。您可以在第4章中进一步了解静态方法。

Java编程语言没有操作符将一个量提升到一个幂:必须在Math类中使用pow方法。语句

double y = Math.pow(x, a);

将y设置为x的幂(x^a)。pow方法的参数都是double类型,它也返回double。

floorMod方法旨在解决一个长期存在的整数余数问题。考虑表达式n%2。每个人都知道,如果n是偶数,这是0;如果n是奇数,这是1。当然,当n为负时除外。然后是-1。为什么?当第一台计算机建立时,有人必须为整数除法和余数对负操作数的作用制定规则。数学家已经知道了几百年的最优(或“欧几里得”)规则:总是让余数大于等于0。但是,这些先驱们并没有打开数学教科书,而是提出了看似合理但实际上不方便的规则。

考虑这个问题。你计算时钟时针的位置。应用了一个调整,您希望规范化为介于0和11之间的数字。这很容易:(position + adjustment) % 12。但是如果调整是负的呢?你可能会得到一个负数。因此,您必须引入分支,或使用((position + adjustment) % 12 + 12) % 12。不管怎样,这都很麻烦。

floorMod方法更简单:floorMod(position + adjustment, 12) 总是生成介于0和11之间的值。(不幸的是,floorMod对负除数给出了否定的结果,但这种情况在实践中并不常见。)

Math类提供了常见的三角函数:

Math.sin
Math.cos
Math.tan
Math.atan
Math.atan2

指数函数及其逆函数、自然对数和十进制对数:

Math.exp
Math.log
Math.log10

最后,两个常数表示最接近数学常数π和E的近似值:

Math.PI
Math.E

提示

通过在源文件的顶部添加以下行,可以避免数学方法和常量的Math前缀:

import static java.lang.Math.*;

例如:

System.out.println("The square root of \u03C0 is " + sqrt(PI));

我们在第4章讨论静态引入。

注意

Math类提供了几种使整数算术更安全的方法。当计算溢出时,数学运算符会悄悄地返回错误的结果。例如,10亿乘以3(100000000*3)的计算结果为-1294967296,因为最大的int值刚刚超过20亿。如果您改为调用Math.multiplyExact(100000000, 3),则会生成一个异常。您可以捕获该异常,或者让程序终止,而不是静静地继续执行错误的结果。还有addExact、subtractExact、incrementExact、decrementExact、negateExact等方法,都带有int和long参数。

3.5.3 数字类型之间的转换

通常需要从一种数值类型转换为另一种数值类型。图3.1显示了合法转换。

图3.1 数字类型之间的合法转换

图3.1中的六个实心箭头表示没有信息丢失的转换。三个虚线箭头表示可能会丢失精度的转换。例如,一个大整数(如123456789)的位数超过了浮点类型所能表示的位数。当整数转换为浮点数时,得到的值具有正确的大小,但会丢失一些精度。

int n = 123456789;
float f = n; // f is 1.23456792E8

当两个值与一个二元运算符(如n+f,其中n是一个整数,f是一个浮点值)组合时,在执行操作之前,两个操作数都将转换为一个公共类型。

  • 如果其中一个操作数的类型为double,则另一个操作数将转换为double。
  • 否则,如果其中一个操作数的类型为float,则另一个操作数将转换为float。
  • 否则,如果其中一个操作数的类型为long,则另一个操作数将转换为long。
  • 否则,两个操作数都将转换为int。

3.5.4 转化

在前面的部分中,您看到了必要时,int值会自动转换为双精度值。另一方面,显然有一些时候你想把一个双精度数看作一个整数。数字转换在Java中是可能的,但是当然可能丢失信息。信息可能丢失的转换是通过强制转换完成的。强制转换的语法是在括号中给出目标类型,后跟变量名。例如:

double x = 9.997;
int nx = (int) x;

现在,变量nx的值为9,因为将浮点值强制转换为整数会丢弃小数部分。

如果要将浮点数舍入到最接近的整数(在大多数情况下,这是一个更有用的操作),请使用Math.round方法:

double x = 9.997;
int nx = (int) Math.round(x);

现在变量nx的值为10。调用round时仍然需要使用cast(int)。原因是round方法的返回值很长,并且long只能通过显式强制转换分配给int,因为存在信息丢失的可能性。

小心

如果试图将一个类型的数字强制转换为另一个超出目标类型范围的数字,则结果将是具有不同值的截断数字。例如,(byte)300实际上是44。

C++注意

不能在布尔值和任何数值类型之间强制转换。这种惯例防止了常见的错误。在极少数情况下,如果要将布尔值转换为数字,可以使用条件表达式,如b ? 1 : 0

3.5.5 结合赋值和运算符

在赋值中使用二元运算符有一个方便的快捷方式。例如,

x += 4

等价于

x = x + 4

(通常,将运算符放在=符号的左侧,例如*=%=)。

注意

如果运算符生成的值的类型与左侧的不同,则强制该值匹配。例如,如果x是int,那么语句

x += 3.5;

有效,将x设置为(int) (x+3.5)。

3.5.6 递增和递减运算符

当然,程序员知道使用数字变量最常见的操作之一是加或减1。遵循C和C++的脚步,Java具有增量和减量运算符:n++给变量n的当前值加1,而n–减去1。例如,代码

int n = 12;
n++;

将n改为13。由于这些运算符更改了变量的值,因此它们不能应用于数字本身。例如,4++不是合法声明。

这些运算符有两种形式;您刚刚看到了放在操作数后面的运算符的后缀形式。还有一个前缀形式,++n。两者都将变量的值更改为1。只有在表达式内部使用时,这两者之间的差异才会出现。前缀形式首先执行加法;后缀形式的计算结果是变量的旧值。

int m = 7;
int n = 7;
int a = 2 * ++m; // now a is 16, m is 8
int b = 2 * n++; // now b is 14, n is 8

我们建议不要在表达式内部使用++,因为这通常会导致代码混乱和令人讨厌的错误。

3.5.7 关系运算符和布尔运算符

Java有完整的关系操作符的补充。要测试相等性,请使用双等号,==。例如,值

3 == 7

是false。

使用a!=表示不等式。例如,值

3 != 7

是true。

最后,您有通常的<(小于)、>(大于)、<=(小于或等于)和>=(大于或等于)运算符。

Java遵循C++,使用&&用于逻辑与操作,使用||用于逻辑或操作。就像你很容易记住的那样!=运算符,感叹号!是逻辑否定运算符。&&||运算符以“短路”方式计算:如果第一个参数已经确定了值,则不会计算第二个参数。如果将两个表达式用&&运算符组合在一起,

expression1 && expression2

第一个表达式的真值被确定为假,那么结果就不可能是真的。因此,不计算第二个表达式的值。可以利用这种行为来避免错误。例如,在表达式中

x != 0 && 1 / x > x + y // no division by 0

如果x等于零,则从不计算第二部分。因此,如果x为零,则不计算1/x,并且不会出现被零除错误。

同样,如果第一个表达式为真,则expression1 || expression2的值自动为真,而不计算第二个表达式。

最后,Java支持三元表达式?:,这个偶尔很有用。表达式

condition ? expression1 : expression2

如果条件为真,则计算为第一个表达式,否则计算为第二个表达式。例如,

x < y ? x : y

给出x和y的较小值

3.5.8 位运算符

对于任何整数类型,都有可以直接处理组成整数的位的运算符。这意味着您可以使用掩码技术获取数字中的单个位。位运算符是

& (“and”) | (“or”) ^ (“xor”) ~ (“not”)

这些运算符处理位模式。例如,如果n是一个整数变量,那么

int fourthBitFromRight = (n & 0b1000) / 0b1000;

如果n的二进制表示中右边的第四位是1,则给出1,否则为0。使用&和适当的2次方能让你屏蔽所有,除了一个位。

注意

当应用于布尔值时,&|运算符生成布尔值。这些运算符类似于&&||运算符,只是&|运算符的计算方式不是“短路”,也就是说,两个参数都是在计算结果之前进行计算的。

还有>>和<<运算符,它们左右移动位模式。当您需要建立位模式来进行位屏蔽时,这些运算符非常方便:

int fourthBitFromRight = (n & (1 << 3)) >> 3;

最后,一个>>>运算符用零填充顶部的位,而>>运算符将符号位扩展到顶部的位。没有<<<运算符。

小心

移位运算符的右侧参数是约简模32(除非左侧参数是long,在这种情况下,右侧参数是约简模64)。例如,1<<35的值与1<<3或8的值相同。

C++注意

在C/C++中,不能保证>>是否执行算术移位(扩展符号位)或逻辑移位(用零填充)。实施者可以自由选择效率更高的方案。这意味着C/C++ >>算子可以产生负相关的实现结果。Java消除了不确定性。

3.5.9 括号和运算符层次结构

表3.4显示了运算符的优先级。如果不使用括号,则按指示的层次顺序执行操作。同一级别的运算符从左到右进行处理,但右相关的运算符除外,如表中所示。例如,&&的优先级高于||,因此表达式

表3.4 操作符优先级

操作符关联性
[] . ()(方法调用)左到右
! ~ ++ – +(一元) -(一元) ()(强制转化) new右到左
* / %左到右
+ -左到右
<< >> >>>左到右
< <= > >= instanceof左到右
== !=左到右
&左到右
^左到右
|左到右
&&左到右
||左到右
?:右到左
= += -= *= /= %= &= = ^= <<= >>= >>>=右到左
a && b || c

意味着

(a && b) || c

因为+=从右向左关联,所以表达式

a += b += c

意味着

a += (b += c)

也就是说,b+=c的值(加上后b的值)加在a上

C++注意

与C或C++不同,Java没有逗号运算符。但是,可以在for语句的第一个和第三个槽中使用逗号分隔的表达式列表。

内容概要:该研究通过在黑龙江省某示范村进行24小时实地测试,比较了燃煤炉具与自动/手动进料生物质炉具的污染物排放特征。结果显示,生物质炉具相比燃煤炉具显著降低了PM2.5、CO和SO2的排放(自动进料分别降低41.2%、54.3%、40.0%;手动进料降低35.3%、22.1%、20.0%),但NOx排放未降低甚至有所增加。研究还发现,经济性和便利性是影响生物质炉具推广的重要因素。该研究不仅提供了实际排放数据支持,还通过Python代码详细复现了排放特征比较、减排效果计算和结果可视化,进一步探讨了燃料性质、动态排放特征、碳平衡计算以及政策建议。 适合人群:从事环境科学研究的学者、政府环保部门工作人员、能源政策制定者、关注农村能源转型的社会人士。 使用场景及目标:①评估生物质炉具在农村地区的推广潜力;②为政策制定者提供科学依据,优化补贴政策;③帮助研究人员深入了解生物质炉具的排放特征和技术改进方向;④为企业研发更高效的生物质炉具提供参考。 其他说明:该研究通过大量数据分析和模拟,揭示了生物质炉具在实际应用中的优点和挑战,特别是NOx排放增加的问题。研究还提出了多项具体的技术改进方向和政策建议,如优化进料方式、提高热效率、建设本地颗粒厂等,为生物质炉具的广泛推广提供了可行路径。此外,研究还开发了一个智能政策建议生成系统,可以根据不同地区的特征定制化生成政策建议,为农村能源转型提供了有力支持。
<think>我们正在寻找Reactor库中SignalListenerFactory的用法文档。Reactor是一个响应式编程库,用于构建非阻塞应用。SignalListenerFactory可能用于监听信号事件,如onNext, onError, onComplete等。 由于Reactor的文档可能没有直接提到SignalListenerFactory,我们可能需要查看源代码或寻找相关讨论。 根据Reactor的版本,这个类可能位于不同的包中。用户提到的是`reactor.core.observability`,但在Reactor 3.x中,我们通常使用`reactor.core.publisher`包。在Reactor 3.4.0之后,引入了`reactor.core.observability`包吗?实际上,在Reactor 3.x中,我们并没有看到这个包。因此,这个类可能是用户自定义的,或者是另一个库的一部分?或者可能是Reactor 4.x中的内容? 然而,Reactor 4.x目前还没有发布(截至2023年,Reactor 3.x是主流版本)。因此,我们需要确认这个类的存在性。 经过搜索,在Reactor 3.x的官方文档中并没有找到`SignalListenerFactory`。在GitHub的Reactor Core仓库中搜索,也没有发现这个类。因此,它可能是用户自定义的,或者来自其他库。 但是,Reactor有一个`Signal`类,它代表一个事件信号。而监听信号的一种常见方式是使用`doOnXXX`操作符,例如`doOnNext`, `doOnError`等。 如果用户确实需要自定义信号监听,Reactor提供了`Flux`和`Mono`的`tap`操作符(在Reactor 3.5.0及以上版本)来使用一个`SignalListener`接口。在Reactor 3.5.0中,引入了`SignalListener`接口和`SignalListenerFactory`。 因此,我们假设用户使用的是Reactor 3.5.0或更高版本。 在Reactor 3.5.0中,`SignalListener`和`SignalListenerFactory`位于`reactor.core.observability`包中。 文档: `SignalListener`是一个接口,用于监听来自`Publisher`的信号。它包含以下方法(可能): - `doOnEvent(Signal)`: 当有信号发生时被调用。 - `doOnComplete()`: 当序列完成时被调用。 - `doOnError(Throwable)`: 当发生错误时被调用。 - `doOnCancel()`: 当被取消时被调用。 - `doOnRequest(long)`: 当有请求时被调用。 - `doOnSubscribe(Subscription)`: 当被订阅时被调用。 但实际上,在Reactor 3.5.0中,`SignalListener`接口定义如下(根据源代码): ```java public interface SignalListener<T> { default void doOnComplete() {} default void doOnError(Throwable error) {} default void doOnNext(T value) {} default void doOnSubscribe(Subscription subscription) {} default void doOnCancel() {} default void doOnRequest(long request) {} default void doOnAfterComplete() {} default void doOnAfterError(Throwable error) {} default void doOnAfterNext(T value) {} // ... 可能还有其他方法 } ``` 而`SignalListenerFactory`用于创建`SignalListener`实例。它通常与`tap`操作符一起使用。 用法示例: 1. 首先,创建一个实现`SignalListener`的类,例如: ```java class MySignalListener<T> implements SignalListener<T> { @Override public void doOnNext(T value) { System.out.println("Next: " + value); } // 根据需要覆盖其他方法 } ``` 2. 然后,创建一个`SignalListenerFactory`,用于生成这个监听器的实例: ```java class MySignalListenerFactory implements SignalListenerFactory<MySignalListener<Object>> { @Override public MySignalListener<Object> createListener(Publisher<?> source, ContextView context) { return new MySignalListener<>(); } } ``` 3. 在流中使用`tap`操作符: ```java Flux<Integer> flux = Flux.just(1, 2, 3) .tap(new MySignalListenerFactory()); ``` 注意:`tap`操作符需要Reactor 3.5.0及以上版本。 另外,`SignalListenerFactory`还可以返回一个`SignalListener`的装饰器,用于支持多线程等。 由于官方文档可能没有详细说明,我们可以参考Reactor的源代码和测试用例。 参考链接: - [Reactor 3.5.0 Release Notes](https://github.com/reactor/reactor-core/releases/tag/v3.5.0) 提到了`tap`操作符和`SignalListener`。 - 相关类:`reactor.core.observability.SignalListener` 和 `reactor.core.observability.SignalListenerFactory`。 因此,我们可以总结如下: `SignalListenerFactory`用于创建`SignalListener`实例,然后通过`tap`操作符将其附加到`Flux`或`Mono`上,以监听各种信号事件。 示例代码: ```java import reactor.core.observability.SignalListener; import reactor.core.observability.SignalListenerFactory; import reactor.core.publisher.Flux; import reactor.util.context.ContextView; public class SignalListenerExample { static class MySignalListener<T> implements SignalListener<T> { @Override public void doOnNext(T value) { System.out.println("Next: " + value); } } static class MySignalListenerFactory implements SignalListenerFactory<MySignalListener<Object>> { @Override public MySignalListener<Object> createListener(Publisher<?> source, ContextView context) { return new MySignalListener<>(); } } public static void main(String[] args) { Flux.just(1, 2, 3) .tap(new MySignalListenerFactory()) .subscribe(); } } ``` 运行上述代码,将会在控制台打印每个元素的值。 注意:`SignalListenerFactory`是一个函数式接口,只有一个方法,因此也可以使用Lambda表达式,但需要类型匹配。 由于`SignalListenerFactory`是泛型接口,我们需要指定监听器的类型。上面的例子中,我们使用了`MySignalListener<Object>`,但也可以使用具体的类型。 另外,`createListener`方法的第二个参数`ContextView`可以用于获取当前上下文的信息。 因此,用户可以根据需要自定义监听器,并通过工厂将其注入到响应式流中。 以上即为`SignalListenerFactory`的用法。 如果用户需要更详细的信息,建议查看Reactor 3.5.0的官方文档(如果有的话)或源代码。 注意:在Reactor 3.5.0中,这个功能可能还处于实验阶段,后续版本可能有调整。 相关问题: 1. 如何在Reactor中监听流的事件? 2. Reactor 3.5.0中`tap`操作符的作用是什么? 3. 如何自定义一个`SignalListener`来记录响应式流的事件? 4. `SignalListenerFactory`在Reactor中的使用场景有哪些? 5. 除了`tap`操作符,Reactor还有哪些方式可以监听信号事件?</think>### Reactor库中`SignalListenerFactory`的用法文档 在Reactor库中,`reactor.core.observability.SignalListenerFactory` 是一个**实验性 API**(标记为`@Experimental`),用于创建自定义信号监听器,以观察和响应响应式流中的事件(如`onNext`、`onError`、`onComplete`等)。以下是关键用法说明: --- ### 核心功能 1. **监听信号事件** 通过实现`SignalListener`接口,可捕获以下事件: - `doOnNext(T value)` - `doOnError(Throwable error)` - `doOnComplete()` - `doOnCancel()` - `doOnRequest(long n)` - `doOnSubscribe(Subscription s)` 2. **工厂模式** `SignalListenerFactory` 负责按需创建监听器实例,支持上下文感知的监听器初始化。 --- ### 基本用法示例 ```java import reactor.core.observability.*; import reactor.core.publisher.*; // 1. 自定义信号监听器 class CustomSignalListener<T> implements SignalListener<T> { @Override public void doOnNext(T value) { System.out.println("[Next] " + value); } @Override public void doOnError(Throwable error) { System.out.println("[Error] " + error.getMessage()); } } // 2. 实现监听器工厂 class CustomFactory implements SignalListenerFactory<CustomSignalListener<Object>> { @Override public CustomSignalListener<Object> createListener(Publisher<?> source, ContextView context) { return new CustomSignalListener<>(); } } // 3. 在流中附加监听器 Flux.just("A", "B", "C") .tap(new CustomFactory()) // 通过tap()操作符注入 .subscribe(); ``` **输出**: ``` [Next] A [Next] B [Next] C ``` --- ### 关键特性 1. **上下文集成** `createListener` 的 `ContextView` 参数允许访问响应式流的上下文信息: ```java public CustomSignalListener<Object> createListener(Publisher<?> source, ContextView context) { String id = context.getOrDefault("traceId", "UNKNOWN"); return new CustomSignalListener(id); // 传递上下文数据 } ``` 2. **资源管理** 监听器可实现`SignalListener`接口提供生命周期钩子: ```java class CustomSignalListener<T> implements SignalListener<T> { @Override public void doFinally(SignalType terminationType) { // 流终止时释放资源 } } ``` 3. **与`Metrics`集成** 可用于自定义指标采集(需配合`Micrometer`等库): ```java public void doOnNext(T value) { Metrics.counter("events.processed").increment(); } ``` --- ### 注意事项 1. **实验性状态** 此 API 在 Reactor 3.5+ 中标记为 `@Experimental`,未来可能变更[^1]。 2. **性能影响** 避免在监听器中执行阻塞操作,可能破坏非阻塞特性。 3. **替代方案** 简单场景优先使用标准操作符: ```java Flux.just(1, 2, 3) .doOnNext(v -> System.out.println("Received: " + v)) .doOnError(e -> System.err.println("Failed: " + e)) .subscribe(); ``` --- ### 典型应用场景 1. 分布式追踪(如集成 OpenTelemetry) 2. 自定义指标监控 3. 审计日志记录 4. 流事件诊断工具开发 > **官方参考**: > 详细 API 文档见 [Reactor 3.5 Javadoc - SignalListenerFactory](https://projectreactor.io/docs/core/release/api/reactor/core/observability/SignalListenerFactory.html)[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值