【JAVA】面向对象一

本文详细介绍了Java中的面向对象特性,包括继承、封装和多态。讲解了构造器、方法重写、super关键字的使用,以及如何通过实例变量和类变量进行对象初始化。此外,还探讨了Java中的包、访问控制符、静态成员和非静态成员的差异,以及对象和类的关系。文章以实例展示了如何在实际编程中运用这些概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、 面向对象

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);
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值