1、java.lang 研究
本篇文章讨论那些由java.lang定义的类和接口。
正如你所知道的那样,java.lang被自动导入所有的程序。它所包含的类和接口对所有实际的Java程序都是必要的。它是Java最广泛使用的包。
java.lang包括了下面这些类:
Boolean Long StrictMath (Java 2,1.3)
Byte Math String
Character Number StringBuffer
Class Object System
ClassLoader Package (Java 2) Thread
Compiler Process >ThreadGroup
Double Runtime ThreadLocal (Java 2)
Float >RuntimePermission (Java 2) Throwable
>InheritableThreadLocal (Java 2) SecurityManager Void
>Integer >Short >
另外还有两个由Character定义的类:Character.Subset和Character.UnicodeBlock,它们是在Java 2中新增加的。
java.lang也定义了如下的接口:
? Cloneable
? Comparable
? Runnable
其中Comparable接口是在Java 2中新增加的。
java.lang中的几个类包含了过时的方法,其中的大多数可以追溯到Java 1.0。在Java2中仍然提供了这些方法,用于支持逐渐减少的老程序,而这些方法在新程序中不被推荐使用。大多数的过时方法出现在Java 2之前,因此在这里不讨论这些方法。而在Java 2中出现的那些过时的方法将被提及。
Java 2也在java.lang包中增加了几个新的类和方法,这些新类和方法被说明如下。
在可靠的环境中,可以在你的多任务操作系统中使用Java去执行其他特别繁重的进程(也即程序)。exec( )方法的几种形式允许命名想运行的程序以及它们的输入参数。exec( )方法返回一个Process对象,这个对象可以被用来控制你的Java程序如何与这个正在运行的新进程相互作用。因为Java可以运行在多种平台和多种操作系统的情况下,exec( )方法本质上是依赖于环境的。
下面的例子使用exec( )方法装入Window的简单文本编辑器--notepad。显而易见,这个例子必须在Windows操作系统下运行。
// Demonstrate exec().
class ExecDemo {
public static void main(String args[]) {
Runtime r = Runtime.getRuntime();
Process p = null;
try {
p = r.exec("notepad");
} catch (Exception e) {
System.out.println("Error executing notepad.");
}
}
}
exec( )方法有几个形式可用,而在本例子中展示的是最常用的一种。在新程序开始运行之后,由exec( )方法返回的Process对象可以被Process方法使用。可以使用destroy( )方法杀死子进程。waitFor( )方法暂停你的程序直至子进程结束。当子进程结束后,exitValue( )方法返回子进程返回的值。如果没有问题发生,它通常返回0。下面是前面关于exec( )方法例子的改进版本。例子被修改为等待直至正在运行的进程退出:
// Wait until notepad is terminated.
class ExecDemoFini {
public static void main(String args[]) {
Runtime r = Runtime.getRuntime();
Process p = null;
try {
p = r.exec("notepad");
p.waitFor();
} catch (Exception e) {
System.out.println("Error executing notepad.");
}
System.out.println("Notepad returned " + p.exitValue());
}
}
当子进程正在运行时,可以从它的标准输入输出进行读和写。getOutputStream( )方法和getInputStream( )方法返回子进程的标准输入(in)和输出(out)的句柄。
System类保存静态方法和变量的集合。标准的输入,输出和Java运行时错误输出存储在变量in,out和err中。由System类定义的方法列在表14-11中。注意当所做操作是安全方式所不允许的时,许多方法引发一个安全异常(SecurityException)。应当注意的另一点是:Java 2不赞成使用runFinalizersonExit( )方法。该方法是在Java 1.1中增加的,同时也被证明是不可靠的。
让我们看一看System类的一些普遍用法。
表11 由Sysem定义的方法
方法 描述
static void arraycopy(Object source, int sourceStart, Object target, int targetStart, int size) 复制数组。被复制的数组由source传递,而source中开始复制数组时的下标由sourceStart传递。接收复制的数组由target传递。而target中开始复制数组时的下标由targetStart传递。Size是被复制的元素的个数
static long currentTimeMillis( ) 返回自1970年1月1日午夜至今的时间,时间单位为毫秒。
static void exit(int exitCode) 暂停执行,返回exitCode值给父进程(通常为操作系统)。按照约定,0表示正常退出,所有其他的值代表某种形式的错误
static void gc( ) 初始化垃圾回收
static Properties getProperties( ) 返回与Java运行系统有关的属性类(Properties class)将在第15章中介绍)
static String getProperty(String which) 返回与which有关的属性。如果期望的属性没有被发现,返回一个空对象(null object)
static String getProperty(String which, String default) 返回一个与which有关的属性。如果期望的属性没有被发现,则返回default
static SecurityManager getSecurityManager( ) 返回当前的安全管理程序,如果没有安装安全管理程序,则返回一个空对象(null object)
static native int identityHashCode(Object obj) 返回obj的特征散列码
static void load(String libraryFileName) 载入其文件由libraryFileName指定的动态库,必须指定其完全路径
static void loadLibrary(String libraryName) 载入其库名为libraryName的动态库
static String mapLibraryName(String lib) 对应名为lib的库,返回一个指定平台的名字(在Java 2中新增加的)
static void runFinalization( ) 启动调用不用的但还不是回收站中的对象的finalize( )方法。
static void setErr(PrintStream eStream) 设置标准的错误(err)流为iStream
static void setIn(InputStream iStream) 设置标准的输入(in)流为oStream
static void setOut(PrintStream oStream) 设置标准的输出(out)流eStream
static void setProperties(Properties sysProperties) 设置由sysProperties指定的当前系统属性
Static String setProperty(String which, String v) 将v值赋给名为which的属性(在Java 2中新增加的)
static void setSecurityManager ( SecurityManager secMan) 设置由secMan指定的安全管理程序
可以发现System类的一个特别有意义的用法是利用currentTimeMillis( )方法来记录你的程序的不同部分的执行时间。currentTimeMillis( )方法返回自从1970年1月1号午夜起到现在的时间,时间单位是毫秒。如果要记录你的程序中一段有问题程序的运行时间可以在这段程序开始之前存储当前时间,在该段程序结束之际再次调用currentTimeMillis( )方法。执行该段程序所花费的时间为其结束时刻的时间值减去其开始时刻的时间值。下面的程序说明了这一点:
// Timing program execution.
class Elapsed {
public static void main(String args[]) {
long start, end;
System.out.println("Timing a for loop from 0 to 1,000,000");
// time a for loop from 0 to 1,000,000
start = System.currentTimeMillis(); // get starting time
for(int i=0; i < 1000000; i++) ;
end = System.currentTimeMillis(); // get ending time
System.out.println("Elapsed time: " + (end-start));
}
}
这里是程序运行的一个输出样本(记住你的程序的运行结果可能与此不同):
Timing a for loop from 0 to 1,000,000
Elapsed time: 10
使用arraycopy( )方法可以将一个任意类型的数组快速地从一个地方复制到另一个地方。这比使用Java中编写的循环要快的多。下面是一个用arraycopy( )方法复制两个数组的例子。首先,将数组a复制给数组b,接下来,数组a中的所有元素向后移一位,然后数组b中元素向前移一位。
// Using arraycopy().
class ACDemo {
static byte a[] = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 };
static byte b[] = { 77, 77, 77, 77, 77, 77, 77, 77, 77, 77 };
public static void main(String args[]) {
System.out.println("a = " + new String(a));
System.out.println("b = " + new String(b));
System.arraycopy(a, 0, b, 0, a.length);
System.out.println("a = " + new String(a));
System.out.println("b = " + new String(b));
System.arraycopy(a, 0, a, 1, a.length - 1);
System.arraycopy(b, 1, b, 0, b.length - 1);
System.out.println("a = " + new String(a));
System.out.println("b = " + new String(b));
}
}
正如从下面的输出中看到的那样,可以使用相同的源和目的在任一方向进行复制:
a = ABCDEFGHIJ
b = MMMMMMMMMM
a = ABCDEFGHIJ
b = ABCDEFGHIJ
a = AABCDEFGHI
b = BCDEFGHIJJ
下面的属性在Java 2的所有环境中可以使用:
file.separator java.vendor.url os.arch
java.class.path java.version os.name
java.class.version java.vm.name os.version
java.ext.dirs java.vm.specification.name Path.separator
java.home java.vm.specification.vendor User.dir
java.specification.name java.vm.specification.version User.home
java.specification.vendor java.vm.vendor User.name
java.specification.version java.vm.version
java.vendor line.separator
可以通过调用System.getProperty( )方法来获得不同环境变量的值。例如下面的程序显示当前用户目录的路径:
class ShowUserDir {
public static void main(String args[]) {
System.out.println(System.getProperty("user.dir"));
}
}
正如我们在第1部分所提及的,Object类是其他所有类的一个超类。表14-12给出了Object类中定义的方法,这些方法对于每一个对象都是可用的。
表12 由Object定义的方法
方法 描述
Object clone( ) Throws CloneNotSupportedException 创建一个与调用对象一样的新对象
Boolean equals(Object object) 如果调用对象等价于object返回true
void finalize( ) throws Throwable 默认的finalize( )方法。常常被子类重载
final Class getClass( ) 获得描述调用对象的Class对象
int hashCode( ) 返回与调用对象关联的散列码
final void notify( ) 恢复等待调用对象的线程的执行
final void notifyAll( ) 恢复等待调用对象的所有线程的执行
String toString( ) 返回描述对象的一个字符串
final void wait( ) throws InterruptedException 等待另一个执行的线程
final void wait(long milliseconds) throws InterruptedException 等待直至指定毫秒数的另一个执行的线程
final void wait(long milliseconds, int nanoseconds) throws InterruptedException 等待直至指定毫秒加毫微秒数的另一个执行的线程
由Object类定义的绝大部分方法在本书其他部分讨论。而一个特别值得关注的方法是clone( )。clone( )方法创建调用它的对象的一个复制副本。只有那些实现Cloneable接口的类能被复制。
Cloneable接口没有定义成员。它通常用于指明被创建的一个允许对对象进行位复制(也就是对象副本)的类。如果试图用一个不支持Cloneable接口的类调用clone( )方法,将引发一个CloneNotSupportedException异常。当一个副本被创建时,并没有调用被复制对象的构造函数。副本仅仅是原对象的一个简单精确的拷贝。
复制是一个具有潜在危险的操作,因为它可能引起不是你所期望的副作用。例如,假如被复制的对象包含了一个称为obRef的引用变量,当副本创建时,副本中的obRef如同原对象中的obRef一样引用相同的对象。如果副本改变了被obRef引用的对象的内容,那么对应的原对象也将被改变。这里是另一个例子。如果一个对象打开一个I/O流并被复制,两个对象将可操作相同的流。而且,如果其中一个对象关闭了流,而另一个对象仍试图对I/O流进行写操作的话,将导致错误。
由于复制可能引起问题,因此在Object内,clone( )方法被说明为protected。这就意味着它必须或者被由实现Cloneable的类所定义的方法调用,或者必须被那些类显式重载以便它是公共的。让我们看关于下面每一种方法的例子。
下面的程序实现Cloneable接口并定义cloneTest( )方法,该方法在Object中调用clone( )方法:
// Demonstrate the clone() method.
class TestClone implements Cloneable {
int a;
double b;
// This method calls Object's clone().
TestClone cloneTest() {
try {
// call clone in Object.
return (TestClone) super.clone();
} catch(CloneNotSupportedException e) {
System.out.println("Cloning not allowed.");
return this;
}
}
}
class CloneDemo {
public static void main(String args[]) {
TestClone x1 = new TestClone();
TestClone x2;
x1.a = 10;
x1.b = 20.98;
x2 = x1.cloneTest(); // clone x1
System.out.println("x1: " + x1.a + " " + x1.b);
System.out.println("x2: " + x2.a + " " + x2.b);
}
}
这里,方法cloneTest( )在Object中调用clone( )方法并且返回结果。注意由clone( )方法返回的对象必须被强制转换成它的适当类型(TestClone)。
下面的例子重载clone( )方法以便它能被其类外的程序所调用。为了完成这项功能,它的存取说明符必须是public,如下所示:
// Override the clone() method.
class TestClone implements Cloneable {
int a;
double b;
// clone() is now overridden and is public.
public Object clone() {
try {
// call clone in Object.
return super.clone();
} catch(CloneNotSupportedException e) {
System.out.println("Cloning not allowed.");
return this;
}
}
}
class CloneDemo2 {
public static void main(String args[]) {
TestClone x1 = new TestClone();
TestClone x2;
x1.a = 10;
x1.b = 20.98;
// here, clone() is called directly.
x2 = (TestClone) x1.clone();
System.out.println("x1: " + x1.a + " " + x1.b);
System.out.println("x2: " + x2.a + " " + x2.b);
}
}
由复制带来的副作用最初一般是比较难发现的。通常很容易想到的是类在复制时是很安全的,而实际却不是这样。一般在没有一个必须的原因的情况下,对任何类都不应该执行Cloneable。
Class封装对象或接口运行时的状态。当类被加载时,类型Class的对象被自动创建。不能显式说明一个类(Class)对象。一般地,通过调用由Object定义的getClass( )方法来获得一个类(Class)对象。由Class定义的一些最常用的方法列在表14-13中。
表13 由Class定义的一些方法
方法 描述
static Class forName(String name) throws ClassNotFoundException 返回一个给定全名的Class对象
static Class forName(String name, Boolean how, ClassLoader ldr) throws ClassNotFoundException 返回一个给定全名的Calss对象。对象由ldr指定的加载程序加载。如果how为true,对象被初始化,否则它将不被初始化(在Java 2中新增加的)
Class[ ] getClasses( ) 对每一个公共类和接口,返回一个类(Class)对象。这些公共类和接口是调用对象的成员
ClassLoader getClassLoader( ) 返回一个加载类或接口的ClassLoader对象,类或接口用于实例化调用对象
Constructor[ ] getConstructors( ) throws SecurityException 对这个类的所有的公共构造函数,返回一个Constructor对象
Constructor[ ] getDeclaredConstructors( ) throws SecurityException 对由这个类所声明的所有构造函数,返回一个Constructor对象
Field[ ] getDeclaredFields( ) throws SecurityException 对由这个类所声明的所有域,返回一个Field对象
Method[ ] getDeclaredMethods( ) throws SecurityException 对由这个类或接口所声明的所有方法,返回一个Method对象
Field[ ] getFields( ) throws SecurityException 对于这个类的所有公共域,返回一个Field对象
Class[ ] getInterfaces( ) 当调用对象时,这个方法返回一个由该对象的类类型实现的接口数组。当调用接口时,这个方法返回一个由该接口扩展的接口数组
Method[ ] getMethods( ) throws SecurityException 对这个类中的所有公共方法,返回一个Method对象
String getName( ) 返回调用对象的类或接口的全名
ProtectionDomain getProtectionDomain( ) 返回与调用对象有关的保护范围(在Java 2中新增加的)
Class getSuperclass( ) 返回调用对象的超类。如果调用对象是类型Object的,则返回值为空(null)
Boolean isInterface( ) 如果调用对象是一个接口,则返回true。否则返回false
Object newInstance( ) throws IllegalAccessException, InstantiationException 创建一个与调用对象类型相同的新的实例(即一个新对象)。这相当于对类的默认构造函数使用new。返回新对象
String toString( ) 返回调用对象或接口的字符串表达式
由Class定义的方法经常用在需要知道对象的运行时类型信息的场合。如同表14-13中所说明的那样,由Class提供的方法确定关于特定的类的附加信息。例如它的公共构造函数,域以及方法。这对于本书后面将要讨论的Java Beans函数是很重要的。
下面的程序说明了getClass( )(从Object继承的)和getSuperclass( )方法(从Class继承的):
// Demonstrate Run-Time Type Information.
class X {
int a;
float b;
}
class Y extends X {
double c;
}
class RTTI {
public static void main(String args[]) {
X x = new X();
Y y = new Y();
Class clObj;
clObj = x.getClass(); // get Class reference
System.out.println("x is object of type: " +
clObj.getName());
clObj = y.getClass(); // get Class reference
System.out.println("y is object of type: " +
clObj.getName());
clObj = clObj.getSuperclass();
System.out.println("y's superclass is " +
clObj.getName());
}
}
这个程序的输出如下所示:
x is object of type: X
y is object of type: Y
y’s superclass is X
抽象类ClassLoader规定了类是如何加载的。应用程序可以创建扩展ClassLoader的子类,实现它的方法。这样做允许使用不同于通常由Java运行时系统加载的另一些方法来加载类。由ClassLoader定义的一些方法列在表14-14中。
表14 由CalssLoader定义的一些方法
方法 描述
final Class defineClass(String str, byte b[ ], int index, int numBytes) throws ClassFormatError 返回一个类(Class)对象,类的名字在str中,对象包含在由b指定的字节数组中。该数组中对象开始的位置下标由index指定,而该数组的长度为numBytes。b中的数据必须表示一个有效的对象
final Class findSystemClass(String name) throws ClassNotFoundException 返回一个给定名字的类(Class)对象
abstract Class loadClass(String name, boolean callResolveClass) throws ClassNotFoundException 如果callResolveClass为true,这个抽象方法的实现工具必须加载一个给定名字的类,并调用resolveClass( )方法
final void resolveClass(Class obj) 用obj引用的类被解析(即,它的名字被输入在类名字空间中)
Math类保留了所有用于几何学,三角学以及几种一般用途方法的浮点函数。Math定义了两个双精度(double)常数:E(近似为2.72)和PI(近似为3.14)。
下面的三种方法对一个以弧度为单位的角度接收一个双精度(double)参数并且返回它们各自的超越函数的结果:
方法 描述
Static double sin(double arg) 返回由以弧度为单位由arg指定的角度的正弦值
static double cos(double arg) 返回由以弧度为单位由arg指定的角度的余弦值
static double tan(double arg) 返回由以弧度为单位由arg指定的角度的正切值
下面的方法将超越函数的结果作为一个参数,按弧度返回产生这个结果的角度值。它们是其非弧度形式的反。
方法 描述
static double asin(double arg) 返回一个角度,该角度的正弦值由arg指定
static double acos(double arg) 返回一个角度,该角度的余弦值由arg指定
static double atan(double arg) 返回一个角度,该角度的正切值由arg指定
static double atan2(double x, double y) 返回一个角度,该角度的正切值为x/y
Math定义了下面的指数方法:
方法 描述
static double exp(double arg) 返回arg的e
static double log(double arg) 返回arg的自然对数值
static double pow(double y, double x) 返回以y为底数,以x为指数的幂值;例如pow(2.0, 3.0)返回8.0
static double sqrt(double arg) 返回arg的平方根
Math类定义了几个提供不同类型舍入运算的方法。这些方法列在表15中。
表15 由Math定义的舍入方法
方法 描述
static int abs(int arg) 返回arg的绝对值
static long abs(long arg) 返回arg的绝对值
static float abs(float arg) 返回arg的绝对值
static double abs(double arg) 返回arg的绝对值
static double ceil(double arg) 返回大于或等于arg的最小整数
static double floor(double arg) 返回小于或等于arg的最大整数
static int max(int x, int y) 返回x和y中的最大值
static long max(long x, long y) 返回x和y中的最大值
static float max(float x, float y) 返回x和y中的最大值
static double max(double x, double y) 返回x和y中的最大值
static int min(int x, int y) 返回x和y中的最小值
static long min(long x, long y) 返回x和y中的最小值
static float min(float x, float y) 返回x和y中的最小值
static double min(double x, double y) 返回x和y中的最小值
static double rint(double arg) 返回最接近arg的整数值
static int round(float arg) 返回arg的只入不舍的最近的整型(int)值
static long round(double arg) 返回arg的只入不舍的最近的长整型(long)值
除了给出的方法,Math还定义了下面这些方法:
static double IEEEremainder(double dividend, double divisor)
static double random( )
static double toRadians(double angle)
static double toDegrees(double angle)
IEEEremainder( )方法返回dividend/divisor的余数。random( )方法返回一个伪随机数,其值介于0与1之间。在大多数情况下,当需要产生随机数时,通常用Random类。toRadians( )方法将角度的度转换为弧度。而toDegrees( )方法将弧度转换为度。这后两种方法是在Java 2中新增加的。
下面是一个说明toRadians( )和toDegrees( )方法的例子:
// Demonstrate toDegrees() and toRadians().
class Angles {
public static void main(String args[]) {
double theta = 120.0;
System.out.println(theta + " degrees is " +
Math.toRadians(theta) + " radians.");
theta = 1.312;
System.out.println(theta + " radians is " +
Math.toDegrees(theta) + " degrees.");
}
}
程序输出如下所示:
120.0 degrees is 2.0943951023931953 radians.
1.312 radians is 75.17206272116401 degrees.
在Java 2的1.3版本中增加了StrictMath类。这个类定义一个与Math中的数学方法类似的一套完整的数学方法。两者的区别在于StrictMath中的方法对所有Java工具保证产生精确一致的结果,而Math中的方法更大程度上是为了提高性能。
Compiler类支持创建将字节码编译而非解释成可执行码的Java环境。常规的程序不使用它。
Runnable接口以及Thread和ThreadGroup类支持多线程编程。下面分别予以说明。
注意:关于管理线程,实现Runnable接口以及创建多线程程序的概述已在第11章中介绍过。
Runnable接口必须由启动执行的独立线程的类所实现。Runnable仅定义了一种抽象方法,叫做run( )。该方法是线程的入口点。它的形式如下所示:
abstract void run( )
所创建的线程必须实现该方法。
Thread创建一个新的执行线程。它定义了如下的构造函数:
Thread( )
Thread(Runnable threadOb)
Thread(Runnable threadOb, StringthreadName)
Thread(String threadName)
Thread(ThreadGroup groupOb, Runnable threadOb)
Thread(ThreadGroup groupOb, Runnable threadOb, String threadName)
Thread(ThreadGroup groupOb, String threadName)
threadOb是实现Runnable接口的类的一个实例,它定义了线程运行开始的地方。线程的名字由threadName指定。当名字未被指定时,Java虚拟机将创建一个。groupOb指定了新线程所属的线程组。当没有线程组被指定时,新线程与其父线程属于同一线程组。
下面的常数由Thread定义:
MAX_PRIORITY
MIN_PRIORITY
NORM_PRIORITY
正如所期望的那样,这些常数指定了最大,最小以及默认的线程优先权。
由Thread定义的方法列在表14-16中。在比Java 2早的版本中,Thread中也包括了stop( ),suspend( )以及resume( )方法。然而正如在第11章中解释的那样,这些方法由于其固有的不稳定性而在Java 2中被摈弃了。在Java 2中摈弃的还有countStackFrames( )方法,因为它调用了suspend( )方法。
表16 由Thread定义的方法
方法 描述
static int activeCount( ) 返回线程所属的线程组中线程的个数
void checkAccess( ) 引起安全管理程序检验当前的线程能访问和/或能改变在其上checkAccess( )方法被调用的线程
static Thread currentThread( ) 返回一个Thread对象,该对象封装了调用这个方法的线程
void destroy( ) 终止线程
static int enumerate(Thread threads[ ]) 将当前线程组中的所有Thread对象的拷贝放入threads中。返回线程的个数
ClassLoader getContextClassLoader( ) 返回用于对这个线程加载类和资源的类加载程序(在Java 2中新增加的)
final String getName( ) 返回线程名
final int getPriority( ) 返回线程的属性设置
final ThreadGroup getThreadGroup( ) 返回调用线程是其中一个成员的ThreadGroup对象
void interrupt( ) 中断线程
static boolean interrupted( ) 如果当前执行的线程已经被预先设置了中断,则返回true;否则,返回false
final boolean isAlive( ) 如果线程仍在运行中,则返回true;否则返回false
final boolean isDaemon( ) 如果线程是一个后台进程线程(Java运行系统的一部分),则返回true;否则返回false
boolean isInterrupted( ) 如果线程被中断,则返回true,否则返回false
final void join( ) throws InterruptedException 等待直至线程终止
续表
方法 描述
final void join(long milliseconds) throws InterruptedException 等待直到为终止线程而指定的以毫秒计时的时间
final void join(long milliseconds, int nanoseconds) throws InterruptedException 等待直到为终止线程而指定的以毫秒加毫微秒计时的时间
void run( ) 开始线程的执行
void setContextClassLoader(ClassLoader cl) 设置将被调用线程用于cl的类加载程序(在Java 2中新增加的)
final void setDaemon(boolean state) 标记线程为后台进程线程
final void setName(String threadName) 将线程的名字设置为由threadName指定的名字
final void setPriority(int priority) 设置由priority指定的线程优先权
static void sleep(long milliseconds) throws InterruptedException 以指定的毫秒为单位的时间长度挂起执行的线程
static void sleep(long milliseconds, int nanoseconds) throws InterruptedException 以指定的毫秒加毫微秒为单位的时间长度挂起执行的线程
void start( ) 开始线程的执行
String toString( ) 返回线程的等价字符串形式
static void yield( ) 调用线程将CPU让给其他的线程
14.13.3 ThreadGroup
线程组(ThreadGroup)创建了一组线程。它定义了如下的两个构造函数:
ThreadGroup(String groupName)
ThreadGroup(ThreadGroup parentOb, String groupName)
对于两种形式,groupName指定了线程组的名字。第一种形式创建一个新的线程组,该线程组将当前的线程作为它的父线程。在第二种形式中,父线程由parentOb指定。
由ThreadGroup定义的方法列在表14-17中。在比Java 2更早出现的Java版本中,ThreadGroup中也包括了stop( ),suspend( )以及resume( )方法。这些方法由于其本身固有的不稳定性,而在Java 2中被摈弃。
表14-17 由ThreadGroup定义的方法
方法 描述
int activeCount( ) 返回线程组加上以这个线程作为父类的所有线程组中线程的个数
int activeGroupCount( ) 返回调用线程是父类的线程的组数
final void checkAccess( ) 引起安全管理程序检验调用线程能访问和/或能改变在其上checkAccess( )方法被调用的线程组
final void destroy( ) 撤消被调用的线程组(以及任一子线程组)
int enumerate(Thread group[ ]) 将构成调用线程组的线程放入group数组中
int enumerate(Thread group[ ], boolean all) 将构成调用线程组的线程放入group数组中。如果all为true,那么线程组的所有子线程组中的线程也被放入group中
int enumerate(ThreadGroup group[ ]) 将调用线程组的子线程组放入group数组中
int enumerate(ThreadGroup group[ ], boolean all) 将调用线程组的子线程组放入group数组中。如果all为true,所有子线程组的子线程组(等等)也被放入group中
final int getMaxPriority( ) 返回对线程组设置的最大优先权
final String getName( ) 返回线程组名
final ThreadGroup getParent( ) 如果调用ThreadGroup对象没有父类,则返回null;否则返回调用对象的父类
final void interrupt( ) 调用线程组中所有线程的interrupt( )方法(在Java 2中新增加的)
final boolean isDaemon( ) 如果线程组是一个端口后台进程组,则返回true;否则返回false
boolean isDestroyed( ) 如果线程组已经被破坏,则返回true;否则,返回false
void list( ) 显示关于线程组的信息
final boolean parentOf(ThreadGroup group) 如果调用线程是group的父线程(或group本身),则返回true;否则返回false
final void setDaemon(boolean isDaemon) 如果isDaemon为true,那么调用线程组被标记为一个端口后台进程组
final void setMaxPriority(int priority) 对调用线程组设置最大优先权priority
String toString( ) 返回线程组的字符串等价形式
void uncaughtException(Thread thread, Throwable e) 当一个异常未被捕获时,该方法被调用
线程组提供了一种方便的方法,可以将一组线程当做一个单元来管理。这在想挂起或恢复一些相关的线程的情况下,是特别有用的。例如假想在一个程序中,有一组线程被用来打印文档,另一组线程被用来将该文档显示在屏幕上,同时另一组线程将文档保存为磁盘文件。如果打印被异常中止了,想用一种很简单的方法停止所有与打印有关的线程。线程组为这种处理提供了方便。下面的程序说明了这种用法,在程序中创建两个线程组,每一线程组中有两个线程:
// Demonstrate thread groups.
class NewThread extends Thread {
boolean suspendFlag;
NewThread(String threadname, ThreadGroup tgOb) {
super(tgOb, threadname);
System.out.println("New thread: " + this);
suspendFlag = false;
start(); // Start the thread
}
// This is the entry point for thread.
public void run() {
try {
for(int i = 5; i > 0; i--) {
System.out.println(getName() + ": " + i);
Thread.sleep(1000);
synchronized(this) {
while(suspendFlag) {
wait();
}
}
}
} catch (Exception e) {
System.out.println("Exception in " + getName());
}
System.out.println(getName() + " exiting.");
}
void mysuspend() {
suspendFlag = true;
}
synchronized void myresume() {
suspendFlag = false;
notify();
}
}
class ThreadGroupDemo {
public static void main(String args[]) {
ThreadGroup groupA = new ThreadGroup("Group A");
ThreadGroup groupB = new ThreadGroup("Group B");
NewThread ob1 = new NewThread("One", groupA);
NewThread ob2 = new NewThread("Two", groupA);
NewThread ob3 = new NewThread("Three", groupB);
NewThread ob4 = new NewThread("Four", groupB);
System.out.println("/nHere is output from list():");
groupA.list();
groupB.list();
System.out.println();
System.out.println("Suspending Group A");
Thread tga[] = new Thread[groupA.activeCount()];
groupA.enumerate(tga); // get threads in group
for(int i = 0; i < tga.length; i++) {
((NewThread)tga[i]).mysuspend(); // suspend each thread
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Resuming Group A");
for(int i = 0; i < tga.length; i++) {
((NewThread)tga[i]).myresume(); // resume threads in group
}
// wait for threads to finish
try {
System.out.println("Waiting for threads to finish.");
ob1.join();
ob2.join();
ob3.join();
ob4.join();
} catch (Exception e) {
System.out.println("Exception in Main thread");
}
System.out.println("Main thread exiting.");
}
}
该程序的一个输出样本如下所示:
New thread: Thread[One,5,Group A]
New thread: Thread[Two,5,Group A]
New thread: Thread[Three,5,Group B]
New thread: Thread[Four,5,Group B]
Here is output from list():
java.lang.ThreadGroup[name=Group A,maxpri=10]
Thread[One,5,Group A]
Thread[Two,5,Group A]
java.lang.ThreadGroup[name=Group B,maxpri=10]
Thread[Three,5,Group B]
Thread[Four,5,Group B]
Suspending Group A
Three: 5
Four: 5
Three: 4
Four: 4
Three: 3
Four: 3
Three: 2
Four: 2
Resuming Group A
Waiting for threads to finish.
One: 5
Two: 5
Three: 1
Four: 1
One: 4
Two: 4
Three exiting.
Four exiting.
One: 3
Two: 3
One: 2
Two: 2
One: 1
Two: 1
One exiting.
Two exiting.
Main thread exiting.
注意在这个程序中,线程组A被挂起四秒。由于输出确认,造成线程One和线程Two暂停,但是线程Three和线程Four仍然运行。四秒钟之后,线程One和线程Two被恢复。注意线程组A是如何被挂起和恢复的。首先通过对线程组A调用enumerate( )方法得到线程组A中的线程。然后每一个线程重复通过得到的数组而被挂起。为了恢复线程组A中的线程,序列再一次被遍历,每一个线程被恢复。最后一点:这个例子使用了Java 2推荐使用的方法去完成挂起和恢复线程的任务。而没有用在Java 2中被摈弃的方法suspend( )和resume( )。
在Java 2的java.lang中增加了两个与线程有关的类:
? ThreadLocal?用于创建线程局部变量。每个线程都拥有自己局部变量的拷贝。
? InheritableThreadLocal?创建可以被继承的线程局部变量。
在Java 2中增加了一个称为Package的类。这个类封装了与包有关的版本数据。包版本信息由于包的增值以及由于Java程序可能需要知道哪些包版本可以利用而变得更加重要。Package中定义的方法列在表14-18中。下面的程序通过显示程序当前已知的包而说明了Package。
表18 由Package定义的方法
方法 描述
String getImplementationTitle( ) 返回调用包的标题
String getImplementationVendor( ) 返回调用包的实现程序的程序名
String getImplementationVersion( ) 返回调用包的版本号
String getName( ) 返回调用包的名字
Static Package getPackage(String pkgName) 返回一个由pkgName指定的Package对象
Static Package[ ] getPackages( ) 返回调用程序当前已知的所有包
String getSpecificationTitle( ) 返回调用包的规格说明的标题
String getSpecificationVendor( ) 返回对调用包的规格说明的所有者的名字
String getSpecificationVersion( ) 返回调用包的规格说明的版本号
Int hashCode( ) 返回调用包的散列码
Boolean isCompatibleWith(String verNum) throws NumberFormatException 如果verNum小于或等于调用包的版本号,则返回true
Boolean isSealed( ) 如果调用包被封,则返回true;否则返回false
Boolean isSealed(URL url) 如果调用包相对于url被封,则返回true;否则返回false。
String toString( ) 返回调用包的等价字符串形式
// Demonstrate Package
class PkgTest {
public static void main(String args[]) {
Package pkgs[];
pkgs = Package.getPackages();
for(int i=0; i < pkgs.length; i++)
System.out.println(
pkgs[i].getName() + " " +
pkgs[i].getImplementationTitle() + " " +
pkgs[i].getImplementationVendor() + " " +
pkgs[i].getImplementationVersion()
);
}
}
在Java 2的java.lang中也新增加了RuntimePermission。它与Java的安全机制有关,这里不做进一步的讨论。
Throwable类支持Java的异常处理系统,它是派生所有异常类的类。在本书第10章已经讨论过它。
SecurityManager是一个子类可以实现的抽象类,它用于创建一个安全管理程序。一般不需要实现自己的安全管理程序,如果非要这样做,需要查阅与你的Java开发系统一起得到的相关文档。
Java 2在java.lang中新增加了一个接口:Comparable。实现Comparable的类的对象可以被排序。换句话说,实现Comparable的类包含了可以按某种有意义的方式进行比较的对象。Comparable接口说明了一个方法,该方法用于确定Java 2调用一个类的实例的自然顺序。该方法如下所示:
int compareTo(Object obj)
这个方法比较调用对象和obj。如果他们相等,就返回0。如果调用对象比obj小,则返回一个负值。否则返回一个正值。
该接口由前面已经介绍的几种类实现。特别是Byte,Character,Double,Float,Long,Short,String以及Integer类定义了compareTo( )方法。另外,下一章将会介绍到,实现这个接口的对象可以被使用在不同的集合中。
在Java中定义了两个java.lang的子包:java.lang.ref和java.lang.reflect。下面分别予以简单介绍。
在前面学到过,在Java中,垃圾回收工具自动确定何时对一个对象,没有引用存在。然后这个对象就被认为是不再需要的,同时它所占的内存也被释放。在Java 2中新增加的java.lang.ref包中的类对垃圾回收处理提供更加灵活的控制。例如,假设你的程序创建了大量的在后面某个时间又想重新使用的对象,可以持续保持对这些对象的引用,但是这可能需要更多的内存开销。
作为替代,可以对这些对象定义“软”引用。如果可以利用的内存接近用完的话,一个可以“软实现”的对象可以从垃圾回收工具中释放。在那种情况下,垃圾回收工具将这个对象的“软”引用设为空(null)。否则,垃圾回收工具保存对象以便以后使用。
程序设计人员具有确定是否一个“软实现”的对象被释放的能力。如果它被释放了,可以重新创建它。如果没有释放,该对象对于后面的应用将一直是可以利用的。也可以为对象创建“弱”(weak)和“假想”(phantom)引用,不过关于这些以及java.lang.ref包中其他特性的讨论已经超过了本书的范围。
Reflection是一个程序分析自己的能力。包java.lang.reflect提供了获得关于一个类的域、构造函数、方法和修改符的能力。需要这些信息去创建可以使你利用Java Beans组件的软件工具。这个工具使用映射动态地确定组件的特征。这个主题将在第25章中讨论。
另外,包java.lang.reflect包括了一个可以动态创建和访问数组的类。