什么时候在Java中使用原始vs类?


54

我看到Java具有Boolean(类)vs boolean(原始)。同样,有一个Integer(类)vs int(原始)。何时使用原始版本与类的最佳实践是什么?除非我有特定的理由(性能?),否则我是否应该基本上一直使用类版本?什么是最常见的,公认的使用方式?



在我看来,这些不是课程,而是盒子。它们仅在此处,因此您可以在需要对象的地方使用原始体,即在集合中。您不能添加两个Integer(您可以假装,但实际上Java会为您自动装箱|对值装箱)。
stonemetal

Answers:


47

在有效Java的第5项中,Joshua Bloch说

这课很清楚:相对于装箱的图元,更喜欢图元,并提防意外的自动装箱

类的一个很好的用途是当将它们用作泛型类型(包括Collection类,例如列表和地图)时,或者当您希望将它们转换为其他类型而没有隐式转换时(例如,Integer类具有方法doubleValue()byteValue()

编辑:约书亚·布洛赫(Joshua Bloch)的原因是:

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

该程序得到了正确的答案,但是由于一个字符的印刷错误,它的速度要慢得多。将该变量sum声明为a,Long而不是a long,这意味着程序构造了大约2 ^ 31个不必要的Long实例(每次将大约long i添加到时,大约一个Long sum)。将总和的声明从更改Longlong可以将我的机器上的运行时间从43秒减少到6.8秒。


2
如果您列出Bloch的原因,而不只是引用他的结论,那将会有所帮助!
vaughandroid13年

@Baqueta我已经编辑了帖子。原因是性能。
m3th0dman

谢谢,这有点清楚。我现在有理由发表自己的答案。:)
vaughandroid13年

您可能会发现lmax体系结构很有趣-“提高另一个数量级需要更多的技巧。LMAX团队发现有几件事情可以帮助实现这一目标。其中之一是编写设计的java集合的自定义实现。做到这一点的一个例子是使用原始的Java long作为具有特殊编写的数组支持的Map实现的hashmap键。”

JIT应该处理的事情
deFreitas

28

除非您要处理泛型(除非您知道自动装箱和拆箱!),否则标准做法是使用原语。

遵循该约定有很多充分的理由:

1.避免简单的错误:

有一些微妙的,非直觉的案例通常会吸引初学者。即使是经验丰富的编码人员也会滑入并有时犯下这些错误(希望他们在调试代码并找到错误时会发誓!)。

最常见的错误是使用a == b而不是a.equals(b)。人们习惯于a == b使用基元,因此在使用对象包装器时很容易做到。

Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
    // This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
    // This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
    // Whether this gets executed depends on which compiler you use!
}

2.可读性:

考虑以下两个示例。大多数人会说第二个更具可读性。

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

3.性能:

事实上,它速度较慢使用对象封装器原语比只使用原语。您要将对象实例化,方法调用等的成本添加到各处使用的事物中。

克努斯(Knuth)的“……大约有97%的时间说:过早的优化是万恶之源”的说法在这里并不适用。他在谈论使代码(或系统)更加复杂的优化-如果您同意第2点,那么这是使代码减少复杂性的优化!

4.约定:

如果您对99%的其他Java程序员做出不同的风格选择,则有两个缺点:

  • 您会发现其他人的代码更难阅读。99%的示例/教程/等都将使用原语。每当您阅读一本书籍时,您就会产生额外的认知负担,要思考一下它以您惯用的风格呈现的样子。
  • 其他人会发现您的代码更难阅读。每当您在Stack Overflow上提出问题时,都必须筛查“为什么不使用原语?”的答案/注释。如果您不相信我,只需看看人们在诸如括号放置之类的问题上所经历的斗争,这甚至都不会影响所生成的代码!

通常,我会列出一些反对意见,但老实说,我不认为有任何好的理由不遵守这里的约定!


2
不要建议人们将对象与进行比较==。对象应与进行比较equals()
TulainsCórdova'13

2
@ user61852我并不是建议这样做,而是犯了一个常见错误!我应该更清楚一点吗?
vaughandroid13年

是的,您没有提到应该将对象与equals()... 进行比较...而是给它们一种解决方法,以便将对象与进行比较可以==产生预期的结果。
图兰斯·科尔多瓦

好点子。改了
vaughandroid13年

我添加equals()了第二个代码段并更改了我的投票。
图兰斯·科尔多瓦

12

通常我会选择原始元素。但是,使用像Integer和这样的类的一个特殊之处Boolean是可以分配null给那些变量。当然,这意味着你必须做的null检查,所有的时间,但还是更好地得到一个NullPointerException,而不是有因使用一些逻辑错误intboolean尚未正确初始化变量。

当然,自Java 8以来,您可以(而且应该应该)走得更远,而不是例如Integer可以使用Optional<Integer>可能具有或没有值的变量。

另外,它引入了用于null为这些变量分配“ 未知 ”或“ 通配符 ”值的可能性。在某些情况下,例如在三元逻辑中,这可能很方便。或者您可能想检查某个对象是否与某个模板匹配;在这种情况下,您可以使用null模板中那些在对象中可以具有任何值的变量。


2
(不是我的不赞成,但是...)Java已经检测到未初始化的变量,并且不允许您读取变量,直到导致其使用的每个代码路径都明确分配了一个值。因此,您可以从null默认分配功能中获益匪浅。相反:最好不要完全“初始化”变量。设置任何默认值,甚至null,都将关闭编译器……但也阻止它检测到所有代码路径中缺少有用的分配。因此,编译器可能会捕获的错误会进入运行时。
cHao 2014年

@cHao但是,如果没有明智的默认值来初始化变量怎么办?您可以将其设置为0.0,或-1,或Integer.MAX_VALUEFalse,但是最后您不知道这是默认值还是分配给该变量的实际值。在这很重要的情况下,拥有null价值可能会更清晰。
tobias_k

这还不清楚,只是更容易告诉编译器在错误已传播之前不要警告您有关逻辑不清晰的信息。:P如果没有合理的默认值,请不要初始化变量。仅当您有合理的价值可放置时,才进行设置。如果您的值可能未正确设置,则Java可以在编译时阻止您。

@cHao我的意思是,在某些情况下,您可能无法初始化变量,而必须在运行时进行处理。在这种情况下,可以清楚地将其识别为默认值或“未初始化”的“默认值”(例如“ null”)可能比也可能是有效值的任何编译时初始化要好。
tobias_k

你有这种情况吗?我能想到的情况是在接口边界处(例如:作为参数或返回类型)...但是即使在那里,如果包装起来,它们也会更好。(赤裸裸的nulls带来了很多问题,包括无效的妄想症。)在一个函数中,一个可能在使用时实际上未初始化的变量通常表示未发现的情况。(确定分配分析很简单,因此可能会出现误报。但是您通常可以通过简化逻辑来解决它们,因此。)
cHao


0

正如m3th0dman指出的那样,Java具有自动装箱功能。考虑一下最低的级别,您会发现自动装箱(输入或输出)原始值将意味着如果您在应用程序周围使用本机数据类型,则不需要执行某些任务的时钟周期。

通常,您应该尽可能使用本机数据类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.