案例简介

在某一个学校开设了一个专业,叫做计算机科学与应用,专业编号J0001,学制为4年。
有四名学生报名学习了本专业,每个人的具体信息如下所示:

综合案例——学生信息管理 - 图1

通过Java面向对象的语言来实现这样的场景,最后输出的内容如下所示:
综合案例——学生信息管理 - 图2

案例分析与实现

通过对该场景的分析,我们可以得到这样的两个类
综合案例——学生信息管理 - 图3

编写专业类及其测试类

  1. package com.dodoke.school.model;
  2. /**
  3. * 专业类
  4. * @author LiXinRong
  5. *
  6. */
  7. public class Subject {
  8. //成员属性:学科名称、学科编号、学制年限
  9. private String subjectName;
  10. private String subjectNo;
  11. private int subjectLife;
  12. public String getSubjectName() {
  13. return subjectName;
  14. }
  15. public void setSubjectName(String subjectName) {
  16. this.subjectName = subjectName;
  17. }
  18. public String getSubjectNo() {
  19. return subjectNo;
  20. }
  21. public void setSubjectNo(String subjectNo) {
  22. this.subjectNo = subjectNo;
  23. }
  24. public int getSubjectLife() {
  25. return subjectLife;
  26. }
  27. public void setSubjectLife(int subjectLife) {
  28. if(subjectLife < 0) {
  29. return;//直接使用return退出该setSubjectLife方法,使学制年限保存默认值为0
  30. }
  31. this.subjectLife = subjectLife;
  32. }
  33. public Subject() {
  34. }
  35. public Subject(String subjectName, String subjectNo, int subjectLife) {
  36. super();
  37. this.setSubjectLife(subjectLife);
  38. this.setSubjectName(subjectName);
  39. this.setSubjectNo(subjectNo);
  40. }
  41. /**
  42. * 该方法用于描述专业信息
  43. * @return 返回描述专业相关信息的字符串
  44. */
  45. public String info() {
  46. //字符串的拼接,尽量不要写成一长串的形式,会让人看得难受
  47. String str = "专业信息如下:\n专业名称:" + this.getSubjectName() + "\n";
  48. str += "专业编号:" + this.getSubjectNo() + "\n";
  49. str += "学制年限:" + this.getSubjectNo() + "年";
  50. return str;
  51. }
  52. }

测试类

package com.dodoke.school.test;
import com.dodoke.school.model.Subject;
public class SchoolTest {
    public static void main(String[] args) {
        Subject sub = new Subject("计算机科学与应用","J0001",4);
        //info()方法为什么不直接打印输出:我们现在学习的语言环境是比较简陋的,只用在控制台打印输出信息
        //如果需要在网页,在app中输出信息,则返回值为String类型的info()方法,可复用性更高一些
        System.out.println(sub.info());
    }
}

编写学生类及其测试

package com.dodoke.school.model;
public class Student {
    //成员属性:学号,姓名,性别,年龄
    private String studentNo;

    private String studentName;

    private String studentSex;

    private int studentAge;
    public String getStudentNo() {
        return studentNo;
    }
    public void setStudentNo(String studentNo) {
        this.studentNo = studentNo;
    }
    public String getStudentName() {
        return studentName;
    }
    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }
    public String getStudentSex() {
        return studentSex;
    }

    /**
     * 性别限制为男或女,反之,强制限制为男
     * @param studentSex
     */
    public void setStudentSex(String studentSex) {
        if(studentSex.equals("男") || studentSex.equals("女")) {
            this.studentSex = studentSex;
        } else {
            this.studentSex = "男";
        }
    }
    public int getStudentAge() {
        return studentAge;
    }

    /**
     * 给年龄赋值,限制范围在10-100之间,赋值设置为18岁
     * @param studentAge
     */
    public void setStudentAge(int studentAge) {
        if(studentAge > 10 && studentAge < 100) {
            this.studentAge = studentAge;
        } else {
            this.studentAge = 18;
        }
    }

    //无参构造
    public Student() {
        super();
    }

    //多参构造,实现对全部属性的赋值
    public Student(String studentNo, String studentName, String studentSex, int studentAge) {
        super();
        this.setStudentNo(studentNo);
        this.setStudentName(studentName);
        this.setStudentSex(studentSex);
        this.setStudentAge(studentAge);
    }

    /**
     * 学生自我介绍
     * @return 用于返回描述学生信息字符串
     */
    public String introduction() {
        String str = "学生信息如下:\n姓名:" + this.getStudentName() + "\n";
        str += "学号:" + this.getStudentNo()+ "\n";
        str += "性别:" + this.getStudentSex()+ "\n";
        str += "年龄:" + this.getStudentAge();
        return str;
    }
}

测试:

public class SchoolTest {
    public static void main(String[] args) {
        Subject sub = new Subject("计算机科学与应用","J0001",4);
        //info()方法为什么不直接打印输出:我们现在学习的语言环境是比较简陋的,只用在控制台打印输出信息
        //如果需要在网页,在app中输出信息,则返回值为String类型的info()方法,可复用性更高一些
        System.out.println(sub.info());
        System.out.println("=============================");
        Student stu0 = new Student("S01","张三","男", 18);
        System.out.println(stu0.introduction());
    }
}

专业与学生关联起来

方案一:重载introduction方法,添加专业信息

/**
 * 学生自我介绍 + 所学专业
 * @param subjectName 所学专业名称
 * @param subjectLife 学制年限
 * @return 用于返回描述学生信息和所学专业的字符串
 */
public String introduction(String subjectName, int subjectLife) {
    String str = "学生信息如下:\n姓名:" + this.getStudentName() + "\n";
    str += "学号:" + this.getStudentNo()+ "\n";
    str += "性别:" + this.getStudentSex()+ "\n";
    str += "年龄:" + this.getStudentAge() + "\n";
    str += "所报专业名称:" + subjectName + "\n";
    str += "学制年限:" + subjectLife + "\n";
    return str;
}

关联:

public static void main(String[] args) {
    Subject sub = new Subject("计算机科学与应用","J0001",4);
    System.out.println(sub.info());
    System.out.println("=============================");
    Student stu0 = new Student("S01","张三","男", 18);
    System.out.println(stu0.introduction());
    System.out.println("=============================");
    Student stu1 = new Student("S02","李四","女", 19);
    System.out.println(stu1.introduction("计算机科学与应用", 4));
}

方案二:重载introduction方法,将专业对象作为参数

/**
 * 学生自我介绍 + 所学专业
 * @param sub 专业对象
 * @return 用于返回描述学生信息和所学专业的字符串
 */
public String introduction(Subject sub) {
    String str = "学生信息如下:\n姓名:" + this.getStudentName() + "\n";
    str += "学号:" + this.getStudentNo()+ "\n";
    str += "性别:" + this.getStudentSex()+ "\n";
    str += "年龄:" + this.getStudentAge() + "\n";
    str += "所报专业名称:" + sub.getSubjectName() + "\n";
    str += "学制年限:" + sub.getSubjectLife() + "\n";
    return str;
}

关联:

public class SchoolTest {
    public static void main(String[] args) {
        Subject sub = new Subject("计算机科学与应用","J0001",4);
        System.out.println(sub.info());
        System.out.println("=============================");
        Student stu0 = new Student("S01","张三","男", 18);
        System.out.println(stu0.introduction());
        System.out.println("=============================");
        Student stu1 = new Student("S02","李四","女", 19);
        System.out.println(stu1.introduction("计算机科学与应用", 4));
        System.out.println("=============================");
        Student stu2 = new Student("S03","王五","男", 18);
        System.out.println(stu2.introduction(sub));
    }
}

通过对象传参,可以一次性的获取对象中的所有信息。比如我们可以在方法中获取对象的专业编号,这是之前两个方法在不改变参数的情况下无法做到的。

方案三:将专业对象作为成员属性存在

在进入大学的时候,每个人都需要先选择一个专业作为自己学习的方向,因此,我们可以将专业的信息作为学生的一个属性

package com.dodoke.school.model;
public class Student {
    //成员属性:学号,姓名,性别,年龄 专业
    private String studentNo;

    private String studentName;

    private String studentSex;

    private int studentAge;

    //使用对象作为属性和使用基本数据类型作为属性没有区别-都是数据类型 + 属性名
    //注意:此时的sub对象没有被实例化,依旧是默认值null
    private Subject sub;
    public String getStudentNo() {
        return studentNo;
    }
    public void setStudentNo(String studentNo) {
        this.studentNo = studentNo;
    }
    public String getStudentName() {
        return studentName;
    }
    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }
    public String getStudentSex() {
        return studentSex;
    }

    /**
     * 性别限制为男或女,反之,强制限制为男
     * @param studentSex
     */
    public void setStudentSex(String studentSex) {
        if(studentSex.equals("男") || studentSex.equals("女")) {
            this.studentSex = studentSex;
        } else {
            this.studentSex = "男";
        }
    }
    public int getStudentAge() {
        return studentAge;
    }

    /**
     * 给年龄赋值,限制范围在10-100之间,赋值设置为18岁
     * @param studentAge
     */
    public void setStudentAge(int studentAge) {
        if(studentAge > 10 && studentAge < 100) {
            this.studentAge = studentAge;
        } else {
            this.studentAge = 18;
        }
    }

    /**
     * 获取专业对象,如果没有实例化,先实例化再返回
     * @return
     */
    public Subject getSub() {
        //为安全起见,在返回时,判断sub是否已经被实例化了
        if(this.sub == null) {
            this.sub = new Subject();
        }
        return sub;
    }
    public void setSub(Subject sub) {
        //此处就是通过外部参数对sub属性进行实例化
        this.sub = sub;
    }
    //无参构造
    public Student() {
        super();
    }

    //多参构造
    public Student(String studentNo, String studentName, String studentSex, int studentAge) {
        super();
        this.setStudentNo(studentNo);
        this.setStudentName(studentName);
        this.setStudentSex(studentSex);
        this.setStudentAge(studentAge);
    }

    //多参构造,实现对全部属性的赋值
    public Student(String studentNo, String studentName, String studentSex, int studentAge, Subject sub) {
        super();
        this.setStudentNo(studentNo);
        this.setStudentName(studentName);
        this.setStudentSex(studentSex);
        this.setStudentAge(studentAge);
        this.setSub(sub);
    }

    /**
     * 学生自我介绍
     * @return 用于返回描述学生信息字符串
     */
    public String introduction() {
        String str = "学生信息如下:\n姓名:" + this.getStudentName() + "\n";
        str += "学号:" + this.getStudentNo()+ "\n";
        str += "性别:" + this.getStudentSex()+ "\n";
        str += "年龄:" + this.getStudentAge() + "\n";
        str += "所报专业名称:" + this.sub.getSubjectName() + "\n";
        str += "学制年限:" + this.sub.getSubjectLife() + "\n";
        return str;
    }

    /**
     * 学生自我介绍 + 所学专业
     * @param subjectName 所学专业名称
     * @param subjectLife 学制年限
     * @return 用于返回描述学生信息和所学专业的字符串
     */
    public String introduction(String subjectName, int subjectLife) {
        String str = "学生信息如下:\n姓名:" + this.getStudentName() + "\n";
        str += "学号:" + this.getStudentNo()+ "\n";
        str += "性别:" + this.getStudentSex()+ "\n";
        str += "年龄:" + this.getStudentAge() + "\n";
        str += "所报专业名称:" + subjectName + "\n";
        str += "学制年限:" + subjectLife + "\n";
        return str;
    }

    /**
     * 学生自我介绍 + 所学专业
     * @param sub 专业对象
     * @return 用于返回描述学生信息和所学专业的字符串
     */
    public String introduction(Subject sub) {
        String str = "学生信息如下:\n姓名:" + this.getStudentName() + "\n";
        str += "学号:" + this.getStudentNo()+ "\n";
        str += "性别:" + this.getStudentSex()+ "\n";
        str += "年龄:" + this.getStudentAge() + "\n";
        str += "所报专业名称:" + sub.getSubjectName() + "\n";
        str += "学制年限:" + sub.getSubjectLife() + "\n";
        return str;
    }
}

测试:

public class SchoolTest {
    public static void main(String[] args) {
        Subject sub = new Subject("计算机科学与应用","J0001",4);
        System.out.println(sub.info());
        System.out.println("=============================");
        //Student stu0 = new Student("S01","张三","男", 18);
        //System.out.println(stu0.introduction());空指针异常
        Student stu0 = new Student("S01","张三","男", 18, sub);
        System.out.println(stu0.introduction());//空指针异常
    }
}

各方案小结

综合案例——学生信息管理 - 图4

新增功能

新增需求及分析

对于这个专业,如果想要统计该专业有多少个学生进行了报名学习?
综合案例——学生信息管理 - 图5

对于这个需求,我们可以试着通过将报名的学生装入一个容器中,然后统计该容器中有多少个学生就可以了。
综合案例——学生信息管理 - 图6
那么该使用什么样的容器呢?

添加数组作为属性完成学生信息存储

package com.dodoke.school.model;
/**
 * 专业类
 * @author LiXinRong
 *
 */
public class Subject {
    //成员属性:学科名称、学科编号、学制年限、报名该专业的学习信息、报名该专业的学生个数
    private String subjectName;

    private String subjectNo;

    private int subjectLife;

    private Student[] students;//此时该数组为初始化,默认值为null

    //因为数组的长度200,实际保存学生30,并不一定是实际保存的学生个数,所以设置一个属性来统计学生个数
    private int studentNum;

    public String getSubjectName() {
        return subjectName;
    }

    public void setSubjectName(String subjectName) {
        this.subjectName = subjectName;
    }

    public String getSubjectNo() {
        return subjectNo;
    }

    public void setSubjectNo(String subjectNo) {
        this.subjectNo = subjectNo;
    }

    public int getSubjectLife() {
        return subjectLife;
    }

    public void setSubjectLife(int subjectLife) {
        if(subjectLife < 0) {
            return;//直接使用return退出该setSubjectLife方法,使学制年限保存默认值为0
        }
        this.subjectLife = subjectLife;
    }

    /**
     * 获取选修专业的学生信息,如果保存的学生信息的数组未初始化,则,先初始化长度200
     * @return 保存学生信息的数组
     */
    public Student[] getStudents() {
        if(this.students == null) {
            //对于数组动态初始化,没有办法准确的定下数组长度,只能根据具体情况具体分析
            this.students = new Student[200];
        }
        return students;
    }
    public void setStudents(Student[] students) {
        this.students = students;
    }
    public int getStudentNum() {
        return studentNum;
    }
    public void setStudentNum(int studentNum) {
        if(studentNum < 0) {
            this.studentNum = 0;
            return;//这里使用同样使用return终结方法的运行
        }
        this.studentNum = studentNum;
    }
    public Subject() {

    }

    //带参构造
    public Subject(String subjectName, String subjectNo, int subjectLife) {
        super();
        this.setSubjectLife(subjectLife);
        this.setSubjectName(subjectName);
        this.setSubjectNo(subjectNo);
    }

    public Subject(String subjectName, String subjectNo, int subjectLife, Student[] stus) {
        super();
        this.setSubjectLife(subjectLife);
        this.setSubjectName(subjectName);
        this.setSubjectNo(subjectNo);
        this.setStudents(stus);
    }

    /**
     * 该方法用于描述专业信息
     * @return
     */
    public String info() {
        //字符串的拼接,尽量不要写的太长,会让人看得难受
        String str = "专业信息如下:\n专业名称:" + this.getSubjectName() + "\n";
        str += "专业编号:" + this.getSubjectNo() + "\n";
        str += "学制年限:" + this.getSubjectLife() + "年";
        return str;
    }

    /**
     * 将学生信息保存到数组中,将学生个数保存到个数统计中
     * @param stu 学生信息
     */
    public void addStudent(Student stu) {
        /* 如何添加学生信息到数组中去
         * 需要将学生数组进行遍历,依次判读数组中保存的元素是否为null
         * 如果为null就可以用学生信息(学生对象)替代 
         */
        int i;
        for(i = 0; i < this.getStudents().length; i++) {
            if(this.getStudents()[i] == null) {
                this.getStudents()[i] = stu;
                break;
            }
        }
        //将个数保存到个数统计中去
        this.setStudentNum(i + 1); 
        //对于该方法中的两步操作,还可以怎么写?
    }
}

测试:

public class SchoolTest {
    public static void main(String[] args) {
        Subject sub = new Subject("计算机科学与应用","J0001",4);
        System.out.println(sub.info());
        System.out.println("=============================");
        Student stu0 = new Student("S01","张三","男", 18, sub);
        System.out.println(stu0.introduction());
        System.out.println("=============================");
        Student stu1 = new Student("S02","李四","女", 19);
        System.out.println(stu1.introduction("计算机科学与应用", 4));
        System.out.println("=============================");
        Student stu2 = new Student("S03","王五","男", 18);
        System.out.println(stu2.introduction(sub));
        System.out.println("=============================");
        sub.addStudent(stu0);
        sub.addStudent(stu1);
        sub.addStudent(stu2);
        System.out.println(sub.getSubjectName() + "的专业中,已有" + sub.getStudentNum() + "个学生进行了报名");
    }
}

问题分析

数组未实例化造成的空指针异常

实际上,在这样的一个小案例中,我们通常会发现学生会出现一些问题。

public Student[] getStudents() {
    /*if(this.students == null) {
        //对于数组动态初始化,没有办法准确的定下数组长度,只能根据具体情况具体分析
        this.students = new Student[200];
    }*/
    return students;
}

如果将上述代码注释掉之后,我们会发现,运行时出现了空指针的异常。这是因为,我们没有针对数组进行初始化。解决方法有两种:

  1. 给数组属性直接初始化
  2. 在使用数组属性时初始化

    通过一个方法完成学生和专业的双向关联

    之前的代码中,我们先通过实例化学生完成了学生与专业之间的关联,而后又通过添加学生方法实现了,专业与学生之间的关联。是否能有一个办法,完成学生与专业的双向关联。
    综合案例——学生信息管理 - 图7
    /**
    * 将学生信息保存到数组中,将学生个数保存到个数统计中
    * @param stu 学生信息
    */
    public void addStudent(Student stu) {
     /* 如何添加学生信息到数组中去
      * 需要将学生数组进行遍历,依次判读数组中保存的元素是否为null
      * 如果为null就可以用学生信息(学生对象)替代 
      */
     int i;
     for(i = 0; i < this.getStudents().length; i++) {
         if(this.getStudents()[i] == null) {
             stu.setSub(this);
             this.getStudents()[i] = stu;
             break;
         }
     }
     //将个数保存到个数统计中去
     this.setStudentNum(i + 1); 
     //对于该方法中的两步操作,还可以怎么写?
    }
    

测试:

public class SchoolTest {
    public static void main(String[] args) {
        Subject sub = new Subject("计算机科学与应用","J0001",4);
        Student stu0 = new Student("S01","张三","男", 18);
        sub.addStudent(stu0);
        System.out.println(sub.getSubjectName() + "的专业中,已有" + sub.getStudentNum() + "个学生进行了报名");
    }
}