转载--编写高质量代码:改善java程序的151个建议(第4章:字符串___建议52~55)

     2022-03-21     318

关键词:

建议52:推荐使用String直接量赋值

  一般对象都是通过new关键字生成的,但是String还有第二种生成方式,也就是我们经常使用的直接声明方式,这种方式是极力推荐的,但不建议使用new String("A")的方式赋值。为什么呢?我们看如下代码:

技术分享
public class Client58 {
    public static void main(String[] args) {
        String str1 = "詹姆斯";
        String str2 = "詹姆斯";
        String str3 = new String("詹姆斯");
        String str4 = str3.intern();
        // 两个直接量是否相等
        System.out.println(str1 == str2);
        // 直接量和对象是否相等
        System.out.println(str1 == str3);
        // 经过intern处理后的对象与直接量是否相等
        System.out.println(str1 == str4);
    }
}
技术分享

  注意看上面的程序,我们使用"=="判断的是两个对象的引用地址是否相同,也就是判断是否为同一个对象,打印的结果是true,false,true。即有两个直接量是同一个对象(进过intern处理后的String与直接量是同一个对象),但直接通过new生成的对象却与之不等,原因何在?

  原因是Java为了避免在一个系统中大量产生String对象(为什么会大量产生,因为String字符串是程序中最经常使用的类型),于是就设计了一个字符串池(也叫作字符串常量池,String pool或String Constant Pool或String Literal Pool),在字符串池中容纳的都是String字符串对象,它的创建机制是这样的:创建一个字符串时,首先检查池中是否有字面值相等的字符串,如果有,则不再创建,直接返回池中该对象的引用,若没有则创建之,然后放到池中,并返回新建对象的引用,这个池和我们平常说的池非常接近。对于此例子来说,就是创建第一个"詹姆斯"字符串时,先检查字符串池中有没有该对象,发现没有,于是就创建了"詹姆斯"这个字符串并放到池中,待创建str2字符串时,由于池中已经有了该字符串,于是就直接返回了该对象的引用,此时,str1和str2指向的是同一个地址,所以使用"=="来判断那当然是相等的了。

  那为什么使用new String("詹姆斯")就不相等了呢?因为直接声明一个String对象是不检查字符串池的,也不会把对象放到字符串池中,那当然"=="为false了。

  那为什么intern方法处理后即又相等了呢?因为intern会检查当前对象在对象池中是否存在字面值相同的引用对象,如果有则返回池中的对象,如果没有则放置到对象池中,并返回当前对象。

  可能有人要问了,放到池中,是不是要考虑垃圾回收问题呀?不用考虑了,虽然Java的每个对象都保存在堆内存中但是字符串非常特殊,它在编译期已经决定了其存在JVM的常量池(Constant Pool),垃圾回收不会对它进行回收的。

  通过上面的介绍,我们发现Java在字符串的创建方面确实提供了非常好的机制,利用对象池不仅可以提高效率,同时减少了内存空间的占用,建议大家在开发中使用直接量赋值方式,除非必要才建立一个String对象。

建议53:注意方法中传递的参数要求

   有这样的一个简单需求,写一个方法,实现从原始字符串中删除与之匹配的所有字符串,比如在"好是好"中,删除"好",代码如下:

技术分享
public class StringUtils {
    //删除字符串
    public static String remove(String source, String sub) {
        return source.replaceAll(sub, "");
    }
}
技术分享

  StringUtils工具类很简单,它采用了String的replaceAll方法,该方法是做字符串替换的,我们编写一个测试用例,检查remove方法是否正确,如下所示:

技术分享
import static org.junit.Assert.*;
import org.junit.Test;
public class TestStringUtils {

    @Test
    public void test() {
        assertTrue(StringUtils.remove("好是好","好").equals("是"));
        assertTrue(StringUtils.remove("$是$","$").equals("是"));
    }
}
技术分享

  单独运行第一个是绿条,单独运行第二个是红条,为什么第二个(assertTrue(StringUtils.remove("是","$").equals("是")))不通过呢?

  问题就出在replaceAll方法上,该方法确实需要传递两个String类型的参数,也确实进行了字符串替换,但是它要求第一个参数是正则表达式,符合正则表达式的字符串才会被替换。对上面的例子来说,第一个测试案例传递进来的是一个字符串"好",这是一个全匹配查找替换,处理的非常正确,第二个测试案例传递进来的是一个""""符号,""符号在正则表达式中表示的是字符串的结束位置,也就是执行完replaceAll后在字符串结尾的地方加上了空字符串,其结果还是""""是"",所以测试失败也就再所难免了。问题清楚了,解决方案也就出来了:使用replace方法替换即可,它是replaceAll的方法的简化版,可传递两个String参数,与我们的编码意图是吻合的。

  大家如果注意看JDK文档,会发现replace(CharSequence target,CharSequence replacement)方法是1.5版本以后才开始提供的, 在此之前如果要对一个字符串进行全体换,只能使用replaceAll方法,不过由于replaceAll方法的第二个参数使用了正则表达式,而且参数类型只要是CharSequence就可以(String的父类),所以很容易使使用者误解,稍有不慎就会导致严重的替换错误。

  注意:replaceAll传递的第一个参数是正则表达式  

建议54:正确使用String、StringBuffer、StringBuilder

   CharSequence接口有三个实现类与字符串有关,String、StringBuffer、StringBuilder,虽然它们都与字符串有关,但其处理机制是不同的。

  String类是不可变的量,也就是创建后就不能再修改了,比如创建了一个"abc"这样的字符串对象,那么它在内存中永远都会是"abc"这样具有固定表面值的一个对象,不能被修改,即使想通过String提供的方法来尝试修改,也是要么创建一个新的字符串对象,要么返回自己,比如:

String  str = "abc";
String str1 = str.substring(1);

  其中str是一个字符串对象,其值是"abc",通过substring方法又重新生成了一个字符串str1,它的值是"bc",也就是说str引用的对象一但产生就永远不会变。为什么上面还说有可能不创建对象而返回自己呢?那是因为采用substring(0)就不会创建对象。JVM从字符串池中返回str的引用,也就是自身的引用。

  StringBuffer是一个可变字符串,它与String一样,在内存中保存的都是一个有序的字符序列(char 类型的数组),不同点是StringBuffer对象的值是可改变的,例如:

StringBuffer sb = new StringBuffer("a");
sb.append("b");

  从上面的代码可以看出sb的值在改变,初始化的时候是"a" ,经过append方法后,其值变成了"ab"。可能有人会问了,这与String类通过 "+" 连接有什么区别呢?例如

String s = "a";
s = s + "b";

  有区别,字符串变量s初始化时是 "a" 对象的引用,经过加号计算后,s变量就修改为了 “ab” 的引用,但是初始化的 “a” 对象还没有改变,只是变量s指向了新的引用地址,再看看StringBuffer的对象,它的引用地址虽不变,但值在改变。

  StringBuffer和StringBuilder基本相同,都是可变字符序列,不同点是:StringBuffer是线程安全的,StringBuilder是线程不安全的,翻翻两者的源代码,就会发现在StringBuffer的方法前都有关键字syschronized,这也是StringBuffer在性能上远远低于StringBuffer的原因。

  在性能方面,由于String类的操作都是产生String的对象,而StringBuilder和StringBuffer只是一个字符数组的再扩容而已,所以String类的操作要远慢于StringBuffer 和 StringBuilder。

  弄清楚了三者之间的原理,我们就可以在不同的场景下使用不同的字符序列了:

  1. 使用String类的场景:在字符串不经常变化的场景中可以使用String类,例如常量的声明、少量的变量运算等;
  2. 使用StringBuffer的场景:在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程的环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装等;
  3. 使用StringBuilder的场景:在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼接,JSON封装等。

  注意:在适当的场景选用字符串类型 

建议55:注意字符串的位置

   看下面一段程序:

技术分享
public class Client55 {
    public static void main(String[] args) {
        String str1 = 1 + 2 + "apples";
        String str2 = "apples" + 1 + 2;
        System.out.println(str1);
        System.out.println(str2);
    }
}
技术分享

  想想两个字符串输出的结果的苹果数量是否一致,如果一致,会是几呢?

  答案是不一致,str1的值是"3apples" ,str2的值是“apples12”,这中间悬殊很大,只是把“apples” 调换了一下位置,为何会发生如此大的变化呢?

  这都源于java对于加号的处理机制:在使用加号进行计算的表达式中,只要遇到String字符串,则所有的数据都会转换为String类型进行拼接,如果是原始数据,则直接拼接,如是是对象,则调用toString方法的返回值然后拼接,如:

  str =  str + new ArrayList();

  上面就是调用ArrayList对象的toString方法返回值进行拼接的。再回到前面的问题上,对与str1 字符串,Java的执行顺序是从左到右,先执行1+2,也就是算术加法运算,结果等于3,然后再与字符串进行拼接,结果就是 "3 apples",其它形式类似于如下计算:

  String str1 = (1 + 2 ) + "apples" ;

  而对于str2字符串,由于第一个参与运算的是String类型,加1后的结果是“apples 1” ,这仍然是一个字符串,然后再与2相加,结果还是一个字符串,也就是“apples12”。这说明如果第一个参数是String,则后续的所有计算都会转变为String类型,谁让字符串是老大呢!

  注意: 在“+” 表达式中,String字符串具有最高优先级。

转载---编写高质量代码:改善java程序的151个建议(第2章:基本类型___建议26~30)

阅读目录建议26:提防包装类型的null值建议27:谨慎包装类型的大小比较建议28:优先使用整型池建议29:优先选择基本类型建议30:不要随便设置随机种子回到顶部建议26:提防包装类型的null值  我们知道Java引入包装类型(Wrapp... 查看详情

转载--编写高质量代码:改善java程序的151个建议(第5章:数组和集合___建议60~64)

阅读目录建议60:性能考虑,数组是首选建议61:若有必要,使用变长数组建议62:警惕数组的浅拷贝建议63:在明确的场景下,为集合指定初始容量建议64:多种最值算法,适时选择      噢,它明白了,河水既没有牛伯... 查看详情

转载--编写高质量代码:改善java程序的151个建议(第3章:类对象及方法___建议31~35)

阅读目录建议31:在接口中不要存在实现代码建议32:静态变量一定要先声明后赋值建议33:不要覆写静态方法建议34:构造函数尽量简化建议35:避免在构造函数中初始化其它类                  书读的多... 查看详情

转载--编写高质量代码:改善java程序的151个建议(第4章:字符串___建议52~55)

阅读目录建议52:推荐使用String直接量赋值建议53:注意方法中传递的参数要求建议54:正确使用String、StringBuffer、StringBuilder建议55:注意字符串的位置回到顶部建议52:推荐使用String直接量赋值  一般对象都是通过new关键字生... 查看详情

转载---编写高质量代码:改善java程序的151个建议(第3章:类对象及方法___建议47~51)

阅读目录建议47:在equals中使用getClass进行类型判断建议48:覆写equals方法必须覆写hashCode方法建议49:推荐覆写toString方法建议50:使用package-info类为包服务建议51:不要主动进行垃圾回收回到顶部建议47:在equals中使用getClass进行... 查看详情

转载--编写高质量代码:改善java程序的151个建议(第5章:数组和集合___建议65~69)

阅读目录建议65:避开基本类型数组转换列表陷阱建议66:asList方法产生的List的对象不可更改建议67:不同的列表选择不同的遍历算法建议68:频繁插入和删除时使用LinkList建议69:列表相等只关心元素数据回到顶部建议65:避开基... 查看详情

转载---编写高质量代码:改善java程序的151个建议(第3章:类对象及方法___建议41~46)

阅读目录建议41:让多重继承成为现实建议42:让工具类不可实例化建议43:避免对象的浅拷贝建议44:推荐使用序列化对象的拷贝建议45:覆写equals方法时不要识别不出自己建议46:equals应该考虑null值情景回到顶部建议41:让多重... 查看详情

转载--编写高质量代码:改善java程序的151个建议(第1章:java开发中通用的方法和准则___建议16~20)

阅读目录建议16:易变业务使用脚本语言编写建议17:慎用动态编译建议18:避免instanceof非预期结果建议19:断言绝对不是鸡肋建议20:不要只替换一个类回到顶部建议16:易变业务使用脚本语言编写  Java世界一直在遭受着异种... 查看详情

转载----编写高质量代码:改善java程序的151个建议(第1章:java开发中通用的方法和准则___建议1~5)

阅读目录建议1:不要在常量和变量中出现易混淆的字母建议2:莫让常量蜕变成变量  建议3:三元操作符的类型务必一致 建议4:避免带有变长参数的方法重载建议5:别让null值和空值威胁到变长方法      ... 查看详情

编写高质量代码:改善java程序的151个建议-笔记

1、字母“l”作为长整形标志时务必大写。eg: long num=11L;2、3、4、 查看详情

编写高质量代码:改善java程序的151个建议-笔记

1、字母“l”作为长整形标志时务必大写。eg: long num=11L;2、3、4、 查看详情

转载--编写高质量代码:改善java程序的151个建议(第1章:java开发中通用的方法和准则___建议11~15)

阅读目录建议11:养成良好习惯,显示声明UID建议12:避免用序列化类在构造函数中为不变量赋值建议13:避免为final变量复杂赋值建议14:使用序列化类的私有方法巧妙解决部分属性持久化问题建议15:break万万不可忘回到顶部建... 查看详情

编写高质量代码:改善java程序的151个建议--[78~92](代码片段)

编写高质量代码:改善Java程序的151个建议--[78~92]HashMap中的hashCode应避免冲突多线程使用Vector或HashTableVector是ArrayList的多线程版本,HashTable是HashMap的多线程版本。非稳定排序推荐使用List对于变动的集合排序set=newTreeSet使用TreeSet是... 查看详情

编写高质量代码:改善java程序的151个建议--[52~64](代码片段)

编写高质量代码:改善Java程序的151个建议--[52~64]推荐使用String直接量赋值Java为了避免在一个系统中大量产生String对象(为什么会大量产生,因为String字符串是程序中最经常使用的类型),于是就设计了一个字符串池(也叫作字符串常... 查看详情

编写高质量代码:改善java的151个建议四(类对象方法)31-51

31.接口中不要存在实现代码  接口中不能存在实现代码(虽然可以实现,但是如果把实现代码写在接口中,那么接口就绑定了可能变化的因素,这就导致实现不在文档和可靠,是随时可能被抛弃,被修改,被重构的)packagejsont... 查看详情

编写高质量代码:改善java程序的151个建议(第1章:java开发中通用的方法和准则___建议11~15)

建议11:养成良好习惯,显示声明UID我们编写一个实现了Serializable接口(序列化标志接口)的类,Eclipse马上就会给一个黄色警告:需要添加一个SerialVersionID。为什么要增加?他是怎么计算出来的?有什么用?下面就来解释该问题... 查看详情

编写高质量代码:改善java程序的151个建议(第1章:java开发中通用的方法和准则___建议16~20)

建议16:易变业务使用脚本语言编写  Java世界一直在遭受着异种语言的入侵,比如PHP,Ruby,Groovy、Javascript等,这些入侵者都有一个共同特征:全是同一类语言-----脚本语言,它们都是在运行期解释执行的。为什么Java这种强编... 查看详情

编写高质量代码:改善java程序的151个建议(第1章:java开发中通用的方法和准则___建议6~10)

建议6:覆写变长方法也循规蹈矩   在JAVA中,子类覆写父类的中的方法很常见,这样做既可以修正bug,也可以提供扩展的业务功能支持,同时还符合开闭原则(Open-ClosedPrinciple)。符合开闭原则(Open-ClosedPrinciple)的主要特征: ... 查看详情