Java学习笔记-Day17 Java 类中的执行顺序、Obejct类和包装类
- 一、Java 类中的执行顺序
- 1、类的构成
- 1.1、静态代码块
- 1.2、构造代码块
- 1.3、构造方法
- 1.4、普通代码块
- 2、执行顺序
- 二、Obejct类
- 1、equals方法
- 2、hashCode方法
- 3、在自定义类中重写equals方法和hashCode方法
- 4、finalize方法
- 5、native关键字
- 三、包装器类
- 1、装箱和拆箱
- 1、装箱
- 2、拆箱
- 2、自动装箱和自动拆箱
- 3、何时会触发自动装箱和自动拆箱
- 4、常量池
- 5、例题
- 四、instanceof运算符
一、Java 类中的执行顺序
1、类的构成
1.1、静态代码块
在类中(方法中不能存在),使用 static 和 { } 声明的代码块,在类被加载的时候运行,只会运行一次,优先于各种代码块和构造方法执行。
如果在一个类中有多个静态代码块的话,则会按顺序执行。
1.2、构造代码块
在类中,使用 { } 声明的代码块,在创建对象时被调用,每创建一次对象就会调用一次,优先于构造方法执行。
如果在一个类中有多个的话,则会按顺序执行。
1.3、构造方法
命名必须与类名完全相同,通过 new 运算符创建对象时才会调用。
1.4、普通代码块
在方法中,使用 { } 声明的代码块,按方法中书写的顺序执行。
如果在一个类中有多个的话,则会按顺序执行。
2、执行顺序
执行顺序:
父类的静态变量 --> 父类的静态代码块 --> 子类的静态变量 --> 子类的静态代码块 --> 父类的成员变量 --> 父类的构造代码块 --> 父类的构造方法 --> 子类的成员变量 --> 子类的构造代码块 --> 子类的构造方法
示例:
class HelloA {
//父类的静态变量
static String sa = "sa";
//父类的成员变量
String a = "a";
//父类的构造方法
public HelloA() {
System.out.println("HelloA");
}
//父类的构造代码块
{
System.out.println("I'm A class");
}
//父类的静态代码块
static {
System.out.println("static A");
}
}
public class HelloB extends HelloA {
//子类的静态变量
static String sb = "sb";
//子类的成员变量
String b = "b";
//子类的构造方法
public HelloB() {
System.out.println("HelloB");
}
//子类的构造代码块
{
System.out.println("I'm B class");
}
//子类的静态代码块
static {
System.out.println("static B");
}
public static void main(String[] args) {
new HelloB();
}
}
代码的执行结果:
二、Obejct类
Object类是Java语言中所有类的父类,所有的类都直接或间接的继承了Object类。数组也继承了Object类。
1、equals方法
Object类中的equals方法的作用,与 == 相同,都是比较两个对象的内存地址值是否相同。Java中的很多类都对equals()方法进行了重写,用来比较两个对象的属性的值,如果属性的值相等,则认为两个对象相等。
String类对Object类的equals()方法进行了方法重写,用来比较两个字符串的字符序列值,即用来比较两个字符串的值是否相同。
String类中equals()方法的基本思想是将两个字符串转换成字符数组,逐个对比字符是否一致,若字符都一致,则这两个对象相等。
String类中equals()方法的源码:
//String类中的equals方法的源码,重写Object类中的equals方法。
public boolean equals(Object anObject) {
//1、比较anObject对象和当前对象的内存地址值,判断是否为同一个对象
if (this == anObject) {
return true;
}
//2、判断anObject对象是否为String类
/*
* anObject是Object类,
* equals方法的形式参数要接收是String类的对象,
* Object类是String类的父类,
* 即父类引用指向子类对象(多态),
* 所以anObject本质上还是String类。
*/
if (anObject instanceof String) {
//3、anObject是Object类,需要将anObject对象强制转换为String类
String anotherString = (String)anObject;
//4、获取字符数组的长度
int n = value.length;
//5、比较 anotherString转换成字符数组的长度是否相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
//6、逐个对比两个字符数组的字符元素是否一致
/*
* 循环递减直到字符数组的长度为0,
* 从下标0开始依次比较两个字符数组。
*/
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
2、hashCode方法
Object类中定义了hashCode方法 public int hashCode() ,用来返回对象的哈希码。哈希码是JDK根据对象的内存地址或者字符串或者数字计算出来的int类型的数值。哈希码是唯一的,是对象的标识,用来证明对象的身份。
3、在自定义类中重写equals方法和hashCode方法
在自定义类中重写equals方法和hashCode方法,需要先添加几个成员变量。再通过 右键 -> Source -> Generate hashCode() and equals() -> Select All -> Generate。重写的equals方法是根据当前类的属性的值来比较两个对象,如果两个对象的属性的值都相同,则返回true。重写的hashCode方法是根据当前类的属性的值进行数学运算返回哈希值。
public class Execise {
private String name;
private int age;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Execise other = (Execise) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
4、finalize方法
Object中包含了一个叫做finalize()的方法,提供在对象被回收时调用以释放资源,在默认情况下其不执行任何动作。由于Object是Java所有类的父类,因此事实上所有的Java类都具备finalize方法。当垃圾回收器确定了一个对象没有任何引用时,就会调用该类的finalize()方法。
finalize()方法并不保证调用时机,因此也不建议重写finalize()方法。如果必须要重写finalize()方法,请记住使用super.finalize()调用父类的清除方法,否则对象清理的过程可能不完整。每个对象只能被垃圾回收器自动调用finalize( )方法一次。
如果在finalize( )方法执行时产生异常Exception,则该对象仍可以被垃圾回收器收集。
当finalize( )方法尚未被调用时,System. runFinalization( )方法可以用来调用finalize( )方法,并实现相同的效果,对无用对象进行垃圾收集。
5、native关键字
native为Java本地接口(Java Native Interface),使用的native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且编译成了DLL,由Java去调用,这些函数的实现体在DLL中,JDK的源码并不包含这些函数。
三、包装器类
包装器类是将基本的数据类型和一些辅助方法包装到一个类中。
Java语言中有8个基本数据类型,对应有8个类,这8个类统称包装器类(Wrapper类)。
基本数据类型 | byte | short | int | long | float | double | char | boolean |
包装器类 | Byte | Short | Integer | Long | Float | Double | Character | Boolean |
1、装箱和拆箱
1、装箱
装箱:基本数据类型转换为包装器类型,称为装箱(boxing)。例如,int型转换为Integer类型。
语法:
① 通过Integer类的有参构造方法(给Integerl类中的常量value赋值)创建一个Integer对象。
源码:
private final int value;
public Integer(int value) {
this.value = value;
}
示例:
//通过Integer类的有参构造方法
Integer integer1 = new Integer(10);
② 通过Integer类的静态方法 valueOf() 返回一个Integer对象。
源码:
/*
*如果 i 的值在 IntegerCache.low ~ IntegerCache.high,
*则直接返回IntegerCache.cache[i + (-IntegerCache.low)],
*没有新构建对象,而是返回的一个数组中的元素。
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
示例:
//通过Integer类的静态方法valueOf
Integer integer2 = Integer.valueOf(10);
2、拆箱
拆箱:包装器类型转换为基本数据类型,称为拆箱(unboxing)。例如Integer类型转换为int类型。
语法:通过Integer类的成员方法 intValue() 返回一个int类型的值。
源码:
public int intValue() {
return value;
}
示例:
//通过Integer类的方法intValue()返回一个int类型的值
int a = integer1.intValue();
2、自动装箱和自动拆箱
JDK1.5之后提供自动拆装箱。
java源文件中:
// 自动装箱
Integer integer1 = 1;
// 自动拆箱
int a= integer1;
编译后的字节码文件:
3、何时会触发自动装箱和自动拆箱
(1)在进行装箱和拆箱的赋值操作的时候。
(2)涉及包装类的对象之间的基本加减乘除操作时是需要拆装成基本类型计算后,然后再装箱。
(3)包装类和对应的基本数据类型比较时,java会自动拆包装为基本数据类型,然后进行比较,实际上就变为两个基本数据类型的变量的比较。
4、常量池
常量池就是方法区的一部分。在自动装箱拆箱过程中,Java使用到了常量池,在自动装箱拆箱过程中,只有赋值给Integer对象的数值是 -128~127 范围内的时候,才使用到常量池,否则都是分配新的内存空间。
缓存处理:在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。
IntegerCache是 Integer 类的一个私有静态内部类,其中关键部分是 static final Integer cache[];
,即它内部保存了Integer类型的数组,用来缓存值在 IntegerCache.low
~ IntegerCache.high
( -128~ 127 )之间的Integer对象。如果数据在-128~ 127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。当自动装箱发生时或者或者手动调用valueOf()时,就会先判断Integer指向对象的值是否在-128 ~ 127的区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象。
有xxxCache私有静态内部类的包装器类就有常量池。
Byte、Short、Integer、Long、Character、Boolean包装器类都有常量池,Boolean有True和False两个实例,这两个实例在常量池中。而Float和Double没有常量池。
IntegerCache类的源码:
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() {}
}
例如Integer类,在自动装箱 (使用Integer类的valueOf方法)时,对于Integer i = 数值;
,如果 i 指向的对象的值是在-128 至 127 范围内时,则生成的Integer对象是由 IntegerCache.cache()
方法产生,它会复用已有对象。
Integer类的valueOf()方法源码:
/*
*如果 i 的值在IntegerCache.low ~ IntegerCache.high(-128~127),
*则直接返回IntegerCache.cache[i + (-IntegerCache.low)],
*没有新构建对象,而是返回的一个数组中的元素。
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在这个数值区间内的 Integer对象的引用名可以直接使用 == 进行判断,因为值相同,指向的是同一个对象。但是这个数值区间以外的所有数据,自动装箱都会在堆上创建一个新的对象,并不再复用已有对象,为了避免这个问题,推荐使用 equals 方法进行Integer的判断。
而对于采用new运算符对包装器类进行创建对象时,不会发生对象的复用,因为new关键字每次使用,都会在堆中创建新的对象。
5、例题
public class Execise {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c == d);//true
System.out.println(e == f);//false
System.out.println(c == (a + b));//true
System.out.println(c.equals(a + b));//true
System.out.println(g == (a + b));//true
System.out.println(g.equals(a + b));//false
System.out.println(g.equals(a + h));//true
}
}
编译后的字节码文件:
四、instanceof运算符
instanceof 运算符 用于判断该运算符前面的 引用类型变量指向的对象 是否是 后面的类,或者其子类、接口实现类创建的对象。如果是则返回true,否则返回false,
格式: 引用类型变量 instanceof 类、抽象类或接口