2020 年 6 月 5 日 继承
利用继承,人们可以基于已存在的类构造一个新类。继承已存在的类就是复用这些类的方法和域。
反射是指在程序运行期间发现更多的类及其属性的能力。
1、类、超类和子类
1、定义子类
关键字extends表示继承:
public class Manager extends Employee{
}。
关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类、基类或者父类。新类称为子类、派生类或孩子类。
在通过扩展超类定义子类的时候,仅需要指出子类与超类的不同之处。因此在设计类的时候,应该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。
2、覆盖方法
超类中有些方法对子类并不适用,需要提供一个新的方法来覆盖超类的方法。
如果需要在子类的方法中调用超类的方法,可以通过super关键字来进行调用。
注意:super和this并不是 同一个概念,super不是一个对象的引用,不能讲super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。
3、子类构造器
在子类的构造器中可以直接调用父类构造器通过super关键字来进行调用,使用super调用构造器的语句必须是子类构造器的第一条语句。
如果子类没有显示地调用父类构造器,将自动的调用超类默认的构造器(无参构造器)。
如果超类中没有不带参数的构造器,并且在子类的构造器中没有显示的调用超类的其他构造器,则Java编译器将报告错误。
一个对象变量可以指示多种实际类型的现象被称为多态(继承能体现java的多态这一特性)
在运行时能够自动地选择调用哪个方法的现象称为动态绑定。
4、继承层次
继承并不仅限于一个层次,由一个公共超类派生出来的所有类的集合被称为继承层次。
在继承层次中,从某个特定的类型到祖先的路径被称为该类的继承链。
5、多态
“is-a”规则:它表明子类的每个对象也是超类的对象。
“is-a”规则的另一种表述法是置换法则。它表明在程序中出现超类对象的任何对象都可以用子类对象置换(子类拥有超类的属性和方法)。然而,不能将一个超类的引用赋值给一个子类变量。
6、阻止继承:final类和方法
不允许扩展的类被称为final类。如果定义类的时候使用了final修饰符就表明这个类是final类。
类中特定的方法也可以被声明为final,如果该这样做子类就不能覆盖这个方法(final类中所有的方法都自动的称为final方法)。
将类声明为final主要的目的就是:确保它们不会在子类中改变语义。
7、强制类型转换
类型转换:将一个类型强制转换成另外一个类型的过程被称为类型转换。
将一个子类的的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行强制类型转换,这样才能通过运行时检查。
总结:
1、只能在继承层次内进行类型转换。
2、在将超类转换成子类之前应该使用instance of 进行检查。
8、抽象类
使用abstract关键字修饰的类称为抽象类,一个抽象类可以包含一个或多个抽象方法。反过来,包含一个或多个抽象方法的类必须是抽象的。
抽象类可以包含具体数据和具体方法。
抽象方法充当着占位的角色,它们具体实现在子类中。
扩展抽象类可以有两种选择:
一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类。
另一种是定义全部抽象方法,这样一来,子类就不是抽象的了。
类即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化。也就是说,如果将一个类声明为abstract,就不能创建这个类的对象。
在Java程序中,抽象方法是一个重要的概念。在接口中将会看到更多的抽象方法。
9、受保护访问
对于子类来说也不能访问超类的私有域,有些时候,人们希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域,为此需要将这些方法或域声明为protected。子类得到信任可以使用protected修饰的类和方法,而其他类则不行。
Java中用于控制可见性的4个访问修饰符:
1、仅对本类可见——private
2、对所有类可见——public
3、对本包和所有子类可见——protected
4、对本包可见——默认。不需要修饰符
2、Object:所有的超类
在Java中object类是所有类的始祖,如果没有明确的指出超类,Object类就被认为是这个类的超类。
在java中只有基本类型不是对象,所有的数组类型,不管是对象数组还是基本类型数组都扩展了Object类。
1、equals方法
Object类中的equals方法将用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用则说明是相等的。
在子类中定义equals方法时,首先要调用超类的equals。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。
2、相等测试与继承
在判断两个不同的对象引用是否相等时,可以对显示参数使用instance of进行检测,然后再判断值是否相等。
Java语言要求equals方法具有下面的特性:
1、自反性:对于任何非空引用x,x.equals(x)应该返回true
2、对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true
3、传递性:对于任何引用x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true。
4、一致性:如果x,y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果
5、对于任意非空引用x,x.equals(null)应该返回false;
3、hashCode方法
散列码是由对象导出的一个整型值。散列码是没有规律的。如果x和y是两个不同的对象,x.hashCode()与y.hashCode()基本上不会相同。
由于hashCode()方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址
4、toString方法
toString方法用于返回表示对象值的字符串。
绝大多数的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值。
3、泛型数组列表
在Java中ArrayList在添加元素或删除元素时,具有自动调节数组容量的功能,而不需要为此编写任何代码。ArrayList是一个采用类型参数的泛型类。为了指定数组的元素类型,需要用一对尖括号将类名括起来加在后面。
数组列表管理着对象引用的一个内部数组。一旦能够确定数组列表的大小不再发生变化,就可以调用trimToSize()方法。这个方法将存储区域的大小调整为当前元素数量所需的存储空间数目。
1、访问数组列元素
常见的方法:set,get,add,remove
4、对象包装器与自动封箱
有时需要将基本类型转化为对象,所有的基本类型都有一个与之对应的类,例如Integer类对应基本类型int。这些类称为包装器,这些对象包装器拥有很明显的名字:Integer,Long,Float,Double,Short,Byte,Character,Void和Boolean(前6个类派生于公共的超类Number)
自动封箱:如果将int类型插入到ArrayList<Integer>中,将会自动的进行转换
自动拆箱:将一个Intger对象赋值给一个int值时,将会自动地拆箱
注释事项:
1、由于包装器引用可以为null,所以自动装箱有可能抛出一个NullPointerException异常
2、如果在一个条件表达式中混合使用Integer和Double类型,Integer值就会拆箱,提升为double,再装箱为Double
3、装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。
5、参数数量可变的方法
案例:
这里的省略号 ... 是Java代码的一部分,它表明这个方法可以接收任意数量的对象。
实际上printf方法接收两个参数,一个是格式化字符串,另一个是Object[] 数组,其中保存着所有的参数。换句话说,对于printf的实现者来说,Object... 类型参数 与Object[] 完全一样。
6、枚举类
如何定义枚举类型:
在比较两个枚举类型的值时,永远不要调用equals,而直接调用“==”就可以了。
如果需要的话,也可以在枚举类中添加一些构造器、方法和域。当然构造器只是在构造枚举常量的时候被调用。
所有的枚举常量都是Enum类的子类。
每一个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组。
7、反射
能够分析类能力的程序称为反射。反射机制可以用来:
在运行时分析类的能力
在运行时查看对象
实现通用的数组操作代码
利用Method对象,这个对象很像C++中的函数指针
1、Class类
在运行程序期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。然而可以通过专门的Java类访问这些信息,保存这些信息的类被称为Class。
Object类中的getClass()方法将返回一个Class类型的实例。
如同用一个Employee对象表示一个特定的雇员一样,一个Class对象将表示一个特定的类的属性。最常用的Class方法是getName(),这个方法将返回类的名字。
注意:一个Class对象实际上表示的是一个类型,而这个类型未必是一种类。例如int不是类,但int.class是一个Class类型的对象。
虚拟机为每个类型管理一个Class对象。因此可以直接利用==运算符实现两个对象比较的操作,还有一个很有用的方法就是newInstance(),可以用来动态的创建一个类的实例。newInstance方法调用默认的构造器初始化新建的对象。
2、捕获异常
异常有两种类型:未检查异常和已检查异常
3、利用反射分析类的能力
反射机制最重要的内容——检查类的结构
在java.lang.reflect包中有三个类:Field,Method和Constructor,分别用于描述类的域,方法和构造器。这三个类都有一个getName方法用来返回项目名称。
Field类有一个getType方法,用来返回描述域所属类型的Class对象。
Method和Construction类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。
Class类中的getField、getMethods、和getConstructions方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。
Class类中的getDeclareFileds、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域,方法和构造器。
4、在运行时使用反射分析对象
5、使用反射编写泛型数组代码
将一个Employee[]临时转换成一个Object[]数组,然后再把它转换回来是可以的,但是一个从开始就是Object[]的数组却永远转换不成Employee[]数组。为了编写通用的数组代码,需要能够创建与原数组类型相同的新数组,其中最关键的就是Array类中的静态方法newInstance,它能够构造新数组。在调用它的时候必须提供两个参数,一个是数组的元素类型,一个是数组的长度
例子:
6、调用任意方法
反射机制允许你调用任意方法,在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。
invoke方法的签名是:Object invoke (Object obj,Object ... args)
第一个参数是隐士参数,其余的对象提供了显式参数,对于静态方法,第一个参数可以被忽略,即他可以被设置为null。
如果返回类型是基本类型,invoke方法会返回其包装器类型。需要进行类型转换。
例如:double s = (Double)method1.invoke(harry);
如何得到Method对象呢?
1、可以通过调用getDeclareMethods方法,然后对返回的Method对象数组进行查找,直到发现想要的方法为止。
2、通过调用Class类中的getMethod方法得到想要的方法。
例如:Method m1 = Employee.class.getMethod("getName");
8、继承设计技巧
1、将公共操作和域放在超类
2、不要使用受保护的域
3、使用继承实现“is-a”关系
4、除非所有继承的方法都有意义,否则就不要使用继承
5、在覆盖方法时,不要改变预期的行为。
6、使用多态,而非类型信息
7、不要过多的使用反射机制
版权声明: 本文为 InfoQ 作者【瑞克与莫迪】的原创文章。
原文链接:【http://xie.infoq.cn/article/8fc8146afcfbdd53771edae8d】。文章转载请联系作者。
评论