传智播客-刘意-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)
继承关系,可以单继承,也可以多继承。
抽象类和接口的区别: