[读书笔记]Java编程思想第5章之初始化与清理
Posted Spring-_-Bear
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[读书笔记]Java编程思想第5章之初始化与清理相关的知识,希望对你有一定的参考价值。
- Java方法如果实际传入的参数类型小于方法中声明的形式参数类型,实际数据类型就会被提升。char型有所不同,如果无法找到恰好接受char参数的方法,就会把char直接提升为int型。方法接受较小的基本类型作为参数时,如果传入的参数较大,就得通过强制类型转换来执行窄化操作。如果不这样做,编译器就会报错。
- 没有默认构造器的话,就没有方法可以调用,就无法创建对象。但是,如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。
- this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。只有当需要明确指出当前对象的引用时,才需要使用this关键字。例如,当需要返回当前对象的引用时,就常常在return语句里这样写:
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/20 11:38
*/
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();
}
}
由于increment()通过this关键字返回了对当前对象的引用,所以很容易在一条语句里对同一对象执行多次操作。
- this关键字对于将当前对象传递给其它方法也很有用:
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/20 11:46
*/
class Person{
public void eat(Apple apple) {
Apple peeled = apple.getPeeled();
System.out.println("Yummy");
}
}
class Peeler{
static Apple peel(Apple apple) {
return apple;
}
}
class Apple {
Apple getPeeled(){
return Peeler.peel(this);
}
}
public class PassingThis {
public static void main(String[] args) {
new Person().eat(new Apple());
}
}
Apple需要调用Peeler.peel()方法,它是一个外部的工具方法,将执行由于某种原因而必须放在Apple外部的操作(也许是因为该外部方法要应用于许多不同的类,而你却不想重复这些代码)。为了将自身传递给外部方法,Apple必须使用this关键字。
- 可以用this在一个构造器中调用另一个构造器。通常写this的时候,都是指“这个对象”或者“当前对象”,而且它本身表示对当前对象的引用。尽管可以用this调用一个构造器,但却不能调用两个。此外,必须将构造器调用置于最开始处,否则编译器会报错。除构造器外,编译器禁止在其他任何方法中调用构造器。
- static方法就是没有this的方法。在静态方法内不能调用非静态方法,反过来却可以。
- 假定你的对象(并非是使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它并不知道该如何释放该对象的这块“特殊”内存。为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储的空间,将首先调用其finalize()方法,并且在下一次回收垃圾动作发生时,才会真正回收对象所占的内存。所以要是你打算使用finalize(),就能在垃圾回收时刻做一些重要的清理工作。
- Java里的对象并非总是被垃圾回收,C++的对象一定会被销毁(析构),换句话说:1.Java的对象可能不被垃圾回收;2.垃圾回收并不等于“析构”;3.垃圾回收只与内存有关。只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行退出,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交换给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。
- 使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize()方法),它们也必须同内存机器回收有关。但这是否意味着要是对象中含有其它对象,finalize()就应该明确释放那些对象呢?不,无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。
- “本地方法”:本地方法是一种在Java中调用非Java代码的方式。本地方法目前只支持C和C++,但它们可以调用其它语言编写的代码,所以实际上可以调用任何代码。在非Java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非调用了free()函数,否则存储空间将得不到释放,从而造成内存泄漏。当然,free()函数是C和C++中的函数,所以需要在finalize()中用本地方法调用它。
- Java不允许创建局部对象,必须使用new创建对象。如果Java虚拟机并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。
- finalize()还有一个有趣的用法,它并不依赖于每次都要对finalize()进行调用,这就是对象终结条件的验证。如果对某个对象不再感兴趣,也就是它可以被清理了,这个对象应该处于某种状态,使它占用的内存可以被安全地释放。例如,要是对象代表了一个打开的文件,在对象被回收前程序员应该关闭这个文件。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的缺陷。finalize()可以用来方法这种情况------尽管它并不是总是被调用。如果某次finalize()的动作使得缺陷被发现,那么就可据此找出问题所在。
- 以下例子示范了finalize()可能的使用方式:
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/20 15:51
*/
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn(){
checkedOut = false;
}
protected void finalize(){
if (checkedOut) {
System.out.println("Error: checked out");
}
// super.finalize();
}
}
public class TerminationCondition{
public static void main(String[] args) {
Book novel = new Book(true);
novel.checkIn();
new Book(true);
System.gc();
}
}
本例的终结条件是:所有的Book对象在被当作垃圾回收前都应该被签入(check in)。但在main()方法中,由于程序员的错误,有一本书未被签入。要是没有finalize()来验证终结条件,将很难发现这种缺陷。注意,System.gc()用于强制进行终结动作。即使不这么做,通过反复的程序执行(假设程序将分配大量的存储空间为导致垃圾回收动作的执行),最终也能找出错误的Book对象。你应该总是假设基类版本的finalize()也要做某些重要的事情,因此要用super()来调用它,就像在Book.finalize()中看到的那样,在本例中,它被注释掉了,因为它需要进行异常处理。
- 任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。Java虚拟机采用一种自适应的垃圾回收技术:1. 停止------复制; 2. 标记------清扫。别名:“自适应的、分代的、停止-复制,标记-清扫式垃圾回收器”。
- 在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散步于方法定义之间,它们仍然会在任何方法(包括构造器)被调用之前得到初始化。
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/20 16:18
*/
class Window{
Window(int marker){
System.out.println("Window(" + marker + ")");
}
}
public class House {
Window w1 = new Window(1);
House(){
System.out.println("House()");
w3 = new Window(33);
}
Window w2 = new Window(2);
void f() {
System.out.println("f()");
}
Window w3 = new Window(3);
public static void main(String[] args) {
House h = new House();
h.f();
}
}
- 无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/21 7:53
*/
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);
Table() {
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard {
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization {
public static void main(String[] args) {
System.out.println("Creating new Cupboard() in main");
new Cupboard();
System.out.println("Creating new Cupboard() in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
静态初始化只有在必要时刻才会进行,只有在该类的第一个对象被创建或者第一次访问该类的静态数据时,静态变量、对象引用才会被初始化,此后,静态对象不会再被初始化。初始化的顺序先是静态对象,然后才是“非静态对象”。构造器实际上给也是静态方法。
- Java允许将多个静态初始化动作组织成一个特殊的“静态子句”(有时也称静态块)
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/21 18:19
*/
public class Spoon {
static int i;
static {
i = 47;
}
}
- Java数组会自动进行初始化,默认值为Java意义上的0。
package thinkinjava.charpenter5;
import java.util.Arrays;
import java.util.Random;
/**
* @author Spring-_-Bear
* @version 2021/9/22 10:11
*/
public class ArrayNew {
public static void main(String[] args) {
Random rand = new Random(47);
int[] a = new int[rand.nextInt(100)];
System.out.println("length of a = " + a.length);
System.out.println(Arrays.toString(a));
}
}
- 可以用花括号括起来的列表来初始化对象数组,初始化列表的最后一个逗号都是可选的(这一特性使维护长列表变得更容易)
package thinkinjava.charpenter5;
import java.util.Arrays;
/**
* @author Spring-_-Bear
* @version 2021/9/22 10:15
*/
public class ArrayInit {
public static void main(String[] args) {
Integer[] a = {
new Integer(1),
new Integer(2),
3,
};
Integer[] b = {
new Integer(1),
new Integer(2),
3,
};
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
}
}
- 可变参数列表不依赖于自动包装类型,而实际使用的是基本类型。
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/22 10:48
*/
public class VarargType {
static void f(Character... args) {
System.out.print(args.getClass());
System.out.println(" length " + args.length);
}
static void g(int... args) {
System.out.print(args.getClass());
System.out.println(" length " + args.length);
}
public static void main(String[] args) {
f('a');
f();
g(1);
g();
System.out.println("int[]: " + new int[0].getClass());
}
}
练习1. 创建一个类,它包含了一个未初始化的String引用。验证该引用被Java初始化成了null。
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/22 11:14
*/
class TestDrive
{
String s;
}
public class Test1 {
public static void main(String[] args) {
TestDrive td = new TestDrive();
System.out.println(td.s);
}
}
练习2: 创建一个类,它包含了一个在定义时就被初始化了的String域,以及另一个通过构造器初始化的String域。这两种方式有何差异?
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/22 17:18
*/
class StringTest{
String s1;
String s2 = "I am happy!";
String s3;
StringTest(String s) {
this.s3 = s;
}
}
public class Test2 {
public static void main(String[] args) {
StringTest stringTest = new StringTest("Oh,bye!");
System.out.println(stringTest.s1);
System.out.println(stringTest.s2);
System.out.println(stringTest.s3);
}
}
练习3: 创建一个带默认构造器(即无参构造器)的类,在构造器中打印一条消息。为这个类创建一个对象。
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/22 17:23
*/
class Constructor {
Constructor() {
System.out.println("Hello, welcome to Java programming world!");
}
}
public class Test3 {
public static void main(String[] args) {
Constructor constructor = new Constructor();
}
}
练习4: 为前一个练习中的类添加一个重载构造器,令其接受一个字符串参数,并在构造器中把你自己的消息接收的参数一起打印出来。
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/22 17:32
*/
class StringConstructor{
StringConstructor(){
System.out.println("Welcome to Java world first time!");
}
StringConstructor(String s) {
System.out.println(s);
}
}
public class Test4 {
public static void main(String[] args) {
StringConstructor stringConstructor1 = new StringConstructor();
StringConstructor stringConstructor2 = new StringConstructor("Welcome to Java world second time!");
}
}
练习5: 创建一个名为Dog的类,它具有重载的bark()方法。此方法应根据不同的基本数据类型进行重载,并根据被调用的版本,打印出不同类型的犬吠(barking)、咆哮(howling)等信息。编写main()来调用所有不同的版本信息。
package thinkinjava.charpenter5;
/**
* @author Spring-_-Bear
* @version 2021/9/22 18:25
*/
class Dog {
void bark() {
System.out.println("Keep silent!");
}
void bark(boolean b) {
System.out.println("boolean: wang wang");
}
void bark(byte by) {
System.out.println("byte: wang wang");
}
void bark(char ch) {
System.out.println("char: wang wang");
}
void bark(short sh) {
System.out.println("short: wang wang");
}
void bark(int i) {
System.out.以上是关于[读书笔记]Java编程思想第5章之初始化与清理的主要内容,如果未能解决你的问题,请参考以下文章