继承
-
基于已存在的类构造一个新类,继承已存在的类就是复用(继承)这些类的方法和域 通用的方法在超类中,特殊用途的方法放在子类中
-
继承层次
- 由一个公共超类派生出来的所有类的集合被称为 继承层次
- 从某一个特定的类到其祖先的路径被称为该类的 继承链
- 判断是否应该设计为继承关系的简单规则
is - a规则子类的每个对象也是超类的对象置换法则程序中出现超类对象的任何地方都可以用子类的对象置换
-
将方法或类声明为
final主要目的是:确保他们不会在子类中改变语义 -
String类也是final类 -
方法没有被覆盖并且很短,编译器能够对它进行优化处理,这被称为
内联(inlining) -
即时编译器可以知道类之间的继承关系,并能够检测出类中是否真正地存在覆盖给定的方法。
多态
-
一个特定类型的对象引用可以指向不同类型的对象实例
-
动态绑定 :在运行时依赖于隐式参数的实际类型,(在对程序进行扩展时,无须对现存的代码进行修改)
-
静态绑定 :有
finalstaticprivate修饰的方法,在编译期间就可以准确地知道应该调用哪个方法 -
ArrayStoreException数组存放错误对象异常 -
方法调用步骤
- 编译器检测调用方法的 声明类型 和 方法名
编译器将会列举出C类中所有名为 f 的方法和其超类中访问属性为
public且名为 f 的方法 (超类的私有方法不可访问) - 查看调用方法时提供的参数类型,这被称为
重载解析(参数类型匹配) - 如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,会报错。
方法的名字和参数列表被称为方法签名
-
方法表 虚拟机预先为每个类创建了一个
方法表(method table),列出了所有方法的签名和实际调用的方法。 -
动态绑定的解析过程
- 提取类型的方法表(也可能是当前类的其他子类的方法表)
- 搜索要调用的方法签名的类(在方法表中搜索要调用的的方法签名的类,此时虚拟机已经知道应该调用哪个方法)
- 调用方法
- 强制类型转换
- 超类引用赋给子类变量(暂时忽视对象的实际类型之后,使用对象的全部功能)
- 使用子类中特有的方法
-
instanceof查看对象之间是否存在继承关系 和 引用关系 -
强制类型转换 须知
- 在继承层次内进行类型转换
- 转换之前,应该使用
instanceof进行检查
注:应该尽量少用 类型转换 和 instanceof运算符
抽象
-
包含一个或多个抽象方法的类本身必须被声明为抽象的。
-
上层的类更具有通用性,更加抽象,祖先类更加通用。
- 抽象方法的具体实现在子类中。(子类若不实现父类的抽象方法,也必须被定义为抽象类)
- 抽象类不能被实例化
- 定义一个抽象类的对象变量,引用非抽象子类的对象
Object : 所有类的超类
equals方法
四大特性
- 自反性 :对于任何非空引用 x ,x.equals(x)应该返回 true.
- 传递性 :对于任何引用 x 、y、z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true, x.equals(z) 也应该返回 true.
- 对称性 :对于任何引用 x 和 y,当且仅当 y.equals(x) 返回 true,x.equals(y) 也应该返回 ture.
- 一致性 :如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果.
- 对于任意非空引用 x ,
x.equals(null)应该返回false.
- 编写
equals方法的建议
- 是否引用同一个对象
if(this == otherObject)return true; //要比一个一个地比较类中的域所付出的代价小得多
- 是否为 null
if(otherObject == null)return false;
- 比较是否属于同一个类
if(getClass() != otherObject.getClass())return false;
- 使用
instanceof检测 (保证要比较的类型都拥有统一的语义)
if(!(otherObject instanceof ClassName))return false;
- 转换为相同的类类型变量 ClassName other = (ClassName) otherObject
- 开始对需要比较的域进行比较(使用 == 比较基本类型域,使用 equals 比较对象域)
return field1 == other.field1 && Objects.equals(field2,other.field2) && ...;
-
数组类型的域,使用静态的
Arrays.equals方法 -
Java7 以后提供了
Objects 类提供了 静态equals方法进行比较,如果 a 和 b 都为 null ,返回 true ; 其中一个为 null ,则返回 false; 否则返回 a.equals(b), 使用此种方式进行比较,可防止空指针异常。
hashCode方法
- 整数值(包括整数和负数)
- 没有规律
- 对象的存储地址
- 重写
equals方法的同时,也必须重写hashCode方法(将对象插入到散列表中) - 定义必须一致(eg:
equals比较ID ,hashCode就需要散列 ID) equals相同hashCode一定相同 ;hashCode相同,equals则不一定相同
-
Objects.hashCode方法防止空指针异常。 -
调用 JDK1.7 中的
Objects.hash方法,并提供多个参数,可组合多个散列值。 -
数组类型的域,使用静态的
Arrays.hashCode方法 来计算散列码。
toString方法
-
用于返回表示对象值的字符串。
-
getClass().getName()获得类名的字符串。 -
只要对象与一个字符串通过操作符 “+” 连接起来,Java编译器就会自动地调用 toString 方法,以便获得这个对象的字符串描述。
-
Object 类定义了 toString 方法,用来打印输出 对象所属的 类名和散列码。
-
数组的字符串打印
Arrays.toString方法 打印多维数组Arrays.deepToString方法
- 建议为自定义的每一个类增加
toString方法。
泛型数组列表 ArrayList
-
动态列表
-
ArrayList 是一个采用 类型参数(type parameter) 的 泛型类(generic class) 。
-
Java SE 7 中,可以省去右边的类型参数,这被称为 "菱形" 语法,编译器会检查这个变量、参数或方法的泛型类型,然后将这个类型放在
<>中。ArrayList<User> list = new ArrayList<>(); -
如果调用
add且内部数组已经满了,数组列表就将自动创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。 -
ensureCapacity方法:直接指定数量 的方式可有效降低开销。 -
trimToSize方法:垃圾收集器将 回收多余的存储空间 ,应该在确认 不会添加任何元素时,再调用 trimToSize方法 。 -
访问数组元素
- 使用
add方法为数组添加新元素,而不要使用set方法,它只能替换数组中已经存在的元素内容。- 原始的
ArrayList类只能是 Object 类,必须对返回值进行类型转换,存在一定的危险性。- 使用
ArrayList<>泛型类会对内部的数组类型进行检测 ,当加入集合的元素类型不一致时,编译器会出现一个警告。
-
在数组列表的中间插入元素,使用带索引参数的
add方法。int n = staff.size()/2; staff.add(n,e); -
插入一个新元素,位于 n 之后的所有元素都要向后移动一个位置。此时,若数组列表的大小超过了容量,数组列表就会重新分配存储空间。
-
与之类似,当从数组列表中间删除一个元素,位于这个位置之后的所有元素都向前移动一个位置,并且数组的大小减 1。
-
泛型设计是 Java语言糖,在程序运行时,所有的数组列表都是一样的,即没有虚拟机中的类型参数。因此,类型转换(ArrayList)和(ArrayList)将执行相同的运行时检查。所以,在使用原始的列表类型时,出现警告,可以不必担心,这并不会造成严重的后果。
对象包装器与自动装箱
-
所有的基本类型都有一个与之对应的类。这些类称为 包装器(wrapper)。
Byte、Short、Integer、 Long、Character、Float、Double、Boolean、VoidVoid : 表示空类 -
对象包装器都是
final,因此不能定义它们的子类。 -
泛型类的尖括号
<>中的类型参数不允许是基本类型 -
ArrayList<Integer>的效率远远低于int[]数组,应该用它构建 小型集合,此时程序员操作的方便性要比执行效率更加重要。
| 自动装箱 | 自动拆箱 |
|---|---|
| 基本类型==》包装类 | 包装类==》基本类型 |
-
两个包装器对象比较时调用
equals方法。 -
包装器注意事项
- 包装器类引用可以为 null ,所以自动装箱有可能会抛出一个
NullPointerException异常。- 如果在一个条件表达式中混合使用
Integer和Double类型,Integer值就会拆箱,提升为double,再装箱为Double。
-
装箱和拆箱是编译器认可的,而不是虚拟机。
-
parseInt是一个Integer包装类的静态方法。 -
包装类是
final不可变类,包装在包装器中的内容不会改变。 -
修改数值参数值的方法,使用
org.omg.CORBA包中定义的(持有者)holder类型,包括IntHolderBooleanHolder等 -
Number parse(String s)字符串转数值
不定长参数的方法
-
printf方法接收两个参数,一个是 格式字符串,另一个是 Object[]数组,数组内存放着提供的参数,若是其内部为整型数组或者其他基本类型的值,将自动装箱为包装类型,扫描fmt,并将第 i 个格式说明符与args[i]的值匹配。 -
Object...参数类型与Object[]完全一样。 -
最后一个参数是数组的方法可重新定义为可变参数的方法,并不会破坏任何已经存在的代码。
枚举类
- 所有的枚举类都是
Enum类的子类。 - 静态的
values方法,返回一个包含全部枚举值的数组。 - Enum 类省略了一个类型参数,实际上 ,枚举类型 应该 是
Enum<T>这种形式。
反射
- 能够分析类能力的程序称为反射(reflective)
- 在运行时分析类的能力
- 在运行时查看对象
- 实现通用的数组操作代码
- 利用
Method对象,这个对象很像 C++ 中的函数指针。
- 获得Class类的 3种方式
- Object 类中的
getClass方法 - 对象类的
class属性 - Class 类的静态方法
forName方法 ,获得类名字符串,从而得到对应的 Class 对象
-
调用
forName方法时,只有在 className 是类名或者接口名时才能够执行。否则,forName 方法将抛出一个checked exception (检查异常),在使用 forName 方法是,需要提供一个异常处理器。 -
newInstance方法 ,实现动态地创建一个类的实例。 调用的是默认的构造器,若此时没有默认的构造器,就会抛出一个异常。 -
异常分为
运行时异常和检查时异常,检查时异常强制规定在程序中必须被捕获。 -
Modifier类获取 修饰符 和 返回类型。Modifier类的静态方法isPublicisPrivateisFinal等方法可判断 方法或者构造器是否 为相应的 访问级别。 -
在运行时改变对象的状态
- 使用反射获取到类的 数据域 后 ,针对 访问级别不够的数据域,要执行
setAccessible方法,指定参数为ture,方可对相应的数据域进行处理。f.setAccessible(true)setAccessible方法是AccessibleObject类中的一个方法。它是FieldMethodConstructor类的公共超类。 - 基本数据类型 使用
getDouble此形式的方法进行获取;对象类型 使用get方式获取。 - 设置:调用
f.set(obj,value)可以将 obj 对象的 f 域设置成新值 ,相对应的 基本数据类型 ,使用setXXX方法进行设置。
- 编写泛型数组代码
- Array 类允许动态地创建数组
- Array 类中的静态方法
newInstance方法能够构造新数组,必须提供两个参数,一个是数组的 元素类型,一个是数组的长度
Object newArray = Array.newInstance(componentType,newLength);
- 获得数组的长度
Array.getLength(a)获得数组的长度,Array 类的静态getLength方法的返回值得到任意数组的长度。
- 获得数组的元素类型
- 首先获得 a 数组的类对象
Class clazz = arr.getClass(); - 确认它是一个数组
if (clazz.isArray()) {System.out.println("arr是数组");} - 使用
Class类的getComponentType方法确定数组对应的类型
-
System.arraycopy方法数组拷贝,常用于扩容。 -
public Object invoke(Object implicitparameter,Object[] explicitParamenters)反射中 Method类的 执行方法,参数为 对象 和 方法参数,返回方法的返回值,方法若没有参数,第二个参数可以使用 null 作为隐式参数传递,在使用包装器传递基本类型的值时,基本类型的返回值必须是未包装的。