“不安全” 的编程是造成编程代价昂贵的罪魁祸首之一。有两个安全性问题:初始化和清理。C 语言中很多的 bug 都是因为程序员忘记初始化导致的。尤其是很多类库的使用者不知道如何初始化类库组件,甚至他们必须得去初始化。清理则是另一个特殊的问题,因为当你使用一个元素做完事后就不会去关心这个元素,所以你很容易忘记清理它。这样就造成了元素使用的资源滞留不会被回收,直到程序消耗完所有的资源(特别是内存)。
C++ 引入了构造器的概念,这是一个特殊的方法,每创建一个对象,这个方法就会被自动调用。Java 采用了构造器的概念,另外还使用了垃圾收集器(Garbage Collector, GC)去自动回收不再被使用的对象所占的资源。
利用构造器保证初始化
你可能想为每个类创建一个 initialize()
方法,该方法名暗示着在使用类之前需要先调用它。不幸的是,用户必须得记得去调用它。在 Java 中,类的设计者通过构造器保证每个对象的初始化。如果一个类有构造器,那么 Java 会在用户使用对象之前(即对象刚创建完成)自动调用对象的构造器方法,从而保证初始化。
public class Main {
static class Rock {
// 无参构造器
Rock() {
System.out.print("Rock ");
}
// 有参数构造器
//Rock2(int i) {
// System.out.print("Rock " + i + " ");
//}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Rock();
}
}
}
// out: Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock
public class Main {
static class Rock {
// 无参构造器
Rock() {
System.out.print("Rock ");
}
// 有参数构造器
//Rock2(int i) {
// System.out.print("Rock " + i + " ");
//}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Rock();
}
}
}
// out: Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock
- 构造器方法名与类名相同,不需要符合首字母小写的编程风格
- 是特殊的方法,无返回值
- 如果某方法是唯一的构造器,那么编译器不允许你以其他任何方式创建对象
- 如果你创建一个类,类中没有构造器,那么编译器就会自动为你创建一个无参构造器
方法重载
- 构造器重载
- 被重载的方法必须有独立无二的参数列表
this关键字
this 关键字只用在一些必须显式使用当前对象引用的特殊场合。例如,用在 return 语句中返回对当前对象的引用。
javapublic class Leaf { int i = 0; Leaf increment() { i++; return this; } void print() { System.out.println("i = " + i); } public static void main(String[] args) { Leaf x = new Leaf(); x.increment().increment().increment().print(); } }
public class Leaf { int i = 0; Leaf increment() { i++; return this; } void print() { System.out.println("i = " + i); } public static void main(String[] args) { Leaf x = new Leaf(); x.increment().increment().increment().print(); } }
this 关键字在向其他方法传递当前对象时也很有用:
javaclass Person { public void eat(Apple apple) { Apple peeled = apple.getPeeled(); System.out.println("Yummy"); } } public class Peeler { static Apple peel(Apple apple) { return apple; } } public class Apple { Apple getPeeled() { return Peeler.peel(this); } } public class PassingThis { public static void main(String[] args) { new Person().eat(new Apple()); } }
class Person { public void eat(Apple apple) { Apple peeled = apple.getPeeled(); System.out.println("Yummy"); } } public class Peeler { static Apple peel(Apple apple) { return apple; } } public class Apple { Apple getPeeled() { return Peeler.peel(this); } } public class PassingThis { public static void main(String[] args) { new Person().eat(new Apple()); } }
在构造器中调用构造器: 当你在一个类中写了多个构造器,有时你想在一个构造器中调用另一个构造器来避免代码重复。你通过 this 关键字实现这样的调用。
javapublic class Flower { int petalCount = 0; String s = "initial value"; Flower(int petals) { petalCount = petals; System.out.println("Constructor w/ int arg only, petalCount = " + petalCount); } Flower(String ss) { System.out.println("Constructor w/ string arg only, s = " + ss); s = ss; } Flower(String s, int petals) { this(petals); // this避免混淆 this.s = s; System.out.println("String & int args"); } Flower() { this("hi", 47); System.out.println("no-arg constructor"); } void printPetalCount() { System.out.println("petalCount = " + petalCount + " s = " + s); } public static void main(String[] args) { Flower x = new Flower(); x.printPetalCount(); } }
public class Flower { int petalCount = 0; String s = "initial value"; Flower(int petals) { petalCount = petals; System.out.println("Constructor w/ int arg only, petalCount = " + petalCount); } Flower(String ss) { System.out.println("Constructor w/ string arg only, s = " + ss); s = ss; } Flower(String s, int petals) { this(petals); // this避免混淆 this.s = s; System.out.println("String & int args"); } Flower() { this("hi", 47); System.out.println("no-arg constructor"); } void printPetalCount() { System.out.println("petalCount = " + petalCount + " s = " + s); } public static void main(String[] args) { Flower x = new Flower(); x.printPetalCount(); } }
this && static
记住了 this 关键字的内容,你会对 static 修饰的方法有更加深入的理解:
==static 方法中不会存在 this。你不能在静态方法中调用非静态方法(反之可以)。
静态方法是为类而创建的,不需要任何对象。事实上,这就是静态方法的主要目的,静态方法看起来就像全局方法一样,但是 Java 中不允许全局方法,一个类中的静态方法可以被其他的静态方法和静态属性访问。
一些人认为静态方法不是面向对象的,因为它们的确具有全局方法的语义。使用静态方法,因为不存在 this,所以你没有向一个对象发送消息。的确,如果你发现代码中出现了大量的 static 方法,就该重新考虑自己的设计了。然而,static 的概念很实用,许多时候都要用到它。至于它是否真的” 面向对象”,就留给理论家去讨论吧。
// 你不能在静态方法中调用非静态方法(
public class Main {
static void staticFunc() {
this.axx(); // 编译错误
}
void axx() {
staticFunc(); // work
}
}
// 你不能在静态方法中调用非静态方法(
public class Main {
static void staticFunc() {
this.axx(); // 编译错误
}
void axx() {
staticFunc(); // work
}
}
垃圾回收器
Java 中有垃圾回收器回收无用对象占用的内存。
但现在考虑一种特殊情况:你创建的对象不是通过 new 来分配内存的,而垃圾回收器只知道如何释放用 new 创建的对象的内存,所以它不知道如何回收不是 new 分配的内存。为了处理这种情况,Java 允许在类中定义一个名为 finalize()
的方法。
finalize()
是一个潜在的编程陷阱,因为一些程序员(尤其是 C++ 程序员)会一开始把它误认为是 C++ 中的析构函数(C++ 在销毁对象时会调用这个函数)。所以有必要明确区分一下:在 C++ 中,对象总是被销毁的(在一个 bug-free 的程序中),而在 Java 中,对象并非总是被垃圾回收,或者换句话说:- 1 对象可能不被垃圾回收。
- 2 垃圾回收不等同于析构。
- 垃圾回收只与内存有关。
这意味着在你不再需要某个对象之前,如果必须执行某些动作,你得自己去做。Java 没有析构器或类似的概念,所以你必须得自己创建一个普通的方法完成这项清理工作。例如,对象在创建的过程中会将自己绘制到屏幕上。如果不是明确地从屏幕上将其擦除,它可能永远得不到清理。如果在 finalize()
方法中加入某种擦除功能,那么当垃圾回收发生时,finalize()
方法被调用(不保证一定会发生),图像就会被擦除,要是” 垃圾回收” 没有发生,图像则仍会保留下来。
也许你会发现,只要程序没有濒临内存用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,而垃圾回收器一直没有释放你创建的任何对象的内存,则当程序退出时,那些资源会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。
finalize()
的用途
如果你不能将 finalize()
作为通用的清理方法,那么这个方法有什么用呢?
这引入了要记住的第 3 点:
垃圾回收只与内存有关。
也就是说,使用垃圾回收的唯一原因就是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是 finalize()
方法),它们也必须同内存及其回收有关。
当对某个对象不感兴趣时——也就是它将被清理了,这个对象应该处于某种状态,这种状态下它占用的内存可以被安全地释放掉。例如,如果对象代表了一个打开的文件,在对象被垃圾回收之前程序员应该关闭这个文件。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的 bug。finalize()
可以用来最终发现这个情况,尽管它并不总是被调用。如果某次 finalize()
的动作使得 bug 被发现,那么就可以据此找出问题所在——这才是人们真正关心的。以下是个简单的例子,示范了 finalize()
的可能使用方式:
import onjava.*;
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
@Override
protected void finalize() throws Throwable {
if (checkedOut) {
System.out.println("Error: checked out");
}
}
}
public class TerminationCondition {
public static void main(String[] args) {
Book novel = new Book(true);
novel.checkIn();
new Book(true);
// 用于强制进行终结动作
System.gc();
new Nap(1);
}
}
import onjava.*;
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
@Override
protected void finalize() throws Throwable {
if (checkedOut) {
System.out.println("Error: checked out");
}
}
}
public class TerminationCondition {
public static void main(String[] args) {
Book novel = new Book(true);
novel.checkIn();
new Book(true);
// 用于强制进行终结动作
System.gc();
new Nap(1);
}
}
https://juejin.cn/post/7033382936112496653
finalize() 方法是在对象被回收之前调用的方法。但是 finalize() 方法的调用具有不确定性。
当进行完可达性分析之后,某个对象被标记为不可达时,会判断当前对象是否重写了 finalize() 方法,是否已经执行过对象的 finalize() 方法,如果没有覆盖,或者已经执行过,则不再执行,对象将被回收。
如果有必要执行 finalize() 方法,则会将这个对象放到一个 F-Queue 的低优先级队列里等待执行。之后 GC 将对 F-Queue 中的对象再次进行可达性分析。
所以,如果对象可以在 finalize() 方法中再次复活,即将自己的 this 指针,重新赋值到某个可达的对象的引用上。
垃圾回收器如何工作
如果你以前用过的语言,在堆上分配对象的代价十分高昂,你可能自然会觉得 Java 中所有对象(基本类型除外)在堆上分配的方式也十分高昂。然而,垃圾回收器能很明显地提高对象的创建速度。这听起来很奇怪——存储空间的释放影响了存储空间的分配,但这确实是某些 Java 虚拟机的工作方式。这也意味着,Java 从堆空间分配的速度可以和其他语言在栈上分配空间的速度相媲美。
- 其他系统中的垃圾回收机制
- 引用计数: 存在循环引用问题
- 可达性分析算法
- 通过一系列称之为“GC Roots”的对象作为起始节点,通过这些节点搜索所有可达的节点。当发现某些对象不可达,即说明此对象不可用。
- GC Roots 的对象
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
在更快的策略中,垃圾回收器并非基于引用计数。它们依据的是:对于任意” 活” 的对象,一定能最终追溯到其存活在栈或静态存储区中的引用。这个引用链条可能会穿过数个对象层次,由此,如果从栈或静态存储区出发,遍历所有的引用,你将会发现所有” 活” 的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是该对象包含的所有引用,如此反复进行,直到访问完” 根源于栈或静态存储区的引用” 所形成的整个网络。你所访问过的对象一定是” 活” 的。注意,这解决了对象间循环引用的问题,这些对象不会被发现,因此也就被自动回收了。
在这种方式下,Java 虚拟机采用了一种 自适应 的垃圾回收技术。至于如何处理找到的存活对象,取决于不同的 Java 虚拟机实现。其中有一种做法叫做停止 - 复制(stop-and-copy)。顾名思义,这需要先暂停程序的运行(不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有复制的就是需要被垃圾回收的。另外,当对象被复制到新堆时,它们是一个挨着一个紧凑排列,然后就可以按照前面描述的那样简单、直接地分配新空间了。
当对象从一处复制到另一处,所有指向它的引用都必须修正。位于栈或静态存储区的引用可以直接被修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到(可以想象成一个表格,将旧地址映射到新地址)。
这种所谓的” 复制回收器” 效率低下主要因为两个原因。
- 其一:得有两个堆,然后在这两个分离的堆之间来回折腾,得维护比实际需要多一倍的空间。某些 Java 虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。
- 其二在于复制本身。一旦程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾。尽管如此,复制回收器仍然会将所有内存从一处复制到另一处,这很浪费。为了避免这种状况,一些 Java 虚拟机会进行检查:要是没有新垃圾产生,就会转换到另一种模式(即” 自适应”)。这种模式称为标记 - 清扫(mark-and-sweep),Sun 公司早期版本的 Java 虚拟机一直使用这种技术。对一般用途而言,” 标记 - 清扫” 方式速度相当慢,但是当你知道程序只会产生少量垃圾甚至不产生垃圾时,它的速度就很快了。
- “标记 - 清扫” 所依据的思路仍然是从栈和静态存储区出发,遍历所有的引用,找出所有存活的对象。但是,每当找到一个存活对象,就给对象设一个标记,并不回收它。只有当标记过程完成后,清理动作才开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。” 标记 - 清扫” 后剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就需要重新整理剩下的对象。
- “停止 - 复制” 指的是这种垃圾回收动作不是在后台进行的;相反,垃圾回收动作发生的同时,程序将会暂停。在 Oracle 公司的文档中会发现,许多参考文献将垃圾回收视为低优先级的后台进程,但是早期版本的 Java 虚拟机并不是这么实现垃圾回收器的。当可用内存较低时,垃圾回收器会暂停程序。同样,” 标记 - 清扫” 工作也必须在程序暂停的情况下才能进行。
如前文所述,这里讨论的 Java 虚拟机中,内存分配以较大的” 块” 为单位。如果对象较大,它会占用单独的块。严格来说,” 停止 - 复制” 要求在释放旧对象之前,必须先将所有存活对象从旧堆复制到新堆,这导致了大量的内存复制行为。有了块,垃圾回收器就可以把对象复制到废弃的块。每个块都有年代数来记录自己是否存活。通常,如果块在某处被引用,其年代数加 1,垃圾回收器会对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作——大型对象仍然不会复制(只是年代数会增加),含有小型对象的那些块则被复制并整理。Java 虚拟机会监视,如果所有对象都很稳定,垃圾回收的效率降低的话,就切换到” 标记 - 清扫” 方式。同样,Java 虚拟机会跟踪” 标记 - 清扫” 的效果,如果堆空间出现很多碎片,就会切换回” 停止 - 复制” 方式。这就是” 自适应” 的由来,你可以给它个啰嗦的称呼:” 自适应的、分代的、停止 - 复制、标记 - 清扫” 式的垃圾回收器。
Java 虚拟机中有许多附加技术用来提升速度。尤其是与加载器操作有关的,被称为” 即时”(Just-In-Time, JIT)编译器的技术。这种技术可以把程全部或部分翻译成本地机器码,所以不需要 JVM 来进行翻译,因此运行得更快。当需要装载某个类(通常是创建该类的第一个对象)时,编译器会先找到其 .class 文件,然后将该类的字节码装入内存。你可以让即时编译器编译所有代码,但这种做法有两个缺点:一是这种加载动作贯穿整个程序生命周期内,累加起来需要花更多时间;二是会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这会导致页面调度,从而一定降低程序速度。另一种做法称为_惰性评估_,意味着即时编译器只有在必要的时候才编译代码。这样,从未被执行的代码也许就压根不会被 JIT 编译。新版 JDK 中的 Java HotSpot 技术就采用了类似的做法,代码每被执行一次就优化一些,所以执行的次数越多,它的速度就越快。
成员初始化
略
构造器初始化
class Window {
Window(int marker) {
System.out.println("Window(" + marker + ")");
}
}
class House {
Window w1 = new Window(1);
Window w2 = new Window(2);
Window w3 = new Window(3);
House() {
System.out.println("House()");
w3 = new Window(33);
}
void f() {
System.out.println("f()");
}
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.f();
}
}
//Window(1)
//Window(2)
//Window(3)
//House()
//Window(33)
//f()
class Window {
Window(int marker) {
System.out.println("Window(" + marker + ")");
}
}
class House {
Window w1 = new Window(1);
Window w2 = new Window(2);
Window w3 = new Window(3);
House() {
System.out.println("House()");
w3 = new Window(33);
}
void f() {
System.out.println("f()");
}
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.f();
}
}
//Window(1)
//Window(2)
//Window(3)
//House()
//Window(33)
//f()
w3 被初始化了两次:
一次在调用构造器前
一次在构造器调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)
静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。
- static 关键字不能应用于局部变量,所以只能作用于属性(字段、域)。
- 初始值
- 如果一个字段是静态的基本类型,你没有初始化它,那么它就会获得基本类型的标准初值
- 如果它是对象引用,那么它的默认初值就是 null
调用顺序总结
- 初始化static成员变量, 只初始化一次。只有在必要时刻才会进行。
- 这个class进行初始化 、直接调用static 都会初始化这个类内部的全部静态变量
- 初始化非static 成员变量
- 构造方法
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f1(int marker) {
System.out.println("f1(" + marker + ")");
}
}
class Table {
static Bowl bowl1 = new Bowl(1);
static Bowl bowl2 = new Bowl(2);
Table() {
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
}
class Cupboard {
static Bowl bowl4 = new Bowl(4);
static Bowl bowl5 = new Bowl(5);
Bowl bowl3 = new Bowl(3);
Cupboard() {
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
}
public class Main {
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
public static void main(String[] args) {
System.out.println("main creating new Cupboard()");
new Cupboard();
// ---- static的优先初始化: static table ----// Bowl(1)
// Bowl(2)
// Table()
// f1(1) ---- table的构造方法中调用 ---
// ---- static的优先初始化: static cupboard ----// Bowl(4)
// Bowl(5)
// Bowl(3) ---- 非静态成员变量初始化
// Cupboard()
// f1(2) ---- Cupboard的构造方法中调用 ---
// main creating new Cupboard() - -main方法 --// Bowl(3) ---- 非静态成员变量初始化 ,静态不再初始化
// Cupboard()
// f1(2)
}
}
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f1(int marker) {
System.out.println("f1(" + marker + ")");
}
}
class Table {
static Bowl bowl1 = new Bowl(1);
static Bowl bowl2 = new Bowl(2);
Table() {
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
}
class Cupboard {
static Bowl bowl4 = new Bowl(4);
static Bowl bowl5 = new Bowl(5);
Bowl bowl3 = new Bowl(3);
Cupboard() {
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
}
public class Main {
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
public static void main(String[] args) {
System.out.println("main creating new Cupboard()");
new Cupboard();
// ---- static的优先初始化: static table ----// Bowl(1)
// Bowl(2)
// Table()
// f1(1) ---- table的构造方法中调用 ---
// ---- static的优先初始化: static cupboard ----// Bowl(4)
// Bowl(5)
// Bowl(3) ---- 非静态成员变量初始化
// Cupboard()
// f1(2) ---- Cupboard的构造方法中调用 ---
// main creating new Cupboard() - -main方法 --// Bowl(3) ---- 非静态成员变量初始化 ,静态不再初始化
// Cupboard()
// f1(2)
}
}
测试2
public class Main {
public static void main(String[] args) {
Bowl bowl1 = Table.bowl1;
// 输出:
// Bowl(1)
// Bowl(2)
}
}
public class Main {
public static void main(String[] args) {
Bowl bowl1 = Table.bowl1;
// 输出:
// Bowl(1)
// Bowl(2)
}
}
public class Main {
public static void main(String[] args) {
Table table = new Table();
// 输出:
// Bowl(1)
// Bowl(2)
// Table()
// f1(1)
}
}
public class Main {
public static void main(String[] args) {
Table table = new Table();
// 输出:
// Bowl(1)
// Bowl(2)
// Table()
// f1(1)
}
}
静态初始化只有在必要时刻才会进行
- 如果不创建 Table 对象,也不引用 Table.bowl1 或 Table.bowl2,那么静态的 Bowl 类对象 bowl1 和 bowl2 永远不会被创建。
- 只有在第一个 Table 对象被创建(或被访问)时,它们才会被初始化。此后,静态对象不会再次被初始化。
概括一下创建对象的过程,假设有个名为 Dog 的类:
- 即使没有显式地使用 static 关键字,构造器实际上也是静态方法。所以,当首次创建 Dog 类型的对象时,或是首次访问 Dog 类的静态方法或属性,Java 解释器必须在类路径中查找,以定位 Dog.class。
- 当加载完 Dog.class 后(后面会学到,这将创建一个 Class 对象),有关静态初始化的所有动作都会执行。因此,静态初始化只会在首次加载 Class 对象时初始化一次。
- 当用
new Dog()
创建对象时,首先会在堆上为 Dog 对象分配足够的存储空间。 - 分配的存储空间首先会被清零,即会将 Dog 对象中的所有基本类型数据设置为默认值(数字会被置为 0,布尔型和字符型也相同),引用被置为 null。
- 执行所有出现在字段定义处的初始化动作。
- 执行构造器。你将会在” 复用” 这一章看到,这可能会牵涉到很多动作,尤其当涉及继承的时候。
显式的静态初始化
你可以将一组静态初始化动作放在类里面一个特殊的” 静态子句”(有时叫做静态块)中。像下面这样:
public class Spoon {
static int i;
static {
i = 47;
}
}
非静态实例初始化
Java 提供了被称为_实例初始化_的类似语法,用来初始化每个对象的非静态变量,例如:
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{
mug1 = new Mug(1);
mug2 = new Mug(2);
System.out.println("mug1 & mug2 initialized");
}
Mugs() {
System.out.println("Mugs()");
}
Mugs(int i) {
System.out.println("Mugs(int)");
}
public static void main(String[] args) {
System.out.println("Inside main()");
new Mugs();
System.out.println("new Mugs() completed");
new Mugs(1);
System.out.println("new Mugs(1) completed");
}
}
输出:
Inside main
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{
mug1 = new Mug(1);
mug2 = new Mug(2);
System.out.println("mug1 & mug2 initialized");
}
Mugs() {
System.out.println("Mugs()");
}
Mugs(int i) {
System.out.println("Mugs(int)");
}
public static void main(String[] args) {
System.out.println("Inside main()");
new Mugs();
System.out.println("new Mugs() completed");
new Mugs(1);
System.out.println("new Mugs(1) completed");
}
}
输出:
Inside main
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
看起来它很像静态代码块,只不过少了 static 关键字。这种语法对于支持” 匿名内部类”(参见” 内部类” 一章)的初始化是必须的,但是你也可以使用它保证某些操作一定会发生,而不管哪个构造器被调用。从输出看出,示例初始化子句是在两个构造器之前执行的。
数组初始化
int[] a1 = {1, 2, 3, 4, 5};
for (int i = 0; i < a1.length; i++) {
a1[i] += 1;
}
int[] a1 = {1, 2, 3, 4, 5};
for (int i = 0; i < a1.length; i++) {
a1[i] += 1;
}
动态数组
import java.util.Arrays;
import java.util.Random;
public class Main {
public static void main(String[] args) {
Random rand = new Random(47);
Integer[] a = new Integer[rand.nextInt(20)];
System.out.println("length of a = " + a.length);
for (int i = 0; i < a.length; i++) {
a[i] = rand.nextInt(500);
}
System.out.println(Arrays.toString(a));
}
}
import java.util.Arrays;
import java.util.Random;
public class Main {
public static void main(String[] args) {
Random rand = new Random(47);
Integer[] a = new Integer[rand.nextInt(20)];
System.out.println("length of a = " + a.length);
for (int i = 0; i < a.length; i++) {
a[i] = rand.nextInt(500);
}
System.out.println(Arrays.toString(a));
}
}
先初始化了大小,再进行赋值。
可变参数列表
void printArray(Object... args) {
for (Object obj: args) {
System.out.print(obj + " ");
}
System.out.println();
}
void printArray(Object... args) {
for (Object obj: args) {
System.out.print(obj + " ");
}
System.out.println();
}
枚举类型
- 默认会创建toString方法
- 默认创建ordinal方法
- 可在switch语句中使用
enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMING
}
public class Main {
public static void main(String[] args) {
Spiciness howHot = Spiciness.MEDIUM;
// 枚举
// 默认会创建toString方法
// 默认会创建 ordinal System.out.println(howHot);
System.out.println(howHot.toString());
System.out.println(howHot.ordinal());
for (Spiciness value : Spiciness.values()) {
System.out.println(value + ":" + value.ordinal());
}
// NOT:0
// MILD:1
// MEDIUM:2
// HOT:3
// FLAMING:4
}
}
enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMING
}
public class Main {
public static void main(String[] args) {
Spiciness howHot = Spiciness.MEDIUM;
// 枚举
// 默认会创建toString方法
// 默认会创建 ordinal System.out.println(howHot);
System.out.println(howHot.toString());
System.out.println(howHot.ordinal());
for (Spiciness value : Spiciness.values()) {
System.out.println(value + ":" + value.ordinal());
}
// NOT:0
// MILD:1
// MEDIUM:2
// HOT:3
// FLAMING:4
}
}