重踏学习Java路上_Day09(final,多态,抽象类,接口)
引入final的缘由:由于继承中方法有一个现象:方法重写;所以,父类的功能会被子类所覆盖掉。有些时候,我们不想让子类去覆盖掉父类的功能,就是关闭方法重写的功能,只能让该方法使用,此时,针对这种状况,Java就提供了一个关键字:final
1:final关键字(掌握)
(1)是最终的意思,可以修饰类,方法,变量。
(2)特点:
A:它修饰的类(编译器称其为最终类),该类不能被继承,一般是最底层的类才用final,因为不想让其被其他类所继承。
B:它修饰的方法,不能被重写(覆盖)。
C:它修饰的变量,该修饰后的变量不能被重新赋值(或者说是分配新值),因为这个被修饰的变量是一个常量(自定义常量)。
常量有两种,(1)字面值常量:"hello",10,true等;(2)自定义常量:final int x = 10;
(3)面试相关:
A:局部变量
a:基本类型 值不能发生改变
b:引用类型 地址值不能发生改变,但是对象的内容是可以改变的
B:初始化时机
a:只能初始化一次(默认赋值那一次不算,显示赋值或构造方法赋值,包含构造方法块,这三地方只能有一处赋值点),被final修饰的变量只能赋值一次。
b:常见的给值
定义的时候。(推荐)
构造方法中,在构造方法完毕前(注意该变量不能是静态的变量,如果静态final变量不赋初值运行,会出现"可能尚未初始化变量xxx",因为静态变量比构造方法运行早,没有默认赋值就执行,所以报错)。
面试题,局部变量问题例子:
/*
面试题:final修饰局部变量的问题
基本类型:基本类型的值不能发生改变。
引用类型:引用类型的地址值不能发生改变,但是,该对象的堆内存的值是可以改变的。
*/
class Student {
int age = 10;
}
class FinalTest {
public static void main(String[] args) {
//局部变量是基本数据类型
int x = 10;
x = 100;
System.out.println(x);
final int y = 10;
//无法为最终变量y分配值
//y = 100;
System.out.println(y);
System.out.println("--------------");
//局部变量是引用数据类型
Student s = new Student();
System.out.println(s.age);
s.age = 100;
System.out.println(s.age);
System.out.println("--------------");
final Student ss = new Student();
System.out.println(ss.age);
ss.age = 100;
System.out.println(ss.age);
//重新分配内存空间
//无法为最终变量ss分配值
ss = new Student();
}
}
面试题,初始化时机问题例子:
/*
final修饰变量的初始化时机
A:被final修饰的变量只能赋值一次。
B:在构造方法完毕前。(非静态的常量)
*/
class Demo {
//int num = 10;
//final int num2 = 20;
int num;
final int num2;
{
//num2 = 10;
}
public Demo() {
num = 100;
//无法为最终变量num2分配值
num2 = 200;
}
}
class FinalTest2 {
public static void main(String[] args) {
Demo d = new Demo();
System.out.println(d.num);
System.out.println(d.num2);
}
}
2:多态(掌握)
ClassCastException:类型转换异常
一般在多态的向下转型中容易出现,编译时候不会出错,在运行时会报错,因为语法没有错,编译就不会报错
子类中没有父亲中出现过的方法,方法就被继承过来了。
多态的概述:某一个事物,在不同时刻表现出来的不同状态。
(1)同一个对象在不同时刻体现出来的不同状态。
(2)多态的前提:
A:要有继承关系。
B:要有方法重写。
其实没有也是可以的,但是如果没有这个就没有意义。
动物 d = new 猫();
d.show();
动物 d = new 狗();
d.show();
C:要有父类引用指向子类对象。
父 f = new 子();
多态的分类:
a:具体类多态
class Fu {}
class Zi extends Fu {}
Fu f = new Zi();
b:抽象类多态
abstract class Fu {}
class Zi extends Fu {}
Fu f = new Zi();
c:接口多态
interface Fu {}
class Zi implements Fu {}
Fu f = new Zi();
(3)多态中的成员访问特点(刘意视频:09.07)
Fu f = new Zi(); f:指的就是左边,Zi()指的就是右边
A:成员变量
编译看左边,运行看左边(因为成员变量都是放在堆内存,按父类自己生成一份的变量数据内存)
B:构造方法
子类的构造都会默认访问父类构造(创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化,子类构造方法默认首句执行父类无参构造方法,为父类进行数据初始化)
C:成员方法
编译看左边,运行看右边(因为有重写的关系,所以运行的子类的重名方法)
D:静态方法
编译看左边,运行看左边(静态和类相关,算不上重写,所以,访问还是左边的)
为什么?
只有一个是运行看右边,就是成员方法: 由于成员方法存在方法重写,所以它运行看右边。
例子:
class Fu {
public int num = 100;
public void show() {
System.out.println("show Fu");
}
public static void function() {
System.out.println("function Fu");
}
}
class Zi extends Fu {
public int num = 1000;
public int num2 = 200;
public void show() {
System.out.println("show Zi");
}
public void method() {
System.out.println("method zi");
}
public static void function() {
System.out.println("function Zi");
}
}
class DuoTaiDemo {
public static void main(String[] args) {
//要有父类引用指向子类对象。
//父 f = new 子();
Fu f = new Zi();
System.out.println(f.num);
//找不到符号
//System.out.println(f.num2);
f.show();
//找不到符号
//f.method();
f.function();
}
}
(4)多态的好处:
A:提高代码的维护性(继承体现)
B:提高代码的扩展性(多态体现)
/*
多态的好处:
A:提高了代码的维护性(继承保证)
B:提高了代码的扩展性(由多态保证)
猫狗案例代码
*/
class Animal {
public void eat(){
System.out.println("eat");
}
public void sleep(){
System.out.println("sleep");
}
}
class Dog extends Animal {
public void eat(){
System.out.println("狗吃肉");
}
public void sleep(){
System.out.println("狗站着睡觉");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
public void sleep() {
System.out.println("猫趴着睡觉");
}
}
class Pig extends Animal {
public void eat() {
System.out.println("猪吃白菜");
}
public void sleep() {
System.out.println("猪侧着睡");
}
}
//针对动物操作的工具类
class AnimalTool {
private AnimalTool(){}
/*
//调用猫的功能
public static void useCat(Cat c) {
c.eat();
c.sleep();
}
//调用狗的功能
public static void useDog(Dog d) {
d.eat();
d.sleep();
}
//调用猪的功能
public static void usePig(Pig p) {
p.eat();
p.sleep();
}
*/
public static void useAnimal(Animal a) {
a.eat();
a.sleep();
}
}
class DuoTaiDemo2 {
public static void main(String[] args) {
//我喜欢猫,就养了一只
Cat c = new Cat();
c.eat();
c.sleep();
//我很喜欢猫,所以,又养了一只
Cat c2 = new Cat();
c2.eat();
c2.sleep();
//我特别喜欢猫,又养了一只
Cat c3 = new Cat();
c3.eat();
c3.sleep();
//...
System.out.println("--------------");
//问题来了,我养了很多只猫,每次创建对象是可以接受的
//但是呢?调用方法,你不觉得很相似吗?仅仅是对象名不一样。
//我们准备用方法改进
//调用方式改进版本
//useCat(c);
//useCat(c2);
//useCat(c3);
//AnimalTool.useCat(c);
//AnimalTool.useCat(c2);
//AnimalTool.useCat(c3);
AnimalTool.useAnimal(c);
AnimalTool.useAnimal(c2);
AnimalTool.useAnimal(c3);
System.out.println("--------------");
//我喜欢狗
Dog d = new Dog();
Dog d2 = new Dog();
Dog d3 = new Dog();
//AnimalTool.useDog(d);
//AnimalTool.useDog(d2);
//AnimalTool.useDog(d3);
AnimalTool.useAnimal(d);
AnimalTool.useAnimal(d2);
AnimalTool.useAnimal(d3);
System.out.println("--------------");
//我喜欢宠物猪
//定义一个猪类,它要继承自动物,提供两个方法,并且还得在工具类中添加该类方法调用
Pig p = new Pig();
Pig p2 = new Pig();
Pig p3 = new Pig();
//AnimalTool.usePig(p);
//AnimalTool.usePig(p2);
//AnimalTool.usePig(p3);
AnimalTool.useAnimal(p);
AnimalTool.useAnimal(p2);
AnimalTool.useAnimal(p3);
System.out.println("--------------");
//我喜欢宠物狼,老虎,豹子...
//定义对应的类,继承自动物,提供对应的方法重写,并在工具类添加方法调用
//前面几个必须写,我是没有意见的
//但是,工具类每次都改,麻烦不
//我就想,你能不能不改了
//太简单:把所有的动物都写上。问题是名字是什么呢?到底哪些需要被加入呢?
//改用另一种解决方案。
}
/*
//调用猫的功能
public static void useCat(Cat c) {
c.eat();
c.sleep();
}
//调用狗的功能
public static void useDog(Dog d) {
d.eat();
d.sleep();
}
*/
}
(5)多态的弊端:
父不能使用子的特有功能。(因为多态编译成员方法时,编译看左边(即父类),所以左边调用右边方法会出现报错,因为父类不存在该方法)
现象:
子可以当作父使用,父不能当作子使用。
(6)多态中的转型
A:向上转型
从子到父 父类引用指向子类对象 Fu f = new Zi();
B:向下转型
从父到子 父类引用转为子类对象 Zi z = (Zi) f;父类引用赋予给子类变量,打开它的儿子功能。
注意:多态的弊端
不能使用子类的特有功能。
我就想使用子类的特有功能?行不行?
行。
怎么用呢?
A:创建子类对象调用方法即可。(可以,但是很多时候不合理。而且,太占内存了)
B:把父类的引用强制转换为子类的引用。(向下转型)
对象间的转型问题:
向上转型:
Fu f = new Zi();//外面是一父的形式展现到外面,但内部是子的实例,但只能调用父类的相同东西,子类的特有功能是不能用的。
向下转型:
Zi z = (Zi)f; //要求该f必须是能够转换为Zi的。就是说强制转换前必须知道该f是Zi的对象。现在这样写就可以使用子类的特有功能了。
(7)孔子装爹的案例帮助大家理解多态
(8)多态的练习
A:猫狗案例
B:老师和学生案例
多态继承中的内存图解:
多态中的对象变化内存图解
3:抽象类(掌握)
(1)抽象类的概述:
把多个共性的东西提取到一个类中,这是继承的做法。
但是呢,这多个共性的东西,在有些时候,方法声明一样,但是方法体。
也就是说,方法声明一样,但是每个具体的对象在具体实现的时候内容不一样。
所以,我们在定义这些共性的方法的时候,就不能给出具体的方法体。
而一个没有具体的方法体的方法是抽象的方法。
在一个类中如果有抽象方法,该类必须定义为抽象类。
(2)抽象类的特点
A:抽象类和抽象方法必须用abstract关键字修饰
B:抽象类中不一定有抽象方法,但是有抽象方法的类必须定义为抽象类
C:抽象类不能实例化
因为它不是具体的。
抽象类有构造方法,但是不能实例化?构造方法的作用是什么呢?
用于子类访问父类数据的初始化,因为子类继承父类,在子类构造方法处会默认调用父类无参构造方法
D:抽象的子类
a:如果不想重写抽象方法,该子类也是一个抽象类。
b:重写所有的抽象方法,这个时候子类是一个具体的类。
抽象类的实例化其实是靠具体的子类实现的。是多态的方式。
Animal a = new Cat();
(3)抽象类的成员特点:
A:成员变量
既可以是变量,也可以是常量
B:构造方法
有构造方法,用于子类访问父类数据的初始化,抽象类可以有构造方法,构造方法不可继承(所有构造方法都不能继承,只能是调用),但是可以供子类用super()或者super(参数,参数。。。。)调用。
C:成员方法
有抽象,有非抽象
A:抽象方法 强制要求子类做的事情,其实就是要子类重写方法,是方法有意义
B: 非抽象方法 子类继承父类的事情,提高代码的复用性
(4)抽象类的练习
A:猫狗案例练习
B:老师案例练习
C:学生案例练习
D:员工案例练习
(5)抽象类的几个小问题
A:抽象类有构造方法,不能实例化,那么构造方法有什么用?
用于子类访问父类数据的初始化
B:一个类如果没有抽象方法,却定义为了抽象类,有什么用?
为了不让创建对象
C:abstract不能和哪些关键字共存
a:final 冲突(非法修饰符组合:absract和final,因为final就是不让子类重写覆盖,而abstract就是让自己继承并重写,所以报错)
b:private 冲突(非法修饰符组合:absract和private,因为private就是不让子类继承,而abstract就是让自己继承并重写,所以报错)
c:static 无意义(非法修饰符组合:absract和static,因为static是在类加载是加载,直接类名.方法名可以调用,但抽象方法都是没有方法体的,执行一个没有方法体的方法,所以一点用都没有,所以是无意义)
例子:抽象类的特点:
//abstract class Animal //抽象类的声明格式
abstract class Animal {
//抽象方法
//public abstract void eat(){} //空方法体,这个会报错。抽象方法不能有主体
public abstract void eat();
public Animal(){}
}
//子类是抽象类
abstract class Dog extends Animal {}
//子类是具体类,重写抽象方法
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
class AbstractDemo {
public static void main(String[] args) {
//创建对象
//Animal是抽象的; 无法实例化
//Animal a = new Animal();
//通过多态的方式
Animal a = new Cat();
a.eat();
}
}
4:接口(掌握)
(1)回顾猫狗案例,它们仅仅提供一些基本功能。
比如:猫钻火圈,狗跳高等功能,不是动物本身就具备的,
是在后面的培养中训练出来的,这种额外的功能,java提供了接口表示。
(2)接口的特点:
命名方法:接口名+Impl这种格式是接口的实现类格式
A:接口用关键字interface修饰
interface 接口名 {}
B:类实现接口用implements 修饰
class 类名 implements 接口名 {}
C:接口不能实例化
D:接口的实现类
a:是一个抽象类实现接口,但意义不大,因为抽象类也是不能实例化。
b:是一个具体类,这个类必须重写接口中的所有抽象方法。(推荐方案)
实现多态的方法:
(1)A:具体类多态(使用很少,机会没有);
(2)B:抽象类多态(常用);
(3)C:接口多态(最常用);
(3)接口的成员特点:
A:成员变量
只能是常量(就算是正常的"public int num=20;"在编译器中也会自动变为public static final形式:"public static final int num = 20;",因为这是接口的对于所有成员变量的默认特点)
默认修饰符:public static final (所以可以直接通过接口名.成员变量进行访问,写与不写,这三个修饰符都是一样的)
建议:每次写接口成员变量是都写完整版的,防止自己忘记,即public static final int num03 = 30;
B:构造方法
接口并没有构造方法 ,因为它的所有方法都是public abstract,都没有方法体,哪会有构造方法 接口主要是拓展功能的,而没有具体存在 (记住所有的类都是继承于无参Object类,但接口没有构造方法,实现接口的类的构造方法会调用Object的无参构造运行方法,所以不会报错)
C:成员方法
只能是抽象的
默认修饰符:public abstract (默认你写与不写,都默认为public abstract,建议还是写,因为自己会铭记)
(4)类与类,类与接口,接口与接口
A:类与类
继承关系,只能单继承,可以多层继承
B:类与接口
实现关系,可以单实现,也可以多实现。
还可以在继承一个类的同时,实现多个接口
C:接口与接口
继承关系,可以单继承,也可以多继承 interface Sister extends Father,Mother{},接口的多继承
(5)抽象类和接口的区别
抽象类和接口的区别:
A:成员区别
抽象类:
成员变量:可以变量,也可以常量
构造方法:有
成员方法:可以抽象,也可以非抽象
接口:
成员变量:只可以常量
成员方法:只可以抽象
B:关系区别
类与类
继承,单继承
类与接口
实现,单实现,多实现
接口与接口
继承,单继承,多继承
C:设计理念区别
抽象类 被继承体现的是:”is a”的关系。抽象类中定义的是该继承体系的共性功能。 Animal a = new Cat() 猫 是一只(is a) 动物
接口 被实现体现的是:”like a”的关系。接口中定义的是该继承体系的扩展功能。 like a :像,就是说不是他的特有功能,而是因时因地拓展出来的功能
(6)练习:
A:猫狗案例,加入跳高功能
B:老师和学生案例,加入抽烟功能0
接口定义的例子:
//定义动物培训接口
interface AnimalTrain {
public abstract void jump();
}
//抽象类实现接口
abstract class Dog implements AnimalTrain {
}
//具体类实现接口
class Cat implements AnimalTrain {
public void jump() {
System.out.println("猫可以跳高了");
}
}
class InterfaceDemo {
public static void main(String[] args) {
//AnimalTrain是抽象的; 无法实例化
//AnimalTrain at = new AnimalTrain();
//at.jump();
AnimalTrain at = new Cat();//这里利用Cat的对象引用,把引用赋予给接口变量,接口变量引用接口对象
at.jump();
}
}
类与类,类与接口,接口与接口的关系
interface Father {
public abstract void show();
}
interface Mother {
public abstract void show2();
}
interface Sister extends Father,Mother {
}
//class Son implements Father,Mother //多实现
class Son extends Object implements Father,Mother {
public void show() {
System.out.println("show son");
}
public void show2() {
System.out.println("show2 son");
}
}
class InterfaceDemo3 {
public static void main(String[] args) {
//创建对象
Father f = new Son();
f.show();
//f.show2(); //报错
Mother m = new Son();
//m.show(); //报错
m.show2();
}
}