6封装
6.1封装的概念
面向对象程序有三大特性:封装、继承、多态。这节就主要围绕封装讲讲。
那么,封装是什么呢。以电脑为例,我们都知道电脑主机内部是由很多电子元器件很多电路组成的,但我们只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。那么计算机厂商在出厂时,在外部套上壳 子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,这个细节隐藏就是封装。
6.2怎么封装
在Java中,封装主要通过访问修饰符来实现。Java提供了四种访问修饰符:
private
:私有访问级别,只有定义该成员的类内部可以访问,任何外部类都无法直接访问。default
(也称为包访问级别):没有指定访问修饰符时默认为此级别,同一个包(package)内的类可以访问。protected
:受保护的访问级别,同一个包内的类可以访问,不同包的子类也可以访问。public
:公共访问级别,任何地方都可以访问
6.3包
包是Java中组织类的一种方式,它提供了一种命名空间机制,有助于避免命名冲突和提高模块性。简单来说,就类似我们的文件夹,将多个类放在一个包里,方便分类和查找。
那么该如何创建和使用包呢?
在Java中,每个源文件(.java
文件)都属于一个包。要在Java文件中指定包,你需要在文件的最开始(在任何类定义之前)使用package
语句。package就代表它属于哪个包。
// 声明一个包
package com.example.myapp;
public class MyClass {
// 类定义...
}
如果要访问不同包中的类,我们需要使用完全限定名(即包名和类名,之间用.
分隔)。同时避免在每次引用其他包中的类时都使用完全限定名,你可以使用import
语句来导入所需的类或整个包。
例如:你有一个名为MyClass
的类,它位于com.example.myapp
包中。如果你想要在你的另一个包(比如com.example.utils
)中的另一个类MyUtility
中访问MyClass
,
// MyUtility.java 文件,位于 com.example.utils 包中
package com.example.utils;
// 导入 MyClass 类所在的包
import com.example.myapp.MyClass;
public class MyUtility {
public void doSomething() {
// 使用完全限定名来创建 MyClass 的实例(虽然这里已经导入了 MyClass,所以直接使用 MyClass 也可以)
MyClass myObject = new MyClass();
// ... 其他操作
}
}
我们再用上前面封装的知识来看两个不同包之间的访问:
com.example.package1 包
ClassA.java
// 文件路径: src/com/example/package1/ClassA.java
package com.example.package1;
public class ClassA {
public int publicField = 1;
protected int protectedField = 2;
int defaultField = 3; // 默认(包级私有)访问修饰符
private int privateField = 4;
public void publicMethod() {
System.out.println("Public method called.");
}
protected void protectedMethod() {
System.out.println("Protected method called.");
}
void defaultMethod() { // 默认(包级私有)访问修饰符
System.out.println("Default method called.");
}
private void privateMethod() {
System.out.println("Private method called.");
}
}
com.example.package2 包
ClassB.java (尝试访问ClassA的成员,但位于不同包)
// 文件路径: src/com/example/package2/ClassB.java
package com.example.package2;
import com.example.package1.ClassA;
public class ClassB {
public void accessClassA() {
ClassA obj = new ClassA();
// 可以访问
System.out.println(obj.publicField);
// 编译错误:protectedField 在不同包中不能直接访问
// System.out.println(obj.protectedField);
// 编译错误:defaultField 是包级私有的,不能在不同包中访问
// System.out.println(obj.defaultField);
// 编译错误:privateField 是私有的,不能在任何地方(除了ClassA内部)访问
// System.out.println(obj.privateField);
// 可以访问
obj.publicMethod();
// 编译错误:protectedMethod 在不同包中不能直接访问
// obj.protectedMethod();
// 编译错误:defaultMethod 是包级私有的,不能在不同包中访问
// obj.defaultMethod();
// 编译错误:privateMethod 是私有的,不能在任何地方(除了ClassA内部)访问
// obj.privateMethod();
}
public static void main(String[] args) {
ClassB b = new ClassB();
b.accessClassA();
}
}
7Static
在Java中,static
关键字是一个非常重要的修饰符,它可以应用于变量、方法、代码块以及内部类。 static
修饰的成员属于类本身,而不是类的某个特定对象。这意味着,无论创建了多少个类的对象,static
成员在内存中只有一份拷贝,被所有对象共享。
7.1静态成员变量
以学生为例,我们有两个学生,每个学生都有自己的姓名和年龄,这是他们独有的成员变量。但一个专业的人可能会在一个教室上课,如果再给他们每人安排一个成员变量就太浪费内存了。为此就有了 static 修饰的成员,所有人共享。一个人的教室的变更,意味着所有人教室的变更。
public class MyClass {
static int count = 0; // 静态变量
public MyClass() {
count++; // 每次创建对象时,静态变量递增
}
public static void main(String[] args) {
MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();
System.out.println(MyClass.count); // 输出2
}
}
观察这段代码,我们会发现静态变量可以通过类名直接访问,而不需要创建类的实例。
静态变量是不依附对象,其内容也是共享的。
特点:
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3. 类变量存储在方法区当中
4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
7.2静态成员方法
静态方法属于类,而不是类的某个对象。因此,它们不能访问类的非静态成员(变量和方法),静态方法可以通过类名直接调用,而不需要创建类的实例。
public class MyClass {
static void showMessage() {
System.out.println("Hello, static method!");
}
public static void main(String[] args) {
MyClass.showMessage(); // 直接通过类名调用静态方法
}
}
特点:
1. 不属于某个具体的对象,是类方法
2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者
3. 不能在静态方法中访问任何非静态成员变量
4. 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用(this引用需要对象)
7.3静态代码块
下一节代码块讲。
8代码块
代码块一般分四种。
1.普通代码块
2.构造块
3.静态块
4.同步代码块(暂时不讲,我也不会)
8.1普通代码块
它们通常用于限制变量的作用域,以提高代码的可读性和可维护性。局部代码块中的变量在代码块外部是不可见的。 用的比较少。
public void myMethod() {
{
// 这是一个局部代码块
int localVariable = 10;
System.out.println(localVariable);
// localVariable 在这里之后就不再可访问
}
// 这里不能访问 localVariable
}
8.2构造块
构造块又叫实例代码块,是在类的方法之外但在类的任何构造函数之前定义的代码块。每次创建类的实例时,都会执行这些代码块(在构造函数之前)。它们通常用于初始化实例变量。
public class MyClass {
// 实例变量
int instanceVar;
// 实例代码块
{
// 这里是实例代码块的内容
// 可以在这里初始化实例变量
instanceVar = 10;
System.out.println("实例代码块执行");
}
// 构造函数
public MyClass() {
// 构造函数的内容
System.out.println("无参构造函数执行");
}
// 其他构造函数...
}
特点:
- 自动执行:每当创建类的新对象时,实例代码块都会在任何构造函数之前自动执行。
- 初始化:实例代码块通常用于初始化实例变量,尤其是当这些初始化操作需要在所有构造函数中都执行时。
- 执行顺序:如果有多个实例代码块和构造函数,它们将按照在类中出现的顺序执行。首先执行所有实例代码块,然后执行构造函数。
8.3静态块
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量
public class MyClass {
static {
// 这是一个静态代码块
System.out.println("Static Block");
}
public static void main(String[] args) {
System.out.println("Main Method");
// 静态代码块在main方法之前执行,且只执行一次
}
}
// 输出:Static Block, Main Method
特点:
- 自动执行:当类被JVM首次加载时,静态代码块会自动执行。
- 只执行一次:静态代码块在类的生命周期内只执行一次,无论创建多少个类的实例。
- 初始化:静态代码块通常用于初始化静态变量或执行只需执行一次的静态初始化操作。
- 执行顺序:如果有多个静态代码块,它们将按照在类中出现的顺序执行。