继承基本思想:基于已有的类创建新的类,新类复用旧类的方法,并且可以增加新的方法和字段

5.1类、超类、子类

5.1.1定义子类

继承Employee类来定义Manager类,关键字extends表示继承

public class Manager extends Employee

{

  private double bonus;

  public void setBonus(double bonus)

  {

    this.bonus=bonus;

  }

}

在java中 所有继承都是公共继承

尽管在Manager类中没有显式定义getName和getHireDay方法,但是可以调用,hireDay,name,等字段也同样继承了

5.1.2覆盖方法

超类中的有些方法对子类Manager并不一定适用,例如Manager类中的getSalary方法应该返回薪水和奖金的总和。

为此需要提供一个新方法覆盖超类的方法

注意只有Employee方法能够访问Employee类的私有字段,这就需要使用super关键字

public double getSalary()

{

  double baseSalary=super.getSalary();

  return baseSalary+bonus;

}

5.1.3子类构造器

public Manager(String name,double salary,int year,int month,int day)

{

  super(name, salary, year, month, day);//super是调用超类Employee的构造器,必须是第一句

  bonus=0;

}

如果子类构造器没有显式调用超类构造器,将自动调用超类的无参数构造器

若超类没有无参数构造器,且在子类中又没有显式调用超类其他构造器,java编译器会报错

## 5.1.4 继承层次
java不支持多重继承,但提供了一些类似多重继承的功能
## 5.1.5 多态
有一个简单规则来判断是否应该将数据设计为继承关系 这就是is-a规则,它指出子类的每个对象也是超类的对象,例如每个经理也是员工\
程序中出现超类对象的任何地方都可以使用子类对象替换
```
Employee e;
e = new Employee(...);
e = new Manager(...);
```
java中,对象变量是多态的
```
Manager boss=new Manager(...);
Employee[] staff=new Employee[3];
staff[0]=boss;
```
在这个例子中,变量staff[0]和boss引用同一个对象,但是编译器只将staff[0]看成是一个Employee对象,这意味着
```
boss.setBonus(5000);//Ok
staff[0].setBonus(5000);//Error
```
因为setBonus不是Employee类的方法\
另外不可以将超类的引用赋给子类变量
## 5.1.6 理解方法调用
假设要调用x.f(args),隐式参数x声明为类c的一个对象,下面为调用详细过程:\
1. 编译器查看对象的声明类型和方法名,编译器会列举类c中所有名为f的方法和其超类中名为f并且可访问的方法(超类私有方法不可访问)
2. 编译器确定方法调用中提供的参数类型并挑选完全匹配的,这个过程称为重载解析\
    备注:返回类型不是签名的一部分,不过在覆盖一个方法时,需要保证返回类型的兼容性,允许子类将覆盖方法的返回类型改为原返回类型的子类型,这称为可协变的返回类型
    ```
    public Employee getBuddy(){...}
    public Manager getBuddy(){...}
    ```
3. 如果是private、static、final或构造器方法,那么编译器可以准确知道调用哪个方法。这称为静态绑定
4. 虚拟机预先为每个类计算了一个方法表,搜索表即可

代码中关于调用e.getSalary()的解析过程:
1. 虚拟机获取e的实际类型方法表
2. 查找定义了getSalary()签名的类
3. 调用方法

tip:覆盖一个方法时,子类方法不能低于超类方法的可见性
## 5.1.7 组织继承:final类和方法
不允许扩展的类称为final类
类中的某个方法可以声明为final,这样子类就不能覆盖这个方法
final类中的所有方法自动成为final方法(不包括字段)
## 5.1.8 强制类型转换
```
double x=3.1415;
int x2=(int) x;
```
如上所示,将浮点类型强制转换为整数,舍弃小数部分
同样可以将某个类的对象引用转换为另一个类的对象引用
```
Manager boss=(Manager) staff[0];
```
在ManagerTest类中,由于某些元素是普通员工,所以staff数组必须是Employee对象数组。需要将数组中引用经理的元素复原成Manager对象,才能访问子类新特性。
若将子类引用赋给超类变量,编译器是允许的。若将一个超类引用赋给一个子类变量,则必须进行强制类型转换。
另外,在进行强制类型转换前,首先用instanceof来检查是否可行
```
if(staff[1] instanceof Manager)
{
    boss=(Manager)staff[1];
    ...
}
```
综上所述,首先只能在继承层次内进行强制类型转换,其次转换前用instanceof操作符来检查是否可行。
## 5.1.9 抽象类
学生类和雇员类都是人,可以抽象出人类
```
public abstract class Person
{
    private String name;
    public Person(String name)
    {
        this.name=name;
    }
    public abstract String getDescription();
    public String getName()
    {
        return name;
    }
}
```
抽象方法充当占位方法的角色,他们在子类中具体实现,扩展抽象类有两种选择。一种在子类中保留抽象类中的部分或所有抽象方法仍未定义,这样就需要将子类也标记为抽象类。另一种则定义全部方法,子类非抽象。

抽象类不能实例化,可以定义一个抽象类的对象变量,但是这样的变量只能引用非抽象子类的对象。这里的p是一个抽象类型Person的变量,它引用了一个非抽象子类Student的实例。
```
Person p=new Student("Vince Vu","Economics");
```
下面定义一个扩展抽象类Person的具体子类Student
```
public class Student extends Person
{
    private String major;
    public Student(String name,String major)
    {
        super(name);
        this.major=major;
    }
    public String getDescription()
    {
        return "a student major in"+major;
    }
}

```
### PersonTest.java
```
package javaSX.abstractClasses;

public class PersonTest
{
    public static void main(String[] args)
    {
        var people=new Person[2];
        people[0]=new Employee("Harry",50000,1989,10,1);
        people[1]=new Student("Maria","computer science");
        for(Person p:people)
        {
            System.out.println(p.getName()+","+p.getDescription());
        }
    }
}
```
### Person.java
```
package javaSX.abstractClasses;

public abstract class Person
{
    public abstract String getDescription();
    private String name;
    public Person(String name){
        this.name=name;
    }
    public String getName()
    {
        return name;
    }
}
```
### Employee.java
```
package javaSX.abstractClasses;

import 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);
        this.salary=salary;
        this.hireDay=LocalDate.of(year,month,day);
    }
    public double getSalary()
    {
        return this.salary;
    }
    public LocalDate getHireDay()
    {
        return this.hireDay;
    }
    public void raiseSalary(double percent)
    {
        double raise=this.salary*percent/100;
        this.salary+=raise;
    }
    public String getDescription()
    {
        return String.format("an employee with a salary of $%.2f",salary);
    }
}   
```
### Student.java
```
package javaSX.abstractClasses;

public class Student extends Person
{
    private String major;
    public Student(String name,String major)
    {
        super(name);
        this.major=major;
    }
    public String getDescription()
    {
        return "a student majoring in"+major;
    }
}
```

## 5.1.10 受保护方法
1. 仅对本类可见——private
2. 对外部完全可见——public
3. 对本包可见——protected
4. 对本包可见——默认,无需修饰符

# 5.2 Object:所有类的超类
## 5.2.1 Object类型变量
可以使用Object类型的变量引用任何类型的对象
```
Object obj=new Employee("harry",35000);
```
object类型变量只能作为一个泛型容器,要想执行具体操作,需要知晓对象原始类型,并进行强制类型转换
```
Employee e=(Employee) obj;
```
## 5.2.2 equals方法
Object中实现的equals方法将确定两个对象引用是否相等。
```
public class Employee
{
    ...
    public boolean equals(Object otherObject)
    {
        if(this==otherObject) return true;
        if(otherObjecct==null) return false;
        if(getClass()!=otherObject.getClass()) return false;
        Employee e=(Employee) otherObject;
        return name.equals(e.name)&&salary==e.salary&&hireDay.equals(e.hireDay);
    }
    ...
}
```
getClass()会返回对象所属的类

为了防止name和hireDay为null的情况,需要使用Object.equals方法。\
如果两个参数都为null,Object.equals将返回true。\
如果其中一个参数为null,则返回false.\
 若两个参数都不为null,则可以调用a.equals(b)
```
return Object.equals(name,e.name)&&salary==e.salary&&Object.equals(hireDay,e.hireDay); 
```
在子类中定义equals方法时,首先调用超类equals方法,再比较子类独有字段
```
public class Manager extends Employee
{
    ...
    public boolean equals(Object otherObject)
    {
        if(!super.equals(otherObject)) return false;
        Manager m=(Manager) otherObject;
        return bonus==m.bonus;
    }
    ...
}
```
## 5.2.3 相等测试与继承
若子类可以有自己的相等性概念,则对称性需求将强制使用getClass检测\
如果由超类决定相等性概念,那么就可以使用instanceof检测,这样可以在不同子类的对象之间进行相等性比较。

在员工与经理的例子中,如果需要比较子类经理的奖金,我们就要使用getClass来检测。如果使用员工ID来比较,就可以使用instanceof,并且应该将Employee.equals生命为final

下面列出书写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;
```
如果所有子类都有相同的equals语义,可以使用instanceof检测
```
if(!(otherObject instanceof ClassName)) return fasle;
```
5. 将otherObject强制转换为相应类型变量
```
ClassName other=(ClassName) otherObject;
```
6. 最后比较字段,使用==比较基本类型,使用Object.equals比较对象类型
```
return Object.equals(name,other.name)&&salary==other.salary&&Object.equals(hireDay,other.hireDay);
```
如果在子类中重新定义equals,则要在其中使用super.equals(otherObject),再进行强制类型转换\
对于数组类型的字段,可以使用静态的Arrays.equals方法检测相应的数组元素
## 5.2.4 hashCode方法
散列码是由对象导出的整型值,可正可负。
```
var s="ok";
var sb=new StringBuilder(s);
System.out.println(s.hashCode()+" "+sb.hashCode());
var t=new String("ok");
var tb=new StringBuilder(t);
System.out.println(t.hashCode()+" "+tb.hashCode());
```
字符串s和t有相同的散列码,因为String的散列码由内容导出\
但sb与tb有着不同的散列码,因为StringBuilder没有定义hashCode,Object的hashCode默认从存储地址导出。

equals与hashCode必须要相容,如果x.equals(y)返回true,那么x.hashCode()就必须和y.hashCode()返回相同的值
## 5.2.5 toString方法
Employee中的toString
```
public String toString()
{
    return getClass().getName()+"[name="+name+", salary="+salary+", hireDay="+hireDay+"]";
}
```
Manager中的toString
```
public class Manager extends Employee
{
    ...
    public String toString()
    {
        return super.toString()+"[bonus="+bonus+"]";
    }
    ...
}
```
随处可见toString()方法的原因在于,只要对象与一个字符串通过操作符“+”连接起来,Java编译器就会自动调用toString方法来获得这个对象的字符串描述,例如
```
var p=new Point(10,20);
String message="Current point is "+p;
```
+p 也就意味着调用p.toString(), 对于基本数据类型同样适用

打印数组可以使用Arrays.toString,多维数组可以使用Arrays.deepToString

### EqualsTest.java
```
package javaSX.equals;

public class EqualsTest {
    public static void main(String args[])
    {
        var alice1=new Employee("Alice",75000,1987,12,15);
        var alice2=alice1;
        var alice3=new Employee("Alice",75000,1987,12,15);
        var bob=new Employee("Bob",50000,1989,10,1);
        System.out.println("alice==alice2:"+(alice1==alice2));
        System.out.println("alice==alice3:"+(alice1==alice3));
        System.out.println("alice.equals(alice3):"+(alice1.equals(alice3)));
        System.out.println("alice.equals(bob):"+(alice1.equals(bob)));
        System.out.println("bob.toString():"+bob);

        var carl=new Manager("Carl",80000,1987,12,15);
        var boss=new Manager("Carl",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());
    }
}
```
### Employee.java
```
package javaSX.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;
        this.hireDay=LocalDate.of(year,month,day);
    }
    public String getName()
    {
        return this.name;
    }
    public double getSalary()
    {
        return this.salary;
    }
    public LocalDate getHireDay()
    {
        return this.hireDay;
    }
    public void raiseSalary(double percent)
    {
        double raise=this.salary*percent/100;
        this.salary+=raise;
    }
    public String getDescription()
    {
        return String.format("an employee with a salary of $%.2f",salary);
    }
    public int hashCode()
    {
        return Objects.hash(name,salary,hireDay);
    }
    public String toString()
    {
        return getClass().getName()+"[name="+name+",salary="+salary+",hireday="+hireDay+"]";
    }
    public boolean equals(Object otherObject)
    {
        if(this==otherObject) return true;
        if(otherObject==null) return false;
        if(getClass()!=otherObject.getClass()) return false;
        var other=(Employee) otherObject;
        return Objects.equals(name,other.name)&&salary==other.salary&&Objects.equals(hireDay,other.hireDay);
    }
}
```
### Manager.java
```
package javaSX.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);
        this.bonus=0;
    }
    public void setBonus(double bonus)
    {
        this.bonus=bonus;
    }
    public int hashCode()
    {
        return java.util.Objects.hash(super.hashCode(),bonus);
    }
    public boolean equals(Object otherObject)
    {   
        if(!super.equals(otherObject)) return false;
        var other=(Manager) otherObject;
        return other.bonus==this.bonus;
    }
    public String toString()
    {
        return super.toString()+" bonus:"+bonus;
    }
    public double getSalary()
    {
        double baseSalary=super.getSalary();
        return baseSalary+bonus;
    }
}
```
# 5.3 泛型数组列表
运用ArrayList来解决动态数组问题,ArrayList是一个有类型参数的泛型类
## 5.3.1 声明数组列表
声明并构造一个保存Employee对象的数组列表
```
ArrayList<Employee> staff=new ArrayList<>();
```
使用add来添加元素,使用staff.remove(i)来删除元素
```
staff.add(new Employee("Harry",...));
staff.add(new Employee("Tony",...));
```
数组列表管理一个内部的对象引用数组,数组空间可能用尽,再次使用add时,会自动创建一个更大的数组列表,并拷贝现有元素。
```
staff.ensureCapacity(100);
ArrayList<Employee> staff=new ArrayList<>(100);
```
前者在声明构建暂时确定数组列表空间,后者在声明构建的时候暂时确定\
staff.size()返回数组列表当前元素个数。
## 5.3.2 访问数组列表元素
不能使用[]访问或改变数组列表的元素,要使用get和set方法。
set方法是替换元素,首先需要要通过add来添加元素。
Employee e=staff.get(i);可以获得数组列表第i个元素

使用for each循环遍历数组列表
```
for(Employee e:staff)
{
    ...//do something with e
}
//上下两种循环一致
for(int i=0;i<staff.size();i++)
{
    Employee e=satff.get(i);
    ...//do something with e
}
```
# 5.4 对象包装器与自动装箱
所有基本类型都有一个与之对应的类,称之为包装器,如下所示\
Integer,Long,Float,Double,Short,Byte,Character和Boolean\
包装器类是不可改变的,即不能改变其中的值\
包装器类还是final 即不能派生他们的子类
```
var list=new ArrayList<Integer>
list.add(3);
int n=list.get(i);
```
自动装箱:list.add(3)会自动变换成list.add(Integer.valueOf(3));\
自动拆箱:list.get(i)会自动变换成list.get(i).inValue();

==运算符可以应用于包装器类对象,不过检测的是对象是否有相同的内存地址,因此下面的比较通常会失败
```
Integer a = 1000;
Integer b = 1000;
if(a==b) ...
```
自动装箱规范要求boolean,byte,char<=127,介于-128和127之间的short和int被包装到固定的对象中。若在前面的例子中赋值为100,那么比较结果一定成功。

如果在一个表达式中混用Integer和Double,Integer值就会拆箱,提升为double,再装箱为Double\
要想将字符串转换成整形,可以使用下面的语句\
这里与Integer对象没有关系,parseInt是一个静态方法
```
int x=Integer.parseInt(s);
```
# 5.5 参数数量可变的方法
printf方法是这样定义的
```
public class PrintStream
{
    public PrintStream printf(String fmt,Object... args)
    {
        return format(fmt,args);
    }
}
```
省略号是代码的一部分,printf接受两个参数,一个是格式字符串,另一个是Object[] 数组。

自定义一个函数计算若干数值中的最大值
```
public static double max(double... values)
{
    double largest=Double.NEGATIVE_INFINITY;
    for(double v:values)
    {
        if(v>largest)
        {
            largest=v;
        }
    }
    return largest;
}
double m=max(3.1,40.05,-8.6);//调用方式
```
# 5.6 枚举类
一个类的对象是有限且固定的,这种情况下我们使用枚举类
```
package javaSX.enums;

import java.util.*;

public class EnumTest {
    public static void main(String[] args)
    {
        var in=new Scanner(System.in);
        System.out.print("Enter a size:(SMALL,MEDIUM,LARGE,EXTRA_LARGE) ");
        String input=in.next().toUpperCase();
        Size size=Enum.valueOf(Size.class, input);
        System.out.println("size="+size);
        System.out.println("abbreviation="+size.getAbbreviation());
        if(size==Size.EXTRA_LARGE)
        {
            System.out.println("Good Job");
        }
        in.close();
    }    
}
enum Size
{
    SMALL("S"),MEDIUM("M"),LARGE("L"),EXTRA_LARGE("XL");
    private String abbreviation;
    private Size(String abbreviation)
    {
        this.abbreviation=abbreviation;
    }
    public String getAbbreviation()
    {
        return this.abbreviation;
    }
}

```
1. enum和class、interface的地位一样
2. 使用enum定义的枚举类默认继承了java.lang.Enum,而不是继承Object类。枚举类可以实现一个或多个接口。
3. 枚举类的所有实例都必须放在第一行展示,不需使用new 关键字,不需显式调用构造器。自动添加public static final修饰。
4. 使用enum定义、非抽象的枚举类默认使用final修饰,不可以被继承。
5. 枚举类的构造器只能是私有的。
# 5.8 继承的设计技巧
1. 将公共操作和字段放在超类中
2. 不要使用受保护字段
3. 使用继承实现“is-a”关系

————————————————————————————————

tips:

this的两个含义:1、指示隐式参数的调用  2、调用该类的其他构造器

super的两个含义:1、调用超类的方法  2、调用超类的构造器