重踏学习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();
    }
}

posted @ 2015-06-25 11:25  暴走骑士  阅读(233)  评论(0编辑  收藏  举报