类的设计及特性
类的设计
隐藏数据
为了类的封装性,在设计类时,应该避免直接对外开发数据域。为了保证开发者不依赖于我们的底层细节,应该隐藏数据域(因为不能保证数据形式不会变化),并提供稳定的接口。
必要时,会提供访问器和更改器,对数据进行访问保护。
public long getAge() {
return this.age;
}
public void setAge(int age) {
// 校验 age 是否和发
if(/* condition */){
this.age = age;
}
}
bool:
public boolean isMale() {
return te;
}
public void setMale(boolean male) {
this.amle = male;
}
引用类型:
返回一个引用就相当于直接暴露了数据,应该避免这么做,我们可以返回一个副本。
public String getName() {
return this.name.clone();
}
不过,对于String
则没有这个顾虑,因为String
是不可变的。
工厂方法
当某个类足够复杂时,用工厂方法来构造实例可能是更好的解决方法。
例如,该类根据功能不同拥有多个子类,此时就可以用工厂方法,并以多态的形式返回新实例。
单元测试
任何类都可以拥有一个main
方法,由于任何一个main
方法都可以作为 Java 程序的入口,所以我们可以方便地进行单元测试。
例如我们要测试类 A,那么就应该在类 A 中实现一个main
方法,然后java A
运行测试。
可变参方法
像 C/C++ 中的printf
函数那样,输入任意个参数。
在 Java 中,只需要使用下面这种语法:
void method(Object...args) {
for(Object obj : args) {
// ... obj
}
}
从实现者的视角来看,Object...
与一个Object[]
的行为没有任何区别。
而从调用者的视角来看,则是可以传入任意个参数。
包装类
包装类是基本类型的包装,且其的实例是不可变的(避免引用传参)。
从 jdk9 起,包装类的构造方法被注解为废弃的,并推荐使用包装类的工厂方法。
It is rarely appropriate to use this constructor. The static factory is generally a better choice, as it is likely to yield significantly better space and time performance.
工厂方法:(以Integer
为例)
public static Integer valueOf(int i)
public static Integer valueOf(String s)
public static Integer valueOf(String s, int radix)
该工厂方法也是自动装箱时编译器的默认行为,例如:
list.add(3);
经过编译后相当于:
list.add(Integer.valueOf(3));
自动拆箱会隐式调用:
int intValue()
自动拆箱常发生在算数运算或赋值中:
int n = list.get(i);
相当于:
int n = list.get(i).intValue();
同时包装类也作为一个工具类,对应基础类型相关的工具被封装为静态方法。
static String toString(int i)
// 整数转换为字符串
static String toString(int i ,int radix)
// 整数转换为 radix 进制的字符串
static int parselnt(String s)
// 字符串转换为整数
static int parseInt(String s,int radix)
// 字符串转换为 radix 进制的整数
枚举类
Java 中的枚举类与 C/C++ 中的枚举类型不太一样,在 Java 中,枚举类是一个类。
通过enum
代替class
来定义一个枚举类:
enum State {
OPEN, CLOSE
}
注意,枚举类不允许在局部作用域下(方法中)定义。
使用枚举类的一般情况:
State flag = State.OPEN;
if(flag == State.CLOSE){
// ...
}
这是如何实现的呢?
枚举类会隐式继承Enum
类,类初始化时会调用一个私有构造器,构造出所有的public static final
实例。
这些实例就是我们定义的枚举常量,这些常量都是对象,所以拥有一些方法:
String name()
String toString()
// 返回字符串表示
int ordinal()
// 返回该对象的声明序号
int compareTo(E o)
// 对比声明顺序
boolean equals(Object other)
// 可以使用 ==
Class<?> getDeclaringClass()
// 返回枚举类的 Class 实例,或其外部类的 Class 实例
除此之外,枚举类作为一个类,允许我们重写方法、定义构造和成员函数。
例如,为每一个枚举常量留一个描述,并允许调用者获取到这个儿描述:
enum State {
OPEN("This instance means that the current state is open"),
CLOSE("This instance means that the current state is off");
String desc;
private State(String aDesc){
this.desc = aDesc;
}
public String getDesc() {
return desc;
}
};
在来一个例子,我们还可以实现枚举类的实例的多态。
正常情况下,枚举类:
enum State {
OPEN, CLOSE
};
他的原理类似:
class State extends Enum{
public static final State OPEN;
public static final State CLOSE;
private State(String name, int i) {
super(name, i);
}
// 通过类的静态初始化块,实例化出所有枚举类型实例
static {
OPEN = new State("OPEN", 0);
CLOSE = new State("CLOSE", 1);
}
}
对于这一部分:
public static final State OPEN;
public static final State CLOSE;
它们也许可以是State
的子类,我们可以在 State 中定义一个抽象方法,然后让这些枚举成员继承State
并实现抽象方法:
enum State {
OPEN{
@Override
String getDesc() {
return "This instance means that the current state is open";
}
},
CLOSE {
@Override
String getDesc() {
return "This instance means that the current state is off";
}
};
abstract String getDesc();
};
这一段代码的原理相当于:
class State extends Enum{
public static final State OPEN;
public static final State CLOSE;
private State(String name, int i) {
super(name, i);
}
abstract String getDesc();
// 通过类的静态初始化块,实例化出所有枚举类型实例
static {
OPEN = new State("OPEN", 0){
@Override
String getDesc() {
return "This instance means that the current state is open";
}
};
CLOSE = new State("CLOSE", 1){
@Override
String getDesc() {
return "This instance means that the current state is off";
}
};
}
}