当前位置:   article > 正文

【Java】类和对象详解_面向对象编程(一):类与对象

面向对象编程(一):类与对象


一、面向对象

1.1 什么是面向对象

面向对象(Object-oriented)是一种程序设计的方法和编程范式,它以对象作为程序的基本单位,通过封装、继承、多态等概念来组织和实现程序逻辑。面向对象的编程思想强调将问题分解为对象的集合,每个对象具有自己的状态(属性)和行为(方法),并通过相互之间的消息传递来实现协助和交互。

1.2 面向对象与面向过程

面向对象和面向过程是两种不同的编程范式,它们在解决问题和设计程序时有着不同的思维方式和方法。

  • 面向过程编程(Procedural Programming)是一种以过程为中心的编程方式,将程序视为一系列的步骤或过程的集合。它关注如何完成任务,通过编写一系列的函数来实现功能,函数接受输入,执行一系列操作,并返回输出。面向过程编程强调算法和步骤的顺序,逐步解决问题。

  • 面向对象编程(Object-Oriented Programming)是一种以对象为中心的编程方式,将程序视为一系列的对象的集合,这些对象通过相互之间的消息传递来协作和交互。面向对象编程关注问题的建模,将问题分解为对象,每个对象具有自己的状态和行为,并通过封装、继承和多态等机制来实现代码的模块化、重用性和灵活性。

例如洗衣服的例子,我们可以将传统洗衣服的过程与现代洗衣服的过程进行对比:

传统洗衣服(面向过程):

  1. 收集脏衣服。
  2. 排序脏衣服。
  3. 填充水槽。
  4. 加入洗衣粉。
  5. 将脏衣服放入水槽中。
  6. 搓揉脏衣服。
  7. 冲洗脏衣服。
  8. 拧干脏衣服。
  9. 晾干脏衣服。

在面向过程的洗衣服过程中,步骤和操作是线性的,每一步都按照特定的顺序执行。

现代洗衣服(面向对象):

  1. 准备洗衣机对象。
  2. 将脏衣服对象放入洗衣机。
  3. 设定洗衣机的程序(例如洗涤时间、水温等)。
  4. 开始洗衣机的洗涤过程。
  5. 洗衣机自动完成洗涤、漂洗、脱水等过程。
  6. 完成洗涤后,取出干净衣服对象。

在面向对象的洗衣服过程中,洗衣机对象具有自己的状态(如洗涤时间、水温)和行为(如洗涤、脱水等),通过对象之间的消息传递好方法的调用来实现洗衣服的功能。整个过程更加的模块化,洗衣机对象自己管理洗涤的细节,而我们只需要与对象进行交互即可。通过这个例子我们就能很好地理解面向对象和面向过程

二、类的定义和实例化

2.1 类的定义

类(Class)是用来对一个实体(对象)进行描述的,主要包括实体(对象)的属性和行为的描述。在Java语言中,类是面向对象编程的基本组织单位,它是对象的模版或者蓝图,描述了对象的属性和行为。

在Java中,类具有以下特点和概念:

  1. 属性(Fields):类可以定义成员变量,也称为属性或字段。属性表示了对象的状态或特征,用于存储对象的数据。每个对象通过类的属性来描述自己的特定状态。

  2. 方法(Methods):类可以定义成员方法,也称为方法或函数。方法定义了对象的行为或操作,用于执行特定的功能。方法可以访问和操作类的属性,以及与其他对象进行交互。

  3. 对象实例化(Instantiation):类本身只是一个模板,需要通过实例化(Instantiation)来创建对象。通过关键字 “new” 加上类名,可以在内存中创建一个对象的实例。

  4. 构造方法(Constructor):构造方法是一种特殊的方法,在对象实例化时被调用,用于初始化对象的状态。构造方法与类同名,没有返回类型,并可以接受参数。

  5. 封装性(Encapsulation):类通过封装将数据和方法组合在一起,将对象的内部细节隐藏起来,只暴露出必要的接口供外部访问。通过访问修饰符(如private、public等),可以控制对类的属性和方法的访问权限。

  6. 继承(Inheritance):类可以通过继承(Inheritance)机制创建子类,子类继承了父类的属性和方法,并可以添加自己的特定功能。继承实现了代码的重用,提高了代码的可扩展性和可重用性。

  7. 抽象类(Abstract Class):抽象类是一种不能被实例化的类,它提供了一种用于派生子类的模板。抽象类可以包含抽象方法和具体方法,子类必须实现抽象方法才能被实例化。

  8. 接口(Interface):接口是一种纯抽象的类,它定义了一组方法的规范,但没有实际的实现。类可以实现一个或多个接口,实现接口的类必须实现接口中定义的所有方法。

通过定义类,可以实现对象的抽象和封装,使得程序具有更好的模块化、可维护性和可扩展性。类是Java中面向对象编程的核心概念,它为Java程序提供了结构和行为的定义。

2.2 类的创建

在Java中,创建类的格式如下:

访问修饰符 class 类名 {
    // 成员变量(属性)
    访问修饰符 数据类型 变量名;

    // 构造方法
    访问修饰符 类名(参数列表) {
        // 构造方法的实现
    }

    // 成员方法
    访问修饰符 返回类型 方法名(参数列表) {
        // 方法的实现
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这是一个基本的类的格式模板,下面是对各个部分的说明:

  1. 访问修饰符(Access Modifiers):用于控制类、成员变量和成员方法的访问权限,常用的访问修饰符包括 publicprivateprotected 和默认(不使用修饰符)。

  2. class 关键字:用于声明一个类。

  3. 类名:类的名称,采用大写开头的驼峰命名法。

  4. 成员变量(属性):用于表示类的属性或状态,定义在类的内部,但在方法之外。可以有多个成员变量,每个变量包括访问修饰符、数据类型和变量名。

  5. 构造方法:用于在对象实例化时进行初始化,与类同名,没有返回类型,并可以接受参数。构造方法的主要作用是为对象的属性赋初值。

  6. 成员方法:用于定义类的行为或操作,定义在类的内部,但在其他方法之外。可以有多个成员方法,每个方法包括访问修饰符、返回类型、方法名和参数列表。

通过上述格式,我们可以根据需求定义类,并在类中声明成员变量和成员方法。类提供了对象的模板,我们可以根据类创建对象的实例,并通过对象调用成员变量和成员方法来实现具体的功能。

例如,创建一个简单的Person类:

// 定义一个名为Person的类
public class Person {
    // 成员变量
    public String name;
    public int age;

    // 构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 成员方法
    public void sayHello() {
        System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
    }

    // 入口方法
    public static void main(String[] args) {
        // 创建一个Person对象实例
        Person person = new Person("John", 25);

        // 调用对象的成员方法
        person.sayHello();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 在上述例子中,我们定义了一个名为Person的类,该类具有两个成员变量(name 和 age)和一个构造方法。构造方法用于在创建对象时对对象进行初始化。类还有一个成员方法(sayHello),用于输出对象的信息。
  • 在类的入口方法main中,我们创建了一个Person对象实例,使用构造方法传入参数进行对象的初始化。然后,我们调用对象的成员方法sayHello,输出对象的信息。

2.3 类的实例化

定义了一个类,就相当于定义了一种新的类型,与intdouble类似,只不过intdouble是Java语言自带的内置类型,而类是用户自定义了一个新的类型,比如上述的:Person类。有了这些自定义的类型之后,就可以使用这些类来定义实例(对象)。

用类类型创建对象的过程,称为类的实例化,在Java中,可以使用以下几种方式实例化类:

  1. 使用关键字 new:最常见的实例化方式是使用关键字 new 加上类名,然后调用类的构造方法来创建一个类的实例。例如:
ClassName obj = new ClassName();
  • 1
  1. 使用构造方法重载:类可以定义多个构造方法,每个构造方法可以接受不同的参数。根据需要,可以调用适合的构造方法来实例化类。例如:
ClassName obj = new ClassName(parameter1, parameter2, ...);
  • 1
  1. 使用反射机制:Java的反射机制可以在运行时动态地获取类的信息,并实例化类。通过反射,可以使用类的完全限定名来创建类的实例。例如:
Class<?> cls = Class.forName("com.example.ClassName");
ClassName obj = (ClassName) cls.newInstance();
  • 1
  • 2
  1. 使用工厂方法模式:工厂方法模式是一种创建对象的设计模式,它通过定义一个工厂类来封装对象的实例化过程。通过调用工厂类的方法,可以返回类的实例。例如:
ClassName obj = Factory.createInstance();
  • 1
  1. 使用单例模式:单例模式是一种保证类只有一个实例的设计模式。通过在类内部控制对象的创建和访问,可以保证只有一个实例存在。例如:
ClassName obj = ClassName.getInstance();
  • 1

以上是常见的实例化方式,根据具体的需求和设计模式,选择适合的方式来实例化类。实例化类后,可以使用对象调用类的成员变量和成员方法,进行相应的操作和处理。

2.4 类和对象的联系

在面向对象编程中,类(Class)和对象(Object)是密切相关的概念,它们之间有以下关系:

  1. 类是对象的模板:类是用于创建对象的模板或蓝图。它定义了对象的属性和行为,描述了对象的结构和功能。

  2. 对象是类的实例:对象是根据类定义创建的实体。通过实例化类,可以在内存中创建一个对象。对象具体化了类的属性和行为,拥有自己的状态和行为。

  3. 类是对象的类型:类确定了对象的类型。对象属于某个类的实例,继承了类的属性和行为,并遵循类的定义。

  4. 对象具有独立性:每个对象都是独立的实体,它们可以有自己的状态(成员变量的值)和行为(成员方法的调用)。对象之间相互独立,拥有各自的内存空间。

  5. 类提供了对象的创建和访问方式:类定义了对象的属性和行为,并提供了对象的创建和访问方式。通过类,可以创建对象的实例,并使用对象调用类的成员变量和成员方法。

  6. 对象是类的多个实例:一个类可以创建多个对象实例,每个实例都是类的独立副本。每个对象都可以拥有不同的状态和行为,但遵循同一个类的定义。

简单来说,类是对象的模板和类型,描述了对象的结构和行为。对象是类的实例,具体化了类的属性和行为,拥有独立的状态和行为。通过类,可以创建多个对象的实例,并对每个对象进行操作和访问

三、认识 this 引用

在Java中,关键字this表示当前对象的引用,用于引用当前对象的成员变量、成员方法和构造方法。

3.1 为什么引入 this 引用

假设我们有一个自定义的Date类来表示日期,该类包含三个成员变量:yearmonthday,以及一个用于设置日期的方法setDate()。让我们看看为什么引入this关键字在这种情况下是有用的。

package demo1;

public class Date {
    private int year;
    private int month;
    private int day;

    public void setDate(int year, int month, int day) {
        year = year;   
        month = month; 
        day = day;     
    }

    @Override
    public String toString() {
        return "Date{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    public static void main(String[] args) {
        Date date = new Date();
        date.setDate(2023, 6, 15);
        System.out.println(date.toString());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

运行上述代码,可以发现输出的结果为:Date{year=0, month=0, day=0},这样的结果并不是我们所预期的,通过观察setDate方法:

public void setDate(int year, int month, int day) {
    year = year;   
    month = month; 
    day = day;     
}
  • 1
  • 2
  • 3
  • 4
  • 5

此时,我们就会产生疑问了:**当形参名与成员变量名相同时,在函数体中到底是谁给谁赋值?**究竟是成员变量给成员变量、参数给参数、参数给成员变量、成员变量给参数?估计自己都搞不清楚了。

此时,this关键字就起作用了,修改setDate方法的相关代码:

public void setDate(int year, int month, int day) {
    this.year = year;   // 使用this引用当前对象的year成员变量
    this.month = month; // 使用this引用当前对象的month成员变量
    this.day = day;     // 使用this引用当前对象的day成员变量
}
  • 1
  • 2
  • 3
  • 4
  • 5

此时运行的结果:Date{year=2023, month=6, day=15},正是我们所预期的。

在上述例子中,使用this关键字来引用当前对象的成员变量,其发挥的作用是:

  1. 区分成员变量和方法参数:在setDate()方法中,方法参数与成员变量具有相同的名称(year、month和day)。使用this关键字可以明确指示我们要访问的是当前对象的成员变量,而不是方法参数。这样可以避免命名冲突和混淆,确保正确赋值。
  2. 指向当前对象this关键字引用了当前对象的引用,我们可以通过它来访问当前对象的成员变量和方法。在setDate()方法中,this.year表示当前对象的year成员变量,this.month表示当前对象的month成员变量,以此类推。使用this关键字可以明确表示我们正在引用当前对象。

3.2 this 引用的功能

通过上面的例子,关于this指针的功能可以总结如下:

  1. 引用当前对象this关键字用于引用当前对象的引用。它可以在类的方法中访问和操作当前对象的成员变量和成员方法。通过使用this关键字,可以明确指示当前对象。
  2. 区分成员变量和方法参数:当方法的参数与类的成员变量同名时,使用this关键字可以区分它们,明确指示访问的是成员变量而不是方法参数。这样可以避免命名冲突和混淆。

除此之外,this还具有的功能有:调用其他构造方法、返回当前对象、在内部类中引用外部类对象等。

  1. 调用其他构造方法:在类的构造方法中,使用this关键字可以调用同一个类的其他构造方法。这种方式称为构造方法的重载。通过使用this关键字调用不同的构造方法,可以避免代码重复,提高代码的复用性。

例如,为Date类增加一个无参构造和有参构造:

public Date(){
    this(2023, 6, 15);
    System.out.println("Date()");
}

public Date(int year, int month, int day) {
    System.out.println("Date(int year, int month, int day)");
    this.year = year;
    this.month = month;
    this.day = day;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

使用下面代码创建对象:

    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date.toString());
    }
  • 1
  • 2
  • 3
  • 4

输出结果为:

在实例化对象的时候使用的是new Date(),会直接调用Date类的无参构造,如何在无参构造中通过this调用有参构造,有参构造调用结束之后返回无参构造,执行无参构造中的打印语句,此时对象便被构造完成。

另外值得注意的是:

在构造方法中使用this调用其他构造方法时,this语句必须在方法体中的第一行。
  • 1

另外避免在多个构造方法中调用this构造,以免出现循环调用。
  • 1

  1. 作为方法的返回值:this关键字可以作为方法的返回值,用于在方法链式调用时返回当前对象的引用。这样可以实现一连串的方法调用。例如:
public class MyClass {
    private int value;

    public MyClass setValue(int value) {
        this.value = value;
        return this;  // 返回当前对象的引用
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  1. 在内部类中引用外部类对象:在内部类中,使用this关键字可以引用外部类对象。内部类可以访问外部类的成员,但如果有命名冲突,使用this关键字可以明确指示外部类对象。

3.3 this 引用的特性

  1. this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型

  2. this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法
    对象的引用传递给该成员方法,this负责来接收

  3. 在静态方法中无法使用this关键字,因为静态方法与任何对象实例无关。

  4. 当使用this关键字调用其他构造方法时,必须将this关键字放在构造方法的第一行。

  5. 调用构造方法时,this关键字只能用于调用同一个类的其他构造方法,不能用于调用父类的构造方法。

  6. this关键字只在对象的上下文中有效,即在对象方法中使用。在静态方法、静态初始化块或类初始化块中无法使用this关键字。

四、对象的构造初始化

4.1 如何初始化对象

通过对Java基础知识的学习,我们知道了在Java方法内部定义一个局部变量时,必须对其进行初始化,否则就会编译失败。

但是如果是我们创建的对象呢?

    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
        date.setDate(2023, 6, 15);
        System.out.println(date);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

运行结果:

我们可以发现,当直接创建一个对象,没有给其成员变量赋值的时候,这些成员变量会被默认初始化为0。如何可以通过setDate方法为其成员变量赋值。

那么此时就有两个问题:

  1. 每次对象创建好后调用SetDate方法设置具体日期,比较麻烦,那对象该如何初始化?
  2. 局部变量必须要初始化才能使用,为什么字段声明之后没有给值依然可以使用?

4.2 构造方法

构造方法(构造器)是一个特殊的成员方法,其名字必须和类名相同,无返回值,在创建对象时由编译器自动调用,并且在整个对象的生命周期中只调用一次

例如Date类的构造方法:

public Date(){

}

public Date(int year, int month, int day) {
    //this();
    System.out.println("Date(int year, int month, int day)");
    this.year = year;
    this.month = month;
    this.day = day;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

构造方法的特点:

  1. 名字必须与类名相同。
  2. 没有返回值类型,并且不设置为void
  3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)。
  4. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)。
  5. 如果用户没有显式定义构造方法,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
public class Date {
    private int year;
    private int month;
    private int day;
        @Override
    public String toString() {
        return "Date{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

上述Date类中,没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法。这就是为什么在定义局部变量的时候,我们没有对成员变量进行初始化,也能通过编译。

**注意:**如果我们定义了任何构造方法,编译器则不再默认生成。


public class Date {
    private int year;
    private int month;
    private int day;
    
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
        @Override
    public String toString() {
        return "Date{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

结果:

此时,编译器就不再提供默认构造方法,需要我们自己显示添加。

4.3 默认初始化

在上文中提出的第二个问题:为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:Date date = new Date();

在程序层面只是简单的一条语句,在JVM层面需要做好多事情,简单介绍如下:

  1. 检测对象对应的类是否加载了,如果没有加载则加载
  2. 为对象分配内存空间
  3. 处理并发安全问题
    • 比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
  4. 初始化所分配的空间
    • 对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:
数据类型默认值
byte0
char‘\u0000’
short0
int0
long0L
booleanfalse
float0.0f
double0.0
referencenull
  1. 设置对象头信息。
  2. 调用构造方法,给对象中各个成员赋值。

4.4 就地初始化

在声明成员变量时,也可以直接对成员变量赋初始值,成为就地初始化,例如:

public class Date {
    private int year = 2023;
    private int month = 6;
    private int day = 15;
    @Override
    public String toString() {
        return "Date{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
    
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

运行结果为:Date{year=2023, month=6, day=15}

五、封装

5.1 什么是封装

面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。封装(Encapsulation)是面向对象编程中的一种重要概念,它将数据和操作数据的方法(即属性和方法)组合在一个单元中,并对外部隐藏了实现的细节,只暴露必要的接口供其他对象使用。封装有助于实现数据的安全性和灵活性,并支持代码的可维护性和扩展性。

比如:

  • 对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。
  • 但实际上:电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
  • 对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。
  • 因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。

所以简单来说,封装就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互

5.2 访问限定符

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用

Java提供了四种访问限定符(Access Modifiers)来控制类的成员(属性和方法)的访问范围和可见性。这些访问限定符用于在类的内部和外部控制对成员的访问权限。

以下是Java中的四种访问限定符及其范围:

  1. public(公共):public是最宽松的访问限定符,它表示成员可以被任何类访问。具有public访问限定符的成员可以在任何地方被访问,包括不同的包和子类。对外公开的接口和方法通常使用public。

  2. protected(受保护的):protected访问限定符表示成员只能在同一包中的其他类或者不同包中的子类中被访问。受保护的成员不允许被包外的非子类访问。protected成员对于子类的继承和访问非常有用。

  3. default(默认,不使用访问限定符):当成员没有明确的访问限定符时,它被视为具有默认访问限定符。默认访问限定符将成员限制在同一包中可见,但对于不同包中的类是不可见的。这种访问限定符通常称为包级访问。

  4. private(私有):private是最严格的访问限定符,它表示成员只能在同一类中被访问。私有成员对于隐藏实现细节和确保数据安全性非常有用,只有通过公共方法才能访问私有成员。

下表总结了四种访问限定符的范围:

访问限定符类内部访问同一包中的类访问不同包中的子类访问不同包中的非子类访问
public✔️✔️✔️✔️
protected✔️✔️✔️
default✔️✔️
private✔️

请注意,访问限定符不仅适用于类的成员,也适用于类本身。一个类可以具有public和默认(无访问限定符)两种访问级别。使用适当的访问限定符,可以控制代码的可见性和访问权限,从而提高代码的安全性和可维护性。

六、包的认识

6.1 什么是包

在Java中,包(Package)是用于组织和管理类和接口的一种机制。它提供了一种逻辑上的组织结构,将相关的类和接口组织在一起,以便更好地管理和使用它们。

以下是包的一些关键概念和特点:

  1. 命名空间: 包提供了命名空间的概念,它允许我们在程序中使用相同名称的类,只要它们在不同的包中即可。通过使用包名和类名的组合,可以唯一地标识一个类。

  2. 组织和分类: 包提供了组织和分类类和接口的方式。相关的类和接口可以放置在同一个包中,使得代码更具可读性和可维护性。包可以按照功能、层次结构或其他自定义规则进行组织。

  3. 访问控制: 包也提供了访问控制的机制。通过使用访问修饰符(如public、protected、default、private),可以控制包中的类和接口对外部的可见性和访问权限。

  4. 包的层次结构: 包可以形成层次结构,类似于文件系统的目录结构。这种层次结构可以通过使用点号(.)来表示包之间的关系。例如,com.example.myapp表示了一个层次结构的包名称。

  5. 导入: 在使用其他包中的类时,可以使用import语句将类引入当前的编译单元。这样就可以直接使用被导入的类,而不需要使用完全限定的类名。

通过使用包,我们可以避免类名冲突、更好地组织和管理代码、控制访问权限以及提供更清晰的代码结构。在Java开发中,包是一种常见的实践,可以帮助我们构建大型和复杂的应用程序。

6.2 导入包中的类

Java 中已经提供了很多现成的类供我们使用。例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 `Date
类。

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果需要使用 java.util 中的其他类,可以使用 import java.util.*

import java.util.*;
public class Test {
  public static void main(String[] args) {
    Date date = new Date();
    // 得到一个毫秒级别的时间戳
    System.out.println(date.getTime());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

但是建议显式的指定要导入的类名,否则还是容易出现冲突的情况,例如:

import java.util.*;
import java.sql.*;

public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10


因为在java.sql这个包下也有一个Date类,所以就会导致编译器不知道使用哪个Date类。

七、类的 static 成员

7.1 static 修饰成员变量

static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。

静态成员变量特性:

  1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中。
  2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问。
  3. 类变量存储在方法区当中。
  4. 生命周期伴随类的一生(随类的加载而创建,随类的卸载而销毁)。

例如下面的例子:

public class Counter {
    private static int count; // 静态变量

    public Counter() {
        // 构造方法
    }

    public void increment() {
        count++; // 静态方法中访问静态变量
    }

    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();

        c1.increment();
        System.out.println("Count: " + Counter.count); // 访问静态变量
        c2.increment();
        System.out.println("Count: " + Counter.count);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在上述示例中,Counter类包含一个静态变量count,它被所有Counter类的实例共享。increment()方法是一个普通成员方法,它可以访问和操作静态变量count

main方法中,创建了两个Counter类的实例c1c2。通过调用increment()方法和访问count静态变量,我们可以增加和获取计数器的值。

最后运行的结果:

说明count静态成员变量是由所有对象所共享的。

7.2 static 修饰成员方法

使用static修饰成员方法时,该方法被称为静态方法(Static Method)。静态方法属于类而不是类的实例,可以在没有创建类的实例的情况下直接调用。

下面是使用static修饰成员方法的示例:

public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }

    public static double square(double num) {
        return num * num;
    }

    public static void main(String[] args) {
        int sum = MathUtils.add(5, 3); // 调用静态方法
        System.out.println("Sum: " + sum);

        double result = MathUtils.square(2.5);
        System.out.println("Square: " + result);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在上述示例中,MathUtils类包含两个静态方法:add()square()。这些方法可以直接使用类名调用,而不需要创建类的实例

main方法中,通过MathUtils.add(5, 3)调用了add()方法来计算两个整数的和,并通过MathUtils.square(2.5)调用了square()方法来计算给定数的平方。

【注意事项】

  • 静态方法与实例方法不同,它们没有访问实例变量的能力,因为它们不依赖于类的实例。静态方法只能访问静态成员(静态变量和其他静态方法)和方法内的局部变量

  • 静态方法不能被子类重写,因为它们属于类而不是实例。当在子类中定义了与父类中的静态方法相同的方法签名时,子类的方法实际上是一个新的静态方法,而不是对父类方法的重写。

  • 静态方法在以下情况下常常使用:

    • 当方法不依赖于类的实例状态,只需要执行某个操作或返回一个结果时。
    • 当方法需要在没有创建类的实例的情况下直接访问时。
    • 当方法用于执行通用的操作,而不需要特定的实例上下文时。

通过使用静态方法,我们可以在不创建类的实例的情况下执行特定的操作,提供了更灵活和方便的调用方式。

7.3 static 成员变量初始化

静态成员变量(Static Variables)在类加载时被初始化,具有类级别的生命周期。它们在类的任何实例之前进行初始化,并且只会初始化一次。

在Java中,有几种方法可以初始化静态成员变量:

  1. 直接赋值初始化: 静态成员变量可以直接在声明时进行赋值初始化,也称为就地初始化。
public class MyClass {
    public static int number = 10; // 直接赋值初始化
}
  • 1
  • 2
  • 3
  1. 静态代码块初始化: 可以使用静态代码块在类加载时初始化静态成员变量。
public class MyClass {
    public static int number;

    static {
        number = 10; // 静态代码块初始化
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 静态方法初始化: 可以使用静态方法初始化静态成员变量。
public class MyClass {
    public static int number;

    public static void initialize() {
        number = 10; // 静态方法初始化
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

【注意事项】

  • 静态成员变量的初始化顺序是按照声明的顺序进行的。如果一个静态成员变量依赖于另一个静态成员变量的值,则确保被依赖的成员变量先被初始化。

  • 静态成员变量可以通过类名直接访问,例如:MyClass.number。它们在类的所有实例中共享相同的值。

  • 静态成员变量的初始化是在类加载时进行的,而且只会执行一次。如果在运行时更改静态成员变量的值,这个值将对所有实例和后续访问生效。

八、代码块

在Java中,代码块(Code Block)是一段用大括号{}括起来的代码片段,它可以包含一系列的语句。代码块用于组织和限定代码的作用域和生命周期。

根据其位置和声明方式,代码块可以分为以下几种类型:

  1. 实例初始化块(Instance Initialization Block): 实例初始化块用于初始化实例成员变量,在创建对象时被执行。它没有使用任何修饰符,直接写在类中。
public class MyClass {
    {
        // 实例初始化块
        // 初始化实例成员变量
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 静态初始化块(Static Initialization Block): 静态初始化块用于初始化静态成员变量,在类加载时被执行。它使用static关键字修饰,并且直接写在类中。
public class MyClass {
    static {
        // 静态初始化块
        // 初始化静态成员变量
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:

  1. 静态代码块不管生成多少个对象,其只会执行一次。

  2. 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的。

  3. 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)。

  4. 实例代码块只有在创建对象时才会执行。

  5. 局部代码块(Local Block): 局部代码块用于限定局部变量的作用域,在代码块内部声明的变量只能在该代码块内使用。它一般在方法中使用,用大括号括起来。

public class MyClass {
    public void myMethod() {
        {
            // 局部代码块
            // 声明和使用局部变量
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 同步代码块(Synchronized Block): 同步代码块用于实现多线程中的同步操作。它使用synchronized关键字修饰,并指定一个对象作为锁。多个线程在同步代码块中对共享资源进行操作时,只能有一个线程进入该代码块执行,其他线程需要等待。
public class MyClass {
    public void myMethod() {
        synchronized (lock) {
            // 同步代码块
            // 对共享资源的操作
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这些不同类型的代码块在语法和用途上有所不同,但它们共同提供了一种在特定范围内组织和限制代码执行的机制。通过使用代码块,可以控制变量的作用域、初始化成员变量、实现同步操作等,从而提高代码的可读性、灵活性和可维护性。

九、内部类

在Java中,内部类(Inner Class)是定义在另一个类内部的类。内部类提供了一种在类的内部定义另一个类的方式,可以实现更强大的封装和组织代码的能力。内部类可以访问外部类的成员,包括私有成员,而外部类也可以访问内部类的成员。

根据内部类的定义位置和访问权限,内部类可以分为以下几种类型:

  1. 成员内部类(Member Inner Class): 成员内部类是定义在外部类的成员位置上的内部类。它与外部类的实例相关联,只能在外部类的实例化对象上创建内部类的实例
public class Outer {
    private int outerField;

    public class Inner {
        private int innerField;

        public void innerMethod() {
            // 访问外部类的成员
            outerField = 10;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 静态内部类(Static Inner Class): 静态内部类是定义在外部类内部,但使用static关键字修饰的内部类。它与外部类的实例无关,可以直接通过外部类名访问。
public class Outer {
    private static int outerField;

    public static class Inner {
        private int innerField;

        public void innerMethod() {
            // 访问外部类的静态成员
            outerField = 10;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 局部内部类(Local Inner Class): 局部内部类是定义在方法内部的内部类。它的作用域仅限于定义它的方法内部,不能在方法外部访问。
public class Outer {
    public void myMethod() {
        class LocalInner {
            public void innerMethod() {
                // 内部类的方法
            }
        }

        LocalInner inner = new LocalInner();
        inner.innerMethod();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 匿名内部类(Anonymous Inner Class): 匿名内部类是一种没有显式命名的内部类,它通常作为参数传递给方法或作为接口的实现使用。它没有构造函数,直接在创建对象时定义其字段和方法。
public class Outer {
    public void myMethod() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 匿名内部类的方法实现
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

通过使用内部类,可以实现更紧凑和模块化的代码结构,同时保持对外部类的访问控制。内部类可以访问外部类的私有成员,并且可以用于实现接口、继承其他类、封装实现细节等。在某些场景下,内部类能够更好地表

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/1011089
推荐阅读
相关标签
  

闽ICP备14008679号