java语法基础3:面向对象 类(封装 继承 多态 关键字)

一.面向对象

        面向对象与面向过程相对,都是程序设计软件开发的方式。

1.两者的介绍:

        面向过程:以过程为核心,强调事件的流程、顺序
                 PO:Procedure Oriented。代表语言:C语言
                面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
                以做饭为例,面向过程就像是蛋炒饭,第一步放鸡蛋,再放饭,随后是调料和葱花。
               

        面向对象:以对象为核心,强调事件的角色、主体,如:C++、Java。

                OO:Object Oriented 面向对象。包括OOA,OOD,OOP。OOA:Object Oriented Analysis 面向对象分析。OOD:Object Oriented Design 面向对象设计。OOP:Object Oriented Programming 面向对象编程。代表语言:Java、C#、Python等。
                人类是以面向对象的方式去认知世界的。所以采用面向对象的思想更加容易处理复杂的问题。
                面向对象就是分析出解决这个问题都需要哪些对象的参加,然后让对象与对象之间协作起来形成一个系统。 例如开汽车:汽车对象、司机对象。司机对象有一个驾驶的行为。司机对象驾驶汽车对象。 再例如装修房子:水电工对象,油漆工对象,瓦工对象,木工对象。每个对象都有自己的行为动作。最终完成装修。
                以做饭为例,面向对象就像是盖浇饭,第一步放饭,再盖上菜,最后加小料和煎蛋。

2.两者的对比

        面向过程优点:
具体步骤较为清晰,性能较高

        面向过程缺点
可维护性低,耦合度高

        面向对象优点:
可维护性高,耦合度低

        面向对象缺点:
性能没有面向过程高

一些名词解释:
        1.为什么面向过程性能效率更高:因为面向对象中类调用时需要实例化,开销比较大,比较消耗资源;因此单片机、嵌入式开发、 Linux/Unix等一般采用面向对象开发,这些开发中性能是最重要的因素。
        2.可维护性主要表现在三个方面,即可理解性、可测试性和可修改性。——前面提到过面向对象就像是盖浇饭,因此其中饭和菜的分离是很容易的,提高了盖浇饭制作的灵活性;而若是蛋炒饭则较难进行修改和复用
        3.耦合度是块或对象之间相互依赖的程度,即一个模块或对象对另一个模块或对象的依赖程度;蛋炒饭中两者已经几乎融为一体,因此互相依赖,耦合度较差,而盖浇饭中饭和菜都有自己的功能和任务耦合程度较低

3.面向对象的三大特征:

封装
        将属性和方法都放在一个类里,而且还可以通过访问类的权限属性给区分开。用户或开发者不需要知道里面具体的实现逻辑和方法而可以拿过来直接用;也提高了安全性

继承
        就是把之前已经实现好的代码或者方法通过继承的方法拿过来使用,大大地提高了代码的复用性和可维护性,也有效地减少了代码量

多态
        由于可以继承多个类,能够组合成多种特性,但多态的关键是覆盖,就是同一个方法可以用不同的方式去实现,展现出多态性,就是方法和属性有多种形态。

4.类和对象

4.1什么是对象:

        对象(又称实例)即是真实存在的一个实体在java中,一切事物皆为对象

4.2什么是类:

       类即是对一个对象的描述。描述对象的属性(尺寸,颜色、型号)和功能(类=属性+功能)
               现实世界中,事物与事物之间具有共同特征,例如:刘德华和梁朝伟都有姓名、身份证号、身高等状态,都有吃、跑、跳等行为。将这些共同的状态和行为提取出来,形成了一个模板,称为类。
                类实际上是人类大脑思考总结的一个模板,类是一个抽象的概念。 状态在程序中对应属性。属性通常用变量来表示。 行为在程序中对应方法。用方法来描述行为动作。

        

如图:
        1. 刘德华和梁朝伟都有姓名、身份证号、身高等状态,都有吃、跑、跳等行为。将这些共同的状态和行为提取出来,形成了一个模板,称为类。又由于他们都是明星,所以可以称作明星类
        2. 通过这一个明星类,又可以再创造出来n个对象(实例),比如此中可以创造出宋小宝 潘长江。

4.3定义类的语法格式:

        

/*
1. 定义类的语法格式:
    [修饰符列表] class 类名{
        类体 = 属性 + 方法;

        // 属性(实例变量),描述的是状态

        // 方法,描述的是行为动作
    }
2. 为什么要定义类?
    因为要通过类实例化对象。有了对象,让对象和对象之间协作起来形成系统。

3. 一个类可以实例化多个java对象。(通过一个类可以造出多个java对象。)

4. 实例变量是一个对象一份,比如创建3个学生对象,每个学生对象中应该都有name变量。

5. 实例变量属于成员变量,成员变量如果没有手动赋值,系统会赋默认值
    数据类型        默认值
    ----------------------
    byte            0
    short           0
    int             0
    long            0L
    float           0.0F
    double          0.0
    boolean         false
    char            \u0000
    引用数据类型      null
 */
public class Student {

    // 属性:姓名,年龄,性别,他们都是实例变量

    // 姓名
    String name;

    // 年龄
    int age;

    // 性别
    boolean gender;
}

4.4对象的创建和一些基本操作:

package com.powernode.javase.oop01;

public class StudentTest01 {
    public static void main(String[] args) {
        // 局部变量
        int i = 10;

        // 通过学生类Student实例化学生对象(通过类创造对象)
        // Student s1; 是什么?s1是变量名。Student是一种数据类型名。属于引用数据类型。
        // s1也是局部变量。和i一样。
        // s1变量中保存的是:堆内存中Student对象的内存地址。
        // s1有一个特殊的称呼:引用
        // 什么是引用?引用的本质上是一个变量,这个变量中保存了java对象的内存地址。
        // 引用和对象要区分开。对象在JVM堆当中。引用是保存对象地址的变量。
        Student s1 = new Student();

        // 访问对象的属性(读变量的值)
        // 访问实例变量的语法:引用.变量名(其中.的意思可以理解为”的“)
        // 两种访问方式:第一种读取,第二种修改。
        // 读取:引用.变量名 s1.name; s1.age; s1.gender;
        // 修改:引用.变量名 = 值; s1.name = "jack"; s1.age = 20; s1.gender = true;
        System.out.println("姓名:" + s1.name); // null
        System.out.println("年龄:" + s1.age); // 0
        System.out.println("性别:" + (s1.gender ? "男" : "女"));

        // 修改对象的属性(修改变量的值,给变量重新赋值)
        s1.name = "张三";
        s1.age = 20;
        s1.gender = true;

        System.out.println("姓名:" + s1.name); // 张三
        System.out.println("年龄:" + s1.age); // 20
        System.out.println("性别:" + (s1.gender ? "男" : "女")); // 男

        // 再创建一个新对象
        Student s2 = new Student();

        // 访问对象的属性
        System.out.println("姓名=" + s2.name); // null
        System.out.println("年龄=" + s2.age); // 0
        System.out.println("性别=" + (s2.gender ? "男" : "女"));

        // 修改对象的属性
        s2.name = "李四";
        s2.age = 20;
        s2.gender = false;

        System.out.println("姓名=" + s2.name); // 李四
        System.out.println("年龄=" + s2.age); // 20
        System.out.println("性别=" + (s2.gender ? "男" : "女")); // 女


    }
}

4.5jvm的内存分析:

a.内存创立时的内存分析

 eg:Student s1 = new Student()
        s1是一个特殊的局部变量,我们称它为引用,引用存储的是对象的地址(类似c中的指针)而对象则存储在jvm的堆中。

b.空指针异常时的内存分析

       注意:引用一旦为null,表示引用不再指向对象了。但是通过引用访问name属性,编译可以通过。不过运行时会出现异常:空指针异常。NullPointerException。这是一个非常著名的异常。
       为什么会出现空指针异常?因为运行的时候会找真正的对象,如果对象不存在了,就会出现这个异常。
        若没有任何指针指向该对象,则该对象会被当作垃圾被回收(释放)




4.6方法调用时参数是怎么传递的?

        简单来说就一句话:方法调用时,不管参数是什么类型。都是把变量中的值保存一份复制进去

这就分成了两种情况:

        situation1:传入的参数是一个普通数据类型 

public class ArgsTest01 {
    public static void main(String[] args) {
        int i = 10;
        // 调用add方法的时候,将i传进去,实际上是怎么传的?将i变量中保存值10复制了一份,传给了add方法。
        add(i);
        System.out.println("main--->" + i); // 10
    }
    public static void add(int i){ // 方法的形参是局部变量。
        i++;
        System.out.println("add--->" + i); // 11
    }
}

        通过程序及其输出不难看出:传入的参数和外面的变量就是两个东西,他们毫无关联,也不互相影响

        situation2:传入的参数是一个引用数据类型     

        要注意的是,此时"方法调用时,不管参数是什么类型。都是把变量中的值保存一份复制进去“这句话仍是成立的;只是引用数据类型变量存储的是地址,相当于传进方法的是地址。

public class ArgsTest02 {
    public static void main(String[] args) {
        User u = new User();
        u.age = 10;
        // u是怎么传递过去的。实际上和i原理相同:都是将变量中保存的值传递过去。
        // 只不过这里的u变量中保存的值比较特殊,是一个对象的内存地址。
        add(u);
        System.out.println("main-->" + u.age); // 11
    }
    public static void add(User u) { // u是一个引用。
        u.age++;
        System.out.println("add-->" + u.age); // 11
    }
}

二.面向对象的封装性:

1. 初步介绍:

1.1 什么是封装:


        封装是一种将数据和方法加以包装,使之成为一个独立的实体,并且把它与外部对象隔离开来的机制。具体来说,封装是将一个对象的所有“状态(属性)”以及“行为(方法)”统一封装到一个类中,从而隐藏了对象内部的具体实现细节,向外界提供了有限的访问接口,以实现对对象的保护和隔离。

1.2 为什么要封装:

        前面我们已经学习了类和对象,但如果我们一味地把数据修改和操作的权限都给用户,这会带来许多的问题,一方面用户操作起来并没有那么便捷直观,另一方面这非常的不安全,用户可能会修改一些核心数据。

1.3 封装的好处:

        在1.2我们已经知道了封装的必要性,那么封装的好处也是显而易见的。

        封装通过限制外部对对象内部的直接访问和修改,保证了数据的安全性,并提高了代码的可维护性和可复用性。

2. 怎么进行封装:

        只需两步:即属性私有化+对外提供getter和setter方法

        其中属性私有化(用private去修饰属性)是为了让用户不再能操作这个属性,而getter和setter方法则是为了让用户能对这个属性进行被限制的操作(获取值和修改值)

例子:

public class User {
    private int age;

    // 读取age属性的值
    // getter方法是绝对安全的。因为这个方法是读取属性的值,不会涉及修改操作。
    public int getAge(){
        //return this.age;
        return age;
    }

    // 修改age属性的值
    // setter方法当中就需要编写拦截过滤代码,来保证属性的安全。
    /*public void setAge(int num){
        //this.age = num;
        if(num < 0 || num > 100) {
            System.out.println("对不起,您的年龄值不合法!");
            return;
        }
        age = num;
    }*/

    // java有就近原则。
    public void setAge(int age){
        if(age < 0 || age > 100) {
            System.out.println("对不起,您的年龄值不合法!");
            return;
        }
        // this. 大部分情况下可以省略。
        // this. 什么时候不能省略?用来区分局部变量和实例变量的时候。
        this.age = age;
    }
}

好处从代码来看显而易见:
        1.用户角度来看,只需要调用getter方法和setter即可,较为方便
        2.程序员角度:可以对方法进行限制达到保护的效果。其中getter只是获取数据,是绝对安全的;而setter还要修改数据,可能存在安全隐患。

this的理解:
        this出现在实例方法中,代表当前对象。
        this大部分时候可以省略,只有用于区分局部变量(方法体中的变量)和实例变量(类中的变量)的时候才不能省略。

快捷键:(idea中)

        1. 快速生成getter和setter方法:alt+insert,随后选择getter and setter
        2. 快速生成if:布尔类型值(true/false).if
        3. 快速生成创建用户语句:类名.new.var

        !!!:要记得——不管是实例变量还是实例方法:调用方法都是,先新建对象,后通过引用.去调用变量和方法

    /**
     * 实例方法(因为没有加static)
     * 这种行为动作是需要对象才能触发的。需要对象才能参与的。
     */
    public void shopping(){
        //System.out.println(this.name + "正在疯狂购物!!!");
        // this. 大部分情况下是可以省略的。
        System.out.println(name + "正在疯狂购物!!!");
        // 最后要结账(调用付款的方法)
        //this.pay();
        // this. 大部分情况下是可以省略的。
        pay();
    }

        类中的属性即是实例变量
        而如果类中的方法没有加static,则说明是实例方法(这些方法是需要对象参与,对象来触发的)

三. 构造方法Constructor(构造器)

1. 构造方法有什么用:

        构造方法的执行分为两个阶段:对象的创建和对象的初始化。这两个阶段不能颠倒,也不可分割。
         在Java中,当我们使用关键字new时,就会在内存中创建一个新的对象,虽然对象已经被创建出来了,但还没有被初始化。而初始化则是在执行构造方法体时进行的。
        其中:new的时候在堆内存中开辟空间,给所有属性赋默认值,此时就完成了对象的创建(这个过程是在构造方法体执行前就完成了
                  构造方法执行结束时,标志对象初始化完毕。

2. 无参数构造方法:

        不难发现,我们前面书写类的时候并没有写构造方法但却仍能正常进行,这是因为如果一个类没有显示的定义任何构造方法,系统会默认提供一个无参数构造方法(也叫缺省构造器)。
        一旦显示的定义了构造方法,则缺省构造器将不存在。为了方便对象的创建,建议将缺省构造器显示的定义出来。

3. 构造方法的定义和调用:

        定义:[修饰符列表] 构造方法名(形参){}
        调用:new 构造方法名(实参);     (调用时一般左边会有一个变量来接收值)

4. 构造方法支持方法重载:

        如下:

 /**
     * 无参数的构造方法显示的定义出来。
     */
    public Student(){
        /*for(int i = 0; i < 10; i++){
            System.out.println("i = " + i);
        }*/
        System.out.println("Student类的无参数构造方法执行了");
    }

    public Student(String name) {
        /*for(int i = 0; i < 10; i++){
            System.out.println("i = " + i);
        }*/
        this.name = name;
    }

    public Student(String name, int age) {
        /*for(int i = 0; i < 10; i++){
            System.out.println("i = " + i);
        }*/
        this.name = name;
        this.age = age;
    }

    public Student(String name, int age, boolean sex, String address){
        /*for(int i = 0; i < 10; i++){
            System.out.println("i = " + i);
        }*/
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.address = address;
    }

5. 构造方法块(一个语法糖):

加上构造方法块后,完整的过程变成了:
                                  a.new的时候在堆内存中开辟空间,给所有属性赋默认值,此时就完成了对象的创建(这个过程是在构造方法体执行前就完成了)
                                  b. 执行构造代码块进行初始化
                                  c.执行构造方法体进行初始化;构造方法执行结束,对象初始化完毕。

// 构造代码块
    {
        //System.out.println("构造代码块执行!");
        // 这里能够使用this,这说明,构造代码块执行之前对象已经创建好了,并且系统也完成了默认赋值。
        //System.out.println(this.name);

        for(int i = 0; i < 10; i++){
            System.out.println("iiiiiiiiiii = " + i);
        }

    }

        所有对象被创建之后,都会立刻执行一遍构造代码块。

四. this关键字:

1.基本使用:this出现在实例方法中,代表当前对象。

2.语法:this.
         this本质上是一个引用,该引用保存当前对象的内存地址。 通过“this.”可以访问实例变量,可以调用实例方法。
        this存储在:栈帧的局部变量表的第0个槽位上。
        this. 大部分情况下可以省略,不过用于区分局部变量和实例变量时不能省略。

3.限制:this不能出现在静态方法中。(this不能出现在有static的地方)

4.特殊用法:“this(实参)”语法: 只能出现在构造方法的第一行。 通过当前构造方法去调用本类中其他的构造方法。 作用是:代码复用。

eg:

/**
 * 需求:定义一个日期类,代表日期。日期属性包括:年月日。
 * 提供两个构造方法,一个是无参数构造方法,当通过无参数构造方法实例化日期对象的时候,默认创建的日期是1970-01-01
 * 另一个构造方法三个参数,通过传递年月日三个参数来确定一个日期。注意属性要提供封装。
 *
 * this(实参):
 *      1. 通过这种语法可以在构造方法中调用本类中其他的构造方法。
 *      2. 作用:代码复用。
 *      3. this(实参); 只能出现在构造方法第一行。
 */
public class Date {
    /**
     * 年
     */
    private int year;
    /**
     * 月
     */
    private int month;
    /**
     * 日
     */
    private int day;

    public Date() {
        /*
        this.year = 1970;
        this.month = 1;
        this.day = 1;
         */

        // 不要这么写,这样会导致再创建一个新的对象。
        //new Date(1970,1,1);

        // 不会创建新对象。只是通过一个构造方法去调用另一个构造方法。
        this(1970,1,1);

        System.out.println("日期创建成功!~");
    }

    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

        不难看出,此时第一个构造方法不再需要重复写一遍,而是可以通过这个语法糖直接利用下面的有参构造方法即可。

五.static关键字:

        回顾:变量分为三种:


public class Main {
    public static void main(String[] args) {

            //局部变量:在方法体中定义的变量,即局部变量(它只在当前大括号内有效)
            int a = 1;

    }

    //在类体中定义的变量都是成员变量,其中又分为实例变量和静态变量

            //这种普通的即使实例变量
            int b = 0;
            //如果前面有static修饰符,则是静态变量
            static int c = 0;
}

        在类体中定义的变量即是成员变量,我们来辨析一下其中的实例变量和静态变量

                1.若是类体中普通的变量,则是实例变量——这种变量的特点是类中每个对象的此变量都不尽相同,如在中国人这个一个类中每个人的身高不一样,因此应该设为实例变量。
                2.若是变量前面存在static进行修饰,则是静态变量——这种变量的特点是类中每个对象的此变量都完全相同,如在中国人这个一个类中每个人的国籍都一样,因此应该设为静态变量。(这种变量没必要每个人都不同,单独保存,因此去设为静态变量可以大幅减少内存)

1.静态变量/方法的介绍: 

        static修饰的变量叫做静态变量。当所有对象的某个属性的值是相同的,建议将该属性定义为静态变量,来节省内存的开销。同理,static修饰的方法叫做静态方法。
        静态变量在类加载时初始化,存储在堆中。

       

2.静态变量/方法的调用:

        所有静态变量和静态方法,统一使用“类名.”调用。虽然可以使用“引用.”来调用,但实际运行时和对象无关,所以不建议这样写,因为这样写会给其他人造成疑惑。——因为静态变量方法是所有人都有的,自然应该用类名.来调用

        使用“引用.”访问静态相关的,即使引用为null,也不会出现空指针异常。

        静态方法中不能使用this关键字。因此无法直接访问实例变量和调用实例方法。(这也是很自然的,因为静态方法中压根就没有对象,自然无法调用实例变量和实例方法,也无法用this指向当前对象)

3.静态代码块:

/*
 * 1.语法格式:
 *      static {
 *
 *      }
 *
 * 2. 静态代码块什么时候执行?执行几次?
 *      静态代码块在类加载时执行,并且只执行一次。
 *
 * 3. 静态代码块可以编写多个,并且遵循自上而下的顺序依次执行。
 *
 * 4. 静态代码块什么时候使用?
 *      本质上,静态代码块就是为程序员预留的一个特殊的时间点:类加载时刻
 *      如果你需要再类加载时刻执行一段程序的话,这段代码就可以写到静态代码块当中。
 *      例如,有这样一个需求:请在类加载时,记录日志。那么记录日志的代码就可以编写到静态代码块当中。
 */
public class StaticTest01 { // com.powernode.javase.oop13.StaticTest01

    // 实例方法
    public void doSome(){ // com.powernode.javase.oop13.StaticTest01.doSome
        System.out.println(name);
    }

    // 实例变量
    String name = "zhangsan"; // com.powernode.javase.oop13.StaticTest01.doSome.name

    // 静态变量
    static int i = 100;

    // 静态代码块
    static {
        // 报错原因:在静态上下文中无法直接访问实例相关的数据。
        //System.out.println(name);
        // 这个i可以访问,是因为i变量是静态变量,正好也是在类加载时初始化。
        System.out.println(i);
        System.out.println("静态代码块1执行了");
        // j无法访问的原因是:程序执行到这里的时候,j变量不存在。
        //System.out.println(j);

        System.out.println("xxxx-xx-xx xx:xx:xx 000 -> StaticTest01.class完成了类加载!");
    }

六.设计模式

1.设计模式是什么:

        设计模式(Design Pattern)是一套被广泛接受的、经过试验验证的、可反复使用的基于面向对象的软件设计经验总结,它是软件开发人员在软件设计中,对常见问题的解决方案的总结和抽象。设计模式是针对软件开发中常见问题和模式的通用解决方案

2.GoF设计模式的分类:

创建型:主要解决对象的创建问题
结构型:通过设计和构建对象之间的关系,以达到更好的重用性、扩展性和灵活性
行为型:主要用于处理对象之间的算法和责任分配

3.单例模式

(GoF23种设计模式之一,最简单的设计模式:如何保证某种类型的对象只创建一个)

        有两种方法——饿汉式和饱汉式

//饿汉式:类加载时就创建对象。
public class Singleton {
    private static Singleton instance = new Singleton(); // 在类加载的时候就创建实例
    private Singleton() {}  // 将构造方法设为私有化
    public static Singleton getInstance() {  // 提供一个公有的静态方法,以获取实例
        return instance;
    }
}


//懒汉式:第一次调用get方法时才会创建对象。
public class Singleton {
    private static Singleton instance; // 声明一个静态的、私有的该类类型的变量,用于存储该类的实例
    private Singleton() {} // 将构造方法设为私有化
    public static Singleton getInstance() { // 提供一个公有的静态方法,以获取实例
        if (instance == null) { // 第一次调用该方法时,才真正创建实例
            instance = new Singleton(); // 创建实例
        }
        return instance;
    }
}

七.面向对象三大特征之继承

1.继承作用:

        基本作用:代码复用
        重要作用:有了继承,才有了方法覆盖和多态机制。

2.继承在java中如何实现:

         [修饰符列表] class 类名 extends 父类名{}

        extends翻译为扩展。表示子类继承父类后,子类是对父类的扩展。
        继承相关的术语:当B类继承A类时 A类称为:父类、超类、基类、superclass B类称为:子类、派生类、

3.注意:

        subclass Java只支持单继承,一个类只能直接继承一个类。
        Java不支持多继承,但支持多重继承(多层继承)。——即可以a继承b,b再去继承c
        子类继承父类后,除私有的不支持继承、构造方法不支持继承。其它的全部会继承。
        一个类没有显示继承任何类时,默认继承java.lang.Object类。

4.方法覆盖:

4.1. 什么时候考虑使用方法重写:

        当从父类中继承过来的方法,无法满足子类的业务需求时。

4.2. 当满足什么条件的时候,构成方法重写:

        条件1:方法覆盖发生在具有继承关系的父子类之间。
        条件2:具有相同的方法名(必须严格一样)
        条件3:具有相同的形参列表(必须严格一样) 
        条件4:具有相同的返回值类型(可以是子类型)

4.3. 关于方法覆盖的细节:

        a.在java语言中,有一个注解,这个注解可以在编译阶段检查这个方法是否是重写了父类的方法。 即 @Override注解,用来标注方法,被标注的方法必须是重写父类的方法,如果不是重写的方法,编译器会报错。 且@Override注解只在编译阶段有用,和运行期无关。
        b.如果返回值类型是引用数据类型,那么这个返回值类型可以是原类型的子类型
        c.私有的方法,以及构造方法不能继承,因此他们不存在方法覆盖。
        d.方法覆盖针对的是实例方法。和静态方法无关。方法覆盖针对的是实例方法。和实例变量没有关系。

八.多态:

        分为两个部分,分别是多态的语法(how) 和 多态的作用(why)

1.多态的基础语法:

1.1引入:向上类型转换 和 向下类型转换:

java允许具有继承关系的父子类型之间的类型转换。(无论是向上转型还是向下转型,前提条件是:两种类型之间必须存在继承关系。这样编译器才能编译通过。)

        向上转型(upcasting):子-->父      子类型的对象可以赋值给一个父类型的引用。

         /*
        向上转型(upcasting):
            1. 子 --> 父
            2. 也可以等同看做自动类型转换
            3. 前提:两种类型之间要有继承关系
            4. 父类型引用指向子类型对象。这个就是多态机制最核心的语法。
         */
        Animal a2 = new Cat();


        向下转型(downcasting):父-->子 父类型的引用可以转换为子类型的引用。但是需要加强制类型转换符。

        /*
        假如现在就是要让a2去抓老鼠,怎么办?
            向下转型:downcasting(父--->子)
        什么时候我们会考虑使用向下转型?
            当调用的方法是子类中特有的方法。
         */
        Animals a2 = new Animals();
        Cat c2 = (Cat)a2;
        c2.catchMouse();

1.2多态:

        多态出现在父类型引用指向子类对象。Animal a = new Cat(); a.move();

         /*
        向上转型(upcasting):
            1. 子 --> 父
            2. 也可以等同看做自动类型转换
            3. 前提:两种类型之间要有继承关系
            4. 父类型引用指向子类型对象。这个就是多态机制最核心的语法。
         */
        Animal a2 = new Cat();

        //Animal a3 = new Bird();

        /*
        java程序包括两个重要的阶段:
            第一阶段:编译阶段
                在编译的时候,编译器只知道a2的类型是Animal类型。
                因此在编译的时候就会去Animal类中找move()方法。
                找到之后,绑定上去,此时发生静态绑定。能够绑定
                成功,表示编译通过。
            第二阶段:运行阶段
                在运行的时候,堆内存中真实的java对象是Cat类型。
                所以move()的行为一定是Cat对象发生的。
                因此运行的时候就会自动调用Cat对象的move()方法。
                这种绑定称为运行期绑定/动态绑定。

            因为编译阶段是一种形态,运行的时候是另一种形态。因此得名:多态。
         */
        a2.move();

        什么是多态——多种形态,编译阶段一种形态,运行阶段另一种形态,因此叫做多态。

1.3经典错误 和 解决方法:

        向下转型时,使用不当,容易发生类型转换异常:ClassCastException。

        在向下转型时,一般建议使用instanceof运算符进行判断来避免ClassCastException的发生。 

        /*
        instanceof 运算符的语法规则:
            1. instanceof运算符的结果一定是:true/false
            2. 语法格式:
                (引用 instanceof 类型)
            3. 例如:
                (a instanceof Cat)
                    true表示什么?
                        a引用指向的对象是Cat类型。
                    false表示什么?
                        a引用指向的对象不是Cat类型。
         */
          
        Animals x = new Animals();


        // 做向下转型之前,为了避免ClassCastException的发生,一般建议使用instanceof进行判断
        System.out.println(x instanceof Bird);

        if(x instanceof Bird){
            System.out.println("=========================");
            Bird y = (Bird)x;
        }

       

2.多态有什么用:

2.1 ocp设计原则:

        开闭原则 (Open-Closed Principle,OCP):一个软件实体应该对扩展开放,对修改关闭。即在不修改原有代码的基础上,通过添加新的代码来扩展功能。(最基本的原则,其它原则都是为这个原则服务的。)

2.2 多态作用:

        建议直接看原视频:156-Java零基础-第三章多态在开发中的作用_哔哩哔哩_bilibili

        多态倡导的是抽象编程,完美符合ocp原则,可以实现对扩展开放,对修改关闭。面对抽象而非面对具体编程可以降低耦合度,提高扩展性

2.3 方法覆盖的回顾

        多态即 编译时绑定的是父类的方法,而运行时则运行的是子类的方法(子类的方法把父类方法给方法覆盖了) 因此方法覆盖和多态一起使用才是有意义的

        a.方法覆盖只针对实例方法,不能对静态方法使用。(因为静态方法本身就是没有对象参与的,而多态需要对象的参与,因此方法覆盖和多态没有关系)

        b.方法覆盖说的是实例方法,和实例变量不管。——对于变量而言 编译时绑定的是谁,运行时就应该是谁。

九.super关键字

        super关键字和this关键字对比来学习。this代表的是当前对象。super代表的是当前对象中的父类型特征。 super不能使用在静态上下文中。(很显然,因为super也是针对的是当前对象,而静态上下文就没有对象自然无法使用super)

        如图:this指向的是这个对象有的元素,而super又指的是这个对象中从父对象那里继承过来的特征(属性和方法)

        super.”大部分情况下是可以省略的。但当父类和子类中定义了相同的属性(实例变量)或者相同方法(实例方法)时,如果需要在子类中访问父类的属性或方法时,super.不能省略。
        this可以单独输出,super不能单独输出。
        super(实参); 通过子类的构造方法调用父类的构造方法,目的是为了完成父类型特征的初始化。 当一个构造方法第一行没有显示的调用“super(实参);”,也没有显示的调用“this(实参)”,系统会自动调用super()。因此一个类中的无参数构造方法建议显示的定义出来。
        super(实参); 这个语法只能出现在构造方法第一行。和this(实参)同理,因此这俩只能出现一个且必须要出现一个。(若没有出现,则是一个隐藏的super())
        在Java语言中只要new对象,Object的无参数构造方法一定会执行。(因为父类往上走都是object)

十.final关键字:

        final修饰的类不能被继承
        final修饰的方法不能被覆盖
        final修饰的变量,一旦赋值不能重新赋值
        final修饰的实例变量必须在对象初始化时手动赋值
        final修饰的实例变量一般和static联合使用:称为常量(常shi全大写的)
        final修饰的引用,一旦指向某个对象后,不能再指向其它对象。但指向的对象内部的数据是可以修改的。        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值