笔记:继承

类,超类与子类

继承

  • 继承是明显的“is-a”关系,由关键字extends表示继承;
    已经存在的类称为超类、基类或父类,新类称为子类或派生类;
package inheritance;
public class ManagerTest {
	public static void main(String[] args) {
		Manager boss=new Manager("Cral",5000,1987,12,15);
  		boss.setBonus(2000);
  		Employee[] staff=new Employee[3];
  		staff[0]=boss;
  		staff[1]=new Employee("Kral",3000,1999,1,15);
  		staff[2]=new Employee("Dral",4000,1996,10,5);
  		for(Employee e:staff) {
   			System.out.println("name="+e.getName()+" salary="+e.getSalary()+" hireDay="+e.getHireDay());
  		}
  	}
  }
package inheritance;
import java.time.LocalDate;
public class Employee {
	private String name;
 	private double salary;
 	private LocalDate hireDay;
 
 	public Employee(String n,double s,int year,int month,int day) {
  		name=n;
  		salary=s;
  		hireDay=LocalDate.of(year, month, day);
 	}
 	public String getName(){
  		return name;
 	}
 	public double getSalary(){
  		return salary;
 	}
 	public LocalDate getHireDay() {
  		return hireDay;
 	}
 }
package inheritance;
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);
  		setBonus(0);
 	}
 	//覆盖方法
 	public double getSalary() {
  		double baseSalary=super.getSalary();
  		return baseSalary+bonus;
 	}
 	public void setBonus(double bonus) {
  		this.bonus = bonus;
 	}
 }
  • 覆盖方法:
    public double getSalary() {
    return salary+bonus;
    }
    若直接返回salary,将无法运行,这是因为Manager的getSalary类无法直接访问超类的私有域,所以需要关键字super来调用Employee类的getSalary方法来调用salary的值,如下:
    public double getSalary() {
    double baseSalary=super.getSalary();
    return baseSalary+bonus;
    }
    注意在覆盖一个方法的时候,子类方法不能低于超类方法的可见性;
  • 子类构造器:
    例如:
    public Manager(String name,double salary,int year,int month,int day) {
    super(name,salary,year,month,day);
    bonus=0;
    }
    其中super(name,salary,year,month,day)是调用Employee中含有n,s,year,month,day的构造器,如果子类构造器没有显式的调用超类的构造器,则将自动调用超类的无参构造器,但是如果超类没有无参构造器,系统将报错;

多态

  • 一个对象变量可以指示多种实际类型的情况称为多态;
  • “is-a”规则也称为置换法则,表明出现超类对象的任何地方都可以用子类来置换,注意不能将一个超类引用赋给子类对象;
  • **final类和方法:**若是不允许其他类利用这个类定义子类,将这个不允许扩展的类称为final类,例如:
    public final class Executive extends Manager{
    }
    类中的方法也可以定义为final,这样的方法就不允许子类覆盖;
    public class Manager{
    public final String getName(){
    return name;
    }
    }
  • **强制类型转换:**为了在暂时忽略对象的实际类型的情况下使用对象的全部功能,可以使用强制类型转换。将一个子类的引用赋给超类变量,编译器是允许的,但是要将一个超类的引用赋给子类变量,必须进行强制类型转换,例如:
    Manager boss=(Manager)staff[0];
    有时候可能会发生“谎报”的情况,例如:
    String boss=(String)staff[0];//error所以在进行类型转换前要先判断是否可以成功转换,例如:
    if(staff[1] instanceof Manager){
    }
  • 综上:1、只能在继承关系内进行转换;2、在将超类转换成子类之前,应该使用instanceof进行检查。

抽象类

  • 对于抽象方法,就像占位表面需要一个这样的功能,但是具体实现出现在子类中;
  • 为了提高程序的清晰度,往往将包含一个或以上个抽象方法的类声明为抽象的,抽象类除了抽象方法还可以包含具体数据和方法;
  • 抽象类不能实例化,但是可以定义一个抽象类的对象变量,只能引用非抽象子类的对象;
package abstractClasses;
public class PeopleTest {
	public static void main(String[] args) {
		People[] people=new People[2];
  		people[0]=new Employee("Cral",5000,1999,9,19);
  		people[1]=new Student("Kitty","Math");
  		for(People e : people){
   			System.out.println(e.getName()+","+e.getDescription());
  		}
  	}
  }
package abstractClasses;
//包含抽象方法的类,用abstract修饰
public abstract class People {
private String name;
 public People(String n) {
  this.name=n;
 }
 public abstract String getDescription();//抽象方法
 public String getName() {
  return name;
 }
}
package abstractClasses;
import java.time.*;
public class Employee extends People{
private double salary;
 private LocalDate hireDay;
 
 public Employee(String n,double s,int year,int month,int day) {
  super(n);
  this.salary=s;
  this.hireDay=LocalDate.of(year, month, day);
 }
 public double getSalary(){
  return salary;
 }
 public LocalDate getHireDay() {
  return hireDay;
 }
 @Override
 public String getDescription() {
  return String.format("an employee with a salary of $%.2f", salary);
 }
}
package abstractClasses;
public class Student extends People {
private String major;
 public Student(String n,String m) {
  super(n);
  this.setMajor(m);
  // TODO 自动生成的构造函数存根
 }
 public String getMajor() {
  return major;
 }
 public void setMajor(String major) {
  this.major = major;
 }
 @Override
 public String getDescription() {
  // TODO 自动生成的方法存根
  return "an student major in"+major;
 }
 
}
  • 对于有些方法或域只允许子类来访问,可以将这些方法或域用protected修饰
    Java常用的访问修饰符:
    private–仅对本类可见
    public–对所有类可见
    protected–对本包和所有子类可见

Object:所有类的超类

//Object类是所有类的超类
public class ObjectTest extends Object{
public static void main(String[] args) {
  //可以使用Object类型的变量引用任何类型的对象
  Object obj=new Employee("Crel",3000,1999,8,9);
  Employee e=(Employee)obj;
  //在Java中,只有基本类型不是对象
  //所有数组类型无论是基本类型的数组或是对象数组都扩展了Object类
  Employee[] staff=new Employee[10];
  obj=staff;
  obj=new int[10];
 }
}

equals方法

Java中equals方法用来检测一个对象是否等于另一个对象,即判断两个对象的引用是否相等,但是对于多数类来说这种操作并没有多大意义,所以还要在此基础上判断两个对象的状态是否相等,如果相等则认为这两个对象是相等的,例如:两个Employee对象的姓名,薪资,入职时间相等,就认为他们是相等的;

public class Employee{
...
public boolean equals(Object otherObject) {
  //判断两个对象是否引用同一个对象,这句话只是一个优化,因为简单计算这个等式比一个个检测域付出代价小
  if(this==otherObject) return true;
  //比较对象不为null
  if(otherObject==null)return false;
  //判断两个对象所属的类是否相等
  if(this.getClass()!=otherObject.getClass())return false;
  //现在知道otherObject所属的类为employee
  Employee other=(Employee)otherObject;
  //判断状态是否相等
  return name.equals(other.name)&&salary==other.salary&&hireDay.equals(other.hireDay);
 ...
}
}

在子类调用equals方法的时候,首先调用超类的equals方法,如果判断失败就不可能相等,例如:

public class Manager extends Employee{
	...
	public boolean equals(Object otherObject){
		if(!super.equals(otherObject)) return false;
		Manager other=(Manager)otherObject;
		return bonus==other.bonus;
	}
}

if(this.getClass()!=otherObject.getClass())return false

if(!otherObject instanceof (Employee))return false 的比较:
如果子类会不断改写equals方法则使用第一种,否则使用第二种同时equals方法最好用final修饰;

另外如果写成
public boolean equals(Employee otherObject){}
则没有覆盖Object方法而是定义了一个完全无关的方法,所以可以用
@override public boolean equals(Object otherObject)
来修饰,这样如果写错程序会出现报错。

hashCode方法

由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列值,其值为对象的存储地址

  String s="Ok";
  StringBuilder sb=new StringBuilder(s);
  System.out.println(s.hashCode()+" "+sb.hashCode());
  String t=new String("Ok");
  StringBuilder tb=new StringBuilder(t);
  System.out.println(t.hashCode()+" "+tb.hashCode());

输出:
2556 366712642
2556 1829164700
其中s和t的散列码的值相等,sb和tb的不相等,这是因为在String类中重新改写了hashCode方法,StringBuilder没有重新定义,,他的散列码由Object类默认的hashCode方法得到,如果重新定义hashCode函数就必须重新定义hashCode函数,以便用户可以将对象插入散列表中;
扩展:null安全的方法,Objects.hashCode,如果值为null的话返回0;
可以使用静态方法Double.hashCode来避免创建Double对象。

toString方法

  • 绝大多数的toString方法都遵循这样的格式:类的名字加上一对方括号括起来的域值,例如:
    java.awt.Point[x=10,y=20];
public String tostring() {
  return getClass().getName()+"[name="+name
    +",salary="
    +salary
    +",hireDay"
    +hireDay+"]";
 }

子类改写toString方法,例如:

class Manager extends Employee{
...
  public String toString(){
    return super.toString()+"[bonus="+bonus+"]";
  }
...
} 
  • 在Java中随处可见toString方法的原因是:只要对象和一个字符串用+号连接,Java编译就会自动调用toString方法,以便得到这个对象的字符串描述;
    System.out.println(x);//直接调用x.toString()方法,并打印输出得到的字符串;
  • System.out.println(System.out);//Object类中定义了toString方法,用来打印输出对象所属的类名和散列码;输出为:java.io.printStream@2f6684
  • 注意:数组继承了Object的toString方法,例如:
    int[] a={2,3,4,5,6};
    String s=""+a;//s="[I@1a46e30";
    要得到字符串[2,3,4,5,6]需要调用Arrays.toString方法,例如:
    String s=Arrays.toString(a);
    要打印多维数组,需要调用Arrays.deepToString方法;
package Object;
public class EqualsTest {
  public static void main(String[] args) {
  Employee a1=new Employee("Alice",75000,1999,1,2);
  Employee a2=a1;
  Employee a3=new Employee("Alice",75000,1999,1,2);
  Employee b=new Employee("Bob",55000,1998,12,2);
  //存储地址是否相等
  System.out.println("a1==a2:"+(a1==a2));
  System.out.println("a1==a3:"+(a1==a3));
  //比较对象是否相等
  System.out.println("a1.equals(a3):"+a1.equals(a3));
  System.out.println("a1.equals(b):"+a1.equals(b));
  System.out.println("b.toString():"+b.toString());
  
  Manager c=new Manager("Carl",80000,1877,9,8);
  Manager boss=new Manager("Carl",80000,1877,9,8);
  boss.setBonus(5000);
  System.out.println("boss.toString():"+boss.toString());
  System.out.println("c.equals(boss):"+c.equals(boss));
  System.out.println("a1.hashCode():"+a1.hashCode());
  System.out.println("a3.hashCode():"+a3.hashCode());
  System.out.println("b.hashCode():"+b.hashCode());
  System.out.println("c.hashCode():"+c.hashCode());
 }
}
package Object;
import java.time.LocalDate;
import java.util.Objects;
class Employee{
 private String name;
 private double salary;
 private LocalDate hireDay;
 public Employee(String n,double s,int year,int month,int day) {
  name=n;
  salary=s;
  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=salary+raise;
 }
 public boolean equals(Object otherObject) {
  if(this==otherObject) return true;
  if(otherObject==null)return false;
  if(this.getClass()!=otherObject.getClass())return false;
  Employee other=(Employee)otherObject;
  return name.equals(other.name)&&salary==other.salary&&hireDay.equals(other.hireDay);
 }
 public int hashCode() {
  return Objects.hash(name,salary,hireDay);
 }
 public String toString() {
  return getClass().getName()+"[name="+name+",salary="+salary+",hireDay="+hireDay+"]";
 }
}
package Object;
public class Manager extends Employee{
 private double bonus;
 public Manager(String n,double s,int year,int month,int day) {
  super(n,s,year,month,day);
  bonus=0;
 }
 public double getSalary() {
  double baseSalary=super.getSalary();
  return baseSalary+bonus;
 }
 public void setBonus(double b) {
  bonus=b;
 }
 public boolean equals(Object otherObject) {
  if(!super.equals(otherObject))return false;
  Manager other=(Manager)otherObject;
  return other.bonus==bonus;
 }
 public int hashCode() {
  return super.hashCode()+17*new Double(bonus).hashCode();
 }
 public String toString() {
  return super.toString()+"[bonus="+bonus+"]";
 }
}

输出:

a1==a2:true
a1==a3:false
a1.equals(a3):true
a1.equals(b):false
b.toString():Object.Employee[name=Bob,salary=55000.0,hireDay=1998-12-02]
boss.toString():Object.Manager[name=Carl,salary=80000.0,hireDay=1877-09-08][bonus=5000.0]
c.equals(boss):false
a1.hashCode():172825473
a3.hashCode():172825473
b.hashCode():-528129866
c.hashCode():1436129247

泛型数组列表

  • 为了解决动态更改数组的问题,可以使用Java中一个称为ArrayList的类,它的使用类似于数组,但是在添加或是删除元素的时候具有自动调节数组容量的功能。
  • ArrayList是一个采用类型参数的泛型类,为了指定数组列表保存的元素对象类型,需要将类名用一对尖括号括起来加到后面,这种写法称为菱形语法,例如:
ArrayList<Employee> staff=new ArrayList<Employee>();
 //也可以省略写为
ArrayList<Employee> staff=new ArrayList<>();
  • 使用add方法可以将元素添加到数组列表中,例如:
staff.add(new Employee("Harry",...));
staff.add(new Employee("Tarry",...));

数组列表管理着对象引用的一个内部数组,如果调用add方法且内部数组已经满了,数组列表就将自动创建一个更大的数组,并将所有的对象从较小的数组拷贝到较大的数组中。

  • 如果已经确认数组的长度,可以在填充数组前调用ensureCapacity方法:staff.ensureCapacity(100);
    此外还可以直接把初始容量传递给ArrayList构造器:
    ArrayList staff=new ArrayList<>(100);
    容量为100的数组列表只是拥有保存100个元素的潜力,数组列表一开始甚至在初始化之后都不会含有任何元素;
    staff.size();等价于数组的a.length();
  • 一旦确定数组列表的大小,就可以调用trimToSize方法,这个方法将自动将数组的存储区域的大小调整为当前元素数量所需要的存储空间数目,垃圾处理机制将回收多余的存储空间。
  • 要访问或修改数组元素要使用get和set方法:
staff.set(i,harry);//等价于a[i]=harry;
Employee e=staff.get(i);//等价于Employee e=a[i];
int n=staff.size()/2;
staff.add(n,e);//插入元素
Employee e=staff.remove(n);//删除元素

下列的方法既实现了灵活扩展数组,又可以方便访问元素:

//创建数组,添加元素
ArrayList<X> list=new ArrayList<>();
while(...){
 x=...;
 list.add(x);
}
//toArray方法将数组元素拷贝到一个数组里
X[] a=new X[list.size()];
list.toArray(a);

自动装箱

  • 所有的基本类型都有一个与之对应的类,例如Integer类对应基本类型int,通常这些类称为包装器,对象包装器类是不可变的,一旦构造了包装器,就不允许改变包装在里面的值,同时对象包装器类还是final,因此不能定义它们的子类。
  • 装箱和拆行是编译器认可的而不是虚拟机。
ArrayList<Integer> list=new ArrayList<>();
//由于尖括号里面的类型参数不允许是基本类型,因此需要将值包装在integer对象中
list.add(3);
//将自动变换为list.add(Integer.valueOf(3));这种变换称为自动装箱
int n=list.get(i);
//即int n=list.get(i).intValue();这称为自动拆箱
Integer n=null;//由于包装器类对象可以为null,又可能会抛出异常
System.out.println(2*n);//抛出NullPointerException异常
Integer m=1;
Double x=2.0;
System.out.println(true?m:x);//如果一个式子中Integer和Double混用,Integer值会自动拆箱提升为double,在自动装箱为Double
  • 包装器类对象是不可变的,所以无法无法使用这些包装器类创建修改数值的方法,如果想编写一个修改数值参数值的方法,可以使用持有者类型:
public static void triple(IntHolder x){ 
   x.value=3*x.value;
}

参数数量可变的方法

例如对于printf方法的定义:

public class PrintStream{
  public PrintStream printf(String fmt,Object...args){return format(fmt,args);}
}//这里的...表明这个方法可以接受任何数量的对象

用户可以自己定义可变参数的方法,并将参数指定为任意类型,例如:

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.2,-5)

枚举类

public enum Size{ SMALL,MEDIUM,LARGE,EXTER_LARGE };
实际上,这个定义的是一个类,它刚好有四个实例,因此在比较两个枚举类型的值的时候,永远不需要调用equals,而可以直接使用==;

小结:==:如果是引用数据类型,会判断对象的存储地址是否相等;equals:判断对象的值的字符串形式是否相等;对于枚举类型的对象,使用equals和双等号都可以;

如果需要,可以在枚举类型中添加构造器、方法和域,例如:

import java.util.Scanner;
public class EnumTest {
  public static void main(String[] args) {
  Scanner in=new Scanner(System.in);
  System.out.println("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!!!");
  }
 }
}
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 abbreviation;}
}

------摘于《Java核心技术卷1》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值