Java实践(二)---继承

1.类、超类、子类

在Java中,所有的继承都是公有继承,关键字extends表示继承
子类比超类拥有的功能更加丰富

在通过超类扩展子类的时候,仅需要指出子类与超类的不同之处,通用的方法放在超类中,特殊用途的方法放在子类中,有时需要提供一个新的方法来覆盖超类中的方法

在子类的方法不能够直接地访问超类的私有域,需要通过关键字super.超类方法的方式来访问超类中的私有域

super不是一个对象的引用,不能讲super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字

子类可以增加域,增加方法或覆盖超类的方法,然而绝对不能删除继承的任何域和方法

由于子类的构造器不能访问超类的私有域,所以必须利用超类的构造器对这部分私有域进行初始化,通过super实现对超类构造器的调用,使用super调用构造器的语句必须是子类构造器的第一条语句

如果子类的构造器没有显式地调用超类的构造器,则会自动地调用超类默认的构造器(没有参数的)。如果超类没有不带参数的构造器,并且在子类的构造器中没有显式地调用超类的其他构造器,则Java编译器将会报告错误

this关键字有2个用途,引用隐式参数,调用该类的其他构造器
super关键字有2个用途,调用超类方法,调用超类构造器

调用构造器的语句只能作为另一个构造器的第一条语句出现,构造参数既可以传递给本类(this)的其他构造器,也可以传递给超类(super)的构造器

由一个公共超类派生出来的所有类的集合称为继承层次(inheritance hierarchy),在继承层次中,从某个特定的类到其祖先的路径称为继承链(inheritance chain)【Java不支持多继承】

一个对象变量可以指示多种实际类型的现象称为多态(polymorphism)在运行时能够自动地选择调动哪个方法的现象称为动态绑定(dynamic binding)

多态
有一个用来判断是否应该设计为继承关系的简单规则,“is-a”规则,它表明子类的每个对象也是超类的对象,“is-a”规则的另一个表述为置换法则,它表明程序中出现超类对象的任何地方都可以用子类对象置换
在Java程序设计语言中,对象变量是多态的,不能将一个超类的引用赋给子类
在Java中子类数组的引用可以转换为超类数组的引用,而不需要采用强制类型转换

所有数组都要牢记创建它们的元素类型,并负责监督仅将类型兼容的引用存储到数组中

动态绑定
调用对象方法的执行过程如下:

1.编译器查看对象的声明和方法名
2.编译器将查看调用方法时提供的参数类型,这个过程称为重载解析(overloading resolution)【返回类型不是签名的一部分,允许子类将覆盖方法的返回类型定义为原返回类型的子类型】
3.如果是private方法,static方法,final方法或者是构造器,那么解释器将可以准确地知道应该调用哪个方法,这种调用方式成为静态绑定(static banding)与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定
4.当程序运行时,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最适合的那个类的方法【虚拟机预先为每个类创建一个方法表(method table)】,其中列出了所有方法的签名和实际调用的方法

动态绑定有一个非常重要的特性,无需对现存的代码进行修改,就可以对程序进行扩展
在覆盖一个方法的时候,子类方法不能低于超类方法的可见性

阻止继承:final类和方法

不允许扩展的类称为final类,类中的特定方法也可以被声明为final,这样的话,子类就不能覆盖这个方法(final类中的所有方法自动地称为final方法)
对于final域来说,构造对象之后就不允许改变它们的值(如果将一个类声明为final,只有其中的方法自动地成为final,而不包括域)

将一个方法或类声明为final主要目的是:确保它们不会在子类中改变语义

强制类型转换
将一个类型强制转换为另一个类型

double x = 3.405;
int nx = (int) x;

有时需要将某个类的对象引用转换成另一个类的对象引用

Manager boss = (Manager)staff[0];

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

在Java中,每个对象变量都属于一个类型,类型描述了这个变量所引用的以及能够能够引用的对象类型
【将子类的引用赋值给一个超类变量,编译器是允许的,但是将一个超类的引用赋给一个子类变量,必须进行类型转换】

在进行类型转换前,先查看一下是否能够成功地转换,这个过程简单地使用instanceof运算符就可以实现

只能在继承层次内进行类型转换
在将超类转换成子类之前,应该使用instanceof进行检查

if x is null
x instancof c 
return false

因为null没有引用任何对象

一般情况下,尽量少用类型转换和instanceof运算符

抽象类
位于上层的类更具有通用性,甚至可能更加抽象(祖先类更加通用,只将它作为派生类的基类,而不作为想使用的特定的实例类)
【包含一个或多个抽象方法的类本身必须是抽象类】
除了抽象方法之外,抽象类还可以包含具体数据和具体方法
建议尽量将通用的域和方法(不管是否是抽象的)放在超类(不管是否是抽象类)中

扩展抽象类可以有2种选择:

在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类
定义全部的抽象方法,子类就不是抽象的了

(类即使不含抽象方法也可以将类声明为抽象类,抽象类不能被实例化)
可以定义一个抽象类的对象变量,但是它只能引用抽象子类的对象

Person p = new Student("Vince", "Econimic");

p是一个抽象类Person的变量,Person引用了一个非抽象子类Student的实例

编译器只允许调用在类中声明的方法

受保护访问
最好将类中的域标记为private,将方法标记为public;子类也不能访问超类的私有域
如果希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域,就要将这些方法或域声明为protected
【Java中的受保护部分对所有子类及同一个包中的所有其他类都是可见的】
访问修饰符:

1.仅对本类可见——private
2.对所有类可见——public
3.对本包和所有子类可见——protected
4.对本包可见——默认,不需要修饰符

Object:所有类的超类

在Java中,每个类都是Object类扩展而来的,可以使用Object类型的变量引用任何类型的对象(Object类型的对象只能用于作为各种值的通用持有者,要想对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的类型转换)
在Java中,只有基本类型不是对象,所有的数组类型,不管是对象数组还是基本类型的数组都是扩展于Object类

1.equals方法
用于检测一个对象是否等于另一个对象,在Object类中,这个方法将判断2个对象是否具有相同的引用(然而,经常需要检测2个对象状态的相等性,如果2个对象的状态是相等的,就认为这2个对象是相等的)

2.相等测试与继承
如果隐式和显式参数不属于同一个类型,equals方法将如何处理?
Java语言规范要求equals方法具有下面的特性:

自反性
对称性
传递性
一致性
对于任意非空引用x,x.equals(null)应该返回false

如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测
如果由超类决定相等的概念,那么久可以使用instanceof进行检测,这样可以在不同子类的对象之间进行相等的比较

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

1.显式参数命名为otherObject,稍后需要讲它转换成另一个叫做other的变量
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;
如果所有子类都拥有统一的语义,就使用instanceof检测:
if(!(otherObject instanceof ClassName)) return false ;·
5.将otherObject转换为相应的类类型变量:
ClassName other = (ClassName)otherObject;
6.现在开始对所有需要比较的域进行比较,使用==比较基本类型域,使用equals比较对象域,如果所有域都匹配,返回true,否则返回false
return field1 == other.field1 && Objects.equals(field2,other.field2) && . . .;

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

3.hashcode方法
散列码(hash code)是由对象导出的一个整型值

字符串的散列码是由内容导出的,
StringBuffer中没有定义hashcode方法,它的散列码是由Object类的默认hashcode方法导出的对象存储地址

equals与hashcode定义必须一致:如果x.equals(y),那么x.hashcode()就必须与y.hashcode()具有相同的值

4.toString方法
返回表示对象值的字符串
只要对象和一个字符串通过操作符“+”连接起来,Java编译器就会自动地调用toString方法,以便获得这个对象的字符串描述

在自定义的类中应该覆盖这个方法

3.泛型数组列表

ArrayList是一个采用类型参数(type parameter)的泛型类(generic class),为了指定数组列表保存的元素对象类型,需要用一对尖括号将类型名括起来加在后面。

数组列表的容量和数组列表的大小有一个非常重要的区别:

如果为数组分配100个元素的存储空间,数组就有100个空位置可以使用
而容量为100个元素的数组列表只是拥有保存100个元素的潜力(实际上,重新分配空间的话,将会超过100)

size方法返回数组列表中包含的实际元素数目,等价于数组a的a.length
一旦能够确定数组列表的大小不在发生变化,就可以调用trimToSize方法,这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目,垃圾回收器将回收多余的存储空间。一旦整理了数组列表的大小,添加新元素需要花时间再次移动存储快

a == b;

在Java中,这条赋值语句的操作结果是让a和b引用同一个数组列表

1.访问数组列表元素
使用get和set方法实现访问和改变数组元素的操作,set只能替换数组中已存在的元素,使用add方法为数组添加新元素

2.类型化与原始数组列表的兼容性

书本p186-187

4.对象包装器与自动装箱

所有的基本类型都有一个与之对应的类,这些类称为包装器,这些对象包装器类有鲜明的名字:(Integer、Long、Float、Double、Short、Byte)、Character、Void和Boolean,前面括号中的6个派生于公共的超类Number,对象包装器类是不可改变的(即一旦构造了对象包装器,就不允许更改包装在其中的值),对象包装器类都是final,不能定义它们的子类,

假设要定义一个整型数组列表,而尖括号中的类型参数不允许是基本类型,因此定义如下:ArrayList<Integer> list = new ArrayList<>();

list.add(3);

将会自动地变为

list.add(Integer.valueOf(3));

这种变换称为自动装箱(autoboxing)

int n = list.get(i);

翻译为

int n = list.get(i).intValue();

这个过程是自动拆箱

在算术表达式中也能够进行自动的装箱和拆箱

==运算符也可以应用于对象包装器对象,只不过检测的是对象是否指向同一个存储区域
一般采用equals方法对2个包装器对象进行比较

自动装箱规范要求boolean、byte、char<127,介于-128~127之间的short和int被包装到固定的对象中
Integer对象是不可变的,包含在包装器中的内容不会改变,不能使用这些包装器类创建修改数值参数的方法
装箱和拆箱是编译器认可的,而不是虚拟机,编译器在生成类的字节码时,插入必要的方法调用,虚拟机只是执行这些字节码

5.参数变量可变的方法

public class PrintStream
{
    public PrintStream printf(String fmt, Object ... args)
    {
        return format(fmt,args);
    }
}

这里省略号…是Java代码的一部分,它表明这个方法可以接受任意数量的对象(出fmt参数之外)
实际上,printf方法接收2个参数,一个是格式字符串,另一个是Object[ ]数组,其中保存着所有的参数(如果调用这提供的是整型数组或者其他基本类型的数组,自动装箱功能将会把它们转换成对象)

允许将一个数组传递给可变参数方法的最后一个参数,可以将已经存在且最后一个参数是数组的方法重新定义为可变参数的方法,而不会破坏任何已经存在的代码

6.枚举类

public enum Size{SMALL.MEDIUM,LARGR,EXTRA_LARGE};

在比较2个枚举类型的值时,应用不要调用equals,直接使用==就可以
可以在枚举类型中添加一些构造器,方法或者域,构造器只是在构造枚举常量的时候被调用,所有的枚举类型都是Enum的子类

Size.SMALL.toString();

返回字符串“SMALL”
toString的逆方法是静态方法valueOf

Size s = Enum.valueOf(Size.class,"SMALL");

每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组

7.反射

好难需要在看一遍

书p192-212

8.继承设计的技巧

1.将公共操作和域放在超类
2.不要使用受保护的域【子类集合是无限制的,任何一个人都能够由某个类派生一个子类,并编写代码以直接访问protected的实例域,从而破坏了封装性;在Java程序设计语言中,在同一个包中的所有类可以访问protected域,而不管它是否为这个类的子类】
3.使用继承实现“is-a”关系
4.除非所有继承的方法都有意义,否则不要使用继承
5.在覆盖方法时,不要改变预期的行为
6.使用多态,而非类型信息【使用多态方法或接口编写的代码比使用对多种类型进行检测的代码更加易于维护和扩展】
7.不要过多的使用反射【反射机制对于编写系统程序来说极其实用,但是不适用于编写应用程序】

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值