封装
封装
封装(Encapsulation)
封装是面向对象编程(OOP)的四大特性之一(其他三个是继承、多态和抽象)。封装将对象的状态信息隐藏在对象内部,不允许外部直接访问对象的内部状态,而是通过公共的方法(接口)来访问和修改对象的状态信息。这样做的好处是提高了代码的安全性、可维护性和灵活性。
访问修饰符
public
(1)定义:public是公共的,被public所修饰的成员可以在任何类中都能被访问到。
(2)作用域:public能用来修饰类,在一个java源文件中只能有一个类被声明为public,而且一旦有一个类为public,那这个java源文件的文件名就必须要和这个被public所修饰的类的类名相同,否则编译不能通过。public用来修饰类中成员(变量和方法),被public所修饰的成员可以在任何类中都能被访问到。通过操作该类的对象能随意访问public成员。public在类的继承上的体现,被public所修饰的成员能被所有的子类继承下来。
protected
(1)定义:protected是受保护的,受到该类所在的包所保护。
(2)作用域:被protected所修饰的成员会被位于同一package中的所有类访问到。同时,被protected所修饰的成员也能被该类的所有子类继承下来。(注意:这里是指同一个package或者不同的package中的子类都能访问)
default
(1)定义:default是默认,缺省的,即在成员的前面不写任何的访问修饰符的时候,默认就是友好的。所谓友好的,是对同一package的类友好。
(2)作用域:同一package中的所有类都能访问。被friendly所修饰的成员只能被该类所在同一个package中的子类所继承下来。(也就是说只有在同一个package中的子类才能访问到父类中friendly修饰的成员)
private
(1)定义:private是私有的,即只能在当前类中被访问到,它的作用域最小。
(2)作用域:private可以修饰数据成员,构造方法,方法成员,不能修饰类(此处指外部类,不考虑内部类)。被private修饰的成员,只能在定义它们的类中使用,在其他类中不能调用。
以下是四种主要的访问修饰符及其对应的访问范围:
访问修饰符 | 描述 | 示例代码 |
---|---|---|
public |
最宽松的访问级别,允许任何类访问该成员。 | public class MyClass { public void method() {} } |
protected |
成员可以被同一个包内的类访问,也可以被不同包中的子类访问。 | protected class MyClass { protected void method() {} } |
默认(无修饰符) | 成员仅能被同一个包内的类访问,这种权限也称为包级权限。 | class MyClass { void method() {} } |
private |
最严格的访问级别,成员只能被其所在的类访问。 | private class MyClass { private void method() {} } |
访问修饰符 | 同一类中 | 同一包 | 不同包中的非子类 | 不同包中的子类 | 同包的子类 |
---|---|---|---|---|---|
public |
是 | 是 | 是 | 是 | 是 |
protected |
是 | 是 | 否 | 是 | 是 |
默认 | 是 | 是 | 否 | 否 | 是 |
private |
是 | 否 | 否 | 否 | 否 |
这里,“同一文件”意味着成员可以被定义在同一源文件中的其他类访问;“同一包”指的是成员可以被位于同一个包中的类访问;“不同包中的非子类”是指那些不在同一包内的类,且不是继承关系;而“不同包中的子类”则是指位于不同包内的类,但是子类继承了拥有成员的那个类。
请注意,这里提到的“同一文件”实际上并不是标准的术语,但它有助于理解在同一个源文件中定义的内部类或嵌套类如何访问外部类的成员。对于private
成员来说,即使在同一文件中,也只有包含该成员的类能够访问到它。
Getter 访问器和 Setter 修改器
Getter 访问器用于返回私有成员变量的值,而 Setter 修改器用于设置私有成员变量的值。它们提供了一种安全的方式来访问和修改对象的内部状态,而不需要直接暴露成员变量。
在Java中,Getter(访问器)和Setter(修改器)方法是用于访问和修改类中私有变量的常用方式。它们有助于实现封装,即隐藏对象的具体实现细节,并通过公共接口来访问和设置对象的状态。以下是关于Java中Getter和Setter的详细解释:
Getter(访问器)
Getter方法用于获取类中私有变量的值。通常,这些方法被命名为get<VariableName>
的形式,其中<VariableName>
是相应私有变量的名称。例如,如果有一个名为age
的私有变量,对应的Getter方法通常被命名为getAge()
。Getter方法不接受任何参数,并返回私有变量的值。
// 定义Engine类
public class Engine {
public void start() {
System.out.println("Engine started.");
}
}
// 定义Wheel类
public class Wheel {
public void rotate() {
System.out.println("Wheels are rotating.");
}
}
// 定义Seat类
public class Seat {
public void adjust() {
System.out.println("Seat adjusted.");
}
}
// 定义Car类,组合Engine, Wheel, Seat类
public class Car {
private Engine engine;
private Wheel wheel;
private Seat seat;
public Car(Engine engine, Wheel wheel, Seat seat) {
this.engine = engine;
this.wheel = wheel;
this.seat = seat;
}
public void startCar() {
engine.start();
wheel.rotate();
seat.adjust();
}
public Engine getEngine() {
return engine;
}
}
public class Main {
public static void main(String[] args) {
Engine engine = new Engine();
Wheel wheel = new Wheel();
Seat seat = new Seat();
Car car = new Car(engine, wheel, seat);
car.startCar(); // 启动汽车,将会依次启动引擎、旋转轮胎、调整座椅
// 通过汽车对象来启动引擎
car.getEngine().start();
}
}
public Engine getEngine() {
return engine;
}
这个 getEngine
方法是一个公共的实例方法,它返回 Car
对象中的 engine
成员变量的值。因为这个方法被声明为公共的,所以它可以在 Car
类的外部被调用。
在 Main
类的 main
方法中,你可以看到 getEngine
方法被调用的示例:
Car car = new Car(engine, wheel, seat);
car.startCar(); // 启动汽车,将会依次启动引擎、旋转轮胎、调整座椅
// 通过汽车对象来启动引擎
car.getEngine().start();
在这里,car.getEngine().start();
调用了 Car
对象的 getEngine
方法来获取 Engine
对象,然后调用了 Engine
对象的 start
方法。
如果没有 getEngine
方法,你将无法从 Car
类的外部直接访问 engine
成员变量,因为 engine
被声明为私有的。私有成员变量只能在其所属类的内部被访问。getEngine
方法提供了一种方式来“获取”或“访问”这个私有成员变量,这就是为什么它被称为“访问器”方法。
Setter(修改器)
Setter方法用于设置类中私有变量的值。这些方法通常被命名为set<VariableName>
的形式,其中<VariableName>
是相应的私有变量的名称。例如,对于名为age
的变量,对应的Setter方法通常被命名为setAge(int age)
。Setter方法接受一个参数,并用该参数值更新私有变量。
public class Person {
private int age;
public void setAge(int age) {
this.age = age;
}
}
Getter和Setter的作用
使用Getter和Setter有几个关键的作用:
-
封装:通过将类的属性声明为私有,并通过公共的Getter和Setter方法来访问这些属性,可以实现更好的封装。这使得类的实现细节对外界隐藏起来,从而提高了代码的安全性和稳定性。
-
验证:在Setter方法中可以添加验证逻辑,以确保只有合法的值才能被设置到私有变量中。例如,可以确保年龄值在合理范围内。
-
灵活性:通过使用Getter和Setter,可以在不修改现有客户端代码的情况下改变类的行为。例如,可以在Getter方法中添加额外的计算逻辑,或者在Setter方法中添加额外的处理步骤。
-
框架支持:某些Java框架(如Hibernate或Spring)依赖于Getter和Setter方法来进行属性的自动管理或依赖注入。
示例
下面是一个完整的示例,展示了如何使用Getter和Setter方法来保护一个类的私有属性,并在设置属性值时进行验证:
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setAge(25); // 正确
System.out.println(person.getAge()); // 输出 25
person.setAge(-1); // 抛出异常
}
}
常见错误
在实现Getter和Setter时,有一些常见的错误需要注意:
-
未使用受限访问修饰符:如果私有变量被声明为公共(public),则可以直接使用点(.)运算符来访问它,从而使Getter和Setter变得无效。应当使用更受限制的访问修饰符(如protected或private)来保护变量。
-
直接在Setter中分配对象引用:如果在Setter方法中直接赋值对象引用,可能会导致对象状态的不一致。为了加强封装,可以创建新的对象并将原始对象的内容复制到新对象中。
综上所述,使用Getter和Setter不仅可以帮助保护类的内部状态,而且还能增强代码的可读性、可维护性和安全性。
注:其他类如何访问所属类里的私有成员变量
在Java中,由于封装原则,其他类是不能直接访问一个类的私有成员变量的。但是,可以通过一些方式间接地访问这些私有成员变量:
使用Getter和Setter方法
最常见的方式是通过提供公共的getter和setter方法来访问私有变量。这种方式不仅符合面向对象的设计原则,还允许在方法中加入必要的逻辑处理,比如参数校验或者日志记录。
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("John Doe");
System.out.println(person.getName());
}
}
使用反射机制
尽管不推荐这样做,因为反射破坏了封装性并且可能引入安全性问题,但在某些特定情况下,可以使用Java反射API来访问私有成员变量。这种方式适用于需要动态操作类的情况,比如框架或者库的开发。
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception {
Person person = new Person();
Field nameField = Person.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person, "Jane Doe");
System.out.println(nameField.get(person));
}
}
class Person {
private String name;
}
内部类访问
如果一个类定义了内部类,那么这个内部类可以访问外部类的所有成员,包括私有的成员。这是因为内部类被认为是外部类的一部分。
public class OuterClass {
private String secret;
public class InnerClass {
public void printSecret() {
System.out.println(secret); // 可以访问外部类的私有成员
}
}
}
public class Main {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.secret = "Top Secret";
OuterClass.InnerClass inner = outer.new InnerClass();
inner.printSecret();
}
}
子类访问
如果父类中的私有成员变量被子类继承,那么子类是不能直接访问这些私有成员的。但如果需要在子类中访问父类的私有成员,可以考虑让父类提供相应的getter和setter方法。
总之,最佳实践是使用getter和setter方法来访问私有成员变量,这样不仅可以保持封装性,还能增加代码的灵活性和安全性。
this 关键字
this
关键字在Java中用于引用当前对象实例本身。它可以帮助解决一些常见的编程问题,特别是在处理类的实例变量和方法时。以下是this
关键字的主要用途:
-
引用当前对象的成员变量:当局部变量和成员变量同名时,
this
关键字可以帮助区分两者。例如:public class Person { private String name; public void setName(String name) { this.name = name; // 使用this来引用成员变量name } }
-
调用当前对象的方法:在方法内部,可以通过
this
来调用当前对象的其他方法。例如:public class MyClass { public void methodA() { this.methodB(); // 调用当前对象的方法methodB() } public void methodB() { // 方法body } }
-
作为方法的返回值:如果方法需要返回当前对象,可以使用
this
作为返回值。这常用于实现方法链式调用。例如:public class MyClass { public MyClass setProperty(String property) { // 设置属性... return this; // 返回当前对象 } }
-
在构造函数中调用本类的另一个构造函数:使用
this
可以在一个构造函数中调用同一类中的另一个构造函数。例如:public class MyClass { private int x; private String s; public MyClass(int x) { this(x, "Default"); // 调用另一个构造函数 } public MyClass(int x, String s) { this.x = x; this.s = s; } }
5.强调当前对象的引用(链式调用支持):在Java中,链式调用(Chaining)是一种允许你连续调用同一个对象上的多个方法,并且每个方法调用都返回该对象本身(通常是this
)的编程模式。这种方式可以使得代码更加简洁和易于阅读。为了支持链式调用,你需要确保每个参与链式调用的方法都返回当前对象的引用(即this
)。
下面是一个简单的例子来展示如何在Java中实现链式调用:
public class BuilderExample {
private String firstName;
private String lastName;
private int age;
// 构造函数可以是空的
public BuilderExample() {}
// 设置firstName并返回this
public BuilderExample setFirstName(String firstName) {
this.firstName = firstName;
return this; // 返回当前对象的引用
}
// 设置lastName并返回this
public BuilderExample setLastName(String lastName) {
this.lastName = lastName;
return this; // 返回当前对象的引用
}
// 设置age并返回this
public BuilderExample setAge(int age) {
this.age = age;
return this; // 返回当前对象的引用
}
// 打印信息的方法
public void printInfo() {
System.out.println("Name: " + firstName + " " + lastName + ", Age: " + age);
}
public static void main(String[] args) {
BuilderExample example = new BuilderExample()
.setFirstName("John")
.setLastName("Doe")
.setAge(30)
.printInfo(); // 链式调用
}
}
在这个例子中,我们定义了一个名为BuilderExample
的类,它有几个方法用来设置这个对象的属性,并且每个方法最后都返回了this
,这样就可以支持链式调用了。在main
方法中,我们可以看到如何连续地调用这些方法,形成一个链式调用。
这种方法在构建者模式(Builder Pattern)中特别常见,其中this
关键字的使用是实现链式调用的关键部分。通过返回当前对象的引用,我们可以使代码更加紧凑和易读,同时保持良好的封装性和清晰性。
static 关键字
static
关键字用于声明静态成员,包括静态变量、静态方法和静态代码块。
static关键字的用法与理解
1.static关键字用于声明全局方法或变量,使它们不属于任何特定对象,而是属于类本身。
2.静态方法不依赖于任何实例对象,可以直接通过类名调用,无需创建对象实例。
3.静态变量是类级别的变量,被所有对象共享,不是每个对象都有其独立的副本。
4.封装与static关键字无直接关系,封装主要是指将属性设为私有,并提供公共方法进行访问。
-
静态变量:
-
静态变量被所有类的实例共享。一旦声明为
static
,变量就成为了类级别的变量,而不是实例级别的变量。这意味着不论创建多少个对象,该变量都只有一份拷贝。 -
代码示例:
public class MyClass { static int sharedCount = 0; // 静态变量 public MyClass() { sharedCount++; // 所有实例共享这个计数器 } }
-
-
静态方法:
-
静态方法也是类级别的方法,不是实例级别的方法。这意味着你可以在不创建类的实例的情况下调用这些方法。
-
属于类本身,不能直接访问类的非静态成员(变量和方法),因为非静态成员属于对象,而静态方法属于类。
静态变量与方法的应用
1.静态变量和方法可以独立于任何实例对象,通过类名直接访问。
2.静态变量作为类的全局变量,可以在没有创建对象的情况下使用。
3.静态方法不能直接访问非静态变量或方法,因为非静态成员依赖于实例对象。
-
代码示例:
public class MyClass { public static void staticMethod() { System.out.println("This is a static method."); } }
-
-
静态代码块:
-
在类加载时执行,且只执行一次。常用于初始化静态变量或执行只需执行一次的静态初始化操作。
-
代码示例:
public class MyClass { static { System.out.println("Static block executed."); } }
-
-
静态内部类:
- 静态内部类不需要依赖外部类的实例即可创建。
jar 包
JAR(Java ARchive)包是Java平台中的一种文件格式,用于将多个Java类文件、相关的元数据和资源(如文本、图片等)打包成一个文件。JAR文件的扩展名为.jar
,它实际上是一个压缩文件,类似于ZIP格式,但专门为Java应用程序设计。下面是对JAR包的详细解释及其使用方法:
JAR包的基本概念
JAR包可以包含以下几种内容:
- 类文件(.class):编译后的Java类文件。
- 资源文件:如配置文件、图片、音频等。
- 元数据:如清单文件(MANIFEST.MF),描述JAR包的属性。
- 库文件:其他JAR包或库文件,可以嵌套在JAR包中。
JAR包的优势
- 简化分发:将所有相关文件打包成一个文件,方便分发和部署。
- 提高性能:JAR包可以被Java虚拟机(JVM)直接加载,提高加载速度。
- 安全性:JAR包可以被签名,确保代码的完整性和来源。
创建JAR包
创建JAR包可以通过多种方式完成:
使用命令行创建JAR包
-
编译Java源代码:首先需要将Java源代码编译成字节码文件(
.class
)。javac HelloWorld.java
这将生成一个
HelloWorld.class
文件。 -
创建JAR包:使用
jar
命令创建JAR包。jar cf HelloWorld.jar HelloWorld.class
其中,
c
表示创建新的JAR包,f
表示指定JAR包的文件名。 -
运行JAR包:使用
java
命令运行JAR包。java -jar HelloWorld.jar
使用清单文件(MANIFEST.MF)
清单文件用于描述JAR包的属性,例如可以指定JAR包的主类。
Manifest-Version: 1.0
Main-Class: HelloWorld
将清单文件保存为MANIFEST.MF
,然后创建JAR包时指定清单文件:
jar cfm HelloWorld.jar MANIFEST.MF HelloWorld.class
这里,m
表示指定清单文件。
使用IDE创建JAR包
大多数Java集成开发环境(IDE),如Eclipse和IntelliJ IDEA,都提供了创建JAR包的图形化界面。以下是使用IntelliJ IDEA的步骤:
- 右键点击项目,选择Open Module Settings。
- 在Artifacts选项卡中,点击+,选择JAR -> From modules with dependencies。
- 选择主类,并配置输出路径。
- 点击OK,然后点击Build -> Build Artifacts,选择Build。
JAR包的高级应用
嵌套JAR包
JAR包可以包含其他JAR包,这在分发依赖库时非常有用。你可以将依赖库的JAR包放在lib目录下,然后打包成一个包含所有依赖的JAR包。
签名JAR包
为了确保JAR包的完整性和来源,可以对JAR包进行签名。签名后的JAR包可以被验证,防止篡改。
jarsigner -keystore myKeystore -storepass myPassword HelloWorld.jar myKeyAlias
使用JAR包作为库
你可以将JAR包作为库文件,供其他项目使用。只需将JAR包添加到项目的类路径中即可。
总结
JAR包是Java开发中不可或缺的工具,它简化了代码的分发和部署,提高了性能和安全性。无论是初学者还是资深开发者,理解和熟练使用JAR包都将极大地提升Java开发效率。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)