on java8之初始化和清理

本文详细介绍了构造器在Java中的应用,包括保证初始化、方法重载、this关键字、构造器间的调用、垃圾回收机制、成员变量初始化、构造器初始化、数组和枚举的实例化。通过实例演示,深入理解构造器的用法和内存管理过程。
摘要由CSDN通过智能技术生成

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
  1. new Rock() ,内存被分配,构造器被调用
  2. 构造器方法名与类名相同
  3. 无参构造器被称为默认构造器, 不手动去添加构造方法时,系统默认有一个无参构造方法,如果自己手动写了有参构造方法,则默认的无参构造方法失效
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 的含义

  1. static方法中不会存在 this
  2. 不能在静态方法中调用非静态方法(所以在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.
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodePanda@GPF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值