> 文章列表 > final关键字的继承问题

final关键字的继承问题

final关键字的继承问题

final关键字的继承问题

  • 前言
  • 接口中的final关键字
    • 基本接口
    • 内部接口
    • 接口中使用final有什么影响
  • 抽象类中的final关键字
  • 普通类中的final关键字
    • 更多一点思考

前言

虽然现在已经有很多博客验证了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跟moneyprivate 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跟moneyprivate 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());}
}

显然,icsay方法也一如既往的正常输出:

name: sakebow plus, money: 0.2, sex: 0
name: sakebow ultra pro max plus, money: 0.3, sex: 1
321

输出并不是123123123,依然是喜闻乐见的321321321

内部接口使用final关键字并没有起到关键作用,也只不过是限定了关键的字段只能被初始化一次。当然,仅限于实现类也使用了final关键字。

例如,还是上面这个代码,取消了NAME字段的final限制,并在say方法中再一次赋值:

public class App implements MyInterface {// 继承final类型的name跟money// 但是name不再finalprivate 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());}
}

在这里,继承了NAMEMONEY,同样继承了方法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身上,就像使用的条件还是尽可能严苛一些,否则优化的方向并不是程序员能够控制的。