¶缓存池
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
编译器会在缓冲池范围内的基本类型自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象(在 Java 8 中,Integer 缓存池的大小默认为 -128~127) ```java public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high)
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。
-
接口成员特点
成员变量
- 只能是常量
- 默认修饰符: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新特性
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
- 为了满足里式替换原则,重写有有以下两个限制:
- 子类方法的访问权限必须大于等于父类方法;
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
使用 @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
数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
声明方法不能被子类重写。
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的那个的类型的子类对象