传智播客-刘意-java深入浅出精华版学习笔记Day09

这几天的课真是越来越难了。。。。

 

final:

我们知道,在继承中,有方法的重写这一项。如果我不想让子类重写父类的方法,应该怎么做?针对这种情况,Java提供了关键字final。

final可以修饰类、方法、变量。

在父类中,如果final修饰了一个方法,在子类中试图对该方法进行重写,运行时会报错。

下面来讲final的特点:

1.    当final修饰类时,该类叫做最终类,最终类不能被继承。【类似于绝育手术。。。。。。

2.    当final修饰方法时,该方法不能被重写。

3.    当final修饰变量时,该变量不能被重新赋值。因为该变量其实是一种常量。

【事实上,常量可以分为两种,字面值常量(“hello,10,true”),自定义常量(final int = 10)】

final还可以修饰局部变量。在之前的学习中,权限修饰符从来没有修饰过局部变量,因为局部变量的作用域原本就十分有限。但是final可以修饰局部变量。

final修饰局部变量时,如果是基本变量,作用域中该变量不能被重新赋值。

如果是引用变量,比如说final修饰了一个新的对象。

上一段不会报错,下一段会。因为final修饰的是ss这个对象,而ss这个名字,代表的是对象及其内容在堆内存中的地址。即,地址不能变,但是内容没有被final修饰,因此是可以改变的。

1.    final修饰变量的初始化时机

1) 被final修饰的变量只能被赋值一次。【如果多次赋值,结果并不是后面的赋值无效,而是报错】

2) 在构造方法完毕前(非静态的常量)

多态:同一个对象,在不同时刻表现出来的不同状态

多态的前提:

要有继承关系;

要有方法重写;(其实没有也是可以的,但是如果没有这个就没有意义)

要有父类引用指向子类对象。【父 f = new 子()】(但是试了一下好像没有也行)

多态中的成员访问方法:

成员变量:

编译看左边,运行看左边。

构造方法:

创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。

成员方法

编译看左边,运行看右边。

静态方法:

编译看左边,运行看左边。也就是静态方法没有多态。

由于成员方法存在方法重写,所以它运行看右边

【这个时候有一个小小问题,并不是静态方法不能被重写,而是它的所有对象共享同一个值,不能被重写的是final】

多态的好处:

1.    提高了代码的维护性(继承)

2.    提高了代码的扩展性(对象类型是一样的类型,方法却可以是不同的方法)

多态的弊端:

不能使用子类的特有功能(定义不报错,访问才报错)

如果我就想使用子类的特有功能怎么办呢?

解决方法:创建子类对象调用方法;或;把父类的引用强制转换为子类的引用(向下转型)


多态的成员访问特点及转型的理解:孔子装爹问题

       class 孔子爹 {

              public int age = 40;

             

              public void teach() {

                     System.out.println("讲解JavaSE");

              }

       }

      

       class 孔子 extends 孔子爹{

              public int age = 20;

             

              public void teach() {

                     System.out.println("讲解论语");

              }

             

              public void playGame() {

                     System.out.println("英雄联盟");

              }

       }

      

       //Java培训特别火,很多人来请孔子爹去讲课,这一天孔子爹被请走了

       //但是还有人来请,就剩孔子在家,价格还挺高。孔子一想,我是不是可以考虑去呢?

       //然后就穿上爹的衣服,带上爹的眼睛,粘上爹的胡子。就开始装爹

       //向上转型

       孔子爹 k爹 = new 孔子();

       //到人家那里去了

       System.out.println(k爹.age); //40

       k爹.teach(); //讲解论语

       //k爹.playGame(); //这是儿子才能做的

      

      

       //讲完了,下班回家了

       //脱下爹的装备,换上自己的装备

       //向下转型

       孔子 k = (孔子) k爹;

       System.out.println(k.age); //20

       k.teach(); //讲解论语

       k.playGame(); //英雄联盟

      

成员变量代表的是类的属性,成员方法代表的才是类的功能。对于多态来说,外表是父类的外表(变量),功能是子类的功能(方法)。

多态定义的内存情况:


从上面这个图来看,当我访问f的变量时,输出的是40;

当我访问f的show方法时,编译器先找到了super区的方法,然后发现有重写,ok,调用子类的方法;

当我访问f的method方法时,编译器在super区没找到东西。报错。

多态强转的内存情况:


Dog d = (Dog)a 一步中,类型强转,地址赋给你,堆中没有创建新的空间,内容还有,子类方法可以访问了。

红色下面这些都是自己的理解,不一定对,以后可能会修正

最后一步当然是错的,但是错的时候不是编译的时候报错,而是运行的时候报错。

在上文中已经提到过,多态调用子类方法会出错,如果只是子类里面有别的方法不会出错,你去调用的时候才会出错。这里即使不调用方法,仅仅是运行都会报错。

为什么?

因为编译的时候,只检查有没有基本的语法错误。我们在创建对象时的要求是“右边是左边。”

比如:

Dog dd = new Animal();//动物是狗,错误

Dog ddd = new Cat();//猫是狗,错误

Dog dd = new Dog();//狗是狗,正确

Animal dd = new Dog ();//狗是动物,正确

上面两种错误都会在编译时给出“不兼容的类型”。

而这种赋值方式:

Dog d = (Dog)a

a这个地址已经存在了,编译器并不知道a里面是什么,它只是一看,狗是狗,把0x002的值扔给dd这个引用的局部变量,ok。运行时创造对象,才会发现,哦,狗不能变成猫。报ClassCastException而在上文中提到的调用方法的问题,显然创建引用类型对象时没有出错,然后我们也没有去调用它不能调用的方法,就不会报错。

为了说清楚问题,再来一个例子。

/*

    不同地方饮食文化不同的案例

*/

class Person {

    public voideat() {

        System.out.println("吃饭");

    }

}

 

class SouthPerson extends Person {

    public voideat() {

        System.out.println("炒菜,吃米饭");

    }

   

    public voidjingShang() {

        System.out.println("经商");

    }

}

 

class NorthPerson extends Person {

    public voideat() {

        System.out.println("炖菜,吃馒头");

    }

   

    public voidyanJiu() {

        System.out.println("研究");

    }

}

 

class DuoTaiTest2 {

    public staticvoid main(String[] args) {

        //测试

        //南方人

        Person p =new SouthPerson();

        p.eat();

        System.out.println("-------------");

        SouthPersonsp = (SouthPerson)p;

        sp.eat();

        sp.jingShang();

        System.out.println("-------------");

       

        //北方人

        p = new NorthPerson();//没有问题,p是person类型

        sp = new NorthPerson();//报错,不兼容的类型: NorthPerson无法转换为SouthPerson

        p.eat();

        System.out.println("-------------");

        NorthPersonnp = (NorthPerson)p;

        np.eat();

        np.yanJiu();

    }

}

其实对多态还是有很多不太理解,先到这里。

抽象:

动物不应该定义为具体的东西,而且动物中的吃,睡等也不应该是具体的。我们把一个不是具体的功能称为抽象的功能,而一个类中如果有抽象的功能,该类必须是抽象类。(不过抽象类中不一定有抽象方法)

1.抽象类和抽象方法必须用abstract方法修饰。

2.抽象方法不能有方法体。

3.抽象类不能实例化,但是有构造方法(用于子类访问父类数据的初始化)

4.抽象类的子类。

1)子类是抽象类

2)子类不是抽象类,但是子类必须重写所有的抽象方法(构造方法好像不用重写)。

3)因此,抽象类可以通过多态的方式实例化。(即父类是抽象类,子类是非抽象类)。所以上文我们说“要有方法重写;(其实没有也是可以的,但是如果没有这个就没有意义)

”。抽象类的实例是一个多态方式,而多态的主要应用就是抽象类这里。

抽象类的成员特点:

成员变量:既可以是变量,也可以是常量。(正常存在,正常继承,正常使用)

构造函数:既可以是带参的,也可以是无参的,也正常。

成员函数:既可以是抽象的,也可以是不抽象的,不抽象的方法不需要重写。

其中,抽象方法是强制要求子类做的事情。非抽象方法是子类继承的事情,提高代码的复用性。

抽象类中的几个小问题:【面试经常出现】

1.    一个类没有抽象方法,能不能定义为抽象类?如果可以,有什么意义?

可以。意义是不让创建对象。

2.    抽象类不能和哪些关键字共存?为什么?

private:这里指的成员方法的声明不能和private共存。因为抽象要求必须重写,而这里private说不让重写。

final:同上。

static:无意义。因为static的意义是直接通过类名访问,而抽象是没有方法体的。访问一个没有方法体的方法,要他干嘛。

不过这些都是原理上的分析。在编译时,报的错都是“非法的修饰符组合”。

接口:

1.接口用关键字interface表示。

类实现接口用implements实现。

2.接口并不是实际意义上的类。它只是代表功能的扩展

3.接口是抽象的,不能实例化。必须用多态的方式实例化。

【由此可见,多态的三种实现方法:

1.    具体类多态(几乎不用)

2.    抽象类多态(常用)

3.    接口多态(最常用)】

当然了,我们不用多态来实现后两种是完全可以的,甚至还可以避免子类特有方法不能用的弊端。但是用多态就可以实现代码的扩展性。

4.    接口的子类:

1) 可以是抽象类,但是没什么意义。

2) 可以是具体类,但是要注意重写抽象方法。接口名+Impl这种格式是接口的实现类格式。(推荐方案)

接口的成员特点(和抽象类的区别):

成员变量:接口中的变量默认是常量,并且是静态的。

也就是有默认修饰符:public(权限够大),static(和接口直接相关,可以直接调用,一改全改),final(不能修改)。自己写的话,建议手动把这些都写上,不容易错。

构造方法:接口没有构造方法。

                那么接口的子类的构造方法是什么情况呢?

                所有的类都默认继承自这一个方法:Object类。类 Object 是类层次结构的根类。每个类都使用 Object 作为超类。Object类是无参构造,所以所有的类的默认构造方法都是无参构造方法。因此,接口没有构造方法没关系,接口子类的构造方法继承自Object的构造方法。

成员方法:接口方法不能带有主体。默认为抽象方法。建议自己手动给出。【事实上 ,一个方法要么需要有方法体,要么需要声明抽象,否则会报错。只是在接口中,可以不加方法体,此时默认抽象不报错】

类与接口的关系:

类与类:(extends)

              继承关系,只能单继承,可以多层继承。

       类与接口:(implements)

              实现关系,可以单实现,也可以多实现。

              并且还可以在继承一个类的同时实现多个接口。但是注意多态时,方法要对应。

       接口与接口:(extends)

              继承关系,可以单继承,也可以多继承。

抽象类和接口的区别:


猜你喜欢

转载自blog.csdn.net/weixin_39655021/article/details/79690983