【Java 基础】面向对象(二) 封装、继承和多态

摘要:本篇文章主要介绍了面向对象的三大特征,封装、继承和多态。封装将类的某些信息隐藏在类内部,不允许外部程序直接访问;通过封装的方法来控制成员变量的操作,提高了代码的安全性,把代码用方法进行封装,提高了代码的复用性。继承可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法;继承让类与类之间产生关系,提高了代码的复用性和可维护性。多态是指同一个对象在不同时刻下表现出不同的形态;多态的好处是消除了类与类之间的耦合关系,提高了程序的扩展性、灵活性和简化性。

封装

封装是面向对象三大特征之一(封装,继承,多态),它是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的。

封装隐藏对象的属性和实现细节,仅对外公开访问方法,控制在程序中属性的读和写的访问级别。

封装将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问成员变量 private,提供对应的 getXxx()/setXxx() 方法。

通过封装的方法来控制成员变量的操作,提高了代码的安全性,把代码用方法进行封装,提高了代码的复用性。封装的好处:

  • 良好的封装能够减少耦合。
  • 可以对成员变量进行更精确的控制。
  • 隐藏信息,实现细节,使用者不必了解具体的实现细节,而只要通过对外公开的访问方法,来使用类的成员。
  • 提高代码安全和代码的复用性。

封装的基本要求

  • 把所有的属性私有化。
  • 对每个属性提供 getter 和 setter 方法。
  • 如果有一个带参的构造函数的话,那一定要写一个不带参的构造函数。
  • 建议重写 toString 方法,但这不是必须的。

private 关键字

private 是一个修饰符,可以用来修饰成员(成员变量,成员方法),被 private 修饰的成员,只能在本类进行访问,针对 private 修饰的成员变量,如果需要被其他类使用,需要提供相
应的 set 或者 get 操作。

  • 提供 get 变量名() 方法,用于获取成员变量的值,方法用 public 修饰。
  • 提供 set 变量名(参数) 方法,用于设置成员变量的值,方法用 public 修饰。

示例代码
定义学生类,设置年龄:

public class Student {
    // 成员变量
    String name;
    private int age;

    // 提供get/set方法
    public void setAge(int a) {
        if(a<0 || a>120) {
            System.out.println("你给的年龄有误");
        } else {
            age = a;
        }
    }

    public int getAge() {
        return age;
    }

    // 成员方法
    public void show() {
        System.out.println(name + "," + age);
    }
}

定义测试类:

public class StudentDemo {
    public static void main(String[] args) {
        // 创建对象
        Student s = new Student();

        // 给成员变量赋值
        s.name = "林青霞";
        // s.age = 30;
        // s.age = -30;
        s.setAge(30);
        // s.setAge(-30);
        // 调用show方法
        s.show(); // 林青霞,30
    }
}

this 关键字

this 修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)。

  • 方法的形参如果与成员变量同名,不带 this 修饰的变量指的是形参,而不是成员变量。
  • 方法的形参没有与成员变量同名,不带 this 修饰的变量指的是成员变量。
 public class Student { 
    private String name; 
    private int age;
    public void setName(String name) {
        this.name = name;  // 方法的形参如果与成员变量同名,不带 this 修饰的变量指的是形参,而不是成员变量
}
    public String getName() {
        return name; // 方法的形参没有与成员变量同名(无形参),不带 this 修饰的变量指的是成员变量。
}
    public void setAge(int age) {
        this.age = age;
}
    public int getAge() {
        return age;
}
    public void show() {
        System.out.println(name + "," + age);
} 
}

this 除了修饰变量用于指代成员变量外,还代表所在类的对象引用,this 代表当前调用方法的引用,哪个对象调用的方法,this 就代表哪一个对象。

public class StudentDemo {
public static void main(String[] args) {
    Student s1 = new Student(); 
    s1.setName("wangxiong1"); // setName 方法被 s1 这个对象调用,this 就代表 s1 这个对象。
    Student s2 = new Student(); 
    s2.setName("wangxiong2");  // setName 方法被 s2 这个对象调用,this 就代表 s2 这个对象。
} 
}

上面代码的运行内存原理分析(栈内存和堆内存中的分析):

  • 代码执行 main 方法开始,main 方法进入栈内存。
  • 在堆内存创建 s1 对象(堆内存出现一个内存空间,将地址 001 赋值给 s1)。
  • s1 开始调用 setName 方法,setName 方法加载进栈内存,s1 通过 this 关键字,从栈内存中进入堆内存修改 name 的值为 wangxiong1,setName 方法调用完毕之后便从栈内存消失。
  • 紧接着,在堆内存创建 s2 对象(堆内存出现一个内存空间,将地址 002 赋值给 s2)。
  • s2 开始调用 setName 方法,setName 方法加载进栈内存,s2 通过 this 关键字,从栈内存中进入堆内存修改 name 的值为 wangxiong2,setName 方法调用完毕之后便从栈内存消失。
  • main 方法执行完毕,从栈内存消失。

结论:从上面的内存分析中我们可以得出,s1 对象调用 setName 方法,s2 对象调用 setName 方法,setName 方法被哪个对象调用,this 就代表哪一个对象。

继承

基本概念:继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法。

使用格式:class 子类 extends 父类 { }

class Dog extends Animal { }

继承的好处:

  • 继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员。
  • 提高了代码的复用性(多个类相同的成员可以放到同一个类中)。
  • 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)。

示例代码:

public class Father {
    public void show() {
        System.out.println("Father中的show方法被调用");
    }
}

public class Son extends Father {
    public void method() {
        System.out.println("Son 中的method方法被调用");
    }
}

public class Demo {
    public static void main(String[] args) {
        //创建对象,调用方法
        Father f = new Father();
        f.show(); // Father 中的show方法被调用

        Son s = new Son(); 
        s.method(); // Son 中的 method方 法被调用
        s.show(); // Father 中的show方法被调用
    }
}

继承的弊端:继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削 弱了子类的独立性。

继承的场景:使用继承,需要考虑类与类之间是否存在谁是谁的一种的关系,不能盲目使用继承。例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类。

继承中变量和成员的访问特点

继承中成员变量的访问特点,采用的是就近原则。

  • 子类局部范围找
  • 子类成员范围找
  • 父类成员范围找
  • 如果都没有就报错

继承中成员方法的访问特点:

  • 子类成员范围找
  • 父类成员范围找
  • 如果都没有就报错

示例代码:

public class Father {
    // 年龄
    public int age = 40;
}

public class Son extends Father {
    // 身高
    public int height = 175;

    // 年龄
    public int age = 20;

    public void show() {
        int age = 30;
        System.out.println(age); 
        System.out.println(height);
        // System.out.println(weight); // 报错
    }
}

public class Demo {
    public static void main(String[] args) {
        // 创建对象,调用方法
        Son s = new Son();
        s.show(); // 30 175
    }
}

this 和 super 关键字

this 和 super 关键字:

  • this:代表本类对象的引用 。
  • super:代表父类存储空间的标识(可以理解为父类对象引用)。

this 和 super 的使用区别 :

  • 成员变量:this.成员变量,代表访问本类成员变量;super.成员变量,代表访问父类成员变量。
  • 成员方法:this.成员方法,代表访问本类成员方法;super.成员方法,代表访问父类成员方法。
  • 构造方法:this(...) 代表访问本类构造方法;super(...) 代表访问父类构造方法。

示例代码

public class Father {
    public int age = 40;
}

public class Son extends Father {
    public int age = 20;

    public void show() {
        int age = 30;
        System.out.println(age); // 30
        // 访问本类的成员变量age
        System.out.println(this.age); // 20
        // 访问父类的成员变量age
        System.out.println(super.age); // 40
    }
}

public class Demo {
    public static void main(String[] args) {
        // 创建对象,调用方法
        Son s = new Son();
        s.show(); // 30 20 40
    }
}

注:对象在堆内存中,会单独存在一块super区域,用来存放父类的数据。

继承中构造方法的访问特点

子类在继承父类的数据时,可能还会用到父类的数据,子类初始化之前,一定要先将父类数据初始化。

子类中所有的构造方法都会访问父类中的无参构造方法,原因在于,每一个子类的构造方法的第一条语句默认都是:super()。

如果父类中没有提供无参构造方法,只有带参构造方法时候,子类继承该怎么办?

  • 通过使用 super 关键字去显示的调用父类的带参构造方法。
  • 在父类中自己提供一个无参构造方法(推荐)。

继承中的方法重写

基本概念:子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)。
应用场景:当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样即沿袭了父类的功能,又定义了子类特有的内容。
Override注解:用来检测当前的方法,是否是重写的方法,起到校验的作用。

注意事项:

  • 私有方法不能被重写(父类私有成员子类是不能继承的)。
  • 子类方法访问权限不能更低(public > 默认 > 私有)。

示例代码:

public class Father {
        private void show() {
            System.out.println("Father中show()方法被调用");
     }
            void method() { 
            System.out.println("Father中method()方法被调用");
    } 
}

public class Son extends Father {
        /* 编译【出错】,子类不能重写父类私有的方法*/ 
        @Override
        private void show() {
            System.out.println("Son中show()方法被调用"); 
    }
        /* 编译【出错】,子类重写父类方法的时候,访问权限需要大于等于父类 */ 
        @Override
        private void method() {
             System.out.println("Son中method()方法被调用"); 
    }
        /* 编译【通过】,子类重写父类方法的时候,访问权限需要大于等于父类 */ 
        @Override
        public void method() {
             System.out.println("Son中method()方法被调用"); 
    }
}

多继承与多层继承

  • Java 中类只支持单继承,不支持多继承(接口支持多继承)。

继承中的多继承错误范例:

public class A extends B, C { }

接口中的多继承正确范例:

// 接口之间通过继承来实现,既可以是单继承,也可以是多继承
public interface Inter3 extends Inter1,Inter2 {}
  • Java 中类支持多层继承。

多层继承的示例代码:

public class GrandFather {
    public void drink() {
        System.out.println("爷爷爱喝酒");
    }
}

public class Father extends GrandFather {
    public void smoke() {
        System.out.println("爸爸爱抽烟");
    }
}

public class Son extends Father {

}

继承的案例

需求:请采用继承的思想实现猫和狗的案例,并在测试类中进行测试。

思路分析:

1、猫
成员变量:姓名,年龄 。
构造方法无参,带参 。
成员方法:get/set 方法,抓老鼠()。
2、狗
成员变量:姓名,年龄 。
构造方法:无参,带参 。
成员方法:get/set 方法,看门()。
3、共性
成员变量:姓名,年龄。
构造方法:无参,带参。
成员方法:get/set 方法。

具体步骤:

1、定义动物类(Animal)
【成员变量:姓名,年龄】【 构造方法:无参,带参】【成员方法:get/set方法】 

2、定义猫类(Cat),继承动物类
【构造方法:无参,带参】【成员方法:抓老鼠() 】

 3、定义狗类(Dog),继承动物类
【构造方法:无参,带参】【成员方法:看门() 】 

4、定义测试类(AnimalDemo),写代码进行测试

示例代码:
定义动物类(Animal):

public class Animal {
    private String name;
    private int age;

    public Animal() {
    }

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

定义猫类(Cat),继承动物类:

public class Cat extends Animal {

    public Cat() {
    }

    public Cat(String name, int age) {
        super(name, age);
    }

    public void catchMouse() {
        System.out.println("猫抓老鼠");
    }
}

定义狗类(Dog),继承动物类:

public class Dog extends Animal {

    public Dog() {
    }

    public Dog(String name, int age) {
        super(name, age);
    }

    public void lookDoor() {
        System.out.println("狗看门");
    }
}

定义测试类(AnimalDemo):

public class AnimalDemo {
    public static void main(String[] args) {
        // 创建猫类对象并进行测试
        Cat c1 = new Cat();
        c1.setName("加菲猫");
        c1.setAge(5);
        System.out.println(c1.getName() + "," + c1.getAge()); // 加菲猫,5
        c1.catchMouse();// 猫抓老鼠

        Cat c2 = new Cat("加菲猫", 5);
        System.out.println(c2.getName() + "," + c2.getAge()); // 加菲猫,5
        c2.catchMouse(); // 猫抓老鼠
    }
}

多态

多态是指同一个对象,在不同时刻表现出来的不同形态。可以理解为相同的事物,调用其相同的方法,参数也相同时,但表现的行为却不同。

多态类比:

我们可以说猫是猫:猫 cat = new 猫();
我们也可以说猫是动物:动物 animal = new 猫();
这里猫在不同的时刻表现出来了不同的形态,这就是多态。

多态的前提:

  • 有继承/实现关系
  • 有方法重写
  • 有父类引用指向子类对象

示例代码:Animal 父类代码

public class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

Cat 继承 Animal 代码:

// 有继承或实现关系
public class Cat extends Animal {
    // 方法的重写
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

测试类代码:

public class AnimalDemo {
    public static void main(String[] args) {
        // 有父类引用指向子类对象
        Animal a = new Cat(); // 猫是动物,同一个对象 new Cat() 表现出动物的形态
        Cat a = new Cat();  // 猫是猫,同一个对象 new Cat() 表现出猫的形态
    }
}

多态中成员的访问特点:

  • 成员变量:编译看父类(左边),运行看父类(左边)。
  • 成员方法:编译看父类(左边),运行看子类(右边)。

示例代码:

public class Animal {
    public int age = 40;
    public void eat() {
        System.out.println("动物吃东西");
   }
}
public class Cat extends Animal {

    public int age = 20;
    public int weight = 10;

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    public void playGame() {
        System.out.println("猫捉迷藏");
    }
}
public class AnimalDemo {
    public static void main(String[] args) {
       Cat b = new Cat();  // 仅体现继承关系
        // 有父类引用指向子类对象
        Animal a = new Cat(); // 体现了多态关系
        System.out.println(a.age); // 40 多态中,成员变量编译和运行都看父类
        // System.out.println(a.weight); // Animal 对象中没有 weight 成员变量,编译报错
        a.eat(); // 猫吃鱼 多态中,成员方法编译看父类,运行看子类
        // a.playGame(); // Animal 对象中没有playGame()成员方法,编译报错
    }
}

从以上的测试结果来看,多态中成员变量和成员方法的访问是不一样的。成员变量编译和运行都看父类,而成员方法编译看父类,运行看子类。

多态中,为什么成员变量和成员方法的访问不一样呢?

因为成员方法有重写,成员变量没有重写。

多态的好处:

提高程序的扩展性。多态的好处是消除了类之间的耦合关系,使程序更容易扩展。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作。

多态的弊端:(可使用多态的向下转型解决该弊端)

不能使用子类的特有成员,只能访问共有的成员。

示例:
Animal 类:

public class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

Cat 类:

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

Dog 类:

public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
    public void lookDoor() {
        System.out.println("狗看门");
    }
}

Pig 类:

public class Pig extends Animal {
    @Override
    public void eat() {
        System.out.println("猪吃白菜");
    }
}

AnimalOperator 动物操作类:

public class AnimalOperator {

    /*
    public void useAnimal(Cat c) { 
        // Cat c = new Cat();
        c.eat();
    }

    public void useAnimal(Dog d) { 
        // Dog d = new Dog();
        d.eat();
    }
    */

    public void useAnimal(Animal a) {
        // Animal a = new Cat();
        // Animal a = new Dog();
        a.eat();
       // a.lookDoor(); // 不能访问子类的特有功能
    }
}

AnimalDemo 类:

public class AnimalDemo {
    public static void main(String[] args) {
        // 创建动物操作类的对象,调用方法
        AnimalOperator ao = new AnimalOperator();
        Cat c = new Cat();
        ao.useAnimal(c); // 猫吃鱼

        Dog d = new Dog();
        ao.useAnimal(d); // 狗吃骨头

        Pig p = new Pig();
        ao.useAnimal(p); // 猪吃白菜
    }
}

多态的转型:

  • 向上转型,从子到父,父类引用指向子类对象就是向上转型。
  • 向下转型,从父到子,父类引用转为子类对象就是向下转型。

向下转型格式:子类型 对象名 = (子类型)父类引用

示例代码:
Animal 类:

public class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

Cat 类继承 Animal:

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
    public void playGame() {
        System.out.println("猫捉迷藏");
    }
}

测试类:

public class AnimalDemo {
    public static void main(String[] args) {
        // 多态
        Animal a = new Cat(); // 向上转型
        a.eat(); // 猫吃鱼
        // a.playGame(); // 会报错,Animal a 没有此方法

        /*
        // 重新创建 Cat 类的对象,不可取,因为堆内存中会产生了两个 Cat() 实例
        Cat c = new Cat();
        c.eat();
        c.playGame();
        */

        // 向下转型 格式:子类型 对象名 = (子类型)父类引用
        Cat c = (Cat)a;
        c.eat(); // 猫吃鱼
        c.playGame(); // 猫捉迷藏
    }
}

向上转型和向下转型的具体示例:

public class AnimalDemo {
    public static void main(String[] args) {
        // 向上转型
        Animal a = new Cat();
        a.eat(); // 猫吃鱼

        // 向下转型
        Cat c = (Cat) a;
        c.eat(); // 猫吃鱼
        c.playGame(); // 猫捉迷藏

        // 向上转型
        a = new Dog();
        a.eat(); // 狗吃骨头

        // 向下转型
        // ClassCastException 类型转换异常
        Cat cc = (Cat) a; // Dog 和 Cat 都继承自 Animal,但是 Dog 和 Cat 相互是不等价的,不能强制转换
        cc.eat();
        cc.playGame();
    }
}

相关推荐

微信扫一扫,分享到朋友圈

【Java 基础】面向对象(二) 封装、继承和多态
返回顶部

显示

忘记密码?

显示

显示

获取验证码

Close