类、对象及方法。

  • 在接口中不要存在实现代码

        接口中可以声明常量,声明抽象方法,也可以继承父接口,但就是不能具体实现,因为接口是一种契约(Contract),是一种框架性协议,这表明他的实现类都是同一种类型,或者是具备相似特征的一个集合体。

        接口是一个契约,不仅仅约束着实现者,同时也是一个保证,保证提供的服务(常量、方法)是稳定、可靠的,如果把实现代码写到接口中,那接口就绑定了可能变化的因素,着就会导致实现不再稳定和可靠,是随时都可能被抛弃、被修改、被重构的。所以,接口中虽然可以有实现,但应避免使用。

  • 静态变量一定要先声明后赋值

        静态变量是类加载时被分配到数据区(Data Area)的,他在内存中只有一个拷贝,不会被分配多次,其后的所有赋值操作都是值改变,地址则保持不变,我们知道JVM初始化变量是先声明空间,然后再赋值的,也就是说:

        int i = 100;

        在JVM中是分开执行,等价于:

        int i; // 分配地址空间

        i = 100; // 赋值

        静态变量是在类初始化时首先被加载的,JVM会去查找类中所有的静态声明,然后分配空间,注意这时候只是完成了地址空间的分配,还没有赋值,之后JVM会根据类中静态值(包括静态类赋值和静态块赋值)的先后顺序来执行。

  • 不要覆写静态方法

        在Java中可以通过覆写(Override)来增强或减弱父类的方法和行为,但覆写是针对非静态方法(也叫做实例方法,只有生成实例才能调用的方法)的,不能针对静态方法(static修饰的方法,也叫做类方法)。

        一个实例对象有两个类型:表面类型(Apparent Type)和实际类型(Actual Type),表米娜类型是声明时的类型,实际类型是对象产生时的类型。对于非静态方法来说就比较特殊了,首先静态方法不依赖实例对象,他是通过类名访问的;其次,可以通过对象访问静态方法,如果是通过对象调用静态方法,JVM则会通过对象的表面类型查找到静态方法的入口,继而执行之。

        在子类中构建与父类相同的方法名、输入参数、输出参数、访问权限(权限可以扩大),并且父类、子类都是静态方法,此种行为为隐藏(Hide),他与覆写有两点不同:

        1、表现形式不同。隐藏用于静态方法,覆写用于非静态方法。在代码上的表现是:@Override注解可以用于覆写,不能用于隐藏。

        2、职责不同。隐藏的目的是为了抛弃父类静态方法,重现子类方法,也就是期望父类的静态方法不要破坏子类的业务行为;而覆写则是将父类的行为增强或减弱,延续父类的职责。

  • 构造函数尽量简化

        子类实例化时,会首先初始化父类(注意这里是初始化,可不是生成父类对象),也就是初始化父类的变量,调用父类的构造函数,然后才会初始化子类的变量,调用子类自己的构造函数,最后生成一个实例化对象。

        注意:构造函数简化,再简化,应该达到“一眼洞穿”的境界。

  • 避免在构造函数中初始化其他类
  • 使用构造代码块精炼程序

        用大括号把多行代码封装在一起,形成一个独立的数据体,实现特定算法的代码集合即为代码块,一般来说代码块是不能单独运行的,必须要有运行主体。在Java中一共有四种类型的代码块:

        1、普通代码块:就是在方法后面使用“{}”括起来的代码片段,他不能单独执行,必须通过方法名调用执行。

        2、静态代码块:在类中使用static修饰,并使用“{}”括起来的代码片段,用于静态变量的初始化或对象创建前的环境初始化。

        3、同步代码块:使用synchronized关键字修饰,并使用“{}”括起来的代码片段,他表示同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制。

        4、构造代码块:在类中没有任何的前缀或后缀,并使用“{}”括起来的代码片段。

        构造代码块会在每个构造函数内首先执行(需要注意的是:构造代码块不是在构造函数之前运行的,他依托于构造函数的执行)。明白了这一点,我们就可以把构造代码块应用到如下场景中:

        1、初始化实例变量(Instance Variable)

        2、初始化比例环境

        以上两个场景利用了构造代码块的两个特性:在每个构造函数中都运行和在构造函数中他会首先运行。很好的利用构造代码块的这两个特性不仅可以减少代码量,还可以让程序更容易阅读,特别是当所有的构造函数都要实现逻辑,而且这部分逻辑有很复杂时,这时就可以通过编写多个构造代码块来实现。每个代码块完成不同的业务逻辑(当然了,构造函数尽量简单,这时基本原则),按照业务顺序依次存放,这样在创建实例对象时JVM也就会按照顺序依次执行,实现复杂对象的模块化创建。

  • 构造代码块会想你所想

        如果遇到this关键字(也就是构造函数调用自身其他的构造函数时)则不插入构造代码块。

        构造代码块是为了提取构造函数的共同量,减少各个构造函数的代码而产生的,因此,Java就很聪明的认为把代码块插入到没有this方法的构造函数中即可,而调用其他构造函数的则不插入,确保每一个构造函数只执行一次构造代码块。

        this是特殊情况,super不会有类似处理。

  • 使用静态内部类提高封装性

        静态内部类是内部类,并且是静态(static修饰)的即为静态内部类。只有在是静态内部类的情况下才能把static修复符放在类前,其他任何时候static都是不能修饰类的。

        静态内部类与一般定义的类优点:

        1、提高封装性。

        2、提高代码的可读性。

        3、形似内部,神似外部。

        静态内部类与普通内部类的区别:

        1、静态内部类不持有外部类的引用。

        2、静态内部类不依赖外部类。

        3、普通内部类不能声明static的方法和变量。

  • 使用匿名类的构造函数
  • 匿名类的构造函数很特殊

        匿名类的构造函数特殊处理机制,一般类(也就是具有显式名字的类)的所有构造函数默认都是调用父类的无参构造的,而匿名类因为没有名字,只能由构造代码块代替,也就无所谓的有参和无参构造函数了,他的初始化时直接调用了父类的同参数构造,然后再调用了自己的构造代码块。

        首先会调用父类有两个参数的构造函数,而不是无参构造,这时匿名类的构造函数与普通类的差别。

  • 让多重继承成为现实

        内部类的一个重要特性:内部类可以继承一个与外部类无关的类,保证了内部类的独立性,正是基于这一点,多重继承才会成为可能。

  • 让工具类不可实例化

        工具类的方法和属性都是静态的,不需要生成实例即可访问,而且JDK也做了很好的处理,由于不希望被初始化,于是就设置构造函数为private访问权限,表示除了类本身外,谁都不能产生一个实例。

        工具类的构造函数中,不仅仅设置成private访问权限,还抛异常。

        工具类最好不要做继承的打算,因为如果子类可以实例化的话,那就要调用父类的构造函数,可是父类没有可以被访问的构造函数,于是问题就会出现。

        注意:如果一个类不允许实例化,就要保证“平常”渠道都不能实例化他。

  • 避免对象的浅拷贝

        一个类实现了Cloneable接口就表示他具备了被拷贝的能力,如果再覆写clone()方法就会完全具备拷贝能力。拷贝是在内存中进行的,所以在性能方面比直接通过new生成对象要快很多,特别是在大对象的生成上,这会使性能的提升非常显著。但是对象拷贝也有一个比较容易忽略的问题:浅拷贝(Shadow Clone,也叫做影子拷贝)存在对象属性拷贝不彻底的问题。

        所有类都继承自Object,Object提供了一个对象拷贝的默认方法,即super.clone方法,但是该方法是有缺陷的,他提供的是一种浅拷贝方式,也就是说他并不会把对象的所有属性全部拷贝一份,而是有选择性的拷贝,他的拷贝规则如下:

        1、基本类型:如果变量是基本类型,则拷贝其值,比如int、float等。

        2、对象:如果变量是一个实例对象,则拷贝地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。这在Java中是很疯狂的,因为他突破了访问全县的定义:一个private修饰的变量,竟然可以被两个不同的实例对象访问,这让Java的访问权限系情何以堪!

        3、String字符串:这个比较特殊,拷贝的也是一个地址,是个引用,但是在修改时,他会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,在此处我们可以认为String是一个基本类型。

  • 推荐使用序列化实现对象的拷贝

        通过序列化方式来处理,在内存中通过字节流的拷贝实现,也就是把母对象写到一个字节流中,再从字节流中将其读出来,这样就可以重建一个新对象了,该新对象与母对象之间不存在引用共享的问题,也就相当于深拷贝了一个新对象。

        用此方法进行对象拷贝时需要注意两点:

        1、对象的内部属性都是可序列化的

        2、注意方法和属性的特殊修饰符

        当然,采用序列化方式拷贝时还有一个更简单的方法,即使用Apache下的commons工具包中的SerializationUtils类,直接使用更加简洁方便。

  • 覆写equals方法时不要识别不出自己
        equals方法的自反性原则:对于任何非空引用x,x.equals(x)应该返回true。
  • equals应该考虑null值情景
        对称性原则:对于任何引用x和y的情形,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。
  • 在equals中使用getClass进行类型判断

        equals的传递性原则是指对于实例对象x、y、z来说,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。

        在覆写equals时建议使用getClass进行类型判断,而不要使用instanceof。

  • 覆写equals方法必须覆写hashCode方法

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

        对象元素的hashCode方法返回的值是一个对象的哈希码,是由Object类的本地方法生成的,确保每个对象有一个哈希码(这也是哈希算法的基本要求:任意输入k,通过一定算法f(k),将其转换为非可逆的输出,对于两个输入k1和k2,要求若k1=k2,则必须f(k1)=f(k2),但也允许k1≠k2,f(k1)=f(k2)的情况存在)。

        HashCodeBuilder是org.apache.commons.lang.builder包下的一个哈希码生成工具。

  • 推荐覆写toString方法

        使用apache的commons工具包中的ToStringBuilder类。

        println的实现机制:如果把一个原始类就直接打印,如果是一个类类型,则打印出其toString方法的返回值。

  • 使用package-info类为包服务

        专门为本包服务的,特殊体现在3个方面:

        1、他不能随便被创建

        2、他服务的对象很特殊

        3、package-info类不能有实现代码

        作用体现在3个方面:

        1、声明友好类和包内访问常量

        2、为在包上标注注解提供便利

        3、提供包的整体注释说明

  • 不要主动进行垃圾回收

猜你喜欢

转载自blog.csdn.net/en_joker/article/details/80453709