面向对象三大特点——封装、继承和多态

学过面向对象的我们都知道,面向对象有三大特点为——封装、继承和多态。

封装

之前在介绍函数时我们说一个函数就是封装的体现,封装就是将一类事物进行封装成一个类,或者封装具有独立功能的代码块,封装的好处就是明确了内外,内部的东西外部无需知道,只要创建一个类的实例对象进行调用其中的方法即可。目前我们所见的封装体现有:循环、函数、类、 private(用该关键词修饰的函数和对外界是不可被访问的,且也不会被继承,被修饰的成员变量对外界也是不被访问的,需要创建相应的修改器和访问器对成员函数进行修改和访问)

继承

继承是类与类之间的关系,当我们在定义若干类的时候发现,这些类有共同的属性和行为,则可以将这些共同的属性和行为单独抽取出来,放到另一个类中,那么这个类可以称之为其他类的父类,而其他类称之为子类。父类和子类之间是继承关系用“extends”表现,即:

表示:
    class 父类{

    }
    class 子类 extends 父类{

    }

注意:继承必须是同种类且符合常理的,代码上的体现仅仅是属性和行为重复(前提)

class Demo01{
    public static void main(String[] args){
        Worker w=new Worker();
        System.out.println(w.age);
        w.eat();
    }
}

class Person{  //定义一个person类作为不同人的类型的父类
    int age;   //每个人都有一个年龄
    String name;  //每个人也都有相应的姓名
    Person(){     //默认的无参构造函数

    }

    Person(String name){    //带姓名的参数的构造函数
        System.out.println("Person construction");
    }

    void eat(){  //每个人都要吃饭
        System.out.println("三餐");
    }
}

class Worker extends Person{    //工人属于人类一种类型,可继承person类中的属性
    Worker(){
        super();  //表示调用父类的无参构造函数,如果这里不写表示隐藏,但必须在第一句
        System.out.println("Worker construction");
    }
    
    void work(){}  //工人有他自己的行为是工作
}

class Student extends Person{  //学生也属于人类一种类型,也可继承person类中的属性
    Student(){  //学生的构造函数,这里没有super()不代表就没有调用,而是隐藏了
       System.out.println("Student construction");
    }
    void study(){}  //学生有他自己的行为是学习
}

如上代码中,我们看到工人有姓名、年龄等属性和吃行为,学生也有这些属性和行为,如果分别在工人类和学生类中写的话,也会造成代码的冗余,因此我可以将公共属性抽取出来重新定义一个符合常理的类就是person类,那么用继承则两个子类就都可以访问到父类中的属性了。其中我们看到子类在调用构造函数创建对象时,构造函数有一个super()方法,这个方法是调用父类的构造函数,就算不写也会有一个隐藏的这个方法,那么为什么要调用父类的构造函数呢?

是因为在继承父类的属性时,我们并不是将父类的属性都移动或者复制到子类中,而是通过对象去访问父类中的属性和方法,也就是说子类中并没这些属性和方法而是可以访问到这些方法和属性,因此在子类创建对象的时候要对相应的成员变量进行默认初始化,那么父类也要做相应的初始化为子类提供后续的操作

那么之前介绍构造函数的时候说到,调用构造函数就意味着创建了对象,那么这里子类创建一个对象意味着父类也要跟着创建一个对象吗?显然不是的,这里只是为子类提供成员变量的初始化,并没有创建父类的对象,所有除非new一个父类的对象,否则子类创建它的对象是不会影响到父类是否创建对象的。

在前面我们说到,继承要符合常理,现在我们来看这样一段代码:

public class  Dog{

   int age;    //狗的年龄属性
   String name;   //狗的名字属性
   String type;   //狗的类型属性
     
   void eat(){};   //狗吃的行为
   void speak(){   //狗叫的行为
     System.out.println("汪汪汪") 
    };
 }

这里定义了一个Dog类,它也有相应的名字和年龄属性和吃的行为,从代码上我们也可以让它继承person类,但是继承是要符合常理的,人类不可能生出狗来啊!!!so,这里可以定义一个Animal类将这些属性和行为抽取出来,让Dog类啊,Cat类啊等等去继承它,这就很符合常理了。

继承的特性:

1.单继承和继承体系

       继承相当于就是父亲和孩子的关系,一个父亲可能有多个孩子,但是一个孩子仅有唯一的一个父亲,那么由此说明,在Java的继承体系中,只能单继承一个子类只可继承一个父类,但是一个父类可以被多个子类去继承,如A继承B,C继承B,此时A不能继承C或者其他类。那么如果我们即想要A访问到B中的属性和方法,又想要A访问到C中的属性和方法怎么办呢?在Java中是有继承体系这一说的,相当于人类的“族谱”,即C继承B,A再继承C这样是可以的。因此要记住,继承之中是is a 的关系

2.子父类中构造函数的特点
      子类在创建对象的时候会调用父类的相对应的构造函数,因此super()必须在第一句可以默认不写,但是我们说在表示当前对象时,this()必须在第一句,那么这两个关键字的调用不就冲突了?显然不是的,在前面介绍构造函数时,说构造函数之间不能相互调用,所以至少有一个构造函数第一句不是this(),这样就不会冲突了。如果一旦父类没有无参的构造函数,那么super()就不能用了,应该显示调用相应的代参的super(...)方法。默认无参构造函数第一句默认是super()
       为什么要调用父类构造函数呢?是为子类的属性(继承而来的)进行初始化,但是不代表父类创建了对象。
       父类的构造函数如果被private私有化了,则子类无法创建对象 只能是继承;如果一个类的所有构造函数都private 意味着子类不能创建对象,一般还需要在这个类之前加final 表示真正的不能被继承,因此我们今后在创建类的时候 最好将无参构造函数写出来

3.子父类中成员变量的特点:
    查找变量的顺序:1).在函数当中找(局部变量)
                                 2).在对象当中找(成员变量)
                                 3).在该类的静态方法区找(静态变量)
                                 4).在父类空间里找(父类的成员变量)
                                 5).在父类的静态方法区找(父类的静态变量)

   子类在没有定义要调用的属性时,会自动调用父类的该属性
    如果子父类中有重名属性,则优先调用子类的

4.子父类中成员函数的特点:
     子类对象调用函数先调用自己,再调用父类
     如果子父类中存在函数重名的情况为函数的重写,至于子类怎么解决,都是子类的问题,子类是指拿到了父类中的函数声明。
     子类中的成员函数可以基于父类的内容进行升级和降级
     如果父类的函数已经被private私有化了,则子类不能重写;重写函数,子类的权限必须大于等于父类的权限,子类的返回值和参数列表必须和父类一样。

5.所有类没有明显的继承关系,都有一个最终的父类为object,这个类是所有类的最终父类,它没有构造函数(可以翻看源码)

6.super()和this()

   this表示当前对象的引用
   super(...) 调用父类相对应的代参构造函数
   this(...)  调用当前类中相对应的构造函数
   super.XXX super.XXX() 调用父类的属性或函数
   this.XXX this.XXX()   调用当前对象的属性或函数

多态

多态体现的前提要有继承,状态的自动转换只能向上转,可以向下转(但必须进行强制类型转换),即同一个事物在不同的场合具有不同的形态
多态的最常用的体现是父类的引用指向子类对象,如:

class Demo01{
    public static void main(String[] args){
        Dog dog=new Dog();  //狗类创建一个它自己dog对象
        Cat cat=new Cat();    //猫类创建一个它自己cat的对象
        dog.eat();      //狗类对象调用自己的eat方法
        dog.lookHome();  //狗类对象调用自己的lookHome方法

     //多态的体现

        Animal a1=dog;    //创建一个父类的引用将子类的对象引用给它,这里给的就是子类对象指向的地址
        Animal a2=cat;
        a1.eat();     //这里父类调用的是父类自己的eat方法,而不是子类中的eat方法,它无法访问到子类中的属性和方法
       // a1.lookHome();  这里是错误的调用方式

       // Cat cat2=(Cat)a1;  这里不被允许,是因为a1现在还是一个狗的对象,将它强制转换为猫类对象是不符合道理的,当然在程序中也不允许这样写
        Cat cat2=(Cat)a2;  //将父类引用强制转换为相应的子类对象引用,前面要加相应的强制转换类
    }
}
class Animal{    //创建一个动物类的父类对象
    public abstract void eat(){};
    public void sleep(){
        System.out.println("闭眼睡觉....");
    }
}
class Cat extends Animal{  //定义一个猫类让它继承动物类
    public void  eat(){
        System.out.println("吃鱼....");
    }
    public void catchJerry(){
        System.out.println("捉老鼠....");
    }
}
class Dog extends Animal{  //定义一个狗类让它继承动物类
    public void  eat(){
        System.out.println("吃骨头....");
    }
    public void lookHome(){
        System.out.println("看家....");
    }
}

多态的特点:

1.多态中成员变量的特点
    父类只能访问父类中的成员变量和函数不能访问子类中的属性和函数

2.多态中成员函数的特点
    如果子类没有重写父类的函数,则父类的引用也不能调用子类的函数
    子类当中的函数有:继承而来的函数, 继承重写的函数, 以及特有的函数
    因此父类可以调用继承而来和继承重写的函数,但是唯独不能调用子类特有的函数

至此我们的面向对象三大特点就介绍完了,希望对你有所帮助!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章