《码出高效Java开发手册》- 笔记

第2章 面向对象

2.4 方法

2.4.2 参数

  1. 代码风格:每个逗号后边必须有一个空格,不管是形参,还是实参。
  2. 参数是局部变量,若同名,则遵循作用域就近原则
  3. ALOAD 0: 表示静态变量的引用赋值给虚拟机栈的栈帧中的局部变量表;
  4. 无论是对于基本数据类型,还是引用变量,Java中的参数传递都是值复制的传递过程;
  5. 尽量不要使用可变参数,若使用不建议使用Object作为可变参[类型转换场景不好判断]
package com.goahead;

public class ParamPassing {

    public static void main(String[] args) {
        // 3
        listUsers(1, 2, 3);
        // 1
        listUsers(new int[]{1, 2, 3});
        // 2
        listUsers(3, new String[]{"1", "2"});
        // 3
        listUsers(new Integer[]{1, 2, 3});
        // 2
        listUsers(3, new Integer[]{1, 2});
    }

    public static void listUsers(Object... args) {
        System.out.println(args.length);
    }
}
  • 对待入参:
    1. 保持理性的不信任
    2. 方法第一步不是功能实现,而是参数预处理
      • 入参保护: 实质上是对服务提供方的保护,常见于批量接口。
      • 参数校验:
        需要做校验的场景

        1. 调用频度低的方法
        2. 执行时间开销很大的方法。
        3. 需要极高稳定性和可用的方法。
        4. 对外提供的开放性接口
        5. 敏感权限入口

        不需要做校验的场景

        1. 极有可能被循环调用的方法
        2. 底层调用频繁的方法。一般DAO层和Service都在同一个应用中,部署在同一台服务器中,所以可以省略DAO的参数校验
        3. 声明成private只会被自己代码调用的方法。如果确定调用方的代码传入参数已经做过检查或者肯定不会有问题,可以不进行参数校验。

2.4.3构造方法

定义: 指的是方法名和类名相同的特殊方法

  1. 单一职责,也适用于构造方法,构造方法不能引入业务逻辑;推荐将初始化业务逻辑放在某个方法中,比如init()中,当对象初始化后显示调用;
  2. 静态代码只运行一次,在第二次对象实例化时,不会运行;

2.4.4类内方法

  1. 实例方法:
    当.class字节码文件加载后,实例方法并不会被分配方法入口地址,只有在对象创建之后才会被分配地址。
  2. 静态方法:
    又称类方法。当类加载后,即分配了相应的内存空间
    1. 静态方法中不能使用实例成员变量和实例方法
    2. 静态方法不能使用super和this关键字,这两个关键字指代的都是需要被创建出来的对象。
  3. 静态代码块:
    1. 非静态代码块又称局部代码块,是极不推荐的处理方式;
    2. 静态代码块在类加载时候被调用,并且只执行一次;
    3. 实际应用中例如容器初始化时,可以使用静态代码块实现类加载判断、属性初始化、环境配置等。
package com.goahead;

public class StaticCode {

    // prior必须定义在last前边,否则编译出错: illegal forward reference
    static String prior = "done";
    // 一次调用f()的结果,三目运算符为true,执行g(), 最后赋值成功
    static String last = f() ? g(): prior;
            
    public static boolean f() {
        return true;
    }
    
    public static String g() {
        return "hello, world!";
    }

    static {
        // 静态代码块可以访问静态变量和静态方法
        System.out.println(last);
        g();
    }
}

2.4.5 getter与setter

使用这两个有两点好处:

  1. 满足面向对象语言封装特性。 尽可能将类中的属性定义为private,针对属性值的访问与修改通过相应的getter和setter方法,而不是直接读取和修改。
  2. 有利于统一控制。 虽然直接对属性读取和修改和getter与setter理论上效果一样,但是前者难应对业务的变化。例如业务需要对某个属性增加统一的权限控制,通过setter则很容易完成。

类定义中,方法定义顺序依次:公有方法或保护方法 > 私有方法 > getter/setter方法。
getter/setter典型应用:POJO
书中pojo对象只包含:getter,setter,toString方法的简单类,常见的pojo类包括DO(Domain Object) BO(Business Object) DTO(Data Transfer Object) VO(View Object) AO(Application Object)。
pojo示例:

package com.goahead;

public class TicketDO {
    private Long id;
    
    private String destination;

    public Long getId() {
        return id;
    }

    public String getDestination() {
        return destination;
    }

    // 尽量不要包含业务逻辑
    public void setId(Long id) {
        this.id = id;
    }

    public void setDestination(String destination) {
        this.destination = destination;
    }
}

如下罗列容易出错的getter和setter的方式:

  1. getter/setter中添加业务逻辑。 程序员的惯性思维会忽略getter/setter方法的嫌疑,这会增加排查问题的难度。
  2. 同时定义isXxx()和getXxx()。 在类定义中,两者同时存在会在iBATIS(也就是MyBatis)、JSON序列化等场景下引起冲突。如果iBATIS通过反射机制解析加载属性的getter方法时,首先会获取对象的所有方法,然后筛选出以get和is开头的方法,并存储到类型为HashMap的getMethods变量中。其中key为属性名称,value为getter方法。因此isXxx()和getXxx()只能保留一个,哪个在后存哪个,具有随机性,当两者定义不一致时会存在问题。
  3. 相同的属性名会带来歧义。 应该尽量避免子父类的成员变量之间,不同代码块之间的局部变量之间采用完全相同的命名。
package com.goahead;

public class ConfusingName {
    
    public int alibaba;
    // 反例:非setter/getter方法的参数名称,不允许和本类成员变量同名
    public void get(String alibaba) {
        if (true) {
            final int taobao = 15;
        }
        
        for (int i = 0; i < 10; i++) {
            // 反例:在同一方法体中,不允许与其他代码块中的taobao命名相同
            final int taobao = 15;
            
        }
    }
}

class Son extends ConfusingName {
    // 反例:不允许与父类的成员变量名称相同
    public int alibaba;
}

2.4.6 同步与异步

你可能感兴趣的:(《码出高效Java开发手册》- 笔记)