java基础_Arrays类_String,StringBuffer与StringBuilder的区别_浅拷贝与深拷贝_Throwable异常_序列化

Arrays类

 

 

 

 

 

 

 

 

String,StringBuffer与StringBuilder的区别??

String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
 简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
 而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接(字符串常量进行拼接),所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的
 String S1 = “This is only a” + “ simple” + “ test”;
 StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
 你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
 String S1 = “This is only a” + “ simple” + “test”; 其实就是:
 String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;
这时候 JVM 会规规矩矩的按照原来的方式去做

在大部分情况下 StringBuffer > String
StringBuffer
Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。
在大部分情况下 StringBuilder > StringBuffer
java.lang.StringBuilde
java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

 

浅拷贝与深拷贝

 

一:什么是浅拷贝和深拷贝
浅拷贝:原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。也就是说:在浅拷贝中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象(赋值的只是对象的地址)并没有复制。
深拷贝:无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。也就是说:在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
实现对象克隆有两种方式:

实现Cloneable接口并重写Object类中的clone()方法。
实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
二:浅拷贝
浅拷贝只需要实现Cloneable,在需要克隆的地方调用clone方法即可,实现起来比较简单。

public class Inner implements Cloneable {

}
public class Outer implements Cloneable {
private Inner inner;

public Outer(Inner inner) {
this.inner = inner;
}

public Inner getInner() {
return inner;
}
}
public class MyTest {

public static void main(String[] args) {
Inner inner = new Inner();
Outer outer = new Outer(inner);
try {
Outer cloneOuter = (Outer) outer.clone();
System.out.println("Outer:" + outer + " cloneOuter:" + cloneOuter);
System.out.println("Inner:" + outer.getInner() + " cloneInner:" + cloneOuter.getInner());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

输出:

Outer:Outer@4554617c cloneOuter:Outer@74a14482
Inner:Inner@1540e19d cloneInner:Inner@1540e19d

从输出结果可以看出,浅拷贝只是对当前对象Outer创建了一个新的对象,里面的引用类型Inner还是原对象的地址,并没有重新创建一个对象。

三:深拷贝
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。

深拷贝实现方式一:通过覆盖Object的clone方法实现

此种方法通过重写Object中的clone方法,并在其内部又对引用类型拷贝来实现的深拷贝,如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。

public class Inner implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

public class Outer implements Cloneable {
private Inner inner;

public Outer(Inner inner) {
this.inner = inner;
}

@Override
protected Object clone() throws CloneNotSupportedException {
Outer outer = (Outer) super.clone();
outer.inner = (Inner) outer.inner.clone();
return outer;
}

public Inner getInner() {
return inner;
}
}
public class MyTest {

public static void main(String[] args) {
Inner inner = new Inner();
Outer outer = new Outer(inner);
try {
Outer cloneOuter = (Outer) outer.clone();
System.out.println("Outer:" + outer + " cloneOuter:" + cloneOuter);
System.out.println("Inner:" + outer.getInner() + " cloneInner:" + cloneOuter.getInner());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

运行输出:

Outer:Outer@4554617c cloneOuter:Outer@74a14482
Inner:Inner@1540e19d cloneInner:Inner@677327b6

深拷贝实现方式二:通过序列化方式实现

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

public class Inner implements Serializable {

}

public class Outer implements Serializable {
private Inner inner;

public Outer(Inner inner) {
this.inner = inner;
}

public Inner getInner() {
return inner;
}
}
public class MyTest {

public static void main(String[] args) throws IOException, ClassNotFoundException {
Inner inner = new Inner();
Outer outer = new Outer(inner);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(outer);

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Outer streamOuter = (Outer) objectInputStream.readObject();
System.out.println("Outer:" + outer + " streamOuter:" + streamOuter);
System.out.println("Inner:" + outer.getInner() + " streamInner:" + streamOuter.getInner());

}
}

运行输出:

Outer:Outer@6d6f6e28 streamOuter:Outer@4b67cf4d
Inner:Inner@135fbaa4 streamInner:Inner@7ea987ac

 

浅拷贝只要实现clone接口,深拷贝则是实现clone接口并且重写clone方法,或者实现序列化接口实现。

 

Throwable异常

一:关于异常
JAVA异常是在java程序运行的时候遇到非正常的情况而创建的对,它封装了异常信息。java异常的根类为java.lang.Throwable,整个类有两个直接子类java.lang.Error和java.lang.Exception。

Error是程序本身无法恢复的严重错误,一般是虚拟机或者系统运行出现错误,和程序无关。Exception则表示可以被程序捕获并处理的异常错误。

JVM用方法调用栈来跟踪每个线程中一系列的方法调用过程,栈是线程私有的,每一个线程都有一个独立的方法调用栈,该栈保存了每个调用方法的信息。当一个新方法被调用的时候,JVM会把描述该方法的栈结构置入栈顶,位于栈顶的方法为正在执行的方法。当一个JAVA方法正常执行完毕,JVM会从调用栈中弹出该方法的栈结构,然后继续处理前一个方法。如果java方法在执行代码的过程中抛出异常,JVM必须找到能捕获异常的catch块代码,它首先查看当前方法是否存在这样的catch代码块,如果存在就执行该 catch代码块,否则JVM会调用栈中弹处该方法的栈结构,继续到前一个方法中查找合适的catch代码块。最后如果JVM向上追到了当前线程调用的第一个方法(如果是主线程就是main方法),仍然没有找到该异常处理的代码块,该线程就会异常终止。如果该线程是主线程,应用程序也随之终止,此时JVM将把异常直接抛给用户,在用户终端上会看到原始的异常信息。

Throwable
Java异常体系中根类,有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。

Error(错误)
是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

Exception(异常)
是由于程序本身引起的异常,分为受检查异常和Runtime异常,RuntimeException直接继承于Exception,本身以及其子类异常均表示提前无法预知的异常。除了RuntimeException及其子类,剩余的都是受检查异常,编译器在编译期强制我们添加try catch去捕获,否则编译不通过。

二、Throwable源码分析

1、成员变量

    private transient Object backtrace;
    //异常信息
    private String detailMessage;
    //当前异常是由哪个Throwable所引起的
    private Throwable cause = this;
    //引起异常的堆栈跟踪信息
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;

backtrace:这个变量由native方法赋值,用来保存栈信息的轨迹
detailMessage:这个变量是描述异常信息,比如new Myexception(“My Exception”),记录的就是我们传进去的描述此异常的描述信息 My Exception。
cause :记录当前异常是由哪个异常所引起的,默认是this,可通过构造器自定义。可以通过initCase方法进行修改

public synchronized Throwable initCause(Throwable cause) {
        if (this.cause != this)
            throw new IllegalStateException("Can't overwrite cause with " +
                                            Objects.toString(cause, "a null"), this);
        if (cause == this)
            throw new IllegalArgumentException("Self-causation not permitted", this);
        this.cause = cause;
        return this;
    }

可以看到case只能被修改一次,当发现当前case已经被修改,则会抛出IllegalStateException。默认case=this,如果再次修改case为this也是不允许的。
case一般这样使用

    try {
            Integer.valueOf("a");
        }catch (NumberFormatException e){
            throw new MyException(e);
        }
  • stackTrace 记录当前异常堆栈信息,数组中每一个StackTraceElement表示当前当前方法调用的一个栈帧,表示一次方法调用。StackTraceElement中保存的有当前方法的类名、方法名、文件名、行号信息。
public final class StackTraceElement implements java.io.Serializable {
    private String declaringClass;
    private String methodName;
    private String fileName;
    private int lineNumber;
    }

2、构造函数

public Throwable() {
        fillInStackTrace();
    }
public Throwable(String message) {
        fillInStackTrace();
        detailMessage = message;
    }
public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }
protected Throwable(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
        if (writableStackTrace) {
            fillInStackTrace();
        } else {
            stackTrace = null;
        }
        detailMessage = message;
        this.cause = cause;
        if (!enableSuppression)
            suppressedExceptions = null;
    }

Throwable提供了4个public构造器和1个protected构造器(该构造器由JDK1.7引入)。4个public构造器共同点就是都调用了fillInStackTrace方法。

3、fillInStackTrace()方法

public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }

private native Throwable fillInStackTrace(int dummy);

fillInStackTrace会首先判断stackTrace是不是为null,如果不为null则会调用native方法fillInStackTrace将当前线程的栈帧信息记录到此Throwable中。那么什么时候为null呢,答案是上面的protected构造器可以指定writableStackTrace为false,这样stackTrace就为null了,就不会调用fillInStackTrace获取堆栈信息。

fillInStackTrace将当前线程的栈帧信息记录到此Throwable中为了理解我们来看一个例子

正常情况下我们抛出RuntimeException,异常打印是带有异常堆栈信息的

public class MyException extends RuntimeException {
    public static void method1(){
        System.out.println("method1");
        method2();
    }
    public static void method2(){
        System.out.println("method2");
        method3();
    }
    public static void method3(){
        System.out.println("method3");
        method4();
    }
    public static void method4(){
        throw new MyException();
    }
    public static void main(String[] args) {
        method1();
    }
}

运行结果:

method1
method2
method3
Exception in thread "main" MyException
    at MyException.method4(MyException.java:20)
    at MyException.method3(MyException.java:17)
    at MyException.method2(MyException.java:13)
    at MyException.method1(MyException.java:9)
    at MyException.main(MyException.java:24)

我们来重写fillInStackTrace方法,来看一下运行结果

public class MyException extends RuntimeException {
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
    public static void method1(){
        System.out.println("method1");
        method2();
    }
    public static void method2(){
        System.out.println("method2");
        method3();
    }
    public static void method3(){
        System.out.println("method3");
        method4();
    }
    public static void method4(){
        throw new MyException();
    }
    public static void main(String[] args) {
        method1();
    }
}

输出:

method1
method2
method3
Exception in thread "main" MyException

从例子可以看到fillInStackTrace作用是将当前线程的栈帧信息记录到此Throwable中。

4、addSuppressed()和getSuppressed()方法

public final synchronized void addSuppressed(Throwable exception) {
        if (exception == this)
            throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);

        if (exception == null)
            throw new NullPointerException(NULL_CAUSE_MESSAGE);

        if (suppressedExceptions == null) // Suppressed exceptions not recorded
            return;

        if (suppressedExceptions == SUPPRESSED_SENTINEL)
            suppressedExceptions = new ArrayList<>(1);

        suppressedExceptions.add(exception);
    }
public final synchronized Throwable[] getSuppressed() {
        if (suppressedExceptions == SUPPRESSED_SENTINEL ||
            suppressedExceptions == null)
            return EMPTY_THROWABLE_ARRAY;
        else
            return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
    }
如果try中抛出了异常,在执行流程转移到方法栈上一层之前,finally语句块会执行,但是,如果在finally语句块中又抛出了一个异常,那么这个异常会覆盖掉之前抛出的异常,这点很像finally中return的覆盖。比如下面这个例子:
public class MyTest {
    public static void main(String[] args) {
        try {
            Integer.valueOf("one");
        } catch (NumberFormatException e) {
            throw new RuntimeException("One", e);
        } finally {
            try {
                Integer.valueOf("two");
            } catch (NumberFormatException e) {
                throw new RuntimeException("Two", e);
            }
        }
    }
}

输出:

Exception in thread "main" java.lang.RuntimeException: Two
    at MyTest.main(MyTest.java:12)
Caused by: java.lang.NumberFormatException: For input string: "two"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.valueOf(Integer.java:766)
    at MyTest.main(MyTest.java:10)

Throwable对象提供了addSupperssed和getSupperssed方法,允许把finally语句块中产生的异常通过addSupperssed方法添加到try语句产生的异常中。

public class MyTest {
    public static void main(String[] args) {
        RuntimeException exception1 = null;
        try {
            Integer.valueOf("one");
        } catch (NumberFormatException e) {
            exception1 = new RuntimeException("One", e);
            throw exception1;
        } finally {
            try {
                Integer.valueOf("two");
            } catch (NumberFormatException e) {
                RuntimeException exception2 = new RuntimeException("Two", e);
                exception1.addSuppressed(exception2);
                throw exception1;
            }
        }
    }
}

输出:

Exception in thread "main" java.lang.RuntimeException: One
    at MyTest.main(MyTest.java:8)
    Suppressed: java.lang.RuntimeException: Two
        at MyTest.main(MyTest.java:14)
    Caused by: java.lang.NumberFormatException: For input string: "two"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:580)
        at java.lang.Integer.valueOf(Integer.java:766)
        at MyTest.main(MyTest.java:12)
Caused by: java.lang.NumberFormatException: For input string: "one"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.valueOf(Integer.java:766)
    at MyTest.main(MyTest.java:6)

5、printStackTrace()方法

printStackTrace()方法分四个方面打印出当前异常信息

  1. 打印出当前异常的详细信息
  2. 打印出异常堆栈中的栈帧信息
  3. 打印出support异常信息
  4. 递归打印出引起当前异常的异常信息
public void printStackTrace() {
        printStackTrace(System.err);
    }
private void printStackTrace(PrintStreamOrWriter s) {
        // Guard against malicious overrides of Throwable.equals by
        // using a Set with identity equality semantics.
        Set<Throwable> dejaVu =
            Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        dejaVu.add(this);

        synchronized (s.lock()) {
            // 打印当前异常的详细信息
            s.println(this);
            // 打印当前堆栈中的栈帧信息
            StackTraceElement[] trace = getOurStackTrace();
            for (StackTraceElement traceElement : trace)
                s.println("\tat " + traceElement);

            // 打印suppressed exceptions
            for (Throwable se : getSuppressed())
                se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);

            // 递归打印出引起当前异常的异常信息
            Throwable ourCause = getCause();
            if (ourCause != null)
                ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
        }
    }

序列化

一:什么是序列化
序列化是将Java对象相关的类信息、属性、属性值等信息以一定的格式转换为字节流,反序列化时再将字节流表示的信息来构建出Java对象。过程中涉及到其它对象的引用对象也要参与序列化。

二:序列化的应用场景
永久性保存对象,保存对象的字节序列到本地文件或者数据库中。
通过序列化以字节流的形式使对象在网络中进行传递和接收。
通过序列化在进程间传递对象。
三:序列化的实现方式
Java中实现序列化的方式有两种:1、实现Serializable接口。2、实现Externalizable接口。

1、实现Serializable接口

public class People implements Serializable {
    private String name;
    private int age;
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge());
    }
}

运行输出:name:bobo age:26

2、实现Externalizable接口

用Externalizable接口实现序列化时需要注意两点:

必须要提供公有的无参构造函数,否则会报InvalidClassException。
必须要在writeExternal和readExternal中自己去实现序列化过程。

public class People implements Externalizable {
    private String name;
    private int age;
    public People() {

    }
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge());
    }
}

运行输出:name:bobo age:26

四:序列化核心点
1、serialVersionUID的作用

在序列化操作时,经常会看到实现了Serializable接口的类会存在一个serialVersionUID属性,并且它是一个固定数值的静态变量。它主要用于验证版本的一致性。每个实现Serializable接口的类都拥有这么一个ID,在序列化的时候会一起被写入流中。在反序列化的时候就会被拿出来跟当前类的serialVersionUID值进行比较,两者相同则说明版本一致,可以反序列化成功,如果不通则反序列化失败。
两种serialVersionUID方式:

自己定义,比如比如:private static final long serialVersionUID = 1234567L。
如果没定义,JDK会帮我们生成,生成规则是利用类名、类修饰符、接口名、字段、静态初始化信息、构造函数信息、方法名、方法修饰符、方法签名等组成的信息,经过SHA算法生成serialVersionUID 值。
2、Transient 关键字作用

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到流中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

public class People implements Serializable {
    private transient String name;
    private transient int age;
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge());
    }
}

运行输出:name:null age:0

3、静态变量不会被序列化

这个也很好理解,我们序列化是针对对象的,而静态变量是属于类的。下面看一个例子:

public class People implements Serializable {
    private static int age = 10;
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        age = 26;//改变静态变量的值
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println(" age:" + age);
    }
}

运行输出:age:26,age的值改变了,证明age是没有被序列化的。

4、父类的序列化

如果一个子类实现了Serializable 接口而父类没有实现该接口,则在序列化子类时,子类的属性状态会被写入而父类的属性状态不会被写入。所以如果想要父类属性状态也一起参与序列化,就要让它也实现Serializable 接口。

如果父类未实现Serializable 接口则反序列化生成的对象会再次调用父类的构造函数,以此来完成对父类的初始化,所以父类的属性初始值一般都是类型的默认值。

public class Animal {
    protected int num;
    protected String color;
    public int getNum() {
        return num;
    }
    public String getColor() {
        return color;
    }
}

public class People extends Animal implements Serializable {
    private String name;
    private int age;
    public People(String name, int age, int num, String color) {
        this.name = name;
        this.age = age;
        this.num = num;
        this.color = color;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26,10,"red");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge());
        System.out.println("num:" + serialPeople.getNum() + " color:" + serialPeople.getColor());
    }
}

运行输出:

name:bobo age:26
num:0 color:null

5、被引用的类没有实现Serializable 接口则序列化不成功

序列化对象里面包含的任何引用类型的对象的类都要实现Serializable 接口,否则抛出java.io.NotSerializableException

public class Brother {
    protected int num;
    protected String color;
    public Brother(int num, String color) {
        this.num = num;
        this.color = color;
    }
    public int getNum() {
        return num;
    }
    public String getColor() {
        return color;
    }
}
public class People implements Serializable {
    private String name;
    private int age;
    private Brother brother;
    public People(String name, int age, Brother brother) {
        this.name = name;
        this.age = age;
        this.brother = brother;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Brother brother = new Brother(2, "red");
        People people = new People("bobo", 26, brother);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge() + " brother:" + brother);
    }
}

运行输出:

Exception in thread "main" java.io.NotSerializableException: Brother
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at People.main(People.java:23)

6、自定义序列化过程

如果默认的序列化过程不能满足需求,我们也可以自定义整个序列化过程。这时候我们只需要在需要序列化的类中定义私有的writeObject方法和readObject方法即可。

public class People implements Serializable {
    private String name;
    private int age;
    private transient String color;
    public People(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public String getColor() {
        return color;
    }
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();//默认序列化过程
        color = (String) s.readObject();
    }
    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();//默认序列化过程
        s.writeObject(color);
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26, "red");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge() + " color:" + serialPeople.getColor());
    }
}

运行输出:

name:bobo age:26 color:red

在color属性前加了transient 关键字,意思是不让color实现序列化,但是下面又自定义序列化过程在writeObject和readObject里面实现color的序列化,所以color属性是实现了序列化的。

7、为什么实现readObject()方法和writeObject()方法就可以自定义序列化过程?

readObject()和writeObject() 既不存在于java.lang.Object,也没有在Serializable中声明。那么ObjectOutputStream如何使用它们的呢?原来,ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为private以至于供ObjectOutputStream来使用。

下面我们以ObjectInputStream来源码分析一下:

ObjectInputStream的readObject()方法—>调用到readObject0(boolean unshared)方法—>readOrdinaryObject(boolean unshared)方法—>readSerialData(Object obj, ObjectStreamClass desc)方法---->ObjectStreamClass类的invokeReadObject(Object obj, ObjectInputStream in)方法:

void invokeReadObject(Object obj, ObjectInputStream in)
        throws ClassNotFoundException, IOException,
               UnsupportedOperationException
    {
        if (readObjectMethod != null) {
            try {
                readObjectMethod.invoke(obj, new Object[]{ in });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ClassNotFoundException) {
                    throw (ClassNotFoundException) th;
                } else if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

执行readObjectMethod.invoke(obj, new Object[]{ in }),通过反射的方式调用我们类中定义的readObject的私有方法。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章