当前位置: 首页 > news >正文

Java 类与对象 -- Java 语言的类与对象、构造器、static、final、包和 JAR

大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 006 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。

上一篇文章中,我们讨论了 Java 的流程控制,包括代码块、作用域、循环与依赖。本篇文章我们将深入了解 Java 语言中的类与对象,探讨构造器、staticfinal、包和 JAR 文件。这些概念是面向对象编程的核心,对于理解和掌握 Java 至关重要。

最后在前言的末尾我补充一下,如果这篇文章,对大家有所帮助或收获一定的乐趣和想法,那么非常欢迎大家能够,点赞、评论、收藏、订阅。这些也将是我持续更新的最大动力。


文章目录

      • 1、类与对象
        • 1.1、关于类
        • 1.2、关于对象
          • 1.2.1、Behavior(对象的行为)
          • 1.2.2、State(对象的状态)
          • 1.2.3、Identity(对象的标识)
        • 1.3、类之间的关系
          • 1.3.1、依赖关系(Dependency)
          • 1.3.2、聚合关系(Aggregation)
          • 1.3.3、继承关系(Inheritance)
      • 2、构造器
        • 2.1、构造器的引入
        • 2.2、构造器的特点
        • 3.3、子父类构造器(涉及继承)
      • 3、`static` 关键字
        • 3.1、`static` 修饰变量
        • 3.2、`static` 修饰方法
        • 3.3、`static` 代码块
        • 3.4、构造方法与代码块执行顺序
        • 3.5、`static` 内部类(涉及内部类)
      • 4、`final` 关键字
        • 4.1、`final` 变量
        • 4.2、`final` 方法
        • 4.3、`final` 类
      • 5、Java 包
        • 5.1、包名
        • 5.2、`import` 的用法
        • 5.3、定义包
        • 5.4、类路径
      • 6、JAR 文件
        • 6.1、创建 JAR 文件
        • 6.2、查看 JAR 文件内容
        • 6.3、运行 JAR 文件
        • 6.4、MANIFEST.MF 文件
        • 6.5、解压 JAR 文件


1、类与对象

面向对象程序设计(简称 OOP)是当今主流的程序设计范型,它已经取代了 20 世纪 70 年代的"结构化"过程化程序设计开发技术。

从一开始学习 Java 这门技术时,我们就会了解到 Java 是完全面向对象的,必须熟悉 OOP 才能够编写 Java 程序。同样的 Java 之所以简单而具有优势,就是因为面向对象所带来的方便。这种方式免去了 C++ 中反复而难以理解的指针和多继承,可以让程序员以优雅的思维方式进行复杂的编程。而这之中最为核心也是最基础的部分就是类与对象。

1.1、关于类

类(class)是构造对象的模板或蓝图。我们可以将类想象成制作小甜饼的切割机,将对象想象为小甜饼。由类构造(construct)对象的过程称为创建类的实例(instance)。

标准的 Java 库提供了几千个类,可以用于用户界面设计、日期、日历和网络程序设计。尽管如此,还是需要在 Java 程序中创建一些自己的类,以便描述应用程序所对应的问题域中的对象。

一个类一般包含以下几部分:

  1. 类名:要遵循大驼峰命名法(UpperCamelCase),如 PersonCar
  2. 属性:也称为字段或成员变量,用于存储对象的状态信息。属性通常使用访问修饰符(如 privateprotectedpublic)来控制其可见性;
  3. 方法:也称为成员方法,用于执行操作或表示对象的行为。方法可以访问和修改对象的属性,同样的,方法通常也使用访问修饰符来控制其可见性;
  4. 构造方法:特殊的方法,用于在创建对象时初始化对象的状态。构造方法的名称必须与类名相同。

以下是一个 Car 类的示例,描述了一个汽车的属性和行为:

package com.lizhengi;/*** 汽车类,描述汽车的属性和行为(类)* 组成部分:* 1. 成员变量(属性):描述汽车的特性* 2. 构造方法:用于创建汽车对象实例* 3. 方法:定义汽车的行为** @author Lizhengi*/
public class Car {/* 成员变量(属性)*//** 汽车品牌 */private String make;/** 汽车型号 */private String model;/** 出厂年份 */private int year;/*** 构造方法,用于创建 Car 对象实例** @param make  汽车品牌* @param model 汽车型号* @param year  出厂年份*/public Car(String make, String model, int year) {this.make = make;this.model = model;this.year = year;}/*** 启动引擎的方法*/public void startEngine() {System.out.println("The engine is starting...");}
}
1.2、关于对象

对象与类的关系是:对象的共性抽象为类,类的实例化就是对象。

对象(object)是类的实例,是使用类的构造方法创建的实体。对象代表现实生活中的实体,它拥有类中定义的属性和方法。每个对象都有自己的一组属性值,这些值定义了对象的状态。通过调用对象的方法,可以改变对象的状态或执行某些操作。

例如,汽车(Car)类可以有很多对象,每个对象代表不同的汽车。每辆汽车都有自己的品牌、型号和出厂年份,但它们都是基于同一个类(Car)创建的。

要想使用 OOP,一定要清楚对象的三个主要特性:

  1. Behavior(对象的行为):可以对对象施加哪些操作,或可以对对象施加哪些方法?
  2. State(对象的状态):当施加那些方法时,对象如何响应?
  3. Identity(对象的标识):如何辨别具有相同行为与状态的不同对象?
1.2.1、Behavior(对象的行为)

对象的行为是指对象能够执行的操作或方法。这些操作定义了对象可以做什么。行为通常通过方法来实现,方法可以对对象的状态进行操作或进行其他操作。行为是类中定义的,是对象可以执行的具体功能。

例如,汽车对象的行为可以包括启动引擎(startEngine)、加速(accelerate)、刹车(brake)等。通过调用这些方法,能够使对象执行相应的操作。

public class Car {private String make;private String model;private int year;public Car(String make, String model, int year) {this.make = make;this.model = model;this.year = year;}/*** 启动引擎的方法*/public void startEngine() {System.out.println("The engine is starting...");}/*** 加速的方法*/public void accelerate() {System.out.println("The car is accelerating...");}/*** 刹车的方法*/public void brake() {System.out.println("The car is braking...");}
}
1.2.2、State(对象的状态)

对象的状态是指对象在某一时刻的属性值。对象的状态由其属性(成员变量)的值决定。通过改变属性的值,可以改变对象的状态。状态反映了对象在特定时刻的特征和条件。

例如,汽车对象的状态可以包括汽车品牌(make)、车型(model)和出厂年份(year)。这些属性的值定义了对象的当前状态。

public class Car {private String make;private String model;private int year;public Car(String make, String model, int year) {this.make = make;this.model = model;this.year = year;}/* Getter 和 Setter 方法 *//*** 获取汽车品牌** @return 汽车品牌*/public String getMake() {return make;}/*** 设置汽车品牌** @param make 汽车品牌*/public void setMake(String make) {this.make = make;}/*** 获取汽车型号** @return 汽车型号*/public String getModel() {return model;}/*** 设置汽车型号** @param model 汽车型号*/public void setModel(String model) {this.model = model;}/*** 获取出厂年份** @return 出厂年份*/public int getYear() {return year;}/*** 设置出厂年份** @param year 出厂年份*/public void setYear(int year) {this.year = year;}
}
1.2.3、Identity(对象的标识)

对象的标识是指每个对象在内存中的唯一标识。即使两个对象具有相同的状态和行为,它们在内存中也是不同的实体。对象的标识使得可以区分具有相同行为和状态的不同对象。

在 Java 中,对象的标识由内存地址决定。即使两个对象的属性值完全相同,它们仍然是不同的对象,因为它们在内存中的地址不同。

public class Main {public static void main(String[] args) {// 创建两个具有相同属性值的 Car 对象Car car1 = new Car("Toyota", "Corolla", 2020);Car car2 = new Car("Toyota", "Corolla", 2020);// 比较两个对象的内存地址if (car1 == car2) {System.out.println("car1 and car2 are the same object.");} else {System.out.println("car1 and car2 are different objects.");}// 比较两个对象的属性值if (car1.getMake().equals(car2.getMake()) &&car1.getModel().equals(car2.getModel()) &&car1.getYear() == car2.getYear()) {System.out.println("car1 and car2 have the same state.");} else {System.out.println("car1 and car2 have different states.");}}
}

输出结果为:

car1 and car2 are different objects.
car1 and car2 have the same state.

通过理解对象的行为、状态和标识,可以更好地掌握面向对象编程的核心概念,编写出更具模块化和可维护性的代码。

1.3、类之间的关系

在面向对象编程中,类之间的关系至关重要,它们决定了系统的结构和行为。常见的类之间的关系有:依赖、聚合和继承。

1.3.1、依赖关系(Dependency)

依赖关系是指一个类使用另一个类的实例。通常表现为一个类的方法接收另一个类的对象作为参数,或在方法中创建另一个类的对象。这种关系是最弱的耦合关系。

依赖关系可以理解为"使用"关系,即一个类依赖于另一个类来完成某些功能。

示例:

/*** Driver 类表示驾驶员* 依赖关系:Driver 类依赖于 Car 类*/
public class Driver {public void drive(Car car) {car.startEngine();System.out.println("The driver is driving the car.");}
}

在这个例子中,Driver 类依赖于 Car 类,drive 方法使用了 Car 类的对象。

1.3.2、聚合关系(Aggregation)

聚合关系是一种"整体-部分"关系,一个类包含另一个类的实例,但这种关系并不表示强依赖。被包含的对象可以独立存在,而不会因为包含它的对象被销毁而销毁。

聚合关系通常使用成员变量来实现,一个类拥有另一个类的实例作为其成员变量。

示例:

/*** Engine 类表示引擎*/
public class Engine {public void start() {System.out.println("Engine started.");}
}/*** Car 类表示汽车* 聚合关系:Car 类聚合了 Engine 类*/
public class Car {private Engine engine;public Car(Engine engine) {this.engine = engine;}public void startCar() {engine.start();System.out.println("Car started.");}
}

在这个例子中,Car 类聚合了 Engine 类,Car 对象包含一个 Engine 对象,但 Engine 对象可以独立存在。

1.3.3、继承关系(Inheritance)

继承关系是面向对象编程中的一种强依赖关系,它表示一个类是另一个类的子类。子类继承了父类的属性和方法,可以复用父类的代码,增加代码的可维护性和扩展性。

继承关系是一种"是一种"关系,子类是父类的一种特殊形式。

示例:

/*** Vehicle 类表示交通工具* 父类*/
public class Vehicle {private String brand;public Vehicle(String brand) {this.brand = brand;}public String getBrand() {return brand;}
}/*** Car 类表示汽车* 继承关系:Car 类继承了 Vehicle 类*/
public class Car extends Vehicle {private String model;public Car(String brand, String model) {super(brand);this.model = model;}public String getModel() {return model;}public void startEngine() {System.out.println("The car engine is starting...");}
}

在这个例子中,Car 类继承了 Vehicle 类,Car 类不仅拥有 Vehicle 类的属性和方法,还可以定义自己的属性和方法。


2、构造器

理解构造器之前,首先我们需要了解 Java 中为什么要引入构造器,以及构造器的作用。在很久之前,程序员们编写 C 程序总会忘记初始化变量(这真的是一件琐碎但必须的事),因此后来 C++ 引入了构造器(constructor)的概念,这是一个在创建对象时被自动调用的特殊方法。Java 也采用了构造器。

构造器也被称为构造方法,是一种特殊的方法,调用构造方法可以创建新对象。构造方法可以执行任何操作,实际应用中,构造方法一般用于初始化操作,例如初始化对象的数据域。

构造函数与普通方法的主要区别如下:

  1. 名称:构造函数的名称必须与类名相同,而普通方法可以有任何有效的标识符作为名称;

  2. 返回类型:构造函数没有返回类型,而普通方法必须有返回类型;

  3. 调用方式:构造函数在创建对象时自动调用,无需手动调用。而普通方法需要手动调用;

  4. 用途:构造函数主要用于初始化对象的状态(即设置属性的初始值)。而普通方法用于描述对象的行为。

2.1、构造器的引入

构造器的定义:在定义构造器时,首先使用修饰符(如 publicprivate 等)来指定构造器的可见性,然后构造方法名必须与类名相同,最后是参数列表,可以为空也可以包含参数。构造器的主体包含在 {} 内,用于初始化对象。

修饰符 构造方法名 (参数列表) {}

引入构造器帮助我们解决了哪些问题呢?假设我们每定义一个类都必须定义一个 initialize() 方法,该方法提醒你,每次使用对象之前都要执行一次该方法,这意味着用户每次都必须记得自己去调用此方法,这和上文提到的 C 程序员一样,很容易就忘记了。Java 构造器的出现很好的规避掉了这种问题,创建对象时,java 会在使用对象之前调用相应的构造器,保证对象正确初始化。

首先,让我们看一下没有构造器时的情况:

public class Car {private String make;private String model;private int year;// initialize 方法,用于初始化对象public void initialize(String make, String model, int year) {this.make = make;this.model = model;this.year = year;}public void displayInfo() {System.out.println("Car make: " + make + ", model: " + model + ", year: " + year);}public static void main(String[] args) {Car car = new Car();// 必须手动调用 initialize 方法进行初始化car.initialize("Toyota", "Corolla", 2020);car.displayInfo();}
}

在这个例子中,我们必须手动调用 initialize 方法来初始化 Car 对象。如果忘记调用 initialize 方法,Car 对象的属性将保持默认值,这可能导致程序错误。

现在,让我们看一下使用构造器的情况:

public class Car {private String make;private String model;private int year;// 构造器,用于初始化对象public Car(String make, String model, int year) {this.make = make;this.model = model;this.year = year;}public void displayInfo() {System.out.println("Car make: " + make + ", model: " + model + ", year: " + year);}public static void main(String[] args) {// 创建对象时自动调用构造器进行初始化Car car = new Car("Toyota", "Corolla", 2020);car.displayInfo();}
}

在这个例子中,构造器 Car(String make, String model, int year) 在创建对象时自动调用,确保对象在使用前已被正确初始化。这样就不必担心忘记调用初始化方法,从而提高了代码的安全性和可维护性。

2.2、构造器的特点

构造器具有以下特点:

  1. 每一个类都必须有一个构造方法:如果自己不写,编译的时候,系统会给出默认构造方法。默认构造器(又名无参构造器)是没有形式参数的,它创建的是 “默认对象”;
  2. 构造器的命名必须与类名相同:这确保了构造器可以正确地识别和关联到类;
  3. 构造器没有返回类型:包括没有 void,也不需要写返回值。因为它是为构建对象的,对象创建完,方法就执行结束;
  4. 构造器可以有参数,可以重载:有默认无参构造,也有带参构造。为了满足不同的初始化需求,我们通常会需要定义多个带参构造器,由于都是构造器,它们的名称必须相同,为了让方法名相同而参数不同的方法存在,我们就必须使用方法重载,这是构造器所必须的;
  5. 构造器在创建对象时自动调用:而且只执行一次

以下是带有多个构造器的类示例:

public class Car {private String make;private String model;private int year;// 默认构造器public Car() {this.make = "Unknown";this.model = "Unknown";this.year = 0;System.out.println("默认构造器被调用");}// 带参构造器public Car(String make, String model) {this.make = make;this.model = model;this.year = 0;System.out.println("带参构造器(make, model)被调用");}// 带参构造器public Car(String make, String model, int year) {this.make = make;this.model = model;this.year = year;System.out.println("带参构造器(make, model, year)被调用");}public void displayInfo() {System.out.println("Car make: " + make + ", model: " + model + ", year: " + year);}public static void main(String[] args) {// 调用默认构造器Car car1 = new Car(); car1.displayInfo();// 调用带参构造器(make, model)Car car2 = new Car("Toyota", "Corolla"); car2.displayInfo();// 调用带参构造器(make, model, year)Car car3 = new Car("Honda", "Civic", 2022); car3.displayInfo();}
}

在这个例子中,Car 类有三个构造器:一个默认构造器和两个带参构造器。通过实例化 Car 类,我们可以看到不同的构造器被调用,并初始化不同的对象属性。

3.3、子父类构造器(涉及继承)

在创建子类对象时,父类的构造方法会先执行,因为子类中所有构造方法的第一行有默认的隐式 super(); 语句,它是用来访问父类中的空参数构造方法,进行父类成员的初始化操作。

this() 是调用本类的构造方法,super() 是调用父类的构造方法,且两条语句不能同时存在。

子类构造方法时使用 super()this 的注意点:

  1. 子类的所有构造方法,直接或间接必须调用到父类构造方法。所以在 Java 中,每个构造方法都必须在其构造方法体的第一行显式或隐式地调用 super()this()
  2. super()this() 调用父类的构造方法,必须在构造方法的第一行。
  3. super()this() 不能同时被构造方法调用!(因为二者均要求放在第一行才行)。

此外,构造方法是可以被 private 修饰,作用是:其他程序无法创建该类的对象。而当父类使用 private 修饰构造方法时,子类将无法正常调用 super() 方法。

以下是一个示例,展示了如何在子类构造方法中使用 super()this()

class Parent {/** 父类的名字 */private String name;/** * 父类无参构造方法 */public Parent() {this.name = "Default Parent";System.out.println("Parent class no-arg constructor called");}/** * 父类带参构造方法 * @param name 父类的名字*/public Parent(String name) {this.name = name;System.out.println("Parent class parameterized constructor called");}
}class Child extends Parent {/** 子类的年龄 */private int age;/** * 子类无参构造方法,调用父类的无参构造方法 */public Child() {// 默认调用 super();this.age = 0;System.out.println("Child class no-arg constructor called");}/** * 子类带参构造方法,调用父类的带参构造方法 * @param name 子类的名字* @param age 子类的年龄*/public Child(String name, int age) {super(name); // 调用父类的带参构造方法this.age = age;System.out.println("Child class parameterized constructor called");}/** * 子类带单个参数的构造方法,调用本类的无参构造方法 * @param age 子类的年龄*/public Child(int age) {this(); // 调用本类的无参构造方法this.age = age;System.out.println("Child class parameterized constructor (age) called");}
}public class Main {public static void main(String[] args) {// 创建子类对象,调用不同的构造方法Child child1 = new Child();System.out.println();Child child2 = new Child("John", 10);System.out.println();Child child3 = new Child(20);}
}

运行结果:

Parent class no-arg constructor called
Child class no-arg constructor calledParent class parameterized constructor called
Child class parameterized constructor calledParent class no-arg constructor called
Child class no-arg constructor called
Child class parameterized constructor (age) called

在这个示例中,我们可以看到:

  1. 当调用 Child 类的无参构造方法时,隐式调用了父类的无参构造方法;
  2. 当调用 Child 类的带参构造方法时,显式调用了父类的带参构造方法;
  3. 当调用 Child 类的带有单个参数的构造方法时,显式调用了本类的无参构造方法,然后进行了额外的初始化。

3、static 关键字

static 关键字是 Java 中用于定义类成员(变量或方法)的关键字。使用 static 关键字修饰的成员属于类本身,而不是类的实例。static 关键字可以用于变量、方法、代码块和内部类。

  1. 静态变量:static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
  2. 静态方法:static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。
  3. 静态代码块:static 关键字还可以形成静态代码块以优化程序性能。static 代码块在类加载的时候就运行了,而且只运行一次,同时运行时机是在构造函数之前。

总的来说,static 关键字主要有以下几个作用:实现类的成员共享,节省内存;优化程序性能,提高运行效率。作为工具类的方法修饰符,方便调用。

3.1、static 修饰变量

static 变量,也称为类变量或静态变量,是属于类本身的变量。与实例字段的,在每个实例中都有自己的一个独立 “空间” 不同,静态字段只有一个共享 “空间”,而所有实例都会共享该字段。当一个实例修改该变量时,所有其他实例都可以看到修改后的值。

虽然实例可以访问静态字段,但是它们指向的其实都是Person class的静态字段。所以,所有实例共享一个静态字段。

因此,不推荐用 实例变量.静态字段 去访问静态字段,因为在 Java 程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段只是因为编译器可以根据实例类型自动转换为 类名.静态字段 来访问静态对象。推荐用类名来访问静态字段

示例:

public class Counter {/** 静态变量 count */private static int count = 0;public Counter() {count++;}public static int getCount() {return count;}public static void main(String[] args) {Counter c1 = new Counter();Counter c2 = new Counter();Counter c3 = new Counter();// 打印出 3,因为创建了三个 Counter 实例System.out.println("Count: " + Counter.getCount());}
}

在这个例子中,count 是一个 static 变量,所有 Counter 实例共享同一个 count 变量。每次创建 Counter 实例时,count 变量都会增加。

3.2、static 修饰方法

static 方法是属于类本身的方法,可以直接通过类名调用,而不需要创建类的实例。static 方法不能访问实例变量和实例方法,但可以访问静态变量和静态方法。

示例:

public class MathUtils {/** 静态方法 add */public static int add(int a, int b) {return a + b;}public static void main(String[] args) {// 通过类名直接调用静态方法int sum = MathUtils.add(5, 3);System.out.println("Sum: " + sum);}
}

在这个例子中,add 是一个 static 方法,可以通过类名 MathUtils 直接调用,而不需要创建 MathUtils 的实例。

3.3、static 代码块

static 代码块用于初始化类级别的资源,它在类加载时执行,并且只执行一次。static 代码块通常用于初始化静态变量。

示例:

public class Configuration {/** 静态变量 config */private static String config;/** 静态代码块 */static {config = "Default Configuration";System.out.println("Static block executed");}public static String getConfig() {return config;}public static void main(String[] args) {// 静态代码块已在类加载时执行System.out.println("Config: " + Configuration.getConfig());}
}

在这个例子中,静态代码块在类加载时执行,初始化静态变量 config

3.4、构造方法与代码块执行顺序

在 Java 中,类的静态代码块、非静态代码块和构造方法有特定的执行顺序。理解这些执行顺序有助于我们更好地掌握类的初始化过程,确保代码按预期运行。

当一个类的实例被创建时,执行顺序如下:

父类B静态代码块 > 子类A静态代码块 > 父类B非静态代码块 > 父类B构造函数 > 子类A非静态代码块 > 子类A构造函数

为了更好地理解这种顺序,我们来看一个示例:

class Parent {/** 父类静态代码块 */static {System.out.println("父类 B 静态代码块");}/** 父类非静态代码块 */{System.out.println("父类 B 非静态代码块");}/** 父类构造方法 */public Parent() {System.out.println("父类 B 构造方法");}
}class Child extends Parent {/** 子类静态代码块 */static {System.out.println("子类 A 静态代码块");}/** 子类非静态代码块 */{System.out.println("子类 A 非静态代码块");}/** 子类构造方法 */public Child() {System.out.println("子类 A 构造方法");}public static void main(String[] args) {System.out.println("创建子类 A 的实例:");new Child();}
}

运行结果:

父类 B 静态代码块
子类 A 静态代码块
创建子类 A 的实例:
父类 B 非静态代码块
父类 B 构造方法
子类 A 非静态代码块
子类 A 构造方法

从输出结果可以看出执行顺序如下:

  1. 父类静态代码块:首先执行父类的静态代码块。静态代码块只在类加载时执行一次;
  2. 子类静态代码块:接着执行子类的静态代码块。静态代码块只在类加载时执行一次;
  3. 父类非静态代码块:然后执行父类的非静态代码块。非静态代码块在每次创建对象时都会执行;
  4. 父类构造方法:在父类的非静态代码块之后,执行父类的构造方法;
  5. 子类非静态代码块:接下来执行子类的非静态代码块。非静态代码块在每次创建对象时都会执行;
  6. 子类构造方法:最后执行子类的构造方法。
3.5、static 内部类(涉及内部类)

static 内部类是使用 static 关键字修饰的内部类。静态内部类不能访问外部类的非静态成员,但可以访问外部类的静态成员。

示例:

public class OuterClass {private static String staticOuterField = "Static Outer field";/** 静态内部类 */public static class StaticInnerClass {public void display() {System.out.println("Static Outer field: " + staticOuterField);}}public static void main(String[] args) {OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();staticInner.display();}
}

在这个例子中,StaticInnerClassOuterClass 的静态内部类,可以访问外部类的静态成员 staticOuterField


4、final 关键字

final 关键字是 Java 中的一个修饰符,用于表示最终、不可变的含义。final 关键字可以修饰变量、方法和类,每种用法都有其特定的意义和作用。

4.1、final 变量

在变量层面,final 关键字用于声明常量,一旦被赋值,就无法再修改。这有助于提高代码的可读性和可维护性,同时也避免了一些潜在的 Bug。

final 变量可以是基本类型或引用类型,对于引用类型,final 表示引用本身不可改变,但引用的对象内容可以改变。

示例:

public class Constants {/** 定义常量 */public static final int MAX_VALUE = 100;public static void main(String[] args) {// MAX_VALUE = 200; // 编译错误,final 变量不能被重新赋值System.out.println("Max value: " + MAX_VALUE);}
}

在这个例子中,MAX_VALUE 是一个 final 变量,表示常量,其值不能被改变。

4.2、final 方法

在方法级别,final 关键字表示该方法不能被子类重写。这对于确保某些方法的逻辑不被修改是非常有用的,尤其是一些关键的算法或者安全性相关的方法,这在设计一个类的 API 时非常有用,可以防止子类改变父类中的关键行为。

示例:

public class Parent {/** final 方法 */public final void show() {System.out.println("This is a final method.");}
}public class Child extends Parent {// 试图重写 final 方法会导致编译错误// public void show() {//     System.out.println("Cannot override final method.");// }
}

在这个例子中,show 方法被声明为 final,因此不能在 Child 类中被重写。

4.3、final

当我们使用 final 修饰一个类时,意味着这个类不能被继承,也就是说,它是一个终结类,不允许其他类再来继承它。这样做的好处是防止其他类修改或扩展该类,保护了类的完整性。

示例:

public final class ImmutableClass {private final int value;public ImmutableClass(int value) {this.value = value;}public int getValue() {return value;}
}// 试图继承 final 类会导致编译错误
// public class SubClass extends ImmutableClass {
// }

在这个例子中,ImmutableClass 是一个 final 类,不能被继承。

总的来说,final 关键字的作用是为了让我们的代码更加稳定和可靠,避免不必要的修改和继承。当你看到某个类、方法或者变量被标记为 final 时,就知道它是不可变的,可以更加放心地使用。


5、Java 包

Java 包(Package)是用于组织类和接口的一种机制,它提供了命名空间来管理类和接口,避免命名冲突,并且可以控制访问权限。通过使用包,可以将相关的类和接口组织在一起,形成模块化的代码结构,便于维护和管理。

5.1、包名

Java 允许使用包将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。使用包的主要原因是确保类名的唯一性。同名的类放置在不同的包中,就不会产生冲突。如同硬盘的目录嵌套一样,也可以使用嵌套层次组织包。

示例:

package com.example.myapp;

在这个例子中,com.example.myapp 是包名。通常,包名使用小写字母,以便区分类名。

为了保证包名的绝对唯一性,Sun 公司建议将公司的因特网域名以逆序的形式作为包名,并且对于不同的项目使用不同的子包。所以,我们经常在类库中看到一堆包名像这样的:com.sunorg.apache 等等。

5.2、import 的用法

import 语句用于引入其他包中的类或接口,使得在当前类中可以直接使用它们。可以导入具体的类,也可以使用通配符 * 导入整个包。

示例:

// 导入具体的类
import java.util.ArrayList;// 导入整个包
import java.util.*;

在这个例子中,ArrayList 类被导入,可以在代码中直接使用。同时,使用 * 可以导入 java.util 包中的所有类和接口。

一个完整的类名是「包名+类名」,在没有 import 导入的情况下,使用一个类需要给出完整的类名,如 java.util.Date。为了方便,Java 自动导入两个包:java.lang 包和默认包。

注意,从编译器的角度来看,嵌套的包之间没有任何关系、例如,java.util 包和 java.util.jar 包毫无关系。每一个都拥有独立的类集合。

还有,只要使用星号(*)来导入一个包,而不能使用 import java.* 来导入以 java 为前缀的所有包。而且,如果要同时使用两个类名相同的类,只能在使用的时候给出类的完整包名。

在包中定位类是编译器的工作。因此,.class 文件中的字节码是使用完整的包名来引用其他的类。这样 Jvm 就会直接根据这个包名来找到对应的 .class 文件。

Ps:import 语句不仅可以导入类,还可以导入静态方法和静态域。例如通过 import static java.lang.System.*; 就可以使用 System 类的静态方法和静态域,而不必加类名前缀。

5.3、定义包

在 Java 中,通过在源文件的第一行使用 package 语句来定义包。包名与目录结构对应,编译器根据包名将类文件放在相应的目录中。

示例:

package com.example.myapp;public class MyClass {public void display() {System.out.println("Hello from MyClass in package com.example.myapp");}
}

假设源文件存放在 src/com/example/myapp/MyClass.java,编译后的类文件将存放在 bin/com/example/myapp/MyClass.class

5.4、类路径

类路径(Classpath)是 Java 虚拟机和编译器用来寻找类文件的路径。可以通过设置类路径,使得 JVM 和编译器可以找到包和类。

设置类路径的方法有多种:

  1. 通过命令行参数设置:
javac -cp path/to/classes com/example/myapp/MyClass.java
java -cp path/to/classes com.example.myapp.MyClass
  1. 通过环境变量 CLASSPATH 设置:
export CLASSPATH=path/to/classes
  1. 通过 manifest 文件设置:在 JAR 文件的 META-INF/MANIFEST.MF 文件中添加类路径。

示例:

# 编译并运行 MyClass
javac -d bin src/com/example/myapp/MyClass.java
java -cp bin com.example.myapp.MyClass

在这个例子中,javac 命令使用 -d 选项将编译后的类文件放在 bin 目录中,java 命令使用 -cp 选项设置类路径为 bin 目录,然后运行 MyClass


6、JAR 文件

Java Archive (JAR) 文件是用于打包多个 Java 类、元数据和资源(如图像、音频文件等)的压缩文件格式。JAR 文件使用 ZIP 文件格式进行打包,可以包含多个文件和目录结构。JAR 文件的主要用途是分发和部署 Java 应用程序和库。

6.1、创建 JAR 文件

要创建一个 JAR 文件,可以使用 jar 命令行工具。假设我们有以下目录结构:

myapp/
├── com/
│   └── example/
│       └── MyApp.class
└── resources/└── config.properties

要创建一个包含 com.example.MyApp 类和 resources/config.properties 文件的 JAR 文件,可以使用以下命令:

jar cvf myapp.jar -C myapp .

参数说明:

  • c:创建新的 JAR 文件。
  • v:生成详细输出。
  • f:指定 JAR 文件名。
  • -C:切换到指定目录并包含目录内容。

此命令会在当前目录下创建一个名为 myapp.jar 的 JAR 文件,包含 myapp 目录中的所有文件和目录结构。

6.2、查看 JAR 文件内容

可以使用 jar 命令查看 JAR 文件的内容:

jar tf myapp.jar

此命令会列出 JAR 文件中的所有文件和目录。

6.3、运行 JAR 文件

要运行一个包含主类(包含 main 方法)的 JAR 文件,需要在创建 JAR 文件时指定主类。在创建 JAR 文件时,可以使用 -e 参数指定主类:

jar cvfe myapp.jar com.example.MyApp -C myapp .

参数说明:

  • e:指定主类。

然后,可以使用 java -jar 命令运行 JAR 文件:

java -jar myapp.jar
6.4、MANIFEST.MF 文件

JAR 文件中包含一个特殊的文件 META-INF/MANIFEST.MF,用于存储关于 JAR 文件的元数据。MANIFEST.MF 文件可以包含以下信息:

  • Manifest-Version:清单文件的版本。
  • Main-Class:指定 JAR 文件的主类。
  • Class-Path:指定依赖的 JAR 文件路径。

示例 MANIFEST.MF 文件内容:

Manifest-Version: 1.0
Main-Class: com.example.MyApp
Class-Path: lib/dependency.jar

可以手动创建或编辑 MANIFEST.MF 文件,然后在创建 JAR 文件时将其包含在内:

jar cvfm myapp.jar MANIFEST.MF -C myapp .
6.5、解压 JAR 文件

可以使用 jar 命令解压 JAR 文件:

jar xvf myapp.jar

此命令会将 JAR 文件中的所有文件解压到当前目录。

相关文章:

Java 类与对象 -- Java 语言的类与对象、构造器、static、final、包和 JAR

大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 006 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进…...

MTK平台纯色背景抑制

MTK中有两个机制可以抑制纯色背景的亮度,分别是Main target、Histogram。 Main target的纯色背景亮度机制原理大概如下: 将图像分成64*48块,分别统计每一块的亮度Y。 但对于纯色背景时,如果仍然使用Luma来计算,容易造…...

Linux iptables使用详解

一、Linux系统下使用iptables 在Linux中,常用的防火墙工具是iptables。以下是一些基本的iptables命令,用于配置防火墙规则。 查看现有的iptables规则: sudo iptables -L 清除所有现有的规则(慎用,可能导致服务不可用…...

算法02 递归算法及其相关问题

递归 在编程中,我们把函数直接或者间接调用自身的过程叫做递归。 递归处理问题的过程是:通常把一个大型的复杂问题,转变成一个与原问题类似的,规模更小的问题来进行求解。 递归的三大要素 函数的参数。在用递归解决问题时&…...

三个pdf工具和浏览软件(pdftk,muppdf,epdfview)

安装pdftk pdftk是一款功能强大的PDF处理工具,主要用于对PDF文件进行各种操作。它提供了丰富的功能,包括但不限于合并、拆分、旋转、加密、解密、添加水印、从PDF文档中解出附件等。pdftk分为图形界面版本和命令行版本,适用于不同的用户需求…...

UKP3d的excel汇总表

长沙某正版用户就EXCEL的图框两点问题,进行交流: 1.1.图框:中英文两行,字体样式,logo加上等个性化需求; cbl回复:9.3后续版本迭代会加图框(解决用户个性化需求)&#xf…...

体验亚马逊AIGC——Amazon Bedrock

前言 随着人工智能技术的不断发展,我们已经进入了一个全新的时代,即AI驱动的时代。在这个时代,人工智能已经逐渐成为我们生活中不可或缺的一部分,它可以帮助我们更好地处理各种复杂的问题,提高我们的工作效率&#xff…...

Vue前端服务是什么:深入解析与实际应用

Vue前端服务是什么:深入解析与实际应用 在现今的互联网开发领域,前端技术日新月异,Vue.js作为其中的佼佼者,其前端服务更是成为了众多开发者关注的焦点。那么,Vue前端服务究竟是什么?它有哪些核心要素和实…...

mysql_ssl_rsa_setup使用详解

mysql_ssl_rsa_setup 是一个MySQL附带的工具,用于自动创建SSL证书和密钥文件,以便在MySQL服务器与客户端之间启用安全的SSL/TLS连接。这对于确保数据传输的安全性是非常重要的,尤其是在不安全的网络环境中。下面是对mysql_ssl_rsa_setup使用的…...

FreeSWITCH入门到精通系列(三):FreeSWITCH基础概念与架构

FreeSWITCH入门到精通系列(三):FreeSWITCH基础概念与架构 前言 在前两篇博客中,我们介绍了FreeSWITCH的基本概念和安装与配置。本篇文章将深入探讨FreeSWITCH的基础概念和架构,帮助您更好地理解这个强大的通信平台的…...

【C++】AVL树/红黑树实现及map与set的封装

前言 【C】二叉树进阶(二叉搜索树) 这篇文章讲述了关于二叉搜索树知识,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N)&#xff…...

利用CSS隐藏HTML元素并插入替代内容

在创建一个支持切换阅读模式和答题模式的Anki问答题模板中,我创建了一个支持切换阅读模式和答题模式的问答题模板,该文最终利用JavaScript将Anki输出的向下箭头删除,并插入自定义的提示语。经过进一步测试,发现实现上述功能完全不…...

第二节 单机版本redis部署

1. 部署环境 操作系统:centos7.XCPU: 2H内存:4GIP: 192.168.100.102部署版本: redis-7.0.15.tar.gz基础环境: gcc下载 2. 上传Redis安装包 [rootlocalhost opt]# ll 总用量 2932 drwxrwxr-x. 8 root root 4096 1…...

Vim 常用指令

Vim 是一款功能强大且高度可定制的文本编辑器。其高效的编辑方式使其成为许多程序员和系统管理员的首选。 1. Vim 的基本模式 Vim 具有以下几种基本模式: 正常模式(Normal mode):用于浏览和编辑文本(按 ESC 进入&am…...

PySide6实现pdf转化为word和长图片

目录 一:实现思路 二:实现过程 三:完整代码和实现 一:实现思路 最近在使用wps,发现wps中使用pdf转化为长图片还需要收费,这么不地道。就想自己能不能用程序实现这种功能的。还好python在自动化办公领域比较强悍,对文档操作也是得心应手。因此记录下用python实现pdf转…...

嵌入式硬件VS软件,到底哪个更难?

在嵌入式系统开发中,硬件和软件是密不可分的两个方面。但是,究竟是硬件开发更具挑战性,还是软件开发更难以应对呢?本文将就这一问题展开讨论,探究嵌入式硬件和软件在开发过程中的各种挑战与特点。 一、硬件开发&#…...

Spring boot集成log4j及日志配置详解,实战,ELK使用教程。

目录 引言一、SpringBoot 集成 Log4j1. 添加 Log4j 依赖2. 移除默认的Logback组件3. 创建 Log4j 配置文件4. 配置 Log4j2 日志文件 二、Log4j2 XML 文件配置详解基本结构Appenders 配置详解Loggers 配置详解 三、日志的作用四、日志数据采集与分析1. 日志数据采集2. 日志数据分…...

element 树组件 tree 横向纵向滚动条

Html <el-cardshadow"hover"class"solo flex-2"style"height: calc(100vh - 1.6rem); border: 1px solid #ebeef5"><div slot"header" class"clearfix"><span>问题分类</span></div><div …...

matlab 任意二维图像转点云

目录 一、概述二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 给定任意一张图片,通过代码操作将图片转成点云。图像中包含大量可用信息,其中必不可少的信息为像素坐标和像素值,将像…...

编程机器人的参数表怎么看

编程机器人的参数表怎么看 在探索编程机器人的世界时&#xff0c;理解其参数表是至关重要的一步。这些参数不仅反映了机器人的性能特点&#xff0c;还决定了其在实际应用中的表现。然而&#xff0c;对于初学者来说&#xff0c;参数表往往如同一本深奥的秘籍&#xff0c;充满了…...

上位机图像处理和嵌入式模块部署(h750 mcu串口命令处理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面学习103和407的时候&#xff0c;当时学过串口的收发。不过当时使用的主要是阻塞的方式。这一次&#xff0c;我们看下应该怎么利用中断的形式进…...

西王食品2023营收下滑、净利润大幅减亏遭问询,近三年业绩承压

《港湾商业观察》廖紫雯 日前&#xff0c;西王食品股份有限公司&#xff08;以下简称&#xff1a;西王食品&#xff0c;000639.SZ&#xff09;收到来自深交所对公司2023年年报的问询函。 深交所问询函指出&#xff0c;要求公司说明营业收入下降、净利润大幅减亏的原因及合理性…...

视频媒介VS文字媒介

看到一篇蛮有思考意义的文章就摘录下来了&#xff0c;也引起了反思 目录 一、视频的定义 二、”视频媒介“与”文字媒介”作对比 1.形象 VS 抽象 2.被动 VS 主动 三、视频的缺点-【更少】的思考 1.看视频为啥会导致【更少的思考】 2.内容的【浅薄化】 3.内容的【娱乐化…...

虚拟化 之一 详解 jailhouse 架构及原理、软硬件要求、源码文件、基本组件

Jailhouse 是一个基于 Linux 实现的针对创建工业级应用程序的小型 Hypervisor&#xff0c;是由西门子公司的 Jan Kiszka 于 2013 年开发的&#xff0c;并得到了官方 Linux 内核的支持&#xff0c;在开源社区中获得了知名度和吸引力。 Jailhouse Jailhouse 是一种轻量级的虚拟化…...

汇凯金业:黄金期货交易时间规则

黄金期货交易时间规则因交易所不同而有所差异。以下是几个主要交易所的黄金期货交易时间及其相关规则&#xff1a; 一、纽约商品交易所(COMEX) 纽约商品交易所(COMEX)是全球最大的黄金期货交易市场之一&#xff0c;其黄金期货交易时间如下&#xff1a; 电子交易时间(通过CME…...

LogicFlow 学习笔记——4. LogicFlow 基础 边 Edge

边 Edge 和节点一样&#xff0c;LogicFlow 也内置一些基础的边。LogicFlow 的内置边包括&#xff1a; 直线 - line直角折现 - polyline贝塞尔曲线 - bezier 新建 src/views/Example/LogicFlow/Example08.vue 并编写如下代码&#xff1a; <script setup lang"ts&quo…...

QPS、TPS、并发量、PV、UV

QPS、TPS、并发量、PV、UV 目录 QPS、TPS、并发量、PV、UVQPS(Queries Per Second)TPS (Transactions Per Second)并发量 (Concurrency)PV (Page Views)UV (Unique Visitors) QPS(Queries Per Second) 含义&#xff1a;每秒查询率应用场景&#xff1a;常用于计算机中各类搜索引…...

深中通道通车在即,苏州金龙新V系穿梭巴士引领大湾区交通发展新篇章

深中通道&#xff0c;总投资500亿元&#xff0c;历时七年建成的世界级跨海工程&#xff0c;即将投入运营。该桥连接深圳、中山&#xff0c;全长24公里&#xff0c;通过“桥、岛、隧、水下互通”设计&#xff0c;克服地域障碍。桥面“穿梭巴士”同步启动&#xff0c;提供24小时跨…...

集成学习 #数据挖掘 #Python

集成学习是一种机器学习方法&#xff0c;它通过结合多个模型的预测结果来提高整体性能和稳定性。这种方法的主要思想是“集合智慧”&#xff0c;通过将多个模型&#xff08;比如决策树、随机森林、梯度提升机等&#xff09;的预测集成起来&#xff0c;可以减少单个模型的过拟合…...

IDEA 中设置 jdk 的版本

本文介绍一下 IDEA 中设置 jdk 版本的步骤。 一共有三处需要配置。 第一处 File --> Project Structure Project 和 Modules 下都需要指定一下。 第二处 File --> Settings 第三处 运行时的配置...