一、 面向对象
Java完全支持面向对象的三种基本特征:继承、封装和多态。
1.1 结构化程序设计
结构化程序设计方法主张按功能来分析系统需求,其主要原则可概括为自顶向下、逐步求精、模块化等。
1.2 三种结构
顺序、选择、循环
1.3 设计简介
1.4 基本特征
1.4.1 继承
对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法
若是子类重写父类方法,当调用是先调用子类重写方法。
1.4.1.1设计父类通常遵循如下规则
尽量隐藏父类的内部数据。尽量把父类的所有成员变量都设置成private访问类型,不要让子类直接访问父类的成员变量。
.
不要让子类可以随意访问、修改父类的方法。
父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法访问该方法;
如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符来修饰该方法:
如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则可以使用protected来修饰该方法。
.
尽量不要在父类构造器中调用将要被子类重写的方法。
1.4.1.2 从父类派生新的子类?
子类需要额外增加属性,而不仅仅是属性值的改变。
子类需要增加自己独有的行为方式(包括增加新的方法或重写父类的方法)。
继承要表达的是一种“是(is-a)”的关系,而组合表达的是“有(has-a)”的关系。
1.4.1.3 方法重写
方法名相等,形参列表相等
注意一大两小
一大
子类方法的访问权限比父类方法的访问权限更大或相等
两小
子类方法返回值类型比父类方法返回值类型更小或相等
子类声明抛出的异常类比父类方法声明抛出的异常类更小或相等。
重写和被重写方法要么都是类方法要么都是实例方法
1.4.1.4 super
当子类覆盖了父类方法后,子类的对象将无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。如果需要在子类方法中调用父类中被覆盖的方法,则可以使用supr(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。
this不能出现在static修饰的方法中一样,super也不能出现在static修饰的方法中。static修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,因而super限定也就失去了意义。
如果在构造器中使用super,则super用于限定该构造器初始化的是该对象从父类继承得到的实例变量,而不是该类自己定义的实例变量。
class SubClass{
public int a = 5;
}
public class BaseClass extends SubClass{
public int a = 7;
void ac(){
System.out.println(a);
}
void acc(){
System.out.println(super.a); // 从父类得到a
}
public static void main(String[] args) {
BaseClass b = new BaseClass();
b.ac(); //7
b.acc(); //5
}
}
当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。
1.4.1.5 调用父类构造器
子类不会获得父类的构造器,但子类构造器里可以调用父类构造器的初始化代码
在一个构造器中调用另一个重载的构造器使用this调用来完成,在子类构造器中调用父类构造器使用super调用来完成。
当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行;不仅如此,执行父类构造器时,系统会再次上溯执行其父类构造器…依此类推,创建任何Java对象,最先执行的总是java.lang.Object类的构造器。
1.4.2 封装
对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
1.4.2.1访问控制符
1.4.2.2 包
package
package语句,就意味着该源文件里定义的所有类都属于这个包。如果其他人需要使用该包下的类,也应该使
用包名加类名的组合。一个Java源文件只能包含一个package语句
import
import可以向某个Java文件中导入指定包层次下某个类或全部类,import语句应该出现在package语句之后、类定义之前。一个Java源文件可以包含多个import语句,多个import语句用于导入多个包层次下的类。
import static
静态导入也有两种语法,分别用于导入指定类的单个静态成员变量、方法和全部静态成员变量、方法
1.4.2.3 Java常用包
java.lang: Java语言的核心类,如String、Math、System和Thread类等,使用这个包下的类无须使用import语句,系统会自动导入这个包下的所有类。
java.util: 这个包下包含了Java的大量工具类/接口和集合框架类/接口,例如Arrays和List、Set等。
java.net: 这个包下包含了一些Java网络编程相关的类/接口。
java.io: 这个包下包含了一些Java输入/输出编程相关的类/接口。
java.text: 这个包下包含了一些Java格式化相关的类。
java.sql: 这个包下包含了Java进行JDBC数据库编程的相关类/接口。
java.awt: 这个包下包含了抽象窗口工具集的相关类/接口,这些类主要用于构建图形用户界面程序。
java.swing: 这个包下包含了Swing图形用户界面编程的相关类/接口,这些类可用于构建平台无关的GUI程序。
1.4.3 多态
指的是子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征
1.4.3.1 多态性
Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态.
public class Base {
public int book = 6;
public void base(){
System.out.println("父类的普通方法");
}
public void test() {
System.out.println("父类被覆盖的方法");
}
}
public class Sub extends Base{
private String book = "java";
@Override
public void test() {
System.out.println("子类覆盖父类");
}
public void common() {
System.out.println("子类定义新的方法");
}
public static void main(String[] args) {
Base b = new Base();
b.base(); //父类的普通方法
b.test(); //父类被覆盖的方法
System.out.println(b.book); //6
System.out.println("-----------------------");
Sub s = new Sub();
s.base(); //父类的普通方法
s.test(); //子类覆盖父类
System.out.println(s.book); //java
System.out.println("-----------------------");
//多态,编译时类型和运行时类型不一致,编译是Base,运行是Sub
Base a = new Sub();
a.base(); //父类的普通方法
a.test(); //子类覆盖父类,
//因为Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,被称为向上转型,向上转型由系统自动完成。
System.out.println(a.book); //6
//与方法不同的是,对象的实例变量则不具备多态性。所以输出还是base类中
a.common(); //报错,运行时执行的是Base类的,无法调用common
//java: 找不到符号
// 符号: 方法 common() 位置: 类型为g.Base的变量 a
}
}
1.4.3.2 引用变量的强制类型转换
对象的实例变量则不具备多态性,只用编译型类型,若想用运行时类型,则要强制类型转换成运行时类型。
强制类型转换时需要注意:
基本类型之间的转换只能在数值类型之间进行,数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。
引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行,否则将在运行时引发ClassCastException异常。
public class ConversionTest {
public static void main(String[] args) {
double d = 13.4;
long l = (long) d;
System.out.println(l); //13
int n = 5;
// boolean b = (boolean) n; //java: 不兼容的类型: int无法转换为boolean
Object obj = "hello";
String s = (String) obj;
System.out.println(s); //hello Object与String存在继承关系,可强制类型转换
Object o = Integer.valueOf(5);
System.out.println(o); //5 编译时类型为Object,实际类型Integer,Object与Integer存在继承关系,可强制类型转换,实际类型是Integer
String str = (String) o;
System.out.println(str); //报错:java.lang.ClassCastException,运行的是Integer
}
}
1.4.3.3 instanceof运算符
instanceof运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回true,否则返回false。
在使用instanceof运算符时需要注意:
instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误。
public class InstanceofTest {
public static void main(String[] args) {
Object hello = "hello";
System.out.println(hello instanceof Object); //true ,字符串是否是Object实例
System.out.println(hello instanceof String); //true Object是所有类的父类,hello变量实际是string类型
System.out.println(hello instanceof Math); //false
System.out.println(hello instanceof Comparable); //true string实现Comparable接口
String a = "a";
System.out.println(a instanceof Math); //java: 不兼容的类型: java.lang.String无法转换为java.lang.Math 因为没有string和Math没有继承关系
二、 类和对象
可用类定义变量,这种类型被称为引用变量,所有类都是引用类型
2.1 定义成员变量语法
修饰符:修饰符可以省略,也可以是public、protected、private、static、final,其中public、protected、private三个最多只能出现其中之一,可以与static、final组合起来修饰成员变量。
类型:类型可以是Java语言允许的任何数据类型,包括基本类型和引用类型。
成员变量名:成员变量名只要是一个合法的标识符即可,成员变量名应该由一个或多个有意义的单词连缀而成,第一个单词首字母小写,后面每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符。
成员变量用于描述类或对象包含的状态数据
默认值:定义成员变量还可以指定一个可选的默认值。
2.2 static
static是一个特殊的关键字,通常把static修饰的成员变量和方法也称为类变量、类方法。它可用于修饰方法、成员变量等成员。static修饰的成员表明它属于这个类本身,而不属于该类的单个实例
不使用static修饰的普通方法、成员变量则属于该类的单个实例,而不属于该类。
有时也把static修饰的成员变量和方法称为静态变量和静态方法,把不使用static修饰的成员变量和方法称为非静态变量和非静态方法。
静态成员不能直接访问非静态成员。
2.3 构造器
格式
修饰符:修饰符可以省略,也可以是public、protected、private其中之一。
构造器名:构造器名必须和类名相同。
形参列表:和定义方法形参列表的格式完全相同。
构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类通常无法创建实例。
构造器是一个特殊的方法,这个特殊方法用于创建实例时执行初始化。构造器是创建对象的重要途径(即使使用工厂模式、反射等方式创建对象,其实质依然是依赖于构造器),因此,Java类必须包含一个或一个以上的构造器。
构造器既不能定义返回值类型,也不能使用Void声明构造器没有返回值如果用,编译时不会出错,但Java会把这个所谓的构造器当成方法来处理一一它就不再是构造器。
为什么不能用void?
答:类的构造器是有返回值的,当使用new关键字来调用构造器时,构造器返回该类的实例,可以把这个类的实例类成构造器的返回值,构造器的返回值类型总是当前类,无须定义返回值类型。不要在构造器里显式使用return来返回当前类的对象,困为构造器的返回值是隐式的。
2.3.1 使用构造器执行初始化
构造器最大的用处就是在创建对象时执行初始化。当创建一个对象时,系统为这个对象的实例变量进行默认初始化,这种默认的初始化把所有基本类型的实例变量设为0(对数值型实例变量)或false(对布尔型实例变量),把所有引用类型的实例变量设为null。
如果想改变这种默认的初始化,想让系统创建对象时就为该对象的实例变量显式指定初始值,就可以通过构造器来实现。
public class ConstructorTest {
private String name; //实例变量
private int count; //类变量
public ConstructorTest(String name,int count){
this.name = name;
this.count = count;
}
public static void main(String[] args) {
//对对象执行自定义初始化
ConstructorTest c = new ConstructorTest("ll",12);
System.out.println(c.name); //ll
System.out.println(c.count); //12
}
}
2.3.2 构造器重载
同一个类里具有多个构造器,多个构造器的形参列表不同,即被称为构造器重载。构造器重载允许Java类里包含多个初始化逻辑,从而允许使用不同的构造器来初始化Java对象。
public class ConstructorTest {
private String name; //实例变量
private int count; //类变量
public ConstructorTest(String name,int count){
this.name = name;
this.count = count;
}
public ConstructorTest(String name){
this.name = name;
this.count = count;
}
public static void main(String[] args) {
//对对象执行自定义初始化
ConstructorTest c1 = new ConstructorTest("la");
ConstructorTest c2 = new ConstructorTest("ll",12);
System.out.println(c1.name); //la
System.out.println(c2.name); //1l
}
}
2.4 对象
用new关键字来调用某个类的构造器即可创建这个类的实例
//定义P变量的同时并为P变量赋值
Person p = new Person();
p.name = "李刚";
p.age = 0;
static修饰的方法和成员变量,既可通过类来调用,也可通过实例来调用;
没有使用static修饰的普通方法和成员变量,只可通过实例来调用。
堆内存里的对象可以有多个引用,即多个引用变量指向同一个对象
//将P变量的值赋值给P2变量
Person p2=p;
2.4.1 this
Java提供了一个this关键字,this关键字总是指向调用该方法的对象。根据this出现位置的不同,this作为对象的默认引用有两种情形。
构造器中引用该构造器正在初始化的对象。
在方法中引用调用该方法的对象。
this可以代表任何对象,只有当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,this就代表谁。
public class StaticAccessNonstatic {
public void in(){
System.out.println("-------------------------------------");
}
public void run(){
//可以不用创建,调用run(),肯定提供一个StaticAccessNonstatic对象,就可以用已存在的,可不加this
this.in();
}
public static void main(String[] args) {
StaticAccessNonstatic s = new StaticAccessNonstatic (); //必须实例,因为main用static修饰的
s.run();
}
}
2.4.2 静态成员为什么不能直接访问非静态成员?
如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象。由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问不使用static修饰的普通成员
public class StaticAccessNonstatic {
public void info(){
System.out.println("-------------------------------------");
}
public static void main(String[] args) {
info();
}
}
//报错点: java: 无法从静态上下文中引用非静态方法 info()
解释:info()属于实例的方法,不属于类方法,所以必须使用对象。
在上面的main()方法中直接调用info()方法时,系统相当于使用this作为该方法的调用者,而main()方法是一个static修饰的方法,static修饰的方法属于类,而不属于对象,因此调用static修饰的方法的主调总是类本身
解决2方法:
public class StaticAccessNonstatic {
public void info(){ //1.加static
System.out.println("-------------------------------------");
}
public static void main(String[] args) {
//2.新建一个对象
new StaticAccessNonstatic.info();
}
}
2.5 方法
方法是类或对象的行为特征的抽象,方法是类或对象最重要的组成部分。
2.5.1 方法属性
方法与传统的函数有着显著不同:在结构化编程语言里,函数是一等公民,整个软件由一个个的函数组成:在面向对象编程语言里,类才是一等公民,整个系统由一个个的类组成。因此在Java语言里,方法不能独立存在,方法必须属于类或对象。
Java语言是静态的。一个类定义完成后,只要不再重新编译这个类文件,该类和该类的对象所拥有的方法是固定的,永远都不会改变。
同一个类的一个方法调用另外一个方法时,如果被调方法是普通方法,则默认使用this作为调用者;如果被调方法是静态方法,则默认使用类作为调用者。也就是说,表面上看起来某些方法可以被独立执行,但实际上还是使用this或者类来作为调用者。
2.5.2 方法参数传递机制
如果声明方法时包含了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时实际传给形参的参数值也被称为实参。
那么,Java的实参值是如何传入方法的呢?这是由Java方法的参数传递机制来控制的,Java里方法的参数传递方式只有一种:值传递。所谓值传递,就是将**实际参数值的副本(复制品)**传入方法内,而参数本身不会受到任何影响。
基本参数传递
public class PrimitveTransferTest {
public static void swap(int a,int b){
int tmp = a;
a = b;
b = tmp;
System.out.println("swap方法里,a的值是: "+a+",b的值是: "+b); //swap方法里,a的值是: 9,b的值是: 6
}
public static void main(String[] args) {
int a = 6;
int b = 9;
swap(a,b);
System.out.println("交换后swap方法里,a的值是: "+a+",b的值是: "+b); //交换后swap方法里,a的值是: 6,b的值是: 9
}
}
当程序执行swap()方法时,系统进入swap()方法,并将main()方法中的a、b变量作为参数值传入swap()方法,传入swap()方法的只是a、b的副本,而不是a、b本身,进入swap()方法后系统中产生了4个变量,这4个变量在内存中的存储示意图如图所示。
main()方法中的a、b变量作为参数,值传入swap()方法,实际上是在swap()方法栈区中重新产生了两个变量a、b,并将main()方法栈区中a、b变量的值分别赋给swap()方法栈区中的a、b参数(就是对swap()方法的a、b形参进行了初始化)。此时,系统存在两个a变量、两个b变量,只是存在于不同的方法栈区中而已。
这就是值传递的实质:当系统开始执行方法时,系统为形参执行初始化,就是把实参变量的值赋给方法的形参变量,方法里操作的并不是实际的实参变量。
引用类型传递
class DataWrap{
int a ;
int b ;
}
public class PrimitveTransferTest {
public static void swap(DataWrap dw) {
int tmp = dw.a;
dw.a = dw.b;
dw.b = tmp;
System.out.println("swap方法里,a的值是: " + dw.a + ",b的值是: " + dw.b); //swap方法里,a的值是: 9,b的值是: 6
}
public static void main(String[] args) {
DataWrap dw = new DataWrap();
dw.a = 6;
dw.b = 9;
swap(dw);
System.out.println("交换后swap方法里,a的值是: " + dw.a + ",b的值是: " + dw.b); //交换后swap方法里,a的值是: 9,b的值是: 6
}
}
程序从main()方法开始执行,main()方法开始创建了一个Data Wrap对象,并定义了一个dw引用变量来指向Data Wrap对象,dw引用,保存Data Wrap对象地址。系统只复制了dw变量,并未复制Data Wrap对象。
证明main()方法中的dw和swap()方法中的dw是两个变量,在swap()方法的最后一行增加如下代码:
public class PrimitveTransferTest {
public static void swap(DataWrap dw) {
int tmp = dw.a;
dw.a = dw.b;
dw.b = tmp;
//把dw直接赋值为null,让它不再指向任何有效地址
dw = null;
// System.out.println("swap方法里,a的值是: " + dw.a + ",b的值是: " + dw.b);
//java.lang.NullPointerException空指针,但是main()方法里依旧可以读
}
2.5.3 形参可变的方法
public class Varargs {
public static void test(int a, String... books){ //定义形参个数可变的方法
for (String tmp : books){ //books被当成数组处理
System.out.println(tmp);
}
System.out.println(a); //输出整数变量a的值
}
public static void main(String[] args) {
test(5,"java","python","c");
}
}
数组
public class Varargs {
public static void test(int a, String[] books){ //定义形参个数可变的方法
for (String tmp : books){ //books被当成数组处理
System.out.println(tmp);
}
System.out.println(a); //输出整数变量a的值
}
public static void main(String[] args) {
test(5,new String[]{"java","python","c"});
}
}
2.5.4 方法重载
方法同名,但是形参列表必须不同,方法中其他部分,如方法返回值类型、修饰符等,与重载没有任何关系
public class Overload {
public void test(){
System.out.println("无参数");
}
public void test(String name){
System.out.println("有参数");
}
public static void main(String[] args) {
Overload l = new Overload();
l.test(); //根据类型调用无参数
l.test("ll"); //调用字符串
}
}
2.6 成员变量和局部变量
类变量从该类的准备阶段起开始存在,直到系统完全销毁这个类,类变量的作用域与这个类的生存范围相同:实例变量则从该类的实例被创建起开始存在,直到系统完全销毁这个实例,实例变量的作用域与对应实例的生存范围相同。
class PersonTest{
public String name; //实例变量
public static int age; //类变量
}
public class Person {
public static void main(String[] args) {
PersonTest p = new PersonTest();
System.out.println("初始化的类变量值: " + p.age); //0
System.out.println("无显示声明初始化的类变量值: " + PersonTest.age); //0
System.out.println("未被赋值实例变量值: " + p.name);
p.name = "ll";
p.age = 14;
System.out.println("实例变量值: " + p.name); //实例变量值: ll
System.out.println("对象调用时类变量值: " + p.age); //对象调用时类变量值: 14
PersonTest.age = 15;
System.out.println("类变量值: " + PersonTest.age); //类变量值: 15
}
}
局部变量根据定义形式的不同,又可以被分为如下三种。
形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效。
方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效。
代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到该代码块结束时失效。
与成员变量不同的是,局部变量除形参之外,都必须显式初始化。也就是说,必须先给方法局部变量和代码块局部变量指定初始值,否则不可以访问它们。形参的作用域是整个方法内。
{
int a;
a= 5; //错误,a此时还未被初始化
}
Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用this(对于实例变量)或类名(对于类变量)作为调用者来限定访问成员变量。
public class VariableoverrideTest {
private String name = "ll"; //实例变量
private static int age = 15; //类变量
public static void main(String[] args) {
String name = "ab";
int age = 25;
System.out.println(age); //25 覆盖掉了上面的局部变量
System.out.println(VariableoverrideTest.age); //15
System.out.println(name); //ab
VariableoverrideTest v = new VariableoverrideTest();
System.out.println(v.name); //ll
}
}
2.6.1 成员变量的初始化和内存中的运行机制
2.6.2 局部变量的初始化和内存中的运行机制
局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。
栈内存中的变量无须系统垃圾回收,往往随方法或代码块的运行结束而结束。因此,局部变量的作用域是从初始化该变量开始,直到该方法或该代码块运行完成而结束。因为局部变量只保存基本类型的值或者对象的引用,因此局部变量所占的内存区通常比较小。
2.7 初始化
Java使用构造器来对单个对象进行初始化操作,使用构造器先完成整个Java对象的状态初始化,然后将Java对象返回给程序。
2.7.1 使用初始化块
相同类型的初始化块之间有顺序:前面定义的初始化块先执行,后面定义的初始化块后执行
初始化块的修饰符只能是static,使用static修饰的初始化块被称为静态初始化块。初始化块里的代码可以包含任何可执行性语句,包括定义局部变量、调用其他对象的方法,以及使用分支、循环语句等。
从上往下执行
public class PersonTest111 {
{
System.out.println("aaa");
{
System.out.println("bbb");
}
}
{
System.out.println("ccc");
}
public PersonTest111(){
System.out.println("ddd");
}
public static void main(String[] args) {
{
new PersonTest111();
}
}
}
初始化块虽然也是Java类的一种成员,但它没有名字,也就没有标识,因此无法通过类、对象来调用初始化块。初始化块只在创建Jva对象时隐式执行,而且在执行构造器之前执行。
2.7.2 初始化块和构造器
从某种程度上来看,初始化块是构造器的补充,初始化块总是在构造器执行之前执行。系统同样可
使用初始化块来进行对象的初始化操作。
与构造器不同的是,初始化块是一段固定执行的代码,它不能接收任何参数。因此初始化块对同一
个类的所有对象所进行的初始化处理完全相同。把两个构造器中的代码提取成初始化块示意图。
如果两个构造器中有相同的初始化代码,且这些初始化代码无须接收参数,就可以把它们放在初始化块中定义。
通过把多个构造器中的相同代码提取到初始化块中定义,能更好地提高初始化代码的复用,提高整个应用的可维护性。
实际上初始化块是一个假象,使用javac命令编译Java类后,该Java类中的初始化块中代码会被“还原”到每个构造器中,且位于构造器所有代码的前面。
2.7.3 静态初始化块
使用了static修饰符,则这个初始化块就变成了静态初始化块,也被称为类初始化块(普通初始化块负责对对象执行初始化,类初始化块则负责对类进行初始化)。
静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行。因此静态初始化块总是比普通初始化块先执行。
静态初始化块是类相关的,用于对整个类进行初始化处理,通常用于对类变量执行初始化处理。静态初始化块不能对实例变量进行初始化处理。
先执行java.lang.Object类的静态初始化块(如果有),然后执行其父类的静态初始化块…最后才执行该类的静态初始化块,经过这个过程,才完成了该类的初始化过程。只有当类初始化完成后,才可以在系统中使用这个类,包括访问这个类的类方法、类变量或者用这个类来创建实例。
class Root{
static {
System.out.println("root静态");
}
{
System.out.println("root普通");
}
public Root(){
System.out.println("root无参");
}
}
class Mid extends Root{
static {
System.out.println("Mid静态");
}
{
System.out.println("Mid普通");
}
public Mid(){
System.out.println("Mid无参");
}
public Mid(String name){
this();
System.out.println("Mid无参");
}
}
class Leaf extends Mid{
static {
System.out.println("Leaf静态");
}
{
System.out.println("Leaf普通");
}
public Leaf(){
super("java");
System.out.println("Leaf无参");
}
}
public class Test1 {
public static void main(String[] args) {
new Leaf();
new Leaf();
}
}
/**
root静态
Mid静态
Leaf静态
root普通
root无参
Mid普通
Mid无参
Mid无参
Leaf普通
执行left===================
root普通
root无参
Mid普通
Mid无参
Mid无参
Leaf普通
执行left===================
*/
第一次创建一个Leaf对象时,因为系统中还不存在Leaf类,因此需要先加载并初始化Leaf类,初始化Leaf类时会先执行其顶层父类的静态初始化块,再执行其直接父类的静态初始化块,最后才执行Leaf本身的静态初始化块。
一旦Leaf类初始化成功后,Leaf类在该虚拟机里将一直存在,因此当第二次创建Leaf实例时无须再次对Leaf类进行初始化。
每次创建一个Leaf对象时,都需要先执行最顶层父类的初始化块、构造器,然后执行其父类的初始化块、构造器…最后才执行Laf类的初始化块和构造器。
public class StaticInitTest{
//先执行静态初始化块将ā静态成员变量赋值为6
static{
a=6;
}
//再将a静态成员变量赋值为9
static int a=9;
public static void main(String[]args)
//下面代码将输出9
System.out.printIn(StaticInitTest.a);
}
}