‘StringBuffer stringBuffer’ may be declared as ‘StringBuilder’ less… (Ctrl+F1) Inspection info: Reports any variables declared as java.lang.StringBuffer which may be more efficiently declared as java.lang.StringBuilder. java.lang.StringBuilder is a non-thread-safe replacement for java.lang.StringBuffer, available in Java 5 and newer. This inspection only reports if the project or module is configured to use a language level of 5.0 or higher.
以上内容用于搜索引擎抓取,也是写这篇博客的原因。
前言
我从学习java开始,就用的idea,可能我还是一个年轻java开发者。不得不说,idea真的很智能、很顺手,什么重复代码片段抽取、全局搜索、智能提示,我真的是爱不释手。也让我非常佩服idea的开发者,真的是太厉害了。在使用过程中,我也记录了很多快捷键、使用技巧: 有空了一定要把这些记录整理出来,写成一篇博客,跑题了。
基于这些原因,对于代码中idea的提示,我都会看一下提示的原因,然后思考一下为什么会这样提示。今天的这篇博客也是来源于idea的一个提示
idea的提示
今天我在看别人写的代码时,idea给了一个提示,如下图: 我当时就惊呆了,idea居然提示我:StringBuffer或许可以声明为StringBuilder!!!! 也就是idea建议在这里使用StringBuilder而不是使用StringBuffer。关于StringBuffer和StringBuilder,我简单的说一下,可以选择性跳过
因为字符串底层用的是char数组,数组长度不可变,所以字符串本身也是不能改变的。字符串的那些什么replace方法,看起来好像是在修改字符串,其实都是新new了一个字符串对象。我们平时在拼接字符串时,最顺手的可能就是直接用+进行拼接了,这样会浪费内存,因为会产生许多我们不关心的字符串对象,所以在进行多次拼接时,使用StringBuilder或者StringBuffer更好。而Builder和Buffer的区别是,Buffer中所有的方法都加了synchronized,所以是线程安全的,builder虽线程不安全,但是在单线程下,速度更快
idea的开发者不会不知道Builder是线程不安全的,但是居然还给我提示,难道idea不怕开发者听从了这个建议而使代码出现问题吗?我觉得这个提示不是随随便便就提示的,并不是idea仅仅觉得Builder更快,就忽略了Builer线程不安全这个问题。那难道说,idea可以检测到当前运行环境是否是线程安全的??那idea这也太牛了吧,而且现在只是编译时期,没到运行时期,他咋知道这段代码会不会被并发呢?
那我就想,假如,我把Buffer放在一个线程不安全的坏境下,idea还是会提示我StringBuffer stringBuffer’ may be declared as 'StringBuilder吗?,
验证StringBuilder线程不安全
正如上说的那样,StringBuffer线程安全,是因为他的所有方法都加了synchronized关键字,而Builder没有,所以我们只要用多个线程对同一个StringBuilder中的方法进行并发,那么StringBuilder大概率会出问题。示例代码
public class StringBuilderDemo {
public static void main(String[] args) throws InterruptedException {
test1();
}
public static void test1() throws InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 10; i++){
new Thread(() -> {
for (int j = 0; j < 1000; j++){
stringBuilder.append("a");
stringBuffer.append("a");
}
}).start();
}
Thread.sleep(100);
System.out.println("builder length:"+stringBuilder.length());
System.out.println("buffer length:"+stringBuffer.length());
}
}
开10个线程,对同一个builder和buffer进行append,每个线程append1000次。某次运行结果
builder length:9996
buffer length:10000
所以builder是线程不安全的。
情况分析
我在写上面那段段示例代码时就发现了端倪,当我分别把Builder和Buffer 声明并new出来时,就提示我 StringBuffer stringBuffer’ may be declared as 'StringBuilder ,
但是一旦我将多线程那段代码放开时,StringBuffer stringBuffer’ may be declared as 'StringBuilder 这个提示就会消失。只是提示我,buffer被更新了,但是没有被查询,就是没有使用更新过后的内容,上面的builder也是同样的提示
所以说idea是能够检测到,你的代码中,是否对Buffer进行了并发,包括使用线程池,如下图,并没有提示StringBuffer stringBuffer’ may be declared as 'StringBuilder
经过上面的推导,我们得出了一个肤浅的结论,idea能够检测到,开发者写的代码是否对Buffer进行了并发,如果没有,idea就会提示开发者StringBuffer stringBuffer’ may be declared as 'StringBuilder 。反正你又没并发操作Buffer,那还不如用builder更快,我想这是idea开发者在开发这个功能时所想的
为什么说是肤浅的结论呢?因为我由此又想到了两个问题,这两个问题会给上面的结论做限制
问题1
能检测到我当前代码是否对buffer进行了并发,那如果我将buffer传递给别的方法呢?idea还会继续到那个方法里面去检测有没有对这个buffer进行并发吗?
我随便写了一个方法test1,这个方法接收一个StringBuffer参数 我在当前方法里面不对buffer进行并发,还是提示我StringBuffer stringBuffer’ may be declared as 'StringBuilder,而一旦我将buffer变量传递到test1方法中去,这个提示就会消失。
但实际上我在test1方法中,并没有对传递过来的buffer进行并发操作,只是简单的打印而已。所以idea的检测范围,只是在StringBuffer声明的那个方法内,一旦将buffer传递到别的方法中,不管那个方法有没有对buffer进行并发,idea都不再提示。
其实想想也是,如果这个StringBuffer被传递到各个方法,那需要跑到各个方法中去检测,得不偿失,所以只在StringBuffer声明的方法内检测,是最好的选择。既达到了提示开发者的效果,又没有过度的去检测,过度检测可能会使idea吃更大的内存
问题2
上面所说的,都是在方法内部,检测的开发者的代码(准确来说是声明StringBuffer的方法内)有没有对StringBuffer进行并发,那如果是在别的地方对声明StringBuffer的方法进行并发呢?idea是无法检测到的,因为可能是别人使用我的jar包、依赖啥的,但是idea为啥还是敢提示呢?就比如说这样:
test1方法是new StringBuffer 拼接1000个a,但是我在main方法中,创建了10个线程,对这个test1方法进行并发,但实际上这样并不会出现线程安全问题,因为没有出现多个线程对同一个StringBuffer中的同一个方法进行并发。虽然有10个线程对test1方法进行并发,但是每个线程进来之后,都各自new了个StringBuffer,然后各操作各的,所以不会出现线程安全问题。
局部变量,在方法内部,只要在方法内不调用其他方法将这个变量传递出去,这个变量就不会存在线程安全问题,所以也不用担心对这个方法进行并发。
那如果是成员变量呢?测试了一下,当StringBuffer声明成一个成员变量时,不管是否在这个类的方法中对StringBuffer的方法进行并发,都不再会提示,这是为啥呢?因为成员变量可能会会有线程安全问题, 就算在当前类中没有对这个成员变量进行并发,也可以通过反射拿到这个成员变量,然后在别的地方对这个成员变量进行并发。
总结
- 如果StringBuffer是成员变量,无论如何idea都不会提示StringBuffer stringBuffer’ may be declared as 'StringBuilder,因为idea无法保证StringBuffer不会在这个类的外部不被其他方法进行并发,即便StringBuffer是被private修饰的,没有提供对外暴露的方法,但是依然可以通过反射拿到这个StringBuffer,进行并发操作
- 如果StringBuffer是局部变量,当在这个局部范围内(方法内)没有对StringBuffer的方法进行并发,并且没有将StringBuffer传递给别的方法,idea就会提示StringBuffer stringBuffer’ may be declared as 'StringBuilder,因为在这种情况下,外部无法拿到局部的StringBuffer。
说点题外话,idea还是比较严谨的,线程安全问题,的确是一个比较大头的东西,idea提示也是非常委婉,即便是根据一些逻辑判断,才触发这个提示,但是也是用的:或许
|