1. Overview
1.概述
Sometimes, we might want to pass and modify a String within a method in Java. This happens, for example, when we want to append another String to the one in the input. However, input variables have their scope inside a method. Furthermore, a String is immutable. Therefore, finding a solution is unclear if we don’t understand Java memory management.
有时,我们可能希望在 Java 方法中传递和修改 String 。例如,当我们想将另一个 String 追加到输入中的 String 时,就会发生这种情况。但是,输入变量的作用域在方法内部。此外,String 是不可变的。因此,如果我们不了解 Java 内存管理,就很难找到解决方案。
In this tutorial, we’ll understand how an input String is passed to a method. We’ll see how we can use a StringBuilder and how to preserve immutability by creating new objects.
在本教程中,我们将了解如何将输入 String 传递给方法。我们将了解如何使用 StringBuilder 以及如何通过创建新对象来保持不变性。
2. Pass by Value or Reference
2.按值或参照传递
Being an OOP language, Java can define primitives and objects. They can be stored in the stack or heap memory. Furthermore, they can be passed by value or reference to a method.
作为一种 OOP 语言,Java 可以定义基元和对象。它们可以存储在堆或堆内存中。此外,它们还可以通过值或引用传递给方法。
2.1. Objects and Primitives
2.1.对象和基元
Primitives have allocation in the stack memory. When passed to a method, we get a copy of the primitive’s value.
基元在栈内存中进行分配。当传递给一个方法时,我们将获得基元值的副本。
Objects are instances of class templates. They are stored in the heap memory. However, inside a method, a program can access them because it has a reference to the address in the heap memory. Similarly to a primitive, when passing an object to a method, we get a copy of the object’s reference (which we can think of as a pointer).
对象是类模板的实例。它们存储在堆内存中。然而,在方法内部,程序可以访问它们,因为程序拥有指向堆内存中地址的引用。与基元类似,当将对象传递给方法时,我们将获得对象引用的副本(我们可以将其视为指针)。
Although there is a difference between passing a primitive or an object, variables or objects have their scope inside a method. In both cases, a call-by-sharing is what is happening, and we can’t directly update the original value or reference. Therefore, parameters are always copied.
虽然传递基元和传递对象有所不同,但变量或对象的作用域都在方法内部。在这两种情况下,发生的都是共享调用,我们无法直接更新原始值或引用。 因此,参数总是被复制的。
2.2. String Immutability
2.2.字符串不变性
A String is a class instead of a primitive in Java. Therefore, given its runtime instance, we’ll get a reference when passing it to a method.
在 Java 中,String 是一个类而不是一个基元。因此,在给定其运行时实例的情况下,我们在将其传递给方法时会得到一个引用。
Additionally, it’s immutable. Therefore, even if we want to manipulate the String within a method, we can’t modify it unless we create a new one.
此外,它是 不可变的。因此,即使我们想在一个方法中操作 String 也无法修改它,除非我们创建一个新的方法。
3. Use Case
3.使用案例
Let’s define a primary use case before digging into a general solution to the problem.
在深入研究问题的一般解决方案之前,让我们先定义一个主要用例。
Suppose we want to append to an input String within a method. Let’s test what happens before and after the method execution:
假设我们想在一个方法中追加输入的 String 内容。让我们测试一下方法执行前后的情况:
@Test
void givenAString_whenPassedToVoidMethod_thenStringIsNotModified() {
String s = "hello";
concatStringWithNoReturn(s);
assertEquals("hello", s);
}
void concatStringWithNoReturn(String input) {
input += " world";
assertEquals("hello world", input);
}
The String gets a new value inside the concatStringWithNoReturn() method. However, we still have the original value outside the method’s scope.
在concatStringWithNoReturn()方法中,String获得了一个新值。但是,我们在该方法的作用域之外仍保留了原始值。
Naturally, a logical solution would be to make a method return a new String:
当然,合理的解决方案是让方法返回一个新的 String :
@Test
void givenAString_whenPassedToMethodAndReturnNewString_thenStringIsModified() {
String s = "hello";
assertEquals("hello world", concatString(s));
}
String concatStringWithReturn(String input) {
return input + " world";
}
Notably, we avoid side effects while safely returning a new instance.
值得注意的是,我们在安全返回新实例的同时避免了副作用。
4. Use a StringBuilder or StringBuffer
4.使用 StringBuilder 或 StringBuffer
Although String concatenation is an option, using a StringBuilder (or StringBuffer as the thread-safe version) is a better practice.
虽然 String 连接是一种选择,但使用 StringBuilder(或 StringBuffer 作为线程安全版本)是更好的做法。
4.1. StringBuilder
4.1.字符串生成器</em
@Test
void givenAString_whenPassStringBuilderToVoidMethod_thenConcatNewStringOk() {
StringBuilder builder = new StringBuilder("hello");
concatWithStringBuilder(builder);
assertEquals("hello world", builder.toString());
}
void concatWithStringBuilder(StringBuilder input) {
input.append(" world");
}
The String we append to the builder is temporarily stored in an array of characters. Therefore, comparing this approach with a String concatenation, the main benefit is performance-wise. Thus, we won’t create a new String every time. Instead, we wait until we have the sequence we want and, at that moment, make the required String.
我们附加到构建器的 String 会暂时存储在字符数组中。因此,与 String 连接相比,这种方法的主要优势在于性能。因此,我们不会每次都创建一个新的 String 。取而代之的是,我们会一直等待,直到得到我们想要的序列,然后再创建所需的 字符串。
4.2. StringBuffer
4.2 StringBuffer.
We also have the thread-safe version, the StringBuffer. Let’s also see this in action:
我们还有线程安全版本,即 StringBuffer 。让我们来看看它的实际应用:
@Test
void givenAString_whenPassStringBufferToVoidMethod_thenConcatNewStringOk() {
StringBuffer builder = new StringBuffer("hello");
concatWithStringBuffer(builder);
assertEquals("hello world", builder.toString());
}
void concatWithStringBuffer(StringBuffer input) {
input.append(" world");
}
If we need synchronization, this is the class we want. Naturally, this can slow down the process, so let’s understand first if it’s worth it.
如果我们需要同步,这就是我们想要的类。当然,这会减慢进程,所以让我们先了解一下这样做是否值得。
5. Working With Objects Properties
5.使用对象属性
What if a String is an object property?
如果 String 是对象属性怎么办?
Let’s define a simple class we can use for testing:
让我们定义一个简单的类,用于测试:
public class Dummy {
String dummyString;
// getter and setter
}
5.1. Modify String State With the Setter
5.1.使用设置器修改 String 状态
At first, we could think of simply using the setter to modify the object’s String state:
起初,我们可以简单地使用 setter 来修改对象的 String 状态:
@Test
void givenObjectWithStringField_whenSetDifferentValue_thenObjectIsModified() {
Dummy dummy = new Dummy();
assertNull(dummy.getDummyString());
modifyStringValueInInputObject(dummy, "hello world");
assertEquals("hello world", dummy.getDummyString());
}
void modifyStringValueInInputObject(Dummy dummy, String dummyString) {
dummy.setDummyString(dummyString);
}
Notably, we’ll update a copy of the original object in the heap memory (still pointing to the actual value).
值得注意的是,我们将更新堆内存中原始对象的副本(仍指向实际值)。
However, this isn’t a good practice. It hides the String change. Furthermore, we can have synchronization issues if multiple threads are trying to modify the object.
但是,这种做法并不好。它隐藏了 String 的变化。此外,如果多个线程试图修改对象,我们可能会遇到同步问题。
Overall, whenever possible, we should look for immutability and make a method return a new object.
总的来说,只要有可能,我们就应该寻求不变性,并让方法返回一个新对象。
5.2. Create a New Object
5.2.创建新对象
It’s good practice to make methods return new objects when applying some business logic. Furthermore, we can also set properties using the StringBuilder pattern we have seen earlier. Let’s wrap this up in a test case:
在应用某些业务逻辑时,让方法返回新对象是一种很好的做法。此外,我们还可以使用前面提到的 StringBuilder 模式设置属性。让我们用一个测试用例来总结一下:
@Test
void givenObjectWithStringField_whenSetDifferentValueWithStringBuilder_thenSetStringInNewObject() {
assertEquals("hello world", getDummy("hello", "world").getDummyString());
}
Dummy getDummy(String hello, String world) {
StringBuilder builder = new StringBuilder();
builder.append(hello)
.append(" ")
.append(world);
Dummy dummy = new Dummy();
dummy.setDummyString(builder.toString());
return dummy;
}
Although this is a simplified example, we can see how the code is more readable. Furthermore, we avoid side effects and maintain immutability. Any input information of a method is something we use to construct a well-identified instance of a new object.
虽然这是一个简化的示例,但我们可以看到代码的可读性更强了。此外,我们还避免了副作用并保持了不变性。方法的任何输入信息都会被我们用来构建一个身份明确的新对象实例。
6. Conclusion
6.结论
In this article, we saw how to change a method’s input String while preserving immutability and avoiding side effects. We saw how to use a StringBuilder and apply this pattern in new object creation.
在本文中,我们了解了如何在保持不变性和避免副作用的同时更改方法的输入 String 。我们还了解了如何使用 StringBuilder 并在创建新对象时应用此模式。
As always, the code presented in this article is available over on GitHub.
与往常一样,本文中介绍的代码可在 GitHub 上获取。