Reputation: 294
I got below code and I was asked which option gets the following pattern:
XXXX-XXXX-XXXX-2324
...
Code Below:
public class CCMark {
public static String maskCC(String creditCard){
String x = "XXXX-XXXX-XXXX-";
//line 1
}
public static void main(String[] args) {
System.out.println(maskCC("1234-5678-1234-2324"));
System.out.println(maskCC("4567-5678-1234-5643"));
System.out.println(maskCC("1234-5678-1234-4654"));
System.out.println(maskCC("4567-5678-1234-5435"));
}
}
Below possible options that can be inserted on "line 1":
A)
return x + creditCard.substring(15, 19);
B)
StringBuilder sb = new StringBuilder(x);
sb.append(creditCard, 15, 19);
return sb.toString();
I think that the best option here, as A and B provide us with the same output, is B, because it is using StringBuilder which means that its approach is mutable, so it will use less memory than option A.
Am I wrong? Could it be that option A for this particular situation is the best option?
Upvotes: 5
Views: 650
Reputation: 298409
The big advantage of the variant A, return x + creditCard.substring(15, 19);
is that it is simple and clean and it works in all Java versions from 1
to 8
. In the case that its compiled form uses StringBuffer
, a simple recompile for Java 5 or newer will make it use StringBuilder
instead. This flexibility is lost when you work with either, StringBuffer
or StringBuilder
, manually.
The exact compiled form is not fixed. Since the semantic of the method String.substring
is not fixed by the Java Language Specification, compilers usually won’t touch this and compile it as an ordinary method invocation. The specification encourages compiler vendors to use StringBuilder
for string concatenation (the +
operator) whenever there is a benefit and most compilers will do so, even when there is no benefit. Here, both, x
and the result of substring
, are String
s so a simple String.concat
would be simpler but most compilers always use StringBuilder
, compiling variant A to the equivalent of
return new StringBuilder().append(x).append(creditCard.substring(15, 19)).toString();
.
Comparing this typical form with your variant B, we can conclude that variant B has two advantages performance-wise:
new StringBuilder(x)
initializes the StringBuilder
to a capacity of x.length()+16
which is sufficient for the entire operation, whereas the default capacity of new StringBuilder()
, typically used for variant A, is fixed to 16
characters which misses the mark here as we have a result of 19
characters, thus a reallocation and copying of the underlying character array will occur
sb.append(creditCard, 15, 19);
will copy the four characters without the need to create an intermediate String
representation of these characters. The expenses of the substring
operation differ depending on the implementation, e.g. in Oracle’s implementation there was a significant change with version 1.7.0_06
; starting with this version a substring requires a new char[]
array holding a copy of the affected character data as it doesn’t maintain a separate offset
and length
field
But note that all these differences of variant A and B only affect the formal description of the operation to perform. What will actually happen, is up to the JVM/JRE and usually the Hotspot optimizer knows a lot of string related operations and may fuse operations or elide intermediate string representations. Thus, the outcome regarding performance is rather unpredictable and may be affected by subtle changes to the implementation.
That’s why developers might stick to variant A which is, as said, simpler and more readable, and only care for performance once a profiler tells them that there is a performance problem that could be solved by dealing with Stringbuilder
manually.
Upvotes: 2
Reputation: 201477
Options a and b are identical, because the Java compiler will convert option a into option b. You could move the declaration of x
outside the method (and make it final
). Something like,
static final String x = "XXXX-XXXX-XXXX-";
public static String maskCC(final String creditCard) {
return x + creditCard.substring(15, 19);
}
Using javap
to check the first against, the second. Java code like,
String x = "XXXX-XXXX-XXXX-";
String creditCard = "1234-5678-1234-23324";
String x2 = x + creditCard.substring(15, 19);
StringBuilder sb = new StringBuilder(x);
sb.append(creditCard, 15, 19);
String x3 = sb.toString();
generates byte-code that looks like (note lines 6-31 and 32-58)
0: ldc #16 // String XXXX-XXXX-XXXX-
2: astore_1
3: ldc #18 // String 1234-5678-1234-23324
5: astore_2
6: new #20 // class java/lang/StringBuilder
9: dup
10: aload_1
11: invokestatic #22 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
14: invokespecial #28 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
17: aload_2
18: bipush 15
20: bipush 19
22: invokevirtual #31 // Method java/lang/String.substring:(II)Ljava/lang/String;
25: invokevirtual #35 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #39 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_3
32: new #20 // class java/lang/StringBuilder
35: dup
36: aload_1
37: invokespecial #28 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
40: astore 4
42: aload 4
44: aload_2
45: bipush 15
47: bipush 19
49: invokevirtual #43 // Method java/lang/StringBuilder.append:(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder;
52: pop
53: aload 4
55: invokevirtual #39 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
58: astore 5
60: return
Upvotes: 5