zoukankan      html  css  js  c++  java
  • 重踏学习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();
        }
    }

  • 相关阅读:
    php 中的 Output Control 函数
    web安全知识
    php写一个web五子棋
    实现一个web服务器, 支持php
    字节序
    TinyHTTPd源码分析
    linux 管道通信
    linux网络编程
    微信公众号开发-静默授权实现消息推送(微服务方式)
    初学 Nginx
  • 原文地址:https://www.cnblogs.com/canceler/p/4599528.html
Copyright © 2011-2022 走看看