基础

JDK、JDK、JRE的关系

在这里插入图片描述

Java基本数据类型

在这里插入图片描述

final作用

final修饰 解释
不可以被继承
方法 不能被重写
变量 不能被改变,不可变值的是变量的引用,指向的内容可以改变

final finally finalize

区别 描述
final 如上解释
finally 一般作用在try-catch代码块中,一般用来存放一些关闭资源的代码
finalize 属于Object类的一个方法,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。

static作用

static作用
1 变量或者方法是独立于该类的任何对象,被类的实例对象所共享
2 该类被第一次加载的时候,就会去加载被static修饰的部分,但只有在类第一次使用时才进行初始化
3 在类加载的时候分配空间,以后创建类对象的时候不会重新分配
4 修饰的变量或者方法是优先于对象存在的

面向对象、面向过程

区别 优点 缺点
面向对象 易维护、易复用、易扩展 性能比面向过程低
面向过程 能比面向对象高,因为类调用时需要实例化,开销比较大 没有面向对象易维护、易复用、易扩展

面向对象三大特征

面向对象三大特征 解释
封装 隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性
继承 通过使用继承可以提高代码复用性。继承是多态的前提
多态 可以指向子类或具体实现类的实例对象。提高了程序的拓展性

String、StringBuffer、StringBuilder

String StringBuffer StringBuilder
可变性 不可变 可变 可变
安全性 安全,因为final 安全,因为加锁 不安全
适用 少量操作 多线程+大量操作 单线程+大量操作

Int和Integer的区别

// 问题1:Integer和int比较
Integer a = new Integer(3);
Integer b = 3;
System.out.println(a == b);// false,引用类型和值类型不能比较
Integer d = new Integer(3);
System.out.println(a == d); // false,两个引用类型用==不能比较
int c = 3;
System.out.println(c == d); // true,Integer遇到int比较,Integer会拆箱成int做值比较
System.out.println("-------");

Integer底层提前缓存好了[-128,127]的值,所以创建两个范围的对象时,地址是一样的

// 问题2:Integer值返回缓存
Integer f1 = 100;
Integer f2 = 100;
System.out.println(f1 == f2);// true
Integer f3 = 129;
Integer f4 = 129;
System.out.println(f3 == f4);
System.out.println("-------");// false

原因:当一个Integer对象赋给int值的使用,调用Integer的valueOf方法

  public static Integer valueOf(int i) {
      // i在[-128,127]时,就会自动去引用常量池中的Integer对象,不会new新的
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

Equals、==、hashCode区别

== equals hashCode
基本数据类型用,比较的是首地址值 引用类型用,比较是内容值 对象在内存中的地址,算出对象的哈希码值,并将其返回,重写equals方法,必须重写hashCode,因为集合类是通过HashCode判断重复的

序列化类中有一个不可序列化的对象

给该类设置关键字transiient告诉JDK不可被序列化

元注解

Java中使用返回值类型@interface表示该类是一个注解配置类,注解配置不能使用class、interface、abstract修饰

// 自定义注解,以下只是简单的演示。实际开发注解还需要一个注解处理器,自行百度学习
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface FruitProvider {
    public int id() default -1;

    public String name() default "";

    public String address() default "";
}
public class Apple {
    // 使用自定义注解
    @FruitProvider(id = 1, name = "红富士", address = "北京市")
    private String appleProvider;

    public String getAppleProvider() {
        return appleProvider;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }

}

四大元注解:目标、保留、文档、继承

@Target:说明注解对象修饰范围

在这里插入图片描述

@Retention:该注解保留时长

  • source :在源文件中有效, 源文件中就保留
  • class:字节码文件中有效, 即在Class文件中被保留
  • runtime:在运行时有效,即在运行时被保留

@Documented:表示被Javadoc文档工具化,只是一个标记注解,没有成员

@Inherited:表示该类型是被继承的,表名该注解类可以作用于其子类

Java的面向对象

名称 概念
封装 将事物封装成一个类,减少耦合,隐藏细节。保留特定接口和外部联系
继承 从已知的类中派生出一个新的类,可以通过覆盖/重写增强功能
多态 本质是一个程序中存在多个同名的不同方法

封装:将事物封装成一个类,减少耦合,隐藏细节。保留特定接口和外部联系

继承:从已知的类中派生出一个新的类,可以通过覆盖/重写增强功能

  • Java中类的初始化顺序:
    • 父类静态成员变量、静态代码块;子类静态成员变量、静态代码块
    • 父类普通成员变量和代码块,再执行父类构造方法
    • 子类普通成员变量和代码块,再执行父类构造方法
  • 子类特点:
    • 父类非private的属性和方法
    • 添加自己的方法和属性,对父类进行扩展
    • 重新定义父类的方法=方法的覆盖/重写

多态:本质是一个程序中存在多个同名的不同方法

  • 子类的覆盖实现

  • 方法的重载实现

  • 子类作为父类对象使用

什么方法重载、方法重写?

重载(overload):一个类中存在多个同名的不同方法,这些方法的参数个数、顺序和类型不同均可以构成方法重载

重写(override):子类对父类非私有方法的重新编写,涉及写的就会有子父类

如果只有方法返回值不同,可以构成重载吗?

​ 不可以,因为我们调用某个方法,并不关心返回值。编译器根据方法名和参数无法确定我们调用的是那个方法。

Java中有goto关键字吗

goto和const是Java中的保留字,现在未使用,未使用的原因是保证程序的可读性,并且避免使用beak+lebel带标签的语句

跳出循环方式1 跳出循环方式2 跳出循环方式3
break+label,不推荐使用 flag+break throw new 抛出异常

抽象类和接口的

相同点 不同点
都不能直接实例化。 设计理念不同:抽象类是对对象/类的抽象,接口是对行为的抽象
都可以有默认的实现方法(JDK8以后才有的) 抽象类只能单一继承,接口可以多重继承;抽象类可以有构造器,接口没有
都可以不需要实现类或者继承类去实现所有方法 抽象类中的抽象方法可以用public/protected/default abstract修饰;抽象类中的变量可以在子类中重新定义并赋值
如果要实例化,抽象类变量必须实现所有抽象方法,接口变量必须实现所有接口未实现的方法 接口的方法默认修饰符是public abstract,可以加statci关键字;接口中的变量默认是public static final,且必须给初值,在实现类中不能被重新定义和赋值

接口和抽象类该如何选择?

当我们仅仅只需要定义一些抽象方法而不需要额外的具体方法/变量的时候,用接口;反之,考虑抽象类

接口的普通方法、default修饰方法:

public interface MyInterface {
    // 接口的普通方法只能等待实现类实现,不能具体定义
    void test();
    // 但是JDK8以后,接口可以default声明,然后具体定义
    default void say(String message) {
        System.out.println("hello:"+message);
    }
}
public class MyInterfaceImpl implements MyInterface {
    // 实现接口里的抽象方法
    @Override
    public void test() {
        System.out.println("test被执行");
    }

    // 当然也可以重写say方法
    public static void main(String[] args) {
        MyInterfaceImpl client = new MyInterfaceImpl();
        client.test();
        client.say("World");
    }
}

执行结果:

test被执行
hello:World

如果实现类实现了两个接口,两个接口都有相同的(default)默认方法名,那么该方法重写会报错

解决办法:

  • 实现类重写多个多个接口的默认方法
  • 手动指定重写哪个接口的默认方法
public interface MyInterface {
    void test();
    default void say(String message) {
        System.out.println("hello:"+message);
    }
}
public interface MyInterface1 {
    default void say(String message) {
        System.out.println("hello1:" + message);
    }
}
public class MyInterfaceImpl1 implements MyInterface, MyInterface1 {
    @Override
    public void test() {
        System.out.println("test是普通方法被重写");
    }

    @Override
    public void say(String message) {
        // 方法一:System.out.println("实现类重写多个接口相同的默认方法:" + message);
        // 方法二:指定重写哪个接口的默认方法
        MyInterface.super.say(message);
    }

    public static void main(String[] args) {
        MyInterfaceImpl1 client = new MyInterfaceImpl1();
        client.say("好的");
    }
}

执行结果:

实现类重写多个接口相同的默认方法:hello+好的

浅拷贝和深拷贝

浅拷贝 深拷贝
被复制的对象的所有变量都含有与原来对象相同的值,对拷贝后对象的引用依然指向原来的对象 不仅复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值

创建对象的方式

创建对象方式 是否调用构造器
new + 类名
Class.newInstance
Constructor.newInstance
Clone
反序列化

值传递和引用传递

值传递:传递是一个对象副本,即使副本改变,也不会影响原对象

引用传递:传递不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上

public class Test {
    public static void main(String[] args) {
        int a = 1;
        // 基本数据类型:值传递,原值不会变
        change(a);
        System.out.println(a);
    }

    private static void change(int num) {
        num++;
    }
}
public class Test1 {
    public static void main(String[] args) {
        // 以下2个是引用传递,会改变原值
        StringBuilder hello1 = new StringBuilder("hello1");
        StringBuffer hello2 = new StringBuffer("hello2");
        // String存放在常量池,虽然是引用传递,但不会改变原值
        String hello3 = new String("hello3");
        change1(hello1);
        change2(hello2);
        change3(hello3);
        System.out.println(hello1);
        System.out.println(hello2);
        System.out.println(hello3);
    }

    private static void change3(String str) {
        str += " world";
    }

    private static void change1(StringBuilder str) {
        str.append(" world");
    }

    private static void change2(StringBuffer str) {
        str.append(" world");
    }
}
public class Test3 {
    public static void main(String[] args) {
        StringBuffer hello = new StringBuffer("hello");
        System.out.println("before:" + hello);
        changeData(hello);
        // 前后值:都没有发生改变
        // 因为changeData中str形参重新执行了str1,与原值hello无关了
        System.out.println("after:" + hello);
    }

    private static void changeData(StringBuffer str) {
        StringBuffer str1 = new StringBuffer("Hi");
        str = str1;
        str1.append("world");
    }
}
public class PassTest {
    public static void main(String[] args) {
        int i = 1;
        String str = "hello";
        Integer num = 200;
        int[] arr = {1, 2, 3, 4, 5};
        MyData my = new MyData();
        change(i, str, num, arr, my);
        /*
        结果:传值还是传引用?
            i = 1 传值。基本数据类型不会变
            str = hello 传常量池地址。字符串不变
            num = 200,传堆中的地址。原包装类不变,和字符串一样
            arr = [2, 2, 3, 4, 5] 传堆中数组的首地址。发生了改变
            my.a = 11,传堆中地址,资源类变量发生改变。资源类中的变量,会在堆中生成一个实例
         */
        System.out.println("i = " + i);
        System.out.println("str = " + str);
        System.out.println("num = " + num);
        System.out.println("arr = " + Arrays.toString(arr));
        System.out.println("my.a = " + my.a);
    }

    public static void change(int j, String s, Integer num, int[] arr, MyData myData) {
        j += 1;
        s += "world";
        num += 1;
        arr[0] += 1;
        myData.a += 1;
    }
}

class MyData {
    int a = 10;
}

结果:

  • 基本数据类型是值传递,不会改变原值
  • String和包装类是引用传递,但不会改变原值,因为形参指向了另一个新生成的对象,原值不变
  • 数组和自定义类时引用传递,会改变原值,因为数组是连续地址空间,没有在堆中新生成实例;自定义类中的成员变量分配在堆中,也没有重新生成实例
i = 1 // 传值。基本数据类型不会变
str = hello // 传常量池地址。字符串不变
num = 200 // 传堆中的地址。原包装类不变,和字符串一样
arr = [2, 2, 3, 4, 5]// 传堆中数组的首地址。发生了改变
my.a = 11// 传堆中地址,资源类变量发生改变。资源类中的变量,会在堆中生成一个实例

权限修饰符

作用域 当前类 同一包 子孙类 其他包
public 可以 可以 可以 可以
protected 可以 可以 可以 不可以
default 可以 可以 不可以 不可以
private 可以 不可以 不可以 不可以

反射机制

反射优缺点

反射优点 反射缺点 反射使用场景
装载到JVM中得的信息,动态获取类的属性方法等信息,提高语言灵活性和扩展性 性能较差,速度低于直接运行 Spring等框架
提高代码复用率 程序的可维护性降低 动态代理

获取字节码

方式1 方式2 方式3
类名.class 对象名.getClass() Class.forName(classPath)
public class User {
    String username;
    String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
public class ThreeClassGetDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 方式一:类.class
        Class<Integer> intClass = int.class;
        // 方式二:对象.getClass()
        User user = new User();
        Class<? extends User> userClass = user.getClass();
        // 方式三:Class.forName(类名)
        String ClassName = "基础.反射.User";
        Class<?> userClass1 = Class.forName(ClassName);
    }
}
public class UserClassDemo {
    public static void main(String[] args) {
        String className = "基础.反射.User";
        try {
            // 通过反射获取userClass
            Class<?> userClassByForName = Class.forName(className);
            // 获取构造器
            Constructor<?> constructor = userClassByForName.getConstructor();
            // 生成user实例
            User user = (User) constructor.newInstance();
            user.setUsername("张三");
            user.setPassword("123");
            System.out.println(user);
            // 反射来修改成员变量
            Class<? extends User> userClassByuser = user.getClass();
            userClassByuser.getDeclaredField("username").set(user, "张三1");
            userClassByuser.getDeclaredField("password").set(user, "456");
            // 反射修改方法
            Method setUsername = userClassByuser.getMethod("setUsername", String.class);
            setUsername.invoke(user, "张三2");
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印结果:

User{username='张三', password='123'}
User{username='张三2', password='456'}

获取构造器等

获取构造器 获取成员变量 获取成员方法
非私有 getConstructor(类型.class) getMethod(名, 参数.class); getDeclaredField("id");
私有 getDeclaredConstructor(类型.class)和setAccessible(true) getDeclaredMethod(名, 参数.class);setAccessible(true) getDeclaredField("id");setAccessible(true)

定义测试的User:看到原类的构造器、成员属性、方法,想着怎么使用反射生成

package reflect;
public class User {
    private int id=1;
    private String name="张三";
    private static Date date;

    public User() {
    }

    public User(int id) {
        this.id = id;
    }

    private User(String name) {
        this.name = name;
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public void fun1() {
        System.out.println("无参的fun1被调用");
    }

    public void fun2(int id) {
        System.out.println("fun2:" + id);
    }

    public void fun3(int id, String s) {
        System.out.println("fun3:" + id + "," + s);
    }

    private void fun4(Date date) {
        System.out.println("fun4:" + date);

    }

    public static void fun5() {
        System.out.println("fun5");
    }

    public static void fun6(String[] args) {
        System.out.println(args.length);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public static Date getDate() {
        return date;
    }

    public static void setDate(Date date) {
        User.date = date;
    }
}
public class Demo1 {

    /**
     * 获取非私有构造器
     */
    @Test
    public void test2() throws Exception {
        Class<?> userClazz = Class.forName("reflect.User");
        Constructor<?> c1 = userClazz.getConstructor();
        Constructor<?> c2 = userClazz.getConstructor(int.class);
        Constructor<?> c3 = userClazz.getConstructor(int.class, String.class);
        User user = (User) c3.newInstance(1, "A");
        System.out.println(user);
    }

    /**
     * 获取私有构造器
     */
    @Test
    public void test3() throws Exception {
        Class<?> userClazz = Class.forName("reflect.User");
        // 私有需要declared修饰
        Constructor<?> c = userClazz.getDeclaredConstructor(String.class);
        // setAccessible设置暴露破解
        c.setAccessible(true);
        User user = (User) c.newInstance("A");
        System.out.println(user);
    }

    /**
     * 获取所有构造器:私有和非私有
     */
    @Test
    public void test4() throws Exception {
        Class<?> userClazz = Class.forName("reflect.User");
        Constructor<?>[] constructors = userClazz.getDeclaredConstructors();
        for (Constructor c : constructors) {
            System.out.println(c);
        }
    }
}

特殊情况:因为1.4是将字符数组分开作为小个体,String[]作为方法参数需要(Object)强转/new Object[]{包装}

public class Demo2 {

    /**
     * 获取非私有的成员方法
     */
    @Test
    public void test1() throws Exception {
        Class<?> claszz = Class.forName("reflect.User");
        User user = (User) claszz.newInstance();
        Method fun1 = claszz.getMethod("fun1", null);
        fun1.invoke(user, null);
        Method fun2 = claszz.getMethod("fun2", int.class);
        fun2.invoke(user, 1);
        Method fun3 = claszz.getMethod("fun3", int.class, String.class);
        fun3.invoke(user, 1, "A");
    }

    /**
     * 获得私有方法
     */
    @Test
    public void test2() throws Exception {
        Class<?> claszz = Class.forName("reflect.User");
        User user = (User) claszz.newInstance();
        // declared修饰private
        Method fun4 = claszz.getDeclaredMethod("fun4", Date.class);
        // setAccessible设置暴露破解
        fun4.setAccessible(true);
        fun4.invoke(user, new Date());
    }

    /**
     * 获得无数组参数的静态方法
     */
    @Test
    public void test3() throws Exception {
        Class<?> claszz = Class.forName("reflect.User");
        Method fun5 = claszz.getDeclaredMethod("fun5");
        fun5.invoke(null);
    }

    /**
     * 特殊情况:获得String数组参数的静态方法
     */
    @Test
    public void test4() throws Exception {
        Class<?> claszz = Class.forName("reflect.User");
        Method fun6 = claszz.getDeclaredMethod("fun6", String[].class);
        // fun6.invoke(null, new String[]{"1","2"}); 是要报错的,因为JDK4是把字符数组当做一个个对象解析
        // 以下两种方式解决:
        fun6.invoke(null, (Object) new String[]{"1", "2"});
        fun6.invoke(null, new Object[]{new String[]{"1", "2"}});
    }
}

一般来说成员属性都是私有的:getDeclaredFieldsetAccessible)后set

public class Demo3 {
    /**
     * 获取非静态的私有成员变量
     */
    @Test
    public void test1() throws Exception {
        Class<?> userClass = Class.forName("bean.User");
        User user = (User) userClass.newInstance();
        Field id = userClass.getDeclaredField("id");
        id.setAccessible(true);
        id.set(user, 2);
        Field name = userClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(user, "李四");
        System.out.println(user);
    }

    /**
     * 获取静态成员变量
     */
    @Test
    public void test2() throws Exception {
        Class<?> userClass = Class.forName("bean.User");
        Field date = userClass.getDeclaredField("date");
        date.setAccessible(true);
        date.set(null, new Date());
        System.out.println("User的Date:" + User.getDate());
    }
}

String.intern问题

public class StringQuestion {
    /*
    intern:返回值一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池
     */
    public static void main(String[] args) {
        String str1 = new StringBuilder("58").append("同城").toString();
        System.out.println(str1);
        System.out.println(str1.intern());
        System.out.println(str1 == str1.intern());
        System.out.println("-----------");
        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2);
        System.out.println(str2.intern());
        System.out.println(str2 == str2.intern());
    }
}
58同城
58同城
true
-----------
java
java
false

第一个是true,第二个为什么是false?

因为JDK初始化sun.misc.Version会在常量池自动生成一个“Java”,与剩余生成的”Java“地址肯定不一样。其余字符串都是用户创建才会在常量池生成

public class Version {
    private static final String launcher_name = "java";
    private static final String java_version = "1.8.0_271";
    private static final String java_runtime_name = "Java(TM) SE Runtime Environment";
    private static final String java_profile_name = "";
    ...
}

异常分类

异常的概念:异常指在方法不能按照 常方式 ,可以通过抛出异常的方式退出 该方法,在异常中封装了方法执行 程中的错误信息及原因 调用 该异 常后可根据务的情况选择处理该异常或者继续抛出。

异常分类 概述
Error Java 程序运行错误 ,如果程序在启动时出现 Error 则启 动失败;如果程序在运行过程中出现 Error ,则系统将退出进程
Exception Java 程序运行异常,即运行中的程序发生了人们不期望发生的情况,可以被Java异常机制处理
Exception分类 解释 常见
RuntimeException Java 虚拟机正常运行期间抛出的异常 NullPointerException、ClassCastException ArraylndexOutOfBundsException
CheckedException 编译阶段 Java 编译器会检查 CheckedException 异常井强调捕获 IO Exception、SQLExcption、ClassNotFoundException

在这里插入图片描述

在这里插入图片描述

捕获异常

public class ThrowException {
    // 抛出异常的3种方式

    // 1.throw:获取方法中的异常,throw 后面的语句块将无法被执行(finally除外)
    private static void getThrow() {
        String str = "str";
        int index = 10;
        if (index > str.length()) {
            throw new StringIndexOutOfBoundsException("index > str.length");
        } else {
            System.out.println(str);
        }
    }

    // 2.throws作用在方法上
    private static int getThrows(int a, int b) throws Exception {
        return a / b;
    }

    // 3.tryCatch包裹
    private static void getTryCatch() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("最后必须会执行");
        }
    }
}

内部类

内部类 解释
静态内部类 可以访问外部类的静态变量和方法
成员内部类 非静态内部类,不能定义静态方法和变量(final除外)
局部内部类 类中方法中定义的一个类
匿名内部类 继承一个父类或者实现一个接口的方式直接定义并使用的类
public class Outer {

    private void test(final int i) {
        new Service() {
            public void method() {
                for (int j = 0; j < i; j++) {
                    System.out.println("匿名内部类" );
                }
            }
        }.method();
    }
 }
 //匿名内部类必须继承或实现一个已有的接口 
 interface Service{
    void method();
}

泛型标记

在这里插入图片描述

泛型上下限

泛型上下限 解释
<? extends T> ?是T的子类/T接口的子接口
<? super T> ?是T的父类/T接口的父接口

定义泛型

泛型类

泛型类的使用:类名后<?>

public class TDemo<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public static void main(String[] args) {
        TDemo<Integer> tDemo1 = new TDemo<>();
        TDemo<String> tDemo2 = new TDemo<>();
        tDemo1.setValue(1);
        System.out.println(tDemo1.getValue());
        tDemo2.setValue("a");
        System.out.println(tDemo2.getValue());
    }
}

泛型方法

泛型方法使用:在方法返回值前定义泛型<?>,也可以继承一些接口<? extends Comparable<e>></e>

public static <E extends Comparable<E>> void bubbleSort0(E[] arr) {
    // 只需要n-1层外部循环
    for (int i = arr.length - 1; i > 0; i--) {
        for (int j = 0; j < i && arr[j].compareTo(arr[j + 1]) > 0; j++) {
            swap(arr, j, j + 1);
        }
   }
}

泛型接口

接口<?>,其实现类指定类型如implement 接口

public interface TInterfer<T> {
    public T getId();
}
public class TInterferImpl implements TInterfer<String> {

    @Override
    public String getId() {
        return UUID.randomUUID().toString().substring(0, 3);
    }
}

序列化

名词 关键字 特性
序列化 implements Serializable 底层字节数组,保存对象的状态信息,不能保存静态变量
反序列化 transient 被该关键字修饰的,不能被反系列化