- 一个对象变量可以指示多种实际类型的现象被称为多态
- 在运行时能够自动地选择调用哪个方法的现象称为动态绑定
- 如果是private方法、static方法、final方法或者构造器,那么编译器就可以准确地知道应该调用哪个方法,我们将这种调用方法称为静态绑定
类之间的关系
依赖—“uses-a”
聚合—“has-a”
继承—“is-a”
“is-a”规则的另一种表书法是置换原则
Manager boss = new Manager(...);
Employee[] staff = new Employee[3];
staff[0] = boss;
staff[0] 与 boss 引用同一个对象,但编译器将 staff[0]看成 Employee 对象
这意味着,可以这样调用
boss.setBonus(5000);//OK
但不能这样调用
staff[0].setBonus(5000);//Error
这是因为 staff[0] 声明的类型是 Employee ,而 setBonus 不是 Employee 类的方法
理解方法调用
父类
Employee e;
getSalary()
子类
Manager
覆盖
getSalary()
在运行时,调用 e.getSalary() 的解析过程为:
- 首先,虚拟机提取 e 的实际类型的方法表。既可能是 Employee、Manager 的方法表,也可能是 Employee 类的其他子类的方法表。
每次调用方法都要进行搜索,时间开销非常大。因此,虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名(方法的名字和参数列表)和实际调用的方法。
- 虚拟机搜索定义 getSalary 签名的类。此时,虚拟机已经知道应该调用哪个方法
- 最后,虚拟机调用方法
阻止继承: final类和方法
不允许扩展的类称为 final 类
如果一个类声明为 final 类,只有其中的方法自动称为 final,而不包括域
Object:所有类的超类
在Java中,只有基本类型不是对象(不能用 new 的方式获取,但八种基本类型都有对应的包装类,包装类是对象),例如,数值、字符和布尔类型的值都不是对象(方法参数共有两种类型:基本数据类型和对象引用)
对象包装器类拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character、Void 和 Boolean----前 6 个类派生于公共的超类 Number
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
- 一个方法可以改变一个对象参数的状态
- 一个方法不能让对象参数引用一个新的对象
hashCode 方法
散列码(hashcode)是由对象导出的一个整型值。散列码是没有规律的,如果 x 和 y 是两个不同的对象,x.hashCode() 和 y.hashCode() 基本上不会相同。
由于 hashCode 方法定义在 Object 类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。
String s = "OK";
StringBuilder sb = new StringBuilder(s);
String t = "OK";
StringBuilder st = new StringBuilder(t);
System.out.println(s.hashCode() + "--" + sb.hashCode());
System.out.println(t.hashCode() + "--" + st.hashCode());
2524--356573597
2524--1735600054
注:字符串 s 与 t 拥有相同的散列码,这是因为字符串的散列码是由内容导出的。而字符串缓冲 sb 与 st 却有着不同的散列码,这是因为在 StringBuilder 类中没有定义 hashCode 方法,它的散列码是由 Object 类的默认 hashCode 方法导出的对象存储地址。
String 类使用下列算法计算散列码:
int hash = 0;
for(int i = 0; i < length(); i++){
hash = 31 * hash + charAt(i);
}
toString 方法
绝大多数(但不是全部)的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值。
[x=1,y=2]
随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符“+”连接起来,Java编译就会自动地调用 toString 方法,以便获得这个对象的字符串描述。例如
Point p = new Point(10,20);
String message = "The current position is " + p;//自动调用 toString 方法
如果 x 是任意一个对象,并调用System.out.println(x);
println 方法就会直接调用 x.toString 方法,用来打印输出对象所属的类名和散列码。
调用System.out.println(System.out)
将输出下列内容
java.io.PrintStream@f6684
这是因为 PrintStream 类的设计者没有覆盖 toString 方法
反射
使用反射编写反射编写泛型数组代码
package Reflect;
import java.lang.reflect.Array;
import java.util.Arrays;
public class CopyOfTest {
public static void main(String[] args) {
int[] a = {1,2,3};
a = (int[]) goodCopyOf(a, 10);
System.out.println(Arrays.toString(a));
int[] c = Arrays.copyOf(a, 3);
System.out.println(c.getClass().getName());//[I
for (int i = 0; i < c.length; i++) {
System.out.print(c[i] + " ");
}
String[] b = {"Tom","Dick","Harry"};
b = ((String[]) goodCopyOf(b, 10));
System.out.println(Arrays.toString(b));
System.out.println(b.getClass().getSuperclass());//class java.lang.Object
System.out.println("The following call will generate an exception");
b = (String[]) badCopyOf(b, 10);//Object[]对象数组不能转换成String[]类型
}
public static Object[] badCopyOf(Object[] a,int newLength){
Object[] newArray = new Object[newLength];
System.arraycopy(a, 0, newArray, 0,Math.min(a.length, newLength));
return newArray;
}
/**
*Object[]数组不能转换成 Employee[]数组
* 需要能够创建与原数组类型相同的新数组
*/
public static Object goodCopyOf(Object a,int newLength){
Class cl = a.getClass();
System.out.println(cl.getName());//[I [Ljava.lang.String
if (!cl.isArray()){
return null;
}
int length = Array.getLength(a);//获取数组长度
Class c = cl.getComponentType();//获得数组类型,如int
Object newArray = Array.newInstance(c, newLength);//静态方法newInstance构造新数组
System.arraycopy(a, 0, newArray,0,Math.min(length, newLength));
return newArray;
}
}
输出如下
[I
[1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
[I
1 2 3
[Ljava.lang.String;
[Tom, Dick, Harry, null, null, null, null, null, null, null]
class java.lang.Object
The following call will generate an exception
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at Reflect.CopyOfTest.main(CopyOfTest.java:23)
String[] 的类名是[Ljava.lang.String ;而 String.class 类名是 java.lang.String
如果知道一个 class 的完整类名,可以通过静态方法 Class.forName() 获取
Class cls = Class.forName("java.lang.String");
注意一下 Class 实例比较和 instanceof 的差别
Integer n = new Integer(123);
boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类
boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class
注:一个从开始就是 Object[] 的数组永远不能转换成 Employee[] 数组
System.out.println("The following call will generate an exception");
b = (String[]) badCopyOf(b, 10);
Object[]对象数组不能转换成String[]类型,这句话会抛出ClassCastException
The following call will generate an exception
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at Reflect.CopyOfTest.main(CopyOfTest.java:23)
需要能够创建与原数组类型相同的新数组,为此使用 Array 类中的静态方法 newInstance 方法,它能够构造新数组
Object newArray = Array.newInstance(componentType,newLength);
调用时必须提供两个参数,一个是数组的元素类型,一个是数组长度
使用 Class 类的 getComponentType 方法确定数组对应的类型
调用 Array.getLength(a) 获得数组长度
调用任意方法
Method 类中有一个 invoke 方法
invoke 方法签名是:
Object invoke(Object obj,Object... args)
第一个参数是隐式参数(方法名前隐匿的对象实例,或称之为方法调用的目标,例如 "this.方法"中的 this),对于静态方法,第一个参数可以忽略,即可以将它设置为null
package Reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodTest {
public static void main(String[] args) throws NoSuchMethodException {
Method square = MethodTest.class.getMethod("square", double.class);
Method sqrt = Math.class.getMethod("sqrt", double.class);
printTable(1, 10, 10, square);
printTable(1, 10, 10, sqrt);
}
public static double square(double x) {
return x * x;
}
public static void printTable(double from, double to, int n, Method f) {
System.out.println(f);
double dx = (to - from) / (n - 1);
for (double x = from; x <= to; x += dx) {
double y;
try {
y = (double) f.invoke(null, x);
//如果返回类型是基本类型,invoke方法会返回其包装类型,必须完成类型转换;
// 对于静态方法,第一个参数可以可设置为null
System.out.printf("%10.4f | %10.4f%n", x, y);//不是println
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
输出如下
public static double Reflect.MethodTest.square(double)
1.0000 | 1.0000
2.0000 | 4.0000
3.0000 | 9.0000
4.0000 | 16.0000
5.0000 | 25.0000
6.0000 | 36.0000
7.0000 | 49.0000
8.0000 | 64.0000
9.0000 | 81.0000
10.0000 | 100.0000
public static double java.lang.Math.sqrt(double)
1.0000 | 1.0000
2.0000 | 1.4142
3.0000 | 1.7321
4.0000 | 2.0000
5.0000 | 2.2361
6.0000 | 2.4495
7.0000 | 2.6458
8.0000 | 2.8284
9.0000 | 3.0000
10.0000 | 3.1623
动态加载
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如:
// Main.java
public class Main {
public static void main(String[] args) {
if (args.length > 0) {
create(args[0]);
}
}
static void create(String name) {
Person p = new Person(name);
}
}
当执行 Main.java 时,由于用到了 Main,因此,JVM首先会把 Main.class 加载到内存。然而,并不会加载 Person.class,除非程序执行到 create() 方法,JVM发现需要加载 Person 类时,才会首次加载 Person.class。如果没有执行create() 方法,那么 Person.class 根本就不会被加载。
这就是 JVM 动态加载 class 的特性。