重学Java 面向对象 之 final

final 的套路:java

当初在背面试题的时候final出现的几率能够说是至关高了,在各类面试题库中都少不了它的身影,一提及final ,那打开方式差很少就是这样的:面试

1.  对于基本类型变量:final 修饰的变量不可修改json

2.  对于引用型变量: final 修饰的对象,引用自己不可修改,可是被引用的内容能够修改。数组

3. 对于 方法 : 方法不能重写app

4. 对于类:类不能被继承测试

 

由于当时看了太多遍同时内容简单又好背,如今不看书也能写出来了,至于具体的代码示例这里就不放了,网上也比较多。ui

可是本身历来没有想过为何。this

为何不让类被继承?为何不让人家重写方法?spa

 

反向思考设计

关于这个问题我以为反向来思考是最有效的,那就是若是我继承了又怎么样,我重写了又会如何。

最后发现若是继承或重写了咱们可能就有很大的麻烦,仍是不要继承,也不要重写了吧,而后咱们就在类和方法上加上了final。

 

而关于final与不可变性,能够从变量,方法,类三个角度来思考。

 

变量与final

 你们初学时都会碰到的Math类,而Math类中有着许多的static变量,是能够共享的,如耳熟能详的PI,同时它也被设计成了final 的。

 

 

 假设咱们如今正在开发一个计算各类图形参数的程序,你负责开发Circle(sphere)的部分,另一我的(就叫背锅侠吧,名字揭示了命运。。。)负责开发sphere(球体),很明显,大家的各类计算过程当中都会涉及到PI这个变量。

假如PI没有加final

有一天你以为这个PI怎么看起来这么长啊,真是不爽,你的运算结果只须要保留两位小数就好了,但我却须要拿一个这么多小数位的变量,因而你决定在本身的类初始化的时候修改这个变量的值,让它只有两位小数。而你不知道的是,负责开发球体的那个伙计他的计算结果要保留到小数点后4位。

悲剧到这里已经很明显了:大家本身测试的时候都没什么问题,而用户在使用程序计算完一个圆的各类参数后,再计算其余涉及到PI变量的程序都没办法获得想要的结果,好比计算球体的体积。

而用户是不会知道这一切的,他们只会反馈给开发团队一个错误信息:我使用计算球体的程序很不稳定,常常得不到我想要的结果。

因而技术经理把背锅侠喊了过来大骂了一顿,并让他马上修复好bug。

背锅侠同志很郁闷,本身测试的时候什么问题都没有,为何一上线就有bug

但是他怎么调试结果都出不来,一切都很正常,背锅侠很生气也很烦恼,由于他实在不知道问题出在哪儿。

已经晚上9点了,背锅侠尚未找到bug所在,背锅侠气的有点想砸键盘,由于他尚未吃晚饭,有点饿,但又没有心情吃饭。

背锅侠以为本身使用的外部变量只有一个PI,问题确定出在这儿,但是调试的时候一切都正常啊。

因而背锅侠启用了他的终极方案,他按下了Ctrl + Shift + F 对PI进行了全局搜索。。。。。

我相信当他最后知道是谁改了这个变量的值,他砸的必定不是键盘。

 

小结:通常来讲,声明为static的变量或者class都会加上final,由于这是你们都要用的公共变量,也是你们达成了一致共识的这个变量就应该是这个值,或者说这个方法就应该这么写,要是被随意的更改,后果不堪设想,并且还可能找错骂人的对象。。。

 

 

方法与final

 改变方法的主要方式就是重写,那想想我们平时为何会想要重写一个方法呢?固然是以为父类写的很差啦(或者说不太合适)

好比说Object的 toString 方法每每不符合咱们对于打印的要求,那咱们有须要的时候就会去重写这个 toString方法。

重写的意义在于我返回的是跟你同样的类型,只是我实现的方式不一样,而你如今连实现的方式都给我定死了,这也太霸道了。

那么什么状况下咱们不该该重写某个方法呢?

我认为可能有两种状况(固然不会只有这两种状况,我的技术比较菜,暂时只能想到这两种):

  1. 个人方法里面涉及到了对静态变量的修改,那固然就由不得你胡乱修改了,不然就会出现上面的悲剧

  2. 个人方法不只返回类型是固定的,连返回值也是固定的,这个时候我就不但愿你来修改它了,好比我有一个修改人物信息的方法,其中有一个年龄字段我但愿返回值永远都是18,一旦我能让你修改了,你把我改为80怎么办。

这个例子可能不太贴切,咱们再想一个例子:

好比说我们在打游戏的时候,人物挂掉了,会到出生点,这个时候咱们须要设置人物处于满状态(满血,满蓝啥的),那么这个时候咱们会有一个set 人物属性的方法,若是这个方法让你重写了,这游戏还能玩吗?

而后我在JDK中也找到了一个例子,它就是Calendar的clear方法(Calendar类自己不是final修饰的,即它是能够继承的):

 

 这里的变量是啥意思不用关心,你须要知道的是为了实现clear这个方法的功能,我这里的stamp 和 fields 都须要赋一个具体的值0,若是这个方法敞开了让你改,那么这个方法的意义就没有了,你可能把它改为任何东西。

 

对于上面2点,我以为是有一个共性的,那就是个人方法必须得给用户使用(通常是public的),可是我又不想你来修改我方法的返回结果(是的,不是返回类型,连结果都握在手中不放),这个时候咱们就能够考虑用final来修饰一个方法。

 

类 与 final

在类上使用final,那就更绝了,我压根就不让你继承,你全部的方法都得按照个人来,想改变量,想重写方法?门儿都没有

在我所知道的类里面,这么变态的类很少,目前看到了两个,一个Math,一个String

那么一般来讲一个类为何要设计成final的呢?

这里我只能用我浅薄的基础尝试理解一下:

Math

1. 从语义角度上讲:它其中的方法都是涉及到一些 数学的定值、 数学公式的计算、精度的处理等等,这些东西它的返回值应该是按照一种特定的计算过程返回的,因此它并不想让你修改它的方法过程。

2. 从语法角度上讲:Math类并不容许实例化,因此它的变量和方法都是实例化的,由于上面讲到它涉及到的都是一些数学的定值和 数学公式,这些东西在很长时间内都是不变的,彻底能够共享出去让你们使用,而没必要实例化,由于我不须要每个对象都有一个PI变量,PI对于因此人来讲都是同样的,它也不该该被更改。

 

String

关于String的不变性网上的讨论十分多,彻底够再写一两篇文章了,因此这里我只是简单的小结一下:

首先要了解的一点是String的内部其实是一个字符数组,它的成员变量以下:

 

 

 而后做者在注释的第一段就为String的不可变性举出一颗糖炒栗子:

/**
 * String str = "abc";
 *
 * is equivalent to:
 *
 * char data[] = {'a', 'b', 'c'};
 * String str = new String(data);
 */

这段代码啥意思呢?

咱们稍微看一下String的源码能够发现:

public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}

咱们看到它其实是使用了参数的一个copy,这样就算你去改变data数组,你也没法改变str,当你再给str赋值时,实际上就又执行了上面这个过程,str就指向另外一块内存,而不是修改原来的内存了,

String中还有许多这样的操做来保证String的不可变性

在理解了这个知识点的前提下咱们再来看下面的内容

1. 字符串常量池: Java中有一个字符串常量池的概念:

String a = "1";
String b = "1";
boolean flag = (a == b);    // true

就是虽然他们是两个对象,可是指向了同一块内存,它就是Java中的一个常量池

当咱们须要定义一个新的字符串的时候,咱们就会去这个常量池中找有没有已经存在的,若是有直接拿过来用,没有就新建一个

但若是String是可变的, 那你每次都会去直接修改内存的值,那么常量池的意义就没有了。

2.  另一个例子就是HashMap了

咱们在HashMap中经常会用String当作key值,这样比较好理解,同时也很符合经常使用的json的格式。

若是我们的String是可变的会发生什么,我从知乎上找到一个例子,讲的比较清晰:

在java中String类为何要设计成final?  ,代码在第一个答案中

按照这个思路我本身也写了一个:

这是一个name - age 的map

 

    HashMap<StringBuilder,Integer> map = new HashMap<>();
    StringBuilder sb1 = new StringBuilder("李云龙");
    StringBuilder sb2 = new StringBuilder("赵刚");
    map.put(sb1,35);
    map.put(sb2,30);
    System.out.println(map);
StringBuilder sb3
= sb1; sb3.append("的儿子"); map.put(sb3,6); System.out.println(map);

 

结果以下:

 

 我们李大团长哪儿去了?

 

 这里咱们分析一下,sb1 和 sb3 指向了同一块内存,而后sb3修改了内存的值,致使sb1的值也变了,因此sb1中存储的李云龙也变成了“李云龙的儿子”,那我们的李大团长就消失了。

咱们明明是想加一我的进来,却把李团长搞丢了,这明显和咱们的指望不符合,然而若是咱们使用具备不可变性的String则能够达到目的:

 

HashMap<String,Integer> map1 = new HashMap<>();
String laoli = "李云龙";
String xiaozhao = "赵刚";
map1.put(laoli,35);
map1.put(xiaozhao,30);
 System.out.println(map1);

String son = laoli;
son += "的儿子";
map1.put(son,6);
System.out.println(map1);

结果:

 

 

固然了,若是咱们只是单纯的对字符串进行处理,而不是要做为key值,Stringbuilder或者StringBuffer则是更好的选择,可是这不在本篇文章的讨论范围以内,不作赘述。

 

至此,关于final的几个特性我就小结完毕了 ,我的技术有限,若是你们发现什么错漏之处,欢迎发在评论区,你们一块儿讨论

相关文章
相关标签/搜索