核心技术:继承
类、超类和子类
定义子类
关键字 extends 表示正在构造的新类(subclass子类;derived class派生类;child class孩子类)派生于一个已存在的类(superclass超类、base class基类、parent class父类)
覆盖方法
父类中的某些方法对子类并不一定适应,需要子类对方法覆盖(override);
- 子类不能之间访问父类的私有域,必须使用父类私有域对应的公有访问接口;
- 子类可以增加域、增加方法、覆盖方法,但不能删除继承的域和方法;
- 覆盖一个方法的时候,子类方法不能低于超类方法的可见性;
“super”关键字用于只是编译器调用父类方法。
- super与this不同,并非一个对象的引用,不能将super赋给另一个对象变量;
子类构造器
子类构造器中:
- 如果使用super显示调用父类构造器,则该语句必须是子类构造器的第一条语句出现;
- 如果没有显示调用父类构造器,则将自动地调用父类默认(没有参数)的构造器;
- 如果没有显示调用父类构造器,且父类没有无参构造器,则Java编译器将报告错误;
多态
有一个用来判断是否应该设计为继承关系的规则:“is-a”,即“置换法则”,程序中出现超类对象的任何地方都可以用子类对象置换。
- 一个对象可以指示多种实际类型的现象,被称为多态(polymorphism);
- 如,Employee既可以引用一个Employee对象,也可以引用Employee的子类对象(如,Manager)
- 在运行时能自动地选择调用哪个方法的现象,被称为动态绑定(dynamic binding);
- 如果方法是private方法、static方法、final方法,那么编译器可以准确地知道应该调用的方法,这种调用方式被称为静态调用(static bingding);
阻止继承:final类和方法
final使用:
- final域:域不能被修改
- final方法:方法不能被覆盖
- final类:类不能被扩展(即不能被继承)
- 类中的所有方法自动成为final
- 类中的域并不为final
强制类型转换
对象的强制类型转换:
- 只能在继承层次内进行类型转换;
- 在将父类转换为子类之前,应该用instanceof检查
- “instanceof”用于判断对象是否为某个类型
抽象类
- 包含一个或多个抽象方法的类,必须被声明为抽象类;
- 除了抽象方法,抽象类还可以包含具体数据和具体方法;
- 抽象类不能被实例化;
- 可以定义抽象类对象,该对象只能引用非抽象子类的对象;
受保护访问
- public:公有,对其他类可见
- private:私有,对其他类不可见(即使子类也不可见)
- protect:受保护,对所有子类 及同一个包中的其他类可见,包外不可见
- 对本包可见是默认的,不需要修饰符;
Object:所有类的超类
Object 类Java中所有类的始祖,每个类都由它扩展而来
- 只有基本类型不是对象
- 所有的数组类型(无论基本类型数组,对象数组),都扩展了Object类
equals
用于判断一个对象是否等于另一个对象。(需要重写)
- 在Object中,equals将判断两个对象的是否有相同的引用(与“==”一样);
- 为了判断值相等,应该重写equals方法,进行内容判断(先比较hashcode,再比较值);
相等测试与继承
Java语言规范要求equals方法具有以下特性:
- 自反性:对于任何非空引用x,均有x.equals(x)=true;
- 对称性:对于任何引用x、y,当且仅当x.equals(y)=true,则应y.equals(x)=true;
- 传递性:对于任何引用x、y、z,如果x.equals(y)=true且y.equals(z)=true,则有x.equals(z)=true;
- 一致性:如果x、y引用的对象没有变化,反复调用x.equals(y)应返回同样结果;
- 对于任何非空引用x,均有x.equals(null)=false;
hashcode
散列码(hashcode)是由对象导出的一个整型值,是没有规律的:
- 两个不同对象的hashcode基本不会相同;
- 两个对象相同,其hashcode必定相同(所以在equals前先比较hashcode可以提高效率);
- “hashcode()”应该返回一个整型数值(可以负数),并合理地组合实例域的散列码,以便能让各个不同的对象产生的散列码更加均匀;
- 最好使“hashcode()”对null安全:如果参数为null,这个方法返回0,否则返回对参数调用hashcode的结果;
toString
该方法用于返回表示对象值的字符串。(需要重写)
泛型数组列表
ArrayList使用:
ArrayList<E>()
// 构造一个空数组列表
ArrayList<E>(int initialCapacity)
//用指定容量构造一个空数组列表(initialCapacity:初始容量)
boolena add(E obj)
// 在数组列表的尾端添加一个元素
int size()
// 返回存储在数组列表中的当前元素数量
void ensureCapacity(int capacity)
// 确保数组列表在不重新分配存储空间的情况下,就能保存给定数量的元素
void trimToSize()
// 将数组列表的存储容量削减到当前(使用的)尺寸
- 一旦整理了数组列表的大小,或者空间被完全使用,添加新元素时就需要再次移动存储块
访问数组列表元素
void set(int index, E obj)
// 设置元素,将会覆盖原有内容
E get(int index)
//
void add(int index, E obj)
//
E remove(int index)
//
类型化与原始数组列表的兼容性
对象包装器与自动装箱
所有的基本类型都有一个对应的类,即包装器(wrapper):
- “Integer”、“Long”、“Float”、“Double”、“Short”、“Byte”、“Character”、“Void”、“Boolean”:
- 前六个派生于公共超类“Number”;
关于 wrapper:
- 对象包装器类是不可变的,即:一旦构造了包装器,就不允许更改包装在其中的值;
- 同时,对象包装器类还是 final,因此不允许定义它们的子类;
使用:
- 自动装箱:将基本类型赋给一个包装器对象时;
- 自动拆箱:将一个包装器对象赋给基本类型时;
- 拆箱装箱时编译器认可的,而不是虚拟机;
- 自动装箱要求boolean、byte、char<=127、介于-128-127之间的short和int被包装到固定的对象中;
- 如果在一个表达式中,混合使用Integer和Double:Integer会被拆箱为,提升为double,再装箱为Double;
另:
int x = Integer.parseInt(s);
可以将s强制转换为int类型(parse为静态方法)
参数数量可变的方法
在 Java SE 5 之前的版本中,每个方法都有固定数量的参数。
public class PrintStream
{
public PrintStream printf(String fmt, Object... args)
{
return format(fmt, args);
}
}
其中“Object... args”的省略号也是代码的一部分,表示这个方法可以接受任意数量的对象(除 fmt 参数外)。
- 对于printf的实现来说:“Object... args”与“Object[] args”完全一样;
- 对于printf的调用来时:将以前args[]数组的元素,单个依次传递为为方法参数;
- 允许将一个数组传递给可变参数方法的最后一个参数。
枚举类
如[1]:
public enum Size{SMALL, MEDIUM, LARGE, EXTRA_LARGE};
声明了一个枚举类(这是一个类!)
- 所有枚举类都是“Enum”类(java.lang中)的子类;
- 比较两个枚举类型的值时,应该用“==”而非“equals”;
- (其值为 final 修饰,且“equals”方法使用“==”比较)
public final boolean equals(Object other) { return this==other; }
- “toString”方法用于返回枚举常量名;
- “toString”的逆方法为静态方法“valueOf”:用于获取枚举常量;
枚举类的类型
public class TestAgain {
public static void main(String[] args) {
// 枚举类类型
System.out.println(Week.class.getClass().getName());
// 枚举量的类型
System.out.println(Week.Friday.getClass().getName());
}
enum Week { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }
}
输出其类型为:
java.lang.Class
com.eijux.TestAgain$Week
反射
反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便能够动态操纵Java代码的程序。
能够分析类能力的程序成为反射(reflective),可以用作:
- 在运行时分析类的能力;
- 在运行时查看对象;
- 实现通用的数组操作代码;
- 利用“Method”对象,这个对象很像 C++ 中的函数指针;
- 反射主要用在工具框架的构造过程中;
Class类
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称作“运行时”的类型标识。 这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。 然而,可以通过专门的Java类访问这些信息。保存这些新欻的类被称为“Class”。 如同一个Employee对象表示某个的雇员一样,一个Class对象将表示某个类的属性。
获取“Class类”对象
- Java对象的“getClass()”方法:【普通对象的方法】
Employee e = new Employee(); Class c1 = e.getClass(); System.out.println(c1.getName()); // 结果:Employee
- 如果类在包里,则包名也作为类名的一部分,如“java.util.Random”
- “Class”类的静态方法“forName()”:【Class类的静态方法】
String className = "java.util.Random"; Class c2 = Class.forName(className); System.out.println(c2.getName()); // 结果:Random
- 只有在className时类名或接口名时才能执行,否则抛出“checked exception”
- 无论何时使用此方法,都应该提供一个异常处理器(exception handler)
- “Java类名.class”【类字面量】
Class c3 = Employee.class; System.out.println(c3.getName()); // 结果:Employee // 运用.calss的方式获取Class实例(基本类型) Class c4 = Random.class; System.out.println(c4.getName()); // 结果:int // 运用.class的方式获取Class实例(基本数据类型的封装类) Class c5 = Integer.TYPE; System.out.println(c5.getName()); // 结果:int
- 一个“Class”对象实际上表示的时一个类型,而这个类型未必一定是一种类;
- 如:int不是一个类,但int.Class是一个Class类型的对象;
- Class类 实际上是一个泛型类,如:“Employee.class”的类型是“Class<Employee>”
- 虚拟机为每个类型管理一个Class对象,因此可以利用“==”实现两个类对象的比较:
if(e.getClass() == Employee.class)
创建类的实例
“newInstance()”方法用于动态地创建一个类的实例:
e.getClass().newInstance(); // e 是一个Employee对象
- 将 forName 与 newInstance 配合使用,可以根据存储在字符串中的类名创建一个对象:
String s = "java.util.Random"; Object m = Class.forName(s).newInstance();
- newInstance方法调用默认的构造器(无参构造器)来初始化新创建的对象,如果这个类没有默认的构造器,则会抛出异常。
利用反射分析类的能力
在“java.lang.reflect”包中有三个类“Field”、“Method”和“Constructor”分别用于描述类的域、方法和构造器。 这三个类都有一个叫做“getName”的方法,用来返回项目的名称。 Field类有一个“getType”方法,用来返回描述域所属类型的Class对象。 Method 和Constructor 类有能够报告参数类型的方法,Method 类还有一个可以报告返回类型的方法。 这三个类还有一个叫做“getModifiers”的方法,它将返回一个整型数值,用不同的位开关描述public 和static 这样的修饰符使用状况。 另外,还可以利用“java.lang.reflect”包中的“Modifier”类的静态方法分析“getModifiers”返回的整型数值。 例如,可以使用Modifier 类中的“isPublic”、“isPrivate”或“isFinal”判断方法或构造器是否是public、private 或final。 Class 类中的“getFields”、“getMethods”和“getConstructors”方法将分别返回类提供的 public域、方法和构造器数组,其中包括超类的公有成员。 Class 类的“getDeclareFields”、“getDeclareMethods”和“getDeclaredConstructors”方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
java.lang.Class
- Field[] getFields() 1.1
- Filed[] getDeclaredFie1ds() 1.1
- getFields 方法将返回一个包含 Field 对象的数组,这些对象记录了这个类或其超类的公有域。getDeclaredField 方法也将返回包含 Field 对象的数组, 这些对象记录了这个类的全部域。如果类中没有域,或者Class 对象描述的是基本类型或数组类型,这些方法将返回一个长度为0 的数组。
- Method[] getMethods() 1.1
- Method[] getDeclareMethods() 1.1
- 返回包含Method 对象的数组:getMethods 将返回所有的公有方法,包括从超类继承来的公有方法;getDeclaredMethods 返回这个类或接口的全部方法, 但不包括由超类继承了的方法。
- Constructor[] getConstructors() 1.1
- Constructor[] getDeclaredConstructors() 1.1
- 返回包含Constructor 对象的数组, 其中包含了Class 对象所描述的类的所有公有构造器(getConstructors ) 或所有构造器(getDeclaredConstructors)。
java.lang.reflect.Field
java.lang.reflect.Method
java.lang.reflect.Constructor
- Class getDeclaringClass()
- 返冋一个用于描述类中定义的构造器、方法或域的Class 对象。
- Class[] getExceptionTypes() ( 在Constructor 和Method 类中)
- 返回一个用于描述方法抛出的异常类型的Class 对象数组。
- int getModifiers()
返回一个用于描述构造器、方法或域的修饰符的整型数值。使用Modifier 类中的这个方法可以分析这个返回值。
- String getName()
- 返冋一个用于描述构造器、方法或域名的字符串。
- Class[] getParameterTypes() ( 在Constructor 和Method 类中)
- 返回一个用于描述参数类型的Class 对象数组。
- Class getReturnType() ( 在Method 类中)
- 返回一个用于描述返H类型的Class 对象。
java.lang.reflect.Modifier
- static String toString( int modifiers )
- 返回对应modifiers中位设置的修饰符的字符串表示。
- static boolean isAbstract ( int modifiers )
- static boolean isFinal ( int modifiers )
- static boolean islnterface( int modifiers )
- static boolean isNative( int modifiers )
- static boolean isPrivate( int modifiers )
- static boolean isProtected( int modifiers )
- static boolean isPublic( int modifiers )
- static boolean isStatic( int modifiers )
- static boolean isStrict( int modifiers )
- static boolean isSynchronized( int modifiers )
- static boolean isVolati 1e( int modifiers )
这些方法将检测方法名中对应的修饰符在modffiers 值中的位。
在运行时使用反射分析对象
java.lang.reflect.AccessibleObject
- void setAccessible(boolean flag)
- 为反射对象设置可访问标志。flag 为true 表明屏蔽Java 语言的访问检查,使得对象的私有属性也可以被査询和设置。
- boolean isAccessible( )
- 返回反射对象的可访问标志的值。
- static void setAccessible(AccessibleObject[ ] array,boolean flag)
- 是一种设置对象数组可访问标志的快捷方法。
java.lang.Class
- Field getField( String name )
- Field[ ] getFieldO
- 返回指定名称的公有域, 或包含所有域的数组
- Field getDeclaredField( String name )
- Field[ ] getDeclaredFields( )
- 返回类中声明的给定名称的域, 或者包含声明的全部域的数组。
java.lang.reflect.Field
- Object get(Object obj )
- 返回obj 对象中用Field 对象表示的域值。
- void set(Object obj ,Object newValue)
- 用一个新值设置Obj 对象中Field 对象表示的域。
使用反射编写泛型数组代码
java.lang.reflect 包中的Array 类允许动态地创建数组。
【????】
java.lang.reflect.Array
- static Object get(Object array,int index)
- static xxx get/xx(Object array,int index)
- ( xxx 是boolean、byte、char、double、float、int、long、short 之中的一种基本类M。)
- 这些方法将返回存储在给定位置上的给定数组的内容。
- static void set(Object array,int index,Object newValue)
- static setXxx(Object array,int index,xxx newValue)
- ( xxx 是boolean、byte、char、double、float、int、long、short 之中的一种基本类型。)
- 这些方法将一个新值存储到给定位置上的给定数组中。
- static int getLength(Object array)
- 返回数组的长度。
- static Object newInstance(Class componentType,int length)
- static Object newInstance(Class componentType,int[]lengths)
- 返回一个具有给定类型、给定维数的新数组。
调用任意方法
在C 和C++ 中,可以从函数指针执行任意函数。从表面上看,Java 没有提供方法指针,即:将一个方法的存储地址传给另外一个方法,以便第二个方法能够随后调用它。 事实上,Java 的设计者曾说过:方法指针是很危险的,并且常常会带来隐患。 他们认为 Java 提供的接口(interface ) 是一种更好的解决方案。 然而, 反射机制允许你调用任意方法。
调用方法
在Method 类中有一个invoke方法,它允许调用包装在当前Method 对象中的方法。invoke 方法的签名是:
Object invoke(Object obj, Object... args)
- 对于静态方法,第一个参数可以被忽略, 即可以将它设置为null。
// 假设用ml 代表Employee 类的getName 方法 String n = (String) ml.invoke(harry);
- 如果返回类型是基本类型, invoke 方法会返回其包装器类型。
double s = (Double) m2,invoke(harry);
获取方法
- 可以通过调用“getDeclareMethods”方法, 然后对返回的“Method”对象数组进行查找, 直到发现想要的方法为止。
- 也可以通过调用Class 类中的“getMethod”方法得到想要的方法:
Method getMethod(String name, Class... parameterTypes)
- 提供想要的方法名称,及参数类型
如:
Method ml = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class);
java.Iang.reflect.Method
- public Object invoke(Object implicitParameter, Object[] explicitParamenters)
- 调用这个对象所描述的方法,传递给定参数,并返回方法的返回值。对于静态方法,把 null 作为隐式参数传递。在使用包装器传递基本类型的值时,基本类型的返回值必须是未包装的。
继承的设计技巧
- 将公共操作和域放在超类
- 不要使用受保护的域
- 子类集合是无限制的,任何一个人都能够由某个类派生一个子类,并编写代码以直接访问protected 的实例域, 从而破坏了封装性;
- 同一个包中的所有类都可以访问proteced 域,而不管它是否为这个类的子类;
- 使用继承实现“is-a” 关系
- 除非所有继承的方法都有意义,否则不要使用继承
- 在覆盖方法时,不要改变预期的行为
- 置换原则应用于行为
- 使用多态,而非类型信息
- 使用多态性提供的动态分派机制执行相应的动作;
- 使用多态方法或接口编写的代码,比使用对多种类型进行检测的代码更加易于维护和扩展;
- 不要过多地使用反射
- 对于编写系统程序极其实用,但是通常不适于编写应用程序