继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域,以满足新的需求。
一、 类、超类和子类
Super关键字
子类构造器可以通过super实现对超类构造器的调用。使用super调用构造器对这部分的语句必须是子类构造器的第一条语句。
This关键字用途:一是引用隐式参数,二是调用该类其他的构造器。Super关键字的用途:一是调用超类的方法,二是调用超类的构造器。
方法调用的过程:
1)编译器查看对象的声明类型和方法名。此时,编译器已获得所有可能被调用的候选方法。
2)编译器将查看调用方法时提供的参数类型。此时,编译器已获得需要调用的方法名字和参数类型。
3)如果是private方法、static方法、final方法,那么编译器将可以准确的知道应该调用哪个方法,我们将这种调用方式称为静态绑定。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。
4)当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。
final类和方法
不允许扩展的类被称为final类。类中的特点方法也可以被声明为final(final类中的所有方法自动地称为final方法)。
将方法或类声明为final主要目的是:确保它们不会在子类中改变语义。
变量
变量实质是一小块内存单元,这一小块内存里存储着变量的值,当变量指向一个对象时,这个变量就被称为变量引用。
A a = new A(); |
a为变量引用,引用了一个A对象。变量a 的值为它所引用对象的地址。
强制类型转化
将一个子类的引用赋给一个超类变量,编译器是允许的,但将一个超类的引用赋给一个子类变量,必须进行类型转换。在将超类转换为子类之前,应该使用instanceOf进行检查。
抽象类
如果自上而下在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看,祖先类更加通用,人们只将它作为派生其他类的基类。
现在有Employee和Student两个类,继承于Person类。Person类中有一个getName方法。
现在我们增加一个getDescription方法,它可以返回对一个人的简短描述。
但是在Person类中,我们只能得到姓名,但不能得到具体的描述。
这时候我们就需要使用abstract关键字。
abstractclass Person{ publicabstract String getDescription(); //不用实现具体方法 } |
注意:包含一个或多个抽象方法的类本身必须被声明为抽象的。除了抽象方法外,抽象类还可以包含具体数据和具体方法。
如,Person类中还保存姓名和一个返回姓名的方法。
abstractclass Person{ private String name; publicvoid setName(String name) { this.name = name; } public String getName() { returnname; } publicabstract String getDescription(); //不用实现具体方法,具体实现在子类中 } |
类即使不含抽象方法,也可以将类声明为抽象类。
注意:抽象类不能被实例化。也就是说如果将一个类声明为abstract,就不能创建这个类的对象。
class Student extends Person{ private String major; public Student(String name,String major) { super(name); this.major = major; }
@Override public String getDescription() { return"a student major in " + major; } } |
在Student类中定义了getDescription方法。因此,在Student类中的全部方法都是非抽象的,这个类不再是抽象类。
Protected
我们都知道,最好将类中的域(field)标记为private,而方法标记为public。
有时候,人们希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域。
为此,需要将这些方法或域声明为protected。子类可以访问超类中声明为protected的域但不能访问其他声明为private的域。这种限制有助于避免滥用受保护机制,使得子类只能获得访问受保护域的权利。
如果需要限制某个方法的使用,就可以将它声明为protected。这表明子类得到信任,可以正确地使用这个方法,而其他类不行。
总结java用于控制可见性的4个访问修饰符:
1. private:仅对本类可见。
2. public:对所有类可见。
3. protected:对本包和所有子类可见。
4. 默认(缺省、没有修饰符):对本包可见。
二、 Object:所有类的超类
Object类是java中所有类的超类,在java中每个类都是由它扩展来的。
在java中,只有基本类型不是对象,例如,数值、字符和布尔类型的值都不是对象。
所有的数组类型,不管是对象数组还是基本类型数组都扩展了Object类。
equals方法
Object类中的equals方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用(地址)。
getClass方法将返回一个对象所属的类。
注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
hashCode方法
散列码(hashCode)是由对象导出的一个整形值。hashCode没有规律。其值为对象的存储地址。
toString方法
返回对象值的字符串。
getClass()。getName()获得类名的字符串。
Object类定义了toString方法,用来打印输出对象所属的类名+hashCode。
tips:打印数组时:
int[] arr = {1,2,3,4};
String s = Arrays.toString(arr);
打印二维数组:
Arrays.deepToString(arr);
三、 泛型数组列表
ArrayList<String>list = new ArrayList<>();
Tips:
ArrayList类与Vector类区别:
1. Vector属于线程安全级别,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销
2. ArrayList在内容不够时默认是扩展50%+1个,Vector是默认扩展1倍
3. Vector提供indexOf(obj,start)接口,ArrayList没有。
四、 对象包装器与自动装箱
所有基本类型都有一个与之对应的了类。通常,这些类称为包装器。
Integer、Long、Float、Double、Short、Byte、Character、Boolean(前六个类派生与公共的超类Number)。
对象的包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final,因此不能定义它们的子类。
自动拆箱、自动装箱
注意:装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。
五、 参数数量可变的方法
我们来看一下System.out.printf()方法的定义。
public PrintStream printf(String format, Object ... args) { return format(format, args); } |
这里的省略号...是java代码的一部分,它表明这个方法可以接收任意数量的对象(除fmt参数之外)。
实际上,printf方法接收两个参数,一个是格式字符串,另一个是Object[]数组,其中保存着所有的参数。现在扫描fmt字符串,并将第i个格式说明符与args[i]的值匹配起来。
现在我们自己定义一个可变参数的方法。
publicstaticdouble max(double... values){ doublelargest = Double.NEGATIVE_INFINITY; for (doublev : values) { if(v > largest) largest = v; } returnlargest; } |
六、 枚举类
所有的枚举类都是Enum类的子类。
每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组。
publicclass EnumTest { publicstaticvoid main(String[] args) { Size[] size = Size.values(); System.out.println(Arrays.toString(size)); } }
enum Size{ small, medium, large; } |
结果
[small, medium, large] |
七、 反射
能够分析类能力的程序称为反射。
反射是指在程序运行期间发现更多的类及其属性的能力。
Class类
在程序运行期间,java运行时系统始终为所有的对象维护一个被称为运行时的类型标识(运行类型),这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
获得Class对象的三种方法
1.getClass()
String s = "abc"; Classc1 = s.getClass(); |
2.forName
String className = "java.lang.String"; Classc2 = Class.forName(className); |
这个方法只有在className是类名或接口名才能够执行。否则,forName方法将抛出一个checkedException。
3.T.class
Classc3 = String.class; System.out.println(c3.getName()); |
注意:一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但int.class是一个Class类型的对象。
newInstance()方法可以用来动态地创建一个类的实例。
e.getClass().newInstance(); |
创建了一个与e具有相同类类型的实例。newInstance方法调用默认的构造器。
String s = “java.lang.String”; Object m = Class.forName(s).newInstance(); |
Tips:
new关键字与newInstance()方法的区别:
1)类的加载方式不同
在执行Class.forName(“ ”)时,JVM会在classpath中去找对应的类并加载,这时JVM会执行该类的静态代码段。在使用newInstance()方法的时候,必须保证这个类已经加载并已经连接了,而这可以通过Class的静态方法forName()来完成。
2)所调用的构造方法不尽相同
new关键字能调用任何构造方法。
newInstance()只能调用无参构造方法。
3)执行效率不同
new关键字是强类型的,效率相对较高。
newInstance()是弱类型的,效率相对较低。
捕获异常
抛出异常比终止程序要灵活得多,这是因为可以提供一个“捕获”异常的处理器(handler)对异常情况进行处理。
下面是一个实现最简单的处理器
try { String name = ...;//get class name Class c1 = Class.forName(name);//might throw exception do something with c1 } catch (Exception e) { e.printStackTrace(); } |
利用反射分析类的能力
在java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。
这三个类都有一个叫getName的方法,用来返回项目的名称。
Field类有一个getType方法,用来返回描述域所属类型的Class对象。
Method个Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。
这三个类还有一个叫做getModifiers的方法,它将返回一个整形数值,用不同的位开关描述public和static这样的修饰符使用状况。另外,可以使用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整数数值。还可以利用Modifier.toString方法将修饰符打印出来。
Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。
Class类的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域。方法和构造器,其中包括私有和受保护成员,但不包括超类成员。
下面这段代码显示了如何打印一个类的全部信息的方法。
import java.util.*; import java.lang.reflect.*;
publicclass ReflectionTest { publicstaticvoid main(String[] args) { Scanner in = new Scanner(System.in); System.out.println("Enter class name:"); String name = in.next();
try{ Class c1 = Class.forName(name); Class superc1 = c1.getSuperclass(); String modifiers = Modifier.toString(c1.getModifiers()); if(modifiers.length() > 0) System.out.print(modifiers+" "); System.out.print("class "+name);; if(superc1 != null && superc1 != Object.class) System.out.print(" extends "+superc1.getName()); System.out.print("\n{\n"); printConstructors(c1); System.out.println(); printMethods(c1); System.out.println(); printFields(c1); System.out.println("}"); }catch (ClassNotFoundException e) { e.printStackTrace(); } System.exit(0); }
/* * 打印类中的所有构造方法 */ privatestaticvoid printConstructors(Class c1) { Constructor[] constructors = c1.getDeclaredConstructors(); for (Constructor c : constructors) { String name = c.getName(); System.out.print(" "); String modifiers = Modifier.toString(c.getModifiers()); if(modifiers.length() > 0) System.out.print(modifiers+" "); System.out.print(name + "(");
//打印参数类型 Class[] paramTypes = c.getParameterTypes(); for (intj = 0; j < paramTypes.length; j++) { if(j > 0) System.out.print(","); System.out.print(paramTypes[j].getName()); } System.out.println(");"); } }
/* * 打印类中的所有方法 */ privatestaticvoid printMethods(Class c1) { Method[] methods = c1.getDeclaredMethods();
for (Method m : methods) { Class retType = m.getReturnType(); String name = m.getName();
System.out.print(" "); //打印修饰符,返回类型,方法名 String modifiers = Modifier.toString(m.getModifiers()); if(modifiers.length() > 0) System.out.print(modifiers+" "); System.out.print(retType.getName()+" "+name+"("); Class[] paramTypes = m.getParameterTypes(); for (intj = 0; j < paramTypes.length; j++) { if(j > 0) System.out.print(", "); System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } /* * 打印类中的所有字段 */ privatestaticvoid printFields(Class c1) { Field[] fields = c1.getDeclaredFields();
for (Field f : fields) { Class type = f.getType(); String name = f.getName(); System.out.print(" "); String modifiers = Modifier.toString(f.getModifiers()); if(modifiers.length() > 0) System.out.print(modifiers+" "); System.out.println(type.getName()+" "+name+";"); }
} }
|
运行结果:
Enter class name: java.lang.Double public final class java.lang.Double extends java.lang.Number { public java.lang.Double(double); public java.lang.Double(java.lang.String);
public boolean equals(java.lang.Object); public static java.lang.String toString(double); public java.lang.String toString(); public int hashCode(); public static int hashCode(double); public static double min(double, double); public static double max(double, double); public static native long doubleToRawLongBits(double); public static long doubleToLongBits(double); public static native double longBitsToDouble(long); public volatile int compareTo(java.lang.Object); public int compareTo(java.lang.Double); public byte byteValue(); public short shortValue(); public int intValue(); public long longValue(); public float floatValue(); public double doubleValue(); public static java.lang.Double valueOf(java.lang.String); public static java.lang.Double valueOf(double); public static java.lang.String toHexString(double); public static int compare(double, double); public static boolean isNaN(double); public boolean isNaN(); public static boolean isFinite(double); public static boolean isInfinite(double); public boolean isInfinite(); public static double sum(double, double); public static double parseDouble(java.lang.String);
public static final double POSITIVE_INFINITY; public static final double NEGATIVE_INFINITY; public static final double NaN; public static final double MAX_VALUE; public static final double MIN_NORMAL; public static final double MIN_VALUE; public static final int MAX_EXPONENT; public static final int MIN_EXPONENT; public static final int SIZE; public static final int BYTES; public static final java.lang.Class TYPE; private final double value; private static final long serialVersionUID; }
|
八、 继承的设计技巧
1.将公共操作和域放在超类
2.不要使用受保护的域
3.使用继承实现“is-a”关系
4.除非所有继承的方法都有意义,否则不要使用继承
5.在覆盖方法时,不要改变预期的行为
6.不要过多地使用反射
反射机制使得人们可以通过在运行时查看域和方法,让人们编写出更具有通用性的程序。这种功能对于编写系统程序来说极其实用,但是通常不适于编写应用程序。反射是很脆弱的,即编译器很难帮助人们发现程序中的错误,因此只有在运行时才会发现错误并导致异常。