5继承
5.1 类、超类和子类
关键字extends表示继承。
Java中的继承都是公用继承,没有C++中的私有继承和保护继承。
Super class Subclass 来自于集合的术语
Base class Derived class
Parent class Child class
将通用的功能放在超类中,将具有特殊用途的方法放在子类中。
子类中可以覆盖(override)超类中的方法。
子类不能直接访问超类的私有域。
子类中调用超类方法:super.method()
C++中用 [超类名::方法]的方式调用超类方法。
因为子类不能访问超类私有域,
子类的构造器中,第一条语句要用super(...)调用超类构造器;
如果未提供super()语句,则调用默认无参数的超类构造器;
如果超类没有默认构造器,则编译出错。
C++中利用初始化列表语法调用超类构造器。
继承层次:
由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy)。
从某个特定的类到其祖先的路径被称为该类的继承链(inheritance chain)。
一个祖先类可以拥有多个子孙继承链。
Java不支持多继承。
多态:
IS-A规则:子类的每个对象也是超类的对象。
即置换法则,程序中出现超类对象的任何地方都可以用子类对象置换。
超类对象变量可以引用任何其子类对象。
不能将超类的引用赋给子类变量。
静态绑定 static binding:
对于private方法、static方法、final方法或者构造器,编译器可明确知道应该调用哪个方法。
动态绑定:
调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。
虚拟机预先为每个类创建一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。真正调用的时候,虚拟机查找这个表。
覆盖一个方法时,子类方法不能低于超类方法的可见性。
阻止继承:
Final类:不允许被扩展的类。
Final方法:不允许被重写的方法。
Final类中的方法自动的称为final方法。
Final域:常量。
Final的主要目的:确保它们不会在子类中改变语义。
String类是final类。
如果一个方法没有被覆盖并且很短,编译器进行内联(inlining)优化处理。因为分支转移会扰乱指令预取策略。
强制类型转换:
用一对圆括号将目标类名括起来,置于需要转换的目标引用之前。
唯一原因:在暂时忽视对象的实际类型之后,使用对象的全部功能。
可以将子类引用赋给超类变量,但当超类引用赋给子类变量时,必须进行类型转换。
在类型转换之前,进行instanceof检查。
综上所述:
1、只能在继承层次内进行类型转换;
2、在将超类转换成子类之前,使用instanceof进行检查。
在一般情况下,应该尽量少用类型转换和instanceof运算符。
C++的类型转换:Manager* boss = dynamic_cast<Manager*>(staff[1]);
抽象类:
包含一个或多个抽象方法的类必须声明为抽象类。
抽象类可以包含具体数据和具体方法。
其子类如果实现部分抽象方法,则也需声明为抽象类;
子类若实现全部抽象方法,则不必声明为抽象方法。
类即使不包含抽象方法,也可以将其声明为抽象类。
抽象类不能被实例化。
可以定义一个抽象类的对象变量,但是只能引用非抽象子类的对象。
C++中的抽象类:只要包含纯虚函数的类就是抽象类。
编译器只允许调用在类中声明的方法。
受保护访问:
允许子类的方法访问超类的某个域,或希望超类的某些方法被子类访问。
protected
最好的示例:Object类中的clone方法。
Private——对本类可见;
Public——对所有类可见;
Protected——对本包和所有子类可见;
默认——对本包可见。
5.2 Object:所有类的超类
Object类型的变量可以引用任何类型的对象。
在java中,只有基本类型(primitive types)不是对象。
所有数组类型都是对象,扩展于Object类。
equals方法:
public boolean equals( Object other);
Object中,检测两个对象变量是否有相同的引用(功能与==一致)。
在子类定义equals方法时,首先调用超类的equals方法。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。
Java要求equals语法具有下面的性质:
自反性
对称性
传递性
一致性
X.equals(null)应该返回false
重写符号:方法前一行加@override
重写equals方法步骤:
1、显式参数命名为otherObject;
2、检测this与otherObject是否引用同一个对象;if (this == otherObject) return true;
3、检测otherObject是否为空;if (otherObject == null) return false;
4、检测otherObject是否属于同一类。
a) 如果equals的语义在每个子类中有所改变,用getClass检测:
if (getClass() != otherObject.getClass()) return false;
b) 如果所有的子类都拥有统一语义,就使用instanceof检测:
if (!(otherObject instanceof ClassName)) return false;
5、将otherObject转换为相应的类类型变量:ClassName other = (ClassName) otherObject;
6、对所有需要比较的域进行比较。需在其中包含调用super.equals(other);
数组类型的域,用Arrays.equals(a1, a2)检查是否相等。
hashcode方法:
public int hashcode();
定义在Object类中,每个对象都有一个默认的散列码,其值为对象的存储地址。
Equals方法必须和hashcode方法一致,即如果a.equals(b)为真,则a和b的散列码必须相同。
Objects.hashCode(Object a); //如果为Null,返回0,否则返回a.hashcode();
Objects.hash(Object... objects); //返回所有对象的散列码组合。
数组类型的域,用Arrays.hashCode(a)计算散列码。
toString方法:
Object类定义了toString()方法,打印输出对象所属的类名和散列码。
绝大多数遵循格式:类的名字[域值]
用getClass().getName()获得类的名字。
只要对象与字符串通过 + 相连,就自动调用对象的toString()方法。
System.out.println(x); //自动调用x的toString()方法。
数组继承了object类的toString方法。
如果想打印数组的内容,用Arrays.toString(a);多维数组用Arrays.deepToString(a);
强烈建议为自定义的每一个类增加toString方法。
@SuppressWarnings(“unchecked”)
包装器(wrapper) 自动装箱(autoboxing) 自动拆箱
5.3 参数数量可变方法
最后一个参数为 类名... args,实际传入一个数组。调用时可以传入多个类对象,甚至一个类数组。
5.4 枚举类
所有枚举类型都是Enum类的子类。
比较时,直接用“==”
5.6 反射
反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵java代码的程序。这项功能被大量地应用于JavaBeans中,它是Java组件的体系结构。
能够分析类能力的程序称为反射(reflective)。
在程序运行期间,系统保存了所有已加载类的信息,虚拟机用这些信息选择相应的方法执行。
保存这些信息的类被称为Class。
获得类对象的三种方法:
1、Object类中的getClass()方法将会返回一个Class类型的实例。
2、还可以通过调用静态方法forName获得类名对应的Class对象。
String className = “java.util.Date”;
Class c1 = Class.forName(className);
当包含main()方法的类被加载时,将会递归加载全部所需类。
一种逐步加载方法,调用Class.forName()手工加载其他类。
3、Class cl1 = Data.class;
可以利用==运算符实现两个类对象的比较操作。
类对象的newInstance()方法可以快速创建一个类的实例,调用的是默认构造函数。
String s = “java.util.Date”;
Object m = Class.forName(s).newInstance();
利用反射分析类的能力:
反射机制最重要的内容——检查类的结构。
在java.lang.reflect包中用三个类Field、Method、Constructor分别用于描述类的域、方法和构造函数。
Class类中的getFileds()、getMethods()、getConstructors()将返回public域、方法、构造器数组,包括超类的公有成员。
Class类中的getDeclareFiels()、getDeclaredMehtods()、getDeclaredConstructors()返回全部域、方法、构造器数组,不包括超类的公有成员。
public class ReflectionTest
{public static void main(String[] args){String name;Scanner in = new Scanner(System.in);System.out.print("Enter class name: ");name = in.next();StringBuilder sb = new StringBuilder();try{Class c1 = Class.forName(name);Class superc1 = c1.getSuperclass();String modifiers =Modifier.toString(c1.getModifiers());if ( modifiers.length() > 0 )sb.append(modifiers + " ");sb.append("class" + name);if (superc1 != null && superc1 != Object.class)sb.append(" extends " + superc1.getName());sb.append("\n{\n");Constructor[]constructors = c1.getDeclaredConstructors();for (Constructor c : constructors){String cname = c.getName();sb.append("\t");String cmodifier =Modifier.toString(c.getModifiers());if (cmodifier.length() != 0)sb.append(cmodifier + " ");sb.append(cname+"(");Class[]cparameters = c.getParameterTypes();for ( int j = 0; j < cparameters.length; j++){if (j > 0)sb.append(",");sb.append(cparameters[j].getName());}sb.append(");\n");}sb.append("\n\n");Method[]methods = c1.getDeclaredMethods();for (Method m : methods){sb.append("\t");String mmodifier =Modifier.toString(m.getModifiers());if (mmodifier.length() != 0)sb.append(mmodifier + " ");String rtype = m.getReturnType().getName();sb.append(rtype + " " + m.getName() + "(");Class[]mparameters = m.getParameterTypes();for (int i = 0; i < mparameters.length; i++){if (i > 0)sb.append(",");sb.append(mparameters[i].getName());}sb.append(")\n");}sb.append("\n\n");Field[]fields = c1.getDeclaredFields();for (Field f : fields){sb.append("\t");String fmodifier =Modifier.toString(f.getModifiers());if( fmodifier.length() !=0 )sb.append(fmodifier + " ");Class type = f.getType();String tname = type.getName();String fname = f.getName();sb.append(tname + " " + fname + ";\n");}sb.append("}");System.out.println(sb);}catch(ClassNotFoundException e){e.printStackTrace();}}
}
利用反射机制查看域值:
f.setAcessible(true);
f.get(obj);
利用反射机制修改域值:
f.get(obj, value);
编写一个可供任意类使用的通用toString方法:
1、使用getDeclaredFileds获得所有数据域;
2、使用setAccessible将所有的域设置为可访问的;
3、对每个域,获得名字和值。
setAccessible方法是AccessibleObject类中的一个方法,是Filed、Method、Constructor类的公共超类。
Method对象的invoke方法,允许调用包装在当前Method对象中的方法。
方法签名:
Object invoke(Object obj, Object... args);
第一个参数为具体对象,其余为这个方法的参数。
对于静态方法,第一个参数设置为NULL。
Invoke的参数和返回值必须是Object类型的。
使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。
5.7 继承设计的技巧
1、将公共操作和域放在超类;
2、不要使用受保护的域;
3、使用继承体现IS-A关系;
4、除非所有继承的方法都有意义,否则不要使用继承;
5、在覆盖原方法时,不要改变预期的行为;
6、使用多态;
7、不要过多的使用反射——对编写系统程序及其有用,不适用于应用程序的编写。