您的位置:首页 > 编程语言 > Java开发

编写java程序151条建议读书笔记(6)

2017-05-10 17:00 162 查看
建议44:推荐使用序列化对象的拷贝

可以通过序列化方式在内存中通过字节流的拷贝来实现,也就是把母对象写到一个字节流中,再从字节流中将其读出来,这样就可以重建一个对象了,该新对象与母对象之间不存在引用共享的问题,也就相当于深拷贝了一个对象。被拷贝的类只要实现Serializable这个标志性接口即可,不需要任何实现,当然serialVersionUID常量还是要加上去的,然后我们就可以通过CloneUtils工具进行对象的深拷贝了,用词方法进行对象拷贝时需要注意两点:

1)对象的内部属性都是可序列化的:如果有内部属性不可序列化,则会抛出序列化异常,这会让调试者很纳闷,生成一个对象怎么回出现序列化异常呢?从这一点考虑,也需要把CloneUtils工具的异常进行细化处理。

2)注意方法和属性的特殊修饰符:比如final,static变量的序列化问题会被引入对象的拷贝中,这点需要特别注意,同时transient变量(瞬态变量,不进行序列化的变量)也会影响到拷贝的效果。当然,采用序列化拷贝时还有一个更简单的方法,即使用Apache下的 commons工具包中SerializationUtils类,直接使用更加简洁。

建议45:覆写equals方法时不要识别不出自己

建议46:equals应该考虑null值情景

null.equalsIgnoreCase方法报错,覆写equals方法遵循的一个原则对称性原则:对于任何引用x和y的情形,如果x.equals(y),把么y.equals(x)也应该返回true。解决就是前面加上非空判断即可。

建议47:在equals中使用getClass进行类型判断

在继承关系中容易出现使用instanceof判断得到非想要的结果,父类引用了instanceof关键字,它是用来判断一个类的实例对象的,这很容易让子类钻空子。想要解决也很简单,使用getClass来代替instanceof进行类型判断。

建议48:覆写equals方法必须覆写hashCode方法

HashMap的底层处理机制是以数组的方式保存Map条目的(Map Entry)的,这其中的关键是这个数组的下标处理机制:依据传入元素hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有Map条目,并且与传入的值相等则不处理,若不相等则覆盖;如果数组位置没有条目,则插入,并加入到Map条目的链表中。同理,检查键是否存在也是根据哈希码确定位置,然后遍历查找键值的。

建议49:推荐覆写toString方法

Java提供的默认toString方法不友好类名+@+hashCode

建议50:使用package-info类为包服务

package-info类,它是专门为本包服务的,说它特殊主要体现在三个方面:

1)它不能随便创建:在一般的IDE中,Eclipse、package-info等文件是不能随便被创建的,会报"Type name is notvalid"错误,类名无效。在Java中变量定义规范中规定如下字符是允许的:字母、数字、下划线,以及那个不怎么写的$符号,不过中划线可不在之列,那么怎么创建这个文件呢?很简单,用记事本创建一个,然后拷贝进去再改一下就成了,更直接的办法就是从别的项目中拷贝过来。

2)它服务的对象很特殊:一个类是一类或一组事物的描述,比如Dog这个类,就是描述"阿黄"的,那package-info这个类描述的是什么呢?它是描述和记录本包信息的。

3)package-info类不能有实现代码:package-info类再怎么特殊也是 一个类,也会被编译成 package-info.class,但是在package-info.java文件不能声明package-info类。   

package-info类还有几个特殊的地方,比如不可以继承,没有接口,没有类间关系(关联、组合、聚合等)等,Java中既然有这么特殊的一个类,那肯定有其特殊的作用了,我们来看看它的特殊作用,主要表现在以下三个方面:声明友好类和包内访问常量:这个比较简单,而且很实用,比如一个包中有很多内部访问的类或常量,就可以统一放到package-info类中,这样很方便,便于集中管理,可以减少友好类到处游走的情况。

建议51:不要主动进行垃圾回收

System.gc这样的调用主动对垃圾进行回收是一个非常危险的动作因为System.gc要停止所有的响应,才能检查内存中是否存在可以回收的对象,这对一个应用系统来说风险极大,如果是一个Web应用,所有的请求都会暂停,等待垃圾回收器执行完毕,若此时堆内存(heap)中的对象少的话还可以接受,一但对象较多(现在的web项目是越做越大,框架、工具也越来越多,加载到内存中的对象当然也就更多了),这个过程非常耗时,可能是0.01秒,也可能是1秒,甚至20秒,这就严重影响到业务的运行了。

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

java中有一个字符串池(也叫作字符串常量池,String pool或String Constant Pool或String Literal Pool),在字符串池中容纳的都是String字符串对象,它的创建机制是这样的:创建一个字符串时,首先检查池中是否有字面值相等的字符串,如果有,则不再创建,直接返回池中该对象的引用,若没有则创建之,然后放到池中,并返回新建对象的引用,

java的intern方法处理后会检查当前对象在对象池中是否存在字面值相同的引用对象,如果有则返回池中的对象,如果没有则放置到对象池中,并返回当前对象。虽然Java的每个对象都保存在堆内存中但是字符串非常特殊,它在编译期已经决定了其存在JVM的常量池(Constant Pool),垃圾回收不会对它进行回收的。

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

replaceAll方法确实需要传递两个String类型的参数,但是它要求第一个参数是正则表达式。

建议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:注意字符串的位置

 1)System.out.println(1 + 2 + "apples");2)System.out.println("apples" + 1 + 2);结果为3apples和apples12。加号的处理机制:在使用加号进行计算的表达式中,只要遇到String字符串,则所有的数据都会转换为String类型进行拼接,如果是原始数据,则直接拼接,如是对象,则调用toString方法的返回值然后拼接,在“+”
表达式中,String字符串具有最高优先级。

建议56:自由选择字符串拼接方法

字符串拼接有三种方法:加号、concat方法及StringBuilder或StringBuffer的append方法,其中加号是最常用的。在字符串拼接方式中,StringBuilder的append方法最快,StringBuffer的append方法次之(因为StringBuffer的append方法是线程安全的),其次是concat方法,加号最慢。

(1)"+" 方法拼接字符串:虽然编辑器对字符串的加号做了优化,它会使用StringBuilder的append方法进行追加,不过最终是通过toString方法转换为String字符串的,例子中的"+" 拼接的代码如下代码相同str= new StringBuilder(str).append("c").toString();它与纯粹使用StringBuilder的append方法是不同的:一是每次循环都会创建一个StringBuilder对象,二是每次执行完毕都要调用toString方法将其转换为字符串致使效率降低。

(2)concat方法拼接字符串:我们从源码上看一下concat方法的实现,其整体看上去就是一个数组拷贝,虽然在内存中处理都是原子性操作,速度非常快,不过,最后的return语句,每次concat操作都会创建一个String对象,这就是concat速度慢下来的真正原因。

(3)append方法拼接字符串:StringBuilder的append方法直接由父类AbstractStringBuilder实现整个append方法都在做字符数组处理,加长,然后拷贝数组,这些都是基本的数据处理,没有创建任何对象,所以速度也就最快了。

建议57:推荐在复杂字符串操作中使用正则表达式

统计单词个数

public class test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
String str = input.nextLine();
//正则表达式对象
Pattern p =  Pattern.compile("\\b\\w+\\b");
//生成匹配器
Matcher matcher =p.matcher(str);
int wordsCount = 0;
while(matcher.find()){
wordsCount++;
}
System.out.println(str + "单词数:" + wordsCount);
}
}
}
"\b" 表示的是一个单词的边界,它是一个位置界定符,一边为字符或数字,另外一边为非字符或数字,例如"A"这样一个输入就有两个边界,即单词"A"的左右位置,这也就说明了为什么要加上"\w"(它表示的是字符或数字)。正则表达式在字符串的查找,替换,剪切,复制,删除等方面有着非凡的作用,特别是面对大量的文本字符需要处理但是正则表达式是一个恶魔,它会使程序难以读懂,想想看,写一个包含^、$、\A、\s、\Q、+、?、()、{}、[]等符号的正则表达式,然后再告诉你这是一个"
这样,这样......"字符串查找,威力巨大,但难以控制。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: