面向对象系统更加符合人对客观世界的抽象感知,作为面向对象语言的佼佼者,本篇是关于 Java 的面向对象系统。

1. 什么是对象

  • 对象就是数据和行为的集合(主观能动性)
  • 一切用 new 运算符创建出来的都是对象
  • new Object()
  • 特例:new Integer() / new String(),原生类型有其对应的包装类型,可以创建相应类型的对象
  • 特例:new Object[]

2. 对象是由什么组成的

  • 所有的对象都在堆上分配
  • 每个对象 都包含自己的数据(成员变量)
    • 原生类型的成员
    • 引用类型的成员

3. 对象的构造函数

  • 新建对象的唯一途径
    • 在堆上分配空间
    • 执行必要的初始化工作
    • 执行构造器函数
  • 如果没有任何 构造器,编译器会自动生成一个空的构造器
  • 可以用调试器来研究 new 时构造函数调用的全过程

4. 对象的方法

  • 对象的本质是消息传递
  • 数据是“有什么”
  • 方法是“做什么”

5. 方法的重载(overload)

定义:同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可(参数个数或参数类型不同),另外子类也可以对父类的方法进行重载。

信雅达的方法名是可以重复使用的,通过可接收的参数不同来加以区分/匹配。

存在隐式转换时,根据类型优先匹配最接近的,如果存在 null (Java世界中唯一没有类型的值),则会产生歧义,需要手动把 null 强制转为某个类型。

注意:
判断是否重载:
跟方法的权限修饰符、返回值类型、形参变量名和方法体都没有关系!

看一个具有方法重载时的调用优先级:

  1. public class Cat {
  2. public static void main(String[] args) {
  3. Cat cat = new Cat();
  4. cat.foo(1);
  5. }
  6. void foo(int i) {
  7. }
  8. void foo(Integer i) {
  9. }
  10. void foo(Number i) {
  11. }
  12. void foo(Object i) {
  13. }
  14. }

再看下面的例子,此时 null 可能是 Integer 类型也可能是 Object[],两种类型并 没有直接的父子关系:

  1. public class Cat {
  2. public static void main(String[] args) {
  3. Cat cat = new Cat();
  4. cat.foo(null);
  5. }
  6. void foo(int i) {
  7. }
  8. void foo(Integer i) {
  9. }
  10. void foo(Number i) {
  11. }
  12. void foo(Object i) {
  13. }
  14. void foo(Object[] i) {
  15. }
  16. }

image.png
此时产生了歧义,需要强制指定 null 的类型:

  1. // ...
  2. public static void main(String[] args) {
  3. Cat cat = new Cat();
  4. cat.foo((Integer) null);
  5. }
  6. // ...
  • 能否仅仅重载返回值吗(即方法名和参数相同时)?JVM 中是合法的,但IDEA会飘红,这个作为了解,以后再某些场景下会利用到这个特性,但 目前来说,听IDEA的不要这样做。
  • 如何为方法提供默认值?
    JavaScript 在 ES6 规范出来之前也不支持默认值,但可以曲线救国:
  1. function log(x, y) {
  2. y = y || 'World';
  3. console.log(x, y);
  4. }
  5. log('Hello') // Hello World
  6. log('Hello', 'China') // Hello China

ES6 出来之后,原生支持函数的默认值:

  1. function log(x, y = 'World') {
  2. y = y || 'World';
  3. console.log(x, y);
  4. }
  5. console.log('Hello') // Hello World
  6. console.log('Hello', 'China') // Hello China

再回到 Java,目前是不能像 JavaScript 这样直接支持默认值的(或许也不需要这样),但可以通过重载依然曲线救国:

public class  Cat {
    public static void main(String[] args) {
        Cat cat =  new Cat();
        cat.meow(); // 默认参数是 "咪咪咪"
        cat.meow("喵喵喵");
    }

    public void meow() {
        meow("咪咪咪"); // 注意!这里传入的参数,就作为当前所在的没有参数的方法的默认参数
    }
    public void meow(String value) {
        System.out.println(value);
    }
}

6. 构造器的重载

  • this()
  • 例如 HashMap 的构造器重载

构造器本质上也是一个方法,因此也能重载,并且也能曲线救国实现方法参数的默认值:

public class  Cat {
    int age;
    String name;

    public static void main(String[] args) {
        new Cat();
    }
    // 创建一只默认的猫,1岁,名叫小黑
    Cat() {
        this("小黑");
    }
    // 创建一只默认的猫,1岁,名叫name指定的名字
    Cat(String name2) {
        this(1, name2);
    }
    // 创建age和name指定的猫
    Cat(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

7. 对象的初始化

前面说了创建一个对象时:

  • 在堆上分配空间
  • 执行必要的初始化工作
  • 执行构造器函数

具体初始化是有顺序的:

  • 静态成员的初始化
  • 静态初始化块 static {}
  • 实例成员的初始化
  • 实例初始化块 {}

在以下代码中使用断点调试就可验证以上初始化流程:

public class Cat {
    static int count;  // 只在第一次new时初始化一次

    static {  // 只在第一次new时初始化一次
        for (int i = 0; i < 3; i++) {
            count += i;
        }
    }

    int age;  // 每次new一个对象实例时都会初始化
    String name;  // 每次new一个对象实例时都会初始化

    {
        for (int i = 0; i < 3; i++) {
            age += i;
        }
    }

    public Cat(int age, String name) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
        new Cat(2, "wangpeng");
        new Cat(5, "xiaoqian");
    }
}

8. 对象的生命周期

  • 用不到的垃圾会被 GC(Garbage Collection) 回收,但具体什么时候回收要看情况,如果有被引用,那么不会被回收,如果内存条很大,也可能不会被回收。
  • GC 进行垃圾回收时也不一定就是释放内存空间,也可能是进行碎片化整理,进行压缩。
  • GC 如果判断某个对象是否被引用(使用)?
    答案是通过引用链(GC Roots)。
    展开来说,就是按是否存在于引用链中来判断是否是垃圾(可达性)。JVM 中预定义了 GC Roots,沿着 GC Roots 可达的数据都不是垃圾,除此之外都是垃圾。方法栈中的局部变量就是最重要的 GC Roots 之一,这些变量所能引用到的所有东西都不是垃圾。

看一个例子,占用尽可能多的内存,令JVM抛出内存不足 OutOfMemoryError 的异常:

public class Main {
    public static void main(String[] args) {
        Object[] array = new Object[10000];
        for (int i = 0; i < 10000; i++) {
            array[i] =  new byte[1024 * 1024];
        }
    }
}

9. 数组—特殊的对象

  • JVM 为数组提供了特殊的处理方法
  • 数组只有两个操作:[] 与 length
  • 数组的长度不可变 ```java // 创建方式1 dataType[] arrayRefVar = new dataType[arraySize];

// 创建方式2 dataType[] arrayRefVar = {value0, value1, …, valuek};

// 例子 double[] myList = new double[10]; Object[] array = new Object[20]; int[] array2 = {10, 5, 30};

```