缓存池

  • new Integer(123) 与 Integer.valueOf(123) 的区别在于:

    • new Integer(123) 每次都会新建一个对象
    • Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
      1. Integer x = new Integer(123);
      2. Integer y = new Integer(123);
      3. System.out.println(x == y); // false
      4. Integer z = Integer.valueOf(123);
      5. Integer k = Integer.valueOf(123);
      6. System.out.println(z == k); // true
  • valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。

    1. public static Integer valueOf(int i) {
    2. if (i >= IntegerCache.low && i <= IntegerCache.high)
    3. return IntegerCache.cache[i + (-IntegerCache.low)];
    4. return new Integer(i);
    5. }
  • 编译器会在缓冲池范围内的基本类型自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象(在 Java 8 中,Integer 缓存池的大小默认为 -128~127) ```java public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high)

    1. return IntegerCache.cache[i + (-IntegerCache.low)];

    return new Integer(i); }


- **基本类型对应的缓冲池如下:**
   - **boolean values true and false**
   - **all byte values**
   - **short values between -128 and 127**
   - **int values between -128 and 127**
   - **char in the range \u0000 to \u007F**
- **在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。**
- **如果在缓冲池之外:**
```java
Integer m = 323;
Integer n = 323;
System.out.println(m == n); // false

String

概览

  • String 被声明为 final,因此它不可被继承。
  • 内部使用 char 数组存储数据,该数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

    String.intern()

  • 使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象。

  • 下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用。intern() 首先把 s1 引用的对象放到 String Pool(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。

    String s1 = new String("aaa");
    String s2 = new String("aaa");
    System.out.println(s1 == s2);           // false
    String s3 = s1.intern();
    System.out.println(s1.intern() == s3);  // true
    
  • 如果是采用 “bbb” 这种使用双引号的形式创建字符串实例,会自动地将新建的对象放入 String Pool 中。

    String s4 = "bbb";
    String s5 = "bbb";
    System.out.println(s4 == s5);  // true
    
  • 在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

    接口

    概览

  • 接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

  • 从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
  • 接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
  • 接口的字段默认都是 static 和 final 的。

    接口成员特点

  • 成员变量

    • 只能是常量
    • 默认修饰符:public static final
  • 构造方法
    • 没有
  • 成员方法
    • 只能是抽象方法
    • 默认修饰符:public abstract(可以省略)
  • JDK8新特性
    • 默认方法
      • 允许在接口中定义非抽象方法,但是需要使用关键字default修饰,这些方法就是默认方法
      • 作用:解决接口升级的问题
      • 接口中默认方法的定义格式:
        • 格式:public default返回值类型 方法名(参数列表) { }
        • 范例:public default void show() { }
      • 接口中默认方法的注意事项:
        • 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
        • lpublic可以省略,default不能省略
        • 如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写
    • 静态方法
      • 格式:public static返回值类型 方法名(参数列表) { }
      • 范例:public static void show() { }
      • 作用 : 方法调用的更加简洁,可以考虑设计为static静态方法
      • 接口中静态方法的注意事项:
        • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
        • public可以省略,static不能省略
  • JDK9新特性

    • 私有方法
      • 格式1:private 返回值类型 方法名(参数列表) { }
        • 范例1:private void show() { }
      • 格式2:private static 返回值类型 方法名(参数列表) { }
        • 范例2:private static void method() { }
      • 作用:接口中的方法出现了重复的代码,还不想被其他类访问 , 可以考虑抽取出一个私有方法

        重写与重载

        重写(Override)

  • 存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。

  • 为了满足里式替换原则,重写有有以下两个限制:
    • 子类方法的访问权限必须大于等于父类方法;
    • 子类方法的返回类型必须是父类方法返回类型或为其子类型。
  • 使用 @Override 注解,可以让编译器帮忙检查是否满足上面的两个限制条件。

    重载(Overload)

  • 存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。

  • 应该注意的是,返回值不同,其它都相同不算是重载。

    里式替换原则(Liskov Substitution Principle, LSP)

    原则

  • 所有引用基类(父类)的地方必须能透明地使用其子类的对象。

    解释

  • 在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。(类似于多态的向上转型)

    举例说明

  • 有两个类,一个类为BaseClass,另一个是SubClass类,并且SubClass类是BaseClass类的子类,那么一个方法如果可以接受一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub,method1(sub)能够正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:method2(sub),那么一般而言不可以有method2(base),除非是重载方法。

  • 可以从多态的角度去看,多态是父类对象指向子类对象的引用,就好比父类对象调用子类的方法,原本父类为public,现在子类重写方法后为private,就会矛盾,导致无法无法调用,多态也无法成立

    结论

  • 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象

    Object 通用方法

    clone()(基本不用,有待补充整理)

    final

    数据

  • 声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。

    • 对于基本类型,final 使数值不变;
    • 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。

      方法

  • 声明方法不能被子类重写。

  • private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。

  • 声明类不允许被继承

    static

    静态导包

  • 在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。

    初始化顺序

  • 静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

    public static String staticField = "静态变量";
    
    static {
      System.out.println("静态语句块");
    }
    
    public String field = "实例变量";
    
    {
      System.out.println("普通语句块");
    }
    
  • 最后才是构造函数的初始化。

    public InitialOrderTest() {
      System.out.println("构造函数");
    }
    
  • 存在继承的情况下,初始化顺序为:

    • 父类(静态变量、静态语句块)
    • 子类(静态变量、静态语句块)
    • 父类(实例变量、普通语句块)
    • 父类(构造函数)
    • 子类(实例变量、普通语句块)
    • 子类(构造函数)

      内部类

      静态内部类

  • 非静态内部类依赖于外部类的实例,而静态内部类不需要。

    public class OuterClass {
      class InnerClass {
      }
    
      static class StaticInnerClass {
      }
    
      public static void main(String[] args) {
          //非静态内部类的使用,要先穿件外部类的对象,在通过外部类对象去new非静态内部类
          // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
          OuterClass outerClass = new OuterClass();
          //静态内部类的使用
          InnerClass innerClass = outerClass.new InnerClass();
          StaticInnerClass staticInnerClass = new StaticInnerClass();
      }
    }
    
  • 静态内部类不能访问外部类的非静态的变量和方法。

    匿名内部类

  • 本质上是一个没用名字的局部被不累,定义在方法中、代码块中等

  • 作用:方便创建子类,最终目的为了简化代码的编写
  • 格式:

new 类|抽象类|或者接口名(){
重写方法;
}

Emplyee e = new Emplyee(){
    public void work(){
    }
};
a.work;
  • 特点总结:.
    • 匿名内部类是一个没用名字的内部类
    • 匿名内部类写出来就会产生一个匿名内部类的对象
    • 匿名内部类的对象类型相当于当前new的那个的类型的子类对象