Java 面向对象

一、方法

访问修饰符:

  • 定义格式中三个修饰符的意义:

    1. static:表示该方法是静态方法,可以有类直接调用

    2. final:表示该方法不能被子类重写。

    3. abstract:表示该方法是一个抽象方法。

Java 面向对象 - 图1

  • public/private/protected的具体区别
    1、public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
    2、private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用。
    3、protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。

2.值传递与引用传递

1.值传递

  1. package 面向对象.值传递与引用传递;
  2. public class 值传递 {
  3. public static void main(String[] args) {
  4. int a = 3;
  5. int b ;
  6. b = a;
  7. System.out.println(a);
  8. System.out.println(b);
  9. b = 5;
  10. System.out.println(a);
  11. System.out.println(b);
  12. }
  13. }

2.引用传递

  • 示例1 ```java package 面向对象.值传递与引用传递;

public class 引用传递 { public static void main(String[] args) {

  1. Student student1 = new Student();
  2. student1.setName("琴静");
  3. System.out.println("student1的name:"+student1.getName());
  4. Student student2 = new Student();
  5. student2.setName("张俊");
  6. System.out.println("student2的name:"+student2.getName());
  7. student2.setName(student1.getName());
  8. System.out.println("student1的name:"+student1.getName());
  9. System.out.println("student2的name:"+student2.getName());
  10. // 运行之后发现当修改了student2的name值后,student1的name也会变
  11. }

} class Student{ String strName; public String getName(){ return strName; } public void setName(String name){ strName = name; } }

  1. -
  2. 示例2
  3. ```java
  4. package 面向对象.值传递与引用传递;
  5. public class 引用传递1 {
  6. public static void main(String[] args) {
  7. int a[] = {1,2,3};
  8. int b[] = new int[3];
  9. b = a;
  10. System.out.println(b[0]);
  11. a[0] = 5;
  12. System.out.println(b[0]);
  13. }
  14. }

4.静态方法

Java 面向对象 - 图2

  1. package 面向对象;
  2. public class 静态方法 {
  3. // static修饰定义一个静态方法
  4. public static void helloWorld(){
  5. System.out.println("这是一个静态方法!");
  6. }
  7. public static void main(String[] args) {
  8. // 直接用类名调用静态方法
  9. 静态方法.helloWorld();
  10. }
  11. }

5.方法重载

  1. package 面向对象.方法;
  2. public class 方法重载 {
  3. public void method(int x){
  4. System.out.println(x);
  5. }
  6. public void method(int x,int y){
  7. System.out.printf("%s,%s",x,y);
  8. }
  9. public static void main(String[] args) {
  10. 方法重载 a = new 方法重载();
  11. a.method(5);
  12. a.method(10,15);
  13. }
  14. // 可以看到定义了两个方法,但是参数数量不一样。后面定义了一个对象,调用方法,可以发现传入的参数不同,返回结果也是不同的
  15. }

二、常用的类库

1.日期类-Data

Java 面向对象 - 图3

  1. package 面向对象.常用的类库.日期类;
  2. import java.util.Date;
  3. public class 日期类 {
  4. public static void main(String[] args) {
  5. Date date1 = new Date(); // 获取当前时间
  6. Date date2 = new Date(System.currentTimeMillis()+12345); // 当前时间+12345ms
  7. System.out.println(date1);
  8. System.out.println(date2);
  9. System.out.println("时间戳:"+date2.getTime()+"ms");
  10. System.out.println(date1.compareTo(date2)); // 判断date1和date2是否相同,相同返回0,小返回-1,大返回1
  11. System.out.println(date1.before(date2)); // 判断date1是否比date2早
  12. }
  13. }

2.日期格式化类

Java 面向对象 - 图4

  1. package 面向对象.常用的类库;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. public class 日期格式化类 {
  5. public static void main(String[] args) {
  6. // 创建格式化格式
  7. String format1 = "yyyy年MM月dd日 HH:mm:ss E";
  8. String format2 = "yyyy年MM月dd日 HH时mm分ss秒SSS E";
  9. // 创建sdf对象,并传入格式
  10. SimpleDateFormat sdf1 = new SimpleDateFormat(format1);
  11. SimpleDateFormat sdf2 = new SimpleDateFormat(format2);
  12. // 格式化当前日期
  13. String date1 = sdf1.format(new Date());
  14. String date2 = sdf2.format(new Date());
  15. System.out.println(date1);
  16. System.out.println(date2);
  17. }
  18. }
  1. package 面向对象.常用的类库;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Date;
  5. public class 日期格式化类1 {
  6. public static void main(String[] args) {
  7. String strDate = "2012-09-08 13:56:35.321"; // 日期
  8. String format = "yyyy-MM-dd HH:mm:ss.SSS"; // 指定要格式化的日期格式
  9. SimpleDateFormat sdf = new SimpleDateFormat(format); // 创建sdf对象,并传入指定格式化格式
  10. Date date = null; # 创建Date类型
  11. try {
  12. date = sdf.parse(strDate); # parse方法转换
  13. } catch (ParseException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(date);
  17. }
  18. }

3. 随机类

  1. package 面向对象.常用的类库.随机类;
  2. import java.util.Random;
  3. public class 随机类 {
  4. public static void main(String[] args) {
  5. int num;
  6. Random random = new Random();
  7. for(int i=0;i<19;i++){
  8. num = random.nextInt(6)+1;
  9. System.out.println(num);
  10. }
  11. // 利用随机类Random随机产生1-6的20个随机数
  12. }
  13. }

4.字符类

  1. package 面向对象.常用的类库;
  2. public class 字符类 {
  3. public static void main(String[] args) {
  4. // 创建String对象
  5. String string = new String("Hello World!");
  6. System.out.println(string);
  7. // 比较字符串
  8. String string1 = new String("Hello World!");
  9. System.out.println(string==string1); // 判断地址是否相同
  10. System.out.println(string.equals(string1)); // 判断内容是否相同
  11. // String的length,charAt和getChars方法
  12. System.out.println(string.length());
  13. System.out.println(string.charAt(2));
  14. char arr[] = new char[5];
  15. // 取string的下标0-1的数放进arr数组中,从arr[0]开始。
  16. string.getChars(0,2,arr,0);
  17. System.out.println(arr[0]);
  18. System.out.println(arr);
  19. }
  20. }

三、

1.构造方法

  1. package 面向对象;
  2. public class 构造方法 {
  3. public static void main(String[] args) {
  4. Person person = new Person(20,"赵涛涛");
  5. person.say();
  6. }
  7. }
  8. class Person{
  9. int age;
  10. String name;
  11. // 创建了一个构造方法
  12. public Person(int age,String name){
  13. this.age=age;
  14. this.name=name;
  15. }
  16. public void say(){
  17. System.out.println("我的年龄是"+this.age+",我叫"+this.name);
  18. }
  19. }

2.构造方法重载

  1. package 面向对象;
  2. public class 构造方法重载 {
  3. public static void main(String[] args) {
  4. Student stu1 = new Student("赵涛涛");
  5. System.out.println(stu1.name);
  6. Student stu2 = new Student("周杰伦",20);
  7. System.out.printf("%s %s",stu2.name,stu2.age);
  8. }
  9. }
  10. class Student{
  11. String name;
  12. int age;
  13. public Student(String name) {
  14. this.name = name;
  15. }
  16. public Student(String name,int age){
  17. this.name = name;
  18. this.age = age;
  19. }
  20. }

1.类定义的语法形式:

  1. public class 类名{
  2. //定义类属性
  3. 属性1类型:属性1名;
  4. 属性2类型:属性2名;
  5. //定义方法
  6. 方法1定义
  7. 方法2定义

现在创建一个Student类:

  1. public class Student {
  2. String stuName;
  3. int stuAge;
  4. int stuSex;
  5. int stuGrade;
  6. public void learn() {
  7. System.out.println(stuName + "正在认真听课!");
  8. }
  9. public String doHomework(int hour) {
  10. return "现在是北京时间:" + hour + "点," + stuName + "正在写作业!";
  11. }
  12. }

需要注意的是,这个类里面没有 main 方法,所以只能编译,不能运行。

定义好 Student 类后,就可以根据这个类创建(实例化)对象了。类就相当于一个模板,可以创建多个对象。创建对象的语法形式如下。

  1. 类名 对象名 = new 类名();

创建对象时,要使用 new 关键字,后面要跟着类名(构造方法名),类名后的括号内可传递构造参数。

根据上面创建对象的语法,创建王云这个学生对象的代码如下。

  1. Student wangYun = new Student();

这里,只创建了 wangYun 这个对象,并没有对这个对象的属性赋值,考虑到每个对象的属性值不一样,所以通常在创建对象后给对象的属性赋值。在 Java 语言中,通过 . 操作符来引用对象的属性和方法,具体的语法形式如下。

  1. 对象名.属性 ;
  2. 对象名.方法() ;

通过上面的语法形式,可以给对象的属性赋值,也可以更改对象属性的值或者调用对象的方法,具体的代码如下。

  1. wangYun.stuName ="王云";
  2. wangYun.stuAge = 22;
  3. wangYun.stuSex = 1; //1代表男,2代表女
  4. wangYun.stuGrade = 4; //4代表大学四年级
  5. wangYun.learn(); //调用学生听课的方法
  6. wangYun.doHomework(22); //调用学生写作业的方法,输入值22代表现在是22点

接下来通过创建一个测试类 TestStudent(这个测试类需要和之前编译过的 Student 类在同一个目录),来测试 Student 类的创建和使用,程序如下。

  1. public class TestStudent {
  2. public static void main(String[] args) {
  3. Student wangYun = new Student();
  4. wangYun.stuName = "王云";
  5. wangYun.stuAge = 22;
  6. wangYun.stuSex = 1;
  7. wangYun.stuGrade = 4;
  8. wangYun.learn();
  9. String restring = wangYun.doHomework(22);
  10. System.out.println(restring);
  11. }
  12. }

Java 面向对象 - 图5

这个程序虽然非常简单,但却是我们第一次使用两个类来完成程序。其中 TestStudent 类是测试类,测试类中包含 main 方法,提供程序运行的入口。在 main 方法内,创建 Student 类的对象并给对象属性赋值,然后调用对象的方法。

这个程序有两个 Java 文件,每个 Java 文件中编写了一个 Java 类,编译完成后形成 2 个 class 文件。也可以将两个 Java 类写在一个 Java 文件里,但其中只能有一个类用 public 修饰,并且这个 Java 文件的名称必须用这个 public 类的类名命名,详见以下程序。

  1. public class TestStudent2 {
  2. public static void main(String[] args) {
  3. Student2 wangYun = new Student2();
  4. wangYun.stuName = "王云";
  5. wangYun.stuAge = 22;
  6. wangYun.stuSex = 1;
  7. wangYun.stuGrade = 4;
  8. wangYun.learn();
  9. String rstString = wangYun.doHomework(22);
  10. System.out.println(rstString);
  11. }
  12. }
  13. class Student2 {
  14. String stuName;
  15. int stuAge;
  16. int stuSex;
  17. int stuGrade;
  18. public void learn() {
  19. System.out.println(stuName + "正在认真听课!");
  20. }
  21. public String doHomework(int hour) {
  22. return "现在是北京时间:" + hour + "点," + stuName + "正在写作业!";
  23. }
  24. }

Java 面向对象 - 图6

在上面的例子中,对对象的属性都是先赋值后使用,如果没有赋值就直接使用对象的属性,会有什么样的结果呢?

下面将 TestStudent 测试类的代码修改成下面的代码。

  1. public class TestStudent {
  2. public static void main(String[] args) {
  3. Student wangYun = new Student();
  4. System.out.println("未赋值前的学生姓名为:" + wangYun.stuName);
  5. System.out.println("未赋值前的学生年龄为:" + wangYun.stuAge);
  6. System.out.println("未赋值前的学生性别数值为:" + wangYun.stuSex);
  7. System.out.println("未赋值前的学生年纪为:" + wangYun.stuGrade);
  8. wangYun.stuName = "王云";
  9. wangYun.stuAge = 22;
  10. wangYun.stuSex = 1;
  11. wangYun.stuGrade = 4;
  12. System.out.println("赋值后的学生姓名为:" + wangYun.stuName);
  13. System.out.println("赋值后的学生年龄为:" + wangYun.stuAge);
  14. System.out.println("赋值后的学生性别数值为:" + wangYun.stuSex);
  15. System.out.println("赋值后的学生年级为:" + wangYun.stuGrade);
  16. }
  17. }

Java 面向对象 - 图7

从程序运行结果可以看出,在未给对象属性赋值前使用属性时,属性使用的都是对应数据类型的默认值,即如果该属性为引用数据类型,其初始默认值为 null,如果该属性是 int 型,其初始默认值为 0。

  1. import java.util.Scanner;
  2. public class TestStuTea {
  3. static Scanner input = new Scanner(System.in);
  4. public static void main(String[] args) {
  5. Teacher[] tea = new Teacher[2];
  6. Student[] stu = new Student[4];
  7. for (int i = 0; i < tea.length; i++) {
  8. System.out.println("请创建并输入第" + (i + 1) + "个老师的基本信息");
  9. tea[i] = createTeacher();
  10. }
  11. for (int i = 0; i < stu.length; i++) {
  12. System.out.println("请创建并输入第" + (i + 1) + "个学生的基本信息");
  13. stu[i] = createStudent();
  14. }
  15. tea[0].teach();
  16. for (int j = 0; j < stu.length; j++) {
  17. stu[j].learn();
  18. }
  19. for (int j = 0; j < stu.length; j++) {
  20. String tempStr = stu[j].doHomework(20);
  21. System.out.println(tempStr);
  22. }
  23. for (int j = 0; j < stu.length; j++) {
  24. tea[1].checkHomework(stu[j]);
  25. }
  26. }
  27. public static Teacher createTeacher() {
  28. Teacher tea = new Teacher();
  29. System.out.print("请输入老师的姓名:");
  30. tea.teaName = input.next();
  31. System.out.print("请输入老师的专业:");
  32. tea.teaSpecialty = input.next();
  33. System.out.print("请输入老师的所讲授的课程:");
  34. tea.teaCourse = input.next();
  35. System.out.print("请输入老师的教龄:");
  36. tea.teaYears = input.nextInt();
  37. return tea;
  38. }
  39. public static Student createStudent() {
  40. Student stu = new Student();
  41. System.out.print("请输入学生姓名:");
  42. stu.stuName = input.next();
  43. System.out.print("请输入学生年龄:");
  44. stu.stuAge = input.nextInt();
  45. System.out.print("请输入学生性别数值(1代表男、2代表女):");
  46. stu.stuSex = input.nextInt();
  47. System.out.print("请输入学生年级:");
  48. stu.stuGrade = input.nextInt();
  49. return stu;
  50. }
  51. }
  52. class Teacher {
  53. String teaName;
  54. String teaSpecialty;
  55. String teaCourse;
  56. int teaYears;
  57. public void teach() {
  58. System.out.println(teaName + "正在辛苦讲:" + teaCourse + "课程!");
  59. }
  60. public void checkHomework(Student stu) {
  61. System.out.println("讲授:" + teaCourse + "课程的老师:" + teaName + "已经批改完毕:" + stu.stuName + "的作业!");
  62. }
  63. }
  64. class Student {
  65. String stuName;
  66. int stuAge;
  67. int stuSex;
  68. int stuGrade;
  69. public void learn() {
  70. System.out.println(stuName + "正在认真听课!");
  71. }
  72. public String doHomework(int hour) {
  73. return "现在是北京时间:" + hour + "点" + stuName + "正在写作业!";
  74. }
  75. }

Java 面向对象 - 图8

Java 面向对象 - 图9

2.初识封装

封装的目的是简化编程和增强安全性。

  1. 简化编程是指,封装可以让使用者不必了解具体类的内部实现细节,而只是要通过提供给外部访问的方法来访问类中的属性和方法。例如 Java API 中的 Arrays.sort()方法,该方法可以用于给数组进行排序操作,开发者只需要将待排序的数组名放到 Arrays.sort()方法的参数中,该方法就会自动的将数组排好序。可见,开发者根本不需要了解 Arrays.sort()方法的底层逻辑,只需要简单的将数组名传递给方法即可实现排序。
  2. 增强安全性是指,封装可以使某个属性只能被当前类使用,从而避免被其他类或对象进行误操作。例如在 Student.java 的程序中,StudentstuAge 属性是 public 的形式体现的,但这样做实际存在着安全隐患:TestStudent 类(或在访问修饰符可见范围内的其他类)完全可以随意的对 stuAge 进行修改,如以下程序。
  1. public class TestStudent {
  2. public static void main(String[] args) {
  3. Student wangYun = new Student();
  4. wangYun.stuAge = -10;
  5. ...
  6. }
  7. }

如上,给 stuAge 赋了一个不符合逻辑的值,但语法是却正确的。因此这种做法,实际就给程序造成了安全问题。如何避免此类问题呢?使用 private 修饰符来修饰 stuAge 属性,以此禁止 Student 以外的类对 stuAge 属性的修改。但这么做未免显得“过犹不及”,为了保证安全,也不至于让其他类无法访问吧!有没有一种办法,既能让其他类可以访问 Student 类中的 stuAge 属性,又能保证其他类始终是在安全的数值范围内修改 stuAge 值呢?有,先用 private 修饰 stuAge 属性,然后再给该属性提供两个 public 修饰的、保证属性安全的访问方法(setter 方法和 getter 方法),即:

  1. private 禁止其他类直接访问属性;
  2. 给 1 中的属性新增两个 public 修饰的 settergetter 方法,供其他类安全的访问。

setter 方法用于给属性赋值,而 getter 访问用于获取属性的值。并且一般而言,setter 方法的名字通常是 set+属性名getter 方法的名字通常是 get+属性名

根据以上描述,先用 private 修饰 stuAge,禁止 TestStudent 类对 stuAge 的直接访问,以此保证 stuAge 安全性;然后新增 setStuAge()getStuAge()方法,一方面供 TestStudent 类间接的访问 stuAge 属性,另一方面也保证了 stuAge 的数据安全,详见以下程序。

  1. public class StudentPrivate {
  2. private int stuAge;
  3. public int getStuAge() {
  4. return stuAge;
  5. }
  6. public void setStuAge(int age) {
  7. if (age > 0 && age < 110)
  8. stuAge = age;
  9. else
  10. age = 0;
  11. }
  12. }

后续,其他类只需要调用 setStuAge()和 getStuAge()方法,就能对 stuAge 属性进行安全的赋值或取值,代码如下。

  1. public class TestStudent3 {
  2. public static void main(String[] args) {
  3. StudentPrivate wangYun = new StudentPrivate();
  4. wangYun.setStuAge(-10);
  5. int age = wangYun.getStuAge();
  6. System.out.println(age);
  7. wangYun.setStuAge(22);
  8. age = wangYun.getStuAge();
  9. System.out.println(age);
  10. }
  11. }

Java 面向对象 - 图10

实际上,使用 setter 和 getter 的解决方案用到了一个程序设计的基本原则:逻辑代码不能写在变量中,而必须写在方法或代码块中。

3.构造方法基本语法

构造方法不同于普通方法,普通方法代表对象的行为,而构造方法是提供给系统用于创建对象的方法。

构造方法(也称为构造函数)是一种特殊的方法,它具有以下特点。

  • 构造方法的方法名必须与类名相同。
  • 构造方法没有返回类型,在方法名前不声明返回类型(包括 void)。

构造方法的语法形式如下。

  1. [访问修饰符] 类名([参数列表]) ;

虽然构造方法在语法形式上没有返回类型,但其实构造方法是有返回值的,返回的是刚刚被初始化完成的当前对象的引用。既然构造方法返回被初始化对象的引用,为什么不写返回值类型呢?例如 Student 类构造方法为什么不写成 public Student Student(参数列表){…}呢?

因为 Java 设计人员把这种方法名(类名)和返回类型的类名相同的方法看成一个普通方法,只是名称“碰巧”相同罢了,编译器识别时也会认为它是一个方法。为了和普通方法进行区别,Java 设计人员规定构造方法不写返回值,编译器通过这一规定识别构造方法,而不是说构造方法真的没有返回值。

Student 类的代码改为下面的形式:

  1. private String stuName;
  2. private int stuAge;
  3. private int stuSex;
  4. private int stuGrade;
  5. public StudentInit (String name,int age,int sex,int grade) {
  6. stuName = name;
  7. stuAge = age;
  8. stuSex = sex;
  9. stuGrade = grade;
  10. }
  11. public void learn() {
  12. System.out.println(stuName + "正在认真听课!");
  13. }
  14. public String doHomework(int hour) {
  15. return "现在是北京时间:" + hour + "点" + stuName + "正在写作业!";
  16. }

测试类 TestStudent1.java 的代码如下所示。

  1. public class TestStudent1 {
  2. public static void main(String[] args) {
  3. StudentInit wangYun = new StudentInit("王云",22,1,4);
  4. wangYun.learn();
  5. String rstString = wangYun.doHomework(22);
  6. System.out.println(rstString);
  7. }
  8. }

Java 面向对象 - 图11

4.this关键字

this 是 Java 的一个关键字,它表示“指向当前对象的引用(后文简称为‘当前对象’)”。举个例子,在前面小节中给 stuAge 属性赋值的语句是wangYun.setStuAge(22);,这条语句中的当前对象就是 wangYun,因此在 setStuAge()方法中 this 就是 wangYun。

在实际开发时,this 通常用于以下两个场景。

  1. 区分成员变量和局部变量,避免命名的烦恼。
  2. 调用其他构造方法。

先看一下如何使用 this 区分成员变量和局部变量。在前面一小节中,我们是通过以下代码给 stuAge 赋值的。

  1. private int stuAge ;
  2. ...
  3. public void setStuAge(int age) {
  4. ...
  5. stuAge = age ;
  6. }

是否可以将参数 age,和成员变量 stuAge 设置为相同的名称,如下所示。

  1. private int age ;
  2. ...
  3. public void setAge(int age) {
  4. ...
  5. age = age ;
  6. }

显然,编译器将无法区分age = age这句代码中哪个变量是成员变量,哪个变量是参数,因此我们之前就只能通过不同的变量名来区分。而现在,我们就可以通过 this 来区分不同的 age,如下所示。

  1. private int age ;
  2. ...
  3. public void setAge(int age) {
  4. ...
  5. this.age = age ;
  6. }

this.age = age ;这句代码中,因为 this 代表着当前对象,因此this.age就代表了当前对象的 age 属性;而age没有 this 修饰,就会根据变量的作用域,默认为方法参数中的 age。

再看一下如何使用 this 调用其他构造方法。

在构造方法中,还可以使用 this 调用类中的其他构造方法,但这种this()语句必须写在构造方法的一行,如下所示。

  1. public Student() {
  2. //调用有一个String参数的构造方法
  3. this(" WangYun" );
  4. }
  5. public Student(String name) {
  6. //调用有两个参数的构造方法
  7. this(name,23);
  8. }
  9. public Student(String name, int age) {
  10. ...
  11. }

需要注意的是,所有的构造方法之间不能循环调用,否则会出现类似“死循环”的现象。例如以上代码,如果在最后一个构造方法的第一行也加上this(),那么三个构造方法就会无限制的彼此调用。

例:

构造方法的主要作用是完成对象的初始化工作,它能够把定义对象时的参数传给对象。一个类可以定义多个构造方法,但需要根据参数的个数、类型或排列顺序来区分不同的构造方法,详见以下程序。

新建一个 Student1.java 文件,并输入以下代码。

  1. public class Student1 {
  2. private String name;
  3. private int age;
  4. private int sex;
  5. private int grade;
  6. public Student1(String name, int age, int sex, int grade) {
  7. this(name, age, sex);
  8. this.grade = 4;
  9. }
  10. public Student1(String name, int age, int sex) {
  11. this(name, sex);
  12. this.age = age;
  13. this.grade = 4;
  14. }
  15. public Student1(String name, int sex) {
  16. this.sex = sex;
  17. this.name = name;
  18. this.age = 22;
  19. this.grade = 4;
  20. }
  21. public Student1(String name) {
  22. this.name = name;
  23. this.age = 22;
  24. this.grade = 4;
  25. }
  26. public String getName() {
  27. return name;
  28. }
  29. public int getAge() {
  30. return age;
  31. }
  32. public void setSex(int sex) {
  33. this.sex = sex;
  34. }
  35. public int getGrade() {
  36. return grade;
  37. }
  38. public void setAge(int age) {
  39. if (age > 0 && age <110) {
  40. this.age = age;
  41. } else {
  42. this.age = 0;
  43. }
  44. }
  45. public void learn() {
  46. System.out.println(name + "正在认真听课!");
  47. }
  48. public String doHomework(int hour) {
  49. return "现在是北京时间:" + hour + "点," + name + "正在写作业!";
  50. }
  51. }

新建测试类 TestStudent3,其代码如下。

  1. public class TestStudent4 {
  2. public static void main(String[] args) {
  3. Student1 wangYun = new Student1("王云",22,1,4);
  4. Student1 liuJT = new Student1("刘静涛",21,2);
  5. Student1 nanTH = new Student1("南天华");
  6. nanTH.setSex(1);
  7. wangYun.learn();
  8. String rstString = wangYun.doHomework(22);
  9. System.out.println(rstString);
  10. liuJT.learn();
  11. System.out.println(liuJT.getName() + "正在读大学" + liuJT.getGrade() + "年级");
  12. System.out.println(nanTH.doHomework(23));
  13. }
  14. }

Java 面向对象 - 图12

构造方法有一个约定:如果在定义类时没有定义构造方法,编译系统会自动插入一个无参数的默认构造方法,这个构造方法不执行任何代码。如果在定义类时定义了有参的构造方法,没有显式地定义无参的构造方法,那么在使用构造方法创建类对象时,则不能使用默认的无参构造方法。

例如,在 TestStudent3 程序的 main 方法内添加一行语句Student leiJing = new Student();,编译器会报错,提示没有找到无参的构造方法。

5.对象的初始化过程

对象的初始化,实际就是先在堆内存中申请一块用于存放对象属性值的空间,然后再给这些属性值赋上默认值、程序员期望的数据或者用户指定的数据。当堆内存中的这块空间有了值以后,再在栈空间中申请一块空间并存放引用变量,然后用栈中的引用变量指向堆中的对象,最终就可以通过栈中的引用变量访问或者修改堆中的对象了,如图所示。

Java 面向对象 - 图13

下面我们结合代码,分析对象初始化过程中内存演变的细节。

本次在堆中存放的对象是由 Student 类生成的,并且 Student 类通过初始化块给属性赋了值(初始化块会在本节的后续分析内存演变时讲解),详见以下程序。

  1. public class Student2 {
  2. private String name = "";
  3. private int age = -1;
  4. private int sex = -1;
  5. private int grade = -1;
  6. {
  7. System.out.println("使用初始化快初始化");
  8. this.name = "雷静";
  9. this.age = 22;
  10. this.sex = 2;
  11. this.grade = 4;
  12. }
  13. public Student2() {
  14. System.out.println("使用无参构造函数初始化");
  15. }
  16. public Student2(String name,int age,int sex,int grade) {
  17. System.out.println("使用有参构造函数初始化");
  18. this.name = name;
  19. this.age = age;
  20. this.sex = sex;
  21. this.grade = grade;
  22. }
  23. public void setSex(int sex) {
  24. this.sex = sex;
  25. }
  26. public int getGrade() {
  27. return grade;
  28. }
  29. public String getName() {
  30. return name;
  31. }
  32. }

新建测试类 TestStudent5.java,其代码如下。

  1. public class TestStudent5 {
  2. public static void main(String[] args) {
  3. Student2 temp = new Student2();
  4. System.out.println(temp.getName() + "正在读大学" + temp.getGrade() + "年级");
  5. Student2 wangYun = new Student2("王云",22,1,4);
  6. System.out.println(wangYun.getName() + "正在读大学" + wangYun.getGrade() + "年级");
  7. }
  8. }

对象初始化时的内存演变过程如下。

  1. Student2 temp= new Student2();执行时,首先需要在堆内存中申请空间,用于存放对象的实例,这片空间上成员变量的值全部为默认值:name 的值是 null、age 的值是 0……,如图所示。

Java 面向对象 - 图14

  1. 紧接着,执行声明初始化(由设计该类的开发者指定),例如在 Student2 类中private int age = -1;代表程序员希望 age 属性用值-1 覆盖默认值,如图所示。

Java 面向对象 - 图15

3.初始化块初始化。

初始化块就是在类的下一级(与成员变量和成员方法同级)用一对大括号括起来的代码块,语法形式如下:

  1. {
  2. 代码块
  3. }

初始化块可以用来覆盖类的成员变量的值,初始化块的执行时机是发生在“声明初始化”之后,在“构造器初始化”之前。例如,如下的初始化代码,就用于给成员变量再次赋值,如图所示。

  1. {
  2. this.name = "雷静";
  3. this.age = 22;
  4. this.sex = 2;
  5. }

Java 面向对象 - 图16

执行程序后,运行结果如图所示。大家可以通过运行结果,分析刚才说的初始化块的执行时机。

Java 面向对象 - 图17

  1. 构造器初始化,例如Student2 temp = new Student2("王云", 22, 1, 4);,在默认初始化,声明初始化,初始化块之后,再此用构造器覆盖各个属性的值,如图所示。

Java 面向对象 - 图18

6.重载基本语法

在同一个类中,可以有两个或两个以上的方法具有相同的方法名,但它们的参数列表不同。这种形式被称为重载(overload)。所谓参数列表不同包括以下三种情形。

  • 参数的数量不同。
  • 参数的类型不同。
  • 参数的顺序不同。

必须注意的是,仅返回值不同不能视为方法重载,还会得到一个语法错误。而且重载的方法之间只是“碰巧”名称相同罢了,不具备连带效应。既然方法名称相同,在使用相同的名称调用方法时,编译器怎么确定调用哪个方法呢?这就要靠传入参数的不同确定具体调用哪个方法。

注意,一个类的多个构造方法被视为重载,因为多个构造方法的方法名相同,而参数列表不同,符合重载的定义。

看以下程序中的代码,其中的重点是 learn 方法的重载。

  1. public class Student3 {
  2. private String name;
  3. private int age;
  4. public Student3(String name, int age) {
  5. this.name = name;
  6. this.age = age;
  7. }
  8. public String getName() {
  9. return name;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void read(String bookName, String bookAuthor, double bookPrice) {
  15. if (bookName != null && bookAuthor == null && bookPrice != 0.0) {
  16. System.out.println(this.name + "正在读《" + bookName + "》,书价:" + bookPrice);
  17. }
  18. //当bookPrice使用的是默认值时
  19. if (bookName != null && bookAuthor != null && bookPrice == 0.0) {
  20. System.out.println(this.name + "正在读《" + bookName + "》,作者:" + bookAuthor);
  21. }
  22. //当全部的参数都使用的是默认值时
  23. if (bookName == null && bookAuthor == null && bookPrice == 0.0) {
  24. System.out.println(this.name + "正在读书");
  25. }
  26. //当全部的参数都使用的不是默认值时
  27. if (bookName != null && bookAuthor != null && bookPrice != 0.0) {
  28. System.out.println(this.name + "正在读《" + bookName + "》,作者:" + bookAuthor
  29. + ",书价:" + bookPrice);
  30. }
  31. }
  32. public void read(String bookName, String bookAuthor) {
  33. this.read(bookName, bookAuthor, 0.0);
  34. }
  35. public void read(String bookName, double bookPrice) {
  36. this.read(bookName, null, bookPrice);
  37. }
  38. public void read(String bookName) {
  39. this.read(bookName, null, 0.0);
  40. }
  41. public void read() {
  42. this.read(null, null, 0.0);
  43. }
  44. }

测试类 TestStudent6.java 文件代码如下。

  1. public class TestStudent6 {
  2. public static void main(String[] args) {
  3. Student3 stu = new Student3("王云",22);
  4. stu.read("Java编程思想","埃克尔",108.0);
  5. stu.read("Java编程思想","埃克尔");
  6. stu.read("Java编程思想",108.0);
  7. stu.read();
  8. }
  9. }

Java 面向对象 - 图19