201871010126 王亚涛 《面向对象程序设计(java)》 第6-7周学习总结

项目

内容

这个作业属于哪个课程

https://www.cnblogs.com/nwnu-daizh/

这个作业的要求在哪里

https://www.cnblogs.com/nwnu-daizh/p/11605051.html

作业学习目标

  1. 深入理解程序设计中算法与程序的关系;
  2. 深入理解java程序设计中类与对象的关系;
  3. 理解OO程序设计的第2个特征:继承、多态;
  4. 学会采用继承定义类设计程序(重点、难点);
  5. 能够分析与设计至少包含3个自定义类的程序;
  6. 掌握利用父类定义子类的语法规则及对象使用要求。

随笔博文正文内容包括:

第一部分:总结第五章理论知识

第5章 继承

5.1 类、超类、子类

5.1.1 定义子类

不同于C++的冒号继承法,关键字extends表示继承。

public class Manager extends Employee
{
   private double bonus;

    public Manager(String name,double salary,int year,int month,int day)
    {
        super(name, salary, year, month, day);
        bonus=0;
    }

    public double getSalary()
    {
        double baseSalary=super.getSalary();
        return baseSalary + bonus;
    }

    public void setBonus(double b)
    {
        bonus=b;
    }
}
注:java中所有的继承都是公有继承。
子类比超类封装了更多的数据,子类拥有超类的所有域和方法,除此之外,上面的例子中Manager就比超类Employee多了bonus这个数据域,和setBonus这一方法。Manager有自己的构造器,但Manager类不能访问超类的私有域。构造器传递参数给超类构造器用来初始化超类的数据域,要使用关键字super。
而C++中使用冒号带上超类的构造函数来构造超类私有域的。

5.1.2.覆盖方法

因为经理的工资肯定和员工的工资不一样,所以在getSalary上也不能用一致的方法,容易发现上述例子中Manager类提供了一个和超类同名的getSalary方法,叫做覆盖。
注:Manager类的方法不能直接访问超类私有域,具体方法详见super的解释,已经整理
所以在覆盖方法中必须要得到基础工资,不可避免要访问超类的salary,就要用super关键字去调用超类的方法访问超类的私有域。

5.1.3.多态

Employee类对象变量可以引用Employee对象,也可以引用Manager类对象。但是反过来,Manager类对象变量并不能引用Employee对象。道理很简单,通俗的讲,Manager一定是Employee,但是Employee一定是Manager吗?不一定吧。
而从类内部的数据角度分析,因为子类一定有更多的方法或者数据域,而如果用子类对象变量去引用一个超类对象,超类对象并不存在那些子类多出来的方法或数据域,这明显是有问题的引用。但反过来,超类对象变量去引用子类对象时候,当把对象看做超类,并不存在这个变量不能调用的方法。
多态:一个对象可以指示多种实际类型的现象称为多态。
如下:

Manager boss = new Manager(...);

Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee(...);
staff[2] = new Employee(...);
上述例子中 staff是一个Employee类的数组,但是staff[0]引用了一个Manager类的boss对象,而staff[1]和staff[2]则引用了Employee对象。这就是典型的多态。
与多态相关的概念还有动态绑定。
动态绑定:在运行时,上述对象变量能自动地选择调用哪个方法的现象称为动态绑定。
如下:

for(Employee e:staff)
    system.out.println(e.getName()+" "+e.getSalary());
在这个例子里面,注意到getSalary是被覆盖了的,那么e.getSalary()会调用超类还子类的方法呢?答案是根据其实际类型来自动选择。java虚拟机知道e引用对象的实际类型,因而能够正确调用。

重点关注:重载和覆盖的区别

覆盖是子类的方法与超类方法同名,同参数列表即同签名,当时用超类对象变量引用子类对象时,调用被覆盖的方法,尽管是超类的对象变量,但是调用的方法是子类中被覆盖的方法。在覆盖时,对应方法在子类的可见性必须不低于超类的可见性。
重载仅仅同名,但是参数列表不同。
注意:

- 超类对象变量可以引用子类对象,但是不能使用超类对象变量去调用子类方法,下面这样是不合法的:

staff[0].setBonus(1000);
只有

boss.setBonus(1000);
但是同时注意,由于动态绑定,对于覆盖的方法,使用超类对象变量去调用,是合法的。这看上去和上述概念是矛盾的,但可以觉得是偷梁换柱“暗中”调用了子类方法。
- 子类数组的引用可以转换成超类数组的引用,而不需要强制类型转换。也就是:

Manager[] managers = new Manager[10];
Employee[] staff = managers;
这样做是合法的,相当于staff数组的每一个Employee对象变量都引用了一个Manager对象。但是回忆一下数组的引用,需要注意的问题是,managers和staff引用的是同一个数组!而比较恐怖的是,且看如下操作:

staff[0] = new Employee(...);
编译器接受了这句操作(这看上去是不可思议的,因为managers和staff引用的是同一个数组,等于说是managers[0]=new Employee(...),但编译器这种情况下就是接受了)由于managers和staff引用同一个数组,当用managers[0].setBonus(1000)时会出现错误,因为那个实际的对象根本就不是Manager类,从而不存在setBonus这个方法,这是一个有趣的问题。一定要避免这样的事情发生,所以所有数组都要牢记创建他们的元素类型。


5.1.4.理解方法调用


另外还要注意,static方法不存在继承和覆写,它与声明时候的类相关,假设有Shape类,Cirlce类继承了Shape,这两个类里面各自声明了一个static void draw()方法,当有Shape s = new Circle(),s.draw()调用的不是Circle的draw方法,而Circle c = new Circle(),c.draw()才会调用Circle的draw方法。

5.2 object:所有类的超类

Object 类是Java中所有类的始祖,在Java中每个类都是由它扩展来的。

可以使用Object类型的变量引用任何类型的对象。

所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。

1、equals方法

  在Object类中,equals方法用于判断两个对象是否具有相同的引用。Object类中源码如下:  

    public boolean equals(Object obj) { return (this == obj); } 

  如果两个对象具有相同的引用,它们一定是相等的。但是对于大多数类来说,这种判断没有什么意义,因为经常会需要判断两个对象的状态是否相等,状态相等就认为相等。下面给出编写一个完美equals方法的建议:  

  1)显示参数命名为 otherObject

  2)检测 this 和 otherObject 是否引用同一个对象:if  ( this == otherObject ) return true; 起到优化的作用

  3)检测 otherObject 是否为 null,如果为 null,返回 false:if (otherObject == null) return false;

  4)比较 this 与 otherObject 是否属于同一个类,如果 equals 的语义在每个子类中有所改变,即由子类的属性状态决定相等性,就使用 getClass 检测:if (getClass() != otherObject.getClass()) return false;如果所有的子类都拥有统一的语义,即由超类决定相等的概念,那么就使用 instanceof 检测:if (!(otherObject instanceof ClassName)) return false;

  5)将 otherObject 转换成相应的类类型变量:ClassName other = (ClassName) otherObject

  6)现在开始对所有需要比较的域进行比较了。使用==比较基本类型域,使用equals比较对象域。 return field1 == other.field1 && Objects.equals(field2,other.field2) && ...;如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。


  提示:对于数组类型的域,可以使用Arrays.equals方法检测相应的数据元素是否相等。

顺便说一下:

  在Java中,instanceof 运算符的前一个操作符是一个引用变量,该变量为空则返回 false,后一个操作数通常是一个类(可以是接口),用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是返回 true,否则返回 false 。也就是说在使用 instanceof 关键字做判断时, instanceof  操作符的左右操作数必须有继承或实现关系。

使用 Objects 类的 equals 方法 Objects.equals(field2,other.field2) 避免如果 field2 为 null 时,field2.equals(other.field2) 这种比较方法会产生异常,Objects 类在 java.util 包中,源码如下:

    public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); } 

  举个栗子:

  View Code 

运行结果:

1
2
empOne  equals  empTwo: false
empOne  equals  empTwo: true

子类中定义equals方法:

  View Code

到这里基本说完了,还有Java语言规范要求的 equals 方法的 5 条特性,可以在API文档中 Object 类中 equals 方法查看。

2、hashCode方法

  散列码是由对象导出的一个整形值。Object类中定义的hashCode方法源码如下:

1
public  native  int  hashCode();

  使用了native关键字,我看不懂具体方法实现,有时间看完 native关键字 这篇博客应该能明白一些,具体以后再说,暂时不常用。

  String 类使用下列算法计算散列码:

1
2
3
int  hash =  0 ;
for  ( int  i =  0 ; i < length(); i++)
     hash =  31  * hash + charAt(i);

  由于 hashCode 方法定义在 Object 类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。 使用 Object 类的这个方法可以用 Objects 的 hashCode 方法保证 null 安全。

  如果重新定义 equals 方法,就必须重新定义 hashCode 方法,以便用户可以将对象插入到散列表中(散列表在核心技术I第9章讨论)。

  hashCode方法应该返回一个整形数值,并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。例如,下面是 Employee 类的 hashCode 方法。

1
2
3
4
@Override    //组合多个散列值,如果存在数组类型的域,使用静态的Arrays.hashCode方法计算一个散列码,equals与hashCode的定义必须一致
public  int  hashCode() {
     return  Objects.hash(name, salary, hireDay);
}

  涉及的源码如下:

  View Code

  equals 与 hashCode 的定义必须一致:如果 x.equals(y)  返回 true,那么 x.hashCode() 就必须与 y.hashCode() 具有相同的值。例如,如果用定义的 Employee.equals 比较雇员的ID,那么 hashCode 方法就需要散列ID,而不是雇员的姓名或存储地址。

  先说到这吧,后面看了散列表再理解。

3、toString方法

   Object 中的一个重要方法,用于返回表示对象值的字符串。

  下面是 Employee 类中的 toString 方法的实现:

1
2
3
4
5
6
7
8
@Override
public  String toString() {
     return  getClass().getName()
             "[name="  + name
             ",salary="  + salary
             ",hireDay="  + hireDay
             "]" ;
}

  下面是 Manager 类中的 toString 方法:

1
2
3
4
@Override
public  String toString() {
     return  super .toString() +  "[bonus="  + bonus +  "]" ;
}

  随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符 “+” 连接起来,Java 编译就会自动地调用 toString 方法,以便获得这个对象的字符串描述。

    提示:在调用 x.toString() 的地方可以用 ""+x 替代。这里的 x 就是 x.toString() 。与 toString 不同的是,如果 x 是基本类型,这条语句照样能够执行。

  System.out.println(x); println 方法就会直接调用 x.toString(x) ,并打印输出得到的字符串。

  Object 类定义了 toString 方法,用来打印输出对象所属的类名和散列码。例如 System.out.println(System.out); 语句输出 java.io.PrintStream@154617c ,因为 PrintStream 类没有覆盖 toString 方法。Object 类的toString方法源码如下:

1
2
3
public  String toString() {
     return  getClass().getName() +  "@"  + Integer.toHexString(hashCode());
}

    警告:令人烦恼的是,数组继承了 object 类的 toString 方法,数组类型将按照旧的格式打印,生成字符串如 “[I@1a46...”(前缀 [I 表明是一个整形数组)。修正的方式是调用静态方法 Arrays.toString,多维数组调用 Arrays.deepToString 方法。

  toString 方法是一种非常有用的调试工具。在标准类库中,许多类都定义了 toString方法, 以便用户能够获得一些有关对象状态的必要信息。强烈建议为自定义的每一个类增加 toString 方法。

 到这里基本把 Object 类的重要方法介绍了,后续有新东西会继续添加。

 程序清单: 

  Employee.java
  Manager.java

运行结果:

复制代码
empOne equals empTwo:false
empOne equals empTwo:true
-1893166707
-1893166707
com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25]
com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25]
com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25]
empOne equals managerOne:false
managerOne equals managerTwo:false
managerOne equals managerThree:true
-1893166707
-1683903746
com.song.Manager[name=song,salary=10.0,hireDay=2019-05-25][bonus=0.0]
com.song.Manager[name=song,salary=10.0,hireDay=2019-05-25][bonus=0.0]
com.song.Manager[name=wang,salary=100.0,hireDay=2019-05-28][bonus=0.0]
java.io.PrintStream@154617c

Process finished with exit code 0
复制代码

  至于结果是两个不同类的对象生成的散列码是相同的这个情况,是否违背了上面说的 equals 与 hashCode 的定义必须一致的原则。

5.3 泛型数组列表

java.util.ArrayList<T>

ArrayList<>()  构造一个空数组列表

ArrayList<T>(int initialCapacity)  用指定容量构造一个空数组列表

boolean add(T obj)  在数组列表的尾端添加一个元素

int size() 返回存储在数组列表中的当前元素数量。(这个值小于或等于数组列表的容量)

void ensureCapacity(int capacity)  确保数组列表在不重新分配存储空间的情况下就能够保存给定数量的元素。

void trimToSize()  将数组列表的存储容量消减到当前尺寸

使用set()和get()方法实现访问或改变数组元素的操作,而不使用[]

例如,要设置第i个元素,可以使用:

staff.set(i, harry);

等价于对数组a的元素赋值(数组的下标从0开始):

a[i] = harry;

注意:

只有i小于或等于数组列表的大小时,才能够调用list.set(i,x)。例如,下面这段代码是错误的:

ArrayList<Employee> list = new ArrayList<>(100); //capacity 100,size 0

list.set(0,x); // no element 0 yet

使用add方法为数组添加新元素,而不要使用set方法, 它只能替换数组中已经存在的元素内容。

Employee e = staff.get(i);

等价于:

Employee e = a[i];

下面这个既可以灵活扩展数组(对小型数组),又可以方便地访问数组元素。

首先,创建一个数组,并添加所有的元素

ArrayList<X> list = new ArrayList<>();

while(...)

{

  x = ...;

  list.add(x);

}

执行完之后,使用toArray方法将数组元素拷贝到另一个数组中

X[] a = new [list.size()];

list.toArray(x);

5.4  对象包装类与自动装箱

5.4.1 对象型包装类
对象型(Object的子类):Boolean、Character(char)

5.4.2 装箱与拆箱
装箱:将基本数据类型变为包装类对象,通过每个包装类的构造方法实现装箱处理
拆箱:将包装类中包装的基本数据类型取出,利用的是××Value()方法

eg:Integer提供的intValue()
public class Test {
public static void main(String[] args) {
//装箱
Integer integer = new Integer(10);
//拆箱
int data = integer.intValue();
System.out.println(data+10);
}
}

5.4.3 自动拆装箱(语法糖)
JDK1.5新特性

public class Test {
public static void main(String[] args) {
//自动装箱 将基本数据类型变成对象
Integer integer = 10;
//自动拆箱
System.out.println(integer+10);
}
}

5.5 参数数量可变的方法

5.5.1,概述

在java SE 5.0 之前版本,每个java方法都是固定参数的。然而,现在提供了可变参数的方法调用。

5.5.2,定义

举例:

public class PrintStream{

    ……

    ​public PrintStream printf(String fmt,Object . . . args){ //三个英文句号 表示多参数参数

     ​    ​return format(fmt,args);

     }

5.5.3 使用

System.out.print("d% %s",n,"hello");

 实际上 args 等价于args[] ,所以在程序中当作数据类型处理。在这个例子中参数类型是任意的。也可以是某个类型的。比如

//多参数入参

public  int max(int... ints){

int maxi=0;

if(ints.length>0){

maxi=ints[0];

for(int i:ints){

if(i>maxi){maxi=i;}

}

}

System.out.println(maxi);

return maxi;

}

5.6 枚举类

5.6.1 定义

从JDK1.5之后,出现了枚举类这么一个概念,就是使用enum关键字来定义枚举类,枚举类enum是引用数据类型,可以简单地理解为一种特殊的java类。

枚举类的对象个数是有限的且固定的,可以将每一个对象一一列举出来。

5.6.2.枚举类的继承、被继承、实现接口问题

枚举类可以实现其他接口,但是不能继承其他的类,因为所有枚举类在编译后的字节码中都继承自 java.lang.Enum,因为Java没有多继承,所以不能继承其他的类;

枚举类也不能被其他类继承,因为所有枚举类在编译后的字节码中都是继承自 java.lang.Enum(由编译器添加)的 final class 类,final 的类是不允许被派生继承的。

5.6.3.Java枚举如何保证线程安全

因为 Java 类加载与初始化是 JVM 保证线程安全,而 Java enum 枚举在编译器编译后的字节码实质是一个 final 类,每个枚举类型是这个 final 类中的一个静态常量属性,其属性初始化是在该 final 类的 static 块中进行,而 static 的常量属性和代码块都是在类加载时初始化完成的,所以自然就是 JVM 保证了并发安全。

5.7 反射

5.7.1  使用反射的三种方式

 {  1, 类名.class

     2.对象.getClass()

     3.使用Class.forName("路径+类名")}

5.7.2 定义

在程序运行状态中,对于任意一个类,都能够知道它的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

通俗点讲:有一个类,我想访问这个类中的成员变量、构造方法和成员方法,正常情况下,我们拿到的是这个类的.java文件,那么我直接通过new关键字创建一个类的实例对象,然后通过这个实例对象就可以访问这个类的成员变量、构造方法、成员方法了。但是现在我没有这个类的.java文件,而是只能拿到了这个类的.class文件,那么如何去访问到这个类中的成员变量、构造方法和成员方法呢?这就是反射机制解决的问题了。

第二部分:实验部分

1、实验目的与要求

(1) 理解继承的定义;

(2) 掌握子类的定义要求

(3) 掌握多态性的概念及用法;

(4) 掌握抽象类的定义及用途。

2、实验内容和步骤

实验1:测试程序1

Ÿ 在elipse IDE中编辑、调试、运行程序5-1 —5-3(教材152页-153

Ÿ 掌握子类的定义及用法;

Ÿ 结合程序运行结果,理解并总结OO风格程序构造特点,理解Employee和Manager类的关系子类的用途,并在代码中添加注释;

Ÿ 删除程序中Manager类、ManagerTest类,背录删除类的程序代码,在代码录入中理解父类与子类的关系和使用特点。

例题5.1程序代码如下:

package inheritance;

/**
 * This program demonstrates inheritance.
 * @version 1.21 2004-02-21
 * @author Cay Horstmann
 */
public class ManagerTest
{
   public static void main(String[] args)
   {
      // construct a Manager object
      var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);//创建一个新经理,并设置他的奖金,setBonus属于manager的特有方法

      var staff = new Employee[3];//定义一个包含三个雇员的数组

      // fill the staff array with Manager and Employee objects

      staff[0] = boss;
      staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
      staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
      //父类引用子类对象;并将经理和雇员放入数组中
      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
      //输出每个人的姓名及薪水
   }
}

输出截图如下:

 例题5.2程序代码如下:

package inheritance;

import java.time.*;

public class Employee
{
   private String name;
   private double salary;
   private LocalDate hireDay; //构建成员变量
   //构造器
   public Employee(String name, double salary, int year, int month, int day)
   {
      this.name = name;
      this.salary = salary;
      hireDay = LocalDate.of(year, month, day);
   }
   //域访问器 
   public String getName()//取得name这个属性的值
   {
      return name;
   }

   public double getSalary()//取得Salary这个属性的值
   {
      return salary;
   }

   public LocalDate getHireDay()
   {
      return hireDay;
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }
}

输出截图如下:

例题5.3程序代码如下:

package inheritance;

public class Manager extends Employee
{
   private double bonus;

   /**
    * @param name the employee's name
    * @param salary the salary
    * @param year the hire year
    * @param month the hire month
    * @param day the hire day
    */
 //构造器
   public Manager(String name, double salary, int year, int month, int day)
   {
      super(name, salary, year, month, day);//调用超类构造器
      bonus = 0;
   }

   public double getSalary()
   {
      double baseSalary = super.getSalary();//进行重定义
      return baseSalary + bonus;
   }

   public void setBonus(double b)
   {
      bonus = b;
   }
}

输出截图如下:

 删除类的程序代码如下:

package inheritance;

public class Manager extends Employee    
{

    private int bouns;

    public Manager(String name, double salary, int year, int month, int day) {
        super(name, salary, year, month, day);
        // TODO Auto-generated constructor stub
        bouns=0;
    }

    public int getBouns() {
        return bouns;
    }

    public void setBouns(int bouns) {
        this.bouns = bouns;
    }

    @Override
    public double getSalary() {
        // TODO Auto-generated method stub
        return super.getSalary();
    }
 
}

 OO风格程序构造特点:继承的特点是具有层次结构,使程序的构造思路看上去一目了然。

测试程序2:

Ÿ 编辑、编译、调试运行教材PersonTest程序(教材163页-165页);

Ÿ 掌握超类的定义及其使用要求;

Ÿ 掌握利用超类扩展子类的要求;

Ÿ 在程序中相关代码处添加新知识的注释;

Ÿ 删除程序中Person类、PersonTest类,背录删除类的程序代码,在代码录入中理解抽象类与子类的关系和使用特点。

 例题5.4程序代码如下(Person Test):

package abstractClasses;

/**
 * This program demonstrates abstract classes.
 * @version 1.01 2004-02-21
 * @author Cay Horstmann
 */
public class PersonTest
{
   public static void main(String[] args)
   {
      var people = new Person[2];//超类

      // fill the people array with Student and Employee objects
      people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
      people[1] = new Student("Maria Morris", "computer science");//子类;将雇员和学生填充到Person引用数组

      // print out names and descriptions of all Person objects
      for (Person p : people)
         System.out.println(p.getName() + ", " + p.getDescription());//输出对象的姓名和星描述
   }
}

程序运行截图如下:

例题5.5程序代码如下:

package abstractClasses;
//定义一个抽象类Person
public abstract class Person
{
   public abstract String getDescription();
   private String name;

   public Person(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return name;
   }
}

输出截图如下:

例题5.6程序代码如下(Employee类):

package abstractClasses;

import java.time.*;//导入java.time包

public class Employee extends Person
{
   private double salary;
   private LocalDate hireDay;//构建成员变量
    //构造器
   public Employee(String name, double salary, int year, int month, int day)
   {
      super(name);   //使用关键字super调用超类
      this.salary = salary;//this:当前对象
      hireDay = LocalDate.of(year, month, day);
   }
    //域访问器 
   public double getSalary()
   {
      return salary;
   }

   public LocalDate getHireDay()
   {
      return hireDay;
   }

   public String getDescription()
   {
      return String.format("an employee with a salary of $%.2f", salary);
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }
}

输出结果如下:

例题5.7程序代码如下:

package abstractClasses;

public class Student extends Person
{
   private String major;

   /**
    * @param name the student's name
    * @param major the student's major
    */
   public Student(String name, String major)
   {
      // pass name to superclass constructor
      super(name);
      this.major = major;
   }

   public String getDescription()
   {
      return "a student majoring in " + major;
   }
}

输出结果如下:

 

 删除类的程序代码:

package abstractClasses;

import java.time.*;

public class Employee extends Person
{
   public Employee(String name) {
        super(name);
        // TODO Auto-generated constructor stub
    }
private double salary;
   private LocalDate hireDay;
@Override
public String getDescription() {
    // TODO Auto-generated method stub
    return String.format("an employee with a salary of $%.2f", salary);
}
@Override
protected Object clone() throws CloneNotSupportedException {
    // TODO Auto-generated method stub
    return super.clone();
}
@Override
public boolean equals(Object obj) {
    // TODO Auto-generated method stub
    return super.equals(obj);
}
@Override
protected void finalize() throws Throwable {
    // TODO Auto-generated method stub
    super.finalize();
}
@Override
public int hashCode() {
    // TODO Auto-generated method stub
    return super.hashCode();
}
@Override
public String toString() {
    // TODO Auto-generated method stub
    return super.toString();
}
public double getSalary() {
    return salary;
}
public void setSalary(double salary) {
    this.salary = salary;
}
public LocalDate getHireDay() {
    return hireDay;
}
public void setHireDay(LocalDate hireDay) {
    this.hireDay = hireDay;
}
}

1:抽象类与子类的关系:只要子类没有把从父类继承的抽象方法全部实现(注意是全部实现),就必须也变成抽象类。父类有2个抽象方法,子类只实现了父类的1个抽象方法,则子类必须是抽象类。如果子类把父类的2个抽象方法都实现了,就没必要是抽象类。判断标准就是,非抽象子类中不能有抽象方法,如果有抽象方法,子类就必须是抽象类。如果子类添加了自己的抽象方法,子类也必须是抽象类,无论父类是不是抽象类。

2:抽象类的使用特点:

1)、抽象类不能实例化。

2)、抽象类可以包含抽象方法和抽象访问器。

3)、不能用 sealed 修饰符修饰抽象类,因为这两个修饰符的含义是相反的。 采用 sealed 修饰符的类无法继承,而 abstract 修饰符要求对类进行继承。

4)、从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实际实现。

测试程序3:

Ÿ 编辑、编译、调试运行教材程序5-8、5-9、5-10,结合程序运行结果理解程序(教材174页-177页);

Ÿ 掌握Object类的定义及用法;

在程序中相关代码处添加新知识的注释。

例题5.8程序代码如下:

package equals;

/**
 * This program demonstrates the equals method.
 * @version 1.12 2012-01-26
 * @author Cay Horstmann
 */
public class EqualsTest
{
   public static void main(String[] args)
   {
      var alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);//对alice1进行初始化
      var alice2 = alice1;//将alice1的值赋给alice2
      var alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15);//对alice3进行初始化
      var bob = new Employee("Bob Brandson", 50000, 1989, 10, 1);

      System.out.println("alice1 == alice2: " + (alice1 == alice2));

      System.out.println("alice1 == alice3: " + (alice1 == alice3));

      System.out.println("alice1.equals(alice3): " + alice1.equals(alice3));

      System.out.println("alice1.equals(bob): " + alice1.equals(bob));

      System.out.println("bob.toString(): " + bob);

      var carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);
      System.out.println("boss.toString(): " + boss);
      System.out.println("carl.equals(boss): " + carl.equals(boss));
      System.out.println("alice1.hashCode(): " + alice1.hashCode());
      System.out.println("alice3.hashCode(): " + alice3.hashCode());
      System.out.println("bob.hashCode(): " + bob.hashCode());
      System.out.println("carl.hashCode(): " + carl.hashCode());
   }
}

输出结果截图如下:

 

 例题5.9程序代码如下:

package equals;

import java.time.*;
import java.util.Objects;

public class Employee
{
   private String name;
   private double salary;
   private LocalDate hireDay; //构建成员变量

   public Employee(String name, double salary, int year, int month, int day)
   {
      this.name = name;
      this.salary = salary;
      hireDay = LocalDate.of(year, month, day);
   }
 //域访问器 
   public String getName()//取得name这个属性的值
   {
      return name;
   }

   public double getSalary()//取得Salary这个属性的值
   {
      return salary;
   }

   public LocalDate getHireDay()
   {
      return hireDay;
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }

   public boolean equals(Object otherObject)
   {
      // a quick test to see if the objects are identical
      if (this == otherObject) return true;//判断两个引用是否是同一个

      // must return false if the explicit parameter is null
      if (otherObject == null) return false;// // 若参数为空,则返回false

      // if the classes don't match, they can't be equal
      if (getClass() != otherObject.getClass()) return false;//getClass():得到对象的类

      // now we know otherObject is a non-null Employee
      var other = (Employee) otherObject;

      // test whether the fields have identical values
      return Objects.equals(name, other.name) 
         && salary == other.salary && Objects.equals(hireDay, other.hireDay);
   }

   public int hashCode()
   {
      return Objects.hash(name, salary, hireDay); 
   }

   public String toString()
   {
      return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" 
         + hireDay + "]";
   }
}

输出结果如下:

 例题5.10程序代码如下:

package equals;

public class Manager extends Employee
{
   private double bonus;

   public Manager(String name, double salary, int year, int month, int day)
   {
      super(name, salary, year, month, day);
      bonus = 0;
   }

   public double getSalary()
   {
      double baseSalary = super.getSalary();
      return baseSalary + bonus;
   }

   public void setBonus(double bonus)
   {
      this.bonus = bonus;
   }

   public boolean equals(Object otherObject)
   {
      if (!super.equals(otherObject)) return false;
      var other = (Manager) otherObject;
      // super.equals checked that this and other belong to the same classs
      //检查是否属于同一类
      return bonus == other.bonus;
   }

   public int hashCode()
   {
      return java.util.Objects.hash(super.hashCode(), bonus);
   }

   public String toString()
   {
      return super.toString() + "[bonus=" + bonus + "]";
   }
}

输出结果如下:

Object类的定义及用法:

Object类是所有类的顶层父类,所有的类都直接或间接的继承自他。

Object位于Java.lang包中,Java.lang 中包括着Java最基础的核心类在编译时会自动导入。

实验2编程练习

Ÿ定义抽象类Shape:

属性:不可变常量double PI,值为3.14;

方法:public double getPerimeter();public double getArea())。

Ÿ   让Rectangle与Circle继承自Shape类。

Ÿ   编写double sumAllArea方法输出形状数组中的面积和和double sumAllPerimeter方法输出形状数组中的周长和。

Ÿ   main方法中

1)输入整型值n,然后建立n个不同的形状。如果输入rect,则再输入长和宽。如果输入cir,则再输入半径。
2) 然后输出所有的形状的周长之和,面积之和。并将所有的形状信息以样例的格式输出。
3) 最后输出每个形状的类型与父类型,使用类似shape.getClass()(获得类型),shape.getClass().getSuperclass()(获得父类型);

思考sumAllArea和sumAllPerimeter方法放在哪个类中更合适?

输入样例为:

复制代码
3
rect
1 1
rect
2 2
cir
1
复制代码

输出样例为:

18.28
8.14
[Rectangle [width=1, length=1], Rectangle [width=2, length=2], Circle [radius=1]]
class Rectangle,class Shape
class Rectangle,class Shape
class Circle,class Shape

 实验程序代码如下:

package shape;
import java.util.Scanner;
public class 种类 {
 public static void main(String[] args) {
  Scanner in = new Scanner(System.in);
  System.out.println("请输入形状的个数:");
  int a = in.nextInt();
  System.out.println("请输入形状的种类:");
  String rect="rect";
        String cir="cir";
  shape[] num=new shape[a];
  for(int i=0;i<a;i++){
   String input=in.next();
   if(input.equals(rect)) {
   System.out.println("请输入形状的长和宽:");
   int length = in.nextInt();
   int width = in.nextInt();
         num[i]=new Rectangle(width,length);
         System.out.println("Rectangle["+"length:"+length+"  width:"+width+"]");
         }
   if(input.equals(cir)) {
         System.out.println("请输入形状的半径:");
      int radius = in.nextInt();
      num[i]=new Circle(radius);
      System.out.println("Circle["+"radius:"+radius+"]");
         }
         }
                   种类 c=new 种类();
         System.out.println("求和");
         System.out.println(c.sumAllPerimeter(num));
         System.out.println(c.sumAllArea(num));
         
         for(shape s:num) {
             System.out.println(s.getClass()+","+s.getClass().getSuperclass());
             }
         }
 
           public double sumAllArea(shape score[])
           {
           double sum=0;
           for(int i=0;i<score.length;i++)
               sum+= score[i].getArea();
               return sum;
           }
           public double sumAllPerimeter(shape score[])
           {
           double sum=0;
           for(int i=0;i<score.length;i++)
               sum+= score[i].getPerimeter();
               return sum;
           }    
}
package shape;
//定义抽象父类shape
abstract class shape { 
//定义求解周长的方法
abstract double getPerimeter(); 
//定义求解面积的方法
abstract double getArea(); 
}
//继承父类
class Rectangle extends shape{ 
private int length;
private int width;
public Rectangle(int length, int width) {
    this.length = length;
    this.width = width;
}
//调用父类求周长的方法
double getPerimeter(){ 
return 2*(length+width);
}
//调用父类求面积的方法
double getArea(){
return length*width; 
}
}

class Circle extends shape{
private int radius;
public Circle(int radius) {
    this.radius = radius;
}
double getPerimeter(){
return 2 * Math.PI * radius;
}
double getArea(){
return Math.PI * radius * radius;
}
}

输出截图如下:

实验总结:

1):在第五章中我们学习了继承,通过前一周的学习和在国庆期间的自学,我初步掌握了有关继承类的知识吗,使用继承类更能简化程序。了解了子类和父类的关系。另外,学习了抽象类的用法,在运行程序的过程中,加深了对它的理解。将会程序分装模块化,使得我对问题有了一定的了解并且尝试解决。在上周学长和老师讲解的实验三,将问题进行拆分,使得对编程迷茫的我,有了一定的认知。

2):在这周实验课上,我们进行了小测试实验,在测试中我们意识到了自己的不足,在测试中我们对例题的导入以及错误的修改更加深入的了解,对继承的相关知识有了更进一步的掌握。

3):在老师布置的这次实验中,我通过参考示例程序,解决了这次老师布置的任务。让我对JAVA有了更近一步的认知,在今后的学习中仍需努力,并且多动手,多敲代码,进一步提高自己的编程能力。

猜你喜欢

转载自www.cnblogs.com/wyt0455820/p/11614934.html