java 空则创建保持同步_与Java 14的新功能保持同步

java 空则创建保持同步

Note: Original video also talks about Sealed classes and interfaces (preview language feature) in Java 15 to be released in September 2020. You can read about this here https://www.infoq.com/articles/java-sealed-classes/

注意:原始视频还讨论了Sealed classes and interfaces ( preview language feature) 将于2020年9月发布的Java 15中。您可以在这里https://www.infoq.com/articles/java-sealed-classes/阅读

记录(预览语言功能) (Records (preview language feature))

Based on https://dzone.com/articles/a-first-look-at-records-in-java-14

基于https://dzone.com/articles/a-first-look-at-records-in-java-14

UPDATE

更新

After publishing original post, I’ve read original JEP for records and I was astonished to find the following:

发布原始帖子后,我阅读了原始JEP记录以备查询 ,我惊讶地发现以下内容:

While it is superficially tempting to treat records as primarily being about boilerplate reduction, we instead choose a more semantic goal: modeling data as data…Records go well with sealed types (JEP 360); records and sealed types taken together form a construct often referred to as algebraic data types. Further, records lend themselves naturally to pattern matching. Because records couple their API to their state description, we will eventually be able to derive deconstruction patterns for records as well, and use sealed type information to determine exhaustiveness in switch expressions with type patterns or deconstruction patterns.

从表面上看,将记录主要看作是减少样本量是诱人的,但是我们选择了一个更语义的目标:对数据建模 作为数据...记录与 密封类型 配合得很好 (JEP 360) 记录和密封类型共同构成一个结构,通常称为 代数数据类型 此外,记录自然会适合 模式匹配 由于记录将其API与其状态描述相结合,因此我们最终也将能够导出记录的解构模式,并使用密封的类型信息来确定 具有类型模式或解构模式的 switch 表达式的 穷举性

https://openjdk.java.net/jeps/359

https://openjdk.java.net/jeps/359

So, Java’s records are essentially product types. It is not just about reducing boilerplate. Practically, records are needed for pattern matching that is designed to be added in future version of Java. I will write separate post about this later.

因此,Java的记录本质上是产品类型 。 这只是减少样板 。 实际上,模式匹配需要记录,这些记录被设计为在Java的未来版本中添加。 稍后,我将撰写单独的帖子。

In the video there is answered question. Why records doesn’t follows JavaBean naming convention?

视频中有回答的问题。 为什么记录不遵循JavaBean命名约定?

…Like an enum, a record is a restricted form of class. It declares its representation, and commits to an API that matches that representation… A central aspect of Java’s philosophy is that names matter… That is, a Person class with properties firstName and lastName is clearer and safer than an anonymous tuple of String and String.

…像 enum 一样 record 是类的一种受限形式。 它声明其表示形式,并提交与该表示形式匹配的API ... Java原理的一个主要方面是名称很重要…也就是说, 具有 firstName lastName 属性 Person lastName String String 的匿名元组更清晰,更安全

https://openjdk.java.net/jeps/359

https://openjdk.java.net/jeps/359

END OF UPDATE

更新结束

This is similar to data class in Kotlin. This is special kind of class that is intended to hold pure data in it. Typical use-cases are: DTO — data transfer object or domain model class.

这类似于Kotlin中的数据类。 这是一种特殊的类,旨在在其中保存纯数据。 典型的用例是:DTO-数据传输对象或域模型类。

Code example:

代码示例:

package examples;




record Person (String firstName, String lastName) {}

Note, there is mistake. Records doesn’t implements java.io.Serialzable by default. I think, this is bug in IntelliJ IDEA.

注意,有错误。 记录默认情况下不实现java.io.Serialzable 。 我认为,这是IntelliJ IDEA中的错误。

java 空则创建保持同步_与Java 14的新功能保持同步_第1张图片

Actually, it is demonstrated later in the video.

实际上,将在后面的视频中进行演示。

You can see in line 5 in the link provided above of the decompiled class there is no mentioning of java.io.Serializable.

您可以在反编译类的上面提供的链接的第5行中看到没有提及java.io.Serializable

final class examples.Person extends java.lang.Record

Record class is final. It extends java.lang.Record, which is the base class for all records, much like java.lang.Enum is the base class for all enums. There is a public constructor that is generated for us. For each field there is getter-method to it with exact same name (no “get” prefix!). Three other methods are generated: toString(), hashCode() and equals(). They all rely on invokedynamic to dynamically invoke the appropriate method containing the implicit implementation.

记录课是final. 它扩展了java.lang.Record ,它是所有记录的基类,非常类似于java.lang.Enum是所有枚举的基类。 有一个为我们生成的公共构造函数。 对于每个字段,都有一个名称完全相同的getter方法(没有“ get”前缀!)。 生成其他三种方法: toString()hashCode()equals() 。 它们都依靠invokedynamic来动态调用包含隐式实现的适当方法。

Another code example:

另一个代码示例:

record Person (String firstName, String lastName) {


    public Person {
        if(firstName == null || lastName == null) {
            throw new IllegalArgumentException("firstName and lastName must not be null");
        }
        // We can also omit assigning fields, the compiler will auto-add them


    }
    
    public Person(String fullName) {
        this(fullName.split(" ")[0], fullName.split(" ")[1]);


    }


}

What are old ways to define record?

定义记录的旧方法是什么?

  1. Use of some unrated existing class. One popular one is SimpleEntry (because Pair is not present in the JDK). The main disadvantages:

    使用一些未分级的现有类。 一种流行的方法是SimpleEntry (因为JDK中不存在Pair)。 主要缺点:

  • we’re losing meaningful names , moreover, the names are misleading — getKey() and getValue() and not,say, getFirstName(), getLastName().

    我们正在失去有意义的名称 ,而且,这些名称具有误导性 getKey ()getValue ()而不是getFirstName()getLastName()

  • We are using the same type for different DTO/Model class, so compiler can’t help us to distinguish the use cases and we don’t have semantic information in what this partial DTO/Model class holds.

    对于不同的DTO / Model类,我们使用相同的类型,因此编译器无法帮助我们区分用例,并且在此部分DTO / Model类所拥有的内容中没有语义信息

I’ve seen such usage couple of times also.

我也看过几次这样的用法。

2. Class with public fields only.Actually, from time to time I’m encountering with such solution in different projects. The declaration is short, but it goes with following drawbacks:

2.仅在public领域上课。实际上,我不时在不同项目中遇到这种解决方案。 声明很短,但是具有以下缺点:

  • You can’t add field validation. If you have double amount, you can’t validate precondition on state that amount≥0for example.

    您无法添加字段验证。 如果您有double amount ,则无法validate precondition on state例如amount≥0 validate precondition on state

  • If you have some complex state you don’t have place to handle it. Typically, it is done in setter-method. The work-around is to add such complex setter-method. The drawback is nobody expect that you class has such method, because you state is defined as set of public fields that are assigned directly.

    如果您有一些复杂的状态,那么您就没有地方处理它。 通常,它是使用setter -method完成的。 解决方法是添加这种复杂的setter方法。 缺点是没有人期望您的类具有这种方法,因为您将状态定义为直接分配的一组public字段。

  • You can’t make such object immutable, you have access to it to read the value, so you can also write to it.

    您不能使此类对象immutable ,您可以访问它以读取值,因此也可以对其进行写入。

  • You don’t have readable toString(), etc for debugging. If you start to add them, it takes just a little more effort to convert this field to private and to add getter-method to it. See next bullet.

    您没有可读的toString(),等用于调试。 如果您开始添加它们,则只需花费更多的精力即可将该字段转换为private并向其添加getter -method。 请参阅下一个项目符号。

3. Full-blown class, may be as JavaBean. Write down whole boilerplate code manually.

3.成熟的类,可以作为JavaBean。 手动写下整个样板代码。

3. Full-blown class, using modern IDE to generate most the boilerplate code. This my currently personally preferable way. The main drawbacks are:

3.完善的类,使用现代IDE生成大多数样板代码。 这是我目前个人更喜欢的方式 主要缺点是:

  • poor readability — you need to read a lot of irrelevant code; I’m mitigating this using naming convention, DTO suffix for the class name, etc.

    可读性差 -您需要阅读许多不相关的代码; 我使用命名约定,类名的DTO后缀等来缓解这种情况。

  • difficulty of code modification — If you need to add new field, you should regenerated all equals()/hashCode()/toString()methods. If you have some manual change (for example, you’re omitting some field for equality test), you should remember to redo it after automatic code generation. I’m mitigating this by looking on code diff after I’m regenerated the code and careful investigation of such code. In practice, I’ve never deleted such code.

    代码修改的困难-如果需要添加新字段,则应重新生成所有equals()/hashCode()/toString()方法。 如果您进行了一些手动更改(例如,您省略了进行相等性测试的某些字段),则应记住在自动代码生成后重做一次。 在重新生成代码并仔细研究此类代码之后,我通过查看代码差异来缓解这种情况。 实际上,我从未删除过此类代码。

4. Use Project Lombok. Quote: “Project Lombok is a java library that…plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder.” Lombok uses annotation processing through APT (Annotation Processing Tool), so, when the compiler calls it, the library generates new source files based on annotations in the originals. The main drawback for me is incompatibility with Spring Boot:

4.使用Lombok项目 。 Quote:“ Project Lombok是一个Java库,它…可插入您的编辑器并构建工具,从而为您的Java增光添彩。 永远不要再编写另一种getter或equals方法,带有一个注释的类将具有功能全面的生成器。” Lombok通过APT (注释处理工具)使用注释处理,因此,当编译器调用它时,库会基于原始注释生成新的源文件。 我的主要缺点是与Spring Boot不兼容:

The properties that map to @ConfigurationProperties classes available in Spring Boot, which are configured via properties files, YAML files, environment variables etc., are public API but the accessors (getters/setters) of the class itself are not meant to be used directly…

映射到Spring Boot中可用的@ConfigurationProperties类的属性(通过属性文件,YAML文件,环境变量等进行配置)是公共API,但该类本身的访问器(获取器/设置器)不能直接使用。 …

Some people use Project Lombok to add getters and setters automatically. Make sure that Lombok does not generate any particular constructor for such a type, as it is used automatically by the container to instantiate the object.

有些人使用Lombok项目自动添加获取器和设置器。 确保Lombok不会为这种类型生成任何特定的构造函数,因为容器会自动使用它来实例化该对象。

https://docs.spring.io/autorepo/docs/spring-boot/2.4.0-M2/reference/htmlsingle/#boot-features-external-config-java-bean-binding

https://docs.spring.io/autorepo/docs/spring-boot/2.​​4.0-M2/reference/htmlsingle/#boot-features-external-config-java-bean-binding

It is very difficult to debug Lombok. It took me couple of hours to make @ConfigurationProperties works (it was completely my fault, the problem was in wrong configurations in property files), at the time I just don’t want to spend extra time to configure Lombok (that is not trivial, but shouldn’t be hard) and then if it didn’t work I can’t debug it (I know how to be debug Spring Boot).

调试Lombok非常困难。 我花了几个小时才能使@ConfigurationProperties正常工作(这完全是我的错,问题出在属性文件中的配置错误),当时我只是不想花费额外的时间来配置Lombok(这不是小菜一碟) ,但不应该很困难 ),然后如果它不起作用,我将无法对其进行调试(我知道如何调试Spring Boot)。

Nevertheless, I know a lot of people that happily use Lombok (even with Spring Boot, just they don’t use @ConfigurationProperties at all).

不过,我知道很多人很高兴使用Lombok (即使使用Spring Boot,只是他们根本不使用@ConfigurationProperties )。

instanceof的模式匹配(预览语言功能) (Pattern match for instanceof (preview language feature))

Based on https://www.baeldung.com/java-pattern-matching-instanceof

基于https://www.baeldung.com/java-pattern-matching-instanceof

Old way:

旧方法:

if (animal instanceof Cat) {
    Cat cat = (Cat) animal;
    cat.meow();
   // other cat operations
} else if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.woof();
    // other dog operations
}
 
// More conditional statements for different animals

New way:

新方法:

if (animal instanceof Cat cat) {
    cat.meow();
} else if(animal instanceof Dog dog) {
    dog.woof();
}

Quote:

引用:

Let’s understand what is happening here. In the first, if block, we match animal against the type pattern Cat cat. First, we test the animal variable to see if it’s an instance of Cat. If so, it’ll be cast to our Cat type, and finally, we assign the result to cat.

让我们了解这里发生了什么。 在第一个 if 块中,我们将 animal 与类型模式 Cat cat 匹配 首先,我们测试动物变量以查看它是否是 Cat 的实例 如果是这样,它将被强制转换为 Cat 类型,最后,将结果分配给 cat

It is important to note that the variable name cat is not an existing variable, but instead a declaration of a pattern variable.

重要的是要注意,变量名 cat 不是现有变量,而是模式变量的声明。

We should also mention that the variables cat and dog are only in scope and assigned when the respective pattern match expressions return true. Consequently, if we try to use either variable in another location, the code will generate compiler errors.

我们还应该提到,变量 cat dog 仅在范围内,并且在各个模式匹配表达式返回 true 时才被分配 因此,如果我们尝试在另一个位置使用任何一个变量,则代码将生成编译器错误。

https://www.baeldung.com/java-pattern-matching-instanceof

https://www.baeldung.com/java-pattern-matching-instanceof

TextBlocks( 第二 预览 ) (TextBlocks (second preview))

See https://www.baeldung.com/java-text-blockshttps://www.baeldung.com/java-multiline-string

参见https://www.baeldung.com/java-text-blocks https://www.baeldung.com/java-multiline-string

Text blocks just provide us with another way to write String literals in our source code. The result type of a text block is still a String.

文本块只是为我们提供了另一种在源代码中编写String文字的方法。 文本块的结果类型仍然是String

Inside the text blocks, we can freely use newlines and quotes without the need for escaping. It allows us to include literal fragments of HTML, JSON, SQL, or whatever we need, in a more elegant and readable way.

在文本块内,我们可以自由使用换行符和引号,而无需进行转义。 它使我们能够以更加优雅和易读的方式包含HTML,JSON,SQL或我们需要的任何内容的文字片段。

In the resulting String, the (base) indentation and the first newline are not included.

在结果字符串中,不包括(基本)缩进和第一行换行符。

Code example:

代码示例:

public String getBlockOfHtml() {
    return """
            
 
                
                    example text
                
            """;
}

This is equivalent to:

这等效于:

public String getBlockOfHtml() {
    return "\n"
	      + "\n" 
	      + "    \n"
	      + "        example text\n"
	      + "    \n"
	      + "";
}

Another popular alternatives are:

另一个流行的替代方法是:

  • To copy multi-line string and paste in inside two double quotes in modern IDEs. Many modern IDEs support multi-line copy/paste. Eclipse and IntelliJ IDEA are examples of such IDEs.

    要复制多行字符串并将其粘贴在现代IDE中的两个双引号中。 许多现代的IDE支持多行复制/粘贴。 Eclipse和IntelliJ IDEA是此类IDE的示例。
  • To store multi-line string in a text file. Use some utility method to read it from file (prior Java 8 I’ve used FileUtils and IOUtils from commons-io package), for example:

    将多行字符串存储在文本文件中。 使用一些实用方法 ,从文件中读取(之前的Java 8我用fileutils中的IOUtils 的commons-io的包),例如:

public String loadFromFile() throws IOException {
    return new String(Files.readAllBytes(Paths.get("src/main/resources/stephenking.txt")));
}

The reason for second preview is addition of two escape sequences.

second 预览的原因是添加了两个转义序列。

  • Escaping Line Terminators — we can ignore new line.

    转义行终止符 -我们可以忽略换行符。

public String getIgnoredNewLines() {
    return """
            This is a long test which looks to \
            have a newline but actually does not""";
}

This is equivalent to:

这等效于:

public String getIgnoredNewLines() {
    return "This is a long test which looks to have a newline but actually does not";
}

Note: I’m frequently using this escape sequence in Python.

注意 :我经常在Python中使用此转义序列。

  • Escaping Spaces — we can preserve any spaces spaces in front of new escape sequence \s.

    转义空格-我们可以在新的转义序列\ s之前保留所有空格

public String getEscapedSpaces() {
    return """
            line 1·······
            line 2·······\s
            """;
}

This is equivalent to:

这等效于:

public String getEscapedSpaces() {
    return "line 1\nline 2·······\n";
}

Note: the spaces in the example above are replaced with the ‘·’ symbol to make them visible.

注意 :上例中的空格被替换为'·'符号以使其可见。

开关表达式(标准功能) (Switch expression (standard feature))

See https://www.techiediaries.com/java/java-14-13-switch-expressions-example/

参见https://www.techiediaries.com/java/java-14-13-switch-expressions-example/

Note: It is preview feature in Java 12 and Java 13.

注意:这是Java 12和Java 13中的预览功能。

Note: In Java 13 there was big breaking change, see http://sandny.com/2019/11/17/java-13-switch-expressions/ so the code will look differently on Java 12.

注意:在Java 13中发生了重大变化,请参阅http://sandny.com/2019/11/17/java-13-switch-expressions/,因此代码在Java 12上的外观将有所不同。

In enhanced switch expression the entire switch block “gets a value” that can then be assigned to a variable in same statement.

在增强的switch表达式中,整个switch块“获取一个值”,然后可以在同一语句中将其分配给变量。

Let’s consider classic switch example:

让我们考虑经典的切换示例:

public class SwitchExample {
    public static void main(String[] args) {


        int month = 8;
        String monthString;
        switch (month) {
            case 1:  monthString = "January";
                     break;
            case 2:  monthString = "February";
                     break;
            case 3:  monthString = "March";
                     break;
            case 4:  monthString = "April";
                     break;
            case 5:  monthString = "May";
                     break;
            case 6:  monthString = "June";
                     break;
            case 7:  monthString = "July";
                     break;
            case 8:  monthString = "August";
                     break;
            case 9:  monthString = "September";
                     break;
            case 10: monthString = "October";
                     break;
            case 11: monthString = "November";
                     break;
            case 12: monthString = "December";
                     break;
            default: monthString = "Invalid month";
                     break;
        }
        System.out.println(monthString);
    }
}

Note: The break statements ensures that the next block in the switch statement is not executed.

注意: break语句确保switch语句中的下一个块不执行。

This code will print out “August” (in line 4 we’re assigning 8 to month).

该代码将输出“August” (在第4行中,我们将8分配给month )。

Using a new syntax this can be rewritten as:

使用新语法,可以将其重写为:

public class SwitchExample {
    public static void main(String[] args) {


        int month = 8;
        String monthString = switch (month) {
            case 1 -> "January";
            case 2 -> "February";
            case 3 ->  "March";
            case 4 ->  "April";
            case 5 ->  "May";
            case 6 ->  "June";
            case 7 ->  "July";
            case 8 ->  "August";
            case 9 ->  "September";
            case 10 -> "October";
            case 11 -> "November";
            case 12 -> "December";
            default -> "Invalid month";
        }
        System.out.println(monthString);
    }
}

Note:

注意:

  1. It uses the -> operator instead of the colon

    它使用->运算符而不是冒号

  2. We don’t need the break statement to stop the execution from flowing to the next cases.

    我们不需要break语句就可以阻止执行流向下一个案例。

  3. We can assign the switch expressions to variables or place them wherever expressions are expected in your Java code.

    我们可以 switch表达式分配给变量,也可以将它们放在Java代码中期望表达式的任何位置。

Java 14 also introduced a new yield statement to yield a value which becomes the value of the enclosing switch expression.

Java 14还引入了新的yield语句,以产生一个值,该值成为封闭的switch表达式的值。

For example,

例如,

public class SwitchExample {
    public static void main(String[] args) {


        int month = 8;
        String monthString = switch (month) {
            case 1 -> "January";
            case 2 -> "February";
            case 3 ->  "March";
            case 4 ->  "April";
            case 5 ->  "May";
            case 6 ->  "June";
            case 7 ->  "July";
            case 8 ->  "August";
            case 9 ->  "September";
            case 10 -> "October";
            case 11 -> "November";
            case 12 -> "December";
            default -> {
              System.out.println("Invalid month!");
              yield "Invalid month";
            }
        }
        System.out.println(monthString);
    }
}

Look on default clause. It was modified in two ways. No, this is code block, so curly braces are required. In order to “return” value from the default clause new reserved word “yield” is used.

查看default子句。 它有两种修改方式。 不,这是代码块,因此需要大括号。 为了从default子句中“返回”值,使用了新的保留字“yield”

A return statement returns control to the invoker of a method (§8.4, §15.12) or constructor (§8.8, §15.9) while a yield statement transfers control by causing an enclosing switch expression to produce a specified value.

return语句将控制权返回给方法 (第8.4节 ,第§15.12节 )或构造函数 (第8.8节和§15.9节 ) 的调用者,yield语句通过使封闭的 switch 表达式产生指定值来转移控制权。

Note: In Python yield is used in generators (this is a subset of more general coroutines concept). yieldis a keyword in Python that is used to return from a function without destroying the states of its local variable and when the function is called, the execution starts from the last yield statement. This has nothing to do with Java’s yield.

注意:在Python中, yield用于生成器 (这是更通用的协程概念的子集)。 yield是Python中的一个关键字,用于从函数返回而不会破坏其局部变量的状态,并且在调用该函数时,将从最后一个yield语句开始执行。 这与Java的 yield. 无关 yield.

有关其他信息: (For some additional look on:)

Java 14中的外部内存访问API (Foreign Memory Access API in Java 14)

https://www.baeldung.com/java-foreign-memory-access

https://www.baeldung.com/java-foreign-memory-access

Quote from link above:

从上面的链接引用:

Before the introduction of the foreign memory access API in Java, there were two main ways to access native memory in Java. These are java.nio.ByteBuffer and sun.misc.Unsafe [and from Java 9 java.lang.invoke.VarHandle see below] classes.

在Java中引入外部内存访问API之前,有两种主要的方法来访问Java中的本机内存。 这些是java.nio.ByteBuffersun.misc.Unsafe [和从Java 9 java.lang.invoke.VarHandle参见下面]类。

Note: In the bracket this my addition.

注意:在括号中这是我的补充。

Moreover,

此外,

  1. AtomicInteger is still implemented using Unsafe (that was moved to jdk.internal.misc.Unsafe). It has interesting comment:

    AtomicInteger仍使用Unsafe实现(已移至jdk.internal.misc.Unsafe )。 它有一个有趣的评论:

This class intended to be implemented using VarHandles, but there are unresolved cyclic startup dependencies.

AtomicReference was refactored to use VarHandle

重构AtomicReference以使用VarHandle

Part of the purpose for VarHandles is to replace operations in sun.misc.Unsafe with a safe equivalent.

VarHandle的部分目的是用安全等效项替换sun.misc.Unsafe操作。

https://stackoverflow.com/questions/43558270/correct-way-to-use-varhandle-in-java-9/43561666#43561666

https://stackoverflow.com/questions/43558270/correct-way-to-use-varhandle-in-java-9/43561666#43561666

2. SequenceLayout is C-style array of the same type stored in memory.

2. SequenceLayout是存储在内存中的相同类型的C样式array

3. GroupLayout is C-style struct or union stored in memory.

3. GroupLayout是存储在内存中的C样式structunion

Java 14 @Serial批注指南 (Guide to the @Serial Annotation in Java 14)

https://www.baeldung.com/java-14-serial-annotation

https://www.baeldung.com/java-14-serial-annotation

Quote from link above:

从上面的链接引用:

Similarly to @Override, this annotation is used in combination with the serial lint flag to perform compile-time checks for the serialization-related members of a class.

@Override相似,此注释与序列lint标志结合使用,以对类的与序列化相关的成员执行编译时检查。

Java 14 jpackage指南 (A Guide to jpackage in Java 14)

https://www.baeldung.com/java14-jpackage

https://www.baeldung.com/java14-jpackage

Quote from link above:

从上面的链接引用:

jpackage is a command-line tool to create native installers and packages for Java applications.

jpackage是用于为Java应用程序创建本机安装程序和程序包的命令行工具。

It’s an incubating feature under the jdk.incubator.jpackage module.

这是jdk.incubator.jpackage模块下的一个孵化功能。

  • Helpful NullPointerExceptions in Java 14

    Java 14中的有用NullPointerExceptions

    Helpful NullPointerExceptions in Java 14https://www.baeldung.com/java-14-nullpointerexception

    Java 14中有用的NullPointerExceptions https://www.baeldung.com/java-14-nullpointerexception

Quote from link above:

从上面的链接引用:

In essence, JEP 358 aims to improve the readability of NullPointerExceptions, generated by JVM, by describing which variable is null.

本质上, JEP 358旨在通过描述哪个变量为null来提高JVM生成的NullPointerException的可读性。

翻译自: https://medium.com/swlh/keeping-pace-with-whats-new-in-java-14-5fc6232defab

java 空则创建保持同步

你可能感兴趣的:(java,zookeeper,jdk)