Java有可变的整数、浮点数、双精度和长整型类型吗?

102

9
为什么你想要做这件事? - helpermethod
3
对于某些情况(例如:一个游戏,保存一件携带n卡路里的食物,它可以被消耗或添加),最好使用以用途命名的类(例如:class FoodItem { int calories; }),因为这样更清晰,如果需要,还可以添加方法。 - GKFX
8
Java 8的Lambda表达式仅适用于有效final变量。为了解决这个限制,需要使用可变的int。 - Alex
3
你可能需要一个计数器,需要在方法之间传递。传递一个 int 不起作用,因为如果它在一个方法中被增加,那么该值不会反映在另一个方法中。 - Adam Burley
7个回答

111

如果为了避免包含一个可变包装类的代码过于繁琐,您可以将值始终包装在数组中,例如 int[] mutable = {1};


3
这个非常聪明,几乎不占用空间。此外,它避免了使用 AtomicInt 进行同步。 - CompEng88

66

Java并没有内置这些功能,这是有原因的。使用可变类型是危险的,因为它们很容易被误用。此外,实现它非常容易。例如,commons-lang有一个MutableInt


7
我猜Java开发人员希望Integer的“行为”类似于int,即一旦您拥有对它的引用,它就不会改变(避免混淆)。 但是,对我来说似乎仍然奇怪的是没有可变选项... - rogerdpack
45
“使用可变类型是危险的,因为它们很容易被误用。”大多数东西都可能被误用。不可变类型存在是为了安全起见,尽管它们可以通过反射进行更改。除了那些情况外,没有理由阻止人们在需要时使用可变类型。 - GKFX
2
更好的说法是,不可变类型在例如映射和集合中会减少混淆。如果您有一个可变的 Integer 并改变了它在键值对中的值,那么它会破坏整个集合。但是,如果您需要一些可变的 Integer 实现,那么最简单的方法就是创建一个包含 int 值的类。您可以在其中进行创意设计 - 让它成为计数器或倒计时,而不仅仅是一个启用任何东西的普通 int。赋予它一些逻辑(除非您正在使用自下而上的 Java EE 开发)。 - Vlasec
1
这确实是一个老问题,但希望有人能回答如何滥用它们。使用它们有什么危险之处? - The Fluffy Robot
正如Vlasec所评论的那样,如果您意外修改了一个在其他地方使用过的变量,它会导致奇怪的错误。更糟糕的是,假设您正在编写一个与客户代码配合工作的程序。您需要实现一个具有可变整数类型参数的方法。现在,如果您出于任何原因修改了可变整数,则它也会在客户端程序中更改,这是完全意外的,并且会导致难以找到的错误。 - GregT

62

JDK 1.5以后引入了java.util.concurrent.atomic.AtomicInteger

这是一个线程安全的可变整数类型,以下是使用示例:

final AtomicInteger value = new AtomicInteger(0);
之后发生了这样的事情:
value.incrementAndGet();

21
这种线程安全性会带来性能损耗,在许多情况下是不必要的。例如,我常见的用例是增加由lambda捕获的计数器。使用原子操作是过度的。 - Minas Mina

13

这是我为可变整数编写的一个小型类:

public class MutableInteger {
    private int value;
    public MutableInteger(int value) {
        this.value = value;
    }
    public void set(int value) {
        this.value = value;
    }
    public int intValue() {
        return value;
    }
}

你可以轻松地将此扩展到任何其他基元。当然,就像其他人所说的那样,你应该谨慎使用它。


1
在我看来,最好的选择是不使用原子整数,因为它不会给出关于并发性的错误线索。至于数组,我从不在我的代码中这样做... ;) - Snicolas
1
提供一个通用的包装类可能是个好主意,这样你就不会得到 MutableIntegerMutableDoubleMutableString 等等,而是有一个 Mutable<Integer>Mutable<Double> 等等。使用 Integer 而不是 int 的内存开销通常不会被考虑在内。但是你可以得到一个单一的、可直接使用的类,可以处理大多数情况(如果你想让你的整数可比较或类似的事情,你仍然需要子类化)。 - Qw3ry
这可能是个好主意,将其扩展为Number - Stijn de Witt

6

AtomicInteger 已经被提到过了。可以使用 AtomicReference<Double> 来模拟可变的 Double。前面提到的警告仍然适用,这种方式不太合理,但有时候你会遇到这样的代码。

double sum=0
for (Data data:someListGenerator())
  sum+=data.getValue()

我希望将其重构为Java 8函数式风格。如果代码遵循这种模式但会增加相当的复杂性,最明智的转换方式可能

AtomicReference<Double> sumref=new AtomicReference<>(0d);
someStreamGenerator().forEach(data->
  sumref.set(sumref.get().doubleValue()+data.getValue()));
double sum=sumref.get().doubleValue();

当然,这至少是有问题的风格。但我发现自己不止一次陷入了一个扭曲的循环中,它在计算并部分累积三个不同的信息从ResultSet中。这使得将代码转换为适当的功能样式变得非常困难。根据上述模式转换累加部分对我来说似乎是在清理代码和过度简化重构之间的合理折衷。

你应该认真考虑你的措辞:即使你使用lambda,你的例子也不是“函数式”的,因为它禁止副作用。如果你真的写成函数式的,你就不需要可变对象:someStreamGenerator().mapToDouble(Data::getValue).sum()。即使要累积三个不同的信息,也可以通过使用Stream.reduceStream.collect来实现“函数式”的方式。我看不出将每个循环重构为函数式代码片段的理由,但如果你想走这条路,就应该一直走到底。 - Tobias Liefke
1
正如我所写的:这不是正确的风格,对于任何新代码都不应该使用。但是当重构超过15年演变而来的400,000多行代码时,您会发现构造真正功能性构造的正确重写可能非常困难。然后,重写为这种混合样式可以是一个明智的折衷方案,因为您至少可以使基本结构对齐。forforeach可以成为复杂框架的一部分,这些框架已经被功能性地重写,因此您必须在它们周围以某种方式对齐其余代码。 - Dirk Hillbrecht
你混淆了Lambda和函数式编程。在你的例子中,你没有将其“函数化”。而且,为什么要用Lambda重写所有内容呢?这样会降低可读性,但并不能获得函数式编程的优势。为什么一个人要试图通过使用这种混合方法来消除复杂框架中的每个循环呢? - Tobias Liefke
1
你的代码中有1000个地方使用了某种惯用语。这个惯用语是由一个库或者其他全局结构所驱动的。你将这个全局结构替换为一个基于函数式编程和lambda表达式的新结构。其中有998个地方可以干净利落地转换成函数式风格,但是还有2个地方非常难以适当地进行转换。在这种情况下,我总是要求使用这种混合构造将其转换为新的风格。只有这样,才能摆脱旧的全局结构。虽然不完美,但整体的代码质量会提高。 - Dirk Hillbrecht

6
你可以像@Alexandre建议的那样,使用nnnn[]作为任何基本类型的可变对象,Java还有AtomicInteger和AtomicLong。
在我看来,int通常比Integer更好,因为它是可变的。
你需要一个可变对象的更多细节,也许有另一种方法可以实现相同的事情。

8
除非被声明为finalint类型始终可变。 - Peter Lawrey
19
说一个原始类型是可变的并不正确。如果你只是指向一个新对象,那么每个非final对象也可以被改变。例如,“Integer a = 4;”,然后“a = 5;”是有效的代码,但是Integer并不是可变的。 - mjaggard
我同意Integer实例即使引用它们是可变的,但这是不同的类型。 - Peter Lawrey
这不是一个好的引用int参数的方法吗?例如,一个返回分页项目列表(来自数据库)以及总记录数的函数。在这种情况下,AtomicInteger或MutableInteger似乎很有用。当然,另一种方法是拥有一个getTotalRecords属性,而不是在同一方法中返回它。 - msanjay
2
int 不可变是因为如果你将其传递给一个方法,该方法无法更改其值并在调用方法中反映新的值。 - Adam Burley
@AdamBurley 虽然你不能通过引用传递任何局部变量,但这并不是互通性的意思。 - Peter Lawrey

3
您可以导入org.omg.CORBA包(或者只导入您需要的类),然后在其中使用Holder类。
例如,它有一个“IntHolder”,其中存储整数的字段是公共的,允许访问并进行修改。
public static void triple(IntHolder x){
    x.value = 3 * x.value;
}

IntHolder mutableInt = new IntHolder(10);
triple(mutableInt);     
System.out.println(mutableInt.value);

它还有“LongHolder”和“DoubleHolder”等其他可用选项。请谨慎使用。
以下是其API文档链接:https://docs.oracle.com/javase/7/docs/api/org/omg/CORBA/package-summary.html

2
虽然这是解决问题的非常聪明的方法,但随着Java9的到来,您可能不想使用它——那将为一个int holder导入整个CORBA模块。 - Agoston Horvath
Java 11中还删除了CORBA包。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接