Thinking in Java from Chapter 21

From Thinking in Java 4th Edition

并发

线程可以驱动任务,因此你需要一种描述任务的方式,这可由Runnable接口来提供。

要想定义任务,只需要实现Runnable接口,并编写run()方法,使得该任务可以执行你的命令。

public class LiftOff implements Runnable {
	protected int countDown = 10;	// Default
	private static int taskCount = 0;
	private final int id = taskCount++;

	public LiftOff() {}
	public LiftOff(int countDown){
		this.countDown = countDown;
	}

	public String status(){
		return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!") + "). ";
	}

	public void run(){
		while(countDown-- > 0){
			System.out.println(status());
			Thread.yield();
		}
	}
}

从Runnable导出一个类时,它必须具有run()方法,但是它不会产生任何内在的线程能力。要实现线程行为,你必须显式地将一个任务附着到线程上。

将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器:

public class BasicThread {
	public static void main(String[] args){
		Thread t = new Thread(new LiftOff());
		t.start();
		System.out.println("Waiting for LiftOff");
	}
} /* Output:
Waiting for LiftOff
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),
*/

Thread构造器只需要一个Runnable对象。

1. 调用Thread对象的start()方法为该线程执行必要的初始化操作。

2. 调用Runnable对象的run()方法,以便在这个新线程中启动该任务。

从输出可以看出start()方法迅速返回了,因为"Waiting for LiftOff"消息在倒计时完成之前就出现了。

实际上你产生的是对LiftOff.run()方法的调用,并且这个方法还没完成,但是因为LiftOff.run()是由不同的线程执行的,因此你仍旧可以执行main()线程中的其他操作。

如果添加更多的线程去驱动更多的任务,就可以看到所有任务彼此之间是如何呼应的:

public class MoreBasicThreads {
	public static void main(String[] args){
		for(int i = 0; i < 5; ++i)
			new Thread(new LiftOff()).start();
		System.out.println("Waiting for LiftOff");
	}
} /* Output:
Waiting for LiftOff
#3(9), #1(9), #3(8), #1(8), #3(7), #1(7), #1(6), #1(5),
#1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #4(9), #4(8),
#4(7), #4(6), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4),
#2(3), #2(2), #2(1), #2(LiftOff!), #0(9), #4(5), #3(6),
#4(4), #3(5), #4(3), #3(4), #4(2), #3(3), #4(1), #3(2),
#4(LiftOff!), #0(8), #3(1), #0(7), #3(LiftOff!), #0(6),
#0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),
*/

使用Executor

Executor在客户端任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。

Executor用来代替MoreBasicThreads.java中显式创建Thread对象。

ExecutorService知道如何构建上下文来执行Runnable对象。

import java.util.concurrent.*;

public class CachedThreadPool {
	public static void main(String[] args){
		ExecutorService exec = Executors.newCachedThreadPool();   // No space between new and Cached
		for(int i = 0; i < 5; ++i)
			exec.execute(new LiftOff());
		exec.shutdown();
	}
} /* Output:
#0(9), #2(9), #0(8), #1(9), #0(7), #0(6), #0(5), #0(4),
#0(3), #0(2), #0(1), #0(LiftOff!), #2(8), #1(8), #3(9),
#2(7), #1(7), #3(8), #2(6), #1(6), #3(7), #2(5), #2(4),
#4(9), #3(6), #1(5), #3(5), #1(4), #1(3), #1(2), #1(1),
#1(LiftOff!), #4(8), #2(3), #4(7), #4(6), #3(4), #4(5),
#3(3), #4(4), #3(2), #4(3), #3(1), #4(2), #2(2), #4(1),
#4(LiftOff!), #3(LiftOff!), #2(1), #2(LiftOff!),
*/

上例中,CachedThreadPool将为每个任务都创建一个线程。

ExecutorService对象是使用静态的Executor方法创建的,这个方法可以确定其Executor类型。

常见的情况是,Executor被用来创建和管理系统中的所有的任务。

对shutdown()方法的调用可以防止新任务被提交给这个Executor,当前任务(本例中为main的线程)将继续运行在shutdown()被调用之前提交的所有任务。

下面的程序展示了CachedThreadPool替换为不同类型的Executor。FixedThreadPool使用了有限的线程集来执行所提交的任务

import java.util.concurrent.*;

public class FixedThreadPool {
	public static void main(String[] args){
		// Constructor argument is number of threads:
		ExecutorService exec = Executors.newFixedThreadPool(5);
		for(int i = 0; i < 5; ++i)
			exec.execute(new LiftOff());
		exec.shutdown();
	}
} /* Output:
#0(9), #2(9), #4(9), #0(8), #2(8), #3(9), #1(9), #1(8),
#0(7), #4(8), #0(6), #4(7), #0(5), #4(6), #4(5), #4(4),
#4(3), #4(2), #4(1), #4(LiftOff!), #1(7), #1(6), #1(5),
#1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #3(8), #2(7),
#0(4), #3(7), #2(6), #0(3), #3(6), #2(5), #0(2), #3(5),
#0(1), #3(4), #2(4), #0(LiftOff!), #3(3), #2(3), #3(2),
#2(2), #3(1), #2(1), #3(LiftOff!), #2(LiftOff!),
*/

有了FixedThreadPool,你就可以一次性预先执行代价高昂的线程分配。这可以节省时间,因为你不用为每个任务都固定地付出创建线程的开销。

SingleThreadExecutor就像是线程数量为1的FixedThreadPool。如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前结束,所有的任务将使用相同的线程。

下例中可以看到每个任务都是按照它们被提交的顺序、并且是在下一个任务开始之前完成的。因此SingleThreadExecutor会序列化所有提交给它们的任务,并会维护它自己(隐藏)的悬挂任务。

import java.util.concurrent.*;

public class SingleThreadExecutor {
	public static void main(String[] args){
		ExecutorService exec = Executors.newSingleThreadExecutor();

		for(int i = 0; i < 5; ++i)
			exec.execute(new LiftOff());
		exec.shutdown();
	}
} /* Output:
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),
#1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(LiftOff!),
#2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(LiftOff!),
#3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2), #3(1), #3(LiftOff!),
#4(9), #4(8), #4(7), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(LiftOff!),
*/

从任务中返回值

Runnable是执行工作的独立任务,但是它不返回任何值。如果希望能够返回值,则必须实现Callable接口而不是Runnable接口。

Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它:

import java.util.concurrent.*;
import java.util.*;

class TaskWithResult implements Callable<String> {
	private int id;
	public TaskWithResult(int id){
		this.id = id;
	}

	public String call() {
		return "result of TaskWithResult " + id;
	}
}

public class CallableDemo {
	public static void main(String[] args){
		ExecutorService exec = Executors.newCachedThreadPool();
		ArrayList<Future<String>> results = new ArrayList<Future<String>>();

		for(int i = 0; i < 10; ++i)
			results.add(exec.submit(new TaskWithResult(i)));
		for(Future<String> fs : results)
			try {
				// get() blocks until completion:
				System.out.println(fs.get());
			} catch(InterruptedException e){
				System.out.println(e);
				return;
			} catch(ExecutionException e) {
				System.out.println(e);
			} finally {
				exec.shutdown();
			}
	}
} /* Output:
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 2
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
result of TaskWithResult 9
*/

submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化 。

1. 可以用isDone()方法来查看Future是否完成

2. 任务完成时,可以调用get()方法来获取该结果

也可以不用isDone()进行检查就直接调用get(),这种情况下,get()将阻塞直至结果准备就绪。

休眠

影响任务行为的一种简单方法是调用sleep(),这将使任务中止执行给定的时间。

在LiftOff类中,把yield()的调用换成sleep()将得到:

import java.util.concurrent.*;

public class SleepingTask extends LiftOff {
	public void run(){
		try {
			while(countDown-- > 0){
				System.out.print(status());
				// Old-style
				// Thread.sleep(100);
				// Java SE5/6-style:
				TimeUnit.MILLISECONDS.sleep(100);
			}
		} catch(InterruptedException e) {
			System.err.println("Interrupted");
		}
	}

	public static void main(String[] args){
		ExecutorService exec = Executors.newCachedThreadPool();
		for(int i = 0; i < 5; ++i)
			exec.execute(new SleepingTask());
		exec.shutdown();
	}
} /* Output:
#0(9), #3(9), #1(9), #4(9), #2(9),
#0(8), #3(8), #1(8), #2(8), #4(8),
#0(7), #4(7), #2(7), #3(7), #1(7),
#4(6), #0(6), #3(6), #1(6), #2(6),
#4(5), #3(5), #2(5), #0(5), #1(5),
#4(4), #3(4), #2(4), #1(4), #0(4),
#4(3), #2(3), #3(3), #0(3), #1(3),
#4(2), #2(2), #0(2), #3(2), #1(2),
#4(1), #2(1), #0(1), #3(1), #1(1),
#4(LiftOff!), #2(LiftOff!), #0(LiftOff!), #3(LiftOff!), #1(LiftOff!),
*/

sleep()调用可以抛出InterruptedException异常,并可以看到,它在run()中捕获。因为异常不能跨线程传播回main(),所以你必须在本地处理所有在任务内部产生的异常。

优先级

调度器将倾向于让优先权最高的线程先执行,然而,这并不意味着优先权较低的线程将得不到执行(也就是说,优先权不会导致死锁)。优先级较低的线程仅仅是执行的频率较低。

绝大多数时间里,所有线程都应该按照默认的优先级运行。试图操纵线程的优先级通常是一种错误。

可以用getPriority()来读取现有线程的优先级,并且在任何时候都可以通过setPriority()来修改它:

import java.util.concurrent.*;

public class SimplePriorities implements Runnable {
	private int countDown = 5;
	private volatile double d;	// No optimization
	private int priority;

	public SimplePriorities(int priority){
		this.priority = priority;
	}

	public String toString(){
		return Thread.currentThread() + ": " + countDown;
	}

	public void run(){
		Thread.currentThread().setPriority(priority);
		while(true){
			// An expensive, interruptable operation:
			for(int i = 1; i < 100000; ++i){
				d += (Math.PI + Math.E) / (double)i;
				if(0 == i % 1000)
					Thread.yield();
			}

			System.out.println(this);
			if(0 == --countDown) return;
		}
	}

	public static void main(String[] args){
		ExecutorService exec = Executors.newCachedThreadPool();

		for(int i = 0; i < 5; ++i)
			exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
		exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));

		exec.shutdown();
	}
}

让步

如果知道已经完成了在run()方法的循环的一次迭代过程中所需的工作,就可以给线程调度机制一个暗示:你的工作已经做得差不多了,可以让别的线程使用CPU了。这个暗示将通过yield()方法做出(不过这只是一个暗示,没有任何机制保证它将会被采纳)。

当调用yield()时,你也在建议具有相同优先级的其他线程可以运行。

后台线程

后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可缺少的部分。

当所有非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。

反过来,只要有非后台线程还在运行,程序就不会终止:

import java.util.concurrent.*;
import static net.mindview.util.Print.*;

public class SimpleDaemons implements Runnable {
	public void run(){
		try {
			while(true) {
				TimeUnit.MILLISECONDS.sleep(100);
				print(Thread.currentThread() + " " + this);
			}
		} catch(InterruptedException e) {
			print("sleep() interrupted");
		}
	}

	public static void main(String[] args) throws Exception {
		for(int i = 0; i < 10; ++i){
			Thread daemon = new Thread(new SimpleDaemons());
			daemon.setDaemon(true);	// Must call before start()
			daemon.start();
		}

		print("All daemons started");
		TimeUnit.MILLISECONDS.sleep(175);
	}
} /* Output:
All daemons started
Thread[Thread-4,5,main] [email protected]
Thread[Thread-2,5,main] [email protected]
Thread[Thread-3,5,main] [email protected]
Thread[Thread-8,5,main] [email protected]
Thread[Thread-0,5,main] [email protected]
Thread[Thread-1,5,main] [email protected]
Thread[Thread-9,5,main] [email protected]
Thread[Thread-7,5,main] [email protected]
Thread[Thread-5,5,main] [email protected]
Thread[Thread-6,5,main] [email protected]
*/

SimpleDaemons.java创建了显示的线程,以便可以设置它们的后台标志。通过编写定制的ThreadFactory可以定制由Executor创建的线程的属性(后台、优先级、名称):

package net.mindview.util;
import java.util.concurrent.*;

public class DaemonThreadFactory implements ThreadFactory {
	public Thread newThread(Runnable r) {
		Thread t = new Thread(r);
		t.setDaemon(true);
		return t;
	}
}

现在可以用一个新的DaemonThreadFactory作为参数传递给Executor.newCachedThreadPool():

package net.mindview.util;
import java.util.concurrent.*;

public class DaemonThreadFactory implements ThreadFactory {
	public Thread newThread(Runnable r) {
		Thread t = new Thread(r);
		t.setDaemon(true);
		return t;
	}
}

// Using a Thread Factory to create daemons.
import java.util.concurrent.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;

public class DaemonFromFactory implements Runnable {
	public void run(){
		try {
			while(true){
				TimeUnit.MILLISECONDS.sleep(100);
				print(Thread.currentThread() + " " + this);
			}
		} catch(InterruptedException e){
			print("Interrupted");
		}
	}

	public static void main(String[] args) throws Exception {
		ExecutorService exec = Executor.newCachedThreadPool(new DaemonThreadFactory());
		for(int i = 0; i < 10; ++i)
			exec.execute(new DaemonFromFactory());
		print("All daemons started");
		TimeUnit.MILLISECONDS.sleep(500);	// Run for a while
	}
} /* Output
All daemons started
Thread[Thread-0,5,main] [email protected]
Thread[Thread-9,5,main] [email protected]
Thread[Thread-7,5,main] [email protected]
Thread[Thread-5,5,main] [email protected]
Thread[Thread-3,5,main] [email protected]
Thread[Thread-1,5,main] [email protected]
Thread[Thread-8,5,main] [email protected]
Thread[Thread-2,5,main] [email protected]
Thread[Thread-4,5,main] [email protected]
Thread[Thread-6,5,main] [email protected]
Thread[Thread-0,5,main] [email protected]
Thread[Thread-9,5,main] [email protected]
....
*/

每个静态的ExecutorService创建方法都被重载为接受一个ThreadFactory对象,而这个对象将被用来创建新的线程:

package net.mindview.util;
import java.util.concurrent.*;

public class DaemonThreadPoolExecutor extends ThreadPoolExecutor {
	public DaemonThreadPoolExecutor() {
		super(0, Integer.MAX_VALUE, 60L, TimeUnit, SECONDS, new SynchronousQueue<Runnable>(), new DaemonThradFactory());
	}
}

可以通过iDaemon()方法来确定线程是否是一个后台线程。如果是一个后台线程,那么它创建的任何线程将自动被设置成后台线程。

// Daemon threads spawn other daemon threads.
import java.util.concurrent.*;
import static net.mindview.util.Print.*;

class Daemon implements Runnable {
	private Thread[] t = new Thread[10];

	public void run() {
		for(int i = 0; i < t.length; ++i){
			t[i] = new Thread(new DaemonSpawn());
			t[i].start();
			printnb("DaemonSpawn " + i + " started, ");
		}

		for(int i = 0; i < t.length; ++i)
			printnb("t[" + i + "].isDaemon() = " + t[i].isDaemon() + ", ");

		while(true)
			Thread.yield();
	}
}

class DaemonSpawn implements Runnable {
	public void run(){
		while(true)
			Thread.yield();
	}
}

public class Daemons {
	public static void main(String[] args) throws Exception {
		Thread d = new Thread(new Daemon());
		d.setDaemon(true);
		d.start();
		printnb("d.isDaemon() = " + d.isDaemon() + ", ");

		// Allow the daemon threads to
		// finish their startup processes:
		TimeUnit.SECONDS.sleep(1);
	}
} /* Output:
d.isDaemon() = true,
DaemonSpawn 0 started,
DaemonSpawn 1 started,
DaemonSpawn 2 started,
DaemonSpawn 3 started,
DaemonSpawn 4 started,
DaemonSpawn 5 started,
DaemonSpawn 6 started,
DaemonSpawn 7 started,
DaemonSpawn 8 started,
DaemonSpawn 9 started,
t[0].isDaemon() = true,
t[1].isDaemon() = true,
t[2].isDaemon() = true,
t[3].isDaemon() = true,
t[4].isDaemon() = true,
t[5].isDaemon() = true,
t[6].isDaemon() = true,
t[7].isDaemon() = true,
t[8].isDaemon() = true,
t[9].isDaemon() = true,
*/

应该意识到后台进程在不执行finally子句的情况下就会终止其run()方法:

// Daemon threads don‘t run the finally clause
import java.util.concurrent.*;
import static net.mindview.util.Print.*;

class ADaemon implements Runnable {
	public void run() {
		try {
			print("Starting ADaemon");
			TimeUnit.SECONDS.sleep(1);
		} catch(InterruptedException e){
			print("Exiting via InterruptedException");
		} finally {
			print("This should always run?");
		}
	}
}

public class DaemonsDontRunFinally {
	public static void main(String[] args) throws Exception {
		Thread t = new Thread(new ADaemon());
		t.setDaemon(true);
		t.start();
	}
} /* Output:
Starting ADaemon
*/

目前的示例中,都是实现了Runnable。在非常简单的情况下,你可能会希望使用直接从Thread继承这种可替换的方式:

public class SimpleThread extends Thread {
	private int countDown = 5;
	private static int threadCount = 0;

	public SimpleThread() {
		// Store the thread name:
		super(Integer.toString(++threadCount));
		start();
	}

	public String toString() {
		return "#" + getName() + "(" + countDown + "), ";
	}

	public void run() {
		while(true){
			System.out.print(this);
			if(0 == --countDown)
				return;
		}
	}

	public static void main(String[] args){
		for(int i = 0; i < 5; ++i)
			new SimpleThread();
	}
} /* Output:
#2(5), #2(4), #2(3), #2(2), #2(1),
#4(5), #4(4), #4(3), #4(2), #4(1),
#5(5), #5(4), #5(3), #5(2), #5(1),
#3(5), #3(4), #3(3), #3(2), #3(1),
#1(5), #1(4), #1(3), #1(2), #1(1),
*/

你可以通过适当的Thread构造器为Thread对象赋予具体的名称,这个名称可以通过使用getName()从toString()中获得。

另一种可能会看到的惯用法是自管理的Runnable:

// A Runnable containing its own driver Thread.
public class SelfManaged implements Runnable {
	private int countDown = 5;
	private Thread t = new Thread(this);

	public SelfManaged() {t.start();}

	public String toString() {
		return Thread.currentThread().getName() + "(" + countDown + "), ";
	}

	public void run() {
		while(true){
			System.out.print(this);
			if(0 == --countDown)
				return;
		}
	}

	public static void main(String[] args){
		for(int i = 0; i < 5; ++i)
			new SelfManaged();
	}
} /* Output:
Thread-3(5), Thread-3(4), Thread-3(3), Thread-3(2), Thread-3(1),
Thread-5(5), Thread-5(4), Thread-5(3), Thread-5(2), Thread-5(1),
Thread-6(5), Thread-6(4), Thread-6(3), Thread-6(2), Thread-6(1),
Thread-4(5), Thread-4(4), Thread-4(3), Thread-4(2), Thread-4(1),
Thread-7(5), Thread-7(4), Thread-7(3), Thread-7(2), Thread-7(1),
*/

这与从Thread继承并没有什么特别的差异,只是语法稍微晦涩一些。但是,实现接口使得你可以继承另一个不同的类,而从Thread继承将不行。

注意,这个示例中的start()是在构造器中调用的。应该意识到,在构造器中启动线程可能会变得很有问题,因为另一个任务可能会在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。这是优选Executor而不是显式地创建Thread对象的另一个原因。

有时,通过使用内部类来讲线程代码隐藏在类中将会很有用:

时间: 2024-10-14 23:05:13

Thinking in Java from Chapter 21的相关文章

零元学Expression Blend 4 &amp;ndash; Chapter 21 以实作案例学习MouseDragElementBehavior

原文:零元学Expression Blend 4 – Chapter 21 以实作案例学习MouseDragElementBehavior 本章将教大家如何运用Blend 4内建的行为注入元件「MouseDragElementBehavior」--使物件拥有拖拉功能 ? 本章将教大家如何运用Blend 4内建的行为注入元件「MouseDragElementBehavior」--使物件有拖拉功能 ? 这麽说好了,当A物件被MouseDragElementBehavior寄生以後,A物件就会拥有Mo

我的java学习笔记(21)关于框架定位

1.AWT和Swing中框架和组件类的继承层次. 2.组件类的很多方法是以获取或设置方法对形式出现的. public String getTitle(); public void setTitle(Stirng title); 3.对于类型为boolean的属性,获取方法由is开头. public boolean isLocationByPlatform(); public void setLocationByPlatform(boolean b); 4.如果没有明确地指出指定框架的大小,所有框

【Java EE 学习 21 下】【 使用易宝支付接口实现java网上支付功能】

一.网上支付分为两种情况,一种方法是使用直接和银行的支付接口,另外一种方法是使用第三方支付平台和银行对接完成支付. 1.直接和银行对接. 2.使用第三方支付平台 3.常见的第三方支付平台 二.使用易宝支付接口实现java网上支付功能(农业银行). 1.完整源代码:https://github.com/kdyzm/day21_2_pay 2.实现过程的时序图 3.技术要点 (1)使用GET请求的时候必须将全部参数都带上,参数名称参考开发者文档中的请求参数列表 (2)使用PaymentUtil类实现

akka---Getting Started Tutorial (Java): First Chapter

原文地址:http://doc.akka.io/docs/akka/2.0.2/intro/getting-started-first-java.html Introduction Welcome to the first tutorial on how to get started with Akka and Java. We assume that you already know what Akka and Java are and will now focus on the steps

Thinking in Java from Chapter 11

From Thinking in Java 4th Edition 持有对象 // Simple container example (produces compiler warnings.) // {ThrowsException} import java.util.*; class Apple { private static long counter; private final long id = counter++; public long id() { return id;} } c

Java学习笔记21

Objects是Java 7新增的一个工具类,它提供了一些工具方法来操作对象,这些工具方法大多是"空指针"安全的. Objects实现的方法如下: requireNonNull(T)与requireNonNull(T,String)在源码中定义如下: public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; } publ

java学习第21天(IO流的使用)

IO流分类: A:流向 输入流 读取数据 输出流 写出数据 B:数据类型 字节流 字节输入流 字节输出流 字符流 字符输入流 字符输出流 注意: a:如果我们没有明确说明按照什么分,默认按照数据类型分. b:除非文件用windows自带的记事本打开我们能够读懂,才采用字符流,否则建议使用字节流. 构造 FileOutputStream写出数据 FileOutputStream fos = new FileOutputStream("f.txt") fos.write("Hel

Thinking in Java from Chapter 10

From Thinking in Java 4th Edition 内部类 public class Parcel1 { class Contents { private int i = 11; public int value { return i;} } class Destination { private String label; Destination(String whereTo) { label = whereTo; } String readLabel() { return l

Thinking in Java from Chapter 7

From Thinking in Java 4th Edition final 1. 对于基本类型,final使数值恒定不变 2. 对于对象引用,final使引用恒定不变,即不能指向别的对象,但指向的对象本身可以改变(The same for array in Java) import java.util.*; import static net.mindview.util.Print.*; class Value{ int i; // Package access public Value(i