Java 中的实体和模型应该使用 Primitive 还是 Object?

Should I use Primitive or Object for entity and models in Java?

作为 Java 的初学者,我在选择实体或模型中的变量类型时感到困惑 类。正如 Java Primitives versus Objects 中提到的,它们都各有利弊,例如内存使用情况。但是,我想知道创建实体或模型时的一般方法 类。因此,在这种情况下,我应该更喜欢 Primitive 还是 Object 类型的 long、boolean、int 等变量,如下所示?

public class Student {
    private Long id;              // long ?
    private String name;    
    private Boolean isGraduated;  // boolean ?
    private Integer age;          // int ?
}

我认为没有理由在您的案例中使用对象,但也不认为使用它们会对性能产生很大影响。就个人而言,如果我没有特定的理由使用对象——例如需要 ArrayList<Integer> 或任何其他通用集合——我会使用原语。

然后,如果我确实需要大量整数,我会使用数组(例如 int[])。

编辑: 评论中有人问为什么人们更喜欢 List<T> 而不是传统数组。 ArrayList<T> 等集合是可变的并且易于使用。比较向 ArrayList<T> 添加元素:

List<MyClass> collection = new ArrayList<MyClass>();
collection.add(new MyClass());

...向数组添加元素:

public static int[] addElement(int sourceArr[], int e)
{
    int newArr[] = new int[sourceArr.length + 1];

    for (int i = 0; i < sourceArr.length; i++)
    {
        newArr[i] = sourceArr[i];
    }

    newArr[newArr.length - 1] = e;
    return newArr;
}

// ...

int arr[] = { 1, 2, 3 };
arr = addElement(arr, 4); // { 1, 2, 3, 4 }

LongInteger 变量的默认值为 null

但是longint的默认值是0

比较:

如果您使用对象:

public class Student {
    private Long id;              // long ?
    private String name;    
    private Boolean isGraduated;  // boolean ?
    private Integer age;          // int ?
}

id => null
age => null

如果你使用原语:

public class Student {
    private long id;              // long ?
    private String name;    
    private Boolean isGraduated;  // boolean ?
    private int age;          // int ?
}

id => 0
age => 0

在许多情况下,将 0 作为默认值会造成混淆,因此在这种情况下使用对象是有意义的。如果对象值为“null”,您可以确定该值未初始化。但是如果原始值是“0”,那么就不清楚了:它是一个默认的未初始化值,还是这个值被初始化为“0”。

我通常更喜欢使用原始类型,除非我需要显式空值检查

两者都有效

正如您所指出的,基元需要更少的内存。但是对象有优势,尤其是在某些需要对象而不是原始对象的情况下。在某些情况下,可能需要一个原语,尽管自动装箱通常可以处理。遇到这些情况,编译器肯定会提醒你的。

关于基元具有默认值而对象没有。根据编码情况,两者都可能是一个优势。

请记住,对于您的大部分工作,您不应该担心这个。这种担心是 过早优化 的一个例子。您几乎肯定有更重要的事情要担心。

对于许多常见的商业应用程序,内存使用是无关紧要的。为了方便和功能,我自己的默认设置是使用对象。从我多年来注意到的情况来看,使用情况似乎相当分裂,有些人倾向于基元,有些人倾向于对象。

唯一让我三思而后行的情况是,在处理大量数据的循环时可能会调用 auto-boxing。在这种情况下,仅声明 intInteger 可能会通过避免无意义的装箱而产生重大影响。但是,同样,不要过分担心这一点,因为依靠自动装箱是完全​​可以接受的,通常是最好的策略。

record

Java 16 现在提供 records 功能。记录是编写 class 的一种简短方式,其主要目的是透明且不可变地传递数据。您只需声明成员字段类型和名称。编译器隐式生成构造函数、getter、equals & hashCodetoString 方法。

我提到记录只是为了说明这个方便的新功能,它可能会变得非常流行,同时支持对象和基元。所以使用记录不会影响对象与原始的决定。

您的示例 class 将是:

public record Student( Long id , String name , Boolean isGraduated , Integer age ) {}

… 或:

public record Student( long id , String name , boolean isGraduated , int age ) {}

用法与常规class相同。

Student alice = new Student( 101L , "Alice" , false , 33 ) ;

模糊差异

您应该知道 Oracle 的 Java 团队和相关社区有兴趣模糊原语和对象之间的区别。当然,这样做的诀窍在于尊重作为 Java 平台标志的极端向后兼容性。

部分工作作为 Project Valhalla 的一部分完成,包括可能将 值类型 添加到 Java。

要了解有关这方面未来可能方向的更多信息,请参阅 Oracle 的 Brian Goetz 和其他人的演讲。使用您最喜欢的搜索引擎 search for “Java JEP primitive”。您会找到 JEP 的链接,例如:

正如其他人所说,您可以使用任何一种方法。

作为 Basil Bourque ,如果您使用内存使用(或某种其他类型的运行时效率度量)作为决定的原因...这很可能是 过早的优化。很可能差异太小而不重要。


那么你应该如何决定?

这取决于你。但是>>我的<<规则是:

  • 如果建模告诉你需要能够表示“这个字段未设置”(或类似的),那么你可以1使用(比如)Integer 而不是 int ...并考虑使用 null 来表示“未设置”的情况。

  • 当您需要将类型用作泛型参数时(如List<Integer>),您别无选择,只能使用对象类型。 (在 Java 的当前版本中。)

  • 如果您没有特定理由使用对象类型(例如 Integer),请使用原始类型(例如 int)。换句话说,这是我的默认选择。


为什么 >>my<< 默认选择 使用基本类型?

因为这样的事情:

private Integer field;  // defaults to null which may lead to an NPE

Integer a = ...
Integer b = ...

if (a == b) // MISTAKE

换句话说,原始包装类型的某些方面比它们的原始类型等价物更容易受到程序员错误的影响。


1 - 不利的一面是,如果您支持“unset”,它会使您的代码更加复杂。如果您使用可空类型来执行此操作,那么您可以测试 null 并在您使用(裸)字段的任何地方适当地处理它。因此,最好设计您的系统,使“未设置”字段成为不可能。