java的继承

一、继承的基本要素

继承已存在的类就是复用(继承)这些类的方法和域,在此基础上还可以添加一些新的方法和域,以满足新的需求。
java不支持多继承

让我们设定一个场景,假设我们在一个公司里,公司有很多的员工Employee为公司工作。这个公司中经理和雇员的待遇有一些偏差,不过他们之间也有许多相同的东西。例如他们都领薪水。只是普通的雇员在完成本职工作之后仅领薪水salary,而经理还有额外的奖金bonus。
这种情形就需要用到继承。为经理定义一个新类Manager以便增加一些功能。但同时可以重用Employee类中已经编写的部分代码,并将其中所有域都保留下来。
明显,经理 is a 员工,is a是继承的一个明显的特征。

在通过扩展超类定义子类的时候,仅需要指出子类和超类的不同之处。因此在设计类的时候应该将通用的方法放到超类中,而将具有特殊用途的方法放在子类中。

覆盖

  • 超类Employee中有些方法对子类Manager并不适用。比如Manager的getSalary方法应该返回薪水和奖金的总和,这时可以使用覆盖的方式在子类中重新编写getSalary方法。
  • 使用super关键字调用超类的方法
  • 使用super关键字调用超类的构造器,见下例
public class Manager extends Employee{
	......
	public Manager(String name,double salary,int year,int month,int day){
		//调用超类Employee中含有name、salary、year、month和day参数的构造器
		super(name,salary,year,month,day);
		bonus = 0;
	}
	public double getSalary(){
		return super.getSalary()+bonus;
	}
	......
}

二、多态和动态绑定

一个对象变量可以指示多种实际类型的现象被称为多态。
在运行的时候能够自动选择调用哪个方法的现象称为动态绑定。

置换法则

  • 任何出现超类对象的地方都可以使用子类对象替换。
  • 在java中对象变量是多态的,一个Employee对象既可以引用一个Employee类对象也可以引用一个Employee类的任何一个子类的对象(或子类的子类)
  • 不能将超类的引用赋给子类变量。原因很明显,不是所有的员工都是经理。

方法调用的过程(调用x.f(args),隐式参数x为C类的对象)

  • 编译器一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法(超类的私有方法不可访问)
  • 重载解析。编译器查看调用方法时提供的参数类型。如果找到匹配的,就选择这个方法。至此获得方法名字和参数类型。
  • 静态绑定。如果是private、static或final方法或者构造器,那么编译器将可以准确的知道该调用哪个方法。与此对应的是,调用的方法依赖于隐式参数的实际类型,在运行时实现动态绑定。在这个例子中,编译器采取动态绑定的方法生成一条调用f的指令。
  • 当程序运行,采用动态绑定调用方法的时候,虚拟机一定调用与x所引用对象最适合的那个类的方法。假设x的实际类型是D,它是C类的子类,如果D定义了f就直接调用它;否则就将在D类的超类中寻找f方法,以此类推。

方法表

  • 每次调用方法都要搜索,开销巨大,虚拟机预先为类创建了方法表。
  • 方法表中列出来了类中的方法的签名和实际调用的方法。
Manager类的方法表
	getName() -> Employee.getName()
	getSalary() -> Manager.getSalary()
	raiseSalary(double) -> Employee.raiseSalary(double)
	......
  • 例:调用e.getSalary()的解析过程为
    • 虚拟机提取e的实际类型的方法表,可能是Employee、Manager或其他Employee的子类的方法表。
    • 虚拟机搜索定义getSalary()签名的类。此时虚拟机已经知道应该调用哪个方法。
    • 虚拟机调用方法。

强制类型转换

  • 将一个类型强制转换为另一个类型。java中将一个子类的引用赋给一个超类的变量,编译器是允许的,但是将一个超类的引用赋给一个子类的变量,必须进行类型转换,否则会报ClassCastException的错误,如果没有捕获这个异常,那么程序就会终止。
  • 使用instanceof可以检测是否为某个类的实例
    • 检查对象必须是引用类型,不能是基本类型
    • 检查对象为null会返回false
  • 强制类型转换只能在继承层次内进行类型转换
  • 在将超类转换成子类之前应该使用instanceof进行检查

三、抽象类

在这里插入图片描述
如果自下而上在类的继承层次中上移,位于上层的类更具有通用性,甚至可能更加抽象。为什么要花精力进行这样高层次的抽象呢?

  • 如果我们为Employee和Student增加一个getDescription方法返回一个人的简短描述,那么我们可以很简单的实现这个方法。但是问题来了,在Person类中,我们该提供什么内容呢?Person类除了私有数据域以外一无所知,当然可以让Person.getDescription()返回一个空字符串“”。
  • 然而还有一个更好的办法,就是使用abstract关键字。
//为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的!
public abstract class Person{
	private String name;
	
	public Person(String name){
		this.name = name;
	}
	public abstract String getDescription();
	public String getName(){
		return name;
	}
}
  • 注意抽象类无法被实例化!
  • 因为不能构造抽象类Person的对象,所以变量p永远都不会引用Person对象,而是引用诸如Employee或Student这样的具体子类对象,而这些对象中都定义了getDescription方法。

可见性

  • private:仅对本类可见
  • public:对所有类可见
  • protected:对本包和所有子类可见
  • 默认对本包可见不需要修饰符

四、超类Object

在java中只有基本类型不是对象。

equals方法

  • Object类中的equals方法用于检测一个对象是否等于另外一个对象,判断两个对象是否具有相同的引用。
  • java语言规范要求equals方法满足自反性,对称性,传递性,一致性。
public class Employee{
	public boolean equals(Object otherObject){
		//检测是否引用同一个对象
		if(this = otherObject)return true;
		
		//检测otherObject是否为null
		if(otherObject == null)return false;
		
		//比较this和otherObject是否属于同一个类
		//		1.如果equals的语义在每个子类中有所改变就使用getClass检测
		//		2.如果所有的类都拥有统一的语义就使用instanceof检测
		if(getClass() != otherObject.getClass())return false;
		if(!(otherObject instanceof Employee))return false;
		
		//将otherObject转换为响应的类类型变量
		Employee other = (Employee)otherObject;
		return name.equals(other.name)&&
					salary == other.salary&&
					hireDay.equals(other.hireDay);
	}
}

hashcode方法

  • 散列码是由对象导出的一个整数值,每个对象都有一个默认的散列码。
  • 如果重新定义equals方法就必须重新定义hashcode方法!以便用户可以将对象插入到散列表中。
  • 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
  • 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
  • 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。
public class Employee{
	......
	public int hashCode(){
		return name*hashCode()+new Double(salary).hashCode()+hireDay.hashCode();
		
		//使用null安全的Objects.hashCode会更好,同时使用静态方法Double.hashCode避免创建对象
		return Objects.hashCode(name)+Double.hashCode(salary)+Objects.hashCode(hireDay);

		//需要结合多个散列值的时候,用Objects.hash会对各个参数使用Objects.hashCode,并结合各个散列值
		return Objects.hash(name,salary,hireDay);
	}
}

toString方法

  • 用于返回表示对象的字符串

五、对象包装器与自动装箱

有时需要将int这样的基本类型转换为对象。java中所有的基本类型都有一个与之对应的类。

java基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

在这里插入图片描述
自动装箱和自动拆箱

  • 例如list.add(3),将自动变成list.add(Integer.valueod(3)),这种变换称为自动装箱
  • 相反地当将一个Integer对象赋给一个int值得实时候,将会自动拆箱。例如编译器自动将int n = list.get(0)变成int n = list.get(i).getValue()

参数数量可变的方法

  • java现在的版本提供了可以用可变的参数数量调用的方法
public PrintStream printf(String fmt,Object... args){
	return format(fmt,args);
}
  • 这里的省略号是java代码的一部分,表明这个方法可以接收任意数量的对象。

六、枚举类

public enum Size{SMALL,MEDIUM,LARGE,EXTRA_LARGE}

实际上这个声明定义的类型是一个类,它刚好有4个实例。如果需要的话可以在枚举类型中添加一些构造器、方法和域。

public enum Size{
	SMALL("S"),MEDIUM("M"),LARGE("L"),EXTRA_LARGE("EL");
	
	private String abbreviation;
	
	private Size(String abbreviation){this.abbreviation = abbreviation;}
	public String getAbbreviation(){retrun abbreviation;}
}

七、反射

反射库提供了一个非常丰富且精心设计的工具集,以便编写动态操作java代码的程序
能够分析类能力的程序称为反射

反射机制的功能

  • 在运行时分析类的能力
  • 在运行时查看对象,例如编写一个toString方法供所有类使用
  • 实现通用的数组操作代码
  • 利用Method对象(类似函数指针)

Class类

  • 在程序运行期间,java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息追踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。然而可以通过专门的java类访问这些信息。保存这些信息的类被称为Class。
  • 注意,一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类(或void关键字)。例如int不是个类,但是int.class是一个Class类型的对象。
Employee e;
......
Class class = e.getClass();//返回一个Class类型的实例

e.getClass().getName();	//	获取类名(如果类在包里则包名也作为类名的一部分)

Class class1 = Class.forName(className);//调用静态forName方法获得类名对应的Class对象
Object obj = Class.forName("java.util.Random")

Class class2 = Random.class;//T.class代表匹配的类对象
Class class3 = Double[].class;

反射机制内容复杂,会另开一篇叙述内容。

八、继承的设计技巧

给出一些设计的建议

  • 将公共操作和域放在超类
  • 不要使用protected
  • 使用继承实现“is a”关系,而并非所有关系
  • 除非所有继承的方法都有意义,否则不要使用继承
  • 在覆盖方法的时候不要改变预期的行为,不要改变原有行为的内涵,不要偏离最初的想法
  • 使用多态而并非类型信息。使用多态方法或接口编写的代码比使用对多种类型进行检测的代码更加易于维护和扩展。
if(x is of type 1)
	action1(x);
else if(x is of type 2)
	action2(x);
//action1和action2表示的是相同的概念吗?
//如果是相同的概念,就应该为这个概念定义一个方法,并将其放置在两个类的超类或接口中。
//然后就可以调用x.action(),以便使用多态性提供的动态分配机制执行响应的动作。
  • 不要过多的使用反射!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值