面向对象基础
面向对象四大特性
- 抽象
- 封装
- 继承
- 多态
OOP 理念
- 我是谁? getClass() 说明本质上是谁,而 toString() 是当前值为的名片;
- 我从哪里来? Object() 构造方法是生产对象的基本步骤,clone() 是繁殖对象的另一种方式;
- 我到哪里去? finalize() 是在对象销毁时触发的方法;
- 世界是否因你而不同? hashCode() 和 equals() 就是判断与其他元素是否相等的一组方法;
- 与他人如何协调? wait() 和 notify() 是对象间通信与写作的一组方法;
克隆
克隆分为浅拷贝、一般深拷贝和彻底深拷贝。
浅拷贝只复制当前对象的所有基本数据类型,以及相应的引用变量;
彻底深拷贝是在成功 clone 一个对象之后,此对象与母对象在任何引用路径上都不存在共享的实例对象,但是引用路径递归越深,则越接近 JVM 底层对象,且发现彻底深拷贝实现难度越大。
归根结底,慎用 Object 的 clone() 方法来拷贝对象,因为对象的 clone() 方法默认是浅拷贝,若想实现深拷贝,则需要复写 clone() 方法实现引用对象的深度遍历式拷贝。
类
类的定义
类的定义有访问级别、类型、类名、是否抽象、是否静态、是否继承、是否实现。
- 访问级别:public 和 无访问控制符
- 类型:class、interface、enum
接口与抽象类
内部类
内部类本身就是一个属性,与其他属性定义方式一致
- 静态内部类:如:static class StaticInnerClass{}
- 成员内部类:如:private class InstanceInnerClass{}
- 局部内部类:定义在方法或者表达式内部
- 匿名内部类:如:(new Thread(){}).start()
访问权限控制
注意:为什么子类中不能访问另一个包中父类中的protected方法?
其实这个问题问法是错的,在子类中是可以访问另一个包中父类中的 protected 方法,能问出这样的问题,多半是在不同包的子类中创建了父类对象,通过父类对象去调用 protected 方法,结果发现编译不通过,所以才会萌生这样的疑问。
正确的访问方式:
1. super.protectedMethod()
2. 创建子类对象 instance,通过 instance.protectedMethod() 访问
在定义类时,推荐访问控制级别从严处理.
- 如果不允许外部直接通过 new 创建对象, 构造方法必须是 private ;
- 工具类不允许有 public 或 default 构造方法;
- 类非 static 成员变雪并且与子类共享, 必须是 protected ;
- 类非 static 成员变量并且仅在本类使用, 必须是 private ;
- 类 static 成员变量如果仅在本类使用, 必须是private ;
- 若是 static 成员变量, 必须考虑是否为 fianl ;
- 类成员方法只供类内部调用, 必须是 private ;
- 类成员方法只对继承类公开, 那么限制为 protected 。
类关系
- 继承: extends (is-a)
- 实现: implements (can-do)
- 组合: 类是成员变量 (contains-a) ,比如头和身体的关系
- 聚合: 类是成员变量 (has-a),比如小狗和狗绳的关系,狗绳完全可以复用在一条小狗身上
- 依赖: import 类 (use-a)
序列化
- Java 原生序列化:实现 Serializable 接口实现该类对象的序列化。基于性能及兼容性考虑,不推荐使用
- Hessian 序列化:Hessian 序列化是一种支持动态类型、跨语言、基于对象传输的网络协议。特点:①自描述序列化类型;②与语言无关;③写简单,比 Java 原生序列化高效。Hessian 会把复杂对象所有属性存储在一个 Map 中进行序列化。所以在父类、子类存在同名成员变量的情况下, Hessian 序列化时,先序列化子类,然后序列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖。
- Json 序列化:一种轻量级的数据交换格式,在序列化过程抛弃了类信息,反序列化时只有提供类型信息才能准确的反序列化。JSON 可读性好,方便调试。
序列化常常成为黑客的攻击点,如何防范黑客攻击?有些对象的敏感属性不需要进行序列化传输,可以加 transient 关键字,避免把此属性信息转换成序列化二进制流。如果一定要传递对象的敏感信息,可以使用对称与非对称加密方式独立传输,在使用某个方法把属性还原到对象中。
方法
方法签名
方法签名包括方法名称和参数列表,是 JVM 标识方法的唯一索引,不包括返回值,更不包括访问权限控制符、异常类型等。
参数
package top.simba1949; /** * @author SIMBA1949 * @date 2019/8/15 8:10 */ public class ParamTest { private static int intStatic = 222; private static String stringStatic = "old string"; private static StringBuilder stringBuilderStatic = new StringBuilder("old stringBuilder"); public static void main(String[] args) { /** * 参数是局部变量、拷贝静态变量的 777,并存入虚拟机栈的局部变量表中。 * 虽然方法内部的 intStatic 与静态变量同名,但是因为作用域就近原则,它是局部变量的参数,所有的操作与静态变量无关 * * 无参方法先把本地赋值的 888 压入到虚拟机栈中的操作栈,然后给静态变量 intStatic 赋值 */ // 输出依然是 222 method(intStatic); System.out.println(intStatic); // 无参方法调用之后,反而修改为 888 method(); System.out.println(intStatic); // String 是 immutable 对象,String 没有提供任何方法用于修改对象 // 输出依然是 old string method(stringStatic); System.out.println(stringStatic); // 实参向形参传递的是引用地址,形参修改引用地址对象属性,实参获取该引用地址对象的属性也是变化后的 // 输出 old stringBuilder.method.first-method.second-new method's method method(stringBuilderStatic, stringBuilderStatic); System.out.println(stringBuilderStatic); } public static void method(int intStatic){ intStatic = 777; } public static void method(){ intStatic = 888; } public static void method(String stringStatic){ stringStatic = "new string"; } public static void method(StringBuilder stringBuilderStatic1, StringBuilder stringBuilderStatic2){ stringBuilderStatic1.append(".method.first-"); stringBuilderStatic2.append("method.second-"); // 引用重新赋值 stringBuilderStatic1 = new StringBuilder("new stringBuilder"); stringBuilderStatic2.append("new method's method"); } }
方法重载
- 精确匹配
- 如果是基本数据类型,自动转换成更大表示范围的基本类型
- 通过自动拆箱与装箱
- 通过子类向上转型集成路线依次匹配
- 通过可变参数匹配
例外
package top.simba1949; /** * @author SIMBA1949 * @date 2019/8/14 7:32 */ public class OverloadTest { public static void main(String[] args) { // 以下重载都会直接报错,如果对应形参列表则不会出现报错 // overloadMethod(7, 7); // longOverload(1L, 1L); // doubleOverload(1.0, 1.0); // floatOverload(1.0f,1.0f); // 如果对应形参列表则不会出现报错,即精确匹配 int a = 1; Integer b = 2; overloadMethod(a, b); overloadMethod(b, a); } public static void overloadMethod(int a, Integer b){ System.out.println("int Integer"); } public static void overloadMethod(Integer a, int b){ System.out.println("Integer int"); } public static void longOverload(Long a, long b){} public static void longOverload(long a, Long b){} public static void doubleOverload(Double a, double b){} public static void doubleOverload(double a, Double b){} public static void floatOverload(Float a, float b){} public static void floatOverload(float a, Float b){} }
泛型
泛型的本质是类型参数化,解决不确定具体对象类型的问题。
E 代表 Element ,用于集合中的元素;T 代表 the Type of object ,表示某个类;K 代表 Key,V 代表 Value,用于键值对元素。
泛型可以定义在类、接口、方法中,编译器通过识别尖括号和尖括号内的字母来解析泛型。
- 尖括号里的每一个元素都指代一种未知类型。
- 尖括号未知非常讲究,必须在类名之后或者方法返回值之前。
- 泛型在定义处只具备执行 Object 方法的能力。
- 对于编译之后的字节码指令,其实没有这些花头花脑的方法签名,充分说明了泛型知识一种编写代码时的语法检查。
基本数据类型
引用分为两种数据类型:引用变量本身 refvar (Reference Variable)和引用指向的对象 refobj (Referred Object)。
refvar 是基本的数据类型,它的默认值为 null,存储 refobj 的首地址,可以直接使用 == 进行等值判断。refvar.hashCode() 返回的值, 只是对象的某种哈希计算,可能与地址有关,与 refvar 本身存储的内存单元地址是两回事。refvar 均占用 4B 空间。一个 refvar 至多存储一个 refobj 的首地址。
无论 refobj 是多么小的对象,最小占用的存储空间是 12B (用于存储基本信息,成为对象头),但是由于存储空间分配必须是 8B 的倍数,所有初始分配的空间最少是 16B。
- 对象头( Object Header ):对象头占用 12 个字节,存储内容包括对象标记 (markOop) 和类元信息 (klassOop)。对象标记存储对象本身运行时的数据,如哈希码、GC 标记、锁信息、线程关联信息等,这部分数据在 64 位 JVM 上占用8 个字节,称为 “Mark Word”。为了存储更多的状态信息,对象标记的存储格式是非固定的(具体与 JVM 的实现有关)。类元信息存储的是对象指向它的类元数据(即Klass )的首地址,占用4 个字节,与 refvar 开销一致。
- 实例数据( Instance Data ):存储本类对象的实例成员变量和所有可见的父类成员变量。
- 对齐填充( Padding ):对象的存储空间分配单位是 8 个字节,如果一个占用大小为16 个字节的对象,增加一个成员变量 byte 类型,此时需要占用17 个字节,但是也会分配24 个字节进行对齐填充操作。
合理利用包装类的缓存
基本数据类型的包装类,推荐使用 valueOf(),合理利用缓存,提升程序性能。
各个包装类的缓存区间如下
- Boolean :使用静态 final 变量定义, valueOf() 就是返回这两个静态值。
- Byte: 表示范围是 -128~127 ,全部缓存。
- Short:表示范围是 -32768~32767 ,缓存范围是 -128~127 。
- Character: 表示范围是 0~65535 ,缓存范围是 0~127 。
- Long :表示范围是[-2 63 2^{63}2
package top.simba1949; /** * @author SIMBA1949 * @date 2019/8/14 7:32 */ public class OverloadTest { public static void main(String[] args) { Long a1 = 127L; Long a2 = 127L; Long b1 = 128L; Long b2 = 128L; System.out.println("Long max cached value is 127: a1 == a2 result is true > " + (a1 == a2)); System.out.println("Long max cached value is 127: b1 == b2 result is false > " + (b1 == b2)); } }