初始化
类的初始化
上面我们创建出来了一个 Car 这个对象,其实在使用 new 关键字创建一个对象的时候,其实是调用了这个对象无参数的构造方法进行的初始化,也就是如下这段代码
class Car{ public Car(){} }
这个无参数的构造函数可以隐藏,由 JVM 自动添加。也就是说,构造函数能够确保类的初始化。
成员初始化
Java 会尽量保证每个变量在使用前都会获得初始化,初始化涉及两种初始化。
- 一种是编译器默认指定的字段初始化,基本数据类型的初始化
- 一种是其他对象类型的初始化,String 也是一种对象,对象的初始值都为
null
,其中也包括基本类型的包装类。 - 一种是指定数值的初始化,例如
int a = 11
也就是说, 指定 a 的初始化值不是 0 ,而是 11。其他基本类型和对象类型也是一样的。
构造器初始化
可以利用构造器来对某些方法和某些动作进行初始化,确定初始值,例如
public class Counter{ int i; public Counter(){ i = 11; } }
利用构造函数,能够把 i 的值初始化为 11。
初始化顺序
首先先来看一下有哪些需要探讨的初始化顺序
- 静态属性:static 开头定义的属性
- 静态方法块:static {} 包起来的代码块
- 普通属性:非 static 定义的属性
- 普通方法块:{} 包起来的代码块
- 构造函数:类名相同的方法
- 方法:普通方法
public class LifeCycle { // 静态属性 private static String staticField = getStaticField(); // 静态方法块 static { System.out.println(staticField); System.out.println("静态方法块初始化"); } // 普通属性 private String field = getField(); // 普通方法块 { System.out.println(field); } // 构造函数 public LifeCycle() { System.out.println("构造函数初始化"); } public static String getStaticField() { String statiFiled = "Static Field Initial"; return statiFiled; } public static String getField() { String filed = "Field Initial"; return filed; } // 主函数 public static void main(String[] argc) { new LifeCycle(); } }
这段代码的执行结果就反应了它的初始化顺序
静态属性初始化 静态方法块初始化 普通属性初始化 普通方法块初始化 构造函数初始化
数组初始化
数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方括号下标操作符 []
来定义使用。
一般数组是这么定义的
int[] a1; //或者 int a1[];
两种格式的含义是一样的。
- 直接给每个元素赋值 : int array[4] = {1,2,3,4};
- 给一部分赋值,后面的都为 0 :int array[4] = {1,2};
- 由赋值参数个数决定数组的个数 :int array[] = {1,2};
可变参数列表
Java 中一种数组冷门的用法就是可变参数
,可变参数的定义如下
public int add(int... numbers){ int sum = 0; for(int num : numbers){ sum += num; } return sum; }
然后,你可以使用下面这几种方式进行可变参数的调用
add(); // 不传参数 add(1); // 传递一个参数 add(2,1); // 传递多个参数 add(new Integer[] {1, 3, 2}); // 传递数组
对象的销毁
虽然 Java 语言是基于 C++ 的,但是它和 C/C++ 一个重要的特征就是不需要手动管理对象的销毁工作。在著名的一书 《深入理解 Java 虚拟机》中提到一个观点
在 Java 中,我们不再需要手动管理对象的销毁,它是由 Java 虚拟机
进行管理和销毁的。虽然我们不需要手动管理对象,但是你需要知道 对象作用域
这个概念。
对象作用域
J多数语言都有作用域(scope)
这个概念。作用域决定了其内部定义的变量名的可见性和生命周期。在 C、C++ 和 Java 中,作用域通常由 {}
的位置来决定,例如
{ int a = 11; { int b = 12; } }
a 变量会在两个 {}
作用域内有效,而 b 变量的值只能在它自己的 {}
内有效。
虽然存在作用域,但是不允许这样写
{ int x = 11; { int x = 12; } }
这种写法在 C/C++ 中是可以的,但是在 Java 中不允许这样写,因为 Java 设计者认为这样写会导致程序混乱。
###this 和 super
this 和 super 都是 Java 中的关键字
this 表示的当前对象,this 可以调用方法、调用属性和指向对象本身。this 在 Java 中的使用一般有三种:指向当前对象
public class Apple { int i = 0; Apple eatApple(){ i++; return this; } public static void main(String[] args) { Apple apple = new Apple(); apple.eatApple().eatApple(); } }
这段代码比较精妙,精妙在哪呢,我一个 eatApple() 方法竟然可以调用多次,你在后面还可以继续调用,这就很神奇了,为啥呢?其实就是 this 在作祟了,我在 eatApple
方法中加了一个 return this
的返回值,也就是说哪个对象调用 eatApple 方法都能返回对象的自身。
this 还可以修饰属性,最常见的就是在构造方法中使用 this ,如下所示
public class Apple { private int num; public Apple(int num){ this.num = num; } public static void main(String[] args) { new Apple(10); } }
main 方法中传递了一个 int 值为 10 的参数,它表示的就是苹果的数量,并把这个数量赋给了 num 全局变量。所以 num 的值现在就是 10。
this 还可以和构造函数一起使用,充当一个全局关键字的效果
public class Apple { private int num; private String color; public Apple(int num){ this(num,"红色"); } public Apple(String color){ this(1,color); } public Apple(int num, String color) { this.num = num; this.color = color; } }
你会发现上面这段代码使用的不是 this, 而是 this(参数)
。它相当于调用了其他构造方法,然后传递参数进去。这里注意一点:this() 必须放在构造方法的第一行,否则编译不通过
如果你把 this 理解为指向自身的一个引用,那么 super 就是指向父类的一个引用。super 关键字和 this 一样,你可以使用 super.对象
来引用父类的成员,如下
public class Fruit { int num; String color; public void eat(){ System.out.println("eat Fruit"); } } public class Apple extends Fruit{ @Override public void eat() { super.num = 10; System.out.println("eat " + num + " Apple"); } }
你也可以使用 super(参数)
来调用父类的构造函数,这里不再举例子了。
下面为你汇总了 this 关键字和 super 关键字的比较。
访问控制权限
访问控制权限又称为封装
,它是面向对象三大特性中的一种,我之前在学习过程中经常会忽略封装,心想这不就是一个访问修饰符么,怎么就是三大特性的必要条件了?后来我才知道,如果你信任的下属对你隐瞒 bug,你是根本不知道的。
访问控制权限其实最核心就是一点:只对需要的类可见。
Java中成员的访问权限共有四种,分别是 public、protected、default、private,它们的可见性如下
继承
继承是所有 OOP(Object Oriented Programming)
语言和 Java 语言都不可或缺的一部分。只要我们创建了一个类,就隐式的继承自 Object
父类,只不过没有指定。如果你显示指定了父类,那么你继承于父类,而你的父类继承于 Object 类。
继承的关键字是 extends
,如上图所示,如果使用了 extends 显示指定了继承,那么我们可以说 Father 是父类,而 Son 是子类,用代码表示如下
class Father{} class Son extends Father{}
继承双方拥有某种共性的特征
class Father{ public void feature(){ System.out.println("父亲的特征"); } } class Son extends Father { }
如果 Son 没有实现自己的方法的话,那么默认就是用的是父类的 feature
方法。如果子类实现了自己的 feature 方法,那么就相当于是重写了父类的 feature 方法,这也是我们上面提到的重写了。
多态
多态指的是同一个行为具有多个不同表现形式。是指一个类实例(对象)的相同方法在不同情形下具有不同表现形式。封装和继承是多态的基础,也就是说,多态只是一种表现形式而已。
如何实现多态?多态的实现具有三种充要条件
- 继承
- 重写父类方法
- 父类引用指向子类对象
比如下面这段代码
public class Fruit { int num; public void eat(){ System.out.println("eat Fruit"); } } public class Apple extends Fruit{ @Override public void eat() { super.num = 10; System.out.println("eat " + num + " Apple"); } public static void main(String[] args) { Fruit fruit = new Apple(); fruit.eat(); } }
你可以发现 main
方法中有一个很神奇的地方,Fruit fruit = new Apple()
,Fruit 类型的对象竟然指向了 Apple 对象的引用,这其实就是多态 -> 父类引用指向子类对象,因为 Apple 继承于 Fruit,并且重写了 eat 方法,所以能够表现出来多种状态的形式。
组合
组合其实不难理解,就是将对象引用置于新类中即可。组合也是一种提高类的复用性的一种方式。如果你想让类具有更多的扩展功能,你需要记住一句话多用组合,少用继承。
public class SoccerPlayer { private String name; private Soccer soccer; } public class Soccer { private String soccerName; }
代码中 SoccerPlayer 引用了 Soccer 类,通过引用 Soccer 类,来达到调用 soccer 中的属性和方法。
组合和继承是有区别的,它们的主要区别如下。
关于继承和组合孰优孰劣的争论没有结果,只要发挥各自的长处和优点即可,一般情况下,组合和继承也是一对可以连用的好兄弟。
代理
除了继承和组合外,另外一种值得探讨的关系模型称为 代理
。代理的大致描述是,A 想要调用 B 类的方法,A 不直接调用,A 会在自己的类中创建一个 B 对象的代理,再由代理调用 B 的方法。例如如下代码
public class Destination { public void todo(){ System.out.println("control..."); } } public class Device { private String name; private Destination destination; private DeviceController deviceController; public void control(Destination destination){ destination.todo(); } } public class DeviceController { private Device name; private Destination destination; public void control(Destination destination){ destination.todo(); } }
向上转型
向上转型代表了父类与子类之间的关系,其实父类和子类之间不仅仅有向上转型,还有向下转型,它们的转型后的范围不一样
向上转型
:通过子类对象(小范围)转化为父类对象(大范围),这种转换是自动完成的,不用强制。向下转型
: 通过父类对象(大范围)实例化子类对象(小范围),这种转换不是自动完成的,需要强制指定。
static
static 是 Java 中的关键字,它的意思是 静态的
,static 可以用来修饰成员变量和方法,static 用在没有创建对象的情况下调用 方法/变量。
- 用 static 声明的成员变量为静态成员变量,也成为类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。
static String name = "cxuan";
- 使用 static 修饰的方法称为静态方法,静态方法能够直接使用类名.方法名 进行调用。由于静态方法不依赖于任何对象就可以直接访问,因此对于静态方法来说,是没有 this 关键字的,实例变量都会有 this 关键字。在静态方法中不能访问类的非静态成员变量和非静态方法,
static void printMessage(){ System.out.println("cxuan is writing the article"); }
static 除了修饰属性和方法外,还有静态代码块
的功能,可用于类的初始化操作。进而提升程序的性能。
public class StaicBlock { static{ System.out.println("I'm A static code block"); } }
由于静态代码块随着类的加载而执行,因此,很多时候会将只需要进行一次的初始化操作放在 static 代码块中进行。
final
final 的意思是最后的、最终的,它可以修饰类、属性和方法。
- final 修饰类时,表明这个类不能被继承。final 类中的成员变量可以根据需要设为 final,但是要注意 final 类中的所有成员方法都会被隐式地指定为 final 方法。
- final 修饰方法时,表明这个方法不能被任何子类重写,因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为 final。
- final 修饰变量分为两种情况,一种是修饰基本数据类型,表示数据类型的值不能被修改;一种是修饰引用类型,表示对其初始化之后便不能再让其指向另一个对象。