Java支持结构体吗?

133

Java有类似于C++ struct 的替代品吗?

struct Member {
  string FirstName; 
  string LastName;  
  int BirthYear; 
};

我需要使用自己的数据类型。


2
对于那些过于字面的复制者:在Java中,字段名称确实需要以小写字符开头,因此应该使用firstNamelastNamebirthYear(或者当然也可以使用yearOfBirth)。 - Maarten Bodewes
1
请选中此答案,作为关于即将到来的“内联类”的答案。据我所知,所有其他答案都使用常规对象的变体。 - alife
13个回答

111

在Java中与结构体相当的是

class Member
{
    public String firstName; 
    public String lastName;  
    public int    birthYear; 
 };

在适当的情况下做这样的事情是没有问题的。实际上,这与在C++中何时使用结构体以及何时使用封装数据的类非常相似。


23
在Java中,我会在与C ++相同的情况下使用“POD”类型的类。在J2EE中,当一个方法需要同时返回多个值时,“值对象”是一个很好的选择。没有必要使用getter和setter方法来封装数据,只需将字段设为公共的 - 这样更加简短且更具描述性。任何“正式”的事物,我倾向于像在C++中一样进行封装。因此,我坚持我最初的评论,但这是一种风格选择。我认为你不会失去任何东西,实际上我认为当你拥有一个值类时,更具描述性。 - Tom Quarendon
18
Java中不需要在类的结尾加分号。 - Chef Pharaoh
32
如果我错了,请纠正我,但是结构体的重点在于它是按值而不是按引用传递的。类始终按引用传递。这就是为什么这个答案是错误的原因。 - ULazdins
5
我认为你混淆了C#结构体和C++结构体。当你在C++中将一个类传递给方法时,它会调用复制构造函数,默认情况下会复制每个字段。你必须明确使用语法来按引用传递它。 - Ronaldo Nazarea
8
@PabloAriel,我不同意我之前的评论了。我的想法可以随着7年时间的演变而改变; ) - Michael Berry
显示剩余10条评论

71

Java 绝对没有结构体:) 但您在此描述的看起来像是 JavaBean 类。


36
一个只包含公共变量的类看起来非常像一个结构体。 - mglauche
23
在此回答后的9年,即Java 14发布后,我们将拥有记录。时间过得真快,这真是有趣。 - George Leung
9
类和结构体之间的一个主要差别在于它们在内存中的行为方式。在这方面,这个回答是错误的。Java 没有类似于结构体的东西,但希望在不久的将来可以使用 "inline class",我认为它更像一个结构体。 - Will Calderwood
一个bean具有属性(即getter和通常的setter),现在通常会对其进行注释以符合bean的要求。 - Maarten Bodewes
结构体在面试中是非常好的东西。我有一个企业产品,没有使用结构体。C# 7.0 - zzfima
1
@WillCalderwood,太好了。 我看到它被认为是优化的C#代码胜过优化的Java的最大原因之一。 我认为问题来自于一个完整的工程师世代,他们以前从未接触过像C这样的复合值类型。 他们认为所有的东西都是指针(ref)或原始数据类型。 实际上,Valhalla团队已经改变了他们关于名称和“inline classes”(等等)的想法,可能会成为“primitive classes”。 我只希望他们合并这个东西,因为结构体和具体泛型非常重要。 - alife

51

Java 14增加了Records的支持,它是一种非常容易构建的结构化数据类型。

您可以像这样声明Java记录:

record Person(String name, int age) { }

public record AuditInfo(
    LocalDateTime createdOn,
    String createdBy,
    LocalDateTime updatedOn,
    String updatedBy
) {}
 
public record PostInfo(
    Long id,
    String title,
    AuditInfo auditInfo
) {}

此外,Java编译器将生成与 AuditInfo 记录相关的以下Java类:

public final class PostInfo
        extends java.lang.Record {
    private final java.lang.Long id;
    private final java.lang.String title;
    private final AuditInfo auditInfo;
 
    public PostInfo(
            java.lang.Long id,
            java.lang.String title,
            AuditInfo auditInfo) {
        /* compiled code */
    }
 
    public java.lang.String toString() { /* compiled code */ }
 
    public final int hashCode() { /* compiled code */ }
 
    public final boolean equals(java.lang.Object o) { /* compiled code */ }
 
    public java.lang.Long id() { /* compiled code */ }
 
    public java.lang.String title() { /* compiled code */ }
 
    public AuditInfo auditInfo() { /* compiled code */ }
}
 
public final class AuditInfo
        extends java.lang.Record {
    private final java.time.LocalDateTime createdOn;
    private final java.lang.String createdBy;
    private final java.time.LocalDateTime updatedOn;
    private final java.lang.String updatedBy;
 
    public AuditInfo(
            java.time.LocalDateTime createdOn,
            java.lang.String createdBy,
            java.time.LocalDateTime updatedOn,
            java.lang.String updatedBy) {
        /* compiled code */
    }
 
    public java.lang.String toString() { /* compiled code */ }
 
    public final int hashCode() { /* compiled code */ }
 
    public final boolean equals(java.lang.Object o) { /* compiled code */ }
 
    public java.time.LocalDateTime createdOn() { /* compiled code */ }
 
    public java.lang.String createdBy() { /* compiled code */ }
 
    public java.time.LocalDateTime updatedOn() { /* compiled code */ }
 
    public java.lang.String updatedBy() { /* compiled code */ }
}

请注意,构造函数、访问器方法以及equalshashCodetoString方法已经为您创建,因此使用Java Records非常方便。

Java Record可以像任何其他Java对象一样创建:

PostInfo postInfo = new PostInfo(
    1L,
    "High-Performance Java Persistence",
    new AuditInfo(
        LocalDateTime.of(2016, 11, 2, 12, 0, 0),
        "Vlad Mihalcea",
        LocalDateTime.now(),
        "Vlad Mihalcea"
    )
);

6
这完全忽略了结构体的本意(一种复合数据类型,不需要“分配内存”,可以直接在堆栈上存在而无需引用)。请参见此答案 https://dev59.com/92435IYBdhLWcg3w3EEi#57643471,有关即将推出的“内联类”。 - alife
一个记录就是相同的类/对象范例。它的行为与结构体完全不同。如果你传递它,你传递的是引用值,而不是值本身,并且你仍然受到垃圾回收器的支配。在 Valhalla 稳定之前,OP 可以通过一个具有公共字段的普通类来实现最接近的视觉效果。不是一个带有自动生成访问器等花哨类。在结构体中不需要访问器。 - alife
抛开你对“永远”的看法,弗拉德,我的意思是记录提供的近似结构的内容,一个简单的类已经给了你。记录是不可变的(浅层),而结构/类则不是。记录并没有回答OP的问题...这是一种不同的技术。除非我们指定额外的标准(不可变性、访问器等),否则记录绝不是想要结构的人的解决方案。 - alife
你有C或C++的经验吗?C++创建的对象默认情况下作为值类型传递。这意味着涉及到复制。这是在Java中无法做到的(尚未),即使使用记录也不行。看起来你只接触过引用强制的复合类型。但记录只会让事情变得更加混乱。如果你现在想要一个结构体(没有Valhalla编译器+JVM),那么实例化普通对象的普通类将是最接近的。Java记录根本不能帮助OP。 - alife
我在哪里说过在C++中它们只存在于堆栈上?我在谈论基本的差异。记录不是值类型。在C++中制作一个结构体。现在实例化它,将其发送到方法(按值传递)。修改该方法中结构体的某个内容。这是否会影响外部值?不会。此外,由于值类型,编译器还将替换'a.b'(无论它存在于何处)为通过偏移量直接访问'b'。您无需对'a'进行解引用。查看C#中包含结构体的设计决策以及它如何加速算法。 - alife
谁说过Java会成为C++?没有人。而且OP引入了Java不支持的一个设施,因此谈论该设施实际上是什么。保持简单正是为什么你要避免使用Records,而只需使用一个简单的类。无论如何,我们显然在打转,你乐于争论我从未提出的草人。是的,C++结构体本质上是一个C++类,但它与Java类完全不同,甚至不像Java记录。所以使用一个类。 - alife

23

实际上在C++中,结构体就是一个类(例如您可以在其中定义方法,它可以被扩展,它的工作方式与类完全相同),唯一的区别是默认访问修饰符被设置为public(对于类,默认情况下设置为private)。

这真的是C++中唯一的区别,很多人不知道。;)


21

不,Java目前还没有结构体/值类型。但是,在即将发布的Java版本中,我们将获得inline class,它类似于C#中的struct,有助于我们编写无需分配内存的代码。


inline class point { 
  int x;
  int y;
}


1
谁给这个投票踩了一下,可以告诉我为什么吗?你可以在这里阅读更多有关 Java 内联类的信息:https://www.infoq.com/articles/inline-classes-java/ - ganesan arunachalam
1
太好了!我期待着这个新的添加。 - Jonathan E. Landrum
1
此外,关于这个术语,Oracle似乎有点自相矛盾。这是截至2020年底的情况。我仍然不确定Valhalla最终会走向何方。无论如何,“原始对象”、“原始类”、“原始值类型”等可能是新的术语。 - alife
1
@ganesanarunachalam,“PBV”会给我们带来一些麻烦。明确一下,没有ref关键字,C#将所有内容(包括引用)都按值传递。你的意思是结构体对象的内容被作为值传递(与C类似)。是的,自从我最初提出问题以来,我已经深入研究了Valhalla:JVM确实被修改以允许在堆栈上使用复合值类型。JUnion项目似乎是一个奇怪的半成品,我还没有深入研究过。 - alife
1
https://openjdk.org/jeps/401 仍处于预览阶段。@ryanwebjackson - ganesan arunachalam
显示剩余4条评论

16

Java没有类似于C++的结构体,但您可以使用全部为公共成员的类。


18
实际上,在C++中,structclass基本上是一样的东西(参见http://en.wikipedia.org/wiki/C%2B%2B_classes#Differences_between_struct_and_classes_in_C.2B.2B)。 - Joachim Sauer
1
@JoachimSauer 奇怪!这让我想知道C++类与C结构体有多大的不同。 - jpaugh
@MaartenBodewes,“internal”的问题在于它比“protected”更不可见。在“com.myapp.lib”中声明的“internal”成员将无法在“com.myapp.main”中访问,因此您可能无法在同一项目中的任何地方都访问它。 - jpaugh
@jpaugh:我不是C或C++专家,那只是我随意拾取的一些有趣的小知识。但我猜想既然C++类几乎与C++结构体相同,那么它们两者都与C结构体非常不同(后者不直接支持相关构造函数/方法的继承)。 - Joachim Sauer
@jpaugh 我提到public的问题的原因是您可能不想将一个容易被放入错误状态的记录暴露给许多类; 最好将其保持为内部。请注意,正如此答案中所述,Java记录是不可变的(但您当然可以使用工厂方法来创建新记录)。 - Maarten Bodewes
1
不同的是,在JVM中,对象是一个指针,它具有其他指向其字段的指针;而在C语言中,结构体是内存分配的蓝图,因此可以轻松管理,因为它们总是相同大小,并且一次性分配在其他结构体旁边,就像整数数组一样。 - Salvatore Pannozzo Capodiferro

10

通过在类上注释@Struct注释,您可以在Java中使用结构体(Project JUnion)

@Struct
class Member {
  string FirstName; 
  string LastName;  
  int BirthYear; 
}

有关该项目的更多信息,请访问其网站:https://tehleo.github.io/junion/


好东西。我现在明白了结构体(对象)在内存中是如何表示的。谢谢,伙计 :) - user218046

4

是的,你需要一个类。一个类定义了自己的类型。


2

随着Java 14的发布,它开始支持Record。您可以查看https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Record.html了解更多信息。

public record Person (String name, String address) {}

Person person = new Person("Esteban", "Stormhaven, Tamriel");

在Java 15之后,出现了封闭类(Sealed Classes)。 https://openjdk.java.net/jeps/360
sealed interface Shape permits Circle, Rectangle {

  record Circle(Point center, int radius) implements Shape { }

  record Rectangle(Point lowerLeft, Point upperRight) implements Shape { } 
}

你能举一个记录的例子吗? - Scratte
请不要仅仅将某个工具或库作为答案发布。至少在答案中演示它是如何解决问题的。 - Sabito stands with Ukraine
1
抱歉刚收到通知。本以为很简单,但你是对的。加了一些例子。 - Orcun
记录仍然不是结构体。您仍在处理对象,并且无法像C#一样传递和返回复合对象(结构体)。请参阅此答案,了解ganesan arunachalam即将推出的“内联类”支持:https://dev59.com/92435IYBdhLWcg3w3EEi#57643471。它的结果将会非常有趣。 - alife

1

Java不支持真正的结构体。例如,C#支持代表值并可随时分配的struct定义。

在Java中,获取类似于C++结构体的唯一方式是

struct Token
{
    TokenType type;
    Stringp stringValue;
    double mathValue;
}

// Instantiation

{
    Token t = new Token;
}

不使用静态缓冲区或列表,做某些事情的方式类似于。
var type = /* TokenType */ ;
var stringValue = /* String */ ;
var mathValue = /* double */ ;

因此,只需将变量分配或静态定义到类中即可。

Java 不支持 var,是吗? - jpaugh
1
它从Java 10开始作为变量的基本占位符得到支持,因为构造函数将定义类型,但我不知道它是否可用于内存管理,我认为最好为其类型定义一个变量,使用布尔值应该比使用var然后将其分配给布尔值更好。 - Salvatore Pannozzo Capodiferro
伙计,C++中的变量类型推断是我确实想念的东西。很高兴看到它已经被引入Java了。 - Jonathan E. Landrum
1
请注意,var只是让编译器在编译时推断类型,在运行时没有区别。 - MC Emperor
我仍然不明白你的回答想表达什么。var并不能创建复合值类型,这也是结构体的关键所在。此外,将它们放入类中有什么作用呢?你是在回答他需要自己的类型,还是在回答结构体的何时/为什么/如何使用?如果是后者,那么这个回答就没有意义了。 - alife

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