前言
虽然现在已经有很多博客验证了final
关键字的继承问题,但还是秉着show me the code
的原则进行了尝试。
接口中的final关键字
接口可以说是Java
中最基本的类型了。灵活使用接口可以达到相当丰富的结果。
基本接口
最基本的接口就是单独使用一个文件编辑的接口,与任何类都不相关,只有接口被实现之后才开始相关。我们就定义一个最基本的接口MyInterface
:
public interface MyInterface {
public final String NAME = "sakebow";
public final double MONEY = 0.1;
}
P.S.:为了让代码更规范一点,
final
关键字修饰的变量最好大写
都是final
字段,表示这个字段不可被修改。然后我们创建一个实现(implements
)接口的类
public class App implements MyInterface {
// 继承final类型的name跟money
private final String NAME;
private final double MONEY;
private final int SEX;
// 两种构造函数
public App() {
this.NAME = "sakebow plus";
this.MONEY = 0.2;
this.SEX = 0;
}
public App(String name, double money, int sex) {
this.NAME = name;
this.MONEY = money;
this.SEX = sex;
}
// 查看初始化的结果
public String say() {
return "name: " + this.NAME + ", money: " + this.MONEY + ", sex: " + this.SEX;
}
public static void main(String[] args) throws Exception {
// 默认初始化
App a = new App();
// 传参初始化
App b = new App("sakebow ultra pro max plus", 0.3, 1);
// 查看结果
System.out.println(a.say());
System.out.println(b.say());
}
}
很明显,输出将会是
name: sakebow plus, money: 0.2, sex: 0
name: sakebow ultra pro max plus, money: 0.3, sex: 1
即使是final
关键字修饰的变量,name
属性在MyInterface
中是public
权限的成员变量,在实现接口的过程中被继承下来,并且实现接口的类在实例化之前需要重新赋值。
但通过这个例子,很显然普通接口中定义的final
字段并没有什么意义。
内部接口
内部接口其实就是写在类内的接口。如果需要实现接口并调用类的话,并不能单纯的new
一个类。因为内部类的构造函数并不能直接被外部类调用,而是只能在外部类方法中将内部类构造函数作为一个成员进行使用。对于上面的代码,我们需要修改这些地方:
public class App implements MyInterface {
// 继承final类型的name跟money
private final String NAME;
private final double MONEY;
private final int SEX;
// 两种构造函数
public App() {
this.NAME = "sakebow plus";
this.MONEY = 0.2;
this.SEX = 0;
}
public App(String name, double money, int sex) {
this.NAME = name;
this.MONEY = money;
this.SEX = sex;
}
// 查看初始化的结果
public String say() {
return "name: " + this.NAME + ", money: " + this.MONEY + ", sex: " + this.SEX;
}
// 内部接口
public interface InterInterface {
public final String NAME = "123";
}
// 实现内部接口的内部类
public class InterClass implements InterInterface {
private final String NAME = "321";
public String say() {
return this.NAME;
}
}
// 只能作为成员调用的构造函数
public InterClass getInstance() {
return new InterClass();
}
public static void main(String[] args) throws Exception {
// 默认初始化
App a = new App();
// 传参初始化
App b = new App("sakebow ultra pro max plus", 0.3, 1);
// 内部类
InterClass ic = a.getInstance();
// 查看结果
System.out.println(a.say());
System.out.println(b.say());
System.out.println(ic.say());
}
}
显然,ic
的say
方法也一如既往的正常输出:
name: sakebow plus, money: 0.2, sex: 0
name: sakebow ultra pro max plus, money: 0.3, sex: 1
321
输出并不是 123 123 123,依然是喜闻乐见的 321 321 321。
内部接口使用final
关键字并没有起到关键作用,也只不过是限定了关键的字段只能被初始化一次。当然,仅限于实现类也使用了final
关键字。
例如,还是上面这个代码,取消了NAME
字段的final
限制,并在say
方法中再一次赋值:
public class App implements MyInterface {
// 继承final类型的name跟money
// 但是name不再final
private String NAME;
private final double MONEY;
private final int SEX;
// 两种构造函数
public App() {
this.NAME = "sakebow plus";
this.MONEY = 0.2;
this.SEX = 0;
}
public App(String name, double money, int sex) {
this.NAME = name;
this.MONEY = money;
this.SEX = sex;
}
// 查看初始化的结果
public String say() {
// 再次赋值
this.NAME = "sakebow SE";
return "name: " + this.NAME + ", money: " + this.MONEY + ", sex: " + this.SEX;
}
public InterClass getInstance() {
return new InterClass();
}
public interface InterInterface {
public final String NAME = "123";
}
public class InterClass implements InterInterface {
private final String NAME = "321";
public String say() {
return this.NAME;
}
}
public static void main(String[] args) throws Exception {
// 默认初始化
App a = new App();
// 传参初始化
App b = new App("sakebow ultra pro max plus", 0.3, 1);
// 内部类
InterClass ic = a.getInstance();
// 查看结果
System.out.println(a.say());
System.out.println(b.say());
System.out.println(ic.say());
}
}
很显然输出变了:
name: sakebow SE, money: 0.2, sex: 0
name: sakebow SE, money: 0.3, sex: 1
321
结果就是,接口中定义final
关键字并没有起到任何作用。
接口中使用final有什么影响
上面我们分析道接口中使用final
并不会起到什么作用,那么我们将思维逆转过来,使用final
会出现什么影响呢?
JVM
自己会思考代码的优化。对于定义的同时就赋值的final
变量,JVM
会直接将其作为一个确切值常量进行使用。对于字符串而言,这样的使用方法能够在一定程度上提升效率。例如:
public final String NAME = "sakebow";
但,我这里使用的代码案例并不是这么做的。在本文的代码案例中大量使用了构造函数赋值的方法,使得final
变量并不会被JVM
优化。所以,在本文案例中,完全没用。
至于为什么不探讨final
关键字对方法的继承,那是因为接口本身就不能定义final
方法。
抽象类中的final关键字
知道了final
大概的机制,下面就大差不差了。依然是定义一个抽象类:
public abstract class AbstractClass {
public final String NAME = "sakebow";
public final double money = 0.1;
}
这个时候就不是实现接口了,而是从抽象类中继承(extends
)下来:
public class AbstractClassApp extends AbstractClass {
private final String NAME = "sakebow plus";
private final double MONEY = 0.2;
public String say() {
return "name: " + this.NAME + ", money: " + this.MONEY;
}
public static void main(String[] args) {
AbstractClassApp aca = new AbstractClassApp();
System.out.println(aca.say());
}
}
输出也同样是:
name: sakebow plus, money: 0.2
与接口一样的表现。
普通类中的final关键字
普通类由于能够使用final
修饰方法,所以我们来探索一下新的内容。先定义一个普通类:
public class MyClass {
public final String NAME = "sakebow";
public final double MONEY = 0.1;
public final void say() {}
}
然后我们试着定义一个继承MyClass
的类,看看会有什么效果:
public class ClassApp extends MyClass {
private final String NAME = "sakebow plus";
private final double MONEY = 0.2;
public String say() {
return "name: " + this.NAME + ", money: " + this.MONEY;
}
public static void main(String[] args) {
ClassApp ca = new ClassApp();
System.out.println(ca.say());
}
}
在这里,继承了NAME
与MONEY
,同样继承了方法say
。看起来没问题。
但是编译器报错:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The return type is incompatible with MyClass.say()
at classtest.ClassApp.say(ClassApp.java:6)
at classtest.ClassApp.main(ClassApp.java:11)
没错,重点就在这里:
The return type is incompatible with MyClass.say()
在普通类中,say
方法是void
,那么在子类中就不能够是String
类型。
如果类型相同呢?
Exception in thread "main" java.lang.VerifyError: class classtest.ClassApp overrides final method say.()Ljava/lang/String;
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
没错,也不行:
ClassApp overrides final method say
普通类的成员方法一旦被final
限定后,就无法再进行更改了。
更多一点思考
那么,就算我不重载,直接拿过来用,那又会产生什么作用呢?
在上面也说明了,final
在优化的时候,会考虑将体量小的代码块直接嵌入,在部分计算过程中就会节省一点时间消耗。
当然,也别将希望全部放在JVM
身上,就像使用的条件还是尽可能严苛一些,否则优化的方向并不是程序员能够控制的。