Java中的封装、继承、多态

时间:2022-05-26 00:24:54

封装

如何理解面向对象这篇文章中,提到所谓的封装就是“功能都给你做好了,你不必去理解它是怎么写出来的,直接使用即可。”。但你得清楚一点,那就是这句话是相对于使用者来说的,而作为开发者,封装就得我们自己来干。

那么作为开发者,我们应该如何去封装呢?其实你应该反过来问,他们应该如何去使用,这样一想会简单很多,作为使用者,自然是希望越简单越好,也就是说,一些复杂的东西,我们不应该让使用者去操作,那也就是说我们应该把复杂的,以及不必要的参数给它封死,不让使用者去操作。

为什么不让使用者去操作?

因为往往使用者是不太专业的,如果暴露太多的接口给他们,就很有可能出现一些稀奇古怪的问题,好比一个不会做水煮鱼的,如果让他去做那肯定是不好的,那怎么办,给他买一包水煮鱼的调料,让他直接放进锅里就好,这样就减少了不必要的麻烦。我们封装程序也是这样,把复杂的代码封死,不让操作者去操作,以免出错。

比如下面这个例子:

class Average{
    private int[] fractions = new int[3]; //分数
    private int average = 0; //平均分
    public void setFraction(int[] fraction){
        fractions = fraction;
    }
    public double getAverage(){
        for(int cell:fractions){
            average += cell;
        }
        return (double) average / fractions.length;
    }
}

class app{
    public static void main(String[] args){
        int[] a = {50,40,50};

        Average average = new Average();
        average.setFraction(a); //设置分数
        double n = average.getAverage(); //获取平均分
        System.out.println(average.average); //报错
        System.out.println(n); //46.0
    }
}
提示:Java通过private设置私有变量,通过public将变量设置成公开的。

这里我们之所以将分数和平均分设置成私有变量是为了防止使用者误操作,而且也不必让使用者知道有这么一个变量,只需要让使用者知道怎么去设置分数,和获取平均分就好了。

当然这只是一个很基础的封装,如果想封装出一个好的程序,还得多费一些心思。

继承

拿猫和狗来说,它们都是动物,而且它们有一些共同点,比如:名字,年龄,声音,吃等。把这段话写成代码就是下面这个样子。

class Animal{
    private String name;
    private int age;

    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
}

class Cat extends Animal{
    public void voice(){
        System.out.println(super.getName() + " 喵");
    }
    public void eat(){
        System.out.println(super.getName() + " fish");
    }
}

class Dog extends Animal{
    public void voice(){
        System.out.println(super.getName() + " 汪");
    }
    public void eat(){
        System.out.println(super.getName() + " Bone");
    }
}

class app{
    public static void main(String[] args){
        Cat cat = new Cat();
        cat.setName("猫大王"); //Cat本身没有setName方法,但是它的基类有,所以java解析器会到Cat的基类那里拿
        cat.voice();

        Dog dog = new Dog();
        dog.setName("大黑");
        dog.setAge(13);
        dog.voice();
        System.out.println(dog.getName() + dog.getAge());
    }
}

------Output------
猫大王 喵
大黑 汪
大黑13
提示:Java通过extends关键字来实现继承,父类中通过private定义的变量和方法不会被继承,也就是你不能在子类中直接操作父类通过private定义的变量以及方法。

在上面代码中,我们可以看到,CatDog并没有定义setNamesetAgegetNamegetAge方法,但是我们依然可以在CatDog类中使用,这是因为我们通过extends关键字继承了Animal类,因此在Animal中定义的变量和方法,我们可以在子类中直接使用,除private定义的变量和方法。

反过来说,姓名和年龄是猫和狗的基本信息也是它们的共同特性。

不过得注意一下,一个类只能有一个直接父类,也就是在一个类中只能使用一次extends,虽然说它只能有一个直接父类,但是如果它的父类去继承了其他的类,那么在父类中继承来的变量和方法,也是能被子类所使用的。

重写父类方法或变量

一般重写父类方法,是因为你把猫当成是一个基类,而将狗继承自猫类。看似这很好笑,但如果你去翻翻你的代码,这种情况多如牛毛。当然,如果你不需要继承,那就另说了。那么如果碰到这种情况,我们怎么重写基类呢?很简单,在子类中定义一个和父类中一样的方法,如下面这样:

class Animal{
    private String name;
    private int age;

    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
}

class Dog extends Animal{
    public String getName(){
        return super.getName() + "2";
    }
    public void voice(){
        System.out.println(super.getName() + " 汪");
    }
    public void eat(){
        System.out.println(super.getName() + " Bone");
    }
}

class app{
    public static void main(String[] args){
        Dog dog = new Dog();
        dog.setName("大黑");
        System.out.println(dog.getName()); //执行的是Dog中的getName方法
    }
}
提示:通过super可以在子类中直接调用父类的方法以及变量,通过this调用当前类。

我觉得把这叫做重写不太好,因为如果从本质来讲,它不算重写,只是Java寻找变量以及方法的规则罢了。Java会先看一下,自己身上有没有某个变量或方法,如果没有,它会接着到父类中找,如果父类中还是没有,那么它又会到更上一级中找,如果一直找上去都没有,那么才报错。

在重写父类时,需要注意一下,重写时,方法的返回值类型必须和父类中定义的一致,比如下面这样就会报错

class Animal{
    private String name;

    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
}
class Dog extends Animal{
    public int getName(){ //和父类中的getName返回值不同,报错
        return 123;
    }
}

class app{
    public static void main(String[] args){
        Dog dog = new Dog();
        System.out.println(dog.getName());
    }
}

另外还需要注意,如果重写时,和父类中的参数不一致,则会发生意想不到的事,比如下面这个

class Animal{
    private String name;

    public void setName(String name){
        this.name = name;
    }
    public String getName(String hello){
        return this.name + hello;
    }
}

class Dog extends Animal{
    public String getName(){
        return "123";
    }
}

class app{
    public static void main(String[] args){
        Dog dog = new Dog();
        dog.setName("大黑");
        System.out.println(dog.getName("hello"));
    }
}

------Output------
大黑hello

可以看到当我们给getName传达了参数时,执行的是Animal中的方法,而非Dog中的getName方法,也就是说如果参数不一致最后执行的可能就不是重写的那个方法。另外也不可将父类公开的方法或变量改成私有(如将public改成private),否则也会报错,我估计是Java有一套覆盖规则,如果没有达到条件就不会进行覆盖。

多态

先来几个例子,再讲理论

class Animal{
    public int age = 5;

    public int getAge(){
        return age;
    }
    
}

class Dog extends Animal{
    public int age = 8;

    public int getAge(){
        return age + 2;
    }
}

class app{
    public static void main(String[] args){
        Animal dog = new Dog();
        System.out.println(dog.age);
    }
}
------Output------
5

Animal dog = new Dog();这么一句话,可以发现它们的类型并不一样,但却可以正常运行,之所以可以运行是因为,Dog类是Animal的子类,而父类是包括子类的。我们说动物,那么狗是不是就是动物中的一员呢,这是肯定的,而这里之所以如果运行也正是这个理。

不过需要注意一下,通过这种方式创建的对象,在获取实例变量时,获取到的是父类中的实例变量,如果是方法,则看子类中是否存在和父类中同名的方法,如果存在则使用子类中的方法,但是如果子类中有某个方法,而父类中没有,那么就会报错。如下这段代码就会报错

class Animal{
    public int age = 5;

    public int getAge(){
        return age;
    }
    
}

class Dog extends Animal{
    public int age = 8;

    public int getAge(){
        return age + 2;
    }
    public setAge(int a){
        this.age = a;
    }
}

class app{
    public static void main(String[] args){
        Animal dog = new Dog();
        System.out.println(dog.setAge(5));
    }
}

因为父类中没有setAge这个方法,因此会报错。

从上面这几段代码,可以看出使用多态也是有缺陷的,添加新方法都没有效果,只有覆盖才有效果。我们可以把它总结成如下这句话:

如果一个Dog类型的对象,它的引用却是其他类型,那么则不能使用Dog类型中的方法,除非引用类中也有这个方法。

这里所谓的多态,在程序中你可以理解成,一个方法,它可以有不同的效果,那怎么实现不同的效果呢?在java中通过切换类型来实现(不一定正确)。

多态有什么用?

还是再来看几个例子吧

class Animal{
    public int age = 5;

    public int getAge(){
        return age;
    }
    
}

class Dog extends Animal{
    public int getAge(){
        return age + 2;
    }
}

class Cat extends Animal{
    public int getAge(){
        return age + 3;
    }
}

class app{
    public static void main(String[] args){
        Animal dog = new Dog();
        Animal cat = new Cat();

        System.out.println(dog.getAge());
        System.out.println(cat.getAge());
    }
}
------Output------
7
8

可以看到,它会根据自身执行不同的方法。不过话说回来,这并不能代表什么,毕竟我们按照正常情况来创建,效果也可以一样,不过还真有它的用武之处,比如下面这段代码

class Animal{
    public int age = 5;

    public int getAge(){
        return age;
    }
    
}

class Dog extends Animal{
    public int getAge(){
        return age + 2;
    }
}

class Cat extends Animal{
    public int getAge(){
        return age + 3;
    }
}

class app{
    public static void main(String[] args){
        Animal[] animals = new Animal[2];
        animals[0] = new Dog();
        animals[1] = new Cat();

        System.out.println(animals[0].getAge());
        System.out.println(animals[1].getAge());
    }
}
------Output------
7
8

这段代码和上面一段差不多,不过这段代码中用的是一个数组,这种情况就比较适合使用多态了,不然好像没有其他办法来弄了吧(初学java,不太懂)。在这里面多态不仅仅只是指一个方法有不同的效果,在这里还指类型的多样性。

好了就到这里了。

推荐阅读

Java中函数的重载和重写