Java之封装、继承,多态

Java 之封装、继承,多态

一、封装

1.封装的基本介绍

  封装(encapsulation,有时称为数据隐藏)就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作。

  实现封装的关键在于,绝对不能让类中的方法直接访问其他类的属性(成员变量)。程序只能通过对象的方法与对象数据进行交互。封装给对象赋予了“黑盒”特征,这是提高重用性和可靠性的关键。这意味着一个类完全可以改变存储数据的方式,只要仍旧使用同样的方法操作数据,其他对象就不会知道也不用关心这个类所发生的变化。


2. 封装的实现

  可以分为3步进行实现:

  1. 将属性进行私有化 privat (即不能直接修改属性)
  2. 提供一个公共的( public )set 方法,用于对属性判断并赋值。
public void SetXxx(类型 参数名) { //Xxx表示某个属性
    //可以添加数据验证的业务逻辑
    属性 = 参数名;
}
  1. 提供一个公共的 ( public )get 方法,用于获取属性的值
public 数据类型 getXxx() {
    //可以进行权限判断,Xxx表示某个属性
    return xxx;
}

案例分析:

  一个 Person 类,有姓名,年龄,工资,职位属性。在主类中设置 Person 类对象的属性,以及查看。

public class Encap {
    public static void main(String[] args) {
        Person person = new Person();//创建对象
        //调用set 方法进行属性的设置
        person.setName("熊");
        person.setAge(30);
        //······
        
        //调用get 方法查看属性
        person.getName();
        person.getAge();
        //·····
    }
}

class Person {
    private String name;
    private int age;
    private double salary;
    private String job;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        //控制 name 的长度在2-6字符之间
        if (name.length() >= 2 && name.length() <= 6)
            this.name = name;
        else {
            System.out.println("name 的长度必须在2-6个字符之间!!!");
            System.out.println("请重新设置!");
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        //对设置的年龄进行判断,是否在1-120之间
        //否则设置默认值 18
        if (age >= 1 && age <= 120)
            this.age = age;
        else {
            System.out.println("年龄必须在1-120岁之间!!!");
            this.age = 18;
        }
    }

    public double getSalary() {

        //进行权限控制
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }
}

  分析:属性是私有的,所以我们不能直接修改、查看它的值,但是在创建对象的过程,调用构造器却可以直接跳过 set 方法,进行值的设置。要解决这个问题只需在构造器中也调用 set 方法。


3. 将构造器与 setXxx 方法结合

//下面只写构造器
public Person(String name, int age, double salary) {
    setName(name);
    setAge(age);
    setSalary(salary);
}

二、继承

继承: 继承的基本思想是,可以基于已有的类创建新的类。继承已存在的类就是复用(继承)这些类的方法,而且可以增加一些新的方法和字段(成员变量)。

1. 继承的基本介绍

  当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。

  继承的示意图:

在这里插入图片描述


2. 基本语法

// class 子类 extends 父类 {
//}
//比如 Pupil 类继承 Student 类

class Pupil extends Student {
    
}

  关键字 extends 表明正在构造的新类派生于一个已存在的类。这个已存在的类称为 超类(superclass)、基类(base class) 或 父类(parent class) ;新类称为 子类(subclass)、派生类(derived class)、或孩子类(child class)。


3.继承的深入理解

  1. 子类继承了父类所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问。

  2. 子类必须调用父类的构造器, 完成父类的初始化。

  3. 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过(怎么理解。) [举例说明]

    import pg1;
    public Base {
        private String name;
        private int age;
        
        public Base(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    import pg2;
    public class Sub {
        //具有父类的所有属性
        public Sub() {
            //如果什么都不写,默认为 super() 即调用父类的无参构造器
            super("xiong",12);//调用父类对应参数的构造器
        }
    }
    
  4. 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)。

  5. super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)。

  6. super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器。

  7. java 所有类都是 Object 类的子类, Object 是所有类的基类。

  8. 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)。

  9. 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。

    思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】

  10. 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系。


4. 继承的本质分析(内存存在形式)

  分析:当子类继承父类,创建子类对象时,内存中到底发生了什么?

public class ExtendsTheory {
	public static void main(String[] args) {
        Son son = new Son();//内存的布局
        //?-> 这时请大家注意,要按照查找关系来返回信息
        //(1) 首先看子类是否有该属性
        //(2) 如果子类有这个属性,并且可以访问,则返回信息
        //(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
        //(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object... System.out.println(son.name);
        //返回就是大头儿子
        //System.out.println(son.age);//返回的就是 39
        //System.out.println(son.getAge());//返回的就是 39
        System.out.println(son.hobby);//返回的就是旅游
    }
}

class GrandPa { //爷类
	String name = "大头爷爷";
    String hobby = "旅游";
}

class Father extends GrandPa {//父类
    String name = "大头爸爸";
    private int age = 39;
    public int getAge() {
    	return age;
    }
}
class Son extends Father { //子类
	String name = "大头儿子";
}

5. 子类创建的内存布局

在这里插入图片描述


6. super 关键字

6.1 super 的基本介绍

  super 代表父类的引用,用于访问父类的属性、方法、构造器。

6.2 基本语法
  1. 访问父类的属性,但是不能访问父类的 private 属性.

    //语法
    super.属性名;
    
  2. 访问父类的方法,但不能访问父类的 private 方法

    //语法
    super.方法名(参数列表);
    
  3. 访问父类的构造器。

    //语法
    super(参数列表);
    //只能放在构造器中,并且必须是构造器的第一条语句
    //只能出现一句
    
6.3 使用时的注意事项
  1. super 的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用 super 去访问爷爷类的成员。

    如果多个基类(上级类)中都有同名的成员,使用 super 访问遵循就近原则。A->B->C

  2. this 查找属性或方法规则:

    1. 先找本类,如果有,则调用。

    2. 如果没有,则找父类(如果有,并可以调用,则调用) 。

    3. 如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object 类 。

      提示:如果查找方法或属性的过程中,找到了,但是不能访问, 则报错, cannot access 。

      如果查找方法的过程中,没有找到,则提示方法或属性不存在。

  3. super 查找属性或方法的规则:

    //例如
    class A extends B;
    class B extends C;
    
    //从某种意义上说,C类也是A类的父类,
    //即A继承了C的属性和方法
    
    //所以 super 会先从 B 类开始查找,没有
    //就往C类查找,一直往上,直到 Object 类
    
    1. 直接从父类开始查找,如果有,则调用。

    2. 如果没有,则找上一级类(如果有,并可以调用,则调用)。

    3. 如果上一级类没有,则继续找上一级类的父类,整个规则,就是一样的,直到 Object 类。

      提示:如果查找属性或方法的过程中,找到了,但是不能访问, 则报错, cannot access。

      如果查找属性的过程中,没有找到,则提示属性或方法不存在。

6.4 super 带来的好处
  1. 调用父类构造器的好处 (分工明确,父类属性由父类初始化,子类属性由子类初始化)。
  2. 当子类中有和父类中的成员(属性或方法)重名时,为了访问父类的成员,必须通过 super。如果没有出现重名,使用 super、this、直接访问是一样的效果。
  3. super 的访问不限于父类,如果爷爷类和本类中有同名的成员,也可以使用super 去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用 super 访问遵循就近原则。A->B->C, 当然也需要遵守访问权限的规则。
6.5 super 和 this 的比较
No.区别点thissuper
1访问属性访问本类中的属性,如果本类中没有此属性,则从父类中继续查找从父类开始查找属性
2调用方法访问本类中的方法,如果本类没有此方法,则从父类继续查找从父类开始查找
3调用构造器调用本类的构造器,必须放在构造器的首行调用父类的构造器,必须放在子类构造器的首行
4特殊表示当前的对象子类中访问父类对象

7. 方法重写/覆盖(override)

7.1 基本介绍

  方法覆盖(重写)就是子类有一个方法和父类的某个方法的名称、返回类型、形式参数一样,那么就称为子类的这个方法覆盖了父类的方法。

7.2 使用方式、细节、注意事项

  方法重写(覆盖)需要满足以下几点:

  1. 子类的方法的形参列表、方法名称要和父类方法的形参列表、方法名称完全一样。

  2. 子类方法的返回类型和父类方法的返回类型一样,或者子类方法的返回类型是父类方法的返回类型的子类。

    //例如
    
    class String extends Object;
    //即 String 类是 Object 类的子类
    //父类 的方法:
    public Object getInfo(){
        return null;
    }
    
    //子类的方法:
    public String getInfo(){
        return null;
    }
    //则这2个方法构成方法的重写(覆盖)
    //因为String 类是 Object 类的子类
    
    
  3. 子类方法不能缩小父类方法的访问权限

    public > protect > 默认 > private
    
7.3 方法重载和方法重写(覆盖)的比较
名称发生范围方法名形参列表返回类型修饰符
重载(overload)本类中必须一样类型、个数或者顺序至少有一个不同无要求无要求
重写(override)父子类中必须一样要相同子类方法的返回类型和父类方法的返回类型一样,或者子类方法的返回类型是父类方法的返回类型的子类。子类方法不能缩小父类方法的访问范围

三、多态

1. 基本介绍

  一个对象变量可以指示多种实际类型的现象称为 多态

  方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。


2. 具体体现

2.1 方法的多态

  重写和重载就体现多态。

//重载
public int sum(int a, int b){}
public int sum(int a){}
//调用方法,传入不同的参数调用不同的方法
//体现了多态
sum(1);
sum(3,4);

//重写
class A {
    public void say(){}
}

class B extends A {
    public void say(){}//重写了A类的方法
}

//调用过程体现了多态
A a = new A();
B b = new B();

a.say();
b.say();

2.2 对象的多态

  一些知识点的介绍:

  1. 一个对象的编译类型和运行类型可以不一致。

  2. 编译类型在定义对象时,就确定了,不能再更改。

  3. 运行类型是可以改变的。

  4. 编译类型看声明时,= 号的左边,运行类型看 = 的右边。

    //说明:
    //Dog extends Animal
    //Cat extends Animal
    
    Animal animal = new Dog();
    //animal 的编译类型为 Animal
    //运行类型为 Dog
    
    animal = new Cat();
    //animal 的运行类型变成了 Cat,
    //编译类型没有改变
    

3. 多态的使用

多态的前提是:两个对象(类)存在继承关系。

3.1 多态的向上转型
  1. 本质: 父类的引用指向了子类的对象。

  2. 语法: 父类类型 引用名 = new 子类类型();

    Animal animal = new Dog();
    
  3. 特点: 编译类型看左边,运行类型看右边。

    可以调用父类中的所有成员(包括属性和方法),但是要在遵守访问权限的前提下。

    不能调用子类中特有的成员。

    Animal animal = new Dog();
    //此时,animal 可以调用 Animal 的所有成员,遵循访问权限
    //但是,animal 不能调用 Dog 中特有的成员
    

案例讲解:

public class Ploy {
    public static void main(String[] args) {
        Animal animal = new Dog();
        
        animal.cry();//可以调用
        //animal.dogCry() 不能调用
        //因为在编译阶段,能够调用的成员由编译类型决定
        //即在 Animal 中没有 dogCry() 方法
        //在调用animal.cry() 时,
        //将会按照从子类(运行类型)开始查找方法 
        //然后调用,规则我前面我们讲的方法调用规则一致。
    }
}

class Animal {
    private String name;
    
    public void cry() {
        System.out.println("叫叫叫");
    }
}

class Dog extends Animal {
    private int age;
    
    public void dogCry() {
        System.out.println("汪汪汪");
    }
}
  1. 最终的运行效果看子类(运行效果),即调用方法时,按照从子类(运行类型)开始查找方法 ,然后调用,规则我前面我们讲的方法调用规则一致。

3.2 多态的向下转型
  1. 语法: 子类类型 引用名 = (子类类型)父类引用;

     Dog dog = (Dog)animla;
    
  2. 只能强转父类的引用,不能强转父类的对象。

  3. 要求父类的引用必须指向的是当前目标类型的对象。

    Dog dog = (Dog) animla;
    //即,要求 animal 指向的是 Dog 类型的对象。
    
  4. 当向下转型后,可以调用子类类型中所有的成员。

  5. 属性没有重写一说,属性的值看编译类型。

    public class PolyDetail02 {
    	public static void main(String[] args) {
    		//属性没有重写之说!属性的值看编译类型
            Base base = new Sub();//向上转型
            System.out.println(base.count);// ? 看编译类型 10
            Sub sub = new Sub();
            System.out.println(sub.count);//? 20
    	}
    }
    
    class Base { //父类
    	int count = 10;//属性
    }
    class Sub extends Base {//子类
    	int count = 20;//属性
    }
    
    

3.3 instanceof 比较操作符

  instanceOf 比较操作符,用于判断对象的 运行类型 是否为 XX 类型或 XX 类型的子类型,如果是则返回 true,否则返回 false。

  注意是看运行类型,即具体指向的对象类型。

class Base { //父类
	int count = 10;//属性
}
class Sub extends Base {//子类
	int count = 20;//属性
}

Base b = new Base();
Sub s = new Sub();
Base b0 = new Sub();

System.out.println(s instanceof Sub);//true
System.out.println(b0 instanceof Sub);//true
System.out.println(b instanceof Sub);//fale

4. 动态绑定机制

  Java 的动态绑定机制:

  1. 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定。
  2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>