面向对象的思想

JAVA是一门完全的面向对象的语言,这是一种思想,即把各种事物当成一种对象,然后对象有自己的属性和行为,这种认知方式也比较适合人的认知。

为了实现这个思想,java具备三大特征:封装 、继承 和 多态。

  1. 封装

封装就是把事物所具有的属性和行为抽象出来,封装到一个类里面。所以类是对事物的封装,是抽象的,而对象才是具体的,是实例。

所以,在分析问题时,将某事物进行分析抽象并生成类的是哦户,就是一个封装的过程。类就是一种模版,在需要的时候,可以按照这个模版生成具体对象,而且可以是重复的。

  • 对象内存图

之前我们知道,main函数等各种方法在程序运行的时候,是存在方法栈中的,而类的定义是在方法区中,生成的对象数据是存在堆内存中的。

Java 父类调用子类的属性 java父类对象调用子类方法_多态


如图所示,当函数执行到需要生成Phone类的p对象时,会根据方法区中Phone.class的类结构,在堆内存中生成实例对象p,然后把p的地址返回给函数中的p这个引用变量,之后调用p的时候,都是通过p存储的地址去找数据的。

多个对象时内存图:


Java 父类调用子类的属性 java父类对象调用子类方法_父类引用指向子类对象_02


当生成多个对象时,由于类中的方法大家是共同都有的,所以为了节约内存,就在生成对象时只生成一个方法标记,不用每个对象都生成一个完整的方法体占用内存。当需要的时候,在根据方法标记调用方法即可。


Java 父类调用子类的属性 java父类对象调用子类方法_父类引用指向子类对象_03


成员变量和局部变量

成员变量是类中对属性的封装,而局部变量是对类中行为封装内的变量,是不同层次,不同作用域的两个概念。

成员变量是跟着类的对象走的,它的生命周期是跟着对象实例的。而局部变量是只限于对象中的函数内部的,当出了函数体,局部变量就没了,也就是说局部变量生命周期是跟着函数走的,当函数执行完毕就销毁了。

  • 封装优化

我们虽然实现了封装,但在应用的时候,从安全角度考虑,类中的属性不应该被任何人直接访问,有些不需要泄漏的信息,都可以限制在类的内部使用。这就需要不同的权限,在类中,对属性和函数提供了集中不同级别的权限:

private,即私有的,只能在类的内部调用,不能通过类名.变量名的方式调用;

protected:受保护的,

public:公共的,即可以直接通过类名进行调用的。

通常我们处理方式是,属性不能直接访问,而是通过get 和 set方法进行间接使用,然后部分函数功能私有,部分提供外部调用的函数设置公有。

其次,还有一些方便使用的优化,

1)this关键字:可以在类中使用,通俗讲指的是其本身,原理就是this存储的是实例化的对象本身的地址,所以通过http://this.xxx调用的都是对对象自身的调用。

2)构造函数:可以在对象生成的时候,就完成类的初始化功能。默认是无参数,即不赋值,只初始化内存空间。可以进行重载,定义多个构造函数,然后根据不同需求进行初始化。


Java 父类调用子类的属性 java父类对象调用子类方法_子类_04


3)JavaBean标准规范:是对类的一种设计规范,要求类必须是公共的、具体的,要有无参数的构造函数,提供用于操纵成员变量的get和set方法。


Java 父类调用子类的属性 java父类对象调用子类方法_父类引用指向子类对象_05


  1. 继承

继承实际是一种进一步的封装,把公共的提取出来,作为父类,被大家共有,把特有的写到子类内部。这样就进一步降低冗余,提高复用率。

  • 继承的关键字是extends

格式很简单,在子类的定义时,后面加上extends 父类名即可实现继承。

  • 继承后的内容

成员变量重名时,要调用父类的成员变量时,要用super关键字,super指向的时父类对象地址,与this类似但功能不同,this是指向对象自身地址。

当成员方法重名时,子类中的方法就对父类的同名方法进行了重写override,重写是直接覆盖掉原函数。与重载不同,重载是改变参数生成并立的多个同名函数,重写是直接覆盖,只有一个函数能用。但要注意,重写时,返回值类型/函数名/参数列表都要与父类保持一致。

  • 继承后的构造函数与初始化顺序

继承后,子类的构造函数中会有super();这句话表明了父类与子类的初始化关系。即先对父类进行初始化,再对子类进行初始化。如图


Java 父类调用子类的属性 java父类对象调用子类方法_父类_06


在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。

  • JAVA只支持单继承

一个类只能有一个父类,即对类来说,只能继承一个类,不能同时继承多个类。

但多个类之间可以一一对应,实现多层次的继承,同时,一个父类可以被多个子类继承,这些是允许的。

  1. 多态

通过继承有了父类与子类,多个子类对父类的复方进行重写,就是对同属一个父类的子类们来说,同样的一个行为(如鸣叫)却有了不同的表现形式,每个子类的表现都是独立的个体。这种多样化就是多态的体现。

简言之,多态就是同一种行为的多种表现形态。

  • 多态的前提

1 . 继承或者实现【二选一】

2. 方法的重写【意义体现:不重写,无意义】

3. 父类引用指向子类对象【格式体现】

  • 多态的体现

多态就是在生成子类对象的时候,将类型转换为父类类型,这就叫父类引用指向子类对象。

如Animal是父类,Cat是子类,用多态形式创建对象

Animal cat = new Cat();

  • 多态的好处

之所以改为父类的类型,是因为其都有共同的方法,只是子类重写了父类方法。当调用这些方法时,利用其同属于父类类型的共同之处,可将这个类型抽取出来,作为方法的参数,即可实现多样化的表现方式。节省代码,减少冗余,提高复用率。如:


public class Test {
    public static void main(String[] args) {
        // 多态形式,创建对象
        Cat c = new Cat(); 
        Dog d = new Dog();
        // 调用showCatEat
        showCatEat(c);
        // 调用showDogEat
              showDogEat(d);
        /*
        以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代
        而执行效果一致
        */
        showAnimalEat(c);
        showAnimalEat(d);
    }
    public static void showCatEat (Cat c){
        c.eat();
    }
    public static void showDogEat (Dog d){
        d.eat();
    }
    public static void showAnimalEat (Animal a){
        a.eat();
    }
}


通过示例可以看到,不论有多少种动物,其都有一个共同的行为,就是吃eat(),通过抽取父类类型作为方法参数,只需生成不同的子类对象,传递实例化对象,就可用一个函数匹配所有子类的对吃的行为的调用。

  • 类型转换

我们明白了多态在实现上就是一种类型转换,我们称从子类转换为父类的过程为向上转换;从父类向子类转换的过程为向下转换。

向上转换是默认的,向下转换需要强制类型转换。

在强制类型转换的时候,就会有出错的风险,比如转换的是没有继承关系的类,就会发生异常。java提供isntanceof关键字给引用变量做类型的校验,变量名 instanceof 数据类型,如果变量属于该类型返回true,否则返回false。

ps:之前写过一遍,结果草稿的自动保存出了问题,只能重写一次,没第一次详细,属实有点心态崩。