1. 数据类型
1.1 基本数据类型
基本数据类型 | byte | char | short | int | long | float | double | boolean |
---|---|---|---|---|---|---|---|---|
占用字节 | 1 | 2 | 2 | 4 | 8 | 4 | 8 | 1 |
包装器类型 | Byte | Character | Short | Integer | Long | Float | Double | Boolean |
范围 | [-27 ,27-1] | [0,255] | [-215, 215-1] | [-231,231-1] | [-263 ,263-1] |
1.2 自动装箱拆箱
Java SE5开始提供自动装箱;
装箱:将基本数据类型转化为包装器类型;拆箱:将包装器类型转化为基本数据类型。
所有的包装类都是不可变类。
什么是不可变对象呢?一旦一个类的对象被创建出来,在其整个生命周期中,它的成员变量就不能被修改。
1.2.1 自动装箱如何实现?
public class Box {
public static void main(String[] args) {
Integer i = 10;
int j = 10;
j = new Integer(1000);
Double d = 1.0;
double dd = 1.0;
dd = new Double(1000);
}
}
javac Box.java
javap -c Box
Compiled from "Box.java"
public class com.learn.java.Box {
public com.learn.java.Box();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 10
8: istore_2
9: new #3 // class java/lang/Integer
12: dup
13: sipush 1000
16: invokespecial #4 // Method java/lang/Integer."<init>":(I)V
19: invokevirtual #5 // Method java/lang/Integer.intValue:()I
22: istore_2
23: dconst_1
24: invokestatic #6 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
27: astore_3
28: dconst_1
29: dstore 4
31: new #7 // class java/lang/Double
34: dup
35: ldc2_w #8 // double 1000.0d
38: invokespecial #10 // Method java/lang/Double."<init>":(D)V
41: invokevirtual #11 // Method java/lang/Double.doubleValue:()D
44: dstore 4
46: return
}
由class文件可知,编译后的class在运行时,装箱由包装器类型的 valueOf 方法实现;
拆箱有各个类型对应的“ 类型value” 方法直接返回内置原始类型数据。
1.2.2 valueOf
各个包装器类型实现如下:
包装类型 | 值 | 处理方式 | 备注 |
---|---|---|---|
Byte | [-128,127] | 从ByteCache.cache数组中直接取 | |
Short | [-128,127] | 从ShortCache.cache数组中直接取 | |
Short | [-215,-128)U(127,215-1] | new一个新的Short | |
Integer | [-127,size] | 从IntegerCache.cache数组中直接取 | size默认128 |
Integer | [-231,-127)U(size,231-1] | new一个新的Integer | |
Long | [-128,127] | 从LongCache.cache数组中直接取 | |
Long | [-263 ,-128)U(127,263-1] | new一个新的Long | |
Double | 所有可取的值 | new一个新的Double | |
Float | 所有可取的值 | new一个新的Float | |
Character | [0,127] | 从CharacterCache.cache数组中直接取 | |
Character | (127,255] | new一个新的Character | |
Boolean | true,false | 取中提前创建好的Boolean常量 | |
1.2.3 案例:Integer源码
// Java 5 中引入这个特性的时候,范围是固定的 -128 至 +127。
// 后来在Java 6 后,最大值映射到 java.lang.Integer.IntegerCache.high,
// 可以使用 JVM 的启动参数设置最大值
-XX:AutoBoxCacheMax=size
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
2. 语法权限
权限大小:
private(当前类) <default(当前类,同包)<protected(当前类,同包,子孙类)<public(当前类,同包,子孙类,外部包)
子类覆盖父类方法时,对外权限只能越来越大。
final修饰的类或者方法,不能被继承或覆盖。
3. 多态
3.1 重载、重写
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
3.2 分派:虚拟机对多态的支持
静态类型:即对象的声明类型,编译期可知;
实际类型:对象实际所属类型,编译期不可知,运行时可知。
// m的静态类型是Map,实际类型是HashMap
Map m = new HashMap();
静态分派:所有依赖静态类型来定位方法执行版本的分派,如重载;
动态分配:根据实际类型来确定具体执行方法的分派,如重写。
重载的例子:
public class Super {
public int test(int i) {
System.out.println("Super test(int i)");
return 0;
}
public int test(byte i) {
System.out.println("Super test(byte i)");
return 1;
}
public static void main(String[] args) {
Super s = new Super();
s.test((byte) 1);
s.test(1);
}
}
执行后输出:
Super test(byte i)
Super test(int i)
通过javap查看字节码:
Compiled from "Super.java"
public class com.basic.Super {
public com.basic.Super();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int test(int);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Super test(int i)
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: iconst_0
9: ireturn
public int test(byte);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String Super test(byte i)
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: iconst_1
9: ireturn
public static void main(java.lang.String[]);
Code:
0: new #6 // class com/basic/Super
3: dup
4: invokespecial #7 // Method "<init>":()V
7: astore_1
8: aload_1
9: iconst_1
10: invokevirtual #8 // Method test:(B)I
13: pop
14: aload_1
15: iconst_1
16: invokevirtual #9 // Method test:(I)I
19: pop
20: return
}
字节码中的main方法中第10行和第16行,分别根据参数类型byte、int定位到了test(byte i)和test(int i),由此可见,重载是由编译器在编译期完成的。
重写的例子,接上述Super类:
public class Sub extends Super {
// 重写父类该方法
public int test(byte i) {
System.out.println("Sub superInt(byte i)");
return 1;
}
public static void main(String[] args) {
Super sub = new Sub();
byte i=2;
sub.test(i);
}
}
执行后输出如下:
Sub superInt(byte i)
通过javap查看字节码:
Compiled from "Sub.java"
public class com.basic.Sub extends com.basic.Super {
public com.basic.Sub();
Code:
0: aload_0
1: invokespecial #1 // Method com/basic/Super."<init>":()V
4: return
public int test(byte);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Sub superInt(byte i)
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: iconst_1
9: ireturn
public static void main(java.lang.String[]);
Code:
0: new #5 // class com/basic/Sub
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: iconst_2
9: istore_2
10: aload_1
11: iload_2
12: invokevirtual #7 // Method com/basic/Super.test:(B)I
15: pop
16: return
}
因为sub对象的静态类型是Super,test方法传入参数的静态类型是byte,所以可以在字节码main方法中第12行看到,编译器定位到了Super.test(byte i )方法。而在运行时时,java虚拟机会根据对象的实际类型(Sub)调用实际类型的方法(Sub.test(byte i))。
3.3 重载方法匹配的优先级
因为java中字面量不需要定义,所以字面量没有显示的类型,它的静态类型只能通过语言上的规则去理解和推断。
----《深入理解java虚拟机》周志明
因此,对于重载方法匹配时存在优先级,参数匹配时,数据类型存在如下自动转换,来匹配更合适的方法。
char->int->long->float->double->装箱类型
3.4 父子间的重载?
父类和子类之间存在重载吗?准确来说父子间的重载没有意义。
因为子类重载父类的方法只有在声明类型是子类的时候才会被调用,而声明类型是具体子类时,会使代码缺乏扩展性,违反开闭原则。
4. 接口、抽象类
4.1 继承、实现
继承:子类与父类之间用 extends 来表示继承关系,子类只能有一个父类。子类继承抽象类时,除非声明当前类是抽象类,否则必须重写抽象方法。
实现:子类与接口之间用implements 来表示实现的接口,除非声明当前类是抽象类,否则必须实现接口声明的所有非默认方法。
4.2 接口、抽象类区别
抽象类 | 接口 | |
---|---|---|
非常量成员变量 | 有 | 无 |
构造方法 | 可以有 | 无 |
方法实现 | 有 | jdk8开始有默认方法,default修饰 也可以有接口静态方法, static修饰 |
抽象方法的修饰符 | 只能为public或者protected,默认为public | 只能为public |
继承方式 | 子类只能继承一个抽象类 | 子类可以实现多个接口 |
4.3 菱形继承问题
java8中引入接口的默认方法,由此可能会造成同一方法继承自父类或多个接口,当出现此类问题时,按如下规则进行匹配: