第2章 面向对象
2.4 方法
2.4.2 参数
- 代码风格:每个逗号后边必须有一个空格,不管是形参,还是实参。
- 参数是局部变量,若同名,则遵循作用域就近原则
- ALOAD 0: 表示静态变量的引用赋值给虚拟机栈的栈帧中的局部变量表;
- 无论是对于基本数据类型,还是引用变量,Java中的参数传递都是值复制的传递过程;
- 尽量不要使用可变参数,若使用不建议使用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);
}
}
- 对待入参:
- 保持理性的不信任
- 方法第一步不是功能实现,而是参数预处理
- 入参保护: 实质上是对服务提供方的保护,常见于批量接口。
-
参数校验:
需要做校验的场景- 调用频度低的方法
- 执行时间开销很大的方法。
- 需要极高稳定性和可用的方法。
- 对外提供的开放性接口
- 敏感权限入口
不需要做校验的场景
- 极有可能被循环调用的方法
- 底层调用频繁的方法。一般DAO层和Service都在同一个应用中,部署在同一台服务器中,所以可以省略DAO的参数校验
- 声明成private只会被自己代码调用的方法。如果确定调用方的代码传入参数已经做过检查或者肯定不会有问题,可以不进行参数校验。
2.4.3构造方法
定义: 指的是方法名和类名相同的特殊方法
- 单一职责,也适用于构造方法,构造方法不能引入业务逻辑;推荐将初始化业务逻辑放在某个方法中,比如init()中,当对象初始化后显示调用;
- 静态代码只运行一次,在第二次对象实例化时,不会运行;
2.4.4类内方法
- 实例方法:
当.class字节码文件加载后,实例方法并不会被分配方法入口地址,只有在对象创建之后才会被分配地址。 - 静态方法:
又称类方法。当类加载后,即分配了相应的内存空间- 静态方法中不能使用实例成员变量和实例方法
- 静态方法不能使用super和this关键字,这两个关键字指代的都是需要被创建出来的对象。
- 静态代码块:
- 非静态代码块又称局部代码块,是极不推荐的处理方式;
- 静态代码块在类加载时候被调用,并且只执行一次;
- 实际应用中例如容器初始化时,可以使用静态代码块实现类加载判断、属性初始化、环境配置等。
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
使用这两个有两点好处:
- 满足面向对象语言封装特性。 尽可能将类中的属性定义为private,针对属性值的访问与修改通过相应的getter和setter方法,而不是直接读取和修改。
- 有利于统一控制。 虽然直接对属性读取和修改和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的方式:
- getter/setter中添加业务逻辑。 程序员的惯性思维会忽略getter/setter方法的嫌疑,这会增加排查问题的难度。
- 同时定义isXxx()和getXxx()。 在类定义中,两者同时存在会在iBATIS(也就是MyBatis)、JSON序列化等场景下引起冲突。如果iBATIS通过反射机制解析加载属性的getter方法时,首先会获取对象的所有方法,然后筛选出以get和is开头的方法,并存储到类型为HashMap的getMethods变量中。其中key为属性名称,value为getter方法。因此isXxx()和getXxx()只能保留一个,哪个在后存哪个,具有随机性,当两者定义不一致时会存在问题。
- 相同的属性名会带来歧义。 应该尽量避免子父类的成员变量之间,不同代码块之间的局部变量之间采用完全相同的命名。
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;
}