Bootstrap

【Java基础入门篇】三、面向对象和JVM底层分析(2)

Java基础入门篇


三、面向对象和JVM底层分析

3.4 面向对象的三大特征

面相对象的三大特征:封转继承多态

(一)封装

“封装”(encapsulation),封装的理念是高内聚,低耦合

  • “高内聚”:封装细节,便于修改内部代码,提高代码可维护性
  • “低耦合”:简化外部调用,便于使用者使用,便于扩展和协作

封装的优点:提高代码的安全性,提高代码的复用性
封装的实现——使用访问控制符,Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露。Java中的访问控制符:private、default、protected、public,访问权限:

  • private(同一个类可用————类内友好)
  • default(不定义默认就是default,同一个类、同一个包可用————包内友好)
  • protected(同一个类、同一个包、子类可用————父子友好、包内友好)
  • public(同一个类、同一个包、子类、所有类可用————全局友好)

若protected——若父类和子类“在同一个包中”,子类可以访问父类的protected成员,也可以访问父类new创建对象的protected成员。若protected——若父类和子类“不在同一个包中”,子类可以访问父类的protected成员,不可以访问父类new创建对象的protected成员。

package BasicJava.encapsulation.a;

/*
 *   测试封装
 */
public class Person {
    private int testPrivate;
    int testDefault;
    protected int testProtected;
    public int testPublic;

    public void test(){
        System.out.println(this.testPrivate);
    }



}
package BasicJava.encapsulation.a;
/*
 *   测试封装
 */

// 同一个包内的不同类
public class Student {

    public void study(){
        Person p = new Person();
        // System.out.println(p.testPrivate); // 无权访问
        System.out.println(p.testDefault);
        System.out.println(p.testProtected);
        System.out.println(p.testPublic);

    }
}

// 同一个包内具有继承关系的类
class Student2 extends Person{
    public void study(){
        Person p = new Person();
        // System.out.println(p.testPrivate); // 无权访问
        System.out.println(p.testDefault);
        System.out.println(p.testProtected);
        System.out.println(p.testPublic);

    }

}

开发过程中封装的简单规则:

  1. 属性一般使用private,私有后提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的(注意:boolean变量的get方法是is开头的)。
  2. 方法:一些只用于本类的辅助方法可以用private修饰,希望其他类调用的方法可以用public修饰。
package BasicJava.encapsulation.b;

import BasicJava.encapsulation.a.Person;
/*
 *   测试封装
 */
public class Child extends Person {
    public void play(){
        Person p = new Person();
        // 此时由于是不同包的继承,所以不能使用父类对象的protected修饰的属性/方法
        //System.out.println(p.testProtected);
        System.out.println(super.testProtected); // 但是可以访问父类的protected成员
    }
}
package BasicJava.encapsulation.b;

/*
*   测试封装的简单规则
 */
public class User {
    private int id;
    private String name;
    private boolean man;
    // private类型的属性通过get/set方法调用
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public boolean isMan() {
        return man;
    }

    public void setMan(boolean man) {
        this.man = man;
    }
}
package BasicJava.encapsulation.b;
/*
 *   测试封装的简单规则
 */
public class TestUser {
    public static void main(String[] args) {
        User u = new User();
        u.setId(100);
        u.setName("小明");
        u.setMan(true);

        System.out.println(u.getId());
        System.out.println(u.getName());
        System.out.println(u.isMan());
    }

}

在这里插入图片描述

(二)继承

当我们不显示指定extends时,默认继承自父类java.lang.Object类,继承的作用:

  1. 代码复用,更加容易实现类的扩展,

  2. 方便建模,子类是父类的扩展

instanceof运算符是二元运算法,左边是对象,右边是类,用于判断当前对象是否是右边类或子类所创建的对象,是则返回true,反之返回false。

继承中父类又叫超类、基类,子类又叫派生类。Java中只有单继承,不像C++存在多继承,多继承会使得继承链过于复杂,系统难以维护引起混乱。Java中类没有多继承但是接口有多继承,子类继承父类,可以得到父类全部的属性和方法(除了父类的构造方法),但是不见得可以直接访问,例如父类中的私有属性/方法。

 /*
    *   测试继承
     */
    public static class Person{
        String name;
        int height;
        public void rest(){
            System.out.println("休息一会!");
        }
    }

    public static class Student extends Person{
        String school;

        public Student(String name, int height, String school){
            this.name = name;
            this.height = height;
            this.school = school;
        }
        public void study(){
            System.out.println("学习!");
        }
    }

    public static void testExtends(){
        Student s1 = new Student("小明", 175, "HBU");
        s1.study();
        s1.rest();
        System.out.println(s1 instanceof Student);
        System.out.println(s1 instanceof Person);
    }

在这里插入图片描述

super可以看作是堆父类对象的直接引用,可以通过super来访问父类中被子类覆盖的方法/属性。使用super调用普通方法,语句没有位置限制,可以在子类中任意使用。在一个类中,若是构造方法第一行没有调用super(…)或者this(…),那么Java默认调用super(),含义是调用父类的无参构造方法。

/*
*   使用super调用父类方法
 */
public static class FatherClass{
    int value;

    void f(){
        value = 100;
        System.out.println("FatherClass's value is : " + value);
    }
}

public static class ChildClass extends FatherClass{
    int value;
    int age;

    void f(){
        // 调用父类的普通方法
        super.f();
        value = 200;
        System.out.println("ChildClass's value is : " + value);
        System.out.println(super.value);
    }
}

public static void testSuper(){
    ChildClass c1 = new ChildClass();
    c1.f();
}

}

在这里插入图片描述

方法重写override与方法重载overload毫无关系。重写是指子类重写父类的方法可以用自身的行为替换父类的行为,重写是实现“多态”的必要条件。方法重写需要符合以下三个要点:

  1. “==”方法名、形参列表相同

  2. “≤”返回值类型和声明异常类型,子类小于等于父类

  3. “≥”访问权限,子类大于等于父类

除了继承,**“组合”**也可以实现代码的复用,组合的核心是将父类对象作为子类的属性。组合就是在子类中new一个父类对象,然后通过对象名+属性/方法的方式使用父类。和继承相比,组合更加灵活,继承只能有一个父类,但是组合可以new多个类,使用他们的属性。对于is-a的关系建议使用“继承”(Student is a Person),对于has-a的关系建议使用“组合”(Student has a bike)。

/*
    *   测试方法重写override
     */
    public static  class Vehicle{
        public void run(){
            System.out.println("跑!");
        }
        public Vehicle getVehicle(){
            System.out.println("给你一个交通工具!");
            return null;
        }
    }

    public static class Horse extends Vehicle{
        @Override
        public void run() {
            System.out.println("这是一匹马跑起来了!");
        }

        @Override
        public Horse getVehicle() {
            return new Horse();
        }
    }

    public static void testOverride(){
        Horse h = new Horse();
        h.run();
        h.getVehicle();
    }

在这里插入图片描述

/*
    *   实现组合
     */
    public static class Car{
        String name;
        int number;

        Car(){
            System.out.println("还没初始化");
        }
        Car(String name, int number){
            this.name = name;
            this.number = number;
        }
        public void run(){
            System.out.println(this.name + this.number + "出发!");
        }
    }

    public static class Bike{
        // 组合就是在子类中new一个父类对象,然后通过对象名+属性/方法的方式使用父类
        Car c = new Car();

        int wheel;

        public Bike(int wheel, String name, int number){
            this.wheel = wheel;
            this.c.name = name;
            this.c.number = number;
        }
        public void letCarGo(){
            System.out.println(this.c.name + this.c.number + "出发!");
        }
        public void letBikeGo(){
            System.out.println("这是一个"+ this.wheel + "轮车,出发!");
        }
    }

    public static void testCombine(){
        Car c1 = new Car("奔驰", 1001);
        c1.run();
        Bike b1 = new Bike(3, "丰田", 1002);
        b1.letCarGo();
        b1.letBikeGo();

    }

在这里插入图片描述

属性/方法查找顺序,例如,查找变量h -> 查找当前类中有没有属性h -> 依次上溯每个父类 -> 查找每个父类中是否有h,直到Object -> 如果没有找到,则出现编译错误。
构造方法调用顺序,构造方法第一句总是super(…)来调用父类对应的构造方法,流程是:现象上追溯到Object类 -> 然后再次依次向下执行类的初始化块和构造方法 -> 直到当前子类为止。
静态初始化块的执行顺序和构造方法调用顺序一致。

/*
*   感受构造函数初始化顺序
 */
class FatherClass2{
    //定义静态初始化块
    static{
        System.out.println("静态初始化块:FatherClass2");
    }
    // 无参构造器
    FatherClass2(){
        System.out.println("创建FatherClass2");
    }
}

class ChildClass2 extends FatherClass2{
    //定义静态初始化块
    static{
        System.out.println("静态初始化块:ChildClass2");
    }
    ChildClass2(){
        // 默认情况下是调用父类的无参构造器,等同于super();
        System.out.println("创建ChildClass2");
    }
}

在这里插入图片描述

(三)多态

“多态”(polymorphism):同一个方法调用,不同对象行为完全不同
多态的要点:

  1. 多态是方法的多态,与属性无关
  2. 多态的存在要有三个必要条件(继承、方法重写、父类引用指向子类对象)
  3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,就是多态
package BasicJava.polymorphism;
/*
*   测试多态
 */
public class Animal {
    public void shout(){
        System.out.println("动物的叫声");
    }
}

class Dog extends Animal{
    @Override
    public void shout() {
        System.out.println("汪汪汪!");
    }
    public void seeDoor(){
        System.out.println("正在看门...");
    }
}
class Cat extends Animal{
    @Override
    public void shout() {
        System.out.println("喵喵喵!");
    }
    public void catchMouse(){
        System.out.println("正在抓老鼠...");
    }
}

对象的转型(casting):由父类引用指向子类对象的过程中,我们称之为“向上转型”,属于“自动类型转换”。向上转型后的父类引用变量只能调用它“编译类型”的方法,不能调用它“运行时类型”的方法,这时我们需要进行强制类型转换,我们称之为“向下转型”。

package BasicJava.polymorphism;

public class TestPoly {
    public static void main(String[] args) {
        animalShout(new Dog()); // 相当于Animal a = new Dog(); 属于“向上转型”
        animalShout(new Cat()); // 相当于Animal a = new Cat(); 属于“向上转型”

        // new Dog()是我们真正的类型,Animal animal是我们定义的类型,因此无法执行Dog()类中的seeDoor()方法
        //编译类型       运行时类型
        Animal animal = new Dog();
        animal.shout(); // 可以执行
        // animal.seeDoor(); //无法执行

        // 通过“向下转型”就可以了,这里的d其实就是animal,只不过进行了强制类型转换
        Dog d = (Dog)animal;
        d.seeDoor();

        // 我们可以添加一个判断来避免异常
        if(animal instanceof Dog){
            Dog d2 = (Dog)animal;
            d2.seeDoor();
        }
        if(animal instanceof Cat){
            Cat c2 = (Cat)animal;
            c2.catchMouse();
        }

        // 测试用不同的类进行“向下转型”的强制类型转换
        Cat c = (Cat)animal;
        c.catchMouse(); // 发现并不报错,但是这实际上只是编译时不报错,但是运行的时候会报错的
                        // "ClassCastException: BasicJava.polymorphism.Dog cannot be cast to BasicJava.polymorphism.Cat"



    }

    static  void animalShout(Animal a){
        a.shout();
    }
}

在这里插入图片描述

;