0225学习笔记
栈区和堆区
栈区:变量生命周期局限在函数内部,这种变量叫局部变量,局部变量占据的内存区域就是栈区。
堆区:变量生命周期受程序员控制,超越了函数内部,它们占据的内存区域叫堆区。
栈区性能更高,因为栈区内存的分配和释放非常简单,就是一个指针(寄存器)的移动而已,
而堆区不一样,堆区的内存受程序员自己控制,生命周期各不相同,因此必须精心标记,哪些可用,哪些不可用。此外,由于进程中的所有线程共享一个堆区,因此堆区内存分配器,必须处理好线程安全问题。因此堆区内存管理要比栈区复杂很多,整体上讲,堆区性能要比栈区差。
栈区堆区大小一样吗:栈区(Stack)大小固定,具体大小受操作系统或编译器的设置影响,典型值在 几 MB(比如 1MB ~ 8MB) 之间。因此不能创建占据内存空间很大的局部变量,否则会导致栈溢出(Stack Overflow)。堆区(Heap) 的大小不是固定的,可以动态增长,但仍然受 系统的虚拟内存管理 以及 物理内存大小 限制。堆区中的内存是 动态分配 的(如 malloc
、new
),需要程序员 手动释放(如 free
、delete
)。如果程序 未释放已分配的堆内存,或者失去了对该内存的引用,就会造成 内存泄露。(虽然堆区的大小可以动态增长,但它仍然受 系统的虚拟地址空间和物理内存 限制。如果物理内存不足,或者进程的地址空间已耗尽(例如 32 位系统的 4GB 限制),就无法再分配更多的堆空间。)
封装(Encapsulation)
Encapsulation(封装) 是面向对象编程(OOP)的重要特性之一,它指的是将 对象的状态(属性)和行为(方法)封装在类内部,并通过 访问控制修饰符(private、protected、public) 限制外部访问,从而提高数据的安全性和代码的可维护性。
JAVA封装:属性私有,get/set
封装的意义:
1 提高程序安全性,保护数据
2 隐藏代码的实现细节
3 统一接口
4 提高系统的可维护性
get/set
生成快捷键:alt + insert
继承
快捷键:ctrl + H显示树结构
在JAVA中所有的类都默认直接/间接继承Object类
JAVA中类只有单继承,没有多继承。(子类只能一个父类,父类可以有多个子类)
super()
super()
是 Java 中用于调用 父类的构造方法 的关键字。只能在子类的方法或者构造方法中出现!它必须是 构造方法的第一行,用于确保父类的初始化逻辑在子类执行之前完成。super()和this()不能同时调用构造方法。
class Child extends Parent {Child() {super(); // 调用父类的无参构造方法(可以省略)System.out.println("Child 构造方法被调用");}
}
即使不写 super();
,Java 也会默认调用父类的 无参构造方法。
- 除了用于构造方法,
super
还可以在子类中调用父类的 方法 或 属性:
class Child extends Parent {void show() {super.show(); // 调用父类的 show 方法System.out.println("Child 的 show 方法");}
}class Child extends Parent {int num = 20;void show() {System.out.println("子类 num:" + num);System.out.println("父类 num:" + super.num);}
}
重写
需要有继承关系,子类重写父类的方法
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大但不能缩小: public -> protected -> default -> private
- 抛出的异常:有范围,可以被缩小,但不能扩大
为什么要重写:
父类的功能,子类不一定需要,或不一定满足。
快捷键:alt + insert: override
多态
Child c1 = new Child();
Parent c2 = new Child(); // Person 是 Student 的父类,这里用 父类引用 s2 指向子类 Student 的对象。这就是多态!
- 为什么可以让父类引用指向子类?
在 Java 里,子类对象可以赋值给父类引用,因为子类“是”父类的一种(符合里氏替换原则 LSP)。
- 多态的使用?
当你使用 父类引用指向子类对象,调用方法时:
- 编译时看的是父类的类型(保证方法存在)。
- 运行时执行的是子类的重写方法(动态绑定)。
- 多态的意义?
多态的意义 | 优势 |
---|---|
提高扩展性 | 只需新增类,而不修改已有代码 |
提高复用性 | 让相同方法处理不同对象,减少代码重复 |
提高灵活性 | 适用于不同的对象,适应变化 |
提高可维护性 | 避免大量 if-else 语句,代码更清晰 |
- 多态是方法的多态
- 父类和子类,类型有联系,不然会报错:类型转换类型! ClassCastException!
- 存在条件:
- 继承关系
- 方法需要重写,子类重写父类
- 父类引用指向子类对象(Parent p = new Child();)
哪些方法不能重写(不能重写一定不具备多态性):
- static 方法,因为
static
方法属于类本身,而不是实例。 - final 常量
- private 方法,因为
private
方法对子类不可见
instanceof
instanceof
是 Java 的一个关键字,用于判断一个对象是否是某个类的实例,或者是否是某个父类/接口的子类。
对象 instanceof 类名 //如果 对象 是 类名 或其子类的实例,返回 true。
作用:在多态中,父类的引用可以指向子类的对象,但是不能直接调用子类的特有方法。如果想要调用子类特有的方法,需要先用 instanceof
进行类型检查,然后进行强制类型转换。
class Animal {void makeSound() {System.out.println("Some animal sound");}
}class Dog extends Animal {void bark() {System.out.println("Bark! Bark!");}
}public class TestPolymorphism {public static void main(String[] args) {Animal a = new Dog(); // 父类引用指向子类对象a.makeSound(); // 输出:Some animal sound// a.bark(); // ❌ 编译错误,父类引用无法调用子类方法if (a instanceof Dog) { // 先检查再转换Dog d = (Dog) a; d.bark(); // 输出:Bark! Bark!}}
}
- 如果
a
不是Dog
,直接强制转换Dog d = (Dog) a;
会抛出ClassCastException
。 instanceof
可以避免类型转换异常。
此外,还适用于接口判断(未学到接口):
如果一个类实现了某个接口,那么用 instanceof
也可以判断对象是否实现了某个接口。
总结
instanceof
主要用于 类型检查,常用于多态、强制转换和接口判断,可以避免 ClassCastException
!
static
public class Student {private static int age; //静态变量private double score; //非静态变量public void run(){}public static void go(){}public static void main(String[] args) {Student s1 = new Student();System.out.println(Student.age); //静态变量推荐使用类名来.变量,因为很明显这个变量就是静态变量//System.out.println(Student.score); 报错,非静态变量不能用类名来使用,必须new出对象才可System.out.println(s1.age);System.out.println(s1.score);//Student.run(); 静态方法不能去调用非静态方法//run();Student.go();go();}
}
为什么静态方法不能直接调用非静态方法?
静态方法是在类加载时就存在的,它不依赖于任何实例对象。
而非静态方法属于对象实例,需要通过实例化对象来访问。
如果在没有对象的情况下直接调用非静态方法,编译器无法确定要调用哪个对象的实例方法,因此会报错。
反过来:非静态方法可以调用静态方法
非静态方法可以直接调用静态方法,因为静态方法已经存在于类中,不依赖于实例。
abstract抽象类
abstract
用于定义抽象类,它是一种不能被直接实例化的类,通常作为父类,让子类继承并实现其方法。
public abstract class Action {//是个约束,有人帮我们实现//抽象方法,只有方法名字,没有具体实现//抽象类的子类,都必须要实现它的方法,除非它的子类也是abstractpublic void doSth(){}
}
特点:
- 不能new这个抽象类,只能靠子类去实现它:约束!
- 可以写普通方法。
- 抽象方法必须在抽象类中。
- 子类必须实现抽象方法,否则子类也必须声明为抽象类。
什么时候使用抽象类?
- 当多个类有共同的特性(变量/方法)时,可以用抽象类作为父类,减少代码重复。
- 如果某些方法在父类中没有具体实现(逻辑因子类不同),可以声明为抽象方法,让子类自行实现。
- 如果你不希望某个类被直接实例化(只能作为父类使用),可以将其定义为抽象类。