【持续更新】后端根基

【持续更新】后端根基

-面向对象

三大特征:封装、继承、多态。

万物皆可归类,一个类与外界的封装关系,一个父类和子类的继承关系,一个类和多个类的多态关系。

  1. 封装:对象代表什么,就得封装对应的数据,并提供数据对应的行为。

封装对外隐藏了类的内部实现机制,对外提供访问它的方法,可以在不影响外部使用的情况下改变类内部的结构。说白了就是低耦合,高内聚。使用者通过实现制定好的方法来访问数据,无需关心方法的内部实现。

创建对象时问自己三个问题:要干什么、要用什么东西才能干、干完要不要反馈

  1. 继承:复用。子类继承父类的属性行为,使得子类可以直接具有与父类相同的属性和行为。

子类可以直接访问父类中的非私有的属性和行为。Java通过extends关键字实现继承。Java是单继承的,一个类只能继承一个直接父类,但是可以多级继承。

子类不能继承父类的构造方法。

子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已。可以通过getter/setter方法访问父类的私有成员变量。子类父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果想访问父类成员变量,可以使用super关键字,super代表的是父类对象的引用,this代表的是当前对象的引用。

  1. 多态:是指同一行为,具有多个不同表现形式。

多态的前提:1)有继承或者实现关系;2)方法的重写;3)父类引用指向子类对象。

子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量。

如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。

多态的应用场景1

有了多态之后,方法的形参就可以定义为共同的父类Person。

多态的应用场景2

多态的特点:

调用成员变量时:编译看左边,运行看左边

调用成员方法时:编译看左边,运行看右边

1
2
3
4
5
6
7
Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();

多态的弊端:当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有而父类没有的方法。编译都错误,更别说运行了。所以,想要调用子类特有的方法,必须做向下转型,即父类类型向子类类型向下转换的过程,这个过程是强制的。

为了避免转型报错,Java提供了instanceof关键字。if(a instanceof Dog d)先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d,如果不是Dog类型,则不强转,结果直接是false

-方法重写

子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。

@Override:注解,重写注解校验。只有被添到虚方法表中的方法才能被重写,私有和静态不能添加到虚方法表!

-成员变量、局部变量

  • 成员变量是定义在类的内部,任何方法之外的变量。它们代表了类的状态或属性,每个类的实例(对象)都拥有自己的一套成员变量副本。会有默认初始化,在堆对象内存里(这个说法很片面)。

“成员变量在堆内存里,局部变量在栈内存里”这句话是一中比较片面的说法。不管成员变量还是局部变量,如果是基础数据类型,那么直接存在于栈中,如果是引用数据类型,其引用放在栈里,对象实体放在堆里。

  • 局部变量在方法里声明,存储在栈里,没有默认初始化,需要手动赋值。
1
2
3
4
5
6
7
public void calculate() {
int a = 10; // 基本类型 → 值 '10' 在栈中
double b = 20.5; // 基本类型 → 值 '20.5' 在栈中
char c = 'X'; // 基本类型 → 值 'X' 在栈中

// 方法执行完毕,栈帧弹出,a, b, c 被自动清除
}

-简要理解JVM内存区

堆区:

  1. 存储的全部是对象,每个对象都包含一个与之对应的类的信息。(类信息的目的是得到操作指令)

    所有实例变量都存储在堆(Heap)内存中,作为对象实例数据的一部分。当你是基本类型时,你的值本身直接存储在对象所属的那块堆内存空间中。当 你是引用类型时,在对象所属的堆内存中,存储的是你指向的那个对象的内存地址(引用),你所指向的那个实际对象也存储在堆内存中。

1
2
3
4
5
6
7
8
public class Student {
// 实例变量
private String name; // 引用类型 → 值在堆中,地址在对象的空间里
private int age; // 基本类型 → 值直接在对象的空间里
private Address address; // 引用类型 → 地址在对象的空间里,Address对象在堆中另一处
}

Student stu = new Student("ywb", 20, new Address("Shandong"));
  1. jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。

栈区:

  1. 每个线程包含一个栈区,只保存局部变量和自定义对象的引用(不是对象)。

当基本数据类型作为局部变量(在方法内部声明),存储在栈中。

当基本数据类型作为对象的成员变量(实例字段)时,它们存储在堆(Heap)中,因为它们是对象的一部分。

当基本数据类型被 static 关键字修饰时,它们属于类,而不是任何对象实例,存储在方法区。

  1. 每个栈中的数据都是私有的,其他栈不能访问。

  2. 栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

方法区:

1.方法区又叫静态区,被所有的线程共享。方法区包含所有的类信息和static变量。

方法区中包含的都是在整个程序中永远唯一的元素。

1
2
3
4
5
6
public class Student {
private String name; // 实例变量 → 堆
private int age; // 实例变量 → 堆

public static String school = "Harvard"; // 静态变量 → 方法区(元空间)
}

2.因为不属于任何对象,所以不会在创建对象时在堆中为它们分配空间。它们在类加载阶段就被初始化并放入方法区。。

总结:

  1. 看声明位置:问自己“这个变量是在哪里声明的?”

  2. 应用规则

    • 方法内部? →
    • 类内部(无static)? → 作为对象一部分,在
    • 类内部(有static)? → 方法区
  3. 为什么这么设计?(背后的原理)

    这种设计是基于性能和内存管理的考虑:

    • :分配和销毁效率极高(只是移动栈指针),适合生命周期短、仅限于方法内部的变量。
    • :用于存储生命周期不确定的对象及其所有数据,方便进行统一的垃圾回收(GC)。
    • 方法区:用于存储“唯一”的、与类相关的数据,所有对象实例共享这一份数据。

下图中new 出来的 Student 对象,其所有成员变量都存储在堆内存中:

下图为两个对象的内存图:

-虚方法表

-构造方法

构造方法是一种特殊的方法,用来完成对象数据的初始化。(可以使用带参构造,为成员变量进行初始化)

  • 如果没有定义构造方法,系统将给出一个默认的无参数构造方法。
  • 如果定义了构造方法,系统将不再提供默认的构造方法。

子类的构造方法中默认有一个super() ,表示调用父类的构造方法。this()默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法,为了借用其他构造方法的功能。super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

-标准JavaBean

  1. 类名需要见名知意;

  2. 成员变量(类内方法外)使用private修饰;

  3. 提供至少两个构造方法;

  • 无参构造方法
  • 带全部参数的构造方法
  1. get和set方法;

    提供每一个成员变量对应的setXxx()/getXxx()

  2. 如果还有其他行为(hashCode、equals、toString等),也需要写上。

-消息转换器