随着集成开发环境越来越强大,编程开发工作也变得越来越高效,很多的代码都不需要逐字输入,可以利用代码生成和自动补全来辅助开发。但是这样的便利也可能引起一些疏忽,本文就Java开发中默认生成的接口实现方法来谈谈以前遇到的问题。
平时做Java开发的时候,我们经常需要去实现他人定义的接口,这个时候通常写完implements xxxx
的代码后,使用开发工具来生成缺少的空方法,例如下面这样:
class ATask implements Runnable {
@Override
public void run() {
}
}
这么做可以让我们节省一些时间。上面的例子是最简单的情况,实际开发中情况要复杂的多。下面来看看复杂情况下可能出现的问题
问题一:方法很多并包含很多空实现
有时候用第三方库的时候,经常看到回调的接口中包含5个以上的方法,而这些方法并不是都需要用到,那么就会出现很多的空方法体,比如像Android播放器ExoPlayer
的EventListener
就包含了7个方法:
public interface EventListener {
void onTimelineChanged(Timeline var1, Object var2);
void onTracksChanged(TrackGroupArray var1, TrackSelectionArray var2);
void onLoadingChanged(boolean var1);
void onPlayerStateChanged(boolean var1, int var2);
void onPlayerError(ExoPlaybackException var1);
void onPositionDiscontinuity();
void onPlaybackParametersChanged(PlaybackParameters var1);
}
但这些方法我们并不是都要用到,所以可能在相关功能的实现中存在很多空方法体,像这样:
@Override
public void onTimelineChanged(Timeline timeline, Object o) {
}
@Override
public void onTracksChanged(TrackGroupArray trackGroupArray, TrackSelectionArray trackSelectionArray) {
}
@Override
public void onLoadingChanged(boolean b) {
}
@Override
public void onPlayerStateChanged(boolean b, int i) {
}
@Override
public void onPlayerError(ExoPlaybackException e) {
}
@Override
public void onPositionDiscontinuity() {
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
}
不仅占据大量篇幅, 而且还可能引起歧义:这些方法到底是可忽略的,还是忘记实现的,又或是当时可忽略,将来需要实现的?
针对可忽略的,我们可以像AnimationAdapterListener
那样,定义一个适配器接口,将那些忽略的方法放在这样的Adapter中:
public class AnimationAdapterListener implements Animation.AnimationListener {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
然后原本使用AnimationListener
的地方,就用AnimationAdapterListener
,然后将重写关注的方法即可,这样就不会出现大量的空方法
如果这些方法都不是可忽略的,还是建议在默认生成的方法体中通过注释、日志、甚至抛出异常等方式填充内容,起到一个补充说明的作用,以免将来看着空方法体一头雾水。当然如果IDE允许,尽量还是生成带有信息的方法体,例如下面这样:
class ATask implements Runnable {
@Override
public void run() {
//TODO not implement
}
}
问题二:生成的方法体的默认返回值问题
当接口声明的方法带有返回值的时候,IDE辅助生成的空方法体可能包含默认返回值,例如,接口返回值是boolean
时,默认返回false
:
class Obj implements XXXInter{
@Override
public boolean isXXX(){
return false;
}
}
或许在最初开发的时候,这种情况是可以忽略的,但是过段时间或者经历多人维护以后,就没法区分是“设计如此”还是“无意为之”的结果。对于熟悉的接口不会造成太大问题,如果是不太熟悉的第三方库的接口,那就可能因为疏忽而导致难以预料的情况。
针对有返回值的方法,还是建议默认实现采用抛出异常的方式:
class Obj implements XXXInter{
@Override
public boolean isXXX(){
throw new RuntimeException("not implement");
}
}
只有在明确功能的情况下,才返回具体的值。
问题三:实现不同接口的同名方法
最后一个问题非常少见,但是一旦出现,就可能导致比较诡异的bug。
实际开发中一个类可能会实现3个甚至5个接口,如果这些接口包含方法签名相同的方法,那么IDE辅助生成的方法实现中同签名方法只会有一个方法体,例如:
interface A {
void testA();
boolean isValid();
}
interface B {
void testB();
boolean isValid();
}
class ATask implements A,B {
@Override
public void testA() {
}
@Override
public void testB() {
}
@Override
public boolean isValid() {
return false;
}
}
这里接口A
和B
都包含isValid
方法声明,但是ATask
中只有一个isValid
实现,如果A
和B
中该方法的意义不同,那么情况又比较麻烦了。在开发阶段就发现,可以通过内部类或辅助类的方式来分离两个接口的实现;但如果已经是在老项目中出现这样的代码,那么就可能在不知道这个方法同时属于两个接口的情况下去改动进而出现“写bug”的行为。
对于这个问题,可以按照“单一职责”的思想,一个类尽量不要实现太多接口,保持其职责的单一。如果某些特殊情况真的需要一个方法同时实现两个接口的情况,还是建议补充注释来强调下。