1. 利用构造器保证初始化
class Rock {
Rock() { // This is the constructor
System.out.print("Rock ");
}
}
public class SimpleConstructor {
public static void main(String[] args) {
for(int i = 0; i < 10; i++)
new Rock();
}
}
//Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock
- new Rock() ,内存被分配,构造器被调用
- 构造器方法名与类名相同
- 无参构造器被称为默认构造器, 不手动去添加构造方法时,系统默认有一个无参构造方法,如果自己手动写了有参构造方法,则默认的无参构造方法失效
class Rock2 {
Rock2(int i) {
System.out.print("Rock " + i + " ");
}
}
public class SimpleConstructor2 {
public static void main(String[] args) {
for(int i = 0; i < 8; i++)
new Rock2(i);
//new Rock2(); 编译错误
}
}
/* Output:
Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7
*/
2. 方法重载
方法重载是必要的,它允许方法具有相同的方法名但接收的参数不同(参数类型或参数顺序不同),静态方法和成员方法名字参数相同也是方法重复, 定义不同的构造方法,实际上就是构造器重载
//编译提示方法名重复
public int f(){
return 1;
}
public double f(){
return 1.1;
}
区分重载方法:
//根据参数类型和位置区分重载方法
public class OverloadingOrder {
static void f(String s, int i) {
System.out.println("String: " + s + ", int: " + i);
}
static void f(int i, String s) {
System.out.println("int: " + i + ", String: " + s);
}
public static void main(String[] args) {
f("String first", 11);
f(99, "Int first");
}
}
/* Output:
String: String first, int: 11
int: 99, String: Int first
*/
重载与基本类型:
基本类型可以自动从较小的类型转型为较大的类型
public static void main(String[] args) {
f(1);//1.0 int类型向double类型转化
}
public static void f(double a){
System.out.println(a);
}
返回值不同不能作为重载的依据
void f(){}
int f() {return 1;}
对于上面两个方法,存在以下两种调用情形:
case1: int x=f();
此时编译器知道调用的是有返回值的方法f()
case2: f();
此时没有接受变量,编译器就不知道调用哪个方法了
因此,不能根据返回值类型区分重载的方法
3. this关键字
class Banana { void peel(int i) { /* ... */ } }
public class BananaPeel {
public static void main(String[] args) {
Banana a = new Banana(),
b = new Banana();
a.peel(1);
b.peel(2);
}
}
只有一个方法 peel() ,那么怎么知道调用的是对象 a 的 peel() 方法还是对象 b 的 peel() 方法?
答案:peel()方法中第一个参数隐密地传入了一个指向操作对象的引用
this 关键字只能在非静态方法内部使用。当你调用一个对象的方法时,this 生成了一个对象引用
在一个类的方法里调用该类的其他方法,不要使用 this,直接调用即可,this 自动地应用于其他方法上了
public class Apricot {
void pick() { /* ... */ }
void pit() {
pick();
//this.pick(); 也可以
/* ... */
}
}
this 关键字只用在一些必须显式使用当前对象引用的特殊场合。例如,用在return 语句中返回对当前对象的引用:
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();
}
}
/* Output:
i = 3
*/
4. 在构造器中调用构造器
package housekeeping;
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);
//Flower(petals); 必须使用this(petals)
//- this(s); // Can't call two!
this.s = s; // Another use of "this"
System.out.println("String & int args");
}
Flower() {
this("hi", 47);
System.out.println("Zero-argument constructor");
}
void printPetalCount() {
//- this(11); // Not inside non-constructor!
System.out.println(
"petalCount = " + petalCount + " s = "+ s);
}
public static void main(String[] args) {
Flower x = new Flower();
x.printPetalCount();
}
}
/* Output:
Constructor w/ int arg only, petalCount= 47
String & int args
Zero-argument constructor
petalCount = 47 s = hi
*/
- this调用其他的构造器这条语句必须是当前构造器的第一条语句
- 一个构造器只能调用一次其他的构造器方法
- 不能在构造器之外的方法里调用构造器
static 的含义
- static方法中不会存在 this
- 不能在静态方法中调用非静态方法(所以在main函数中调用非静态方法需要先创建对象,再通过对象调用成员方法),但是非静态方法中可以调用静态方法
5. 垃圾回收器
使用new创建的对象会被垃圾回收器回收,但存在一些比较特殊的情况,即通过某种创建对象方式之外
的方式为对象分配了存储空间(分配内存时可能采用了类似 C 语言中的做法,这种情况主要发生在使用 “本地方法”, JNI), 对于这种分配方式,需要使用fianlize
方法来手动地释放空间(比如在finalize函数中使用C语言中的free)
fianlize方法的另外一个用法:
eg: 对象代表了一个打开的文件,在对象被垃圾回收之前程序员应该关闭这个文件
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
@SuppressWarnings("deprecation")
@Override public void finalize() {
if(checkedOut)
System.out.println("Error: checked out");
// Normally, you'll also do this:
// super.finalize(); // Call the base-class version
}
}
public class TerminationCondition {
public static void main(String[] args) throws InterruptedException {
Book novel = new Book(true);
// Proper cleanup:
novel.checkIn();
// Drop the reference, forget to clean up:
new Book(true);
// Force garbage collection & finalization:
System.gc();
Thread.sleep(1000);; // One second delay
}
}
/* Output:
Error: checked out
*/
上面代码中模拟书的登记情况,即某本书的对象在被垃圾回收之前应该被登记(checkedOut值为false), 为了避免遗漏登记的情况,可以finalize方法中进行判断。
垃圾回收器如何工作:
一种比较慢的垃圾回收机制: 引用计数法
每个对象中含有一个引用计数器,每当有引用指向该对象时,引用计数加 1。当引用离开作用域或被置为 null 时,引用计数减 1,当发现某个对象的引用计数为 0 时,就释放其占用的空间
缺点:如果对象之间存在循环引用,那么它们的引用计数都不为 0,就会出现应该被回收但无法被回收的情况
一直比较块的垃圾回收机制:root追溯法
对于任意 “活” 的对象,一定能最终追溯到其存活在栈或静态存储区中的引用
如果从栈或静态存储区出发,遍历所有的引用,你将会发现所“活” 的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是该对象包含的所有引用,如此反复进行,直到访问完 “根源于栈或静态存储区的引用” 所形成的整个网络
6. 成员初始化
- 成员变量即使不初始化也有一个初始值,比如int类型默认值为0,引用类型默认值是null
- 可以指定成员变量的初值,指定初值可以使用字面量,也可以使用带有返回值的函数
public class MethodInit {
int i = f();//ok
int j=1;//ok
int f() {
return 11;
}
}
方法可以带有参数,但这些参数不能是未初始化的类成员变量:
public class MethodInit {
int i = f();//ok
int j=f(i);//ok
int f(int a) {
return 11;
}
}
注意不能向前引用:
public class MethodInit {
int i = f(j);//not ok j此时还没有初始化
int j=f(i);//ok
int f(int a) {
return 11;
}
}
7. 构造器初始化
public class Counter {
int i;
Counter() {
i = 7;
}
}
//i 首先会被初始化为 0,然后变为 7
类中变量定义的顺序决定了它们初始化的顺序。即使变量定义散布在方法定义之间,它们仍会在任何方法(包括构造器)被调用之前得到初始
class Window {
Window(int marker) {
System.out.println("Window(" + marker + ")");
}
}
class House {
Window w1 = new Window(1);
House() {
// Show that we're in the constructor:
System.out.println("House()");
w3 = new Window(33); // Reinitialize w3
}
Window w2 = new Window(2);
void f() { System.out.println("f()"); }
Window w3 = new Window(3);
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.f(); // Shows that construction is done
}
}
/* Output:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
*/
引用类型的成员变量window的初始化在house构造器之前执行, 不管window类型的引用声明的位置在house构造器前还是后
静态数据的初始化:
无论创建多少个对象,静态数据都只占用一份存储区域。static 关键字不能应用于局部变量,所以只能作用于属性(字段、域)。如果一个字段是静态的基本类型,你没有初始化它,那么它就会获得基本类型的标准初值。如果它是对象引用,那么它的默认初值就是 null
- 静态初始化只有在必要时刻才会进行,第一次创建静态对象或引用静态对象
- 初始化的顺序先是静态对象(如果它们之前没有被初始化的话),然后是非静态对象
- 静态初始化只会在首次加载 Class 对象时初始化一次
显式的静态初始化
public class TerminationCondition {
static {
i = 48;
}
static int i=12;
public static void main(String[] args) throws InterruptedException {
System.out.println(TerminationCondition.i);//12
//i值变化:0->48->12
}
}
上面代码段中的静态代码块只会执行一次
非静态实例初始化
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{ // [1]
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");
}
}
/* Output:
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 关键字, 非静态代码块和静态代码块一样,在构造器之前执行
7. 数组初始化
Java中的数组是对象,数组变量是一个引用
public class ArraysOfPrimitives {
public static void main(String[] args) {
int[] a1 = { 1, 2, 3, 4, 5 };
int[] a2;
a2 = a1;
for(int i = 0; i < a2.length; i++)
a2[i] += 1;
for(int i = 0; i < a1.length; i++)
System.out.println("a1[" + i + "] = " + a1[i]);
}
}
/* Output:
a1[0] = 2
a1[1] = 3
a1[2] = 4
a1[3] = 5
a1[4] = 6
*/
动态数组创建
不确定数组中需要多少个元素,可以使用 new 在数组中创建元素
public class ArrayNew {
public static void main(String[] args) {
int[] a;
Random rand = new Random(47);
a = new int[rand.nextInt(20)];
System.out.println("length of a = " + a.length);
System.out.println(Arrays.toString(a));
}
}
/* Output:
length of a = 18
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
*/
可变参数列表
public class NewVarArgs {
static void printArray(Object... args) {//三个.表示可变参数
for(Object obj : args)
System.out.print(obj + " ");
System.out.println();
}
public static void main(String[] args) {
// Can take individual elements:
printArray(47, (float) 3.14, 11.11);
printArray(47, 3.14F, 11.11);
printArray("one", "two", "three");
printArray(new A(), new A(), new A());
// Or an array:
printArray((Object[])new Integer[]{ 1, 2, 3, 4 });
printArray(); // Empty list is OK
}
}
/* Output:
47 3.14 11.11
47 3.14 11.11
one two three
A@19e0bfd A@139a55 A@1db9742
1 2 3 4
*/
可变参数必须是参数列表中的最后一个参数,当普通参数和可变参数同时存在时,先将参数匹配前面的普通的参数,匹配完剩余的参数给可变参数
public int f(int ...args,int b){}// error
public int f(int b,int ...args){}// ok
8. 枚举类型
public enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMING
}
public class EnumOrder {
public static void main(String[] args) {
for(Spiciness s : Spiciness.values())
System.out.println(
s + ", ordinal " + s.ordinal());
}
}
/* Output:
NOT, ordinal 0
MILD, ordinal 1
MEDIUM, ordinal 2
HOT, ordinal 3
FLAMING, ordinal 4
*/
- 枚举类型的toString方法被重写
- ordinal() 方法表示某个特定 enum 常量的声明顺序(从0开始)
枚举类型可以在switch语句中使用
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree) {
this.degree = degree;
}
public void describe() {
System.out.print("This burrito is ");
switch(degree) {
case NOT: System.out.println(
"not spicy at all.");
break;
case MILD:
case MEDIUM: System.out.println("a little hot.");
break;
case HOT:
case FLAMING:
default: System.out.println("maybe too hot.");
}
}
public static void main(String[] args) {
Burrito
plain = new Burrito(Spiciness.NOT),
greenChile = new Burrito(Spiciness.MEDIUM),
jalapeno = new Burrito(Spiciness.HOT);
plain.describe();
greenChile.describe();
jalapeno.describe();
}
}
/* Output:
This burrito is not spicy at all.
This burrito is a little hot.
This burrito is maybe too hot.
*/