Java关键字:transient、volatile、instanceof、strictfp、assert
-
-
-
- 1.1 transient 和 volatile 修饰符
- 1.2 使用 instanceof 运算符
- 1.3 strictfp
- 1.4 使用 assert
-
-
1.1 transient 和 volatile 修饰符
Java定义了两个有趣的类型修饰符:transient 和 volatile。这两个修饰符用于修饰某些特殊情况。
如果将实例变量声明为transient,那么当存储对象时,实例变量的值将不需要永久保存。
例如:
class T{
transient int a;//will not persistint b;//will persist
}
在此,如果将T类型的对象写入永久存储区域,那么虽然不会保存a的内容,但是会保存b的内容。
修饰符volatile告诉编译器,由volatile修饰的变量可以被程序的其他部分随意修改。涉及多线程的程序就是这些情况中的一种。在多线程程序中,有时两个或多个线程共享相同的变量。出于效率方面的考虑,每个线程自身可以保存这种共享变量的私有副本。真正的(或主)变量副本在各个时间被更新,例如当进入同步方法时。虽然这种方式可以工作,但是有时效率可能不高。在有些情况下,重要的是变量的主副本总是反应自身的当前状态。为了确保这一点,简单地将变量修改为volatile,这会告诉编译器必须总是使用volatile变量的主副本(或者,至少总是保持所有私有版本和最新的主副本一致,反之亦然)。此外,访问主变量的顺序必须和所有私有副本相同,以精确的顺序执行。
1.2 使用 instanceof 运算符
有时,在运行时知道对象的类型是有用的。例如,可能有一个执行线程生成各种类型的对象,而另一个线程处理这些对象。在这种情况下,对于处理线程而言,当接收到每个对象时知道对象的类型是有用的。对于另外一种情况,在运行时知道对象类型也很重要,这种情况涉及类型转换。在Java中,无效的类型转换会导致运行时错误。许多无效的类型转换可以在编译时捕获。但是,涉及类层次的类型转换可能会产生只有在运行时才能发现的无效转换。例如,超类A可以产生两个子类B和C。因此,将类型B的对象转换为类型A的对象,或者将类型C的对象转换为类型A的对象都是合法的,但是将类型B的对象转换为类型C的对象则是非法的(反之亦然)。因为类型A的对象可以引用类型B或C的对象,所以在运行时,当将对象转换为类型C时,如何知道对象实际引用的是什么类型?可能是类A、B或C的对象。如果是类型B的对象,就会抛出运行时异常。Java提供了运行时运算符instanceof来应对这种情况。
运算符instanceof的一般形式如下所示:
objref instanceof type
其中,objref是对类实例的引用,type是类类型。如果objref是指定的类型或者可以被转换为这种指定类型,那么instanceof运算符的求值结果为true;否则为false。因此,程序可以使用instanceof运算符来获得与对象相关的运行时类型信息。
下面的程序演示了instanceof运算符的使用:
//Demonstrate instanceof operator
public class A {
int i,j;
}
public class B {
int i,j;
}
public class C extends A{
int k;
}
public class D extends A{
int k;
}
public class InstanceOf {
public static void main(String[] args) {
A a = new A();B b = new B();C c = new C();D d = new D();if (a instanceof A)System.out.println("a is instance of A");if (b instanceof B)System.out.println("b is instance of B");if (c instanceof C)System.out.println("c is instance of C");if (c instanceof A)System.out.println("c can be cast to A");if (a instanceof C)System.out.println("a can be cast to C");//compare types of derived typesA ob;ob = d;//A reference to dSystem.out.println("ob now refers to d");if (ob instanceof D)System.out.println("ob is instance of D");ob = c;//A reference to cSystem.out.println("ob now refers to C");if (ob instanceof D)System.out.println("ob can be cast to D");elseSystem.out.println("ob cannot be cast to D");if (ob instanceof A)System.out.println("ob can be cast to A");//all objects can be cast to Objectif (a instanceof Object)System.out.println("a may be cast to Object");if (b instanceof Object)System.out.println("b may be cast to Object");if (c instanceof Object)System.out.println("c may be cast to Object");if (d instanceof Object)System.out.println("d may be cast to Object");/*** 执行结果* a is instance of A* b is instance of B* c is instance of C* c can be cast to A* ob now refers to d* ob is instance of D* ob now refers to C* ob cannot be cast to D* ob can be cast to A* a may be cast to Object* b may be cast to Object* c may be cast to Object* d may be cast to Object*/}
}
大多数程序不需要使用instanceof运算符,因为我们通常知道正在使用的对象的类型。但是,当编写用于操作复杂层次对象的通用例程时,instanceof运算符很有用。
1.3 strictfp
在设计Java2时,浮点计算模型稍微宽松了一些。特别地,在计算期间新模型不需要载断特定的中间值。在某些情况下,这可以防止溢出。通过使用strictfp修饰类、方法或接口,可以确保采用与Java以前版本使用的相同方式执行浮点计算(以及所有截断)。如果使用strictfp对类进行了修饰,那么类中的所有方法也将自动使用strictfp进行修饰。
例如,下面的代码段告诉Java为MyClass类中定义的所有方法都使用原始的浮点计算模型:
strictfp class MyCalss{
//...
可能永远不需要使用strictfp,因为strictfp只影响很少的一类问题。
1.4 使用 assert
assert,用于在程序开发期间创建断言,断言是在程序执行期间应当为true的条件。例如,可能有一个应当总是返回正整数数值的方法。可以通过使用一条assert语句来断言返回值大于0,对其进行测试。在运行时,如果条件为true,就不会发生其他动作。但是,如果条件为false,那么会抛出AssertionError异常。断言经常用于证实在测试期间实际遇到了某些期望的条件。在发布代码时通常不实用它们。
assert关键字有两种形式。第一种形式如下所示:
assert condition;
其中,condition是一个求值结果必须为布尔值的表达式。如果结果为true,那么断言为真,不会发生其他动作。如果条件为false,那么断言失败并抛出默认的AssertionError对象。
assert关键字的第二种形式如下所示:
assert condition:expr;
在这个版本中,expr是传递给AssertionError构造函数的值。这个值被转换成相应的字符串格式,并且如果断言失败,将会显示该字符串。典型地,将为expr指定一个字符串,不过也可以指定任何非void表达式,只要定义了合理的字符串转换即可。
下面是使用断言的一个例子。该例验证getnum()方法的返回值为正值。
//Demonstrate assert.
public class AssertDemo {
static int val = 3;//Return an integer.static int getnum() {
return val--;}public static void main(String[] args) {
int n;for (int i = 0; i < 10; i++) {
n = getnum();assert n > 0;//will fail when n is 0System.out.println("n is " + n);}}
}
为了在运行时启用断言检查,必须指定-ea选项。例如,为了启用对AssertDemo的断言,使用下面这行命令执行程序:
java -ea AssertDemo
在向前面那样编译和运行程序后,程序会创建如下所示的输出:
在mian()中重复调用getnum()方法,该方法返回一个整数值。getnum()方法的返回值被赋给n,然后使用assert语句对n进行测试:
assert n > 0;// will fail when n is 0
当n等于0时,这条语句失败。在第4次调用getnum()方法后,n会等于0。当断言失败时,会抛出异常。
可以指定当断言失败时显示的消息。例如,如果使用下面这条语句替换前面程序中的断言:
assert n > 0 : "n is negative!";
将会生成下面的输出:
关于断言需要理解的重要一点是:不能依赖它们执行程序实际需要的任何动作。因为在正常情况下,发布代码在运行时会禁用断言。例如,分析前面程序的另外一个版本:
//A poor way to use assert!!!
public class AssertDemo {
//get a random number generatorstatic int val = 3;//Return an integer.static int getnum() {
return val--;}public static void main(String[] args) {
int n;for (int i = 0; i < 10; i++) {
assert (n = getnum()) > 0;//This is not a good idea!System.out.println("n is " + n);}}
}
在程序的这个版本中,对getnum()方法的调用被移到assert语句内部。尽管如果启用了断言的话,这可以工作的很好,但是当禁用断言时,会导致问题产生,因为多getnum()方法的调用永远都不会执行!实际上,现在必须初始化n,因为编译器能够识别出断言可能不会为n赋值。
Java增加断言特性是一件好事情,因为它们可以简化开发期间对常见错误类型的检查工作。例如,在添加对断言的支持之前,在前面的程序中如果希望验证n为正值,就必须使用类似下面的一系列代码:
if(n < 0){
System.out.println("n is negative!");return;//or throw an exception
}
而使用断言的话,只需要一行代码。此外,不需要从发布代码中移除assert语句。
启用和禁用断言
当执行代码时,可以使用-da选项禁用所有断言。通过-ea或-da选项后面指定包的名称,并在后面跟上三个点,可以启用或禁用指定包(及其所有子包)中的断言。例如,为了启用MyPack包中的断言可以使用:
-ea:MyPack...
为了禁用MyPack包中的断言,可以使用:
-da:MyPack...
还可以为-ea或-da选项具体指定某个类。例如,下面的语句专门启用AssertDemo类的断言:
-ea:AssertDemo