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跟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());
    }
}

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

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());
  }
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ordinary_brony

代码滞销,救救码农

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值