Java 基础功底

Java 基础语法特性

首先了解并做好Java Web 开发环境配置(包含 JDK 的配置)是非常必要的。其中 CLASSPATH 的值开始必须包含 ".",否则用 javac 命令将会报错。

第一个 Java 程序,以做演示:

由于 Java 是面向对象的,因此首先创建一个 HelloWorld 类(需要创建 HelloWorld.java 文件,Java 类文件的命名规范是:和类名相同,否则也会报错):

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello World");
  }
}

在命令行中,编译运行如下:

程序解释:

javac 命令:以类文件名作为参数,用于将 Java 源程序编译生成 JVM class 字节码文件。

java 命令:以类名作为参数,用于执行 Java 类。

Java 修饰符

访问修饰符:

非访问修饰符:

 

Java 面向对象

1、继承(所有类的父类是 java.lang.Object 类,而且会默认继承 Object 类):

格式如下(extends 是继承关键字):

class Parent {
}
class Son extends Parent {
}

继承的特性

1、子类拥有父类的非 private 属性、非构造方法。

2、通过子类对父类进行扩展。

3、子类可以根据自身需求,重写父类的方法。

4、Java 只能单继承类。

5、继承耦合度较大(因此有面向接口编程,以及例如 Bean 容器等解耦的方法)。

继承相关的关键字

super:通过 super 关键字对父类成员的访问,用于引用当前对象的父类引用(特别的是 super() 是使用父类的构造方法)。

this:指向对象自己的引用。

final:(1)修饰类:定义为最终类,即此类不能被继承;(2)修饰方法:该方法不能被子类重写(如果类为 final,那么其所有方法都为 final);(3)修饰常量。

构造方法

父类的构造方法自然不能被子类继承,但是子类可以在自己的构造方法中,通过 super 关键字调用父类的构造方法。无参构造方法是一个类的默认构造。调用默认构造是没有必要的,因为如果没有写 super进行调用,系统会自动调用。

重写(Override)与重载(Overload)

(1)Override 是子类对父类的允许访问的方法进行重新实现,即返回值、方法名、形参都不改变(或者返回值类型兼容),但是方法的过程由子类自定义(外壳不变,核心重写)。由于外壳不变的原则,重写的方法不能抛出新的受检异常,或者比父类原本的方法申明更为宽泛的异常(如父类抛出受检异常 IOException,子类重写方法就不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常)。同理子类重写方法的访问修饰符 > 父类被重写方法的访问修饰符。

来个简单的意思,表示个意思:

package com.qfedu.MyBoot.controller;

public class Parent {
    
    protected void test() {
    }
    
}

package com.qfedu.MyBoot.controller;

public class Son extends Parent {

    @Override
    protected void test() {
    }

}

重写的规则

  • 访问权限不能比父类的被重写方法的访问权限更紧(例如父类的方法是 public,那么子类重写就不能声明为 protected)。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 构造方法不能被重写。
  • 若父、子类在同一个包下,那么子类可以重写父类的非 private 的非 final 方法;若父、子类不在同一包下,那么子类只能重写 public 、protected 的非 final 方法。

(2)Overload 是在同一个类中,方法名相同,形参列表不同(即参数类型、个数、顺序不同,与参数名无关),以及返回类型、访问修饰符可同可不同;此外还可以声明新的、更宽泛的受检异常。最常见的是构造方法的重载。

2、多态

多态的体现方式:重写;接口(作用:能力,约定,角色。扮演interface角色,获得interface的能力,就要遵守interface的约定);抽象类和抽象方法。

3、抽象类

在 OO 中,所有的对象都是由类创建,但并不意味着所有的类都能创建出对象,因为 OO 往往会抽象出一种没有包含能够代表具体对象的信息以及其具体行为的类,来作为具体类的样板以提高代码重用率,这样的类被专门声明为抽象类。也因此抽象类无法实例化对象,而是被具体类(要使用,要实例化,就不能再像爸爸一样抽象了)继承使用,对象的各种特征及行为由具体类实例化。通常在设计阶段决定是否设计抽象类(个人觉得,不自己写啥子框架,一般应该用不上)。

声明抽象类:在访问修饰符之后,class 关键字前,添加 abstract 关键字。

抽象方法:

声明抽象方法:在访问修饰符之后,返回值之前,添加 abstract。抽象类除了不能实例化、可以有抽象方法(没有给出方法体)之外,其他地方和普通类用法一样,因为抽象类可有可没有抽象方法。但是有了抽象方法的类必须定义为抽象类,抽象类的子类也应该实现抽象类未实现的抽象方法,否则其子类就该定义为抽象类(不然编译不能通过,如果用 IDE 一般也会有错误代码提示)。

举个例子(什么叫没有方法体):

public abstract class TestFoo {

    public abstract void test();
    
}

 4、接口

接口是 OO 中的抽象类型,是抽象方法的集合,因此无法实例化接口。类似于抽象类的使用,需要另一个类来实现接口中的抽象方法。接口的定义与类相似:

[访问修饰符] interface 接口名称 [extends 父接口] {
        // 声明变量
        // 抽象方法
}

接口与类的区别,以及接口特性:

  • 接口没有构造方法。
  • 接口没有成员变量,只有 static、final 变量,而且只能是 public static final(通常隐式声明)。
  • 接口的每一个方法都被隐式声明为 public abstract,而且只能是 public abstract,显式声明为其他会报错。
  • 接口不能含有静态代码块、静态方法。
  • 类之间只能单继承,而接口之间可以多继承。
  • 接口不是被继承,而是被类实现 implements,一个类可以实现多个接口。

用途特殊的一种接口 —— 标记接口:

标记接口是空接口,没有任何方法和属性,但是却可以表明一种特定的类型(可以对标记进行检测,然后处理)。

例如,java.awt.event.MouseListener 接口的父接口 java.util.EventListener(即各种 Listener 都必须用 EventListener 做标记):

package java.util;

public interface EventListener {
}

标记接口的作用:

  • 实现该接口的类将变成多态中的接口类型。
  • 如上 EventListener 可以作为各种 Listener 的直接、间接爸爸,那么只需要将 EventListener 选作标记,JVM 便可知道这些 Listener 接口都将用于某些代理方案。

5、Java 包(Package)

为了更好地组织类,Java 提供了包机制,相当于类的命名空间。下面具体列出包的作用:

  • 把功能相似、相同的类放在同一个包下,方便对类的查找和使用。  
  • 包名作为命名空间可以区分同名的类,避免命名冲突。
  • 限定了访问权:拥有包访问权限的类,才能访问包中类。

包语句应该在类文件的最开头,将文件中的所有类声明在包下(如果没有声明,则会放在一个无名的包中,即 default package)。包名与类文件的文件路径相对应,将路径以"."分割所得就是包名,其格式为:

package pkg1[.pkg2[.pkg3…]];

使用包中的类,则需要使用 import 语句引入(否则则需要使用类的完全限定名:包名+类名,以"."连接。如果是同一个包,则不必 import。亦或使用通配符"*"来引入包下所有类型)。import 语句应该在 package 语句之后,所有类定义之前,格式与 package 类似,如下:

import package1[.package2…].(classname|*);

值得注意的是,同一个类文件中,如果含有多个类型,编译时编译器会将不同的类型分别创建其对应的字节码文件,这样来管理,可以方便编译器和 JVM 找到工程中的所有类型。而编译器类的字节码以及一些资源文件等按照包编译到类路径下(class path:JVM 将"/classes"作为类路径)。详细描述参考菜鸟教程

Java 高级教程

Java 数据结构(容器)

Java 的工具包(java.util.*)提供了强大的数据结构,主要包括以下几种接口、类:

  • 枚举(Enumeration)
  • 位集合(BitSet)
  • 向量(动态数组类:Vector)
  • 栈(Stack)
  • 字典(Dictionary)
  • 哈希表(HashTable)
  • 属性(Properties)

不过以上是传统遗留集合(JDK 1.0),在 Java 2 中,引入了一种新的新的框架 —— 集合(Collection)。

枚举(Enumeration)

枚举接口本身不属于数据结构,但它在其他数据结构中使用很广,所以枚举也归在 Util 包中。枚举接口提供了一种从其他数据结构中依次取出元素直到取完为止的方式。这种方式与迭代器(Iterator)类似,不过传统的 Enumeration 基本已被其取代,尽管如此它还是使用在如 Vector、Properties 这些传统类的方法中等。关于 Enumeration 常用的方法如下:

  • boolean hasMoreElements();—— 用于测试此枚举中是否还含有更多元素。
  • Object nextElements();—— 如果 hasMoreElements() 返回 true,则可以执行 nextElements(),用于返回此枚举的下一个元素;否则执行则会抛出异常。

演示用例:

import java.util.Enumeration;
import java.util.Vector;

public class Tester {

    public static void main(String[] args) {
        Enumeration<String> days;
        Vector<String> dayNames = new Vector<>();
        dayNames.add("Sunday");
        dayNames.add("Monday");
        dayNames.add("Tuesday");
        dayNames.add("Wednesday");
        dayNames.add("Thursday");
        dayNames.add("Friday");
        dayNames.add("Saturday");
        days = dayNames.elements();
        while (days.hasMoreElements()){
            System.out.println(days.nextElement());  //在这种循环中,如果没用过nextElement(),游标就不会移动,将会一直指向当前元素,那么hasMoreElements()如果返回true,就会死循环
        }
    }

}

位集合(BitSet)

BitSet 是一种用来保存位值的特殊数组。数组大小动态变化。

向量(Vector)

Vector 是动态数组,在创建数组的时候可指定,可不指定大小,并且会根据数组添加、删除元素的操作动态更改数组大小。这与 ArrayList 相似却不同:

  • Vector 是同步访问的(若非同步,一般则不用于多线程的情况,否则需要自行实现访问同步)。
  • Vector 包含了许多传统方法?,这些方法不属于集合框架。

Vector 有4种构造:

Vector()    //创建一个默认的向量,大小为10
Vector(int size)    //创建指定大小的向量
Vector(int size, int incr)    //创建指定大小的向量,incr?增量?
Vector(Collection c)    //创建一个包含Collection的向量?

常用的方法(我不会像菜鸟教程那样列出一大堆,我认定的常用是我用过的,剩下的就是不常用的 —— 遇到了查 API):

int size();    //向量大小:元素个数
int capacity();   //返回容量?
void add(Object obj);   //插入元素到向量末尾
void addElement(Object obj);    //插入元素到向量末尾,大小+1
Object firstElement();   //返回第一个元素,索引为0
Object lastElement();    //返回最后一个元素
boolean contains(Object elem);    //如果向量包含指定元素,则返回true

演示用例:

import java.util.Vector;

public class Tester {

    public static void main(String[] args) {
        Vector<Integer> v = new Vector<>();
        v.add(1);
        v.add(2);
        v.add(3);
        for (Integer i : v) {
            System.out.println(i);
        }
    }

}

栈(Stack)

Stack 是 Vector 的子类,是后进先出(LIFO)的数据结构。Stack 只有一个默认构造函数,用于创造一个空的栈对象:

public Stack() {
}

常用函数:

Object push(Object Element);    //压栈
Object pop();   //弹栈:移除并返回栈顶元素。如果栈空,pop则会抛出异常。

演示用例:

import java.util.Stack;

public class Tester {

    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        System.out.println(stack);
        stack.push(1);
        stack.push(2);
        System.out.println(stack);
        stack.pop();
        System.out.println(stack);
    }

}

字典(Dictionary)

Dictionary 是一个抽象类,定义了键值对映射的数据结构 Dictionary<K, V>。这与 Map 接口相同,但 Dictionary 却已经过时了,在实际开发中,可以使用 Map 的实现类来存储键值对。

通过其子类来使用 Dictionary 这种数据结构,如 Hashtable。Dictionary 定义的抽象方法如下:

abstract public int size();   //返回此字典的条目(键值对)个数
abstract public boolean isEmpty();
abstract public Enumeration<K> keys();    //返回所有键组成的枚举
abstract public Enumeration<V> elements();    //返回所有值组成的枚举
abstract public V get(Object key);    //根据键映射返回值
abstract public V put(K key, V value);    //插入键值对到此字典
abstract public V remove(Object key);   //根据key删除指定的键值对

哈希表(HashTable)

Hashtable 是 Dictionary 的子类,哈希表 + 键值对:将键通过哈希处理得到的散列码,用作哈希表中值的索引。它与 HashMap 很相似,但是它支持同步。Hashtable 有四个构造方法:

Hashtable();    //默认构造方法
Hashtable(int size);    //创建指定大小的哈希表
Hashtable(int size, float fillRatio);   //指定大小和填充率:填充率介于0与1之间,它决定了哈希表在重新调整大小之前的充满程度
Hashtable(Map m);   //用Map的元素来创建哈希表,其容量为Map的两倍

常用方法(Dictionary 的方法就不说明了):

//暂时没有

演示用例:

import java.util.Enumeration;
import java.util.Hashtable;

public class Tester {

    public static void main(String[] args) {
        Hashtable<Integer, String> hash = new Hashtable<>();
        hash.put(1, "One");
        hash.put(2, "Two");
        Enumeration<Integer> key = hash.keys();
        while(key.hasMoreElements()) {
            System.out.println(hash.get(key.nextElement()));
        }
    }

}

属性(Properties)

Properties 是 Hashtable 的子类,常用于表示一个持久的属性集(即用于加载属性配置文件:.properties 文件。因为 Properties 有很多操作属性文件的 api),是 <String, String> 类型的键值对的集合。

首先 Properties 类有一个默认的属性列表(即一个 Properties 成员变量):

protected Properties defaults;

然后它有两个构造函数:

public Properties();    //默认构造函数:default成员变量为空
public Properties(Properties defaults);   //将default成员赋值为指定的Properties对象

除了 Dictionary 的方法之外,常用的方法有:

String getProperty(String key);    //用指定的键搜索获取属性值
String getProperty(String key, String defaultProperty);   //还提供了默认值(没找到)

演示用例(这个例子举得不好,和 HashTable 一样了,以后要换):

import java.util.Enumeration;
import java.util.Properties;

public class Tester {

    public static void main(String[] args) {
        Properties fruits = new Properties();
        fruits.put("apple", "red");
        fruits.put("peach", "pink");
        Enumeration<Object> fruitType = fruits.keys();
        while(fruitType.hasMoreElements()) {
            System.out.println(fruitType.nextElement());
        }
    }

}

Java 集合框架

以上 Java 实现的数据结构都是传统的,或者说经典的(像一些专门介绍数据结构的书上面都会提到的)。这些数据结构经典意义不可置疑,是非常有用的,但是它们缺少一个核心的、统一的主题(如使用 Vector 和使用 Properties 的方法自然是截然不同的)。因此 Java 2 重构了一些数据结构,实现了集合框架。一套集合框架的设计目标是:

  • 首先必须是高性能。
  • 不同的类型的集合的使用方式类似,具有高度的互操作性。
  • 对集合的扩展和适应很简单。

因此整个 Java 集合框架就围绕着一组标准接口而设计实现。可以直接使用这些接口的标准实现(第二点),如 LinkedList、HashSet、TreeSet 等。也可以通过这些接口实现自己的集合(第三点)。

Java 集合框架位于工具包中。Java 集合框架除了 Collection,还定义了 Map(也是存储键值对,类似于 Dictionary),Map 虽然不是 Collection,但却完全整合在集合框架中。其集合体系图如下:

集合框架的标准接口(上图最顶层的):

  • Collection 接口:最基本的集合接口,一个 Collection 代表一组 Object。Java 没有提供直接实现 Collection 的类,只提供了一些子接口(如 List、Set)及其实现类。
  • Map 接口:类似于 Dictionary,也是存储键值对。
  • Iterator 接口:先空着

上述接口主要的子接口:

Collection 子接口的特点(List 与 Set 区别常常在 Java 的考试中考到):

  • List 接口:1、有序(放进去的顺序,与取出的顺序相同,例如输出顺序与输入顺序相同)。2、元素可重复。—— 这与数组挺像,但不是,因为链表 LinkedList 也是 List 的一种。
  • Set 接口:1、无序。2、务必确保元素不可重复(为此常常有必要重写 hashCode()、equals() 方法)
    • SortedSet 接口(这一级是上一级的直接子接口):在 Set 的基础上,实现有序。

此外,由于结构上的差异,导致的查找、插入、删除操作的差异:

  • Set 由于无序,故检索效率低下。而增删效率较高,因为不会引起元素位置改变。
  • 和 Vector 一样(Vector 是 List 的一个实现类),List 可以动态增长。List 的另外两个实现类:ArrayList、LinkedList。ArrayList 底层是数组(可像数组般使用),访问效率高(Vector 是同步的,而 ArrayList 是不同的);LinkedList 底层是双向循环链表,增删效率高 —— 双方的优点反过来正好是对方的缺点。

Map 子接口:

  • Map.Entry 接口(这不是 Map 的子接口,而是 Map 的内部接口,为方便,在此统一介绍):用于描述 Map 的元素(键值对) —— 不过在真正的 Map 的实现类,如 HashMap,自然不是用 Map.Entry 来代表元素,而是用其实现类 ,如 HashMap.Node 类。
  • SortedMap 接口:在 Map 的基础上,实现有序(即使 Key 保持升序排列)。

上述接口的一些实现类(包括抽象类 —— 提供了接口的部分实现)

实现 Collection 接口的抽象类:

  • AbstractCollection:Collection 接口的实现类,实现了 Collection 大部分的抽象方法。

1、继承了抽象类 AbstractCollection 的 List 的抽象类:

  • AbstractList:继承自 AbstractCollection,在此基础上实现了大部分 List 接口的抽象方法。
  • AbstractSequentialList:继承自 AbstractList,实现了对元素的链式访问而不是随机访问。

实现了 List 相关抽象类的具体类:

  • ArrayList:动态数组的一种;非同步;增删效率低 —— 继承 AbstractList。
  • LinkedList:双向循环链表;非同步(没有同步方法);访问效率低 —— 继承 AbstractSequentialList。

如果多个线程访问同一个 List,如 LinkedList,要实现访问同步,就需要在创建 List 时,使用工具类 Collections 构造一个同步的 List,例如:

List<String> links = Collections.synchronizedList(new LinkedList<String>());

2、继承了抽象类 AbstractCollection 的 Set 抽象类:

  • AbstractSet:继承了 AbstractCollection,实现了部分 Set 接口的抽象方法。

实现了 Set 相关抽象类的具体类:

  • HashSet:集合 Set 的元素是键值对。底层使用哈希存储(散列存储);重写 Set<E> 中泛型类 E 的 hashCode()、equals() 方法 —— 重写 hashCode() 以确保添加元素时,不会添加内容重复的对象;重写 equals() 确保在比较时,能够正确比较内容。
  • TreeSet:其继承关系是 TreeSet —> NavigableSet —> SortedSet,所以是有序的 Set,即可以排序。其底层是一颗排序树,通过 Comparable 接口实现对元素的按照其自然顺序进行排序。

3、继承了抽象类 AbstractCollection 的 Map 抽象类:

  • AbstractMap:实现了大部分 Map 接口的抽象方法。

实现了 Map 相关抽象类的具体类:

  • HashMap:是一个散列表,类似于 Hashtable,使用哈希处理键,得到散列码 hashCode,用作哈希表中值的索引,访问速度很快。不过不支持线程同步。
  • TreeMap:类似于 TreeSet,继承关系是 TreeMap —> NavigableMap —> SortedMap,也是通过排序树存储、访问键值对。
  • WeakHashMap:与 HashMap 同级,使用的却是弱密钥的哈希表。
  • LinkedHashMap:继承于 HashMap,在此基础之上,使用元素的自然顺序进行排序。

遍历集合框架有三种方式:1、for、foreach(例如也可以将 ArrayList 转换成数组,然后遍历)。2、迭代器:Iterator、ListIterator(可以双向遍历) 接口。3、使用 Stream 接口提供的 API。

迭代器的使用(作为 Enumeration 的取代物,二者的用法还是挺像的)

演示用例(使用与不使用迭代器进行对比):

简单的情况 —— 遍历普通的集合:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Tester {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("One");
        list.add("Two");
        
        String[] array = new String[list.size()];
        list.toArray(array);
        for (String word : array) {
            System.out.println(word);
        }
        
        //上面这种方法纯粹是为了演示下怎么转换成数组。明明是可以直接foreach迭代的
        for (String word : list) {
            System.out.println(word);
        }
        
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

}

稍复杂的情况 —— 遍历如 Map 这样复杂的集合:

遍历 Map 时,迭代器显得格外方便 —— 利用 Map 的内部接口 Map.Entry 来同时访问 key、value,不必像 foreach 那样先取 key,再取 value:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Tester {

    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "One");
        map.put(2, "Two");
        
        for (Integer key : map.keySet()) {
            System.out.println(key + "-" + map.get(key));
        }
        
        //Map.Entry代表Map的元素,即一个键值对,因此用迭代器迭代Map.Entry
        Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
        while(iterator.hasNext()) {
            Map.Entry<Integer, String> entry = iterator.next();
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }
        
        //当然上面使用迭代器反而显得绕圈子,不如直接将Map的Set交给foreach,foreach怎样使用也不必关心。
        //更推荐下面的方法,尤其适用于容量大的情况
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }
    }

}

Java 泛型

泛型(generics)是 Java 5 引入的特性。Java 泛型还提供了编译时的类型安全检测机制(泛型限定)。

泛型,即实现类型参数化 —— 操作的数据类型被当做参数使用,使得可以对不同的类型进行统一处理。

泛型方法

定义泛型方法的规则:

  • 声明泛型方法,首先要声明类型参数(形如 <E>),类型参数部分在方法的修饰符之后,返回值之前。
  • 类型参数声明部分可以包含多个类型参数,用逗号 "," 隔开。
  • 类型参数只能是引用类型,不能是原始数据类型(但是没关系,用对应的数据包裹类就行了)。

演示用例(表示个意思,并没什么卵用):

public class Tester {

    public static void main(String[] args) {
        Tester test = new Tester();
        
        test.printAnyType(1);    // Integer:int 自动包装
        test.printAnyType("hello");
    }
    
    /*
     * 就像使用普通的参数一样,知道并遵守其使用规则,具体想干嘛请随意
     */
    public <E> void printAnyType(E anyTypeData) {
        System.out.println(anyTypeData);
    }

}

泛型类

泛型类的声明与普通类相似,不同的是需要在类名之后添加类型参数声明部分(除了位置,声明规则与泛型方法相同) —— 泛型接口亦然。

演示用例:

public class Tester<T> {

    public static void main(String[] args) {
        Tester<Integer> one = new Tester<>();
        one.printAnyType(1);
        
        Tester<String> two = new Tester<>();
        two.printAnyType("hello");
    }
    
    public void printAnyType(T anyTypeData) {
        System.out.println(anyTypeData);
    }

}

类型通配符

不必声明泛型方法,即可像在泛型方法中一样:一次接收一种任意类型的数据。但是类型通配的范围却比泛型类更小、更精准。

演示用例(我只见过这一种用法,想必这就是类型通配符与泛型的区别所在吧):

import java.util.ArrayList;
import java.util.List;

public class Tester {

    public static void main(String[] args) {
        Tester test = new Tester();
        
        List<String> stringList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();
        
        stringList.add("One");
        intList.add(1);
        
        test.myForeach(stringList);
        test.myForeach(intList);
    }
    
    public void myForeach(List<?> anyTypeData) {
        for (Object object : anyTypeData) {
            System.out.println(object);
        }
    }

}

泛型限定

以上的泛型、类型通配符在声明时都没有表达出,其父类是何许人也,其子类又是哪一种。但是这些都是可以指定的,通过泛型限定可以指定只接收哪些类型:

  • 指定上界(父类、父接口):extends、implements,如 <X extends Y>。
  • 指定下界:super,如 <X super Y>。

Java 序列化

对象序列化机制把对象表示为一个字节序列,然后保存到文件中,是数据持久化的一种方式。一个字节序列包含了一个对象自身的数据、对象类型的有关信息、对象中数据的存储类型。

将序列化对象写入文件中后,只要文件在,读取文件并将得到的对象反序列化,即可得到原来对象的数据。

对象序列化并发到输出流依靠 ObjectOutputStream.writeObject 方法完成,接收输入流并反序列化对象则依靠 ObjectInputStream.readObject 方法完成。但是这两个类的方法不会保存和读取对象中用 transient 和 static 修饰的变量,因此可以选择性的存储信息。从对象本身的角度而言,对象的类必须要实现 Serializable 接口,其对象才具备序列化的能力。

演示用例:

新建一个类,用以序列化操作:

import java.io.Serializable;

public class Person implements Serializable {

    /**
     * generated serial ID
     */
    private static final long serialVersionUID = 3877523590249062136L;
    private String name;
    private String address;
    
    @Override
    public String toString() {
        return name + " live in " + address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    
}

序列化以及反序列化:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Tester {

    public static void main(String[] args) {
        Person person = new Person();
        person.setName("shutao");
        person.setAddress("cd");
        
        Person recruit = null;
        
        try {
            FileOutputStream fileOut = new FileOutputStream("E:/person.txt");//按照标准,序列化到文件,文件名后缀是.ser。但也有人用.dat,一般没人用.txt
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(person);
            out.close();
            fileOut.close();
            
            FileInputStream fileIn = new FileInputStream("E:/person.txt");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            recruit = (Person) in.readObject();
            in.close();
            fileIn.close();
        } catch (IOException | ClassNotFoundException e) {
            //ClassNotFoundException就说明只有你的环境中存在对应的序列化对象的类时,才能成功反序列化。不是说随便哪个地方拿个.ser就能反序列化的
            e.printStackTrace();
        }
        System.out.println(recruit);
    }

}

 

posted @ 2017-06-28 15:30  不抛弃,不放弃  阅读(313)  评论(0编辑  收藏  举报