String类可以被继承吗?我们来聊聊final关键字!
String在java基础知识中绝对是个重点知识,关于String的一些问题也是非常的多,而且牵涉到内存等高级知识,在面试中也是经常被考察的一个点,那么我们今天就来一起讨论下这个String类是否可以被继承呢?以及为什么呢?其中我们会谈到一个非常重要的知识点,那就是final关键字了,下面开始吧!
庆哥: 小白,你对String类的掌握如何呢?
小白: String啊,了解的应该比较基础,关于字符串常量池应该是String中比较重要的一个知识点,理解字符串常量池就可以解决String相关的一些问题了,比如经常考察的创建几个对象的问题!
庆哥: 可以,你这块记得还可以,那么我问你一个关于String相关的问题,请听题,请问String类是否可以被继承呢?
小白: ???
庆哥: 还三个问号,怎么?是不是不知道啊?
小白: 实不相瞒,我还真不知道,听到这个问题,我发现我脑子一片空白,没有任何可以调取的信息,这说明我对这个问题是真的不理解啊,还请庆哥指教啊!
String类是否可以被继承?
庆哥: 那我们就一起来看看这个问题,首先我们要知道,如果你要判断一个类是否可以被继承的话,你要知道这个类是如何被定义的,所以我们先来看看这个String类是如何被定义的
以上就是这个String类的定义方式,我们可以明显的看到一个final修饰符!
小白: 哦哦,我知道了,那么这个String类是不可以被继承的。因为被final修饰的类是不可以被继承的。
庆哥: 可以啊,这么快就get到点啦,那你知道为什么吗?
小白: 当看到被final修饰,我脑海中立马就产生了不可以被继承的想法,但是具体的原因我却又说不出来,我发现我对这个final修饰符不是很理解!
庆哥: 是的,理解这个我们需要先来了解下final这个关键字,那你知道final可以修饰什么吗?
小白: Google ing...
哦哦,我知道了,这个final可以修饰类,方法和成员变量,感情一个类中的东西都可以被它修饰啊。
庆哥: 是的,这个final可以用来修饰
- 类
- 成员变量
- 方法
那么接下来我们就来说一下这三个被final修饰有什么作用,首先我们就来看这个修饰类
final修饰类
我们先来写一个被final修饰的类
通过这个例子我们可以看到,被final修饰的类并不可以被继承,所以到这里我们就能知道String类是不能被继承的。
小白: 哦哦,这下知道了,被final修饰的类是不能被继承的,感觉被final修饰像是被加上一把锁一样,被禁锢了一样。
庆哥: 是的,被final修饰的类就不能被其他类继承了,所以在设计类的时候如果真的是不想这个类被其他类继承或者考虑一些安全因素,否则尽量不要把类设计成final的。
final修饰成员变量
那么,你知道final修饰成员变量有什么作用吗?
小白: 这个我好想还真知道,因为这个在平时的编码过程中有用到过,应该是被final修饰的成员变量是不可以改变的,也就是固定下来,是一个常量值了。
庆哥: 对的,确实是这样,对于变量我们知道分为基础数据类型和引用数据类型,我们还是来看一个例子吧!
在这个例子当中我们发现被final修饰的基础数据类型和引用数据类型都是不能再次赋值的,这就说明被final修饰的变量是不可变的。
final修饰方法
接下来我们就来看看这个被final修饰的方法又是怎样的?
小白: 这个让我猜猜,被final修饰的方法是不是也不能被重载覆盖啊,或者不能被调用?
庆哥: 这个嘛....我们还是一起来看看吧,小白,你读过《java编程思想》这本书吗?
小白: 这本书可是经典啊,不过,实在是有点难啃啊。
庆哥: 哈哈。经典当然难啃啦,在这本书中有这样的描述
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。
这句话什么意思呢?也就是说,如果你不想方法在子类中被重写,你就把这个方法设置成final,我们还是来看一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Test1{ public void show(String s){ System.out.println("传进来的字符串是:"+s); } } class Test2 extends Test1{ @Override public void show(String s) { System.out.println(s+"是被传进来的字符串!"); } } |
在这个例子中Test2继承了Test1并且覆盖了Test1的show方法,但是现在如果我不想这个方法被覆盖的话该怎么做呢?
也就是当方法被final修饰之后就不可以被覆盖了。
小白: 哦,我知道了,方法被final修饰之后不可以被子类覆盖,那么这个被final修饰的方法是否还可以正常调用以及可以重载吗?
庆哥: 可不可以我们写一个例子就知道了,看下面的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Test { public static void main(String[] args) { new Test1().show(); } } class Test1{ public final void show(String s){ System.out.println("传进来的字符串是:"+s); } public void show(){ System.out.println("啥玩意也没有传进来!"); } } |
看输出结果
小白: 知道了,被final修饰的方法可以总结如下几点
- 在子类中不能被覆盖了
- 可以实现重载
- 依旧可以正常调用
庆哥: 可以的,总结的不错,关于final的基本用法我们讨论的差不多了,看你的总结觉得你也掌握了,下面我们就来说点高级的,再问你一个问题,你知道final修饰的成员变量和普通变量有什么区别吗?
比如这俩
final String b = "hello"; String c = "hello";
小白: 这个,不就是b不可被修改,而c可以被修改嘛
庆哥: 那你看下面的代码输出结果是什么
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Test { public static void main(String[] args) { String a = "hello1"; final String b = "hello"; String c = "hello"; String d = b + 1; String e = c + 1; System.out.println(a == d); System.out.println(a == e); } } |
小白: 这个?有点迷啊,不应该都是true吗?
庆哥: 实际的答案是
小白: 咦,怎么第二个输出结果是false呢?这个就有点不理解了,e的最终值不也是“hello1”吗,为什么会是false呢?
final变量和普通变量的区别
庆哥: 这就牵涉到final变量和普通变量的区别了,这里的b是被final修饰的成员变量,所以当用到b的时候可以直接替换成b的值,也就是说这里的
String d = b + 1;
其实直接就等价于
String d = "hello" + 1;
这样d的值也就是“hello1”,就像你说的e的值也是“hello1”啊,但是这里不同的是什么呢?
对于d这种在编译期就可以确定下来的值,也就是说在编译阶段已经确定d就是字符串“hello1”,那么这个值就会被放到字符串常量池中,但是对于e就不一样了,因为在编译阶段c是一个变量,也就是说在编译阶段并不能确定e的值,必须等到运行时才能确定e的值是“hello1”,但是这个时候这个值因为不是在编译阶段确定下来的就不会被放到字符串常量池中,a和e的引用是不同的,自然就是false!
小白: 看来在这里我有两个知识点不是很清楚
- 对于被final修饰的变量,在用的时候是可以直接替换的,因为其本身是固定不可变的
- 只有在编译阶段确定下来的字符串才是会被放到字符串常量池中的
庆哥: 我发现现在你的总结能力日益增强啊!很强势!
小白: 庆哥,我突然想到还有一个关键字叫做static,好像表示静态的,想到这个,突然感觉有点迷,这两个有啥区别啊?
final和static
庆哥: 还记得我们一般怎样定义静态常量吗?
是不是这样
static final String NAME = "Java从0到1";
这里要记住的就是
- static同来表示唯一,独此一份,也即是静态
- final用来表示不可变
这两者一结合不就是静态常量嘛,下面举一个随机数的例子,特别能说明这个问题,你看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Test { public static void main(String[] args) { Test1 one = new Test1(); Test1 two = new Test1(); System.out.println(one.a); System.out.println(one.b); System.out.println(two.a); System.out.println(two.b); } } class Test1{ final double a = Math.random(); static double b = Math.random(); } |
输出结果为
你会发现,b的值始终都是一样的!
小白: 哦哦,这下知道了,那static修饰的变量可以被改变吗?
庆哥: 这个我们再测试下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Test { public static void main(String[] args) { Test1 one = new Test1(); Test1 two = new Test1(); Test1.b = 0.2; System.out.println(one.a); System.out.println(one.b); System.out.println(two.a); System.out.println(two.b); } } class Test1{ final double a = Math.random(); static double b = Math.random(); } |
看输出结果
我想不用多说了吧!
小白: 今天真的是让我学到好多知识啊,真是受益匪浅,感谢庆哥!
庆哥: 那你知道String类为什么不可以继承了吗?
小白: 必须知道啊,面试也不怕被问到了!