拾遗

张天宇 on 2020-02-27

一些不知道该总结到哪里的 Java 零碎知识点。

泛型

泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型。

  1. 泛型类和泛型方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Test<T>{
    public void show(T t){
    System.out.println(t);
    }
    public void print(T t){
    System.out.println(t);
    }
    }
    // 使用
    public static void main(String[] args) {
    Test<String> d = new Test<String>();
    d.show("java");
    d.print("Object-C");

    Test<Integer> e = new Test<Integer>();
    e.show(2);
    e.print(new Integer(5));
    }

    泛型类定义的类型,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有操作的类型就已经固定了。例如上面的 d 只能操作 String。定义到方法上就可以解决这个问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Test{
    public <T> void show(T t){
    System.out.println(t);
    }

    public <T> void print(T t){
    System.out.println(t);
    }
    public <U,T> void sum(U u,T t){
    System.out.println(u+" version is "+t);
    }
    }
    // 使用
    public static void main(String[] args) {
    Test d = new Test();
    d.show("java");
    d.print(5);
    d.sum("java", new Double(8));
    }
  2. 泛型方法的好处

    1. 泛型方法可以让不同方法操作不同类型,且类型还不确定。
    2. 与泛型类不同,泛型方法的类型参数只能在它锁修饰的泛型方法中使用。
  3. 创建一个泛型方法

    1
    2
    访问修饰符 [static][final] <类型参数列表> 返回值类型 方法名([形式参数列表])
    // [] 表示可有可无
  4. 泛型接口

    1
    interface 接口名<类型参数表>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface showInterface<T>{
    public void show(T t);
    }
    // 实现类型确定
    class ShowClass implements showInterface<String>{
    public void show(String t){
    System.out.println("show:"+t);
    }
    }
    // 实现类型不确定
    class ShowClass<T> implements showInterface<T>{
    public void show(T t){
    System.out.println("show:"+t);
    }
    }
    public static void main(String[] args) {

    ShowClass<Integer> obj = new ShowClass<Integer>();
    obj.show(6);
    /*
    ShowClass obj = new ShowClass();
    obj.show("java");
    */
    }
  5. 泛型通配符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) {
    ArrayList<String> a1 = new ArrayList<String>();
    a1.add("a");
    a1.add("b");
    a1.add("c");

    ArrayList<Integer> a2 = new ArrayList<Integer>();
    a2.add(1);
    a2.add(2);
    a2.add(3);
    }

    进一步修改,定义一个泛型方法:

    1
    2
    3
    4
    5
    6
    7
    public static <T>void vistor(ArrayList<T> a){
    Iterator<T> iterator = a.iterator();
    while(iterator.hasNext()){
    T t = iterator.next();
    System.out.println(t);
    }
    }

    如果是泛型类,是不允许泛型定义在 static 上面的。

    1
    2
    3
    4
    5
    6
    //占位符,也称为通配符。表示元素类型可以匹配任意类型
    public static void sop(ArrayList<?> a){
    for(Iterator<?> it = a.iterator();it.hasNext();){
    System.out.println(it.next());
    }
    }
    1. 类型通配符上限定(? extends T),可以接受 T 和 T 的子类

      1
      2
      3
      4
      5
      6
      public static void printMethod(ArrayList<? extends Person> a1){
      Iterator<? extends Person> it = a1.iterator();
      while(it.hasNext()){
      System.out.println(it.next().getNmae());
      }
      }
    2. 类型通配符下限定(? super T),接受 T 类型或者 T 的父类型

序列化

  • 保存(持久化)对象及其状态到内存或者硬盘

  • 序列化以字节数组保存,静态成员不保存。对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。

  • 要想将父类对象也序列化,就要让父类也实现 Serializable 接口。

实现方式

  • Serializable 实现序列化
  • ObjectOutputStream 和 ObjectInputStream 对对象进行序列化和反序列化
  • 在类中增加 writeObject 和 readObject 方法实现自定义序列化策略

阻止序列化

  • 在变量声明前加上 Transient 关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
  • 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

深拷贝和浅拷贝

直接赋值复制

A a1 = a2;这实际上复制的是引用,也就是说 a1 和 a2 指向的是同一个对象,所以 a1 变化的时候,a2 实际也在发生变化。

浅拷贝

创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

深拷贝

深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。

序列化

在 Java 语言里深复制一个对象,常常可以先使对象实现 Serializable 接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。