final关键字
final 关键字可以用来修饰类,属性和方法。
final修饰类
Java 中 final 修饰的类不能被继承。如果用 final 关键字修饰类,表明设计者不希望该类被继承。这在实际开发中是真的有存在意义的,比如 java.lang 包中的 System 类,封装了一些系统关键功能,可能是出于安全考虑,就使用 final 修饰此类,不允许其被继承。
平时天天用System.out.println()
方法,就没有想过这行代码的含义吗?System 是 java.lang 包中的一个类,out 则是 System 类内部的一个静态的 PrintStream 类型的一个对象,由 JVM 自动创建,这种代码组织方式就叫做组合。pritnln() 就是 PrintStream 类的一个实例方法。
final修饰方法
final 修饰的实例方法在子类中不能被重写,也就是不允许子类覆盖此方法。一个类不允许其子类覆盖继承下来的方法,也很有实际意义。
私有的方法即使用了 final 关键字修饰,并不影响什么,因为私有方法根本就不会被子类继承,所以也就不存在什么子类不能覆盖此方法的概念了。子类中即使使用了一个本来会覆盖此类的方法,也会被认为是子类新增的方法,父类引用变量是无法使用到这个新增方法的,只有子类引用变量才可以使用到。这根本就不叫覆盖了,这完全是两码事。换个简单的说法就是使用 final 关键字修饰私有方法,纯属吃多了没事做,没卵用。
当子类中有和父类相同的属性以及静态方法时,这种称之为隐藏。在子类的内存空间中,会同时存在父类和子类的这种属性和方法的内存空间。父类引用看到的是父类的属性和方法,子类引用看到的是子类的属性和方法。方法重写相关笔记
从上面可以看出,因为非私有的静态方法也会被继承,所以使用 final 修饰的静态方法一样不能够被子类覆盖。如果是私有的静态方法,那就同上面私有的实例方法一样,子类都无法继承了,也就根本不存在方法重写的概念。
final 修饰属性
final 修饰的属性严格来说叫做单值变量,意思就是只能被赋值一次,以后都不能再被更改了。说 final 修饰的变量属于常量,是错误的说法,不管怎么样,他始终都是变量,说是常变量还可以接受。从内存的角度看,常量是在方法区的常量池,final 修饰的变量可以在任意区域。从这就能看出 final 修饰的不是常量。(2018.7.20 过来复盘了,又忘记了吧,把它当成了常量看待,错了吧。final 修饰的变量属于单值变量)
final 修饰的位于方法栈帧里面的局部变量,允许在声明的时候不赋值,可以在它第一次被使用前进行赋值,此次赋值后就不会被改变。如果是在声明的时候赋初值,就不再能进行第二次赋值了。形参因为也属于局部变量,也可以用 final 修饰,但是在函数被调用时,就属于第一次被赋初值,所以之后就不再被改变了。
final 修饰在类中声明的实例全局变量只能在声明的时候就进行初始化,第一次赋值。
final 修饰在类中声明的静态全局变量只能在声明的时候就进行初始化,以后不能再赋值。
final 还可以用来修饰数组。比如 java.lang.String 类中的数组就是用 final 修饰的,所以它不才是不可改变的,这才是本质原因。
总结就是,因为全局变量会有一个默认的值,所以必须被显示初始化赋值,否则第一次赋值的机会就被这个默认的值用到了。而局部变量因为在未初始化的时候存储的是一个随机的垃圾值,所以允许在声明的时候不赋值,使用前再赋值即可。
因为引用变量存储的是对象在内存中的地址,使用 final 修饰这个引用变量只是说这个引用只能够指向这个一开始就确定的对象,不能够再指向其他对象。至于这个对象内部如何改变,并不影响。 引用变量按照存储区域同样可以分为全局变量和局部变量,具体规则是适用上述总结的。
abstract关键字
abstract 可以用来修饰方法和类,修饰类则这个类叫做抽象类,修饰方法则这个方法叫做抽象方法。
抽象方法
abstract 修饰方法,说明这个方法是抽象方法,一个含有抽象方法的类,也必须是抽象类。之所以设计抽象方法这种东西的存在就是为了让其子类继承这个方法后,来重写它。抽象方法不能够用 private 来修饰,因为这没有意义啊,这就意味着子类不可能会继承这个方法,也就无法重写它。Java 中直接在编译阶段就否定了这种写法。
抽象类
抽象类可以且必须有构造函数,因为抽象类的子类在创建对象的时候一定会调用其父类的构造函数,如果抽象类没有构造函数,那么一定会出现运行错误,子类对象无法成功创建。但是抽象类是无法直接创建对象的!
抽象类可以拥有属性,它可以在构造函数内进行初始化。抽象类也可以有完整的实例方法,还可以不包含任意一个抽象方法,仍然可以作为抽象类。抽象类尽管不能创建对象,但是可以存在一个抽象类类型的引用变量,和其他类的父类的引用变量没有任何区别。
总结,抽象类和普通类的区别就是不能够创建对象,可以存在没有实现的抽象方法,留给继承它的子类去重写它。
接口
Java8 之前接口里面只能有属性和方法,Java8 中允许接口类中出现用 default 修饰符修饰的非抽象方法。Java9 中允许出现用 private 修饰符修饰的非抽象方法。高版本特性先不考虑!
接口是没有构造方法的,从这里就可以证明接口并不是类,因为子类对象创建过程一定会调用父类的构造函数,否则就会出现运行错误,显然,接口并不满足这个条件。但是一个类实现了接口,就相当于继承了它的属性以及必须实现它的抽象方法,这又和类的继承是一样的道理。
接口同样会被编译成 .class 文件,且一个类可以实现多个接口,一个接口也可以继承多个接口。尽管 java 中只允许单继承,但通过这种方式就相当于变相实现了多继承!
接口本质上就是功能规范,一流的企业制定标准。从顶层设计的角度看,接口制定了相关的行为规范,它规定实现这个接口的类必须实现对这种规范做出具体实现。
父类引用指向子类对象,这叫做向上转型。但是一个接口类型的变量指向它的实现类,这有一个专业术语叫做接口的回调。虽然这两者本质上是一样的,但毕竟接口不是类,并不能够用和类一样的向上转型的说法来描述,那就给它弄一个新的概念呗,叫做接口的回调。
接口内的属性
接口内的属性默认是 public final static 修饰的,属于静态全局变量,所以必须在声明的时候显示初始化赋值。static 修饰的变量属于这个类所有对象共享,存在巨大耦合性,但因为又用 final 修饰了,所以不能再次被改变,只能读取,反而避免了这种耦合性带来的问题。
接口内的方法
接口内的方法默认是 public abstract 修饰的,所以实现了这个接口的子类就必须实现这个方法。这里之所以不能用重写这个词,是因为接口本质上不是类,尽管它和继承有着息息相关的特性,专业术语说对了,才显得你有水准嘛。
所以为了显示你的专业性,以后定义接口的时候,变量不要用 public final static 来修饰它,因为它默认就是,直接指明数据类型和变量名就可以了。定义方法的时候也不要用 public abstract 来修饰,因为它默认就是,直接指明返回类型和方法名即可。这和定义数组使用 int[] arg ,而不是 int agr[] 的道理是一样的,专业写法展示了你的专业性。
Java 8 对于接口新增的特性
Java 8 规范中允许接口添加 default 修饰的非抽象方法,这其实也很有好处。比如在学习 awt 事件触发机制的时候,在事件源添加事件监听器的时候,需要传递一个实现了对应事件接口的类作为参数给监听方法,这时候就不得不实现这个接口的所有抽象方法,但很多时候我们只要用它的一个方法啊,这就让人难受,所以它提供了对应的监听器适配器类,这个类提供了对所有抽象方法的空实现,以减少无意义的代码产生。关于awt事件触发机制查看这里
而 Java 8 中则更为灵活,它允许接口添加 default 修饰的非抽象方法,如果它它不要求子类必须实现该方法,可以直接在接口中就提供空实现,这非常的省事啊。JavaWeb 标准类库中的 javax.servlet.FilterConfig 接口就是用这种方式完成的此功能。点此查看过滤器 Filter 的笔记
总结下就是:如果接口中有很多抽象方法,而在实际使用中,又用不到那么多,很多时候只需要用到一个。但是又受限于语法的要求,不得不实现所有方法, 这样就会添加了很多无意义的代码。解决方法有两个:
- 定义适配器类实现该接口,提供对应方法的空实现。然后继承该适配器类
- 直接利用 Java 8 新增特性,在接口中就添加 default 修饰的空实现的方法
网友评论