目录
- 建议21 接口中不要存在实现代码
- 建议22 静态变量一定要先声明后赋值
- 建议23 不要覆写静态方法
- 建议24 构造函数尽量简化
- 建议25 避免在构造函数中初始化其他
接口中不要存在实现代码
部分神看到这可能会疑惑:接口有实现代码?
确实 接口中可以声明常量,声明抽象方法,也可以继承父接口,但就是不能有具体实现,因为接口是一种契约,是一种框架性的协议,表明它的实现类都是同一种类型,或者是具备相似特征的一个集合体。
当然排除JDK1.8新特性在接口中定义 default方法以期待对项目整体添加方法又不影响实现类
public Class Test{
public static void main(String[] args){
B.s.doSomething();//调用接口的实现
}
}
//在接口中存在实现代码
interface B{
public static final S s = new S(){
public void doSomething(){
System.out.println("我在接口中实现了");
}
}
}
//被实现的接口
interface S{
public void doSomething();
}
在main方法执行时能打印出结果。在B接口中声明了一个静态常量S,其值是一个匿名内部类的实例对象,就是该匿名内部类实现了s接口,这就是一种在接口中存在的实现代码。这确实挺好,很强大,但是这是一种不好的编码习惯违背了接口的含义。如果把实现代码写道接口中,那接口就绑定了可能变化的因素,就会导致接口不在稳定和可靠。是随时都可能被抛弃、更改、被重构的。
建议22 静态变量一定要先声明后赋值
- 话不多说先展示代码
public class Test{
public static int i =1;
static {
i = 100;
}
public static void main(String[] args){
System.out.println(i);
}
}
这段代码执行结果是100毋庸置疑
我们再次稍稍修改一下
public class Test{
static {
i = 100;
}
public static int i =1;
public static void main(String[] args){
System.out.println(i);
}
}
变量i的声明和复制调换了位置。 还是可以编译,没有任何问题,结果是1而不是100;
因为 静态变量是类加载时被分配到数据区的,它在内存中只有一个拷贝,不会被分配多次,其后的所有赋值操作都是值改变,地址保持不变。我们指导JVM初始化变量是先声明空间,然后再赋值的。
也就是:
int i = 100;
是先
int i;//分配地址空间
i = 100;//赋值
静态变量在类初始化时首先被加载的,JVM会取查找类中所有的静态声明,然后分配空间,这时候只是完成地址空间分配,还没有赋值,之后JVM会根据类中的静态赋值的先后顺序来执行。也就是先声明了int类型的地址空间,并把地址传递给了i,然后按照类中的先后顺序执行赋值动作,首先执行静态块中i=100,接着执行i=1,所以最后的结果就是i=1了
建议23 不要覆写静态方法
Java中可以通过覆写来增强或者减弱父类的方法和行为,但是覆写是针对非静态方法的,不能针对静态方法
public class Test{
public static void main(String[] args){
Base base = new Sub();
base.doAnything();
base.doSomething();
}
}
class Base{
public static void doSomething(){
System.out.println("父类静态");
}
public void doAnything(){
System.out.println("父类非静态");
}
}
class Sub extends Base{
public static void doSomething(){
System.out.println("子类静态");
}
public void doAnything(){
System.out.println("子类非静态");
}
}
打印的结果是
子类非静态
父类静态
我们可能会有点困惑,同样是调用子类的方法,一个执行了子类方法,一个执行了父类方法,差别仅仅是static修饰,却结果不同
其实一个实例对象有两个类型:
- 表面类型:声明时的类型
- 实际类型:对象产生式的类型
对于以上例子变量base的表面类型是Base实际类型是Sub,对于非静态方法骂它是根据对象的实际类型来执行的,也就是执行了Sub类中的doAnything方法。对于静态方法来说比较特殊,他是不依赖实例对象的,是通过类名访问的。其次可以通过对象访问,如果是对象调用,则JVM会通过对象的表面类型查找执行。
建议24 构造函数尽量简化
在new关键字生成对象时候会调用构造函数,构造函数的简繁会直接影响实例对象的创建是否繁琐。通常情况下构造函数应该京可能简单,尽量不抛出异常,不做复杂算法。
建议25 避免在构造函数中初始化其它
构造函数是一个类初始化必须执行的代码,决定类的初始化效率,如果其构造函数比较复杂,而且还关联了其它类就会产生意想不到的问题。
public class Test{
public static void main(String[] args){
Son s = new Son();
s.doSomething();
}
}
//父类
class Father{
Father(){
new Other();
}
}
//子类
class Son extends Father{
public void doSomething(){
Sysem.out.println("hi");
}
}
class Other{
public Other(){
new Son();
}
}
这段代码的的结果就十分神奇了,栈内存溢出,这是因为声明s变量时调用了Son的无参构造函数,JVM又默认调用父类的构造函数,又初始化了Other类一个死循环就形成了,直到内存被消耗完毕。当然这种一眼能看出来的场景我们细心一点,基本不会出现。
但是如果Father是框架提供Son是我们自己编写的扩展代码,Other是框架要求的拦截类,拿这种场景就很有可能出现了。
注意 不要再构造函数中声明初始化其它类。