封装

封装

  • 封装是将数据(变量)和操作(方法或函数)捆绑在一起的机制,使得对象的内部细节对外部是隐藏的,只有通过公共接口才能访问。
  • 封装提供了访问控制,可以限制对对象内部数据的直接访问,从而保护数据的完整性和安全性。
  • 通过封装,可以隐藏实现的细节,使得代码更易于理解和维护,并且提高了代码的可重用性。

:类中定义对象的属性数据(成员变量),方法(成员方法)

类第一次使用时会加载到方法区

Java 内存的结构分析

  1. 栈: 一般存放基本数据类型(局部变量)

  2. 堆: 存放对象(Cat cat , 数组等)

  3. 方法区:常量池(常量,比如字符串), 类加载信息

image-20240223225406365

对象

  • 从模板中创建的具体实例,实例是数据的打包
  • 新建实例时,在堆内存中新分配内存空间给这个实例

成员变量与局部变量区别

  • 定义的位置不一样

局部变量:在方法的内部

成员变量:在方法的外部,直接写在类当中

  • 作用范围不一样

局部变量:只有方法当中才可以使用,出了方法就不能再用

成员变量:整个类全都可以通用

  • 默认值不一样

局部变量:没有默认值,如果要想使用,必须手动进行赋值

成员变量:如果没有赋值,会有默认值,规则和数组一样

  • 内存的位置不一样

局部变量:位于栈内存

成员变量:位于堆内存

  • 生命周期不一样

局部变量:随着方法进栈而诞生,随着方法出栈而消失

成员变量:随着对象创建而诞生,随着对象被垃圾回收而消失

引用变量

保存一个实例的内存地址(引用变量保存在栈),引用变量的特殊值:null 不保存任何实例的内存地址

image-20240223230937448

this关键字

特殊引用,引用当前对象的地址

this:构造方法之间的调用,必须是首行代码,如果有多个构造方法,会通过this(…)调取下面的所有构造方法,完成赋值。

注意

  • this不能在静态方法中使用
  • 当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量。
  • 如果需要访问本类当中的成员变量,需要使用格式:this.成员变量名
  • 通过谁调用的方法,谁就是this。
1
2
3
4
5
6
7
8
public class Person {
String name; // 成员变量,我自己的名字
// 参数name是对方的名字
// 成员变量name是自己的名字
public void sayHello(String name) {
System.out.println(name + ",你好。我是" + this.name);
}
}

static 关键字

static关键字用于在Java中创建类级别的成员,这些成员不依赖于类的实例而存在,可以直接通过类名访问。

从概念上讲,类变量所使用的内存都应当在 方法区 中进行分配。不过有一点需要注意的是:JDK 7 之前,HotSpot 使用永久代来实现方法区的时候,实现是完全符合这种逻辑概念的。 而在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。

image-20240420112005906

静态变量

  • 当 static 修饰成员变量时,该变量称为类变量。
  • 该类的每个对象都共享同一个类变量的值。
  • 任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。
  • 随着类的加载而加载的,且只加载一次(JDK1.7之后随着Class对象加载到堆区)
  • 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
  • 它优先于对象存在,所以可以被所有对象共享

静态方法

  • 一旦使用static修饰成员方法,那么这就成为了静态方法。
  • 静态方法不属于对象,而是属于类的。
  • 如果没有static关键字,那么必须首先创建对象,然后通过对象才能使用它。
  • 如果有了static关键字,那么不需要创建对象,直接就能通过类名称来使用它。

无论是成员变量,还是成员方法。如果有了static,都推荐使用类名称进行调用。

静态变量:类名称.静态变量

静态方法:类名称.静态方法()

注意事项:

  • 静态方法不能直接访问非静态方法。原因:当编译器解析静态方法时,它不需要创建类的实例,因此无法确定要调用的非静态方法是哪个实例的方法。非静态方法的调用需要通过对象的引用来实现,而静态方法中没有当前对象的引用,无法进行非静态方法的调用。
  • 静态方法当中不能用this。原因:因为this关键字引用的是当前对象的引用,而静态方法没有当前对象的引用。
  • 如果需要需要静态方法调用非静态方法,需要通过new 对象.method()的方式调用

静态代码块

定义在成员位置,使用static修饰的代码块{ }。

执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行

静态代码块的典型用途:用来一次性地对静态成员变量进行赋值。

image-20240223225722596

final关键字

表示不可改变。可以用于修饰类、方法和变量

final类

被修饰的类,不能被继承

含义:当前这个类不能有任何的子类。

注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写

final方法

被修饰的方法,不能被重写

当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。

注意事项:对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾

final变量

被修饰的变量,不能被重新赋值。

对于基本类型来说,不可变说的是变量当中的数据不可改变
对于引用类型来说,不可变说的是变量当中的地址值不可改变

对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。

  • 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
  • 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值,二者选其一。
  • 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person {
private final String name;
public Person() { //通过构造方法赋值
name = "关晓彤";
}

public Person(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

对于基本类型来说,不可变说的是变量当中的数据不可改变
对于引用类型来说,不可变说的是变量当中的地址值不可改变

1
2
3
4
5
6
7
final Student stu2 = new Student("高圆圆");
//错误写法!final的引用类型变量,其中的地址不可改变
//stu2 = new Student("赵又廷");

System.out.println(stu2.getName()); // 高圆圆
stu2.setName("赵又廷");
System.out.println(stu2.getName()); // 赵又廷

四种权限修饰符

image-20240223230745519

可见,public具有最大权限。private则是最小权限。 编写代码时,如果没有特殊的考虑,建议这样使用权限:

成员变量使用 private ,隐藏细节。

构造方法使用 public ,方便创建对象。

成员方法使用 public ,方便调用方法。

小贴士:不加权限修饰符,其访问能力与default修饰符相同

private修饰符

  • private是一个权限修饰符,代表最小权限。
  • 可以修饰成员变量和成员方法。
  • 被private修饰后的成员变量和成员方法,只在本类中的方法才能访问

间接访问

private成员变量,就是定义一对儿Getter/Setter方法

必须叫setXxx或者是getXxx命名规则。

对于Getter来说,不能有参数,返回值类型和成员变量对应;

对于Setter来说,不能有返回值,参数类型和成员变量对应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {
String name; // 姓名
private int age; // 年龄

public void show() {
System.out.println("我叫:" + name + ",年龄:" + age);
}

// 这个成员方法,专门用于向age设置数据
public void setAge(int num) {
if (num < 100 && num >= 9) { // 如果是合理情况
age = num;
} else {
System.out.println("数据不合理!");
}
}

// 这个成员方法,专门获取age的数据
public int getAge() {
return age;
}
}

内部类

成员内部类

成员内部类的定义格式:

1
2
3
4
5
6
修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
// ...
}
// ...
}

访问特点

  • 内部类可以直接访问外部类的成员,包括私有成员。
  • 外部类要访问内部类的成员,必须要建立内部类的对象。

如何使用成员内部类?有两种方式:

  • 在外部类的方法当中,直接new 内部类;然后main使用这个外部类的方法
  • 公式法:外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称()
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Body {  // 外部类
public class Heart { // 成员内部类
// 内部类的方法
public void beat() {
System.out.println("心脏跳动:蹦蹦蹦!");
}
}
//外部类的方法,可以直接new内部类调用方法
public void methodBody() {
System.out.println("外部类的方法");
new Heart().beat();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Demo01InnerClass {
public static void main(String[] args) {
Body body = new Body(); // 外部类的对象
//通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
body.methodBody();

//公式法
Body.Heart heart = new Body().new Heart();
heart.beat();

}
}

局部内部类

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类

“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Outer {
public void methodOuter() {
// 局部内部类,定义在方法内部
class Inner {
int num = 10;
public void methodInner() {
System.out.println(num); // 10
}
}
Inner inner = new Inner(); //只能在方法内访问局部内部类
inner.methodInner();
}
//方法外不能访问局部内部类
//Inner inner = new Inner();
}

局部内部类要访问局部变量,为什么该变量必须是final?

备注:从Java 8+开始,只要局部变量事实不变(即不改变该局部变量的值),那么final关键字可以省略。

原因:

  • new出来的对象在堆内存当中。
  • 局部变量是跟着方法走的,在栈内存当中,方法运行结束之后,立刻出栈,局部变量就会立刻消失。
  • 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失,当内部类对象要使用局部变量时就会出错
  • 因为局部内部类的生命周期比方法的局部变量长,所以要确保方法内的局部变量不改变
1
2
3
4
5
6
7
8
9
10
public class MyOuter {
public void methodOuter() {
final int num = 10; // 所在方法的局部变量,final可省略
class MyInner {
public void methodInner() {
System.out.println(num);
}
}
}
}

那为什么添加final修饰的局部变量,就可以被局部内部类引用呢?

若定义为final,则java编译器则会在内部类内生成一个外部变量的拷贝,而且可以既可以保证内部类可以引用外部属性,又能保证值的唯一性

也就是拷贝了一个变量的副本,提供给局部内部类,这个副本的生命周期和局部内部类一样长,并且这个副本不可以修改,保证了数据的同步

匿名内部类

如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,

那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。

1
2
3
接口名称 对象名 = new 接口名称() {
// 覆盖重写所有抽象方法
};
  • new代表创建对象的动作

  • 接口名称就是匿名内部类需要实现哪个接口

  • {…}这才是匿名内部类的内容

另外还要注意几点问题:

匿名内部类,在【创建对象】的时候,只能使用唯一一次。

如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。

匿名对象,在【调用方法】的时候,只能调用唯一一次。

如果希望同一个对象,调用多次方法,那么必须给对象起个名字。

匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】

强调:匿名内部类和匿名对象不是一回事

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public interface MyInterface {
void method1();
void method2();
}


public static void main(String[] args) {

// 使用匿名内部类,但不是匿名对象,对象名称就叫objA
MyInterface objA = new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-A");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-A");
}
};
objA.method1();
objA.method2();
System.out.println("=================");


//使用了匿名内部类,而且省略了对象名称,也是匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method1();


// 因为匿名对象无法调用第二次方法,所以需要再创建一个匿名内部类的匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method2();
}

静态内部类

  • 静态内部类是指被声明为 static 的内部类。在 Java 中,类可以嵌套在其他类中,被嵌套的类称为内部类。静态内部类与普通内部类的区别在于,静态内部类可以直接通过外部类的类名进行访问,而不需要先创建外部类的实例。

    以下是静态内部类的一些特点和用法:

    1. 静态内部类的声明:静态内部类使用 static 关键字进行声明,它可以访问外部类的静态成员和方法,但不能访问外部类的非静态成员和方法。静态内部类的声明形式如下:

      1
      2
      3
      4
      5
      public class OuterClass {
      static class StaticInnerClass {
      // 静态内部类的成员和方法
      }
      }
    2. 访问方式:静态内部类可以直接通过外部类的类名进行访问,无需先创建外部类的实例。例如:

      1
      OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
  1. 独立性:静态内部类与外部类之间的关系不像普通内部类那样密切,静态内部类可以独立于外部类而存在,因此它不会持有外部类的引用。

  2. 静态内部类的作用:静态内部类通常用于以下情况:

    • 帮助组织类和提高代码的可读性,将相关的类放在一起。
      • 如果内部类不需要访问外部类的实例变量或方法,可以将其声明为静态内部类,避免与外部类实例的绑定。
      • 在外部类的静态方法中使用静态内部类
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
new Heart().beat();
}

public static class Heart { // 成员内部类
// 内部类的方法
public void beat() {
System.out.println("心脏跳动:蹦蹦蹦!");
}
}

封装
http://example.com/2023/05/13/Java/Java基础/封装/
作者
PALE13
发布于
2023年5月13日
许可协议