JavaSE(七) 面向对象中级(封装继承多态)

171 阅读11分钟

idea 快捷键

  1. 删除当前行 ctrl + Y
  2. 复制当前行 ctrl + d
  3. 补全代码 alt + /
  4. 导入该行需要的类 先配置 auto import,然后使用alt +enter即可
  5. 快速格式化代码ctrl+alt+L (即把他的布局弄好看)
  6. 生成构造器等alt+insert[提高开发效率]
  7. 查看一个类的层级关系ctrl+H[学习继承后,非常有用]
  8. 将光标放在一个方法上,输入ctrl+B,可以选择定位到哪个类的方法[学继承后,非常有用]
  9. 自动的分配变量名,通过在后面.var

模板

  1. 区分相同名字的类 相同名字的类放在不同的包中
  2. 当类很多时,可以很好的管理类
  3. 控制访问范围

包基本语法

package com.hspedu;

说明:

  1. package关键字,表示打包
  2. com.hspedu:表示包名 com 一级目录,hspedu二级目录

1. 包的本质分析

包的本质实际上就是创建不同的文件夹/目录来保存文件

2. 包的命名

  • 命名规则:
  • 只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
  • 命名规范
  • 一般是小写字母+小圆点一般是
  • com.公司名.项目名.业务模块名
  • 比如:com.hspedu.oa.model;
  • com.hspedu.oa.controller;
  • 举例:
  • com.sina.crm.user/用户模块
  • com.sina.crm.order/订单模块
  • com.sina.crm.utils/工具类

常用的包

  • java.lang.* 小ang包是基本包,默认引入,不需要再引入,
  • java.util.* util包,系统提供的工具包,工具类,使用Scanner
  • java.net.* 网络包,网络开发
  • java.awt.* 是做java的界面开发

3. 如何导入包

我们引入一个包的主要目的是要使用该包下的类 比如import java.util.Scanner;就是引入一个类Scanner。 import java.util.* 表示将java.util包所有类都引入

我们需要使用到哪个类,就导入哪个类即可,不建议使用*导入

4. 细节和注意事项

  1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
  2. import指令位置放在packagel的下面,在类定义前面,可以有多句且没有顺序要求.

访问修饰符

1. 基本介绍

java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)

  1. 公开级别:用public修饰,对外公开
  2. 受保护级别:用protected修饰,对子类和同一个包中的类公开
  3. 默认级别没有修饰符号,向同一个包的类公开.
  4. 私有级别:用private修饰,只有类本身可以访问,不对外公开.

image.png

2. 使用的注意事项

  1. 修饰符可以用来修饰类中的属性,成员方法以及类
  2. 只有默认的和public才能修饰类!,并且遵循上述访问权限的特点。
  3. 成员方法的访问规则和属性完全一样.

封装

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

封装的理解和好处

  1. 隐藏实现细节方法(连接数据库)<-调用(传入参数)
  2. 可以对数据进行验证,保证安全合理

封装的实现步骤

  1. 将属性进行私有化private【不能直接修改属性】
  2. 提供一个公共的set方法,用于对属性判断并赋值

image.png

  1. 提供一个公共的(public)get方法,用于获取属性的值

image.png

alt + insert 可以快捷生成set和get方法

构造器和set和get结合

package com.hspedu.encap;

public class Encapsulation01 {

    public static void main(String[] args) {

        Person person = new Person();
        person.setName("韩顺平");
        person.setAge(30);
        person.setSalary(30000);
        System.out.println(person.info());
        System.out.println(person.getSalary());

        //如果我们自己使用构造器指定属性
        Person smith = new Person("smith", 80, 50000);
        System.out.println("====smith的信息======");
        System.out.println(smith.info());


    }
}
/*
那么在java中如何实现这种类似的控制呢?
请大家看一个小程序(com.hspedu.encap: Encapsulation01.java),
不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认
年龄, 必须在 1-120, 年龄, 工资不能直接查看 , name的长度在 2-6字符 之间

 */
class Person {
    public  String name; //名字公开
    private int age; //age 私有化
    private double salary; //..

    public void say(int n,String name) {

    }
    //构造器 alt+insert
    public Person() {
    }
    //有三个属性的构造器
    public Person(String name, int age, double salary) {
//        this.name = name;
//        this.age = age;
//        this.salary = salary;
        //我们可以将set方法写在构造器中,这样仍然可以验证
        setName(name);
        setAge(age);
        setSalary(salary);
    }

    //自己写setXxx 和 getXxx 太慢,我们使用快捷键
    //然后根据要求来完善我们的代码.
    public String getName() {
        return name;
    }
    public void setName(String name) {
        //加入对数据的校验,相当于增加了业务逻辑
        if(name.length() >= 2 && name.length() <=6 ) {
            this.name = name;
        }else {
            System.out.println("名字的长度不对,需要(2-6)个字符,默认名字");
            this.name = "无名人";
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        //判断
        if(age >= 1 && age <= 120) {//如果是合理范围
            this.age = age;
        } else {
            System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄18 ");
            this.age = 18;//给一个默认年龄
        }
    }

    public double getSalary() {
        //可以这里增加对当前对象的权限判断
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    //写一个方法,返回属性信息
    public String info() {
        return "信息为 name=" + name  + " age=" + age + " 薪水=" + salary;
    }
}

继承(代码复用性)

基本介绍

继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可

继承的示意图

image.png

基本语法

class 子类 extends 父类{
    
}

继承的细节问题

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

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

  3. 调用子类构造器的时候一定会先调用父类构造器,当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过

  4. super(父类某一构造器的参数);不写参数就代表调用父类的无参构造器前提是父类有无参构造器,

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

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

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

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

  9. 调用子类构造器时,一直向上追溯到顶级父类的构造器执行完,执行下一个子类构造器,直到执行完本类构造器

  10. 子类最多只能继承一个父类(指直接继承),即java中是单继承机制, 思考:如何让A类继承B类和C类?

先让A继承B,B在继承C

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

image.png

如何指定调用父类的哪一个构造器

package com.hspedu.extenddetail;

public class base extends sub{
    public base() {
        super("simith");
        System.out.println("子类的base构造器被调用");
    }
}

继承的本质(重要)

当子类对象创建好后,建立查找的关系

image.png

  1. 加载类信息(属性和方法信息)按照下面顺序进行加载
  2. object -> GrandPa -> Father -> Son
  3. 在堆中分配空间,进行默认初始化(看规则)
  4. 将地址赋给son
  5. 如果子类和父类有相同的name属性,调用该属性时就要看查找关系来返回信息,
  6. (1)首先看子类中是否有该属性
  7. (2)如果子类有这个属性,并且可以访问,则返回信息
  8. (3)如果没有。就看父类,并且可以访问,则返回信息
  9. (4)继续向上访问,直到object类,如果还没有就报错
  10. 只要有一个地方是private,访问时就被挡住了,就会报错,不会越过他继续向上找

继承练习1

//public class ExtendsExercise01 {
//    public static void main(String[] args) {
//        B b=new B();//a , b name, b
//    }
//}
//
//class A {
//    A() {
//        System.out.println("a");
//    }
//
//    A(String name) {
//        System.out.println("a name");
//    }
//}
//
//class B extends A {
//    B() {
//        this("abc");没有super,有this就没有super了
//        System.out.println("b");
//    }
//
//    B(String name) {
//        //默认有 super();
//        System.out.println("b name");
//    }
//}
//

main中:B b = new B();会输出什么

a , b name,b

  • 我是A类
  • 我是B类的有参构造
  • 我是c类的有参构造
  • 我是c类的无参构造

super关键字

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

基本语法

  1. 访问父类的属性,但不能访问父类的private属性
  2. 访问父类的方法,不能访问父类的private方法super.方法名(参数列表):
  3. 访问父类的构造器(这点前面用过):super(参数列表):只能放在构造器的第一句,只能出现一句!

构造器的好处和细节

  1. 调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子 类初始化)

  2. 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果!

        //cal();
        this.cal(); //等价 cal

        //找cal方法(super.call()) 的顺序是直接查找父类,其他的规则一样
        //super.cal();
  1. supe的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用 super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用superi访问遵循 就近原则。A->B->C

super和this的比较

image.png

方法重写(override)

基本介绍

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

重写和重载的比较

image.png

题目

1.编写一个Person类,包括属性/private(name、age),构造器、方法say(返
回自我介绍的字符串)。
2.编写一个Student类,继承Persona类,增加id、score属性/private,以及构造
器,定义sy方法(返回自我介绍的信息)。
3.在main中,分别创建Person和Studenti对象,调用say方法输出自我介绍。

person类

package com.hspedu.extenddetail.override.exercise2;

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

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void say(){
        System.out.println("我的名字是" + name + "我的年龄是" + age  );
    }

}

student类

package com.hspedu.extenddetail.override.exercise2;
//2.编写一个Student类,继承Persona类,增加id、score属性/private,以及构造
//        器,定义sy方法(返回自我介绍的信息)。
public class student extends Person{
    private String id;
    private int score;

    public student(String name, int age, String id, int score) {
        super(name, age);
        this.id = id;
        this.score = score;
    }
    public void  say(){
        super.say();
        System.out.println("id:" + id + " score:" + score);
    }
}

test类

package com.hspedu.extenddetail.override.exercise2;

public class Test {
    public static void main(String[] args) {
        student student = new student("jack",18,"2021010" ,75);
        student.say();

    }
}

多态

对象的多态(重点)

重要的几句话

  1. 一个对象的编译类型和运行类型可以不一致
  2. 编译类型在定义对象时,就确定了,不能改变
  3. 运行类型是可以变化的
  4. 编译类型看定义时=号的左边,运行类型看=号的右边
  • Animal animal=new Dog();
  • 【animal编译类型是Animal,运行类型Dog】
  • 父类的引用指向子类的对象
  • animal = new Cat();
  • 【animal的运行类型变成了Cat,编译类型仍然是Animal】

快速入门案例


    //使用多态机制,可以统一的管理主人喂食的问题
    //animal 编译类型是Animal,可以指向(接收) Animal子类的对象
    //food 编译类型是Food ,可以指向(接收) Food子类的对象
    public void feed(Animal animal, Food food) {
        System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
    }

    //主人给小狗 喂食 骨头
//    public void feed(Dog dog, Bone bone) {
//        System.out.println("主人 " + name + " 给 " + dog.getName() + " 吃 " + bone.getName());
//    }
//    //主人给 小猫喂 黄花鱼
//    public void feed(Cat cat, Fish fish) {
//        System.out.println("主人 " + name + " 给 " + cat.getName() + " 吃 " + fish.getName());
//    }

    //如果动物很多,食物很多
    //===> feed 方法很多,不利于管理和维护
    //Pig --> Rice
    //Tiger ---> meat ...
    //...

多态注意事项和细节

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

多态的向上转型

  1. 本质:父类的引用指向了子类的对象
  2. 语法:父类类型 引用名=new 子类类型():
  3. 特点:编译类型看左边,运行类型看右边。 可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员因为在编译阶段,能调用哪些成员,是由编译类型来决定的
  1. 最终运行效果看子类的具体实现!即调用方法时,按照从子类开始查找方法,然后调用。 编译类型必须先通过,再看运行类型

即属性看编译类型,方法调用看运行类型

多态的向下转型

  1. 语法:子类类型 引用名 = (子类类型) 父类引用:
  2. 只能强转父类的引用,不能强转父类的对象
  3. 要求父类的引用必须指向的是当前目标类型的对象
  4. 当向下转型后,可以调用子类类型中所有的成员

多态注意事项和细节讨论

属性没有重写之说!属性的值看编译类型

insatanceof操作符

instanceof 比较操作符,用于判断对像的运行类型是否为XX类型或XX类型的子类型

结果返回布尔值false或true

动态绑定机制(非常非常重要)

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

package com.hspedu.poly_.dynamic_;

public class DynamicBinding {
    public static void main(String[] args) {
        //a 的编译类型 A, 运行类型 B
        A a = new B();//向上转型
        System.out.println(a.sum());//?40 -> 30
        System.out.println(a.sum1());//?30-> 20
    }
}

class A {//父类
    public int i = 10;
    //动态绑定机制:

    public int sum() {//父类sum()
        return getI() + 10;//20 + 10
    }

    public int sum1() {//父类sum1()
        return i + 10;//10 + 10
    }

    public int getI() {//父类getI
        return i;
    }
}

class B extends A {//子类
    public int i = 20;

//    public int sum() {
//        return i + 20;
//    }

    public int getI() {//子类getI()
        return i;
    }

//    public int sum1() {
//        return i + 10;
//    }
}

多态的应用

1. 多态数组

数组的定义类型为父类类型,里面保存的实际元素类型为子类类型

应用实例:现有一个继承结构如下:要求创建1个Person对象、2个Student对象和2个 Teacher)对象,统一放在数组中,并调用每个对象say方法.

package com.hspedu.poly_.polyarr_;

public class PloyArray {
    public static void main(String[] args) {
        //应用实例:现有一个继承结构如下:要求创建1个Person对象、
        // 2个Student 对象和2个Teacher对象, 统一放在数组中,并调用每个对象say方法

        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20);
        persons[1] = new Student("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        //循环遍历多态数组,调用say
        for (int i = 0; i < persons.length; i++) {
            //老师提示: person[i] 编译类型是 Person ,运行类型是是根据实际情况有JVM来判断
            System.out.println(persons[i].say());//动态绑定机制
            //这里大家聪明. 使用 类型判断 + 向下转型.
            if(persons[i]  instanceof  Student) {//判断person[i] 的运行类型是不是Student
                Student student = (Student)persons[i];//向下转型
                student.study();
                //小伙伴也可以使用一条语句 ((Student)persons[i]).study();
            } else if(persons[i] instanceof  Teacher) {
                Teacher teacher = (Teacher)persons[i];
                teacher.teach();
            } else if(persons[i] instanceof  Person){
              
            } else {
                System.out.println("你的类型有误, 请自己检查...");
            }

        }

    }
}

应用实例升级:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study怎么调用?

多态参数

方法定义的形参类型为父类类型,实参类型允许为子类类型

  1. 应用实例1:前面的主人喂动物
  2. 应用实例2:定义员工类Employe包含姓名和月工资[private],以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法
  3. 测试类中添加一个方法showEmpAnnal(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()]
  4. 测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法

object类详解

equals方法

==和equals的对比(面试题)

==是一个比较运算符

  1. ==:既可以判断基本类型,又可以判断引用类型
  2. ==:如果判断基本类型,判断的是值是否相等。示例:inti=10;double d=10.0;
  3. ==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象【案例说明】
  4. equals:是Object:类中的方法,只能判断引用类型
  5. 默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如 Integer,,String

练习

Person p1 = new Person();
p1.name = "hspedu";

Person p2 = new Person();
p2.name = "hspedu";

System.out.println(p1==p2);
System.out.println (p1.name .equals(p2.name));
System.out.println(p1.equals(p2));

String s1 = new String("asdf");

String s2 = new String("asdf");
System.out.println(s1.equals(s2))
System.out.println(s1==s2);

class person{
    String name;
    }

f t f

t f

image.png t t f

f t

hashcode方法

返回对象的哈希码值

  1. 提高具有哈希结构的容器的效率!
  2. 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
  3. 两个引用,如果指向的是不同对象,则哈希值是不一样的
  4. 哈希值主要根据地址号来的!,不能完全将哈希值等价于地址。
  5. obj.hashCode[测试:Aobj1=newA():Aobj2=new A():A obj3 obj1]
  6. 后面在集合,中hashCode如果需要的话,也会重写
package com.hspedu.object_;

public class HashCode_ {
    public static void main(String[] args) {

        AA aa = new AA();
        AA aa2 = new AA();
        AA aa3 = aa;
        System.out.println("aa.hashCode()=" + aa.hashCode());
        System.out.println("aa2.hashCode()=" + aa2.hashCode());
        System.out.println("aa3.hashCode()=" + aa3.hashCode());


    }
}
class AA {}

toString 方法

返回该对象的字符串表示

√基本介绍 默认返回:全类名(包 + 类)+@+哈希值的十六进制,【查看Object的toString方法】 子类往往重写toString方法,用于返回对象的属性信息

当直接输出一个对象时,toString方法会被默认的调用,比如 System.out.println(monster)

package com.hspedu.object_;

public class ToString_ {
    public static void main(String[] args) {

        /*
        Object的toString() 源码
        (1)getClass().getName() 类的全类名(包名+类名 )
        (2)Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制字符串
        public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
         */

        Monster monster = new Monster("小妖怪", "巡山的", 1000);
        System.out.println(monster.toString() + " hashcode=" + monster.hashCode());

        System.out.println("==当直接输出一个对象时,toString 方法会被默认的调用==");
        System.out.println(monster); //等价 monster.toString()

    }
}

class Monster {
    private String name;
    private String job;
    private double sal;

    public Monster(String name, String job, double sal) {
        this.name = name;
        this.job = job;
        this.sal = sal;
    }

    //重写toString方法, 输出对象的属性
    //使用快捷键即可 alt+insert -> toString
    @Override
    public String toString() { //重写后,一般是把对象的属性值输出,当然程序员也可以自己定制
        return "Monster{" +
                "name='" + name + '\'' +
                ", job='" + job + '\'' +
                ", sal=" + sal +
                '}';
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("fin..");
    }
}

finalize

当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法

  1. 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法 做一些释放资源的操作【演示】
  2. 什么时候被回收:当某个对象没有任何引用时,则j就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法。
  3. 垃圾回收机制的调用,是由系统来决定,也可以通过System.gc0主动触发垃圾回收机制,测试:Car[name]
package com.hspedu.object_;

//演示 Finalize的用法
public class Finalize_ {
    public static void main(String[] args) {

        Car bmw = new Car("宝马");
        //这时 car对象就是一个垃圾,垃圾回收器就会回收(销毁)对象, 在销毁对象前,会调用该对象的finalize方法
        //,程序员就可以在 finalize中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件..)
        //,如果程序员不重写 finalize,那么就会调用 Object类的 finalize, 即默认处理
        //,如果程序员重写了finalize, 就可以实现自己的逻辑
        bmw = null;
        System.gc();//主动调用垃圾回收器

        System.out.println("程序退出了....");
    }
}
class Car {
    private String name;
    //属性, 资源。。
    public Car(String name) {
        this.name = name;
    }
    //重写finalize
    @Override
    protected void finalize() throws Throwable {
        System.out.println("我们销毁 汽车" + name );
        System.out.println("释放了某些资源...");

    }
}

把重写的方法名字打出来+enter就直接可以重写了