java当中的继承

继承的含义

继承已存在的类就是复用(继承)这些类的方法和域,在此基础上,还可以添加一些新的方法和域.is-a关系是继承的一个明显特征

类,超类,子类
例如
public class Manager entends Employee
{
    //添加方法和域
}

关键词entends表明正在构造的新类派生于一个已存在的类

  • 已存在的类称为超类,基类或父类
  • 新类称为子类,孩子类,派生类
  • 子类往往比超类拥有更丰富的域和方法
  • 在扩展子类的时候,仅需要指出子类与超类的不同之处,而将通用的方法放在超类中
覆盖方法

当超类中的方法子类并不适用的时候

    例如
    //超类
     class Employee{
    
    //加薪
    private double salary;
   

    public double getSalary(){
        return  salary;
    }



}
//子类
   public class Manager entends Employee
{
 private double bouns = 200;
 
  public double getSalary(){
  //返回奖金总和
  double baseSalary  = super.getSalary();
        return  bouns+baseSalary;
    }
}

具体来说,子类中的getSalary()方法需要返回奖金总和,尽管Manger类也有一个salary域,但是Manger类的getSalary()方法并不能直接访问Employee类的Salary域.只有Employee才能访问他自己的私有域,如果Manger类有需求访问,需要借助Employee类的接口,就是getSalary()方法,我们需要调用超类的方法而不是子类,所以需要加上surpe关键字.

子类构造器
public class Manager entends Employee
 {
 public Manger(String name,double salary,int year,int month,int day)
 {
    surpe(name,salary,year,month,day);
    bonus = 0;
 }

     }

由于子类的构造器不能访问超类的私有变量,所以必须利用超类的构造器对这部分私有域进行初始化,我们可以通过surpe实现对包含这些参数的构造器显式的调用,否则将会默认使用超类的无参构造,如果既没有子类的显式调用,超类又没有写无参构造,就会报告错误

多态与动态绑定

一个对象变量可以指示多种实际类型的现象称为多态

在运行时能够自动选择执行哪个方法的现象称为动态绑定
在java中,默认的处理方式就是动态绑定

事例
//超类
package inheritance;

import java.time.LocalDate;

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(){
    return name;
}

public double getSalary(){
    return  salary;
}

public LocalDate getHireDay(){
    return hireDay;
}

public void raiseSalary(double byPercent){

    double raise = salary * byPercent / 100;
    salary += raise;

}

}


//子类

package inheritance;

public class Manager extends Employee {


private double bouns;

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

   super(name,salary,year,month,day);
    bouns = 0;


}

public double getSalary(){

    double baseSalary = super.getSalary();

    return baseSalary +bouns;


}

public void setBouns( double bouns){

    this.bouns = bouns;

}

}


//main方法

    package inheritance;

public class ManagerTest {
public static void main(String[] args) {



    Manager boss = new Manager("nike",90000,2000,10,10);
    boss.setBouns(10000);

    Employee[] staff = new Employee[3];

    staff[0] = boss;
    staff[1] = new Employee("make",40000,1989,12,15);
    staff[2] = new Employee("tom",30000,1990,12,15);

    for(Employee e:staff){

        e.raiseSalary(5);

    }
    for (Employee e:staff){
        System.out.println("name"+e.getName()+" "+"salary"+e.getSalary()+" "+"hireDay"+e.getHireDay());
    }


}
}

继承层次

含义:
由一个超类派生出所有子类的集合称为继承层次

继承链

含义:
由某个特定的类到其祖先的路径被称为该类的继承链

  • java中不支持多继承
  • 相关的多继承应该通过接口实现
多态

判断是否应该设计成继承的简单规则"is-a"

它表明子类的每个对象也可以是超类的对象

例如
Employee e;

e = new Employee(...);
e = new Manager(...);

置换法则的优点

    staff[0] = boss;
staff[1] = new Employee("make",40000,1989,12,15);
staff[2] = new Employee("tom",30000,1990,12,15);

但是不能将超类的引用赋给子类变量

例如,下面的引用是非法的
Manager m = staff[i];

//因为不是所有的员工都是经理

还需要注意的是,虽然子类数组的引用可以转化为超类数组的引用,但是所有的数组都要牢记创建他们时候的元素类型,否则就像下面的例子

staff[0] = new Employee(...);

//很显然将普通员工添加到了经理当中,但下面他将找不到setbouns方法,会发生运行时错误,
//如果试图存储不兼容的数组类型将会发生ArrayStoryException异常
理解方法调用

方法调用的一般步骤

  • 编辑器查看对象的声明类型和方法名

  • 然后编辑器将从java虚拟机方法表中列出所有可用的方法(java虚拟机预先为每个类提供了一个方法表)

  • 接下来编辑器查看参数类型(一旦参数类型完全匹配,就选择这个方法,这个过程叫做重载解析)

  • 最后虚拟机调用方法

      例如在Emoployee的方法表中,列出了这个类的所有方法
      Employee:
      getName() ->Employee.getName()
      getSalary() ->Employee.getSalary()
      getHireDay()->Employee.getHireDay()
      raiseSalary() ->Employee.raiseSalary()
      
      实际上这并不完整,省略了Object超类的方法
    

动态绑定的特性:无需对现存的代码进行修改,就可以对程序进行扩展

阻止继承

不允许被扩展(继承)的类称为final类

final类需要提前声明

例如

  public final class Manager extends Employee {
  
      
  }
  
//类中的特定方法也可以声明为final方法,这将意味着他不能被覆盖重写
强制类型转换

有时候需要将某个类的对象装换为另一个类来使用

//对象引用的语法与类数值表达式的类型转换相似

例如

Manager boos = (Manager)staff[0];

进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能

注意:

  • java编译器允许将子类的引用赋给超类,但将一个超类的引用赋给子类变量必须进行类型转换

  • 如果试图在继承链上进行向下的转换,并且谎报有关对象包含的内容,会产生ClassCastException异常

  • 使用instanceof操作符可以进行类型转换前的检查

    例如
    if(staff[1] instanceof Manager){

    }

综上所述

  • 只能在继承层次类内进行对象引用的类型转换
  • 在超类转换成子类之前,应该用instanceof进行检查
  • 在一般情况下,推荐不使用类型转化

抽象类

在java程序设计中,抽象是一个十分重要的概念

抽象类的简单概念
  • 使用abstract关键字定义的类

  • 如果一个类包含一个或多个抽象方法就必须被定义为抽象类

  • 即使某个类不包含任何抽象方法,也可以根据需要被定义为抽象类

  • 抽象类不能被实例化,但可以创建一个具体子类的对象

      实例
      
      
      package abstractClass;
    
    
      //定义一个抽象类
      public abstract  class Person {
    
      //定义一个抽象方法
      public abstract String getDescription();
    
      private String name;
    
      //构造器
      public Person(String name){
          this.name = name;
      }
    
      public String getName(){
          return name;
          }
    
    
    
      }
    

//抽象类的子类
package abstractClass;

import java.time.LocalDate;

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;
        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;

}
}

package abstractClass;

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;

}
}

package abstractClass;

public class PersonTest {
public static void main(String[] args) {

    Person[] people = new Person[2];

   //java当中的多态与动态绑定
    people[0] = new Employee("tom",10000,2000,11,9);
    people[1] = new Student("jake","computer");



    for(Person p: people){

        System.out.println(p.getName()+""+p.getDescription());
    }


    //对象的类型转换,超类的引用转化成子类
    Employee staff = (Employee)people[0];
    //类型转换的意义,忽略对象类型使用全部方法
    staff.raiseSalary(5);
    //得到上涨后的薪资
        System.out.println(staff.getSalary());
  }






}

理解这个例子就理解了抽象类

受保护访问(Protected)
  • 如果希望超类中的某些方法或者某些域允许被子类访问,就可以把这些方法或者这些域设置成Protected属性
  • 但是建议受保护属性最好应用在方法而不是域上,因为这违背了oop的数据封装原则

归纳java用于控制可见性的四个修饰符

1.仅对本类可见--------------privite
2.被所有类可见--------------publice
3.对本包和所有子类可见------protected
4.对本包可见----------------默认(什么都不写)
Object类:所有类的超类

在java中,所有类都是Object类扩展开来的,熟悉这个类提供的所有服务特别重要

equals方法

Object类中的equals方法用于检测一个对象是否等于另一个对象.

  • 如果两个对象具有相同的引用,他们一定是相等的
相等测试与继承

java语言规范要求equsls具有以下特性

 - 自反性:对于任何非空引用x,x.equals(x)应该返回true,x.equals(y)也应该返回true.
 - 对称性:对于任何引用x,y 当且仅当x.equals(y)为true时,y.equals(x)也应该为true
- 传递性:对于任何引用x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true.
 - 一致性:如果x和y的引用的对象没有发生变化,反复调用x.equals(y)应该返回一致的结果
 - 对于任意非空引用x,x.equals(null)应该返回false

重点:在java类库中拥有超过150个equals方法的具体实现,问题来了,java中为什么要重写equals方法?

- Object类中equals方法比较的是两个对象的引用地址,只有对象的引用地址指向同一个地址时,才认为这两个地址是相等的,否则这两个对象就不相等。
- 如果有两个对象,他们的属性是相同的,但是地址不同,这样使用equals()比较得出的结果是不相等的,而我们需要的是这两个对象相等,因此默认的equals()方法是不符合我们的要求的,这个时候我们就需要对equals()方法进行重写以满足我们的预期结果。
- 在java的集合框架中需要用到equals()方法进行查找对象,如果集合中存放的是自定义类型,并且没有重写equals()方法,则会调用Object父类中的equals()方法按照地址比较,往往会出现错误的结果,此时我们应该根据业务需求重写equals()方法。

下面是一个给出编写完美equals方法的步骤建议

1.显式参数命名为otherObject,稍后需要将它转化为另一个叫做other的变量

2.检测this和object类是否引用同一个对象
if(this == otherObject) return ture;

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 false;

5.将otherObject转换为相应的类和变量

ClassName other = (ClassName) otherObject;

6.现在开始对所有需要比较的域进行比较,使用==比较基本域,使用equals比较对象域,如果所有的域都匹配,则返回true,否则返回false

如果在子类中重新定义equals,就要在其中包含super.equals(other).

对于数组类型的域,可以用静态方法Array.equals()进行比较
hashcode方法

散列码(hashcode)是由对象导出的一个整型值,散列码没有规律,每个对象都有一个默认的散列码,其值为对象的储存地址.

例如
    String s = "OK";
    StringBuilder sb = new StringBuilder(s);

    System.out.println(s.hashCode() + "  " + sb.hashCode());

    String t = "OK";
    StringBuilder tb = new StringBuilder(t);

    System.out.println(t.hashCode() + "  " + tb.hashCode());
    
    
    输出的结果是
    2524  460141958
    2524  1163157884

字符串s,t拥有相同的散列码,而字符串缓冲sb和tb却拥有着不同的散列码,这是因为StringBuffer类中并没有定义hashcode方法,它的散列码就是Object类默认的导出对象的存储地址

所以,重新定义equals方法,就需要重新hashcode方法,以便用户可以将对象插入散列表
因为如果x.equals(y)返回true,那么x.hashcode()就必须与y.hashcode()返回相同的散列码.

首先,最好使用null安全的方法Object.hashcode()

public int hashcode()
{
   //如果参数为null,则将返回0
    return 7*Object.hashcode(name)
    //使用静态方法Double.hashcode来避免创建double对象
            +11*Double.hashcode(salary)
            +13*Object.hashcode(hireday);
}

还有更好的方式,如果需要组合多个参数的散列值,可以使用Objects.hash()方法

 public int hashcode()
{
   return Objects.hash(name,salary,hireday);
}
toString 方法
  • toString方法用于返回表示对象值的字符串

  • 只要对象与一个字符串通过+号连接起来,java就会默认调用toString方法

  • 强烈建议为自定义的每个类添加一个toString方法

    例如

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

例外的是,如果想打印出数组,应该使用Array.toString()静态方法,多维数组应该使用Arrays.deepToString()方法

泛型数组列表

在java中,运行在运行时动态更改数组的大小

例如
Int actualSize = ...;

Employee[] staff = new Employee[actualSize] ;

但是这样并不能实际的解决问题,java提供了一个被称为ArrayList的类,它使用起来比较像数组,但在添加或删除元素时,具有自动调节大小的功能
ArrayList是一个采用类型参数的泛型类
声明和构造一个Employee对象

ArrayList<Employee> staff = new ArrayList<>;

//使用add方法可以将元素添加到数组列表中

   staff.add(new Employee("tom",1000,...));

//size方法将返回实际的数组数量

   staff.size();
访问数组列表元素

数组列表自动扩展容量的便利增加了访问元素的复杂程度,原因是ArrayList并不是java程序语言的一部分,而是某个人写的实用类

  • 使用get和set方法实现对数组元素的访问和修改

    //get方法的调用者必须对返回值进行类型转换,怒人就会返回Object类型
    Employee e = (Employee)staff.get(i);
    //尽量使用add方法而不是set,后者会把元素替换掉
    staff.set(i,harry);

  • 下面的一个技巧可以一举两得

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

          ArrayList<X> list = newArrayList();
          while(....)
          {
              X = ....;
              list.add(X);
          }
    

    然后用toArray方法将它拷贝到另一个元素中

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

    可以在数据列表尾部或者中间插入新元素

  • 对数组实施插入删除的效率比较低,对于小型数组来说,这一点不必担心,但如果数组存储的元素太多,就需要用到链表.

对象包装器与自动装箱

所有的基本类型都有一个与之对应的类,这些类称为包装器

    例如
    Integer.Long.Float.Double.Shot.Byte.Character.Void.Boolean
    
    这六个类派生于公共的超类Number

假设一个整形数组列表,由于尖括号内的数组类型不允许是基本类型,所以应该是

ArrayList<Integer> list = new ArrayList<>;

从而有一个自动装箱的特性就是

list.add(3);
//将自动转换为
list.add(Integer.valueOf(3));

相反把Integer类型的数据插入进int类型时,会自动拆箱

这些特性提供便利的同时,如果插入的值为null,则会报告NullPointException异常

有个十分明显的使用,就是将数值转化放在包装器中

int x = Integer.parseInt(s);
参数数量可变的方法(变参)

现在版本的java提供了可变的参数调用的方法

除了printf这样的方法外,用户可以自定义可变参数的方法,并将参数指定为任意类型,甚至是基本类型

例如 计算某些数值的最大值

public static double max(double... values){
    double largest = Double.NEGATIVE_INFINITY;
    for(double v:values)
        if(v>largest)
            largest = v;

    return largest;
}



public static void main(String[] args) {

    double m = max(3.1,40.4,5);
    //编译器将new double[] {3.1,40.4,-5}传给方法

    System.out.println(m);

}
枚举类

所有的枚举类型都是Eunm类的子类,他们继承了许多这个类的方法

其中最有用的是toString()方法,这个方法返回枚举常量名
toString方法的逆方法是valueOf(),给变量设置常量名

下面是一个典型的例子
枚举类判断衣服尺码

import java.util.Scanner;

public class EnumTest {

public static void main(String[] args) {

    Scanner in = new Scanner(System.in);

    System.out.println("Enter size:(SMALL,MEDIUM,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.LARGE) {
        System.out.println("you are boy");
    }


}

enum Size {

    SMALL("S"), MEDIUM("M"), LARGE("L");

    private String abbreviation;

    private Size(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }
}


}

除反射外,继承方面告一段落
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值