考试要点分享——SCJP考点总结(二)
赋值操作符=
基本数据类型的赋值操作
boolean数据只能赋值于另一个boolean数据。不同于c和c++,java中的boolean值只能是true和false。
其它的数据类型可以自由的进行widening conversion(宽化转换)。而进行narrowing conversion(窄化转换)由于有精度的损失,必须进行强制转换。
Primitives may be assigned to "wider" data types, a boolean can only be assigned to another boolean
引用数据类型的赋值操作
引用数据类型的赋值,例如a=b,使得a和b引用指向相同的对象。引用数据类型的赋值可以向上转型。即一超类的引用可以被赋值一子类对象。但向下转型必须强制转换。
Object references can be assigned up the hierarchy from child to base.
示例: 赋值操作符
class SuperClass {
int i;
}
class Child1 extends SuperClass{
int j;
}
class Child2 extends SuperClass{
int k;
}
public class Assignment {
public static void main(String[] args) {
//primitive datatype
boolean b=false;
int i=0;
char c='e';
byte bt=0;
short s=0;
long l=12;
float f =32.0f;
double d=21.0;
// widening conversion
//i=b;
i=c;
i=bt;
i=s;
f=l;
// narrowing conversion
bt=(byte)i;
s=(short)c;
//reference datatype
SuperClass n1 = new Child1();
SuperClass n2 = new SuperClass();
SuperClass n3 = new Child2();
Child1 c1=new Child1();
Child2 c2=new Child2();
n1.i = 9;
n2.i = 47;
System.out.println("1: n1.i: " + n1.i +
", n2.i: " + n2.i);
n1 = n2;
System.out.println("2: n1.i: " + n1.i +
", n2.i: " + n2.i);
n1.i = 27;
System.out.println("3: n1.i: " + n1.i +
", n2.i: " + n2.i);
n1=n3;
n1.i=4;
System.out.println("3: n1.i: " + n1.i +
", n3.i: " + n3.i);
//! c1=(Child1)c2;
}
}
++,--: 前序递增和前序递减(++a,--a)先进行运算然后才增赋值。后序递增和后序递减(a++,a--)先赋值然后才进行运算。
奇怪的++与--:
int a=1,b=5;
b+=b++-a--; (b=9)
b=b++;(b=9)
b=++b; (b=10)
关系操作符
关系操作符(<,>,<=,>=,==,!=)所产生的结果是boolean,==,!=可用于所有的内置的类型,其它运算符不能用于boolean.
奇怪的==(对于引用类型==比较两个对象的内存地址,这可不是我们想要的)
BR> i2.equals(i3); true
i2.equals(d2) ; false
逻辑操作符
逻辑运算符:&&,||,!只能用boolean值身上。位运算符:&,|,^,~:在两个操作数的相应位上进行逻辑代数运算。boolean数据不能~b而用!b。&和|和^即是逻辑的(逻辑与,逻辑或和逻辑非)也是位操作的运算符(按位与,按位或和按位异或)。
奇怪的&&与||(短路的&&和||)
当第一个操作数为flase时,如果用&&运算符时,则不检查第二个操作数就直接返回flase。
当第一个操作数为true时,如果用||运算符时,则不检查第二个操作数就直接返回true。
移位操作符
无符号右移位操作符>>>,它采用零扩展,高位插入0. 左移位运算符<< 能将运算符左边的运算对象向左移动运算符右侧指定的位数在低位补0。有符号右移位运算符>> 则将运算符左边的运算对象向右移动运算符右侧指定的位数,有符号右移位运算符使用了符号扩展。若值为正则在高位插入0,若值为负则在高位插入1。
注:int或<int的数据类型进行位移操作数先要进行mode 32运算所以对大于32的位移运算,先要进行mode 32运算,所以3>>>32的结果为3。Long数据类型在先要进行mode 64运算。
奇怪的>>>:小的负数进行>>>运算将得到一个大的正数
-1>>>1 : 2147483647
A unsigned right shift >>> by a positive amount of a small negative number will result in a large positive number returned.
操作数的优先级
优先级(胃溃疡患者是c程序员的写照 Ulcer Addicats Really Like C A lot)
U: ->Unary 一元运算符 ++, -- ,-,+,!,~
A: -> Arithmetic(shift) *,/,%,+,-,>>,>>>,<<
R: -> Relation >,<,>=,<=,instanceof,==,!=
L: -> Logic(bitwise) &,^,|,&&,||
C: -> 三元运算符
A:-> 赋值 = *=
Objective 2, the equals method
Determine the result of applying the boolean equals(Object) method to objects of any combination of the classes java.lang.String java.lang.Boolean and java.lang.Object.
目标2 equals方法
学会使用对象的equals方法。
equals()和==
equals()用于检测对象的值,即检测对象所引用的内部数据的值。只能用于引用类型。
==用于检测对象引用自身,是否指向同一块内存地址。可用于基本类型,也可用于引用类型。当用于引用类型时,两边的类型必须一至。
默认情况下,equals()和==的返回结果是一样的,是拿references来比较的,但是java标准类库中的大多数类覆盖了equals()。例如:
String类,String对象在代表相同字符串时相等。equals()返回真。
Boolean对象在值相同时相等。equals()返回真。
类型不兼容的两个对象可以用equals(),但是只要比较的对象类型不同(哪怕值相同),永远返回false
一般来说,equals方法认为是一种对象之间的深度比较而==认为是一种浅比较。equals比较引用所指向的内容而不是引用本身。
String的equals方法:
在java中有两种生成String的方法: String s=new String("Hello"); 和 String s="Hello";
这种方法之间的微小区别在于不使用new关键字生成的String将指向String Pool中的相同String.
Boolean的equals方法:
当且仅当lean值相同时,equals方法返回true。
Object的equals方法:
object的equals方法测试的对象是对象的toString()方法所返回的值。而对象的toString()方法仅返回对象的内存地址。所以,它与==操作的功能相同。
注意: StringBuffer类没有覆写equals方法。所以
StringBuffer buf1=new StringBuffer(“123”); StringBuffer buf2=new StringBuffer(“123”);
buf1==buf2: false buf1.equasl(buf2): false
示例: equals方法
public class MyParm{
public static void main(String argv[]){
String s1= "One";
String s2 = "One";
String s3=new String("One");
String s4=new String("One");
MyObj obj1=new MyObj("One");
MyObj obj2=new MyObj("One");
System.out.println("s1: "+ s2+" s2: "+s2+" s3: "+s3+" s4: "+s4);
if(s1.equals(s2)){ //s1,s2指向String Pool的相同String对象返回true
System.out.println("String equals 1");
}
if(s1==s2){ //同上,返回true
System.out.println("String equals 2");
}
if(s1.equals(s3)){ //String覆写了equals方法,返回true
System.out.println("String equals 3");
}
if(s1==s3){ //返回false
System.out.println("String equals 4");
}
if(s3.equals(s4)){ // String覆写equals方法,返回true
System.out.println("String equals 5");
}
if(s3==s4){ //s3,s4是不同的String对象,返回false
System.out.println("String equals 6");
}
boolean b1 = true;
boolean b2 = true;
Boolean b3 = new Boolean(true);
Boolean b4 = new Boolean(true);
if(b3.equals(b4)){ //Boolean覆写了equals方法,返回true
System.out.println("boolean equals 7");
}
if(b3==b4){ //不同的对象,返回false
System.out.println("boolean equals 8");
}
//if(b3==b1){ //不同的数据类型不能==
//System.out.println("String equals 6");
//}
//if(b1.equals(b2)){//基本数据类型没有方法
// System.out.println("true");
//}
if(obj1.equals(obj2)){ //MyObj覆写了equals方法,返回true
System.out.println("Obj equals 9");
}
}
}
class MyObj{
String s;
MyObj(String s){
this.s=s;
}
public String toString(){
return s;
}
public boolean equals(MyObj o){
if(s.equals(o.s)) return true;
else return false;
}
public int hashCode(){
return s.hashCode();
}
}
Objective 3, The & | && and || operators
In an expression involving the operators & | && || and variables of known values state which operands are evaluated and the value of the expression.
目标3 &,|,&&,||操作符
&,|: 位操作符
&&,||: 逻辑操作符
&&,||的短路行为:
当第一个操作数为flase时,如果用&&运算符时,则不检查第二个操作数就直接返回flase。
当第一个操作数为true时,如果用||运算符时,则不检查第二个操作数就直接返回true。
&,|:位操作符用于整数型的基本数据类型。对于boolean数据来说,逻辑与位操作符大约相同,位操作符没有短路行为。
示例:
public class MyClass1{
public static void main(String argv[]){
int Output=10;
boolean b1 = false;
if((b1==true) && ((Output+=10)==20))
{
System.out.println("We are equal "+Output);
}else
{
System.out.println("Not equal! "+Output);
}
if((b1==true) & ((Output+=10)==20))
{
System.out.println("We are equal "+Output);
}else
{
System.out.println("Not equal! "+Output);
}
if((b1==true) || ((Output+=10)==20))
{
System.out.println("We are equal "+Output);
}else
{
System.out.println("Not equal! "+Output);
}
if((b1==false) | ((Output+=10)==20))
{
System.out.println("We are equal "+Output);
}else
{
System.out.println("Not equal! "+Output);
}
}
}
Objective 4, Passing objects and primitives to methods
Determine the effect upon objects and primitive values of passing variables into methods and performing assignments or other modifying operations in that method.
目标4 方法中的参数
方法对传入的参数(对象和基本类型)的影响
当你传入一个值到方法中后将发生什么了?
所有的参数(基本类型的值和对象引用值)都是传值。
基本类型的参数是原数据的一个拷贝,因此,在方法中对它的所有修改在返回后都不可见了。
对象引用型的参数尽管也是原数据引用的一个拷贝,但是由于java中对象是通过对象引用来操作的,因此,在方法中对对象属性的修改,在返回??在返回后则不可见。
示例:
class ValHold{
public int i = 10;
}
public class ObParm{
public static void main(String argv[]){
ObParm o = new ObParm();
o.amethod();
o.othermethod();
}
public void amethod(){
ValHold v = new ValHold(); // v是一个对象引用
System.out.println("Before another ="+v);
v.i=10;
System.out.println("Before another = "+ v.i);
another(v); // v作为方法的参数,方法就具有了对象引用的值,v的对象引用在方法返回后不变
System.out.println("After another = "+ v.i);
System.out.println("After another = "+v);
}//End of amethod
public void another(ValHold v){
System.out.println("In another= "+v);
v.i = 20; //修改对象引用中属性的值
ValHold v1=new ValHold();
v=v1; //修改了对象引用值,由于v是对象引用的一个拷贝,它的改变不影响返回后的对象引用
System.out.println("modified "+v);
System.out.println("In another = "+ v.i);
}//End of another
public void othermethod(){
int i=10;
System.out.println("Before another i= " +i);
another(i);
System.out.println("After another i= " + i);
}
public void another(int i){
i+=10;
System.out.println("In another i= "+i);
}
}
Section 6 Overloading, overriding, runtime type and OO
Objective 1, Encapsulation and OO design
State the benefits of encapsulation in object oriented design and write code that implements tightly encapsulated classes and the relationships "is a" and "has a".
目标1 封装与OO设计
说明封装与OO设计的好处并实现封装类和“is a ”和“has a ”关系
“is a” 和”has a ”关系
“is a ”关系就是继承(inheriatnce)语法。继承的语法:
class BaseClass{}
class DerivedClass extends BaseClass{}
继承类自动获得基类的所有数据成员和成员函数。
“has a”关系就是组合(composition)语法。就是将对象引用置于新的类中。组合的语法:
class CompositedClass{
CompositingClass1 aClass1;
CompositingClass2 aClass2;
….
}
在组合的类之中,属于基本类型的数据,会自动初始化为0,则对象引用被初始化为null,而且如果你试着通过这些引用调用任何函数,会引起异常。
封装
封装是将类的接口与实现公开。接口提供了类的访问方法,其它类只有通过这些访问方法才能访问类的数据。封装的一种方法是将类的成员数据设有私有(private),并通过方法来获得或修改这些数据。
这些方法的标准表示为:
setFileName (setter方法) getFieldName(getter方法)
例如:
private Color fColor;
public void setFColor(Color c){fColor=c;}
public Color getFColor(){return fColor;}
Encapsulation involves hiding data of a class and allowing access only through a public interface.
封装的好处在于对类内部的实现的修改将不影响使用这个类的方法。类的使用者不需要清楚类的内部工作方式只要使用定义好的接口来处理数据。
执行期类型
继承技术中最重要的一个方面是向上转型(Upcasting),就是继承类移至基类。向上转型是安全的,因为这是从专用类型移至通用类型。因此,你不必明确表示转型,编译器仍然允许向上转型。在你面对使用组合或继承时,“我需要向上转型吗”是一个极佳的判断工具。在Java中通过后期绑定(late binding)来实现方法动态调用。后期绑定也可理解为动态绑定或“发送消息给某个对象,让该对象自行找到应该做的事”
向下强制转型:
在Java 中所有强制转型都会自动得到检查和核实。所以即使我们只是进行一次普通的括弧强制转型,进入运行时间以后,仍然会毫不留情地对这个强制转型进行检查保证它的确是我们希望的那种类型。如果不是,就会得到一个ClassCastException(类强制转型违例)。在程序运行时对类型进行检查的行为叫作运行时间类型标识Run-Time Type Identification(RTTI)。
总结: 向上转型是安全的,向下转型必须保证转型类型的一致性。
示例:
class Base {
int i=99;
public void amethod(){
System.out.println("Base.amethod()");
}
Base(){
amethod();
}
int getI(){return i;}
}
class Rt extends Base{
}
public class RType extends Base{
int i=-1;
public static void main(String argv[]){
Base b = new RType();
Base b1=new Base();
RType t=new RType();
//b1=t;
t=(RType)b1; //向下转型错误。b1实际上指向的是Base
t=(RType)b; //向下转型正确。
b.onlyyou(); //错误,Base类不提供onlyyou方法,尽管b实际上是RType
System.out.println(b.i);
System.out.println(b.getI());
b.amethod();
}
public void amethod(){
System.out.println("RType.amethod()");
}
int getI(){return i;}
public void onlyyou(){
}
}
Objective 2, Overriding and overloading
Write code to invoke overridden or overloaded methods and parental or overloaded constructors; and describe the effect of invoking these methods.
重载
如果同一个类中的多个方法有不同的参数列表,那么它们可以具有相同的名称。编译器在区分方法时不考虑返回类型,所以不能声明两个具有相同参数列表但返回类型不同的方法。
覆写与隐藏
如果子类中的一个实例方法与超类的一个实例方法具有相同的标记和返回类型,这被称为子类方法覆写了(override)超类方法。(记住,方法的标记包括它的名称,参数数量和参数类型)。子类覆写方法的能力允许从超类继承行为不同的 throws子句,但没有指定被覆写方法的throws子句没有指定的任何类型。
另外,覆写的方法的访问修饰符不能被被覆写的方法的访问修饰符严格,例如,超类中的保??的。子类不能覆写超类中的final方法。
子类必须覆写超类中声明为abstract的方法,否则子类本身必须是抽象的。
在编写与超类同名的方法时,分清是重载还是覆写,具有相同方法标记的是覆写,具有不同参数数量和类型的的是重载。
如果子类定义的类方法与超类的类方法具有相同的标记,那么子类方法隐藏超类的方法。
实例方法不能覆盖静态方法,而静态方法不能隐藏实例方法。
隐藏成员变量
在类中,如果一个成员变量与超类的成员变量同名(即使类型不同),那么它隐藏超类的成员变量。
在子类中,不能通过简单的名称引用超类的成员变量,而是必须通过super访问它。
示例:类的覆写与重载
class ClassA {
public int value=1;
public void methodOne(int i) {
System.out.println("ClassA methodOne");
}
public void methodTwo(int i) {
System.out.println("ClassA methodTwo");
}
public static void methodThree(int i) {
System.out.println("ClassA methodThree");
}
public static void methodFour(int i) {
System.out.println("ClassA methodFour");
}
int getValue(){return value;}
}
public class ClassB extends ClassA {
public double value=1.0;
public void methodOne(int i) {
System.out.println("ClassB methodOne");
}
public void methodTwo(int i) {
System.out.println("ClassB methodTwo");
}
public static void methodThree(int i) {
System.out.println("ClassA methodThree");
}
public static void methodFour(int i) {
System.out.println("ClassA methodFour");
}
public void methodFive(double value){
this.value=value;
System.out.println("value="+value);
System.out.println("super.value="+super.value);
}
public static void main(String[] args){
ClassA a=new ClassB();
a.methodOne(1);
a.methodThree(1);
ClassB b=(ClassB)a;
b.methodFive(2.0);
}
//double getValue(){return value;}
}
初始化与类的装载
Java的类加载方式:在java中每个类都在专属的.class文件中,这些文件只有在必要时才被装载。也就是说:“类程序代码在初次被使用时才被装载”。所谓的初次被使用,不仅是其第一个对象被建构之时,也可能是在某个static数据成员或static函数被取用时。“初次使用类”的时间点也是静态初始化进行的时机。
示例:关于类加载与初始化过程
class A{
int i;
A(int i){
this.i=i;
System.out.println("A constructor"+i);
}
A(){System.out.println("A constructor");}
static{
int i=7;
System.out.println("i="+i);
}
}
class Insect {
protected static A a=new A();
private int i = 9;
protected int j;
Insect() {
System.out.println("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 =
print("static Insect.x1 initialized");
static int print(String s) {
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = print("Beetle.k initialized");
private A a=new A();
public Beetle() {
System.out.println("k = " + k);
System.out.println("j = " + j);
}
private static int x2 =
print("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle b = new Beetle();
}
}
分析上面程序的初始化过程:
程序的入口在Beetle.main()方法。程序执行时,类装载器被启动,找到Beetle.class并装载,由于Beetle类是Insect类的子类所以类装载器也将Insect.class装载。如果基类还要基类,这个过程将继续。
类加载完成后,将进行静态初始化过程,首先从根基类(Insect)开始,然后是其子类(Bettle),依次类推。在本例中,执行Insect 类中protected static A a=new A();类加载器装载A.class并进行静态初始化过程:A类中的static块,然后构造函数。然后,private static int x1 = print("static Insect.x1 initialized");最后是Beetle类中的private static int x2 = print("static Beetle.x2 initialized");
静态初始化完成后,将进行类实例的生成。首先,对象内的所有基本类型都会被评为设予缺省值,对象引用则被设予null.然后,基类的构造函数被唤起,缺省条件下基类的缺省构造函数被自动唤起,但你可用super(第一个运作)来调用基类的其它构造函数。执行Insect类的缺省函数。
基类构造完成后,是实例变量按次序被初始化,最后,才执行构造本体的剩余部分。
final类, 方法,变量
final变量是固定不变的数据。它可以:
可以是永不改变的编译期常量(compile-time constant)
可以是执行期(runtime)被初始化,而你不想再改变它。
final的对象引用的意义是它让引用保持不变而不是对象本身。某个引用一旦被初始化后,便再也不能指向另一个对象,但此对象本身的内容是可以改变的。
Java允许产生所谓的”blank finals”,也就是允许我们将数据成员声明为final,却不给予初值。注意:在任何情况下,blank finals必须在使用之间进行初始化, 而且编译器保证此事。所以,final的赋值运行如果不是发生在其定义处,就得在构造函数中以表达式设定其值。
final参数
?
final方法
final方法的原因: 第一:锁住这个函数,使子类不能改意其意义,即无法覆写;第二:效率。
类中的?在子类中隐藏,final函数却不能。
final类
final类就是说此类不能被继承。final类中的所有方法是final的但数据成员可是fianl也可不是fianl.
示? class Value {
int i; // Package access
public Value(int i) { this.i = i; }
private final void aMethod(){ //final??can be omitted
System.out.println("In Value.aMethod");
}
final void anotherMehtod(){
System.out.println("In Value.anotherMethod");
}
}
class SuperValue extends Value{
SuperValue(){super(1);}
void aMethod(){System.out.println("In SuperValue");}
//void anotherMethod(){} //final??method can not be changed
}
public class FinalData {
private static Random rand = new Random();
private String id;
public FinalData(String id,int j) { this.id = id; VAL_ONE=1; }
// Can be compile-time constants:
private final int VAL_ONE ; //bland final
private static final int VAL_TWO ;
static{
VAL_TWO=4;
}
// Typical public constant:
public static final int VAL_THREE = 39;
// Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);
static final int i5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value v3 = new Value(33);
// Arrays:
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() {
return id + ": " + "i4 = " + i4 + ", i5 = " + i5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1",9);
//! fd1.VAL_ONE++; // Error: can't change value
fd1.v2.i++; // Object isn't constant!
fd1.v1 = new Value(9); // OK -- not final
for(int i = 0; i < fd1.a.length; i++)
fd1.a++; // Object isn't constant!
//! fd1.v2 = new Value(0); // Error: Can't
//! fd1.v3 = new Value(1); // change reference
//! fd1.a = new int[3];
System.out.println(fd1);
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData("fd2",8);
System.out.println(fd1);
System.out.println(fd2);
Value v1=new SuperValue();
SuperValue v2=new SuperValue();
v2.aMethod();
//!v1.aMethod();
}
} ///:~
Objective 3, Creating class instances
Write code to construct instances of any concrete class including normal top level classes inner classes static inner classes and anonymous inner classes.
抽象类与抽象函数
抽象类是不能实例化的类,它定义了一组继承类的通用接口,继承类中所有的与抽象类标记相同的方法,会通过动态绑定的机制来调用。
抽象函数是一种不完全的函数,只有声明而无本体。具有抽象函数的类必须声明为abstract。
如果你要继承一个抽象类,你得实现抽象类中所有的抽象方法。否则,你的继承类也得声明为abstract.
不含任向abstract函数的类也可声明为抽象。
嵌套类(nested class)
嵌套类是另一个类的成员,反映了两个类之间的关系。当嵌套类只在包含它的类中有意义,或者它依赖于包含它的类来实现功能时,应该在另一个类中定义这个类。
嵌套类的特权: 它可以限制的访问包含它的类的成员,即使这些成员是私有的,嵌套类也可以访问它。
嵌套类可分为:静态嵌套类也称为(nested class)。非静态嵌套类也称为(inner class)
inner class
inner class不能具有任何static类型的data,fields,inner class.
匿名的inner类
只有用于实现事件接口。匿名inner类不能拥有构造函数。同时,匿名类一般与位于函数或Scopes内的类,所以它只接收外部的final数据。对匿名类的数据成员的初始化:一是在定义处,二是使用实体初始化(instance initialization).
示例: 匿名inner类
Section 7 Threads
Objective 1, Instantiating and starting threads
Write code to define, instantiate and start new threads using both java.lang.Thread and java.lang.Runnable
目标1 使用Thread类或Runnable接口定义,实例化和启动线程
什么是线程
线程是轻量级的进程,它能在主程序中并发执行。不同于进程,线程与进程中其它的线程共享内存与数据。
在JSCP考试中你要清楚一个概念:当程序启动了一个线程后,程序就有了多个执行路径。例如:线程A在线程B之前启动,这并不意味A必然先B执行完毕。程序的输出可能与底层的操作系统或系统中的已运行程序有关。
定义线程的两种方式
方式一:类实现Runnable接口
Runnable接口的定义:
public Interface Runnable{
public void run();
}
定义线程:
class MyClass implements Runnable{
public void run(){//Blank Body}
}
实例线程:
MyClass mc = new MyClass();
MyClass mc2 = new MyClass();
Thread t = new Thread(mc);
Thread t2 = new Thread(mc2);
t.start();
t2.start();
注意:实例实现Runnable接口线程,需要先实例Thread并将Runnable类作为构造器参数传递给Thread构造器。Thread的构造器方法之一:
Thread(Runnable target) 或者Thread(Runnable target, String name)
方式二: 类继承在创建的 Thread 类的子类中重写 run() ,加入线程所要执行的代码即可。注意: 这种方式直接明了但这也意味由于java只支持单一继承,所以你的子类不能再继承其它的类了。
定义线程
public class MyThread extends Thread {
public void run(){}
}
实例线程
MyThread t=new MyThread();
t.start();
实例化和启动线程
尽管线程的运行时做的事由run方法提供,但启动线程不是直接调用run方法,而是调用start方法。start方法分配运行线程所需的资源,调度线程运行,并调用线程的run方法。注意:你可以直接调用run方法,这时,run方法仅视为普通的方法而不是线程的一部分。
Although it is the run method code that executes, a thread is actually started via the start method.
示例:
public class Runt extends Thread{
public Runt(String name){
super(name);
}
public static void main(String argv[]){
for(int i=0;i<3;i++){
Runt r = new Runt(""+i);
r.run();
}
System.out.println("Begin To run concurrently");
for(int i=0;i<3;i++){
Runt r = new Runt(""+i);
r.start();
}
}
public void run(){
for(int i=0;i<10;i++)
System.out.println("# Thread-"+getName()+"-"+i);
}
}
Objective 2, When threads are prevented from executing
Recognise conditions that might prevent a thread from executing.
目标2 线程停止的原因
明确线程阻塞的条件
线程的四种状态
1. 新状态(New Thread):线程已被创建但尚未执行(start() 尚未被调用)。
2. 可执行状态(Runnable):线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。
3. 死亡状态(Dead):正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。
4. 阻塞状态(Not Runnable):线程不会被分配 CPU 时间,无法执行。
线程阻塞的原因(Runnable->Not Runnable)
线程进入睡眠状态,那么必须经过指定的时间后线程返回可运行状态
线程由于调用wait而处于暂停状态,另一个对象调用了notfiy或notifyAll通知等待线程
线程由于IO操作而阻塞,IO完成后线程返回可运行状态
线程由于调用suspend()而阻塞,接到resume()后返回可运行状态(Deprecated)
sleep和wait/notify是引起阻塞的主要原因。
public static void sleep(long millis) throws InterruptedException
sleep方法是一个静态方法,它以以毫秒为单位的一段时间作为参数,使线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。注意:sleep方法抛出异常,在调用它时要捕捉这个异常。
线程的yield()方法(Running->Runnable)
public static void yield();
线程的yield方法使当前线程放弃对CPU资源的占用,线程进入Runable状态。由线程调度算法来选择其它Runnable线程runing.
操作系统的调度系统
时间片(Time Slicing)/抢占式(preemptive)
时间片系统将CPU分割为很短的时间段,并给每个具有相等而且最高优先级的线程分配时间段以供运行。时间片系统遍历具有相等且最高优先级的线程,允许它们各自运行一小段时间,直到其中一个或多个完成执行或一个具有更高优先级的线程抢占它们。Windows系统采用了抢占式调度。
非时间片(Non Time Slicing)/合作式(Cooperative)
由优先系统决定线程的运行,最高优先级的线程获得CPU时间。在这种机制下,程序要通过某种方式自动的放弃CPU时间。
示例:
public class SelfishRunner extends Thread {
private int tick = 1;
private int num;
public SelfishRunner(int num) {
this.num = num;
}
public void run() {
while (tick < 400000) {
tick++;
if ((tick % 50000) == 0){
System.out.println("Thread #" + num + ", tick = " + tick);
//yield(); //!自私的线程与公平的线程
}
}
}
}
public class RaceDemo {
private final static int NUMRUNNERS = 2;
public static void main(String[] args) {
SelfishRunner[] runners = new SelfishRunner[NUMRUNNERS];
for (int i = 0; i < NUMRUNNERS; i++) {
runners = new SelfishRunner(i);
runners.setPriority(2);
}
for (int i = 0; i < NUMRUNNERS; i++)
runners.start();
}
}
Objective 3, The wait/notify protocol
Write code using synchronized wait notify and notifyAll to protect against concurrent access problems and to communicate between threads. Define the interaction between threads and between threads and object locks when executing synchronized wait notify or notifyAll.
由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。这出就是线程同步的问题。
synchronized关键字
synchronzied关键字用于标识临界区(critical section)。临界区是单独的并发线程可访问期间的相同对象的代码段。它可以是一段代码或方法。名词Monitor,mutex(mutually exclusive lock)是指实现同步所需要的lock. Java平台将一个锁与每个具有同步代码的对象关联起来。当某个线程进入同步方法时,线程就锁定了此方法的对象。在对象被解锁前,其它线程不能调用??不是基于方法的。同步的方法的实现:
synchronized void amethod() { /* method body */}
或
synchronized (ObjectReference) { /* Block body */ }
When a synchronized block is executed, its object is locked and it cannot be called by any other code until the lock is freed.
wait/notify/notifyAll
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout,int nanos) throws InterruptedException
public final void notify()
public final void notifyAll()
为了获得和释放lock,每个对象都能pause或wait,直到其它对象将锁转效到它。Wait/notify是线程间通信的一种方式。由于它们是Object类所具有的方法,所以在JAVA中所有的类都具有了线程通信的能力。调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。如果没有notify方法,wait方法将毫无意义。因为它通知那些由于wait而阻塞的线程使它们能继续运行。
wait and notify should be placed within synchronized code to ensure that the current code owns the monitor
方法的使用
while(true){
try{
wait();
}catch (InterruptedException e) {}
}
//some producing action goes here
notifyAll();
注:notifyAll方法唤醒所有等待相关对象的线程。被唤醒的线程序争夺锁。一个线程得到锁,其它线程继续等待。notify方法,它任意唤醒等待此对象的线程之一。
示例:生产/消费者问题
public class CubbyHole {
private int contents;
private boolean available = false;
public synchronized int get() {
while (available == false) {
try {
wait();
} catch (InterruptedException e) { }
}
available = false;
notifyAll();
return contents;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait();
} catch (InterruptedException e) { }
}
contents = value;
available = true;
notifyAll();
}
}
生产者
public class Producer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
cubbyhole.put(i);
System.out.println("Producer #" + this.number
+ " put: " + i);
try {
sleep((int)(Math.random() * 100));
基本数据类型的赋值操作
boolean数据只能赋值于另一个boolean数据。不同于c和c++,java中的boolean值只能是true和false。
其它的数据类型可以自由的进行widening conversion(宽化转换)。而进行narrowing conversion(窄化转换)由于有精度的损失,必须进行强制转换。
Primitives may be assigned to "wider" data types, a boolean can only be assigned to another boolean
引用数据类型的赋值操作
引用数据类型的赋值,例如a=b,使得a和b引用指向相同的对象。引用数据类型的赋值可以向上转型。即一超类的引用可以被赋值一子类对象。但向下转型必须强制转换。
Object references can be assigned up the hierarchy from child to base.
示例: 赋值操作符
class SuperClass {
int i;
}
class Child1 extends SuperClass{
int j;
}
class Child2 extends SuperClass{
int k;
}
public class Assignment {
public static void main(String[] args) {
//primitive datatype
boolean b=false;
int i=0;
char c='e';
byte bt=0;
short s=0;
long l=12;
float f =32.0f;
double d=21.0;
// widening conversion
//i=b;
i=c;
i=bt;
i=s;
f=l;
// narrowing conversion
bt=(byte)i;
s=(short)c;
//reference datatype
SuperClass n1 = new Child1();
SuperClass n2 = new SuperClass();
SuperClass n3 = new Child2();
Child1 c1=new Child1();
Child2 c2=new Child2();
n1.i = 9;
n2.i = 47;
System.out.println("1: n1.i: " + n1.i +
", n2.i: " + n2.i);
n1 = n2;
System.out.println("2: n1.i: " + n1.i +
", n2.i: " + n2.i);
n1.i = 27;
System.out.println("3: n1.i: " + n1.i +
", n2.i: " + n2.i);
n1=n3;
n1.i=4;
System.out.println("3: n1.i: " + n1.i +
", n3.i: " + n3.i);
//! c1=(Child1)c2;
}
}
++,--: 前序递增和前序递减(++a,--a)先进行运算然后才增赋值。后序递增和后序递减(a++,a--)先赋值然后才进行运算。
奇怪的++与--:
int a=1,b=5;
b+=b++-a--; (b=9)
b=b++;(b=9)
b=++b; (b=10)
关系操作符
关系操作符(<,>,<=,>=,==,!=)所产生的结果是boolean,==,!=可用于所有的内置的类型,其它运算符不能用于boolean.
奇怪的==(对于引用类型==比较两个对象的内存地址,这可不是我们想要的)
BR> i2.equals(i3); true
i2.equals(d2) ; false
逻辑操作符
逻辑运算符:&&,||,!只能用boolean值身上。位运算符:&,|,^,~:在两个操作数的相应位上进行逻辑代数运算。boolean数据不能~b而用!b。&和|和^即是逻辑的(逻辑与,逻辑或和逻辑非)也是位操作的运算符(按位与,按位或和按位异或)。
奇怪的&&与||(短路的&&和||)
当第一个操作数为flase时,如果用&&运算符时,则不检查第二个操作数就直接返回flase。
当第一个操作数为true时,如果用||运算符时,则不检查第二个操作数就直接返回true。
移位操作符
无符号右移位操作符>>>,它采用零扩展,高位插入0. 左移位运算符<< 能将运算符左边的运算对象向左移动运算符右侧指定的位数在低位补0。有符号右移位运算符>> 则将运算符左边的运算对象向右移动运算符右侧指定的位数,有符号右移位运算符使用了符号扩展。若值为正则在高位插入0,若值为负则在高位插入1。
注:int或<int的数据类型进行位移操作数先要进行mode 32运算所以对大于32的位移运算,先要进行mode 32运算,所以3>>>32的结果为3。Long数据类型在先要进行mode 64运算。
奇怪的>>>:小的负数进行>>>运算将得到一个大的正数
-1>>>1 : 2147483647
A unsigned right shift >>> by a positive amount of a small negative number will result in a large positive number returned.
操作数的优先级
优先级(胃溃疡患者是c程序员的写照 Ulcer Addicats Really Like C A lot)
U: ->Unary 一元运算符 ++, -- ,-,+,!,~
A: -> Arithmetic(shift) *,/,%,+,-,>>,>>>,<<
R: -> Relation >,<,>=,<=,instanceof,==,!=
L: -> Logic(bitwise) &,^,|,&&,||
C: -> 三元运算符
A:-> 赋值 = *=
Objective 2, the equals method
Determine the result of applying the boolean equals(Object) method to objects of any combination of the classes java.lang.String java.lang.Boolean and java.lang.Object.
目标2 equals方法
学会使用对象的equals方法。
equals()和==
equals()用于检测对象的值,即检测对象所引用的内部数据的值。只能用于引用类型。
==用于检测对象引用自身,是否指向同一块内存地址。可用于基本类型,也可用于引用类型。当用于引用类型时,两边的类型必须一至。
默认情况下,equals()和==的返回结果是一样的,是拿references来比较的,但是java标准类库中的大多数类覆盖了equals()。例如:
String类,String对象在代表相同字符串时相等。equals()返回真。
Boolean对象在值相同时相等。equals()返回真。
类型不兼容的两个对象可以用equals(),但是只要比较的对象类型不同(哪怕值相同),永远返回false
一般来说,equals方法认为是一种对象之间的深度比较而==认为是一种浅比较。equals比较引用所指向的内容而不是引用本身。
String的equals方法:
在java中有两种生成String的方法: String s=new String("Hello"); 和 String s="Hello";
这种方法之间的微小区别在于不使用new关键字生成的String将指向String Pool中的相同String.
Boolean的equals方法:
当且仅当lean值相同时,equals方法返回true。
Object的equals方法:
object的equals方法测试的对象是对象的toString()方法所返回的值。而对象的toString()方法仅返回对象的内存地址。所以,它与==操作的功能相同。
注意: StringBuffer类没有覆写equals方法。所以
StringBuffer buf1=new StringBuffer(“123”); StringBuffer buf2=new StringBuffer(“123”);
buf1==buf2: false buf1.equasl(buf2): false
示例: equals方法
public class MyParm{
public static void main(String argv[]){
String s1= "One";
String s2 = "One";
String s3=new String("One");
String s4=new String("One");
MyObj obj1=new MyObj("One");
MyObj obj2=new MyObj("One");
System.out.println("s1: "+ s2+" s2: "+s2+" s3: "+s3+" s4: "+s4);
if(s1.equals(s2)){ //s1,s2指向String Pool的相同String对象返回true
System.out.println("String equals 1");
}
if(s1==s2){ //同上,返回true
System.out.println("String equals 2");
}
if(s1.equals(s3)){ //String覆写了equals方法,返回true
System.out.println("String equals 3");
}
if(s1==s3){ //返回false
System.out.println("String equals 4");
}
if(s3.equals(s4)){ // String覆写equals方法,返回true
System.out.println("String equals 5");
}
if(s3==s4){ //s3,s4是不同的String对象,返回false
System.out.println("String equals 6");
}
boolean b1 = true;
boolean b2 = true;
Boolean b3 = new Boolean(true);
Boolean b4 = new Boolean(true);
if(b3.equals(b4)){ //Boolean覆写了equals方法,返回true
System.out.println("boolean equals 7");
}
if(b3==b4){ //不同的对象,返回false
System.out.println("boolean equals 8");
}
//if(b3==b1){ //不同的数据类型不能==
//System.out.println("String equals 6");
//}
//if(b1.equals(b2)){//基本数据类型没有方法
// System.out.println("true");
//}
if(obj1.equals(obj2)){ //MyObj覆写了equals方法,返回true
System.out.println("Obj equals 9");
}
}
}
class MyObj{
String s;
MyObj(String s){
this.s=s;
}
public String toString(){
return s;
}
public boolean equals(MyObj o){
if(s.equals(o.s)) return true;
else return false;
}
public int hashCode(){
return s.hashCode();
}
}
Objective 3, The & | && and || operators
In an expression involving the operators & | && || and variables of known values state which operands are evaluated and the value of the expression.
目标3 &,|,&&,||操作符
&,|: 位操作符
&&,||: 逻辑操作符
&&,||的短路行为:
当第一个操作数为flase时,如果用&&运算符时,则不检查第二个操作数就直接返回flase。
当第一个操作数为true时,如果用||运算符时,则不检查第二个操作数就直接返回true。
&,|:位操作符用于整数型的基本数据类型。对于boolean数据来说,逻辑与位操作符大约相同,位操作符没有短路行为。
示例:
public class MyClass1{
public static void main(String argv[]){
int Output=10;
boolean b1 = false;
if((b1==true) && ((Output+=10)==20))
{
System.out.println("We are equal "+Output);
}else
{
System.out.println("Not equal! "+Output);
}
if((b1==true) & ((Output+=10)==20))
{
System.out.println("We are equal "+Output);
}else
{
System.out.println("Not equal! "+Output);
}
if((b1==true) || ((Output+=10)==20))
{
System.out.println("We are equal "+Output);
}else
{
System.out.println("Not equal! "+Output);
}
if((b1==false) | ((Output+=10)==20))
{
System.out.println("We are equal "+Output);
}else
{
System.out.println("Not equal! "+Output);
}
}
}
Objective 4, Passing objects and primitives to methods
Determine the effect upon objects and primitive values of passing variables into methods and performing assignments or other modifying operations in that method.
目标4 方法中的参数
方法对传入的参数(对象和基本类型)的影响
当你传入一个值到方法中后将发生什么了?
所有的参数(基本类型的值和对象引用值)都是传值。
基本类型的参数是原数据的一个拷贝,因此,在方法中对它的所有修改在返回后都不可见了。
对象引用型的参数尽管也是原数据引用的一个拷贝,但是由于java中对象是通过对象引用来操作的,因此,在方法中对对象属性的修改,在返回??在返回后则不可见。
示例:
class ValHold{
public int i = 10;
}
public class ObParm{
public static void main(String argv[]){
ObParm o = new ObParm();
o.amethod();
o.othermethod();
}
public void amethod(){
ValHold v = new ValHold(); // v是一个对象引用
System.out.println("Before another ="+v);
v.i=10;
System.out.println("Before another = "+ v.i);
another(v); // v作为方法的参数,方法就具有了对象引用的值,v的对象引用在方法返回后不变
System.out.println("After another = "+ v.i);
System.out.println("After another = "+v);
}//End of amethod
public void another(ValHold v){
System.out.println("In another= "+v);
v.i = 20; //修改对象引用中属性的值
ValHold v1=new ValHold();
v=v1; //修改了对象引用值,由于v是对象引用的一个拷贝,它的改变不影响返回后的对象引用
System.out.println("modified "+v);
System.out.println("In another = "+ v.i);
}//End of another
public void othermethod(){
int i=10;
System.out.println("Before another i= " +i);
another(i);
System.out.println("After another i= " + i);
}
public void another(int i){
i+=10;
System.out.println("In another i= "+i);
}
}
Section 6 Overloading, overriding, runtime type and OO
Objective 1, Encapsulation and OO design
State the benefits of encapsulation in object oriented design and write code that implements tightly encapsulated classes and the relationships "is a" and "has a".
目标1 封装与OO设计
说明封装与OO设计的好处并实现封装类和“is a ”和“has a ”关系
“is a” 和”has a ”关系
“is a ”关系就是继承(inheriatnce)语法。继承的语法:
class BaseClass{}
class DerivedClass extends BaseClass{}
继承类自动获得基类的所有数据成员和成员函数。
“has a”关系就是组合(composition)语法。就是将对象引用置于新的类中。组合的语法:
class CompositedClass{
CompositingClass1 aClass1;
CompositingClass2 aClass2;
….
}
在组合的类之中,属于基本类型的数据,会自动初始化为0,则对象引用被初始化为null,而且如果你试着通过这些引用调用任何函数,会引起异常。
封装
封装是将类的接口与实现公开。接口提供了类的访问方法,其它类只有通过这些访问方法才能访问类的数据。封装的一种方法是将类的成员数据设有私有(private),并通过方法来获得或修改这些数据。
这些方法的标准表示为:
setFileName (setter方法) getFieldName(getter方法)
例如:
private Color fColor;
public void setFColor(Color c){fColor=c;}
public Color getFColor(){return fColor;}
Encapsulation involves hiding data of a class and allowing access only through a public interface.
封装的好处在于对类内部的实现的修改将不影响使用这个类的方法。类的使用者不需要清楚类的内部工作方式只要使用定义好的接口来处理数据。
执行期类型
继承技术中最重要的一个方面是向上转型(Upcasting),就是继承类移至基类。向上转型是安全的,因为这是从专用类型移至通用类型。因此,你不必明确表示转型,编译器仍然允许向上转型。在你面对使用组合或继承时,“我需要向上转型吗”是一个极佳的判断工具。在Java中通过后期绑定(late binding)来实现方法动态调用。后期绑定也可理解为动态绑定或“发送消息给某个对象,让该对象自行找到应该做的事”
向下强制转型:
在Java 中所有强制转型都会自动得到检查和核实。所以即使我们只是进行一次普通的括弧强制转型,进入运行时间以后,仍然会毫不留情地对这个强制转型进行检查保证它的确是我们希望的那种类型。如果不是,就会得到一个ClassCastException(类强制转型违例)。在程序运行时对类型进行检查的行为叫作运行时间类型标识Run-Time Type Identification(RTTI)。
总结: 向上转型是安全的,向下转型必须保证转型类型的一致性。
示例:
class Base {
int i=99;
public void amethod(){
System.out.println("Base.amethod()");
}
Base(){
amethod();
}
int getI(){return i;}
}
class Rt extends Base{
}
public class RType extends Base{
int i=-1;
public static void main(String argv[]){
Base b = new RType();
Base b1=new Base();
RType t=new RType();
//b1=t;
t=(RType)b1; //向下转型错误。b1实际上指向的是Base
t=(RType)b; //向下转型正确。
b.onlyyou(); //错误,Base类不提供onlyyou方法,尽管b实际上是RType
System.out.println(b.i);
System.out.println(b.getI());
b.amethod();
}
public void amethod(){
System.out.println("RType.amethod()");
}
int getI(){return i;}
public void onlyyou(){
}
}
Objective 2, Overriding and overloading
Write code to invoke overridden or overloaded methods and parental or overloaded constructors; and describe the effect of invoking these methods.
重载
如果同一个类中的多个方法有不同的参数列表,那么它们可以具有相同的名称。编译器在区分方法时不考虑返回类型,所以不能声明两个具有相同参数列表但返回类型不同的方法。
覆写与隐藏
如果子类中的一个实例方法与超类的一个实例方法具有相同的标记和返回类型,这被称为子类方法覆写了(override)超类方法。(记住,方法的标记包括它的名称,参数数量和参数类型)。子类覆写方法的能力允许从超类继承行为不同的 throws子句,但没有指定被覆写方法的throws子句没有指定的任何类型。
另外,覆写的方法的访问修饰符不能被被覆写的方法的访问修饰符严格,例如,超类中的保??的。子类不能覆写超类中的final方法。
子类必须覆写超类中声明为abstract的方法,否则子类本身必须是抽象的。
在编写与超类同名的方法时,分清是重载还是覆写,具有相同方法标记的是覆写,具有不同参数数量和类型的的是重载。
如果子类定义的类方法与超类的类方法具有相同的标记,那么子类方法隐藏超类的方法。
实例方法不能覆盖静态方法,而静态方法不能隐藏实例方法。
隐藏成员变量
在类中,如果一个成员变量与超类的成员变量同名(即使类型不同),那么它隐藏超类的成员变量。
在子类中,不能通过简单的名称引用超类的成员变量,而是必须通过super访问它。
示例:类的覆写与重载
class ClassA {
public int value=1;
public void methodOne(int i) {
System.out.println("ClassA methodOne");
}
public void methodTwo(int i) {
System.out.println("ClassA methodTwo");
}
public static void methodThree(int i) {
System.out.println("ClassA methodThree");
}
public static void methodFour(int i) {
System.out.println("ClassA methodFour");
}
int getValue(){return value;}
}
public class ClassB extends ClassA {
public double value=1.0;
public void methodOne(int i) {
System.out.println("ClassB methodOne");
}
public void methodTwo(int i) {
System.out.println("ClassB methodTwo");
}
public static void methodThree(int i) {
System.out.println("ClassA methodThree");
}
public static void methodFour(int i) {
System.out.println("ClassA methodFour");
}
public void methodFive(double value){
this.value=value;
System.out.println("value="+value);
System.out.println("super.value="+super.value);
}
public static void main(String[] args){
ClassA a=new ClassB();
a.methodOne(1);
a.methodThree(1);
ClassB b=(ClassB)a;
b.methodFive(2.0);
}
//double getValue(){return value;}
}
初始化与类的装载
Java的类加载方式:在java中每个类都在专属的.class文件中,这些文件只有在必要时才被装载。也就是说:“类程序代码在初次被使用时才被装载”。所谓的初次被使用,不仅是其第一个对象被建构之时,也可能是在某个static数据成员或static函数被取用时。“初次使用类”的时间点也是静态初始化进行的时机。
示例:关于类加载与初始化过程
class A{
int i;
A(int i){
this.i=i;
System.out.println("A constructor"+i);
}
A(){System.out.println("A constructor");}
static{
int i=7;
System.out.println("i="+i);
}
}
class Insect {
protected static A a=new A();
private int i = 9;
protected int j;
Insect() {
System.out.println("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 =
print("static Insect.x1 initialized");
static int print(String s) {
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = print("Beetle.k initialized");
private A a=new A();
public Beetle() {
System.out.println("k = " + k);
System.out.println("j = " + j);
}
private static int x2 =
print("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle b = new Beetle();
}
}
分析上面程序的初始化过程:
程序的入口在Beetle.main()方法。程序执行时,类装载器被启动,找到Beetle.class并装载,由于Beetle类是Insect类的子类所以类装载器也将Insect.class装载。如果基类还要基类,这个过程将继续。
类加载完成后,将进行静态初始化过程,首先从根基类(Insect)开始,然后是其子类(Bettle),依次类推。在本例中,执行Insect 类中protected static A a=new A();类加载器装载A.class并进行静态初始化过程:A类中的static块,然后构造函数。然后,private static int x1 = print("static Insect.x1 initialized");最后是Beetle类中的private static int x2 = print("static Beetle.x2 initialized");
静态初始化完成后,将进行类实例的生成。首先,对象内的所有基本类型都会被评为设予缺省值,对象引用则被设予null.然后,基类的构造函数被唤起,缺省条件下基类的缺省构造函数被自动唤起,但你可用super(第一个运作)来调用基类的其它构造函数。执行Insect类的缺省函数。
基类构造完成后,是实例变量按次序被初始化,最后,才执行构造本体的剩余部分。
final类, 方法,变量
final变量是固定不变的数据。它可以:
可以是永不改变的编译期常量(compile-time constant)
可以是执行期(runtime)被初始化,而你不想再改变它。
final的对象引用的意义是它让引用保持不变而不是对象本身。某个引用一旦被初始化后,便再也不能指向另一个对象,但此对象本身的内容是可以改变的。
Java允许产生所谓的”blank finals”,也就是允许我们将数据成员声明为final,却不给予初值。注意:在任何情况下,blank finals必须在使用之间进行初始化, 而且编译器保证此事。所以,final的赋值运行如果不是发生在其定义处,就得在构造函数中以表达式设定其值。
final参数
?
final方法
final方法的原因: 第一:锁住这个函数,使子类不能改意其意义,即无法覆写;第二:效率。
类中的?在子类中隐藏,final函数却不能。
final类
final类就是说此类不能被继承。final类中的所有方法是final的但数据成员可是fianl也可不是fianl.
示? class Value {
int i; // Package access
public Value(int i) { this.i = i; }
private final void aMethod(){ //final??can be omitted
System.out.println("In Value.aMethod");
}
final void anotherMehtod(){
System.out.println("In Value.anotherMethod");
}
}
class SuperValue extends Value{
SuperValue(){super(1);}
void aMethod(){System.out.println("In SuperValue");}
//void anotherMethod(){} //final??method can not be changed
}
public class FinalData {
private static Random rand = new Random();
private String id;
public FinalData(String id,int j) { this.id = id; VAL_ONE=1; }
// Can be compile-time constants:
private final int VAL_ONE ; //bland final
private static final int VAL_TWO ;
static{
VAL_TWO=4;
}
// Typical public constant:
public static final int VAL_THREE = 39;
// Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);
static final int i5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value v3 = new Value(33);
// Arrays:
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() {
return id + ": " + "i4 = " + i4 + ", i5 = " + i5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1",9);
//! fd1.VAL_ONE++; // Error: can't change value
fd1.v2.i++; // Object isn't constant!
fd1.v1 = new Value(9); // OK -- not final
for(int i = 0; i < fd1.a.length; i++)
fd1.a++; // Object isn't constant!
//! fd1.v2 = new Value(0); // Error: Can't
//! fd1.v3 = new Value(1); // change reference
//! fd1.a = new int[3];
System.out.println(fd1);
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData("fd2",8);
System.out.println(fd1);
System.out.println(fd2);
Value v1=new SuperValue();
SuperValue v2=new SuperValue();
v2.aMethod();
//!v1.aMethod();
}
} ///:~
Objective 3, Creating class instances
Write code to construct instances of any concrete class including normal top level classes inner classes static inner classes and anonymous inner classes.
抽象类与抽象函数
抽象类是不能实例化的类,它定义了一组继承类的通用接口,继承类中所有的与抽象类标记相同的方法,会通过动态绑定的机制来调用。
抽象函数是一种不完全的函数,只有声明而无本体。具有抽象函数的类必须声明为abstract。
如果你要继承一个抽象类,你得实现抽象类中所有的抽象方法。否则,你的继承类也得声明为abstract.
不含任向abstract函数的类也可声明为抽象。
嵌套类(nested class)
嵌套类是另一个类的成员,反映了两个类之间的关系。当嵌套类只在包含它的类中有意义,或者它依赖于包含它的类来实现功能时,应该在另一个类中定义这个类。
嵌套类的特权: 它可以限制的访问包含它的类的成员,即使这些成员是私有的,嵌套类也可以访问它。
嵌套类可分为:静态嵌套类也称为(nested class)。非静态嵌套类也称为(inner class)
inner class
inner class不能具有任何static类型的data,fields,inner class.
匿名的inner类
只有用于实现事件接口。匿名inner类不能拥有构造函数。同时,匿名类一般与位于函数或Scopes内的类,所以它只接收外部的final数据。对匿名类的数据成员的初始化:一是在定义处,二是使用实体初始化(instance initialization).
示例: 匿名inner类
Section 7 Threads
Objective 1, Instantiating and starting threads
Write code to define, instantiate and start new threads using both java.lang.Thread and java.lang.Runnable
目标1 使用Thread类或Runnable接口定义,实例化和启动线程
什么是线程
线程是轻量级的进程,它能在主程序中并发执行。不同于进程,线程与进程中其它的线程共享内存与数据。
在JSCP考试中你要清楚一个概念:当程序启动了一个线程后,程序就有了多个执行路径。例如:线程A在线程B之前启动,这并不意味A必然先B执行完毕。程序的输出可能与底层的操作系统或系统中的已运行程序有关。
定义线程的两种方式
方式一:类实现Runnable接口
Runnable接口的定义:
public Interface Runnable{
public void run();
}
定义线程:
class MyClass implements Runnable{
public void run(){//Blank Body}
}
实例线程:
MyClass mc = new MyClass();
MyClass mc2 = new MyClass();
Thread t = new Thread(mc);
Thread t2 = new Thread(mc2);
t.start();
t2.start();
注意:实例实现Runnable接口线程,需要先实例Thread并将Runnable类作为构造器参数传递给Thread构造器。Thread的构造器方法之一:
Thread(Runnable target) 或者Thread(Runnable target, String name)
方式二: 类继承在创建的 Thread 类的子类中重写 run() ,加入线程所要执行的代码即可。注意: 这种方式直接明了但这也意味由于java只支持单一继承,所以你的子类不能再继承其它的类了。
定义线程
public class MyThread extends Thread {
public void run(){}
}
实例线程
MyThread t=new MyThread();
t.start();
实例化和启动线程
尽管线程的运行时做的事由run方法提供,但启动线程不是直接调用run方法,而是调用start方法。start方法分配运行线程所需的资源,调度线程运行,并调用线程的run方法。注意:你可以直接调用run方法,这时,run方法仅视为普通的方法而不是线程的一部分。
Although it is the run method code that executes, a thread is actually started via the start method.
示例:
public class Runt extends Thread{
public Runt(String name){
super(name);
}
public static void main(String argv[]){
for(int i=0;i<3;i++){
Runt r = new Runt(""+i);
r.run();
}
System.out.println("Begin To run concurrently");
for(int i=0;i<3;i++){
Runt r = new Runt(""+i);
r.start();
}
}
public void run(){
for(int i=0;i<10;i++)
System.out.println("# Thread-"+getName()+"-"+i);
}
}
Objective 2, When threads are prevented from executing
Recognise conditions that might prevent a thread from executing.
目标2 线程停止的原因
明确线程阻塞的条件
线程的四种状态
1. 新状态(New Thread):线程已被创建但尚未执行(start() 尚未被调用)。
2. 可执行状态(Runnable):线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。
3. 死亡状态(Dead):正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。
4. 阻塞状态(Not Runnable):线程不会被分配 CPU 时间,无法执行。
线程阻塞的原因(Runnable->Not Runnable)
线程进入睡眠状态,那么必须经过指定的时间后线程返回可运行状态
线程由于调用wait而处于暂停状态,另一个对象调用了notfiy或notifyAll通知等待线程
线程由于IO操作而阻塞,IO完成后线程返回可运行状态
线程由于调用suspend()而阻塞,接到resume()后返回可运行状态(Deprecated)
sleep和wait/notify是引起阻塞的主要原因。
public static void sleep(long millis) throws InterruptedException
sleep方法是一个静态方法,它以以毫秒为单位的一段时间作为参数,使线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。注意:sleep方法抛出异常,在调用它时要捕捉这个异常。
线程的yield()方法(Running->Runnable)
public static void yield();
线程的yield方法使当前线程放弃对CPU资源的占用,线程进入Runable状态。由线程调度算法来选择其它Runnable线程runing.
操作系统的调度系统
时间片(Time Slicing)/抢占式(preemptive)
时间片系统将CPU分割为很短的时间段,并给每个具有相等而且最高优先级的线程分配时间段以供运行。时间片系统遍历具有相等且最高优先级的线程,允许它们各自运行一小段时间,直到其中一个或多个完成执行或一个具有更高优先级的线程抢占它们。Windows系统采用了抢占式调度。
非时间片(Non Time Slicing)/合作式(Cooperative)
由优先系统决定线程的运行,最高优先级的线程获得CPU时间。在这种机制下,程序要通过某种方式自动的放弃CPU时间。
示例:
public class SelfishRunner extends Thread {
private int tick = 1;
private int num;
public SelfishRunner(int num) {
this.num = num;
}
public void run() {
while (tick < 400000) {
tick++;
if ((tick % 50000) == 0){
System.out.println("Thread #" + num + ", tick = " + tick);
//yield(); //!自私的线程与公平的线程
}
}
}
}
public class RaceDemo {
private final static int NUMRUNNERS = 2;
public static void main(String[] args) {
SelfishRunner[] runners = new SelfishRunner[NUMRUNNERS];
for (int i = 0; i < NUMRUNNERS; i++) {
runners = new SelfishRunner(i);
runners.setPriority(2);
}
for (int i = 0; i < NUMRUNNERS; i++)
runners.start();
}
}
Objective 3, The wait/notify protocol
Write code using synchronized wait notify and notifyAll to protect against concurrent access problems and to communicate between threads. Define the interaction between threads and between threads and object locks when executing synchronized wait notify or notifyAll.
由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。这出就是线程同步的问题。
synchronized关键字
synchronzied关键字用于标识临界区(critical section)。临界区是单独的并发线程可访问期间的相同对象的代码段。它可以是一段代码或方法。名词Monitor,mutex(mutually exclusive lock)是指实现同步所需要的lock. Java平台将一个锁与每个具有同步代码的对象关联起来。当某个线程进入同步方法时,线程就锁定了此方法的对象。在对象被解锁前,其它线程不能调用??不是基于方法的。同步的方法的实现:
synchronized void amethod() { /* method body */}
或
synchronized (ObjectReference) { /* Block body */ }
When a synchronized block is executed, its object is locked and it cannot be called by any other code until the lock is freed.
wait/notify/notifyAll
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout,int nanos) throws InterruptedException
public final void notify()
public final void notifyAll()
为了获得和释放lock,每个对象都能pause或wait,直到其它对象将锁转效到它。Wait/notify是线程间通信的一种方式。由于它们是Object类所具有的方法,所以在JAVA中所有的类都具有了线程通信的能力。调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。如果没有notify方法,wait方法将毫无意义。因为它通知那些由于wait而阻塞的线程使它们能继续运行。
wait and notify should be placed within synchronized code to ensure that the current code owns the monitor
方法的使用
while(true){
try{
wait();
}catch (InterruptedException e) {}
}
//some producing action goes here
notifyAll();
注:notifyAll方法唤醒所有等待相关对象的线程。被唤醒的线程序争夺锁。一个线程得到锁,其它线程继续等待。notify方法,它任意唤醒等待此对象的线程之一。
示例:生产/消费者问题
public class CubbyHole {
private int contents;
private boolean available = false;
public synchronized int get() {
while (available == false) {
try {
wait();
} catch (InterruptedException e) { }
}
available = false;
notifyAll();
return contents;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait();
} catch (InterruptedException e) { }
}
contents = value;
available = true;
notifyAll();
}
}
生产者
public class Producer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
cubbyhole.put(i);
System.out.println("Producer #" + this.number
+ " put: " + i);
try {
sleep((int)(Math.random() * 100));
Jason
2005-09-25 22:58:23
评论:1
阅读:2588
引用:0
@2005-11-15 18:30:20 idiot
小圣大哥最近在研究这个东东
好就没交流了
好就没交流了
