[改善Java代码]避免对象的浅拷贝】的更多相关文章

建议43: 避免对象的浅拷贝 我们知道一个类实现了Cloneable接口就表示它具备了被拷贝的能力,如果再覆写clone()方法就会完全具备拷贝能力.拷贝是在内存中进行的,所以在性能方面比直接通过new生成对象要快很多,特别是在大对象的生成上,这会使性能的提升非常显著.但是对象拷贝也有一个比较容易忽略的问题:浅拷贝(Shadow Clone,也叫做影子拷贝)存在对象属性拷贝不彻底的问题.我们来看这样一段代码: public class Client { public static void ma…
建议62:警惕数组的浅拷贝 一.分析  在日常工作中,我们会遇见很多数组的拷贝和复制的问题,但是在你使用系统提供的API进行编码的时候,无形中会留下浅拷贝的隐患. 二.场景  有这样一个例子,第一个箱子里面与赤橙黄绿青蓝紫7色气球,现在希望第二个箱子也放入7个气球,其中最后一个气球改为蓝色,也就是赤橙黄绿青蓝蓝七个气球. import org.apache.commons.lang3.builder.ToStringBuilder; public class Client{ public sta…
建议44: 推荐使用序列化实现对象的拷贝 上一个建议说了对象的浅拷贝问题,实现Cloneable接口就具备了拷贝能力,那我们来思考这样一个问题:如果一个项目中有大量的对象是通过拷贝生成的,那我们该如何处理?每个类都写一个clone方法,并且还要深拷贝?想想看这是何等巨大的工作量呀,是否有更好的方法呢? 其实,可以通过序列化方式来处理,在内存中通过字节流的拷贝来实现,也就是把母对象写到一个字节流中,再从字节流中将其读出来,这样就可以重建一个新对象了,该新对象与母对象之间不存在引用共享的问题,也就相…
第3章  类.对象及方法 书读得多而不思考,你会觉得自己知道的很多. 书读得多而思考,你会觉得自己不懂的越来越多. —伏尔泰 在面向对象编程(Object-Oriented Programming,OOP)的世界里,类和对象是真实世界的描述工具,方法是行为和动作的展示形式,封装.继承.多态则是其多姿多彩的主要实现方式,如此,OOP才会像现在这样繁荣昌盛.欣欣向荣. 本章主要讲述关于Java类.对象.方法的种种规则.限制及建议,让读者在面向对象编程的世界中走得更远,飞得更高. 建议31: 在接口中…
建议52:推荐使用String直接量赋值 一.建议 String对象的生成方式有两种: 1.通过new关键字生成,String str3 = new String(“中国”); 2.直接声明,如:Str str = "a". 对于String对象来说,极力推荐使用直接声明的方式. 二.分析 Java为了避免在一个系统中大量产生String对象,于是就设计了一个字符串池(也有叫做字符串常量池String Pool或String Constant Pool或String Literal P…
建议16: 易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP.Ruby.Groovy.JavaScript等,这些“入侵者”都有一个共同特征:全是同一类语言—脚本语言,它们都是在运行期解释执行的.为什么Java这种强编译型语言会需要这些脚本语言呢?那是因为脚本语言的三大特征,如下所示: 1.灵活.脚本语言一般都是动态类型,可以不用声明变量类型而直接使用,也可以在运行期改变类型. 2.便捷.脚本语言是一种解释型语言,不需要编译成二进制代码,也不需要像Java一样生成字节…
上一个建议之处了asList方法在转换基本类型数组时候存在的问题,在看下asList方法返回的列表有何特殊的地方.看代码: import java.util.Arrays; import java.util.List; public class Client { //枚举,声明一个星期 enum Week{Sun,Mon, Tue, Wed,Thu,Fri,Sat} public static void main(String[] args) { //工作日 Week[] workDays =…
我们知道Set与List的最大区别就是Set中的元素不可以重复(这个重复指的equals方法的返回值相等),其他方面则没有太大的区别了,在Set的实现类中有一个比较常用的类需要了解一下:TreeSet,该类实现了类默认排序为升序的Set集合,如果插入一个元素,默认会按照升序排列(当然是根据Comparable接口的compareTo的返回值确定排序位置了),不过,这样的排序是不是在元素经常变化的场景中也适用呢?我们来看例子: import java.util.SortedSet; import…
List接口提供了subList方法,其作用是返回一个列表的子列表.这与String类的subString有点类似.但是他们的功能是否相同?看代码: import java.util.ArrayList; import java.util.List; public class Client { public static void main(String[] args) { //定义一个包含两个字符串的列表 List<String> c = new ArrayList<String>…
一.分析  字符串的操作,诸如追加.合并.替换.倒序.分隔等,都是在编码过程中经常用到的,而且Java也提供了append.replace.reverse.split等方法来完成这些操作,它们使用起来确实方便,但是更多的时候,需要使用正则表达式来完成复杂的处理. 二.场景  统计一篇文章中的单词的数量,代码如下: import java.util.Scanner; public class Client { public static void main(String[] args) { //接…
在系统开发中我们经常会使用HashMap作为数据集容器,或者是用缓冲池来处理,一般很稳定,但偶尔也会出现内存溢出的问题(OutOfMemory错误),而且这经常是与HashMap有关的.而且这经常是与HashMap有关的.比如我们使用缓冲池操作数据时,大批量的增删改产操作就可能会让内存溢出,下面建立一段模拟程序,重现该问题,看代码: import java.util.HashMap; import java.util.Map; public class Client { public stati…
一.场景: 我们来看一个场景,统计一个省的各科高考科目考试的平均分. 当然使用数据库中的一个SQL语句就能求出平均值,不过这个不再我们的考虑之列,这里只考虑使用纯Java的方式来解决.(由于我的机器配置比较好,用80万的数据量有点小,各个统计结果不明显,不能更加显著的说明明问题...这里改为800万) 看代码: import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.uti…
为了更好的使用枚举,Java提供了两个枚举集合:EnumSet和EnumMap,这两个集合的使用方法都比较简单,EnumSet表示其元素必须是某一枚举的枚举项,EnumMap表示Key值必须是某一枚举的枚举项,由于枚举类型的实例数量固定并且有限,相对来说,EnumSet和EnumMap的效率会比其他Set和Map要高. 虽然EnumSret很好用,但是它有一个隐藏的特点.项目中可能定义非常多的枚举项,然后通过EnumSet访问,遍历,但它对不同的枚举数量有不同的处理方式.为了进行对比,我们定义两…
工厂方法模式(Factory Method Patter)是"创建对象的接口",让子类决定实例化哪一个类,并使一个类的实例化延迟到其子类.工厂方法模式在我们的开发工作中,经常会用到. 下面以汽车制造为例,看看一般的工厂方法模式是如何实现的,代码如下: public class Client { public static void main(String[] args) { //生产车辆 Car car = CarFactory.createCar(FordCar.class); }…
每个枚举都是java.lang.Enum的子类,都可以访问Enum类提供的方法,比如hashCode(),name(),valueOf()等..... 其中valueOf()方法会把一个String类型的名称转变为枚举项,也就是枚举项中查找出字面值与该参数相等的枚举项,虽然这个方法很简单,但是JDK却做了一个对于开发人员来说并不简单的处理: 看代码: import java.util.Arrays; import java.util.List; public class Client { pub…
上一个建议解释了为什么要使用forName,本建议就说说哪些地方不适合使用动态加载. 如果forName要加载一个类,那它必须是一个类------8中基本类型就排除在外.它们不是一个具体的类. 其次它必须具有可追溯的类路径...否则就会报ClassNotFoundException. 在Java中,数组是一个非常特殊的类,虽然它是一个类,单没有定义类路径. public class Client { public static void main(String[] args) throws Ex…
动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,对Java程序来说,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时再决定是否需要加载一个类,比如从Web上接受一个String参数作为类名,然后在JVM中加载并初始化,这就是动态加载,此动态加载通常是通过Class.forName(String)实现的,只是这个forName()方法到底是什么意思? 我们知道一个类文件只有在被加载到内存中后才可能生成实例对象,也就是说一个对象的生成必…
Java语言是先把Java源文件编译成后缀为class的字节码文件,然后再通过ClassLoader机制把这些类文件加载到内存中,最后生成实例执行的,这是Java处理的基本机制,但加载到内存中的数据是如何描述一个类的呢? 比如在Dog.class文件中定义了的是一个Dog类,那它在内存中是如何展现的呢? Java使用一个元类(MetaClass)来描述加载到内存中的类数据,这就是Class类,它是一个描述类的类对象,比如Dog.class文件加载到内存中后就是一个Class实例对象描述. 因为C…
从哲学上来说,很难描述一个具体的人,你可以描述它的长相,性格,工作等,但是人都是有多重身份的,估计只有使用多个And(与操作)将所有的描述串联起来才能描述一个完整的人,人在不同的环境中角色也在不断的更换. 用Java程序来对一类人进行管理,比如在公交车费优惠系统中,对部分人员,工资低于2500元的上班族而且是站立着的乘客 车费打8折. 这里的类型参数有两个限制条件,一为上班族,二为乘客,具体到程序中就是一个泛型参数有两个上界Upper Bound,首先定义两个接口以及实现类: //职员 inte…
什么叫做协变(covariance)和逆变(contravariance)? 在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是用一个窄类型替换宽类型,而逆变则是用宽类型覆盖窄类型. 协变:宽类型------>窄类型 逆变:窄类型------>宽类型 class Base{ public Number doStuff(){ return 0; } } class Sub extends Base{ @Override pu…
Java泛型支持通配符(Wildcard),可以单独使用一个"?"表示任意类,也可以使用extends关键字标识某一类(接口)的子类型,还可以使用super关键字标识某一类(接口)的父类型,但问题是什么时候该用extends,什么时候该用super? (1)泛型结构只参与"读"操作则限定上界(extends 关键字) 于都如下代码,看业务逻辑操作是否还能继续? import java.util.Arrays; import java.util.List; publi…
Arrays工具类有一个方法asList可以把一个变长参数或数组变成列表,但是它有一个缺点:它所生成的List长度是不可改变的,而这在我们的项目开发中很不方便. import java.util.Arrays; import java.util.List; public class Client { public static void main(String[] args) { String[] strArray = {"1","18","71"…
泛型类型在编译期被擦除,我们在类初始化时将无法获得泛型的具体参数,比如这样的代码: class Foo<T>{ //private T t =new T();//报错Cannot instantiate the type T //private T[] tArray= new T[5];//报错Cannot create a generic array of T private List<T> list= new ArrayList<T>(); } 这段代码有什么问题?…
泛型可以减少强制类型的转换,可规范集合的元素类型,还可以提高代码的安全性和可读性,正是因为有了这些优点,自从Java引入泛型之后,项目的编码规则上便多了一条,优先使用泛型. Java泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,它与C++中的模板templates比较类似.但是有一点,Java的反省在编译期有效,在运行期被删除,也就是说所有的泛型参数类型在编译后都会被清除掉. 看如下代码: import java.util.List; public class Foo {…
异常原本是正常逻辑的补充,但是有时候会被当做主逻辑使用.看如下代码: public class Client { enum Color { Red, Blue; } public static void main(String[] args) { System.out.println(Contain(Color.class, "Red"));//true System.out.println(Contain(Color.class, "Yellow"));//fal…
Java的异常机制有三种: 一.Error类以及其子类表示的是错误,它是不需要程序员处理也不能处理的异常.比如VirtualMachineError虚拟机错误,ThreadDeath线程僵尸等. 二.RuntimeException类及其子类表示的是非受检查异常,是系统可能会抛出的异常,程序员可以去处理,也可以不去处理,最经典的就是NullPointerException空指针异常和IndexOutOfBoundsException越界异常. 三.Exception类及其子类(不包含非受检查异常…
JavaAPI提供的异常都是比较低级别的,低级别是指只有开发人员才能看懂的异常.而对于终端用户来说基本上就是天书,与业务无关,是纯计算机语言的描述. 异常封装的三方面的好处: 1)提高系统的友好性   2)提高性能的可维护性   3)解决java异常机制自身的缺陷 (1) 提高系统的友好性. 打开一个文件,如果文件不存在,则会报FileNotFoundException异常,如果该方法的编写不做任何的处理,直接抛到上层,则会降低系统的友好性. 代码如下: public static void d…
建议42: 让工具类不可实例化 Java项目中使用的工具类非常多,比如JDK自己的工具类java.lang.Math.java.util.Collections等都是我们经常用到的.工具类的方法和属性都是静态的,不需要生成实例即可访问,而且JDK也做了很好的处理,由于不希望被初始化,于是就设置构造函数为private访问权限,表示除了类本身外,谁都不能产生一个实例,我们来看一下java.lang.Math代码: public final class Math { /** * Don't let…
建议39: 使用匿名类的构造函数 阅读如下代码,看看是否可以编译: public class Client { public static void main(String[] args) { List l1 = new ArrayList(); List l2 = new ArrayList(){}; List l3 = new ArrayList(){{}}; System.out.println(l1.getClass() == l2.getClass()); System.out.pri…
建议38: 使用静态内部类提高封装性 Java中的嵌套类(Nested Class)分为两种:静态内部类(也叫静态嵌套类,Static Nested Class)和内部类(Inner Class).内部类我们介绍过很多了,现在来看看静态内部类.什么是静态内部类呢?是内部类,并且是静态(static修饰)的即为静态内部类.只有在是静态内部类的情况下才能把static修复符放在类前,其他任何时候static都是不能修饰类的. 静态内部类的形式很好理解,但是为什么需要静态内部类呢?那是因为静态内部类有…