改善Java程序的建议(每日5条)5

目录
  • 建议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是框架要求的拦截类,拿这种场景就很有可能出现了。

注意 不要再构造函数中声明初始化其它类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值