Java基础
集合
Java的集合类被定义在Java.util包中,主要有4种集合,分别为List、Queue、Set和Map。(有整理思维导图在另一篇博客Java中的collection思维导图)
List
List是非常常用的数据类型,是有序的Collection,一共有三个实现类,分别是ArrayList、Vector和LinkedList。
- ArrayList - 基于数组实现,增删慢,查询快,线程不安全。缺点是对元素必须连续存储,当需要在ArrayList的中间位置插入或者删除元素时,需要将待插入或者删除的节点后的所有元素进行移动,其修改代价较高,因此,ArrayList不适合随机插入和删除的操作,更适合随机查找和遍历的操作。ArrayList不需要在定义时指定数组的长度,在数组长度不能满足存储要求时,ArrayList会创建一个新的更大的数组并将数组中已有的数据复制到新的数组中。(补充: ArrayList、Vector默认初始容量为10。加载因子为1:即当 元素个数 超过 容量长度 时,进行扩容,扩容增量:原容量的 1.5倍,如 ArrayList的容量为10,一次扩容后是容量为15 (vector是扩容1倍,20))
- Vector - 基于数组实现,增删慢,查询快,线程安全。Vector的数据结构和ArrayList一样,都是基于数组实现的,不同的是Vector支持线程同步,即同一时刻只允许一个线程对Vector进行写操作(新增、删除、修改),以保证多线程环境下数据的一致性,但需要频繁地对Vector实例进行加锁和释放锁操作,因此,Vector的读写效率在整体上比ArrayList低。
- LinkedList - 基于双向链表实现,增删快,查询慢,线程不安全。LinkedList采用双向链表结构存储元素,在对LinkedList进行插入和删除操作时,只需在对应的节点上插入或删除元素,并将上一个节点元素的下一个节点的指针指向该节点即可,数据改动较小,因此随机插入和删除效率很高。但在对LinkedList进行随机访问时,需要从链表头部一直遍历到该节点为止,因此随机访问速度很慢。除此之外,LinkedList还提供了在List接口中未定义的方法,用于操作链表头部和尾部的元素,因此有时可以被当作堆栈、队列或双向队列使用。
Queue
Queue是队列结构,Java中的常用队列如下:
◎ ArrayBlockingQueue:基于数组数据结构实现的有界阻塞队列。
◎ LinkedBlockingQueue:基于链表数据结构实现的有界阻塞队列。
◎ PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
◎ DelayQueue:支持延迟操作的无界阻塞队列。
◎ SynchronousQueue:用于线程同步的阻塞队列。
◎ LinkedTransferQueue:基于链表数据结构实现的无界阻塞队列。
◎ LinkedBlockingDeque:基于链表数据结构实现的双向阻塞队列。
Set
Set核心是独一无二的性质,适用于存储无序且值不相等的元素。对象的相等性在本质上是对象的HashCode值相同,Java依据对象的内存地址计算出对象的HashCode值。如果想要比较两个对象是否相等,则必须同时覆盖对象的hashCode方法和equals方法,并且hashCode方法和equals方法的返回值必须相同。
- HashSet - HashTable实现,无序。HashSet存放的是散列值,它是按照元素的散列值来存取元素的。元素的散列值是通过元素的hashCode方法计算得到的,HashSet首先判断两个元素的散列值是否相等,如果散列值相等,则接着通过equals方法比较,如果equls方法返回的结果也为true, HashSet就将其视为同一个元素;如果equals方法返回的结果为false, HashSet就不将其视为同一个元素。
- TreeSet - 二叉树实现。TreeSet基于二叉树的原理对新添加的对象按照指定的顺序排序(升序、降序),每添加一个对象都会进行排序,并将对象插入二叉树指定的位置。Integer和String等基础对象类型可以直接根据TreeSet的默认排序进行存储,而自定义的数据类型必须实现Comparable接口,并且覆写其中的compareTo函数才可以按照预定义的顺序存储。若覆写compare函数,则在升序时在this.对象小于指定对象的条件下返回**-1**,在降序时在this.对象大于指定对象的条件下返回1。
- LinkedHashSet - 实现数据存储,双向链表记录顺序。LinkedHashSet在底层使用LinkedHashMap存储元素,它继承了HashSet,所有的方法和操作都与HashSet相同,因此LinkedHashSet的实现比较简单,只提供了4个构造方法,并通过传递一个标识参数调用父类的构造器,在底层构造一个LinkedHashMap来记录数据访问,其他相关操作与父类HashSet相同,直接调用父类HashSet的方法即可。
Map
- HashMap: 数组+链表/红黑树数据结构,线程不安全。HashMap基于键的HashCode值唯一标识一条数据,同时基于键的HashCode值进行数据的存取,因此可以快速地更新和查询数据,但其每次遍历的顺序无法保证相同。HashMap的key和value允许为null。HashMap是非线程安全的,即在同一时刻有多个线程同时写HashMap时将可能导致数据的不一致。如果需要满足线程安全的条件,则可以用Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。HashMap的内部是一个数组,数组中的每个元素都是一个单项链表,链表中的每个元素都是嵌套类Entry的实例(key, value, hashcode和用于指向链表下一个元素的next)。HashMap在查找数据时,根据HashMap的Hash值可以快速定位到数组的具体下标,但是在找到数组下标后需要对链表进行顺序遍历直到找到需要的数据,时间复杂度为O(n)。为了减少链表遍历的开销,Java 8对HashMap进行了优化,将数据结构修改为数组+链表或红黑树。在链表中的元素超过8个以后,HashMap会将链表结构转换为红黑树结构以提高查询效率,因此其时间复杂度为O(log N)。
常用的参数有
◎ capacity:当前数组的容量,默认为16,可以扩容,扩容后数组的大小为当前的两倍,因此该值始终为2^n。
◎ loadFactor:负载因子,默认为0.75。
◎ threshold:扩容的阈值,其值等于capacity×loadFactor。
![](https://i-blog.csdnimg.cn/blog_migrate/2d7a6451352e3cc648bedfb50368207d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4ad99d33f55f3164b526fc4e84ed21d7.png)
- ConcurrentHashMap,分段锁实现,线程安全。ConcurrentHashMap采用分段锁的思想实现并发操作,因此是线程安全的。ConcurrentHashMap由多个Segment组成(Segment的数量也是锁的并发度),每个Segment均继承自ReentrantLock并单独加锁,所以每次进行加锁操作时锁住的都是一个Segment,这样只要保证每个Segment都是线程安全的,也就实现了全局的线程安全。concurrencyLevel参数表示并行级别,默认是16,也就是说ConcurrentHashMap默认由16个Segments组成,在这种情况下最多同时支持16个线程并发执行写操作,只要它们的操作分布在不同的Segment上即可。
![](https://i-blog.csdnimg.cn/blog_migrate/58e98a65f4845379f0f7b25cfce4f4f0.png)
- HashTable,线程安全。HashTable是遗留类,很多映射的常用功能都与HashMap类似,不同的是它继承自Dictionary类,并且是线程安全的,同一时刻只有一个线程能写HashTable,并发性不如ConcurrentHashMap。
- TreeMap,基于二叉树数据结构,有序。TreeMap基于二叉树数据结构存储数据,同时实现了SortedMap接口以保障元素的顺序存取,默认按键值的升序排序,也可以自定义排序比较器。TreeMap常用于实现排序的映射列表。在使用TreeMap时其key必须实现Comparable接口或采用自定义的比较器,否则会抛出java.lang.ClassCastException异常。
- LinkedHashMap,基于HashTable数据结构,使用双向链表保存插入顺序。LinkedHashMap为HashMap的子类,其内部使用链表保存元素的插入顺序,在通过Iterator遍历LinkedHashMap时,会按照元素的插入顺序访问元素。(补充:LinkedHashMap有插入顺序和访问顺序两种,两种顺序都是尾插,开启访问顺序后LRU算法就与LinkedHashMap十分适合,true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在尾部,最老访问的放在头部。)
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>((int)Math.ceil(capacity/0.75)+1, 0.75f, true);
异常分类及处理
异常的概念
异常指在方法不能按照正常方式完成时,可以通过抛出异常的方式退出该方法,在异常中封装了方法执行过程中的错误信息及原因,调用方在获取该异常后可根据业务的情况选择处理该异常或者继续抛出该异常。在方法在执行过程中出现异常时,Java异常处理机制会将代码的执行权交给异常处理器,异常处理器根据在系统中定义的异常处理规则执行不同的异常处理逻辑(抛出异常或捕捉并处理异常)。
在Java中,Throwable是所有错误或异常的父类,Throwable又可分为Error和Exception,常见的Error有AWTError、ThreadDeath, Exception又可分为RuntimeException和CheckedException。
![](https://i-blog.csdnimg.cn/blog_migrate/c1f06b80b93a036dba46b572c57f3e37.png)
- Error指Java程序运行错误,如果程序在启动时出现Error,则启动失败;如果程序在运行过程中出现Error,则系统将退出进程。出现Error通常是因为系统的内部错误或资源耗尽,Error不能被在运行过程中被动态处理。如果程序出现Error,则系统能做的工作也只能有记录错误的成因和安全终止。
- Exception指Java程序运行异常,即运行中的程序发生了人们不期望发生的事件,可以被Java异常处理机制处理。Exception也是程序开发中异常处理的核心,可分为RuntimeException(运行时异常)和CheckedException(检查异常)
◎ RuntimeException:指在Java虚拟机正常运行期间抛出的异常,RuntimeException可以被捕获并处理,如果出现RuntimeException,那么一定是程序发生错误导致的。我们通常需要抛出该异常或者捕获并处理该异常。常见的RuntimeException有NullPointerException、ClassCastException、ArrayIndexOutOf BundsException等。
◎ CheckedException:指在编译阶段Java编译器会检查CheckedException异常并强制程序捕获和处理此类异常,即要求程序在可能出现异常的地方通过try catch语句块捕获并处理异常。常见的CheckedException有由于I/O错误导致的IOException、SQLException、ClassNotFoundException等。该类异常一般由于打开错误的文件、SQL语法错误、类不存在等引起。
异常处理方式有抛出异常和使用try catch语句块捕获并处理异常这两种方式。
抛出出异常有三种形式:throws、throw、系统自动抛出异常。其中,throws作用在方法上,用于定义方法可能抛出的异常;throw作用在方法内,表示明确抛出一个异常。
//throw,方法内,直接抛出
throw new StringIndexOutOfBoundsException();
// throws,方法上,可能出现的异常
int div(int a, int b) throws Exception{return a/b; }
throw和throws的区别如下。
◎ 位置不同:throws作用在方法上,后面跟着的是异常的类;而throw作用在方法内,后面跟着的是异常的对象。
◎ 功能不同:throws用来声明方法在运行过程中可能出现的异常,以便调用者根据不同的异常类型预先定义不同的处理方式;throw用来抛出封装了异常信息的对象,程序在执行到throw时后续的代码将不再执行,而是跳转到调用者,并将异常信息抛给调用者。也就是说,throw后面的语句块将无法被执行(finally语句块除外)。
反射机制
动态语言指程序在运行时可以改变其结构的语言,比如新的属性或方法的添加、删除等结构上的变化。JavaScript、Ruby、Python等都属于动态语言;C、C++不属于动态语言。从反射的角度来说,Java属于半动态语言。
反射机制:反射机制指在程序运行过程中,对任意一个类都能获取其所有属性和方法,并且对任意一个对象都能调用其任意一个方法。这种动态获取类和对象的信息,以及动态调用对象的方法的功能被称为Java语言的反射机制。
反射的应用:
Java中的对象有两种类型:编译时类型和运行时类型。编译时类型指在声明对象时所采用的类型,运行时类型指为对象赋值时所采用的类型。如下代码,编译时对象类型为Person,运行时类型为Student,因此,程序在编译期间无法预知该对象和类的真实信息,只能通过运行时信息来发现该对象和类的真实信息,而其真实信息(对象的属性和方法)通常通过反射机制来获取,这便是Java语言中反射机制的核心功能。
Person person = new Student();
Java的反射API
Java的反射API主要用于在运行过程中动态生成类、接口或对象等信息,其常用API如下。
◎ Class类:用于获取类的属性、方法等信息。
◎ Field类:表示类的成员变量,用于获取和设置类中的属性值。
◎ Method类:表示类的方法,用于获取方法的描述信息或者执行某个方法。
◎ Constructor类:表示类的构造方法。
反射的步骤如下。
(1)获取想要操作的类的Class对象,该Class对象是反射的核心,通过它可以调用类的任意方法。
(2)调用Class对象所对应的类中定义的方法,这是反射的使用阶段。
(3)使用反射API来获取并调用类的属性和方法等信息。
获取Class对象的3种方法如下
- 调用某个对象的getClass方法以获取该类对应的Class对象:
Person p = new Person(); Class clazz = p.getClass();
- 调用某个类的class属性以获取该类对应的Class对象:
Class clazz = Person.class;
- 调用Class类中的forName静态方法以获取该类对应的Class对象,这是最安全、性能也最好的方法:
Class clazz=Class.forName("fullClassPath"); //fullClassPath为类的包路径及名称
获取该类中的方法和属性(与上述4个反射API对应)
//1:获取Person类的Class对象
Class clazz = Class.forName("hello.java.reflect.Persion");
//2:获取Person类的所有方法的信息
Method[] method = clazz.getDeclaredMethods();
for(Method m:method){
System.out.println(m.toString());
}
//3:获取Person类的所有成员的属性信息
Field[] field = clazz.getDeclaredFields();
for(Field f:field){
System.out.println(f.toString());
}
//4:获取Person类的所有构造方法的信息
Constructor[] constructor = clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.println(c.toString());
}
创建反射对象的两种方法
◎ 使用Class对象的newInstance方法创建该Class对象对应类的实例,这种方法要求该Class对象对应的类有默认的空构造器。
◎ 先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance方法创建Class对象对应类的实例,通过这种方法可以选定构造方法创建实例。
//1.1:获取Person类的Class对象
Class clazz = Class.forName("hello.java.reflect.Persion");
//2.1:使用newInstane方法创建对象
Person p = (Person) clazz.newInstance();
//1.2:获取构造方法并创建对象
Constructor c = clazz.getDeclaredConstructor
(String.class, String.class, int.class);
//2.2:根据构造方法创建对象并设置属性
Person p1 = (Person) c.newInstance("李四", "男",20);
Method的invoke方法
通过调用Method的invoke方法能够动态调用该方法:
1.获取Method对象getMethod() -> 2.调用invoke方法
代码例子(首先通过Class.forName方法获取Persion类的Class对象;然后调用Persion类的Class对象的getMethod(“setName”, String.class)获取一个method对象;接着使用Class对象获取指定的Constructor对象并调用Constructor对象的newInstance方法创建Class对象对应类的实例;最后通过调用method.invoke方法实现动态调用,这样就通过反射动态生成类的对象并调用其方法。)
//step 1:获取Persion类(hello.java.reflect.Persion)的Class对象
Class clz = Class.forName("hello.java.reflect.Persion");
//step 2:获取Class对象中的setName方法
Method method = clz.getMethod("setName", String.class);
//step 3:获取Constructor对象
Constructor constructor = clz.getConstructor();
//step 4:根据Constructor定义对象
Object object = constructor.newInstance();
step 5:调用method的invoke方法,这里的method表示setName方法
//因此,相当于动态调用object对象的setName方法并传入alex参数
method.invoke(object, "alex");
注解
**注解(Annotation)**是Java提供的设置程序中元素的关联信息和元数据(MetaData)的方法,它是一个接口,程序可以通过反射获取指定程序中元素的注解对象,然后通过该注解对象获取注解中的元数据信息。
元注解(Meta-Annotation)负责注解其他注解。在Java中定义了4个标准的元注解类型@Target、@Retention、@Documented、@Inherited,用于定义不同类型的注解。
- @Target: @Target说明了注解所修饰的对象范围。注解可被用于packages、types(类、接口、枚举、注解类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(循环变量、catch参数等)。在注解类型的声明中使用了target,可更加明确其修饰的目标。
- @Retention: @Retention定义了该注解被保留的级别,即被描述的注解在什么级别有效,有以下3种类型。
◎ SOURCE:在源文件中有效,即在源文件中被保留。
◎ CLASS:在Class文件中有效,即在Class文件中被保留。
◎ RUNTIME:在运行时有效,即在运行时被保留。 - @Documented: @Documented表明这个注解应该被javadoc工具记录,因此可以被javadoc类的工具文档化。
- @Inherited: @Inherited是一个标记注解,表明某个被标注的类型是被继承的。如果有一个使用了@Inherited修饰的Annotation被用于一个Class,则这个注解将被用于该Class的子类。
注解处理器(具体代码见书,利用注解构造接口并赋值)
- 定义注解接口
- 使用注解接口
- 定义注解处理器
内部类
定义在类内部的类被称为内部类。内部类根据不同的定义方式,可分为静态内部类、成员内部类、局部内部类和匿名内部类这4种。
泛型
泛型的本质是参数化类型,泛型提供了编译时类型的安全检测机制,该机制允许程序在编译时检测非法的类型。在不使用泛型的情况下,我们可以通过引用Object类型来实现参数的任意化,因为在Java中Object类是所有类的父类,但在具体使用时需要进行强制类型转换。强制类型转换要求开发者必须明确知道实际参数的引用类型,不然可能引起前置类型转换错误,在编译期无法识别这种错误,只能在运行期检测这种错误(即只有在程序运行出错时才能发现该错误)。而使用泛型的好处是在编译期就能够检查类型是否安全,同时所有强制性类型转换都是自动和隐式进行的,提高了代码的安全性和重用性。
泛型标记及泛型限定:E、T、K、V、N、?
泛型标记 | 说明 |
---|---|
E-Element | 在集合中时候用,表示在集合中存放的元素 |
T-Type | 表示Java类,包括基本的类和我们自定义的类 |
K-Key | 表示键,比如map中的key |
V-Value | 表示值 |
N-Number | 表示数值类型 |
? | 不确定的Java类型 |
类型通配符使用“? ”表示所有具体的参数类型,例如List<? >在逻辑上是List、List等所有List<具体类型实参>的父类。
在使用泛型的时候,若希望将类的继承关系加入泛型应用中,就需要对泛型做限定,具体的泛型限定有对泛型上线的限定和对泛型下线的限定。
- 对泛型上限的限定:<? extends T>:在Java中使用通配符“? ”和“extends”关键字指定泛型的上限,具体用法为<? extends T>,它表示该通配符所代表的类型是T类的子类或者接口T的子接口。
- 对泛型下限的限定:<? super T>:在Java中使用通配符“? ”和“super”关键字指定泛型的下限,具体用法为<? super T>,它表示该通配符所代表的类型是T类型的父类或者父接口。
泛型方法
泛型方法指将方法的参数类型定义为泛型,以便在调用时接收不同类型的参数。在方法的内部根据传递给泛型方法的不同参数类型执行不同的处理方法,具体用法如下,定义了一个泛型方法,该方法根据传入数据的不同类型执行不同的数据处理逻辑,然后通过generalMethod(“1”,2, new Wroker())调用该泛型方法:
public static void main(String[] args) {
generalMethod("1",2, new Wroker());
}
//定义泛型方法generalMethod, printArray为泛型参数列表
public static < T > void generalMethod( T ... inputArray )
{
for ( T element : inputArray ){
if (element instanceof Integer) {
System.out.println("处理Integer类型数据中...");
} else if (element instanceof String) {
System.out.println("处理String类型数据中...");
} else if (element instanceof Double) {
System.out.println("处理Double类型数据中...");
} else if (element instanceof Float) {
System.out.println("处理Float类型数据中...");
} else if (element instanceof Long) {
System.out.println("处理Long类型数据中...");
} else if (element instanceof Boolean) {
System.out.println("处理Boolean类型数据中...");
} else if (element instanceof Date) {
System.out.println("处理Date类型数据中...");
} else if (element instanceof Wroker) {
System.out.println("处理Wroker类型数据中...");
}
}
}
泛型类
泛型类指在定义类时在类上定义了泛型,以便类在使用时可以根据传入的不同参数类型实例化不同的对象。
泛型类的具体使用方法是在类的名称后面添加一个或多个类型参数的声明部分,在多个泛型参数之间用逗号隔开。具体用法如下,代码中通过public class GeneralClass定义了一个泛型类,可根据不同的需求参数化不同的类型(参数化类型指编译器可以自动定制作用于特定类型的类),比如参数化一个字符串类型的泛型类对象,new GeneralClass()。:
//定义一个泛型类
public class GeneralClass<T> {
public static void main(String[] args) {
//根据需求初始化不同的类型
GeneralClass<Integer> genInt =new GeneralClass<Integer>();
genInt.add(1);
GeneralClass<String> genStr =new GeneralClass<String>();
genStr.add("2");
}
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
// 参数化一个字符串类型的泛型类对象
new GeneralClass<String>();
泛型接口
泛型接口的声明和泛型类的声明类似,通过在接口名后面添加类型参数的声明部分来实现。泛型接口的具体类型一般在实现类中进行声明,不同类型的实现类处理不同的业务逻辑,代码通过public interface IGeneral定义了一个泛型接口,并通过public class GeneralIntergerImpl implements IGeneral定义了一个Integer类型的实现类:
//定义一个泛型接口
public interface IGeneral<T> {
public T getId();
}//定义泛型接口的实现类
public class GeneralIntergerImpl implements IGeneral<Integer>{
@Override
public Integer getId() {
Random random = new Random(100);
return random.nextInt();
}
public static void main(String[] args) {
//使用泛型
GeneralIntergerImpl gen = new GeneralIntergerImpl();
System.out.println(gen.getId());
}
}
类型擦除
在编码阶段采用泛型时加上的类型参数,会被编译器在编译时去掉,这个过程就被称为类型擦除。因此,泛型主要用于编译阶段。在编译后生成的Java字节代码文件中不包含泛型中的类型信息。例如,编码时定义的List和List在经过编译后统一为List。JVM所读取的只是List,由泛型附加的类型信息对JVM来说是不可见的。
Java类型的擦除过程为:首先,查找用来替换类型参数的具体类(该具体类一般为Object),如果指定了类型参数的上界,则以该上界作为替换时的具体类;然后,把代码中的类型参数都替换为具体的类。
序列化
Java对象在JVM运行时被创建、更新和销毁,当JVM退出时,对象也会随之销毁,即这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,我们常常需要将对象及其状态在多个应用之间传递、共享,或者将对象及其状态持久化,在其他地方重新读取被保存的对象及其状态继续进行处理。这就需要通过将Java对象序列化来实现。在使用Java序列化技术保存对象及其状态信息时,对象及其状态信息会被保存在一组字节数组中,在需要时再将这些字节数组反序列化为对象。注意,对象序列化保存的是对象的状态,即它的成员变量,因此类中的静态变量不会被序列化。对象序列化除了用于持久化对象,在RPC(远程过程调用)或者网络传输中也经常被使用。
Java序列化API的使用
Java序列化API为处理对象序列化提供了一个标准机制,具体的Java系列化需要注意以下事项。
◎ 类要实现序列化功能,只需实现java.io.Serializable接口即可。
◎ 序列化和反序列化必须保持序列化的ID一致,一般使用private static final long serialVersionUID定义序列化ID。
◎ 序列化并不保存静态变量。
◎ 在需要序列化父类变量时,父类也需要实现Serializable接口。
◎ 使用Transient关键字可以阻止该变量被序列化,在被反序列化后,transient变量的值被设为对应类型的初始值,例如,int类型变量的值是0,对象类型变量的值是null。
例如,通过implements Serializable实现了一个序列化的类。注意,transient修饰的属性和static修饰的静态属性不会被序列化。
import java.io.Serializable;
//通过实现Serializable接口定义可序列化的Worker类
public class Wroker implements Serializable {
//定义序列化的ID
private static final long serialVersionUID = 123456789L;
//name属性将被序列化
private String name;
//transient修饰的变量不会被序列化
private transient int salary;
//静态变量属于类信息,不属于对象的状态,因此不会被序列化
static int age =100;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
对象通过序列化后在网络上传输时,基于网络安全,我们可以在序列化前将一些敏感字段(用户名、密码、身份证号码)使用秘钥进行加密,在反序列化后再基于秘钥对数据进行解密。这样即使数据在网络中被劫持,由于缺少秘钥也无法对数据进行解析,这样可以在一定程度上保证序列化对象的数据安全。
序列化和反序列化
在Java生态中有很多优秀的序列化框架,比如arvo、protobuf、thrift、fastjson。我们也可以基于JDK原生的ObjectOutputStream和ObjectInputStream类实现对象进行序列化及反序列化,并调用其writeObject和readObject方法实现自定义序列化策略。具体的实现代码如下,代码通过文件流的方式将wroker对象的状态写入磁盘中,在需要使用的时候再以文件流的方式将其读取并反序列化成我们需要的对象及其状态数据。:
public static void main(String[] args) throws Exception {
//序列化数据到磁盘
FileOutputStream fos = new FileOutputStream("worker.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Wroker testObject = new Wroker();
testObject.setName("alex");
oos.writeObject(testObject);
oos.flush();
oos.close();
//反序列化磁盘数据并解析数据状态
FileInputStream fis = new FileInputStream("worker.out");
ObjectInputStream ois = new ObjectInputStream(fis);
Wroker deTest = (Wroker) ois.readObject();
System.out.println(deTest.getName());
}