跳到主要内容

第9章-封装

概述

  • 在使用面向对象的思想进行抽象的过程,也就是按照业务特性定义类的过程,就是封装
  • 封装主要目的是让类足够独立(内聚),只包含职责相关的内容,并需要隐藏类的细节,只暴露出想要展现的方法
  • 生活场景
    • 鼠标,这个天天都在用的电脑配件,主要只有左键、右键、滚轮三个与人交互的按钮;但其内部,实际是经过了很多代的迭代,从最早的有线、滚动使用小圆球方式,到无线,到滚动使用红外线,到无线电波。---这就是一个典型的封装,与人交互的永远不变,照顾使用习惯,但内部隐藏功能部分,则持续修改
    • 汽车,对驾驶人操作提供支持的主要功能,从有以来,没有太大变化,离合、刹车、油门、档位等;但内部,涉及了成千上万的零件,在持续的迭代,像发动机,有1.5T、2.0T等不同容量或是电动车的电机,刹车制动方式有多种,或多种配合。---这也是典型的封装,把复杂的隐藏起来,上百年来提供统一的操作体验(即暴露稳定的接口)
    • 在企业某个HR系统(或是学校的学生系统中),为了员工(或学生)之间能相互联系,会对其他同学展现学生姓名、性别、参加的社团等,但肯定不会展现工资、银行卡号、家庭住址等信息
    • ...
  • 对于这些生活场景的软件设计与开发过程中,具体到定义某个类,都不是随意的,里面的所有的属性和方法都必须是与这个类相关的;同时这些属性和方法,面向其他类是否能访问,能访问哪个,也是要仔细思考的
  • 封装时要注意的一些原则和细节
    • 业务相关性,比如,一般定义的老师类中有教龄属性、授课方法,但学生类就不会有这个教龄属性、授课方法
    • 隐藏细节,不呈现一些私密属性,不展示一些特别的加工方法;比如,一个员工类,银行账户属性就不能向其他类随意访问;一个账户类,就不能把里面登录时加密的细节公开出去
    • 暴露方法,类不是自闭的,必须与其他类协同才能工作,通过方法定义,结合访问修饰符,暴露需要的接口;比如,一个账户类,里面的加密方法不对外暴露,但登录方法则会对外暴露,作为类与外部类交互窗口
    • 设置访问权限,暴露的接口中,有些可能希望A能访问,有些可能B才能访问,这也是实际的需求,多使用访问修饰符来处理
    • 附加业务规则,一些属性的值其实是有限定业务规则的,比如学生的年龄属性,就不能小于0

好封装的优点

  • 代码更合理、更内聚,所有的属性与方法,都与类直接相关,职责更清晰
  • 安全、体验更好,隐藏不需要使用者关注的复杂细节
  • 使用简单,只暴露必须且简单的接口,其他类使用更容易,交互更简便
  • 代码可维护性好,只要暴露的接口不变,可以随时调整实现逻辑
  • ...

访问修饰符

  • 用于控制类、属性、方法、接口等的可访问性,实现封装的细节隐藏与接口暴露

  • 主要包括public、protected、默认访问修饰符(不写)、private(对类和接口一般只有public、默认访问修饰符),具体规则如下:

    表示可以访问,否则不能访问

    封装-访问修饰符

    访问修饰符规则
  • 应用实例

    • 实例1,访问修饰符,进行代码演示;具体看“代码和工具”下本章的应用实例目录中的access-modifier项目

【练习】

  1. 练习实例内容,动手熟悉访问修饰符特点

属性封装

  • 基于业务和封装的要求,属性不应该再被直接访问,而应该定义为private,使用方法进行访问

  • 所谓属性访问,即设置属性值获取属性值,也称为gettersetter

  • 另外,根据实际的业务,可能存在只读属性只写属性,如下面的性别昵称、密码属性

  • 并且,在设置和获取时,可以施加一些业务逻辑限制

  • 应用实例

    • 应用实例1,属性封装,标准getter/setter

      实例代码

      package com.bjpowernode.demo;

      /**
      * 工作人员类
      */
      public class Employee {
      //属性,一般都设置为private
      //姓名
      private String name;
      //性别
      private char sex;
      //年龄
      private int age;
      //密码
      private String password;
      //血型
      private String bloodType;

      //getter/setter
      //名称
      public String getName() {
      //姓名返回脱敏,substring取第1个字符
      String desensitizationName = this.name.substring(0, 1) + "**";

      return desensitizationName;
      }
      public void setName(String name) {
      //添加限制
      if (name == null) {
      System.out.println("姓名不能为空!");
      } else {
      //设置值
      this.name = name;
      }
      }

      //性别
      public char getSex() {
      return sex;
      }
      public void setSex(char sex) {
      //添加限制
      if (sex != '男' && sex != '女') {
      System.out.println("性别只能是[男]或[女]!");
      } else {
      this.sex = sex;
      }
      }

      //年龄
      public int getAge() {
      return age;
      }
      public void setAge(int age) {
      //添加限制
      if (age < 0) {
      System.out.println("年龄不能小于0岁!");
      } else {
      this.age = age;
      }
      }

      //密码,【只写】
      public void setPassword(String password) {
      //添加限制
      if (password == null) {
      System.out.println("密码不能为空!");
      } else {
      if (password.length() < 6) {
      System.out.println("密码长度不能小于6位!");
      } else {
      //设置值
      this.password = password;
      }
      }
      }

      //血型,【只读】
      public String getBloodType(){
      return this.bloodType;
      }

      //构造方法
      //无参构造
      public Employee() {
      }

      //有参构造
      public Employee(String name, char sex, int age,String password,String bloodType) {
      this.name = name;
      this.sex = sex;
      this.age = age;
      this.password = password;
      this.bloodType = bloodType;
      }

      /**
      * 入口方法
      *
      * @param args
      */
      public static void main(String[] args) {
      //定义引用,并指向新建对象
      Employee employee = new Employee("张三",'男',18,"123456","B型");

      //设置属性
      //姓名
      employee.setName("尼古拉斯.赵四");
      //性别
      employee.setSex('a'); //设置失败
      employee.setSex('女'); //设置成功
      //年龄
      employee.setAge(-30); //设置失败
      employee.setAge(30); //设置成功
      //密码,只写
      employee.setPassword("123"); //设置失败,长度不够
      employee.setPassword("123456"); //设置成功

      //血型
      // employee.setBloodType("A型"); //设置失败,没有此方法

      //输出密码
      //System.out.println(employee.getPassword()); //获取失败,没有定义该getter方法

      //使用属性,输出
      System.out.println("当前工作人员信息:[" + employee.getName() + "," + employee.getSex() + "," + employee.getAge() + "," + employee.getBloodType() + "]");
      }
      }

      输出结果

      封装-getter&setter

【练习】

  1. 练习实例内容,完成代码编写;掌握属性封装

方法封装

  • 基于业务和封装的要求,不同的方法具备不同的访问特性

    • 对外访问,用于被其他类调用,要保持稳定;一般使用public访问修饰符修饰;如应用实例中的login方法
    • 对内访问,用于类内部、包内部调用,主要是根据业务特性、代码编写规划定义,相对灵活;一般使用private或默认修饰符修饰;如应用实例中的check方法
    • 子类访问,用于被子类继承,体现父类模板特性,让所有子类具备此类访问;一般使用protected访问修饰符修饰
  • 在封装的方法中,可根据业务,编写合理的代码,并调用其他类的方法协同工作

  • 应用实例

    • 应用实例1,方法封装,并换一个类中使用main尝试调用

      实例代码

      package com.bjpowernode.demo;

      /**
      * 用户登录类
      */
      public class UserLogin {
      /**
      * 验证密码,封装原因:1、与User类相关;2、类内部使用,对内使用,相对灵活
      * @param username 用户名
      * @param password 密码
      * @return 验证结果
      */
      private boolean check(String username, String password){
      //密码验证逻辑
      if(username.equalsIgnoreCase("jack") && password.equalsIgnoreCase("123456")){
      return true;
      }else{
      return false;
      }
      }

      /**
      * 登录,v1.0版本,封装特点:1、与User类相关;2、对外接口,需要保持稳定,方法签名一般不变
      * @param username 用户名
      * @param password 密码
      * @return 是否登录成功
      */
      public String login(String username,String password){
      String result = "登录成功!";

      //验证用户名密码
      boolean checkResult = this.check(username,password);
      if(checkResult == false){
      result = "登录失败!";
      }

      return result;
      }

      /**
      * 登录,v2.0版本,重载,封装特点:1、与User类相关;2、对外接口,需要保持稳定,方法签名一般来变
      * @param username 用户名
      * @param password 密码
      * @return 是否登录成功
      */
      public String login(String username,String password,boolean rememberMe){
      String result = "登录成功!";

      //验证用户名和密码
      boolean checkResult = this.check(username,password);
      if(checkResult == false){
      result = "登录失败!";
      }
      //将rememberMe保存到缓存,调用其他类完成 todo

      return result;
      }
      }

    【练习】

    1. 练习实例内容,完成代码编写;掌握方法封装

this

概述

  • 在对象内部存在,指代当前对象的一个关键字

  • 定义于类(模板)中,但应用在实际的对象中

  • “ 一千个读者眼中就会有一千个哈姆雷特 ”,通过同一个类,创建的1000个对象,也各有特色,具备自己的this

  • this在JVM中的示意图,可以更准确的认识this

    封装-this

  • 一般在普通方法和构造方法中使用

    • 普通方法(实例方法)中,用于调用当前对象的其他属性或方法,直接通过“this.属性”、"this.方法名()"调用,简单、快捷
    • 构造方法中放置于第1行,用于调用当前对象的其他构造方法,直接通过“this(参数列表)”调用,减少多个构造方法中重复的初始化代码;注意,”this(参数列表)“必须在构造方法的第一行
  • 不能在static修饰的静态方法中使用

应用实例

  • 实例1,在普通方法中使用this

    实例代码

    package com.bjpowernode.demo;

    /**
    * 工作人员类,在普通方法中使用this
    */
    public class Employee {
    //属性,一般都设置为private
    //姓名
    private String name;
    //性别
    private char sex;
    //年龄
    private int age;

    //getter/setter
    public String getName() {
    return name;
    }

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

    public char getSex() {
    return sex;
    }

    public void setSex(char sex) {
    this.sex = sex;
    }

    public int getAge() {
    return age;
    }

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

    /**
    * 跟别人打招呼
    *
    * @param name 打招呼的人
    */
    public void sayToSomeone(String name) {
    //【在普通方法中使用this】this调用属性
    System.out.println(this.name + "向" + name + "打招呼!");
    }

    /**
    * 拍一拍
    *
    * @param name 拍一拍的人
    */
    public void tapToSomeone(String name) {
    System.out.println(this.name + "拍了拍" + name + "的肩膀!");

    //【在普通方法中使用this】this调用方法
    this.sayToSomeone(name);
    }

    /**
    * 入口方法
    *
    * @param args
    */
    public static void main(String[] args) {
    //定义引用,并指向新建对象
    Employee employee = new Employee();

    //设置属性
    employee.setName("尼古拉斯.赵四");

    //跟张三打招呼
    employee.sayToSomeone("张三");

    //拍了拍李四
    employee.tapToSomeone("李四");
    }
    }

输出结果,运行代码查看

  • 实例2,在构造方法中使用this

    实例代码

    package com.bjpowernode.demo;

    /**
    * 工作人员类,在构造方法中使用this
    */
    public class Employee {
    //属性,一般都设置为private
    //姓名
    private String name;
    //性别
    private char sex;
    //年龄
    private int age;

    /**
    * 构造方法,带全部参数的初始化
    *
    * @param name
    * @param sex
    * @param age
    */
    public Employee(String name, char sex, int age) {
    //使用this访问属性
    this.name = name;
    this.sex = sex;
    this.age = age;
    }

    /**
    * 无参构造
    */
    public Employee() {
    //【在构造方法中使用this】使用另一个有参构造完成,必须放在第1行
    this("匿名", '男', 0);
    }

    //getter/setter
    public String getName() {
    return name;
    }

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

    public char getSex() {
    return sex;
    }

    public void setSex(char sex) {
    this.sex = sex;
    }

    public int getAge() {
    return age;
    }

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

    /**
    * 入口方法
    *
    * @param args
    */
    public static void main(String[] args) {
    //定义引用,并指向新建对象
    Employee employee = new Employee();

    //输出工作人员信息
    System.out.println("当前工作人员:[" + employee.getName() + "," + employee.getSex() + "," + employee.getAge() + "]");
    }
    }

    输出结果,运行代码查看

  • 实例3,验证this指向的是不同的对象

    实例代码

    package com.bjpowernode.demo;

    /**
    * 工作人员类
    */
    public class Employee {
    /**
    * 输出自己的this
    */
    public void printSelfThis(){
    System.out.println(this);
    }

    /**
    * 入口方法
    *
    * @param args
    */
    public static void main(String[] args) {
    //定义引用,并指向新建对象
    Employee employee1 = new Employee();
    //定义引用,并指向新建对象
    Employee employee2 = new Employee();
    //定义引用,并指向新建对象
    Employee employee3 = new Employee();

    //分别输出
    System.out.println("--------employee1--------");
    employee1.printSelfThis();
    System.out.println(employee1);
    System.out.println(employee1.toString());
    System.out.println("--------employee2--------");
    employee2.printSelfThis();
    System.out.println(employee2);
    System.out.println(employee2.toString());
    System.out.println("--------employee3--------");
    employee3.printSelfThis();
    System.out.println(employee3);
    System.out.println(employee3.toString());
    }
    }
    1. 输出结果

      封装-this输出

      结果解析

      • 从上图可见,每个printSelfThis方法输出的this、使用时直接输出对象、使用时输出对象.toString()方法,其实输出的结果都是当前对象类型+序号,其中的序号也称为hashCode,每个对象唯一,使用16进制表示;这个内容,其实是对象的唯一标识
      • 从上图可知,3个对象中的this,指向并不相同

【练习】

  1. 提问:

    1. 下面的代码是否有问题?为什么?

      package com.bjpowernode.demo;

      /**
      * 工作人员类
      */
      public class Employee {
      //姓名
      private String name;

      /**
      * 打招呼
      */
      public static void sayHello(){
      //使用this
      System.out.println(this.name);
      }
      }
    2. 下面的代码中,打了?号注释的代码,为什么要用this.name?与后面的name有啥区别?

        package com.bjpowernode.demo;

      /**
      * 工作人员类
      */
      public class Employee {
      //姓名
      private String name;

      public void setName(String name) {
      //?
      this.name = name;
      }
      }
  2. 练习实例内容,完成代码编写;掌握this关键字的使用

static

概述

  • 表示静态的,不依赖于对象存在,依赖类存在
  • 能被所有类的对象共用,一个类有且只有一份所有对象共用
  • 常用来修饰属性、方法、代码块
  • 使用static关键字

静态属性

  • 定义方式与成员变量(属性)类似,访问修饰符后加static关键字,则就变成了类的静态属性

  • 静态属性的getter/setter方法,也必须是静态的

  • 应用实例

    • 应用实例1,静态属性,员工记数

      实例代码

      package com.bjpowernode.demo;

      /**
      * 工作人员类,员工记数
      */
      public class Employee {
      //静态属性,工作人员数量,并设置初始值
      public static int employeeCount = 0;

      //普通属性
      //姓名
      private String name;

      /**
      * 构造方法
      *
      * @param name 姓名
      */
      public Employee(String name) {
      this.name = name;

      //每生成一个工作人员对象,数量加1
      Employee.employeeCount++;
      }

      //getter/setter
      public String getName() {
      return name;
      }

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

      /**
      * 入口方法
      *
      * @param args
      */
      public static void main(String[] args) {
      //输出初始工作人员数量
      System.out.println("初始工作人员数量:" + Employee.employeeCount);

      //创建对象
      new Employee("张三");
      Employee e1 = new Employee("李四");
      new Employee("");
      new Employee("赵四");
      //循环创建
      for (int i = 0; i < 5; i++) {
      new Employee(i + "");
      }

      //输出现在工作人员数量
      System.out.println("此时工作人员数量:" + Employee.employeeCount);
      }
      }

      输出结果,运行程序查看

静态方法

  • 在一些实际应用中,有些方法可能从业务上来说就不依赖类的对象而存在;也有些工具类,不需要生成对象

  • 此时,这些方法就可以定义成静态方法

  • 应用实例

    • 应用实例1,静态方法,批量生成虚拟员工

      实例代码

      package com.bjpowernode.demo;

      /**
      * 工作人员类,静态方法,批量创建虚拟工作人员
      */
      public class Employee {
      //成员变量
      //姓名
      private String name;

      /**
      * 生成虚拟工作人员,就不依赖某个对象而产生,而是由类进行,所以是静态的
      *
      * @param count
      * @return
      */
      public static Employee[] generateVitualEmployee(int count) {
      //定义数组
      Employee[] employees = new Employee[count];

      for (int i = 0; i < count; i++) {
      //创建对象
      Employee employee = new Employee();
      //设置属性
      employee.name = "虚拟员工" + i;

      //添加到数组
      employees[i] = employee;
      }

      return employees;
      }

      //getter/setter
      public String getName() {
      return name;
      }

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

      /**
      * 入口方法
      *
      * @param args
      */
      public static void main(String[] args) {
      //创建20个虚拟工作人员
      Employee[] employees = Employee.generateVitualEmployee(20);

      //输出每个工作人员姓名
      for (int i = 0; i < employees.length; i++) {
      System.out.println("当前工作人员是:" + employees[i].name);
      }
      }
      }

      输出结果,运行程序查看

    • 应用实例2,静态工具类;员工信息检查工具类

      实例代码

      员工验证工具类,注意其中的静态验证方法

      package com.bjpowernode.demo;

      /**
      * 员工信息验证工具
      */
      public class EmployeeCheckUtil {
      /**
      * 验证密码
      * @param password 密码
      * @return 验证结果
      */
      public static boolean checkPassword(String password) {
      //验证结果,默认为验证通过
      boolean result = true;

      //验证
      if (password == null || password.length() < 6) {
      result = false;
      }

      return result;
      }

      /**
      * 验证性别
      * @param sex 性别
      * @return 验证结果
      */
      public static boolean checkSex(char sex){
      //验证结果,默认为验证通过
      boolean result = true;

      //验证
      if (sex != '男' && sex != '女') {
      result = false;
      }

      return result;
      }
      }

      员工类,注意其中的性别设置、密码设置方法,使用了员工验证工具类的静态验证方法

      package com.bjpowernode.demo;

      /**
      * 工作人员类,使用了员工信息验证工具类
      */
      public class Employee {
      //属性,一般都设置为private
      //姓名
      private String name;
      //性别
      private char sex;
      //年龄
      private int age;
      //密码
      private String password;

      //getter/setter
      //名称
      public String getName() {
      //姓名返回脱敏,substring取第1个字符
      String desensitizationName = this.name.substring(0, 1) + "**";

      return desensitizationName;
      }
      public void setName(String name) {
      //添加限制
      if (name == null) {
      System.out.println("姓名不能为空!");
      } else {
      //设置值
      this.name = name;
      }
      }

      //性别
      public char getSex() {
      return sex;
      }
      public void setSex(char sex) {
      //添加验证,使用工具类
      if(EmployeeCheckUtil.checkSex(sex)){
      this.sex = sex;
      }else{
      System.out.println("性别只能是[男]或[女]!");
      }
      }

      //性别昵称,只读
      public String getSexNick() {
      String sexNick = "";

      switch (this.sex) {
      case '男':
      sexNick = "伢子";
      break;
      case '女':
      sexNick = "妹陀";
      break;
      }

      return sexNick;
      }

      //年龄
      public int getAge() {
      return age;
      }
      public void setAge(int age) {
      //添加限制
      if (age < 0) {
      System.out.println("年龄不能小于0岁!");
      } else {
      this.age = age;
      }
      }

      //密码,只写
      public void setPassword(String password) {
      //添加验证,使用工具类
      if(EmployeeCheckUtil.checkPassword(password)){
      //设置值
      this.password = password;
      }else {
      System.out.println("密码不能为空,且长度不能小于6位");
      }
      }

      /**
      * 入口方法
      *
      * @param args
      */
      public static void main(String[] args) {
      //定义引用,并指向新建对象
      Employee employee = new Employee();

      //设置属性
      //姓名
      employee.setName("尼古拉斯.赵四");
      //性别
      employee.setSex('a'); //设置失败
      employee.setSex('女'); //设置成功
      //年龄
      employee.setAge(-30); //设置失败
      employee.setAge(30); //设置成功
      //密码,只写
      employee.setPassword("123"); //设置失败,长度不够
      employee.setPassword("123456"); //设置成功

      //输出密码
      //System.out.println(employee.getPassword()); //获取失败,没有定义该getter方法

      //使用属性,输出
      System.out.println("当前工作人员信息:[" + employee.getName() + "," + employee.getSex() + "(" + employee.getSexNick() + ")," + employee.getAge() + "]");
      }
      }

静态代码块【扩展】

  • 多用于在第一个对象创建前,初始化一些资源

  • 定义在类内部

  • 应用相对较少

  • 应用实例

    • 应用实例1,静态代码块,提前初始化

      实例代码

      package com.bjpowernode.demo;

      /**
      * 工作人员类,静态方法,批量创建虚拟工作人员
      */
      public class Employee {
      //静态代码块1
      static {
      System.out.println("静态代码块1");
      }

      //静态代码块2
      static {
      System.out.println("静态代码块2");
      }

      //成员变量
      //姓名
      private String name;

      //getter/setter
      public String getName() {
      return name;
      }

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

      /**
      * 无参构造函数
      */
      public Employee(){
      System.out.println("构造函数");
      }

      /**
      * 入口方法
      *
      * @param args
      */
      public static void main(String[] args) {
      //创建多个对象查看效果
      new Employee();
      Employee employee = new Employee();
      new Employee();
      }
      }

      输出结果,运行程序查看

【练习】

  1. 提问:下面的代码正确吗?为什么?

    1. package com.bjpowernode.demo;

      public class Demo {
      public static void main(String[] args) {
      System.out.println("敲代码真快乐。");

      static{
      System.out.println("敲代码真快乐。");
      }
      }
      }
    2. package com.bjpowernode.demo;

      public class Demo {
      //年龄
      int age;

      public static void main(String[] args) {
      System.out.println(age);
      }
      }
    3. package com.bjpowernode.demo;

      public class Demo {
      //年龄
      int age;

      //静态代码块
      static {
      age = 18;
      }
      }
  2. 练习实例内容,完成代码编写;掌握static静态属性、static静态方法的使用,了解static静态代码块的使用和特性

实战和作业

  1. 编写程序,封装一个Book类,要求如下

    1. 具有属性:书名(title)、页数(pageNumber)
    2. 提供属性的getter/setter
    3. 页数不能少于200页,如果设置为少于200页时,提示页数不够
    4. 具备方法detail,输出书名和页数

    再编写一个BookTest类,测试Book类的逻辑

  2. 编写程序,封装一个Account类,要求如下

    1. 具有属性:账号(id)、姓名(name)、余额(balance)
    2. 提供属性getter/setter
    3. 具备方法:
      1. 存款方法deposit,要求存款金额不能小于0
      2. 取款方法withdraw,要求取款金额不能超过余额

    编写AccountTest类,测试Account类的逻辑

  3. 【扩展】重构上一章的Employee类和School类,具体要求

    1. 新加CommonUtil工具类,其中所有方法都是静态方法,包括
      1. 脱敏方法,类似于微信支付转账时显示的用户名效果,接收需要脱敏的原始字符串和替代字符两个参数,返回脱敏后的字符串;如果原始字符串是2个字符,只显示最后一个字符,如果超过2个字符,则只显示第1个和最后1个字符;如:方法接收参数是"张三"和'*',则返回"*三";方法接收参数为"尼古拉斯.赵四"和'-',则返回"尼-四"
      2. 整数范围限制验证方法,检查输入的整数是否在合理范围之内,接收需要验证的整数、最小值和最大值;如:方法接收15、1、10,则返回false;方法接收参数为5,2,8,则返回true
    2. Employee类,重构成getter/setter访问,并对属性进行限制
      1. 姓名,进行脱敏,使用CommonUtil工具类的脱敏方法
      2. 性别,做合适的处理
      3. 年龄,做合适的处理
    3. School类,重构成getter/setter访问,并对属性进行限制
      1. 类型,必须是使用的1~10之间,使用CommonUtil工具类的整数范围验证方法
      2. 员工列表,不能为null且员工数量必须大于等于1
  4. 【扩展】编写程序,封装Customer类、Employee类和EmployeeTest类,要求如下

    1. Customer类,客户类
      1. 具有属性:ID、姓名、年龄
      2. 提供属性的getter/setter
      3. 具备方法toString,返回所有属性信息
    2. Employee类,员工类
      1. 具备属性:ID、姓名、职级、是否为领导、领导(Employe类型)、客户列表(Customer[]类型)
      2. 提供属性的getter/setter
      3. 具备方法
        1. toString,返回ID、姓名、职级、领导姓名
        2. showCustomers,输出所有客户列表信息
    3. EmployeeTest类
      1. 定义一个管理者员工(是否为领导值为真)
        1. 设置id、姓名、职级为1、”张老大“、"L9"
      2. 定义一个普通员工(是否为领导值为假)
        1. 设置id、姓名、职级为7、”陈小云“、"L3"
        2. 设置他的领导为“张老大”
        3. 定义5个客户类对象,并分配给他
      3. 输出如下内容
        1. 普通员工“陈小云”的信息
        2. 普通员工“陈小云”的领导的信息
        3. 普通员工“陈小云”的所有客户信息