Java面向对象知识点

  • Java中数据类型分为俩种:一种是基本数据类型,一种是引用类型,基本数据类型包括:整数类型包括byte、short、int、long、char,浮点类型包括float和double,布尔类型:boolean。引用类型包括类、接口和数组类型,还有一种特殊的null类型。所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。实际上,引用类型变量就是一个指针
  • 空引用(null)只能被转换成引用类型,不能转换成基本类型,因此不要把一个null值赋给基本数据类型的变量。
  • 数组也是一种数据类型,它本身是一种引用类型。例如int是一个基本类型,但int[](这是定义数组的一种方式)就是一种引用类型了。,int[]就是一种数据类型,与int类型、String类型类似,一样可以使用该类型来定义变量,也可以使用该类型进行类型转换等。
  • Java中调动函数都是采用值传递的方式,但是对于基本数据类型来说,传递的是基本数据对象的值,对于引用类型的数据来说,传递的是对象的引用,也即是指向该对象的指针值。
  • 定义并初始化一个数组后,在内存中分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身。数组的创建过程中,数据对象本身会存储到堆内存中,而数据的引用变量会存放到栈内存中。当我们看一个数组时,一定要把数组看成两个部分:一部分是数组引用,也就是在代码中定义的数组引用变量;还有一部分是实际的数组对象,这部分是在堆内存里运行的,通常无法直接访问它,只能通过数组引用变量来访问。
  • 构造器既不能定义返回值类型,也不能使用void定义构造器没有返回值。如果为构造器定义了返回值类型,或使用void声明构造器没有返回值,编译时不会出错,但Java会把这个所谓的构造器当成方法来处理。类的构造器是有返回值的,当我们用new关键字来调用构造器时,构造器返回该类的实例,可以把这个类的实例当成构造器的返回值,因此构造器的返回值类型总是当前类,无须定义返回值类型。但必须注意:不能在构造器里显式使用return来返回当前类的对象,因为构造器的返回值是隐式的。
  • 当一个对象被创建成功以后,这个对象将保存在堆内存中,Java程序不允许直接访问堆内存中的对象,只能通过该对象的引用操作该对象。也就是说,不管是数组还是对象,都只能通过引用来访问它们。堆内存里的对象没有任何变量指向该对象,那么程序将无法再访问该对象
  • this作为对象的默认引用有两种情形:
    • 构造器中引用该构造器正在初始化的对象
    • 在方法中引用调用该方法的对象。
  • 子类扩展了父类,将可以获得父类的全部Field和方法,可使用super限定来调用父类被覆盖的实例方法。
  • 子类中定义与父类中同名的实例变量并不会完全覆盖父类中定义的实例变量,它只是简单地隐藏了父类中的实例变量,可用super.Filed来调用父类的字段
  • 不管我们是否使用super调用来执行父类构造器的初始化代码,子类构造器总会调用父类无参构造器一次;当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行,并会根据继承树进行初始化
  • 什么是多态?(难点)
    • Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)。 易错点:与方法不同的是,对象的Field则不具备多态性
    • 引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如,通过Object p=new Person()代码定义一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里定义的方法。
class BaseClass{
    public int book=6;
    public void base(){
        System.out.println("父类普通方法");
    }
    public void test(){
        System.out.println("父类被覆盖的方法");
    }
}

class SubClass extends BaseClass{
    //重新定义一个book实例的Field隐藏父类book实例Field
    public String book="子类的字段";

    public void test(){
        System.out.println("子类覆盖父类的方法");
    }

    public void sub(){
        System.out.println("子类独有的方法");
    }


    public  static void main(String[] args){

        BaseClass bc =new SubClass();
        //输出6--表名访问的是父类的Field-----与方法不同的是,对象的Field则不具备多态性
        System.out.println(bc.book);
        //下面将执行从父类继承到的方法
        bc.base;
        //下面将调用当前类的test方法(覆盖父类的方法)
        //编译时类型是BaseClass,而运行时类型是SubClass,当调用该引用变量的test方法(BaseClass类中定义了该方法,子类SubClass覆盖了父类的该方法)时,实际执行的是SubClass类中覆盖后的test方法,这就可能出现多态了。
        bc.test;
        //因为bc的编译时类型是BaseClass
        //BaseClass类没有提供sub方法,所以下面的代码编译时会出现错误
        //引用变量只能调用声明该变量时所用类里包含的方法
        //bc.sub();

        //进行强制转化即可调动子类型的方法--对象必须实际上是子类实例才行。
        (SubClass)bc.sub();
    }
}
  • 基本类型之间的转换只能在数值类型之间进行,数值类型和布尔类型之间不能进行类型转换。

  • 引用类型之间的转换只能在具有继承关系的两个类型之间进行。把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型)。

  • 当把子类对象赋给父类引用变量时,被称为向上转型(upcasting),这种转型总是可以成功的,这也从另一个侧面证实了子类是一种特殊的父类。这种转型只是表明这个引用变量的编译时类型是父类,但实际执行它的方法时,依然表现出子类对象的行为方式。但把一个父类对象赋给子类引用变量时,就需要进行强制类型转换,而且还可能在运行时产生ClassCastException异常,使用instanceof运算符可以让强制类型转换更安全。

  • instanceof运算符用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回true,否则返回false。

  • 继承的坏处是什么?

    • 继承是实现类重用的重要手段,但继承带来了一个最大的坏处:破坏父类封装

      解决办法: 组合也是实现类重用的重要方式,而采用组合方式来实现类重用则能提供更好的封装性。组合则是把旧类对象作为新类的Field嵌入,用以实现新类的功能

    • 为了保证父类有良好的封装性,不会被子类随意改变,设计父类通常应该遵循如下规则。

      • 尽量隐藏父类的内部数据。尽量把父类的所有Field都设置成private访问类型,不要让子类直接访问父类的Field。

      • 不要让子类可以随意访问、修改父类的方法。父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法访问该方法;

        如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符 来修饰该方法

        如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则可以使用protected来修饰该方法。

  • 何时使用继承,何时使用组合?

    • 继承要表达的是一种“是(is-a)”的关系,而组合表达的是“有(has-a)”的关系。
  • 初始化块:只在创建Java对象时隐式执行,而且在执行构造器之前执行。

  • 静态初始化块:系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行。因此静态初始化块总是比普通初始化块先执行。

  • 静态初始化块是类相关的,用于对整个类进行初始化处理,通常用于对类Field执行初始化处理。静态初始化块不能对实例Field进行初始化处理。

  • 创建一个类时,如果该类未初始化,则先对类进行初始化(静态初始化快执行等--静态初始化块和声明静态Field时所指定的初始值都是该类的初始化代码),再对该对象进行初始化(初始化快,构造方法等)。

    public class B
    {
      public static B t1 = new B();
      public static B t2 = new B();
      {
          System.out.println("构造块");
      }
      static
      {
          System.out.println("静态块");
      }
      public static void main(String[] args)
      {
          B t = new B();
      }
    }
    输出:构造块  构造块 静态块   构造块
  • 总结一下: 1.程序入口main方法要执行首先要加载类B 2.静态域:分为静态变量,静态方法,静态块。这里面涉及到的是静态变量和静态块,当执行到静态域时,按照静态域的顺序加载。并且静态域只在类的第一次加载时执行 3.每次new对象时,会执行一次构造块和构造方法,构造块总是在构造方法前执行(当然,第一次new时,会先执行静态域,静态域〉构造块〉构造方法) 注意:加载类时并不会调用构造块和构造方法,只有静态域会执行 4.根据前三点,首先加载类B,执行静态域的第一个静态变量,static b1=new B,输出构造块和构造方法(空)。ps:这里为什么不加载静态方法呢?因为执行了静态变量的初始化,意味着已经加载了B的静态域的一部分,这时候不能再加载另一个静态域了,否则属于重复加载 了(静态域必须当成一个整体来看待。否则加载会错乱) 于是,依次static b2 =new B,输出构造块,再执行静态块,完成对整个静态域的加载,再执行main方法,new b,输出构造块。

  • 当使用new String("hello")时,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中。换句话说,new String("hello")一*生了两个对象。如果引用的字符串可以在编译期就确定下来,则它将引用常量池中的字符串对象。**

  • 当通过对象来访问类Field时,系统会在底层转换为通过该类来访问类Field。

  • 对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值。final修饰的类Field、实例Field能指定初始值的地方如下。

    • 类Field:必须在静态初始化块中或声明该Field时指定初始值。
    • 实例Field:必须在非静态初始化块、声明该Field或构造器中指定初始值。
  • final成员变量(包括实例Field和类Field)必须由程序员显式初始化,系统不会对final成员进行隐式初始化。

  • final修饰的局部变量在定义时没有指定默认值,则可以在后面代码中对该final变量赋初始值,但只能一次。

  • 使用final修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。

  • final修饰的方法不可被重写,但并不是不能被重载。

  • final修饰的类不可以有子类。

  • 抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例,只能当作父类被其他子类继承。

  • static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法。

  • 抽象类的体现是什么?

    • 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为方式。
  • 更加特殊的“抽象类”——接口(interface),接口里不能包含普通方法,接口里的所有方法都是抽象方法。

  • 接口里定义的常量Field而言,它们是接口相关的,而且它们只能是常量。

  • 接口不能用于创建实例,但接口可以用于声明引用类型变量。当使用接口来声明引用类型变量时,这个引用类型变量必须引用到其实现类的对象。

  • 接口和抽象类在用法上也存在如下差别

    • 接口里只能包含抽象方法,不包含已经提供实现的方法;抽象类则完全可以包含普通方法。
    • 接口里不能定义静态方法;抽象类里可以定义静态方法。
    • 接口里只能定义静态常量Field,不能定义普通Field;抽象类里则既可以定义普通Field,也可以定义静态常量Field。
    • 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
    • 接口里不能包含初始化块;但抽象类则完全可以包含初始化块。
    • 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。
  • 非静态内部类对象和外部类对象的关系是怎样的?

    • 非静态内部类对象必须寄存在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄存其中。简单地说,如果存在一个非静态内部类对象,则一定存在一个被它寄存的外部类对象。但外部类对象存在时,外部类对象里不一定寄存了非静态内部类对象。因此外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在!而非静态内部类对象访问外部类成员时,外部类对象一定存在
  • 为什么静态内部类的实例方法也不能访问外部类的实例属性呢?

    • 因为静态内部类是外部类的类相关,而不是外部类的对象相关的。也就是说,静态内部类对象不是寄存在外部类对象里的,而是寄存在外部类的类本身中。当静态内部类对象存在时,并不存在一个被它寄存的外部类对象,静态内部类对象里只有外部类的类引用,没有持有外部类对象的引用。如果允许静态内部类的实例方法访问外部类的实例成员,但找不到被寄存的外部类对象,这将引起错误。
  • 不管是静态内部类还是非静态内部类,它们声明变量的语法完全一样。区别只是在创建内部类对象时,静态内部类只需使用外部类即可调用构造器,而非静态内部类必须使用外部类对象来调用构造器。

  • 把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。

  • 闭包和回调(难点)

    • 闭包(Closure)是一种能被调用的对象,它保存了创建它的作用域信息。Java 7虽然没有显式地支持闭包,但对于非静态内部类而言,它不仅记录了其外部类的详细信息,还保留了一个创建非静态内部类对象的引用,并且可以直接调用外部类的private成员,因此可以把非静态内部类当成面向对象领域的闭包。

    • 通过这种仿闭包的非静态内部类,可以很方便地实现回调功能,回调就是某个方法一旦获得了内部类对象的引用后,就可以在合适的时候反过来去调用外部类实例的方法。所谓回调,就是允许客户类通过内部类引用来调用其外部类的方法,这是一种非常灵活的功能。

    • 例如:一个接口程序员和一个基类作家都有一个相同的方法work,相同的方法名,但是其含义完全不同,这时候就需要闭包。

      class Writer {//作家基类
      void work(){};
      }
      interface programmer{//程序员接口
      void work();
      }

      闭包实现代码如下:

      public class WriterProgrammer extends Writer {
      @Override
      public void work(){
        //写作
      }
      public void code(){
        //写代码
      }
      class ProgrammerInner implements programmer{
        @Override
        public void work(){
            code();
        }
      }
      }
  • 对象在内存中的状态分为:可达状态、可恢复状态、不可达状态。只有处于不可达状态时才会被垃圾回收机制所回收。一个对象可以被一个方法的局部变量引用,也可以被其他类的类变量引用,或被其他对象的实例变量引用。当某个对象被其他类的类变量引用时,只有该类被销毁后,该对象才会进入可恢复状态;当某个对象被其他对象的实例变量引用时,只有当该对象被销毁后,该对象才会进入可恢复状态。

  • Jar包知识点

    • 创建可执行的JAR包的关键在于:让javaw命令知道JAR包中哪个类是主类,javaw命令可以通过运行该主类来运行程序。jar命令有一个-e选项,该选项指定JAR包中作为程序入口的主类的类名。因此,制作一个可执行的JAR包只要增加-e选项即可。例如如下命令:

    jar cvfe test.jar MainClass *.class

    ​ 说明:上面命令把当前目录下的所有*.class文件都压缩到test.jar包中,并指定使用Test类作为程序的入口。

    • 运行上面的JAR包有两种方式。
      • 使用java命令,使用java运行时的语法是:java-jar test.jar。
      • 使用javaw命令,使用javaw运行时的语法是:javaw test.jar。当创建JAR包时,所有的类都必须放在与包结构对应的目录结构中,就像上面-e选项指定的Test类,表明入口类为Test。因此,必须在JAR包下包含Test.class文件。

Java集合知识点

  • 数组长度不可变化,一旦在初始化数组时指定了数组长度,这个数组长度就是不可变的。为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java提供了集合类。

  • 集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象 new ArrayList<int>() 是错误的,new ArrayList<Integer>()是正确的

  • Iterator则主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。

  • 当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。Iterator的主要作用是对集合进行删除操作

    //项目中写的模型树的使用  
    private boolean pruneBFS(List treeList,String searchVal){
          boolean flag=false;
          Iterator iterator = treeList.iterator();
          while (iterator.hasNext()){
              TreeManage treeNode = (TreeManage) iterator.next();
              if(treeNode.getNodeName() != null
                      && !"".equals(treeNode.getNodeName())
                      && treeNode.getNodeName().indexOf(searchVal) != -1){
                  flag=true;
              }else{
                  if(treeNode.getChildren().size()==0){
                      iterator.remove();
                  }else{
                      boolean childFlag = pruneBFS(treeNode.getChildren(), searchVal);
                      if(!childFlag){
                          iterator.remove();
                      }
                      flag=flag || childFlag;
                  }
              }
          }
          return  flag;
      }
  • Iterator迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发ConcurrentModificationException异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。

  • 与使用Iterator接口迭代访问集合元素类似的是,foreach循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量,因此在foreach循环中修改迭代变量的值也没有任何实际意义。

  • HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。

  • ListIterator与普通的Iterator进行对比,不难发现ListIterator增加了向前迭代的功能(Iterator只能向后迭代),而且ListIterator还可通过add方法向List集合中添加元素(Iterator只能删除元素)

  • Set与Map之间的关系非常密切。虽然Map中放的元素是key-value对,Set集合中放的元素是单个对象,但如果我们把key-value对中的value当成key的附庸:key在哪里,value就跟在哪里。这样就可以像对待Set一样来对待Map了。事实上,Map提供了一个Entry内部类来封装key-value对,而计算Entry存储时则只考虑Entry封装的key。从Java源码来看,Java是先实现了Map,然后通过包装一个所有value都为null的Map就实现了Set集合。

  • Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,Hashtable不允许使用null作为key和value,如果试图把null值放进Hashtable中,将会引发NullPointerException异常;但HashMap可以使用null作为key或value。

  • 与HashSet类似的是,尽量不要使用可变对象作为HashMap、Hashtable的key,如果确实需要使用可变对象作为HashMap、Hashtable的key,则尽量不要在程序中修改作为key的可变对象

  • Collections类中提供了多个synchronizedXxx()方法,该方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。

Java反射知识点

  • 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成这3个步骤,所以有时也把这3个步骤统称为类加载或类初始化。

    • 类加载指的是将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象
    • 连接阶段(验证-准备-解析)负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。(1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。(2)准备:类准备阶段则负责为类的静态Field分配内存,并设置默认初始值。(3)解析:将类的二进制数据中的符号引用替换成直接引用。
    • 初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化。
  • 创建类的实例。为某个类创建实例的方式包括:使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。

  • 一个类也是Class的对象(一切皆对象),获取一个类的Class有三种方法:(1) Class.forName(A); (2) A.Class;(3)a.getClass();

  • 通过Class对象可以得到大量的Method、Constructor、Field等对象,这些对象分别代表该类所包括的方法、构造器和属性等,程序还可以通过这些对象来执行实际的功能,例如调用方法、创建实例

  • JDK动态代理只能为接口创建动态代理。假设代码块1、代码块2和代码块3既可以执行深色代码部分,又无须在程序中以硬编码方式直接调用深色代码的方法,这时就可以通过动态代理来达到这种效果。

  • 定义了一个简单的 InvocationHandler 实现类,定义该实现类时需要重写 invoke()方法——调用代理对象的所有方法时都会被替换成调用该invoke()方法。

    /**
     * @author zzk
     * @date 2020/9/1 11:42
     */
    public class studyTest {
    
        interface Person{
            void walk();
            void sayHello(String  msg);
        }
    
        static  class GunPerson implements Person{
    
            @Override
            public void walk() {
                System.out.println("GunPerson----正在执行的方法walk");
            }
    
            @Override
            public void sayHello(String msg) {
                System.out.println("GunPerson----正在执行的方法sayHello");
            }
        }
    
    static class MyInvokationHandle implements InvocationHandler{
    //        需要被代理的对象
            private Object target;
    
            public void setTarget(Object target) {
                this.target = target;
            }
    
            /**
             *执行动态代理对象的所有方法时,都会被替代成执行如下的invoke方法
             * @param proxy 动态代理对象
             * @param method  方法
             * @param args 方法传入参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("----正在执行的方法"+method);
    
                //以target作为主调来执行method方法
                method.invoke(target,args);
    
                if(args!=null){
                    for (Object val:args){
                        System.out.println(val);
                    }
                }else{
                    System.out.println("调用方法无参数");
                }
                return null;
            }
        }
    
        public static void main(String[] args) {
            MyInvokationHandle handle = new MyInvokationHandle();
            handle.setTarget(new GunPerson());
    //        不管程序是执行代理对象的 walk()方法,还是执行代理对象的sayHello()方法,
    //        实际上都是执行InvocationHandler对象的invoke()方法。
            Person instance = (Person) Proxy.newProxyInstance(GunPerson.class.getClassLoader(), GunPerson.class.getInterfaces(), handle);
            instance.sayHello("hello");
            instance.walk();
        }
    }

    Java知识查漏补缺

    序列化与反序列化

序列化:把对象转化为可传输的字节序列过程称为序列化;主要作用:最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。

反序列化:把字节序列还原为对象的过程称为反序列化

什么情况下需要序列化?
凡是需要进行“跨平台存储”和”网络传输”的数据,都需要进行序列化。本质上存储和网络传输 都需要经过把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息。

1、java 实现序列化很简单,只需要实现Serializable 接口即可。

2、将对象进行二进制的数据存储后,并从文件中读取数据出来转成对象就是一个序列化和反序列化的过程。

JAVA序列化中常见的问题?

  • 问题一:static 属性不能被序列化
    原因:序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。
  • 问题二:Transient 属性不会被序列化
    原因:在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
    transient使用小结:
    1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
    2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
    3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
  • 问题三:父类、子类序列化问题
    序列化是以正向递归的形式进行的,如果父类实现了序列化那么其子类都将被序列化;子类实现了序列化而父类没实现序列化,那么只有子类的属性会进行序列化,而父类的属性是不会进行序列化的。