JAVA面向对象
面向对象编程的核心思想是将现实世界的事物抽象为对象,并通过对象之间的交互来完成任务。每个对象都有自己的状态(属性)和行为(方法)。
1. 封装
将对象的属性包裹起来,并通过接口控制访问。高内聚低耦合,并隐藏实现细节,安全性高。
1.1 封装的优点
- 提高代码安全性
封装通过隐藏类的内部实现细节,只暴露必要的接口供外部使用,从而防止了外部代码对类内部数据的直接访问和修改。这种保护机制减少了数据被非法篡改的风险,提高了代码的安全性。 - 增强代码可维护性
封装使得类的内部实现细节与外部接口相分离,降低了模块间的耦合度。当类的内部实现发生变化时,只要外部接口保持不变,就不会影响到使用该类的外部代码。这种灵活性使得代码更加易于维护和修改。 - 促进代码重用
封装允许类被多次重用,而不需要每次都重新编写相同的代码。通过继承和多态等面向对象编程的特性,可以创建出具有相似功能但细节不同的新类,从而实现代码的重用和扩展。 - 简化系统设计
封装将复杂的内部实现细节隐藏起来,只暴露简单的外部接口,使得系统设计更加简洁和清晰。这有助于开发者更好地理解系统的结构和功能,降低了系统的复杂性。 - 提高开发效率
封装使得开发者可以更加专注于类的外部接口和功能的实现,而不必担心内部实现的细节。这种分工合作的方式提高了开发效率,使得开发者能够更快地开发出高质量的代码。 - 增强系统稳定性封装通过隐藏内部实现细节,减少了外部代码对类内部数据的直接依赖。这种依赖关系的减少使得系统在面对变化时更加稳定,降低了系统崩溃或出错的风险。
2. 继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承描述的是“is-a”关系。Cat是Animal的子类,则 Animal animal = new Cat(); //Cat是Animal的向上转型 Cat c = (Cat)a;//向下转型
2.1 继承的特性
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承
A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++
继承的一个特性。 - 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
2.2 继承的类型
java不支持多继承,但支持多重继承。
2.3 继承关键字
通过extends和implements关键字实现类和接口的继承,所有类都继承自java.lang.Object。
- extends
public class Animal { private String name; public Animal(String myName) { this.name = myName; } public void eat() { //吃东西方法的具体实现 } } public class Cat extends Animal{ }
- implements
类继承接口,变相使java具有多继承特性。public interface A { public void eat(); } public interface B { public void vote(); } public class C implements A,B { }
3. 多态
同一事件发生在不同对象上会产生不同结果。LOL中不同英雄按下Q技能行为一样但结果不一样。
3.1 优点
- 可替换性
允许子类对象替换父类对象,符合开闭原则。可以灵活地将不同子类对象传入接受父类类型参数的方法中,如在处理动物叫声的例子中,能方便地用Dog
或Cat
对象替换Animal
对象,使代码更灵活,且易于添加新的子类。 - 可扩展性
在不修改原有代码的基础上,通过创建新的子类来扩展功能。像图形绘制系统中添加新图形,只需新建子类并实现相应方法,原有绘制代码不用改变。 - 灵活性
根据运行时对象的实际类型调用相应方法,在处理复杂业务逻辑(如游戏角色攻击)时,避免大量if - else
判断,降低代码复杂度,提高可读性和可维护性。 - 代码的简洁性
避免编写大量重复代码。以动物叫声为例,有了多态只需一个方法就能处理不同动物叫声,减少代码量和出错概率。
3.2 多态存在的三个必要条件
继承、重写、父类引用指向子类对象 Parent p = new Child();
当父类引用调用子类重写的父类方法时,多态就出现了。
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
3.3 多态的实现方式
3.3.1 重写
子类实现覆盖父类方法
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void animalSound(Animal animal) {
animal.makeSound();
}
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
animalSound(dog);
animalSound(cat);
}
}
3.3.2 接口
public interface Shape {
double calculateArea();
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
public class Rectangle implements Shape {
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
}
public static void main(String[] args) {
Circle circle = new Circle(5.0);
Rectangle rectangle = new Rectangle(4.0, 6.0);
printArea(circle);
printArea(rectangle);
}
3.3.3 抽象类
与重写类似,Animal父类方法声明为抽象类即可
public abstract class Animal {
public abstract void makeSound();
}
3.4 概念补充
3.4.1 接口
3.4.1.1 接口的特性
- 接口中方法是隐式抽象的,会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,且会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中可以有用 default 修饰的默认方法。
3.4.1.2 接口与类的区别
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
3.4.2 内部类
为了方便使用外部类相关的属性和方法,有时会在开发中定义一个内部类。
内部类是在一个类内部定义的另一个类。Java 支持四种类型的内部类:成员内部类、局部内部类、匿名内部类和静态内部类。
内部类可以直接访问外部类的私有属性,但外部类不能访问内部类的属性。内部类被当成外部类的成员。
https://zhuanlan.zhihu.com/p/97034966
使用内部类可能会造成内存泄漏。
非静态内部类会持有外部类的一个隐式引用。这意味着只要非静态内部类的对象存在,它所引用的外部类对象就不能被垃圾回收器回收。
public class OuterClass {
private int value;
public OuterClass(int value) {
this.value = value;
}
public class InnerClass {
public void printValue() {
System.out.println(value);
}
}
public static void main(String[] args) {
OuterClass outer = new OuterClass(10);
OuterClass.InnerClass inner = outer.new InnerClass();
// 在这里,如果没有正确地管理 inner 对象和 outer 对象的生命周期,可能会导致内存泄漏。
// 即使 outer 对象不再被需要,但只要 inner 对象存在,outer 对象就不能被回收。
}
}
为什么不能回收?
非静态内部类(也称为成员内部类、局部内部类和匿名内部类)会持有一个外部类对象的隐式引用。
当一个非静态内部类的对象存在时,它所持有的外部类对象的引用会阻止该外部类对象被垃圾回收器回收。因为只要还有对某个对象的引用存在,垃圾回收器就不会回收这个对象。
3.5 经典例子分析
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{ }
public class D extends B{ }
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
//输出
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
规则一:继承链中对象方法调用优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
规则二:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
超类对象引用变量引用子类对象:
SuperClass obj = new SubClass();
obj就是一个超类对象引用变量,它实际上引用了一个SubClass子类对象。
输出1、2、3可直接套用规则一。
输出4:a2是一个引用变量,类型为A。根据规则一,会定位到类A里面show(A obj)方法。由于a2引用的是类B的一个对象,B覆盖了A的show(A obj)方法,因此最终锁定到类B的show(A obj),输出为"B and A”。
输出5:分析同输出4。
其余输出不详细分析。