初级06 - Java对象系统基础
面向对象系统更加符合人对客观世界的抽象感知,作为面向对象语言的佼佼者,本篇是关于 Java 的面向对象系统。
- 构造函数
- 默认构造函数
- 方法重载(overloading)
- 初始值与null
- this关键字
- static关键字
- 初始化
- 方法的可变参数
- 枚举
- 内部类/静态内部类/匿名内部类
1. 什么是对象
- 对象就是数据和行为的集合(主观能动性)
- 一切用 new 运算符创建出来的都是对象
- new Object()
- 特例:new Integer() / new String(),原生类型有其对应的包装类型,可以创建相应类型的对象
- 特例:new Object[]
2. 对象是由什么组成的?
- 所有的对象都在堆上分配
- 每个对象 都包含自己的数据(成员变量)
- 原生类型的成员
- 引用类型的成员
3. 对象的构造函数
- 新建对象的唯一途径
- 在堆上分配空间
- 执行必要的初始化工作
- 执行构造器函数
- 如果没有任何 构造器,编译器会自动生成一个空的构造器
- 可以用调试器来研究 new 时构造函数调用的全过程
4. 对象的方法
- 对象的本质是消息传递
- 数据是“有什么”
- 方法是“做什么”
5. 方法的重载(overload)
定义:同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可(参数个数或参数类型不同),另外子类也可以对父类的方法进行重载。
信雅达的方法名是可以重复使用的,通过可接收的参数不同来加以区分/匹配。
存在隐式转换时,根据类型优先匹配最接近的,如果存在 null (Java世界中唯一没有类型的值),则会产生歧义,需要手动把 null 强制转为某个类型。
注意:
判断是否重载:
跟方法的权限修饰符、返回值类型、形参变量名和方法体都没有关系!
看一个具有方法重载时的调用优先级:
public class Cat {
public static void main(String[] args) {
Cat cat = new Cat();
cat.foo(1);
}
void foo(int i) {
}
void foo(Integer i) {
}
void foo(Number i) {
}
void foo(Object i) {
}
}
再看下面的例子,此时 null 可能是 Integer 类型也可能是 Object[],两种类型并 没有直接的父子关系:
public class Cat {
public static void main(String[] args) {
Cat cat = new Cat();
cat.foo(null);
}
void foo(int i) {
}
void foo(Integer i) {
}
void foo(Number i) {
}
void foo(Object i) {
}
void foo(Object[] i) {
}
}
此时产生了歧义,需要强制指定 null 的类型:
// ...
public static void main(String[] args) {
Cat cat = new Cat();
cat.foo((Integer) null);
}
// ...
- 能否仅仅重载返回值吗(即方法名和参数相同时)?JVM 中是合法的,但IDEA会飘红,这个作为了解,以后再某些场景下会利用到这个特性,但 目前来说,听IDEA的不要这样做。
- 如何为方法提供默认值?
JavaScript 在 ES6 规范出来之前也不支持默认值,但可以曲线救国:
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
ES6 出来之后,原生支持函数的默认值:
function log(x, y = 'World') {
y = y || 'World';
console.log(x, y);
}
console.log('Hello') // Hello World
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];
}
}
}
. 数组—特殊的对象
- JVM 为数组提供了特殊的处理方法
- 数组只有两个操作:[] 与 length
- 数组的长度不可变