finalize、final、abstract

73 阅读8分钟

finalize方法

我们学习了产生对象的语法 -- 使用new 关键字,后面跟上构造方法,就可以在内存堆当中产生一个实例对象。
当这个对象用完以后,我们再也没有管过它了。因为我们知道,在Java当中有一个叫做GC(垃圾回收机制)的东西,由它来负责对象的销毁。那么,GC是如何销毁对象的呢?
销毁的时机:GC通过发现一个对象没有引用指向了,那么它会根据自身算法选择时机完成销毁对象。
但是,我们没有告诉大家,GC是如何实现这个销毁动作的。这个情况跟很多别的编程语言不同,GC的出现其实Java专门的一个设计,也是当初它宣传的一个亮点。就是程序员不用关注销毁,只需要关注产生对象。而比如C++的编程人员就不是这样的。在C++的语法当中,每个类也有构造函数,同时还有一个特殊的函数叫--析构函数。这个函数与构造函数是一对儿,它的作用就是用来销毁C++中的对象的。而我们Java程序员在书写的时候只有构造方法,没有写过析构方法。没有写,不代表没有,其实是Java设计人员在根类Objec当中把这个方法已经实现好了,然后通过继承,我们所有的类都自动具备这个方法。这个方法就是finalize方法。
​
所以,我们需要知道的是:finalize方法是定义在Object当中,由GC在销毁对象的时候自动调用。这个方法一般我们不会去重写,也不会去调用,所以这个方法被设计为protected修饰符。它通常是出现在面试题目当中的。
一般问:
1、finalize方法是谁? --- Object的,通过继承所有类都有这个方法
2、finalize方法是谁调用的? --- GC在销毁对象的时候调用
3finalfinally、finalize的比较
  其中finalfinally是Java当中的两个关键字。
  finalize不是关键字,它是一个方法名。
4、能不能重写finalize方法?
   能,但一般不会重写这个方法。因为这个方法涉及到了比较多的底层操作,以及各种算法和异常处理,所以直接用是最好的。如果,要重写该方法,那么我们应该在重写代码中书写一句"super.finalize()“,让底层操作和算法等仍然使用父类当中的实现,然后再添加上我们自己的实现.
        
        @Override
        protected void finalize() throws Throwable{
            super.finalize();
            /*
               添加上自己想在销毁对象时做的动作。
            */
        }
        

Object当中的finalize()从JDK9开始,正式被声明为“过时方法”

什么叫做过时方法?就是这个方法现在已经不被使用了,有了新的替换。但是为了保证与之前Java版本的过渡,没有把这个方法删除掉,但不建议使用。

如何让一个方法过时呢?只需要在这个方法的头上增加"@Deprecated"的修饰。

   @Deprecated
   public void test(){
   
   }
​

替换成什么了呢?在JDK9及其之后,Java是使用“try-with-resouce” 和 “Cleaner API”的来替代的。

另外我们已经看过了两个带有“@”符号的标记了,这个东西是Java中的一种特殊数据类型 --- “注解”。

final关键字 -- 知识点补全,final不仅仅是用来声明常量的

final关键字 -- final的英文含义就是“最终的”,"不变的“。所以用final修饰的内容都具有最终不变的特点。

1、final修饰的变量(包括属性 和 局部变量,甚至是形参) 就是成为常量

     int a = 10; //a是变量,值可以变
     final int b = 100;//b是常量,值不能变

2、final可以修饰方法,由final修饰的方法不能被“重写”。

记住:是不能重写,不是不能“重载”。重写才是对原方法进行实现部分的改变,重载是添加了一个(或多个)新的重名方法。

final修饰的方法 由于不能被重写,所以我们叫它“最终方法”/“终态方法”。

3、final可以修饰类, 这种类不能被继承。

我们可以把子类看成是对父类的改变,用了父类的属性和行为,还可以自己加新的属性和行为,也能把父类的行为进行重写改变。所以如果一个类被定义为final,不变的,那么它就不能够再生成新的子类。也就是说它不能当爸爸了,成为继承结构树上的某个分支的最终节点。这样的类,称为“终态类”或“最终类”。

4、final不能修饰构造方法。因为构造方法本身就不能被继承(前面讲过的),当然也就不能被重写,所以也不存在被改变的情况。

abstract 关键字

abstract 关键字 是一个修饰符,意思就是“抽象”。

我们发现在很多场景当中,都会出现这么一种情况,所有子类都拥有某一个行为,但是各自的实现不一样。根据我们学到的面向对象的分析,这种情况肯定要把这个共有行为设计到父类当中去。但尴尬的事情发生了,那就是父类虽然有这个行为,但是父类不知道该咋实现。 那么在前面的操作过程中,我们采用了给这个方法打上一对"{}",但是里面不写任何代码,让它做一个空白实现。

这里其实存在一个问题,一个方法是由方法的声明和方法的实现共同组成的。方法的实现就是那对“{}”及其里面的内容。从编程语言的语法结构上来说,只要打上"{}"就叫做这个方法已经实现了,只不过你的实现是什么指令都不发出。

而我们从“宠物”这例子当中,可以看到父类"Pet"只能确定它有这个“叫”的行为,不能确定这个行为的具体实现。因此在宠物这个类当中的叫的行为不应该实现,也就不应该打上这对“{}”。

Java语言在设计的时候考虑到了这种情况,允许一个方法在声明的后面不接"{}",而是用一个";"直接告诉它,这个方法的书写到此为止。形如这样的方法,我们把它称之为“抽象方法”。而抽象方法必须用"abstract"关键字修饰。

这个时候我们看到“Pet”类又再报错,通过查看报错信息,我们发现Java要求“有抽象方法的类必须是一个抽象类”。也就是说“Pet”类的声明处也要加上“abstract”关键字。

什么又是抽象类呢? 抽象类指的的是一种特殊的类,最大的特点是不能产生对象,也就是不能 new。除此之外,抽象类在内容的定义上和普通类没有任何区别,能够定义:属性、构造、方法(抽象方法和非抽象方法)。

抽象类既然不能产生对象,为什么还需要构造方法呢?

我们目前定义类只有两种用法:

1、产生对象;

2、充当父类。

抽象类不能产生对象了,那它就只能当父类了。也就是说抽象类的任务就是专门为子类规范,你们有哪些共有属性和共有行为的。虽然它不能产生对象,但是它的子类是需要产生对象的,而new一个子类对象必然要求调用父类的构造方法,因为子类是不知道有哪些共有属性和行为的,这些代码都是写在父类身上的。所以,抽象类的构造方法不是自己用的,而是产生子类对象的时候去调用的。

抽象设计的好处是什么呢? 主要好处是把上层父类的设计意图强制的推广给下层类,要求下层子类必须重写哪些方法,省却程序员自己去记忆这个环节。 如果子类不重写父类的抽象方法,那么这个子类必须设计为抽象类,不然编译通不过。

总结一下:

语法上:

1、abstract关键字是用来修饰类 或 方法的;修饰类,这个类就叫“抽象类”;修饰方法,就叫“抽象方法”、

2、有抽象方法的类一定是抽象类;

抽象类在语法上可以没有抽象方法; --- 通常我们不会这么设计;

3、抽象类不能产生对象,只能充当父类; --- 这一点抽象类 与 最终类 刚好相反

4、子类必须重写抽象类当中的所有抽象方法,否则它也是抽象类;---- 不遵守就编译不过,这就是父类的设计意图强制子类去实现。

5、除了以上4点,抽象类在书写的时候与普通类没有不同:属性、构造、实现的方法、静态代码块都可以有。

场景当中:

1、抽象类是自然而然设计出来,就是当我们在提取共有行为到父类中的时候,发现这个行为在父类这个等级“只能确定有这个行为,不能确定这个行为的实现”,那么我们就把这个方法设计为”抽象方法“,这类设计为”抽象类“。

2、抽象类也是类,也可以声明变量,但是不能产生对象。那么它的赋值咋办呢?可以赋值为子类的对象

比如:Pet p = new Dog();

p = new Cat();

这里将体现出“多态”的特征。