[笔记]改善Java程序的151个建议---第三章 类,对象,方法

第三章 类,对象,方法
建议31:在接口中不要存在实现代码
接口是一种契约,框架协议,表明它的实现类都是同一种类型,或者具备相似特征的一个集合体。

建议32:静态变量一定要先声明后赋值
静态变量在类初始化时候首先被加载。
public static int i = 1;
static{
     i = 100;
}

建议33:不要覆写静态方法
@Override针对非静态方法
一个实例对象有2个类型,表面类型(Apparent Type),实际类型(Actual Type)。
表面类型是声明时的类型
实际类型是对象产生时候的类型。
非静态方法是根据对象的实际类型执行的。
静态方法特殊,不依赖实例对象,通过类名访问,也可以通过对象访问。对象访问的话,JVM会通过对象的表面类型找到静态方法入口。

在子类中构建与父类相同的静态方法,这种行为叫“隐藏”。

通过实例对象访问静态方法或者属性不是好习惯。

建议34:构造函数尽量简化
构造函数初始化顺序:
子类实例化,首先初始化父类(初始化不是生产父类对象)初始化父类的变量,调用父类的构造函数,然后才会初始化子类的变量,调用子类的构造函数。最后才生产一个实例对象。
构造函数,用于初始化变量,声明实例的上下文。

建议35:避免在构造函数中初始化其他类
不要在构造函数中声明初始化其他类,是良好习惯。
否则会造成StackOverflowError

建议36:使用构造代码块精炼程序
代码块:用大括号把多行代码封装在一起,形成一个独立的数据体,实现特定算法的代码集合。
Java中有4种类型代码块:
(1)普通代码块
方法后面用{},必须通过方法名调用。
(2)静态代码块
static{},用于静态变量初始化,对象创建前的环境初始化。
(3)同步代码块
synchronized{},表示同一时间只能一个线程进入该代码块。一种线程保护机制。
(4)构造代码块
类中没有任何前缀、后缀,用{}括起来。

编译器如果处理构造代码块?编译器会把构造代码块插入到每个构造函数的最前端。构造代码块会在每个构造函数内首先执行。

应用场景:在每个构造函数中运行,在构造函数中首先运行。
初始化实例变量
初始化实例环境

public class Client{
     {
          //构造代码块
          System.out.println("构造代码块");
     }
    
     Client(){
          System.out.println("无参构造函数");
     }
    
     Client(String str){
          System.out.println("有参构造函数");
     }
}

建议37:构造代码块会想你所想
统计一个类实例的数量
构造函数调用自身其他的构造函数(this关键字),则不插入构造代码块。
class Base{
     //对象计数器
     private static int numOfObjects = 0;
    
     {
          //构造代码块
          numOfObject++;
     }
    
     public Base(){}
    
     //有参调用无参构造
     public Base(String str) {
          this();
     }
    
     //有参不调用其它构造
     public Base(int i){
     }
    
     public static int getNumOfObjects(){
          return numOfObjects;
     }
}

public class Client{
     public static void main(String[] args){
          new Base();
          new Base("");
          new Base(0);
          System.out.println(Base.getNumOfObjects());
     }
}

count = 3


建议38:使用静态内部类提高封装性
Java中嵌套类(Nested Class)分为两种:静态内部类(Static Nested Class) 和内部类(Inner Class)。
静态内部类2个优点:加强了类的封装性和提高了代码的可读性。形似内部,神似外部。编译后类文件名也包含外部类,外部类$内部类。他们之间是强关联关系。
静态内部类和普通类的区别:
(1)静态内部类不持有外部类的引用。普通内部类,可以直接访问外部类的属性和方法。静态内部类,只可以访问外部类的静态方法和静态属性,其他不行。
(2)静态内部类不依赖外部类
普通内部类和外部类之间是依赖关系,内部类不能脱离外部类实例,同生共死不能独立存在。静态外部类可以独立存在,即是外部类消亡,其还可以存在。
(3)普通内部内不能声明static变量和方法
普通内部内不能声明static变量和方法,常量(final static)还是可以的,静态内部类没有任何限制。
public class Person{
     private String name;
     private Home home;
    
     Person(String name){
          this.name = name;
     }
    
     public static class Home{
          private String address;
          private String tel;
         
          Home(String address, String tel) {
               this.address =addresss;
               this.tel = tel;
          }
     }
}

建议39:使用匿名类的构造函数
List l1 = new ArrayList();
List l2 = new ArrayList(){};
List l3 = new ArrayList(){{}};
List l4 = new ArrayList(){{}{}{}{}{}{}};//也可以是多个
l2是匿名类的声明和赋值,定义了一个继承于ArrayList的匿名类。代码类似于:
class Sub extends ArrayList{
}
List l2 = new Sub();

l3是匿名类的声明和赋值。代码类似于:
class Sub extends ArrayList{
     {
          //初始化块,充当构造函数功能
     }
}
List l3 = new Sub();

建议40:匿名类的构造函数很特殊
一般类(显式名字的类)所有构造函数默认都是调用父类的无参构造。
匿名类构造函数处理机制,匿名类没有名字,只能有构造代码块代替,在其初始化的时候直接调用父类同参数构造,然后再调用自己的构造函数。
enum Ops{ADD, SUB};

class Calculator{
     private int i,j,result;
    
     public Calculator(){};
    
     public Calculator(int i, intj){
          this.i= i;
          this.j = j;
     }
    
     //设置运算符号
     protected  void setOperator(Ops ops){
          result = ops.equals(Ops.ASS) ? i + j : i -  j;
     }
    
     //获得运算结果
     public int getResult(){
          return this.result;
     }
}

public static void main(String[] args){
     Calculator cl = new Calculator(1,2){
          setOperator(Ops.ADD);
     }
     System.out.println(cl.getResult());
}

//上面匿名函数相当于
class Add extends Calculator{
     {
          setOperator(Ops.ADD);
     }
     //覆写父类的构造方法
     public Add(int i, int j){
          super(i, j);
     }
}

建议41:让多重继承成为现实
一个类可以多个接口,但不能继承多个类。有时候确实需要继承多个类,Java提供内部类曲折解决此问题。
interface Father{
     public int strong();
}
interface Mother{
     public int kind();
}

class FatherImpl implements Father{
     public int strong(){
          return 8;
     }
}

class MotherImpl implements Mother{
     public int kind(){
          return 8;
     }
}

class Son extends FatherImpl implements Mother{
     @Override
     public int strong(){
          return super.strong() + 1;
     }
    
     @Override
     public int kind(){
          return new MotherSpecial.kind();
     }
     //内部类
     private class MotherSpecial extends MotherImpl{
          public int kind(){
               return super.kind() - 1;
          }
     }
}


//女儿继承了母亲的kind,同时覆写了父类的strong,这里建立了一个匿名内部类来覆写父类的方法。
class Daughter extends MotherImpl implements Father{
     @Override
     public int strong(){
          return new FatherImpl(){
               @Override
               public int strong(){
                    return super.strong() - 2;
               }
          }.strong();
     }
}

建议42: 让工具类不可实例化
Java工具类的方法和属性都是静态的。由于不希望被初始化,于是设置构造函数为private权限,表示除了类本身外,谁都不能产生一个实例。工具类不要继承。
public class UtilsClass{
     private UtilsClass(){
          throw new Error("不要实例化我");
     }
}

建议43:避免对象的浅拷贝
一个类实现了Cloneable接口表示具备了被拷贝能力,覆写了clone()方法完全具备拷贝能力。
浅拷贝,对象属性拷贝不彻底的问题。

public class Person implements Cloneable{

        //姓名

        private String name ;

        //父亲

        private Person father ;

       

       Person(String name){

               this.name = name ;

       }

       

       Person(String name, Person parent){

               this.name = name ;

               this.father = parent ;

       }


        public String getName() {

               return name ;

       }


        public void setName(String name) {

               this.name = name ;

       }


       

        public Person getFather() {

               return father ;

       }


        public void setFather(Person father) {

               this.father = father ;

       }


        @Override

        public Person clone(){

              Person p = null;

               try {

                      p = (Person) super.clone();

                      p.setFather( new Person(p .getFather().getName()));

              } catch (CloneNotSupportedException e ) {

                      // TODO Auto-generated catch block

                      e.printStackTrace();

              }

               return p ;

              

       }

       

}


public class Client {


        public static void main(String[] args) {

               //定义父亲

              Person f = new Person("父亲" );

               //定义大儿子

              Person s1 = new Person("大儿子" , f );

               //小儿子

              Person s2 = s1 .clone();

              s2.setName("小儿子");

              

               //认干爹

               s1.getFather().setName( "干爹");

              System. out.println(s1 .getName() + " 父亲是: " + s1.getFather().getName());

              System. out.println(s2 .getName() + " 父亲是: " + s2.getFather().getName());

              

       }

}


建议44:推荐使用序列化实现对象拷贝
序列化实现对象拷贝比每一个类都写clone更好的办法
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class CloneUtils {
     //拷贝一个对象
     @SuppressWarnings("unchecked")
     public static <T extends Serializable> T clone(T obj) {
          //拷贝产生的对象
            T cloneObj = null;
          try {
                    //读取对象字节顺序
               ByteArrayOutputStream baos = new ByteArrayOutputStream();
               ObjectOutputStream oos = new ObjectOutputStream(baos);
               oos.writeObject(obj);
               oos.close();
               //分配内存空间,写入原始对象,生成新对象
               ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
               ObjectInputStream ois = new ObjectInputStream(bais);
                  //返回新对象并作类型转换
               cloneObj = (T)ois.readObject();
               ois.close();
          }catch(ClassNotFoundException | IOException e) {
               e.printStackTrace();
          }
          return cloneObj;
     }
}

上面那个Person类implements Serializable加上SerialVersionUID就可以实现深拷贝

注意两点:
(1)对象内部的属性都是可以序列化的,如内部属性不可序列化,会抛出异常。
(2)注意方法和属性的特殊修饰符
final,static的问题,transient变量的问题。
更简便的方法是,用Apache commons工具包中的SerializationUtils类

建议45:覆写equals方法时候不要识别不出自己
在写一个JavaBean时,经常会覆写equals方法。根据业务规则判断两个对象是否相等。
equals方法的自反性原则:对于任何非空引用x,   x.equals(x)应该返回true。
不要在p.getName()后面在加上.trim()。

例子见46

建议46:equals应该考虑null值情景
equals对称性原则:对于任何引用x和y的情景,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。

//Object Equals
public class PersonEqual {
     String name;
   
     PersonEqual(String _name){
          this.name = _name;
     }
   
   
     public String getName() {
          return name;
     }


     public void setName(String name) {
          this.name = name;
     }


     public boolean equals(Object obj){
          if(obj != null && obj.getClass() == this.getClass()){
               PersonEqual p = (PersonEqual)obj;
               if(p.getName() == null || this.name == null)
                    return false;
               else
                    return this.name.equalsIgnoreCase(p.getName());
          }
          return false;
     }
}


import java.util.ArrayList;
import java.util.List;


public class Client {
     public static void main(String[] args) {
          PersonEqual pe1 = new PersonEqual("yao");
          PersonEqual pe2 = new PersonEqual("yao ");
        
          List<PersonEqual> list = new ArrayList<>();
          list.add(pe1);
          list.add(pe2);
        
          System.out.println(list.contains(pe1));
          System.out.println(list.contains(pe2));
     }
}

//建议47:在equals中使用getClass进行类型判断
equals传递性原则:对于实例对象x,y,z。如果,x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。
//不是同一个类的2个值不能比较相等
//覆写equals时候,用getClass()代替instanceof判断
public class Emp extends PersonEqual {

     private int id;
     Emp(String _name, int _id) {
          super(_name);
          this.id=_id;
          // TODO Auto-generated constructor stub
     }
     public int getId() {
          return id;
     }
     public void setId(int id) {
          this.id = id;
     }

     public boolean equals(Object obj){
          if(obj != null && obj.getClass() == this.getClass()) {
               Emp emp = (Emp)obj;
               return super.equals(obj) && emp.getId() == this.id;
          }
          return false;
     }
}

import java.util.ArrayList;
import java.util.List;


public class Client {
     public static void main(String[] args) {
          Emp e1 = new Emp("yao", 100);
          Emp e2 = new Emp("yao", 1001);
          PersonEqual pe = new PersonEqual("yao");
          System.out.println(pe.equals(e1));
          System.out.println(pe.equals(e2));

     }
}


建议48:覆写equals方法必须覆写hashCode方法
List中Person类的equals覆写了,不再判断两个类的地址是否相等,而是根据equals中的name判断两个对象是否相等。
HashMap的底层处理机制是数组的方式保存MAP条目(Map Entry),关键是数组下标的处理机制。根据传入元素hashCode方法的返回值决定其数组的下标。
如果,该数组位置上已经有了Map条目,并且与传入的键值相等则不处理。
如果,不想等则覆盖;如果数组位置没有条目,则插入,并加入到Map条目的链表中。
检查键是否存在也是根据哈希码定位的,然后遍历查找键值。
hashCode方法的返回值是什么?是一个对象的哈希码。由Object类的本地方法生成,确保每一个对象有一个哈希码。
重写Person类的hashCode方法。
//Person HashCode
public class PersonHashCode {
     String name;
   
     PersonEqual(String _name){
          this.name = _name;
     }
   
   
     public String getName() {
          return name;
     }


     public void setName(String name) {
          this.name = name;
     }


     public boolean equals(Object obj){
          if(obj != null && obj.getClass() == this.getClass()){
               PersonEqual p = (PersonEqual)obj;
               if(p.getName() == null || this.name == null)
                    return false;
               else
                    return this.name.equalsIgnoreCase(p.getName());
          }
          return false;
     }
    
     @Override
     public int hashCode(){
          return new HashCodeBuilder().append(this.getName).toHashCode();
     }
}

public static void main(String[] args){
     //Person类作为Map的Key
     Map<Person, Object> map = new HashMap<>{
          {
               put(new Person("张三"),new Object());
          }
     };
    
     //Person类的实例作为List的元素
     List<Person> list = new ArrayList<>{
          {
               add(new Person("张三"));
          }
         
     };
    
     boolean b1 = list.contains(new Persion("张三"));
     boolean b2 = map.containsKey(new Person("张三"));
}

建议49:推荐覆写toString方法
Java本身提供的toString方法不友好。
ToStringBuilder    //代替Javabean中的toString()方法

建议50:使用package-info类为包服务
特殊的类:package-info
专门为包服务。描述和记录包的信息。
不能随便被创建,服务的对象特殊,此类不能有实现代码。

作用:
(1)声明友好类和包内访问常量
//这里是类包,声明一个包使用的公共类
class PkgClass{
     public void test(){}
}
//包常量,只允许包内访问
class PkgConst{
     static final String  PACKAGE_CONST = "ABC";
}
(2)为包上提供注解便利
写一个注解标注,放到package-info文件中。
(3)提供包的整体注释说明

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


你可能感兴趣的:([笔记]改善Java程序的151个建议---第三章 类,对象,方法)