面向接口编程
方便扩展,和良好的复用代码及解耦
将多个类的通用方法做抽象,使用继承或实现的方式进行灵活扩展使用
枚举相关
枚举是为了良好的替代多类型的情况下的变量
可以严谨的类型约束变量数值不可控性,扩展也更方便
常用方法:
values()方法:返回所有枚举常量的数组集合ordinal() 方法:返回枚举常量的序数,注意从0开始compareTo()方法:枚举常量间的比较name()方法:获得枚举常量的名称valueOf() 方法:返回指定名称的枚举常量
可自定义扩充枚举:声明函数,构造来自定义枚举方便外部类调用
枚举 + 接口:符合开闭原则的情况进行不同情况的返回处理
枚举的集合类:EnumSet是专门为盛放枚举类型所设计的 Set 类型。EnumMap则是用来专门盛放枚举类型为key的 Map 类型。
深拷贝浅拷贝
赋值不属于拷贝,只是引用的转变
浅拷贝:复制对象中,值类型对象(八大类型加String)会被复制,对象中的对象只复制了引用
深拷贝 :浅拷贝的基础上,对象中的对象也会复制副本
实现方法是使对象继承Cloneable接口,重写clone()方法
通过反序列化的方法也能完成拷贝
序列化/反序列化
序列化:把Java对象转换为字节序列。
反序列化:把字节序列恢复为原先的Java对象。
序列化需对象实现Serializable接口,通过对象输出流ObjectOutputStream进行字节序列写入文件,反序列化使用ObjectInputStream读文件后强转Serializable只是一个空接口,在ObjectOutputStream时检查了这个标记,否则抛出异常
serialVersionUID
用于检查本地字节序列和实际运行时对象的版本差异serialVersionUID是序列化前后的唯一标识符
默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!
特殊情况:
凡是被static修饰的字段是不会被序列化的:因为序列化保存的是对象的状态而非类的状态
凡是被transient修饰符修饰的字段也是不会被序列化的:
序列化的受控和加强
序列化的过程是有漏洞不安全的(字节流被截取进行伪造)
序列化也相当于一种 “隐式的”对象构造 ,在反序列化时,进行受控的对象反序列化动作。
自行编写ObjectInputStream时的readObject()函数,设置反序列化验证
函数式编程 语法糖
注解
做标注用:在类、字段变量、方法、接口等位置设置标记,为代码生成、数据校验、资源整合等工作做铺垫。
实现三大步
第一步:定义注解:@注解名
@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public @interface Length{int min();// 允许字符串长度的最小值int max();// 允许字符串长度的最大值String errorMsg();// 自定义的错误提示信息}
- 注解的定义@interface
- 注解的成员变量只能使用基本类型、 String或者 enum枚举,比如 int可以,但 Integer这种包装类型就不行
- @Target、 @Retention 为 元注解,元注解就是java自带注解,可直接用于注解的定义
- @Target(xxx) 用来说明该自定义注解 可用区域
ElementType.FIELD:说明自定义的注解可以用于类的变量ElementType.METHOD:说明自定义的注解可以用于类的方法ElementType.TYPE:说明自定义的注解可以用于类本身、接口或 enum类型- 等等… 还有很多,如果记不住,建议现用现查
- @Retention(xxx) 用来说明你自定义注解的生命周期,比如:
- @Retention(RetentionPolicy.RUNTIME):注解可以一直保留到运行时,因此可以通过反射获取注解信息
- @Retention(RetentionPolicy.CLASS):注解被编译器编译进 class文件,但运行时会忽略
- @Retention(RetentionPolicy.SOURCE):注解仅在源文件中有效,编译时就会被忽略
第二步:获取注解并对其进行验证
利用反射获取运行时想获取注解所代包含的信息
例publicstaticString validate(Objectobject)throwsIllegalAccessException{ // 首先通过反射获取object对象的类有哪些字段 // 对本文来说就可以获取到Student类的id、name、mobile三个字段 Field[] fields =object.getClass().getDeclaredFields(); // for循环逐个字段校验,看哪个字段上标了注解 for(Field field : fields ){ // if判断:检查该字段上有没有标注了@Length注解 if( field.isAnnotationPresent(Length.class)){ // 通过反射获取到该字段上标注的@Length注解的详细信息 Length length = field.getAnnotation(Length.class); field.setAccessible(true);// 让我们在反射时能访问到私有变量 // 用过反射获取字段的实际值 int value =((String)field.get(object)).length(); // 将字段的实际值和注解上做标示的值进行比对 if( value<length.min()|| value>length.max()){ return length.errorMsg(); } } } return null; }第三步:使用注解
publicclassStudent{ privateLong id;// 学号 privateString name;// 姓名 @Length(min =11, max =11, errorMsg ="电话号码的长度必须为11位") privateString mobile;// 手机号码(11位) }重写hashcode和equals
在往线性表 (比如ArrayList) 中存入无序数字后,取出其中某一个数字时,需要进行遍历时间复杂度为O(n)
而往Hash表 (数据结构上的概念,比如HashMap) 存入无序数字,key会被hash算法随机出一个hashCode,和它的存储位置相关联
这样做的好处非常明显。比如我们要从中找其中一个数字,通过Hash函数计算 它的 的索引位置,然后直接使用索引里找到,平均复杂度为O(1)
注:当出现Hash值冲突(即通过hashCode算法计算后Hash值相同)时,Java的HashMap对象采用的是”链地址法”进行解决,在当前索引处创建了一个链表进行存放
为何要?
在hash表数据结构中使用自定义对象时,需要重写hashcode和equals方法
如果不进行重写,会调用超类Object的hashcode(根据内存地址hash)和equals(判断内存地址是否相等)方法,这样,即使两个看上去相等的对象也拥有不同的内存地址,hash出来也会是两个数据,并且equals判断时,永远不会相等(内存地址不同,String类就重写了equals)
示例
import java.util.HashMap;
class Key{
private Integer id;
public Integer getId(){
return id;
}
public Key(Integer id){
this.id = id;
}
//故意先注释掉equals和hashCode方法
//public boolean equals(Object o) {
// if (o == null || !(o instanceof Key))
// { return false; }
// else
// { return this.getId().equals(((Key) o).getId());}
// }
// public int hashCode()
// { return id.hashCode(); }
}
public class WithoutHashCode{
public static void main(String[] args){
Key k1 =newKey(1);
Key k2 =newKey(1);
HashMap<Key,String> hm =newHashMap<Key,String>();
hm.put(k1,"Key with id is 1");
System.out.println(hm.get(k2));
//不重写hashcode 输出 null ---因为k2没有value
System.out.println(k1==k2);
//不重写equals 输出false ---比较的是内存地址不同
}
}
