【java学习笔记】8.Java中的继承

继承是面向对象编程(OOP)的三个基本特征之一。继承是指可以先定义一个相对通用的类,然后从这个通用的类出发定义更多功能的类。新定义的类不仅继承了前面通用类的域和方法,而且还给这个新定义的类增加了一些新的实例域和方法。继承的概念就像儿子继承了父亲的特征和技能,而且拥有了比父亲更多的技能。

1.父类和子类

第7章中定义了MasterCard类。在使用过程中,学校一卡通之间的相同点虽然比较多,但是学生卡和老师卡的使用还是有区别的。例如,学生卡和老师卡之间都必须包含姓名域、发卡时间、性别、卡内余额等,但二者的不同之处在于学校可以给教师的一卡通消费打折。

因此可以重新设计MasterCard类,使其包含所有共同的数据域和方法。定义了新的TeacherMasterCard类 和 StudentMasterCard类。便于为每个类增加一些新的功能,也可以再次使用MasterCard类中已经编写好的部分代码,并将MasterCard类中所有的数据域保存下来。

TeacherMasterCard类与MasterCard类之间存在一种“IS”的关系。即教师一卡通是一卡通。继承关系的一般格式如下

class 子类名 extends 父类名{
	方法
	静态变量
	数据域
}

java中定义继承类使用关键字extends,而C++使用冒号。java中所有的继承都是公共的,而C++是私有的、保护继承的。

关键词extends表示正在构造一个新的类,派生于一个已经存在的类,已经存在的类被称为超类(Super Class)或者父类(Parent Class),而新类被称为子类(Sub Class)或派生类(Derived Class)或孩子类(Child Class)

假设教师在食堂吃饭刷卡是有政策优惠的,例如,给教师打9折。那就需要一个常量域,继承这个折扣,并在教师卡内添加相应的消费方法。

package example;

class TeacherMasterCard  extends MasterCard{
	//覆盖consume方法
	public void consume(double money) {
		this.money = this.money - money * discount;  //重新给mioney赋值
	}
	private static final double discount = 0.9; //声明静态域,表示折扣价	
}

子类TeacherMasterCard中方法域money必须被定义为包护类型,如果是私有类型,则子类将不能直接访问

因为consume()方法不是在MasterCard类中定义的,所以MasterCard类对象不能使用consume()方法。而TeacherMasterCard类自动继承了父类MasterCard中所有方法和数据域,因此TeacherMasterCard类的对象可以随意使用他们。

通常定义子类的时候只需要定义出父类与子类的不同方法和域,所以在设计类的时候,应该将通用的方法放在父类中,将特殊用途的方法放在子类中。

2.super()构造方法调用

子类可以调用父类的方法,也可以调用自己的方法。如果是覆盖的方法,那么子类中如何调用父类的方法呢?

在定义子类的构造方法时,可以调用父类的构造方法,子类的构造方法使用一种特殊的方法调用度类的构造方法。**父类的构造方法初始化所有从父类继承下来的数据域。**这样子类构造方法只需要调用父类的构造方法即可。没有必要重新对父类的域初始化。

调用父类构造方法的一般格式如下:

public 子类名(子类构造方法参数){
super(父类构造方法参数);
其他域的赋值语句
}

子类的构造方法通过super()方法调用父类的构造方法,该方法必须放在子类构造方法的第一句。并且,super()方法可以通过参数来区别调用是父类构造方法中的哪一个。
下面是一个TeacherMasterCard类的构造函数

public TeacherMasterCard(){
	super();		//调用父类的无参构造方法
	storey = 0;	//初始化storey
}
public TeacherMasterCard(String name,char sex,double money,int year,int month,int day,int storey){
	{
		super("T"+flag,name,sex,money,year,month,day);	//调用父类的构造方法
		if(storey<0){				//当storey小于0时
			System.out.println("办公室门牌号不能为负数");//输出字符串信息
			System.exit(0);	//退出程序
		}
	}

public TeacherMasterCard(String name,char sex,double money,int storey){
	super("T"+flag,name,sex,money);			//调用父类的构造方法
	if(storey<0){			//当storey小于0时
				System.out.println("办公室门牌号不能为负数");//输出字符串信息
				System.exit(0);  //退出程序
	}
}

public TeacherMasterCard(String name,char sex,double money,double initial,int storey){
		super("T"+flag,name,sex,money);			//调用父类的构造方法
	if(storey<0){			//当storey小于0时
				System.out.println("办公室门牌号不能为负数");//输出字符串信息
				System.exit(0);  //退出程序
	}
	charge(initial);		//设置折扣价
}

子类TeacherMasterCard的每一个构造方法都调用了父类的MasterCard的构造方法,使用super()语句实现了这样的功能

在调用父类的构造的构造方法时,不能使用子类的构造方法名,必须使用super()方法调用。例如,子类的TeacherMasterCard调用父类的MasterCard的构造方法必须使用super()方法实现

在子类的构造方法中没有调用父类的构造方法,那么系统会自动调用无参构造父类的构造方法。

public TeacherMasterCard(String name,char sex,double money,int year,int month,int day,int storey){
	{
		// super();	 //调用父类的构造方法
		if(storey<0){				//当storey小于0时
			System.out.println("办公室门牌号不能为负数");//输出字符串信息
			System.exit(0);	//退出程序
		}
	}

在一个子类中拥有所有的数据域,如果要对这些数据域进行初始化,那么在父类的构造方法总初始化是最方便的,所以,在定义一个子类的构造方法时,总是先调用父类的构造方法,如果没有自己调用,则系统会自动调用父类中没有参数的构造方法。

This构造方法调用与super构造方法调用区别在于,this调用的是同类的构造方法,而super调用的是父类的构造方法。一个构造方法中不能同时包括this调用和super调用

3.封装和继承

父类声明为private的数据域也被继承到子类的实例域,但是具有private属性的实例域表示这个实例域是专有实例域,也就是说,只能在父类中定义方法时访问,不能再其他类的方法中访问,即使子类也是不可以的。
例如,如果程序中父类MasterCard的各个域都被定义为private,则在该类中可以直接访问。

public void printinfo(){
	System.out.println("name="+name+"sex="+sex+"money="+money+"releasedDay="+releasedDay.toString()); //输出字符串信息
}
private String name; 	//声明字符串类型数据域,表示持卡人的姓名
private char sex;		//声明字符类型数据域,表示持卡人性别
private double money;//声明double类型数据域,表示卡内余额
private Date releasedDay;//声明Date类型数据域,表示发卡时间

在子类TeacherMasterCard中不能直接访问父类的私有数据域,只有通过共有访问方法或变更方法,如getName() 、setName()方法才可以访问。在子类TeacherMasterCard的printinfo()方法中使用父类public方法访问父类的私有数据域。

public void printInfor(){
	System.out.println("name="+getName()+"sex="+getSex()+"money="+getMoney()+"releasedDay="+releasedDay.toString()+"storey="+storey); //输出字符串信息
}

定义为private类型的父类数据域不能通过变量名或方法被子类直接访问,但是子类通过名称可以访问父类中两种类型的实例域和方法,即保护访问和包访问。保护访问可以提供子类访问,而包访问只有子类和父类在同一个包里才能被访问。

如果一个方法或实例被protected修饰,那么他可以被本类、继承类、所在包内的任何类通过使用变量名或方法形名的方式访问。
例如,在MasterCard类中的变量是这样定义的:

protected String name;		//声明字符串数据域,表示持卡人的类型
private char sex;					//声明字符类型数据域,表示持卡人的性别
protected double money;	//声明double类型数据域,表示卡内余额
priate Date releasedDay;	//声明Date类型数据域,表示发卡时间

那么子类TeacherMasterCard中printInfo()方法就可以通过如下方式访问父类的私有数据域:

//直接访问对象的成员变量
public void printInfo(){
	System.out.println("name="+name+"sex="+sex+"money="+money+"releasedDay="+releasedDay.toString()); 
}

也可以通过public类型的getter()方法访问父类的私有数据域

public void printInfo(){
System.out.println("name="+getName()+"sex="+getSex()+"money="+getMoney()+"releasedDay="+releasedDay.toString()+"storey="+storey); 
}

如果类中的实例域,方法没有被public、protected、private中任何一个形式,那么这个变量或方法被称为默认访问。又叫做包访问、友好访问。
在内部定义为包访问权限的域、方法可以被任何类通过使用名称来访问。

在这里插入图片描述

4.使用继承

java中除了基本数据类型,所有数据都是以对类对象的形式表示的。并且所有的类都是Object类的子类。换句话说,Object类是所有Java类的祖先。

1.Object类

Object类是JDK类库中最为特殊的一个类,因为它是所有类直接或间接的父类,包括类库中的类和自定义的类。当定义一个类时,如果不指定是Object类的派生类,那么Java会自动将其编程Object类的子类。

Object的子类可以编写参数为Object类型的方法,这个参数可以被其他任何类型的对象代替。Object类在java.lang包中,该包会被系统自动导入,无需程序员导入。

2.equals()方法

如果要创建如下两个字符串,那么表达式A==B返回真还是假呢?

String str1 = new String("hello");		//创建并初始化字符串str1
String str2 = new String("hello");		//创建并初始化字符串str2
System.out.println(str1==str2);		//输出str1的引用和str2的引用是否相同//false

String字符串是java中比较特殊的一个类。String被定义为final类型,并且同样的字符串在常量池中不能重复。java虚拟机有一个存放字符串的字符串池。在创建一个字符串时,系统的常量池会自动查找字符串是否存在。如果找到该字符串,那么会返回字符串池中的字符串地址。如果没有找到,则创建一个新的字符串,并返回地址。

使用关键字new创建新的字符串,java会在堆中分配新的空间。所以即使字符串的内容相同,通过new创建的字符串的引用也不同。所以上诉代码的返回值为false。

如果要比较两个字符串的值是否相等同(而不是他们的引用是否相同)。可以使用equal()方法。

System.out.println(str1.equals(str2));//输出字符串的值是否相同。

Object类中equals()方法的原型为:

public boolean equals(Object obj){
	return (this == obj);//返回对象的引用是否相同
}

Object中定义的equals(Object)方法用于比较两个对象的引用是否相同,TeacherMasterCard类中给出了一个比较好的equals()覆盖方法,如下:

//覆盖Master类的Object方法
public boolean equals(Object otherObject){
	if(otherObject==null)
		return false;
	else if(getClass()!=otherObject.getClass())
		return false;
	else{
		MasterCard otherMasterCard  = (MasterCard)otherObject;
		return(id.equals(otherMasterCard.id)&&name.equals(otherMasterCard.name)
&&sex==otherMasterCard.sex&&money==otherMasterCard.money&&releasedDay.equals(otherMasterCard).releasedDay))
	}
}

instanceof操作符域getClass()方法都可以检测一个对象类型。两者的区别在于,instanceof操作符测试对象是否属于某种类型,而getClass()方法则使用“==”或“!=”测试这两个对象是否是同一个类创建的
instanceOf操作符的语法如下:

object instanceof ClassName

如果对象object是类 ClassName的对象,则返回true。如果A派生于B类,B类派生于C类,且C类派生于D类,那么下面的运算都返回true。

A a = new A();
a instanceof A;
a instanceof B;
a instanceof C;
a instanceof D;

5.关于设计好类的几个建议

1.java类继承只能实现单个类的继承,而不支持多类继承。即一个类只能有一个父类,不允许继承多个子类。但是反过来,一个类可以被多个类继承。
如下图所示的类ClassB继承了一个类ClassA

class ClassA{}
class ClassB extends ClassA{}

2.java可以实现多层继承。即一个类可以继承一个可以存在的类,而新形成的这个类又可以作为其他类的父类。如下面所示,类ClassB继承了ClassA,类ClassC继承了类ClassB,所以类ClassC间接继承了ClassA。

class ClassA{}
class ClassB extends ClassA{}
class classC extends ClassB{}

3.java中子类可以继承父类所拥有的成员变量和成员方法。但不能继承父类的构造方法。在子类的构造方法中,可以调用super(参数列表)完成对父类构造方法的调用,如子类ClassB调用父类ClassA的构造方法:

class Class A{
	public ClassA(){
	}
}
class ClassB extends ClassA{
	public ClassB(){
		super();
	}
}

4.若子类的构造方法没有显式调用父类的某个构造方法,也没有显式通过关键字this调用其他的构造方法。那么在产生类对象时,系统会调用无参数的构造方法。

class Class A{
	public ClassA(){
	}
}
class ClassB extends ClassA{
	public ClassB(){
//		自动调用super();
	}
}

如果父类没有显式定义任何构造方法,则系统会自动提供默认的无参构造方法。这等同于父类中具有无参数的构造方法。

6.实例:一卡通的继承

1.一卡通的继承类

public class TeacherMasterCard {
//声明枚举类型Title
	enum Title{
		TeachingAssistant,Lecturer,AssistantProfessor,Professor,Default
	}
	
	private int storey;//声明整形数据域,表示办公室楼牌号。
	private static int flag = 0;//声明静态整型数据域,用于自动生成卡ID。
	private static double discount = 0.8;//声明浮点型数据域,表示折扣
	private Title title ; //声明Title枚举类型域,表示教师职称。
}

2.构造方法

TeacherMasterCard类设计为MasterCard3的子类。所以要添加extends关键字来继承父类。
由于teacherMasterCard继承了MasterCard的域和方法,因此必须对域进行初始化。对于父类的域不要需要再重新进行初始化,而是调用父类的构造方法完成对父类域的初始化。

//构造方法
	//TeacherMasterCard类的无参构造方法
	public TeacherMasterCard() {
		super();	//调用父类的构造方法
		setStorey(0); //初始化成员变量Storey
		title = Title.Default;//初始化成员变量title
		
	}
	
	//TeacherMasterCard类的构造方法
	public TeacherMasterCard(String name,char sex,double money,int year,int month,int day,int storey,Title title) {
		super(name,sex,money,year,month,day);//调用父类的构造方法
		setStorey(storey);	//设置办公室的门牌号
		setTitle(title);		//设置职称
	}
	
	//TeacherMasterCard类的构造方法
	public TeacherMasterCard(String name,char sex,double money,int storey,Title title) {
		super(name,sex,money);//调用父类的构造方法
		setStorey(storey);	//设置办公室的门牌号
		setTitle(title);		//设置职称
	}
	
	//TeacherMasterCard类的构造方法
	public TeacherMasterCard(String name,char sex,double money,double initial,int storey,Title title) {
		super(name,sex,money);//调用父类的构造方法
		setStorey(storey);	//设置办公室的门牌号
		setTitle(title);		//设置职称
		charge(initial);6
	}	

4个构造方法的第一条语句都是调用super()方法初始化父类的域,然后初始化子类的新域。

3.setter()方法

TeacherMasterCard类提供了域的setter()方法泳衣设置办公室的门牌号、职称、折扣价,由于门牌号是一个正整数,所以当设置门牌号为负数时,程序将报错并退出。

//setter()方法
	//设置办公室门牌号storey
	public void setStorey(int storey) {
		if(storey<0) {
			System.out.println();
			System.exit(0);//退出程序
		}
		this.storey = storey;//赋值给成员变量storey
	}
	
	//设置职称title
	public void setTitle(Title title) {
		this.title = title;			//给成员变量title赋值
	} 
	
	//设置折扣价
	public static void setDiscount(double discount) {
		TeacherMasterCard.discount = discount;//设置discount的值
	}
	//设置卡号
	public static void setFlag(int Flag) {
		TeacherMasterCard.flag = flag;
	}
	

4.getter()方法

同setter方法对应,TeacherMasterCard提供了域的getter()方法获得域的值

//getter()方法
//返回办公室门牌号storey
	public int getStorey() {
		return storey;			//返回成员变量storey;
	}
	
//返回之职称的字符串表示
	public String getTitle() {
		return title.toString();	//返回title的字符串
	}
//返回折扣价
	public static double getDiscount() {
		return discount;			//返回discount的值
	}
//返回卡号
	public static int getFlag() {
		return flag;
	}

5.方法覆盖

父类中定义了consume()、printInfor()方法,所以TeacherMasterCard类重写了这两个方法。

//方法覆盖
	//持卡人用卡消费
	public void consume(double money) {
		super.consume(money*discount); //调用父类的consume()方法
	}
	//输出卡的信息
	public void printInfo() {
		System.out.println("name="+getName()+"|sex="+getSex()+"|money="+getMoney()+"|releasedDay="+getStringofReleasedDay()+"|storey="+storey+"|title="+title.toString());
	}

6.equals()方法

equals()是Object类的一个方法。重写该方法后,可以按照用户的定义的规则进行比较。

//定义equals方法
	public boolean equals(Object otherobject) {
		if(otherobject == null ) //判断otherObject对象是否为空
			return false;
			//判断该对象类型是否与otherObject类型相同
			else if(getClass()!=otherobject.getClass())
				return false;//返回false;
			else {
				//对otherobject对象进行强制类型转换,并创建otherMasterCard对象
				TeacherMasterCard otherMasterCard = (TeacherMasterCard)otherMasterCard;
				//分别判断对象的值是否相等,如果全部相等则返回true,否则返回false
				return
					   getName().equals(otherMasterCard.getName())
					 &&getSex()==otherMasterCard.getSex()
					 &&getReleasedDay().equals(otherMasterCard.getStringofReleasedDay())
					 &&storey==otherMasterCard.storey
					 &&title == otherMasterCard.title;
					 
			}

	}

7.其他需要强调的地方

该类还使用了初始化块。每当创建一个对象时初始化块就执行一次,因此当创建一个对象时,flag的值会自动加1.由于教师的职称只有4种类型,所以选择枚举类型组委保存教师职称的数据类型。
下面是初始化块和枚举类型的声明代码。

//初始化块,flag每次自动加1
	{
		flag++;
	}

8.创建测试类UseTeacherMasterCard

package example;

import example.TeacherMasterCard.Title;

public class UseTeacherMasterCard {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//创建TeacherMasterCard类型的数组
		TeacherMasterCard[] mc = new TeacherMasterCard[2];
		//构建对象,并指向mc[0]、mc[1]
		//通过构造函数初始化mc[0]
		mc[0] = new TeacherMasterCard("李梅","M",100,2016,3,15,101,Title.AssistantProfessor);
		//初始化mc[1]
		mc[1] = new TeacherMasterCard("韩雷雷","F",25,2016,2,18,200,Title.Lecturer);
		//输出第一个人的部分信息
		mc[0].printInfo();
		//输出第一个人的金额
		System.out.println("充值前:"+mc[0].getMoney());//查看卡的数据类型
		//为第一个人充值
		mc[0].charge(20);//充值20
		System.out.println("充值后"+mc[0].getMoney());//查看卡的数据类型
		
		//为第二个人充值并显示信息
		mc[1].printInfo();
		System.out.println("充值前:"+mc[1].getMoney());//查看卡的数据类型
		mc[1].charge(50);//充值20
		System.out.println("充值后"+mc[1].getMoney());//查看卡的数据类型	
	}
}

7.拓展——领导和员工的差异

对于在同一家公司工作的领导和员工而言,两者之间是有很多共同点的,例如,每个月都会发工资。但是领导除了基本工资,还有分红奖金。此时,如果我们使用员工类来编写领导类就会省略很多重复代码,利用继承技术,可以让领导类使用员工中的属性和方法。
可以通过继承类演示领导和员工的差异。

1.Employee.java

package example;

import java.util.Date;
public class Employee {
	public String getName() {		//获取员工姓名
		return name;
	}
	public void setName(String name) {//设置员工姓名
		this.name = name;
	}
	
	public double getSalary() {		//获取员工工资
		return salary;
	}
	public void setSalary(double salary) { //设置员工工资
		this.salary = salary;
	}
	
	public  Date getDate() {
		return date;
	}
	public void setDate(Date date) {
		this.date = date;
	}	
	private String name;		//员工的姓名
	private double salary;		//员工的工资
	private Date date;			//员工的入场时间
}

2.Manager.java

package example;

public class Manager extends Employee {
	private double bounds;//经理的奖金
	
	public double getBounds() {	//获得经理的奖金
		return bounds;
	}
	
	public void setBounds(double bounds) {//设置经理的奖金
		this.bounds = bounds;
	}
}

3.Test.java

package example;

import java.util.GregorianCalendar;

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
			Employee employee = new Employee();
			employee.setName("tom");
			employee.setSalary(5000);
			employee.setDate(new GregorianCalendar(2016,3,1).getTime());

			Manager manager = new Manager();
			manager.setName("mary");
			manager.setSalary(5500);
			manager.setDate(new GregorianCalendar(2012,7,1).getTime());
			manager.setBounds(2000);
			
			manager.setBounds(2000);
			//输出员工和经理的属性值
			System.out.println("员工的名字"+ employee.getName());
			System.out.println("员工的工资"+ employee.getSalary());
			System.out.println("员工的入职时间"+ employee.getDate());
			
			System.out.println("经理的名字"+ manager.getName());
			System.out.println("经理的工资"+ manager.getSalary());
			System.out.println("经理的入职时间"+ manager.getDate());
			System.out.println("经理的奖金"+ manager.getBounds());
	}

}

在这里插入图片描述

在面向对象的程序设计中,继承是其基本特征之一。
在这里插入图片描述
在父类里声明的域,如果没有被声明为private,则可以在子类里直接使用,不需要再次声明。
成员方法同样也可以继承。父类中声明的非private方法,在子类中也可以直接访问而无需再次声明,同样可以做到代码重用的目的。子类除了可以继承父类中的域和方法,还可以增加自己的成员方法。

8.技术解惑

8.1 super语句必须是子类构造方法的第一句吗

答案是肯定的。子类的构造方法的调用必须先完成父类构造方法的调用,这是系统初始化的特性。所以super语句必须放在第一位,否则编译器会报错:“构造函数调用必须是构造函数中的第一条语句”、

2.equals方法的用法和“==”的用法一致吗

两者的用法不同,首先,==是java的一个关系运算符,表示相等。所以,在所有表示相等关系的表达式中都可以使用。表达式是一个布尔表达式。equals()方法时Java提供的一个Object类方法。用于判断两个对象的内容是否相同,所以equals是不能比较基本类型的变量的。

==比较对象时,比较的是两个对象的引用地址。

String a = new String("foo");
String b = new String("foo");

这两个new语句创建了两个对象,然后用a、b两个变量分别指向他们。a、b是两个不同的String对象,他们的首地址是不同的。(即a 、b中存储的值,也就是两个字符串的首地址是不同的)。但是这两个对象中的内容是相同的。所以 a==b 为 false,a.equals(b)为true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值