整理Java面向对象编程的笔记

(Tips:章节标题大32,中标题中18)

第三章:数据类型和变量

基本数据类型

数据类型 在内存占用字节数默认值

boolean 占1个字节flase

byte 占1个字节 0

short2个字节0

int4个字节 0

long8个字节 0

char2个字节 "\u0000"

float4个字节0.0F

double8个字节0.0D


String字符串,数组,类类型变量也占用内存,大小取决于虚拟机的实现


new关键字创建对象的过程

1:为对象分配内存空间,将对象的实例变量自动初始化为期变量类型的默认值

2:如果实例变量在声明时显示初始化,那就把初始化值赋给 实例变量

3:调用构造方法

4:返回对象引用


变量的作用域和生命周期

作用域

成员变量:在类中声明,作用域是整个类

局部变量:在方法内部或者方法的代码块的内部声明。如果在方法内部声明,作用域是整个方法,如果是方法的某个代码块,也就是{ }里面的内容,作用域也局限在{ }

方法参数:方法或者构造方法的参数,作用域是方法参数的( )里


类的成员变量有两种:静态变量和实例变量

类的静态变量在内存中只有一个,java虚拟机在加载类的过程中为静态变量分配内存,它位于方法区,被类的所有实例共享,可以直接用类名 . 直接访问,生命周期取决于类

类的实例变量,每次创建一个类的实例,Java虚拟机就会为实例变量分配一次内存,实例变量位于堆中。实例变量的生命周期取决于实例的生命周期


局部变量的声明周期:当调用一个方法时,会为这个方法中的局部变量分配内存,当结束调用时,会结束这个方法中的局部变量的声明周期。(局部变量必须显示初始化)

局部变量不能被public private protect  static等修饰,也不能通过类名和引用变量名来访问。

关于内存图去看书97页

this关键字:当一个对象创建好后,java虚拟机会分配一个引用自身的指针:this,    

this是指当前对象自己

第六章:继承


Sub类和Base类位于同一包下:Sub继承Base类中public,protected,和默认级别的成员变量和成员方法

Sub类和Base类位于不同包下:Sub继承Base类中public,protected的成员变量和成员方法


方法重载:

方法名相同

方法参数的类型,个数,顺序至少有一个不同

方法返回类型可以不同

方法修饰符可以不同


方法覆盖

子类方法的名称,参数,和返回类型必须与父类方法相同

子类方法不能缩小父类方法的访问权限

子类方法不能抛出比父类方法更多的异常

方法覆盖只存在于子类和父类

子类可以定义与父类的静态方法同名的静态方法

父类的非静态方法不可以被子类覆盖为静态方法

父类的私有方法不能被子类覆盖

父类的抽象方法可以被子类通过两种途径覆盖:一是子类实现父类的抽象方法;二是子类重新声明父类的抽象方法


super

super的使用场景

在子类的构造方法中调用父类构造方法,

在子类中使用被父类的被屏蔽的方法和属性。

(Tips:只能在构造方法和实例方法中使用使用super关键字,而在静态方法和静态代码块中不能使用super关键字)


继承的利弊和使用原则

可以提高程序代码的可重用性,提高可扩展性。

继承树的层次不可太多,继承树的上层为抽象层,

继承的最大弱点:打破封装


!!!!!!!!!!!!!!!

精心设计用于被继承的类

由于继承关系会打破封装,因此随意继承对象模型中的任意一个类是不安全的做法。

在建立对象模型时,应该充分考虑软件系统中哪些地方需要扩展,为这些地方提供扩展点。

(1)对这些类提供良好的文档说明,使得创建该类的子类的开发人员指导如何正确安全的扩展它。

对于那些允许子类覆盖的方法,应该详细的描述该方法的自用型,以及子类覆盖此方法可能带来的影响。

比如父类有一个speak()方法,此方法里面又调用了change()方法,这样在子类覆盖父类的speak()方法时,change()也会受到影响。

(2)尽可能封装父类的实现细节,也就是把代表实现细节的属性和方法定义为private类型。如果这些实现细节必须被子类访问,可以在父类中把包含

这种实现细节的方法定义为protected类型。当子类仅仅调用父类的protected类型的方法,而不覆盖它时,可以把这种protected类型的方法看做父类

向子类但不对外公开的接口。

(3) 把不允许子类覆盖的方法定义为final类型。

(4)父类的构造方法中不可以调用不能被子类覆盖的方法,因为子类的构造方法中默认super()调用父类构造方法,这样会导致不可预料的错误。

(5)如果某些类不是专门为了继承而设计,那么随意继承它时不安全的。

那么可以把这这个类定义为final类型,

或者把这个类的所有构造方法声明为priavte类型,然后通过一些静态方法来负责构建自身的实例。


组合关系和继承关系的比较

组合关系 继承关系

1:不破坏封装,整体类于局部类之间松耦合,彼此相对独立 破坏封装,子类与父类之间紧密耦合,子类依赖父类的实现,子类缺乏独立性

2:具有良好的可扩展性 支持扩展,但是增加系统结构的复杂程度

3:支持动态组合,在运行时,整体对象可以选择不同类型的局部对象  不支持动态继承,在运行时,子类无法选择不同的父类

4:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 子类不能改变父类的接口

5:整体类不能自动获得和局部类相同的接口 子类能自动继承父类的接口

6:创建整体类的对象时,需要创建所有局部类的对象 创建子类的对象时,无须创建父类的对象

组合关系1234是优点,继承关系有56的优点


第7章 修饰符

访问修饰符:

public: 公开的

private: 私有的,只有类本身可以访问,不对外公开

默认修饰符:向同一包内的类公开

protected:   只向子类和同一包的类公开


abstract:抽象

(1)抽象类中可以没有抽象方法,但包含了抽象方法的类必须是抽象类。如果子类没有实现父类所有的抽象方法,那么子类也必须是抽象类

(2)没有抽象构造方法,也没有抽象静态方法。

(3)抽象类中可以有非抽象的构造方法,创建子类的实例时可能会调用这些构造方法

抽象类不能实例化,但是可以创建一个引用变量,其类型是个抽象类,并让它指向子类的一个实例。

(4)抽象类及抽象方法不能被final修饰符修饰。

abstract与final,private,static连接是无意义的


final修饰符

用final修饰的类不能被继承

用final修饰的方法不能被子类覆盖

private类型的方法都默认是final的方法,因为不能被子类的方法覆盖

用final修饰的变量表示常量,只能被赋值一次


final类   (例如String类不可以被继承)

继承关系的弱点是打破封装,子类能够访问父类的实现细节,而且能以方法覆盖的方式修改实现细节。

如果不是专门为继承而设计的类,类本身的方法之间有复杂的地调度关系,假如随意创建这些类的子类,可能会错误的修改父类的实现细节

出于安全的原因,类的实现细节不允许有任何改动

在创建对象模型时,确信这个类不会再被扩展

final方法 (例如getClass()方法不可以被覆盖)

出于安全的考虑,父类不允许子类覆盖某个方法,此时可以把这个方法声明为final类型

final变量

final修饰符可以修饰静态变量,实例变量和局部变量分别为静态常量,实例常量和局部常量

静态常量一般以大写字母命名,单词之间用_分开

final修饰的成员变量必须显示初始化,否则会导致编译错误

对于final类型的实例变量,可以在定义变量时,或者在构造方法中进行初始化,对于final类型的静态变量,可以在定义变量时进行初始化,或者在静态代码块中初始化

final变量只能被赋值一次

final修饰的变量的作用

提高程序的安全性,

提高程序代码的可维护性

提高程序代码的可读性


static修饰符

static修饰符可以用来修饰类的成员变量,成员方法和代码块

用static修饰的成员变量表示静态变量,可以直接通过类名访问

用static修饰的成员方法表示静态方法,可以直接通过类名访问

用static修饰的程序代码块表示静态代码块,当Java虚拟机加载类时,就会执行该代码块


类的成员变量有两种,一个是静态变量,另一个是实例变量

静态变量在内存中只有一个拷贝,运行时java虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配,可以直接通过类名访问。

被类的所有实例共享,可以作为实例之间的共享数据,如果类的所有实例都包含一个相同的常量属性,可以把这个属性定义为静态常量,从而节省内存空间

实例变量,每次创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响


static方法

静态方法和静态变量一样,直接用类名可以访问

静态方法中不能使用this关键字,也不能直接访问所属类的实例变量和实例方法,只能访问所属类的静态变量和静态方法

静态方法中也不能使用super关键字

在实例方法中可以直接访问所属类的实例变量实例方法,静态变量静态方法

静态方法不能被定义成抽象方法


实例方法和静态方法,它们的字节码都位于方法区。Java编译器把Java方法的源程序代码编译成二进制的编码,成为字节码,java虚拟机的解析器能够解析这种字节码


第八章 接口


接口中的成员变量默认都是public,static,final类型的,必须被显式初始化

接口中的方法默认都是public,abstract类型的

接口中只能包含public,static,final类型的成员变量和public,abstract类型的成员方法

接口没有构造方法,不能被实例化

一个接口不能实现另一个接口,但是可以继承另一个接口

接口必须通过类来实现它的抽象方法,类实现接口的关键字为implements

与子类继承抽象父类相似,当类实现了某个接口时,它必须实现接口中所有的抽象方法,否则这个类必须被定义为抽象类

不允许创建接口的实例,但允许定义接口类型的引用变量,该变量引用实现了这个接口的实例

一个类只能继承一个直接的父类,但能实现多个接口



比较抽象类和接口

代表系统的抽象层

都不能被实例化

都能包含抽象方法,这些抽象方法用于描述系统能提供哪些服务,但不必提供具体的实现


抽象类与接口主要有两大区别

1

在抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们,提高代码的可重用性,这是抽象类的优势所在;

而接口中只能包含抽象方法,如果为接口增加了一个的方法,那么所有实现它的类都要去实现这个方法,不利于扩展。

2

一个类只能继承一个父类,但是能实现多个接口,这个是接口的优势


(Tips:为什么Java语言不允许一个类继承多个直接的父类呢?     因为当子类覆盖父类的实例方法,或者隐藏父类的成员变量及静态方法,或者成员变量和静态方法时,会使Java虚拟机绑定规则更加复杂。

而接口中只有抽象方法,没有实例变量和静态方法,只有接口的实现类才会实现接口的抽象方法。因此,一个类即使实现了多个接口,也不会增加Java虚拟机进行动态绑定的复杂度,因为虚拟机永远不会把方法与接口绑定,只会把方法与它的实现类绑定

对于已经存在的继承树,可以方便地从类中抽象出新的接口,但是从类中抽象出新的抽象类却不那么容易,因此接口更有利于软件系统的维护与重构。)



第十章 类的生命周期


类的加载,连接,初始化

1:加载:查找并加载类的二进制数据

2:包括验证,准备,解析类的二进制数据

验证:确保被加载类的正确性   

②准备:为类的静态变量分配内存,并将其初始化为默认值

③解析:把类中的符号引用转换为直接引用

3:初始化:给类的静态变量赋予正确的初始值



类的加载

类的加载是指把类的.class文件中的二进制数据读入到内存中,把它存放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构

Java虚拟机能够从多种来源加载类的二进制数据

从本地文件系统中加载类的.class文件,这是最常见的加载方法。

通过网络下载类的 .class文件。

从ZIP,JAR或者其他类型的归档文件中提取.class文件。

从一个专有数据库中提取.class文件

把一个Java源文件动态编译为.class文件

类的加载的最终产品是位于运行时数据区的堆区的Class对象。Class对象封装了这个类在方法区内的数据结构,并且向java程序提供了访问类在方法区的数据结构的接口。

如图:书284页


类的初始化

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。

静态的声明语句,以及静态代码块都被看做类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次执行它们。

1:假如这个类还没有被加载和连接,那就先进行加载和连接。

2:假如类存在直接的父类,并且这个父类还没有被初始化,那就先初始化直接的父类

3:假如类中存在初始化语句,那就依次执行这些初始化语句。


类的初始化时机

Java虚拟机只有在程序首次主动使用一个类或接口时才会初始化它

下面6中被视为程序对类或者接口的主动使用

1:创建类的实例。  创建类的实例的途径包括:用new语句创建实例,或者通过反射,克隆及反序列化等方式来创建实例。

2:调用类的静态方法

3:访问某个类或者接口的静态变量,或者对该静态变量赋值。

4:调用Java API中的某些反射方法,比如Class.forName("Person");

5:初始化一个类的子类。    对子类的初始化,可以看做是对它父类的主动使用,因此会先初始化父类。

6:Java虚拟机启动时被标明为启动类的类。

除了上述6种情形,其他使用java类的方式都被看做是被动使用,都不会导致类的初始化。


1对于final类型静态变量,如果在编译时就能计算出变量的取值,那么这种变量被看做编译时常量。Java程序中对类的编译时常量的使用,被看做对类的被动使用,不会初始化

2对于final类型的静态变量,如果在编译时不能计算出变量的取值,那么程序对类的这种使用,被看做是对类的主动使用,会导致初始化。

3当java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。

(在初始化一个类时,并不会先初始化它所实现的接口;在初始化一个接口时,并不会先初始化它的父接口)

4:只有当程序访问的静态变量或者静态方法的确在当前类或接口中定义时,才可看做是对类或接口的主动使用。

5:调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。



第十一章  对象的声明周期

创建对象的方式

1:用new语句创建对象,这是最常用的创建对象的方式

2:运用反射手段,newInstance()方法

3:调用对象的clone()方法

4:运用反序列化方式


创建对象的步骤

1:给对象分配内存

2:将对象的实例变量自动初始化为其变量类型的默认值。

3:初始化对象,给实例变量赋予正确的初始值。


构造方法

构造方法负责对象的初始化工作,为实例变量赋予合适的初始值

语法规则:

1:方法名必须与类名相同

2:不要声明返回类型

3:不能被static,final,synchronized和native修饰。构造方法不能被子类继承,所以用final和abstract修饰没有意义。构造方法用于初始化一个新建的对象,所以用static修饰没有意义,多个线程不会同时创建内存地址相同的同一个对象,所以用synccheronized修饰没有必要。




实现单例类的两种方式

1:

public static final Person o=new Person();

private Person(){

}


2:

private static final Person o=new Person();

private Person(){

}

public static Person getInstance(){

return p;

}


(Tips:约定俗成的规则:获取对象的方法为getInstance();     获取变量的方法为getValue(),为了提高可阅读性)



略过对象声明周期和类声明周期,后面再补











内部类

内部类分为成员内部类,局部内部类,而成员内部类又分为实例内部类和静态内部类(不管是什么类型的内部类,都应该保证内部类和外部类不重名)

如果不希望客户端程序访问成员内部类,外部类可以把成员内部类定义为private类型


最外层的类成为顶层类,顶层类只能处于public 和默认访问级别,而成员内部类可以处于public,protected,private和默认这四种访问级别


实例内部类


实例内部类是成员内部类的一种,没有static修饰。具有的特点如下

1:在创建实例内部类的实例时,外部类的实例必须已经存在。 例如Outer.innerinner=new Outer().new Inner(); 

2:实例内部类的实例自动持有外部类的引用。在内部类中,可以直接访问外部类的所有成员,包括成员变量和成员方法。

3:外部类实例与内部类实例之间是一对多的关系,一个内部类实例只会引用一个外部类实例,而一个外部类实例对应零个或多个内部类实例。

4:在实例内部类中不能定义静态成员,而只能定义实例成员。(也就是这个实例内部类的里面不能有static的内部类和static的变量或方法)

5:如果实例内部类B与外部类A包含同名的成员(变量 i ),那么在类B中,this.i表示类B的成员,A.this.i表示类A的成员。


静态内部类


静态内部类是成员内部类的一种,用static修饰。具有的特点如下

1:静态内部类的实例不会自动持有外部类的特定实例的引用,在创建内部类的实例时,不必创建外部类的实例。  例如   Person.A  a=new  Person.A();

public class MyInterface {
public static class A{
public   class B{
int i=2;
}
}
}

创建B对象的方法如下

public class Test{
public static void main(String[] args){
MyInterface.A.B  b=new MyInterface.A().new B();
System.out.println(b.i);
}
}

2:局部内部类和实例内部类一样,不能包含静态成员。

3:在局部内部类中定义的内部类也不能被public,protected,和private这些访问修饰符修饰。

4:局部内部类和实例内部类一样,可以访问外部类的所有成员,此外,局部内部类还可以访问所在方法中的final类型的参数和变量。



内部类的继承

一个类Person继承了另一个类Outer的内部类Inner(public class Person extends Outer.Inner{   }),那么在Person的构造方法里必须引用这个外部类Outer的实例

public class Person extends Outer.Inner{

person(Outer o){

o.super();

}

}


public class Outer{

class Inner{

}

}

(解释:因为在直接构造实例内部类的实例的时候,java虚拟机会自动使内部类实例引用它的外部类实例。在Person类中,继承了内部类Inner,所以必须要持有外部类的引用,才能让Java虚拟机找到内部类,所以Java虚拟机要求Person类的构造方法必须通过参数传递一个Outer实例的引用,然后在构造方法中调用super语句来建立Person实例与Outer实例的关联关系。)




匿名内部类:如下

A  a=new A(){

void run(){

}

};

a.run();

可以看做是

class   Sub   extends  A{

void run(){

}

}

A  a2=new  SubA();

a2.run();


匿名类具有以下特点:

1:匿名类本身没有构造方法,但是会调用父类的构造方法

2:匿名类尽管没有构造方法,但是可以在匿名类中提供一段实例初始化代码,Java虚拟机会在调用了父类的构造方法后,执行这段代码。

3:除了可以在外部类的方法内定义匿名类以外,还可以在声明一个成员变量时定义匿名类。

4:匿名类除了可以继承类以外,还可以实现接口。

5:匿名类和局部内部类一样,可以访问外部类的所有成员,如果匿名类位于一个方法中,还能访问所在方法的final类型的变量和参数。

6:局部内部类的名字在方法外是不可见的,因此与匿名类一样,能够起到封装类型名字的作用。


内部类的回调详细在书360页。





第十三章 多线程


在Java虚拟机进程中,执行程序代码的任务是由线程来完成的。每个线程都有一个独立的程序计数器和方法调用栈。

程序计数器:也称为PC寄存器,当线程执行一个方法时,程序计数器指向方法区中下一条要执行的字节码指令。

方法调用栈:简称方法栈,用来跟踪线程运行中一系列的方法调用过程,栈中的元素称为栈帧。每当线程调用一个方法的时候,就会向方法栈压入一个新帧。帧用    来存储方法的参数,局部变量和运算过程中的临时数据。

栈帧由三部分组成:

局部变量区:存放局部变量和方法参数。

操作数栈:是线程的工作区,用来存放运算过程中生成的临时数据。

栈数据区:为线程执行指令提供相关的信息,包括如何定位到位于堆区和方法区的特定数据,以及如何正常退出方法或者异常中断的方法。


方法区存放了线程所执行的字节码指令,堆区存放了线程所操纵的数据(以对象的形式存放),java栈区是线程的工作区,保存线程的运行状态。

另外,计算机中机器指令的真正执行者的CPU,线程必须获得CPU的使用权,才能执行一条指令。


线程的创建和启动

public class MyThread extends Thread{

@override

public void run(){

}

}

MyThread  myThread=new MyThread(); 

my.start();



Thread  thread=Thread.currentThread();     //返回当前正在执行这行代码的线程的引用

String   name=thread.getName();      //获取线程的名字

主线程默认的名字为main,用户创建第一个线程的名字默认为Thread-0,第二个线程默认的名字是Thread-1;

不要随意覆盖Thread类的start()方法,比如

public class MyThread extendsThread{

public void run(){

}

public void start(){

run(){}

}

}

public  class Test{

public static void main(String args[]){

MyThread  myThread =new MyThread();

myThread.start();

}

}

如上只是普通的方法调用,因为已经覆盖了start()方法,并且方法里面又调用了自己覆盖的的run()方法,并没有启动线程,只是方法调用。

如果一定要覆盖start()方法,那么在方法体里的第一行调用super.start()就可以了。


实现Runnable接口

public class MyRunnable implements Runnable{

int  i=0;

public void run(){

i++;

}

}

public  class Test{

public static void main(String args[]){

MyRunnable myRunnable=new MyRunnable();

Thread thread1=new Thread(myRunnable);

Thread thread2=new Thread(myRunnable);

thread1.start();

thread2.start();

}

}

thread1和thread2操控的是同一个变量i;

修改Test类

public  class Test2{
public static void main(String args[]){
MyRunnable myRunnable=new MyRunnable();
MyRunnable myRunnable2=new MyRunnable();
Thread thread1=new Thread(myRunnable);
Thread thread2=new Thread(myRunnable2);
thread1.start();
thread2.start();

}
}

这样的情况下thread1和thread2操控的是各自的变量i



线程的状态


1:新建状态

用new语句创建的线程处于新建状态,此时它和其他java对象一样,仅仅在堆区被分配了内存

2:就绪状态

当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待CPU的使用权。

3:运行状态

处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只有一个线程处于这个状态。如果计算机有多个CPU,那么同一时刻可以让几个线程占用不同的CPU,使它们都处于运行状态。只有处于就绪状态的线程才有机会转到运行状态。

4:阻塞状态

阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,JAVA虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态

阻塞状态可分为3种

1:位于对象等待池中的阻塞状态:当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中。

2:位于对象锁池中的阻塞状态:当线程处于运行状态,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对   象的锁池中。

3:其他阻塞状态:当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者放出了I/O请求时,就会进入这个状态。

5:死亡状态

当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。线程有可能是正常执行完run()方法退出的,也有可能是遇到异常退出的,都不会对其他线程造成影响。


Thread类的isAlive()方法判断一个线程是否活着,当线程处于死亡状态或者新建状态时,该方法返回false,其他情况下返回true。


Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程的优先级相同,那么久随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。一个线程会因为以下原因放弃CPU:

1:JAVA虚拟机让当前线程暂时放弃CPU,转到就绪状态,使其他线程获得运行机会。

2:当前线程因为某些原因而进入阻塞状态。

3:线程运行结束。

如果希望明确地让一个线程给另外一个线程运行的机会,可以采用以下办法:

1:调整线程的优先级

2:让处于运行状态的线程调用Thread.sleep()方法

3:让处于运行状态的线程调用Thread.yield()方法

4:让处于运行状态的线程调用另一个线程的join()方法


调整各个线程的优先级

Thread类的setPriority(int i);   设置优先级

Thread类的getPriority()      获取优先级

Thread类又三个优先级的常量

MAX_PRIORITY:取值为10,表示最高优先级

MIN_PRIORITY:取值为1,表示最低优先级

NORM_PRIORITY:取值为5,表示默认的优先级

在不同的操作系统中,选用常量来设置优先级比较合适



线程睡眠

Thread.sleep(int i); 使线程睡眠,参数为毫秒,当一个线程在运行中执行了sleep()方法,它就会放弃CPU,转到阻塞状态。

Thread.interrupt(); 中断睡眠的方法。


线程让步

Thread.yield(); 当线程在运行中执行了Thread类的yield()静态方法,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行,如果没有相同优先级的可运行线程,则yield()方法什么都不做。


sleep()和yield()的比较:他们都是Thread的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给其他线程

    他们的区别在于:sleep()方法会给其他线程运行的机会,而不考虑优先级,因此可能会给较低优先级的线程运行的机会

    yield()方法只会给相同优先级或者更高优先级的线程运行的机会。

     执行sleep()方法后,将转到阻塞状态,执行yield()方法后,将转到就绪状态。

     sleep()方法声明抛出InterruptException异常,而yield()方法没有声明抛出任何异常。

     sleep()方法比yield()方法具有更好的可移植性。不能依靠yield()方法来提高程序的并发性能。



join()方法:当前运行的线程可以调用另一个线程的join(),当前运行的线程将转到阻塞状态,直到另一个线程运行结束,它才会恢复运行。

join方法有两种重载形式:join(); 等待抢占的那个线程运行完毕,才会恢复运行。

       join(long timeout);   比如此线程阻塞100毫秒时间后,会恢复运行;或者其他线程执行完毕,此线程会直接恢复运行。







synchronized

线程同步的特征:

1:如果一个同步代码块和非同步代码块同时操纵共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他线程仍然可以执行对象的非同步代码块。

2:每个对象都有唯一的同步锁。

3:在静态方法前面也可以使用synchronized修饰符。那么锁的是这个类,而不是一个类的对象。

4:当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行。进入同步代码块的线程的线程池也可以执行Thread.sleep();或者执行Thread.yield();方法,此时它并没有释放锁,只是把运行机会让给了其他的线程。

5:synchronized声明不会被继承。如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不再保持同步,除非也用synchronized修饰。


释放对象的锁

可以释放锁的情况

1:执行完同步代码块,就会释放锁。

2:在执行同步代码快的过程中,遇到异常而导致线程终止,锁也会被释放。

3:在执行同步代码块的过程中,执行了锁对象的wait()方法,这个线程会释放锁,进入对象的等待池。

不会释放锁的情况

1:在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。

2:在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。


线程执行流程的例子

public class Machine extends Thread {
private int a=1;
public synchronized void print(){
System.out.println(a);
}
public void run(){
synchronized (this) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a=1/0;
a++;
}
}
public static void main(String[] args) {
Machine machine=new Machine();
machine.start();
Thread.yield();
machine.print();
}
}

1:主线程执行Thread.yield()方法,把CPU的使用权让给machine线程。

2:machine线程获得machine对象的锁,开始执行run()方法中的同步代码块。

3:machine线程执行Thread.sleep(2000); ,machine线程放弃CPU,开始睡眠,但不会释放machine对象的锁。

4:主线程获得CPU,试图执行machine.print()方法,由于主线程无法获得machine对象的锁,因此进入machine对象的锁池,等待machine线程释放锁。

5:machine线程睡眠结束,执行 a=1/0  的操作,导致ArithmeticException异常,machine线程释放锁,并且异常终止。

6:主线程获得锁及CPU,执行machine.print()方法。




synchronized的死锁:在多线程环境中,死锁意味着两个或多个线程一直阻塞,等待其他线程释放锁

如下是一个简单的死锁例子


public class DeadlockSample {
private final Object obj1 = new Object();
private final Object obj2 = new Object();


public static void main(String[] args) {
DeadlockSample test = new DeadlockSample();
test.testDeadlock();
}
//这个方法创建了两个线程t1,t2,并分别启动了线程,t1执行了calLock12(),t2执行了calLock21();
private void testDeadlock() {
Thread t1 = new Thread(new Runnable() {
public void run() {
calLock12();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
calLock21();
}
});
t1.start();
t2.start();


}


private void calLock12() {
synchronized (obj1) {
sleep(100);
synchronized (obj2) {
sleep(100);
}
}
}


private void calLock21() {
synchronized (obj2) {
sleep(100);
synchronized (obj1) {
sleep(100);
}
}
}


private void sleep(int i) {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//如下这个方法创建了两个线程t1,t2,并分别启动了线程,t1执行了calLock12(),它先执行持有obj1对象的同步代码块,休眠后再去运行持有obj2的代码块

//     t2执行了calLock21();  它先执行持有obj2对象的同步代码块,休眠后再去运行持有obj1的代码块。

// 因为他们首先持有的是不同的对象,所以可以t1持有的boj1和t2持有的obj2的代码块可以同步运行,但是t1睡眠后去拿obj2对象的同步锁,此时t2休眠后

//      也在等待obj1的锁,所以造成了死锁。


死锁例子2:

public class Machine extends Thread {
private Counter counter;
public Machine(Counter counter){
this.counter=counter;
start();
}
public void run(){
for(int i=0;i<1000;i++){
counter.add();
}
}
public static void main(String[] args) {
Counter counter1=new Counter();
Counter counter2=new Counter();
counter1.setFriend(counter2);
counter2.setFriend(counter1);

Machine machine1=new Machine(counter1);
Machine machine2=new Machine(counter2);
}
}
class Counter{
private int a;
private Counter friend;
public void setFriend(Counter friend){
this.friend=friend;
}
public synchronized void add(){
a++;
Thread.yield();
friend.delete();System.out.println(Thread.currentThread().getName()+": add");
}
public synchronized void delete(){
a--;
System.out.println(Thread.currentThread().getName()+": delete");
}
}


1:machine1线程获得CPU和counter1对象的锁,开始执行counter1.add()方法。
2:machine1线程执行完   a++ 操作,然后执行Thread.yield()方法,machine1线程放弃CPU,但是不会释放counter1对象的锁。
3:machine2线程获得CPU和counter2对象的锁,开始执行counter2.add()方法。
4:machine2线程执行完   a++ 操作,然后执行Thread.yield()方法,machine2线程放弃CPU,但是不会释放counter2对象的锁。
5:machine1线程获得CPU,试图执行counter2.delete()方法。由于counter2对象的锁已经被占用,machine1线程放弃CPU,
进入counter2对象的锁池,等待machine2线程释放counter2对象的锁。
6:machineer2线程获得CPU,试图执行counter1.delete()方法。由于counter1对象的锁已经被占用,machine2线程放弃CPU,
进入counter1对象的锁池,等待machine1线程释放counter1对象的锁。





线程通信



java.lang.Object类中提供了两个用于线程通信的方法。

wait();执行该方法的线程释放对象的锁,Java虚拟机把该线程放到该对象的等待池中。该线程等待其他线程将它唤醒。

notify();执行该方法的线程唤醒在对象的等待池中等待的一个线程。Java虚拟机从对象的等待池中随机选择一个线程,把它转到对象的锁池中。



等待补充




















第十四章   数组


创建数组对象

int [ ] sourse=new int [100];

在堆区中为数组分配内存空间,以上的代码创建了一个包含100个元素的int数组。每个元素都是int类型,占用4个字节,因此整个数组对象在内存中占用400字节。

数组的每个元素都是int类型,所以默认值是0。数组的下标是从0开始的,所以对于以上的数组,是从sourse[0]到sourse[99]的。

用new语句创建数组对象时,需要指定数组长度。数组长度表示数组中包含的元素数目。

数组对象创建后,它的长度是固定的。数组对象的长度是无法改变的,但是数组变量可以改变所引用的数组对象。

例如:

int [ ]  x=new int[3];

int [ ]  y=x;

x=new  int[4];


数组的length属性表示数组元素的数目。每个数组都有一个length属性,public  final  length,因此在程序中可以读取数组的length属性,但是不能修改这一属性。 

(Tips:String类的toCharArray( )方法能够返回包含字符串中所有字符的数组。)

注意如下的代码:(说明有待补充)

StringBuffer sb=new StringBuffer("a");
StringBuffer[] sbs=new StringBuffer[]{sb,null};
sb.append("b");
System.out.println(sb+"    "+sbs[0]);//输出abab
sb=null;
System.out.println(sb+"    "+sbs[0]);//输出nullab



第十五章 Java集合


set(集):集合中的对象不按特定方式排序,并且没有重复对象。它的有些实现类能对集合中的对象按特定方式排序。

List(列表):集合中的对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象。List与数组有些相似。

Map(映射):集合中的每一个元素包含一对键对象和值对象,集合中没有重复的键对象,值对象可以重复。它的有些实现类能对集合中的键对象进行排序。



Collection和Iterator接口


Collection接口中声明了适用于Java集合(只包括 Set和List)的通用方法。


boolean add(Object o)向集合中假如一个对象的引用

void clear()删除集合中的所有对象,即不再持有这些对象的引用

boolean contains(Object o)判断在集合中是否持有特定对象的引用

boolean isEmpty()判断集合是否为空

Iterator iterator()返回一个Iteritor对象,可用它来遍历集合中的元素

boolean remove(Object o)从集合中删除一个对象的引用

int size() 返回集合中元素的数目

Object [ ]  toArray()返回一个数组,该数组包含集合中的所有元素

Collection接口的iterator()和toArray()方法都用于获得集合中的所有元素,前者返回一个Iteritor对象,后者返回一个包含集合中所有元素的数组。

Iterator接口隐藏底层集合的数据结构,向客户程序提供了遍历各种类型的集合的统一接口。

Iterator接口中声明了如下方法:

hasNext() 判断集合中的元素是否遍历完毕,如果没有,就返回true。

next() 返回下一个元素。

remove() 从集合中删除上一个由next()方法返回的元素

(Tips:如果集合中的元素没有排序,Iterator遍历集合中的元素的顺序是任意的,并不一定与向集合中加入元素的顺序一致)

当通过Collection集合的iterator()方法得到一个Iterator对象后,如果当前线程或其他线程接着又通过Collection集合的一些方法对集合进行了修改操作(调用当前Iterator对象的remove()方法来修改集合除外),接下来访问这个Iterator对象的next()方法会导致java.util.ConcurrentModificationException运行时异常。

Iterator对象运用了快速失败机制(fail-fast),一但检测到集合已被修改(有可能是被其他线程修改的),就抛出ConcurrentModificationException运行时异常,而不是显示修改后的集合的当前内容,这样可以避免潜在的由于共享资源竞争而导致的并发问题。



Set(集)

Set是最简单的一种集合,集合中的对象不按特定方式排序,并且没有重复对象。

Set接口主要有两个实现类:HashSet和TreeSet

HashSet类按照哈希算法来存取集合中的对象,存取速度比较快。HashSet类还有一个子类LinkedHashSet类,它不仅实现了哈希算法,而且实现了链表数据结构,链表数据结构能提高插入和删除元素的性能。

TreeSet类实现了SortedSet接口,具有排序功能。


Set<String>set=new HashSet<String>();

String s1=new String("hello");

String s2=new String("hello");

set.add(s1);

set.add(s2);

System.out.println(set.size());//打印结果为1.

虽然变量s1和s2实际上引用的是两个内存地址不同的String对象,但是由于s2.equals(s1)的比较结果为true,因此Set认为他们是相等的对象。当第二次调用Set的add()方法时,add()方法不会把s2引用的String对象加入到集合中。


HashSet类

HashSet类按照哈希算法来存取集合中的对象,具有很好的存取和查找性能。当向集合中加入一个对象时,HashSet会调用对象的hashCode()方法来获得哈希码,然后根据这个哈希码进一步计算出对象在集合中的存放位置。

在Object类中定义了hashCode()和equals()方法,Object类的equals()方法按照内存地址比较对象是否相等,因此如果object1.equals(object2)为true,则表明object1变量和object2变量实际上引用同一个对象,那么object1和object2的哈希码也肯定相同。

为了保证HashSet正常工作,如果Person类覆盖了equals()方法,也应该覆盖hashCode()方法,并且保证两个相等的Person对象的哈希码也一样。

(Tips:如果两个对象用equals( )方法比较不相等,HashSet或HashMap并不要求这两个对象的哈希码也必须不相等。但是尽量保证用equals()方法比较不相等的两个对象有不同的哈希码,这样可以减少哈希冲突。)


TreeSet类

TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序。

当TreeSet向集合中加入一个对象时,会把它插入到有序的对象序列中。那么TreeSet是如何对对象进行排序的呢?TreeSet支持两种排序方式:自然排序和客户化排序。


1:自然排序

在JDK类库中,有一部分类实现了Comparable接口,如Integer,Double和String等。Comparable接口有一个compareTo(Object  o)方法,它返回整数类型。

对于表达式x.compareTo(y);如果返回值为0,则表示x和y相等,如果返回值大于0,则表示x大于y,如果返回值小于0,则表示x小于y。

TreeSet调用对象的compareTo()方法比较集合中对象的大小,然后进行升序排列,这种排序方式称为自然排序。

使用自然排序时,只能向TreeSet集合中加入同类型的对象,并且这些对象的类必须实现Compareble接口,为了保证TreeSet能正确地排序,要求Person类的

compareTo()方法与equals()方法按相同的规则比较两个Person对象是否相等。也就是说person1.equals(person2)为true,那么person1.compareTo(person2)为0.

值得注意的是,对于TreeSet中已经存在的Person对象,如果修改了它们的name属性或者age属性,则TreeSet不会对集合进行重新排序。

在实际应用中,最适合用TreeSet排序的是不可变类。

Set<Object>  set=new TreeSet<Object>();

set.add(new Integer(8));

set.add(new String("9"));//当第二次调用TreeSet的add()方法时会抛出ClassCastException。



List(列表)


List的主要特征是其元素以线性方式存储,集合中允许存放重复对象。

List接口主要的实现类包括

ArrayList:代表长度可变的数组。允许对元素进行快速的随机访问,但是向ArrayList中插入与删除元素的速度较慢。

LinkedList:在实现中采用链表数据结构。对顺序访问进行了优化,向List中插入和删除元素的速度较快,随机访问速度则相对较慢。随机访问的是指检索位于特定索引位置的元素。LinkedLsit单独具有addFirst(),addLast(),getFirst(),getLast(),removeFirst(),removeLast()方法,这些方法使得LinkedList可以作为堆栈,队列和双向队列来使用。


为列表排序

List只能对集合中的对象按索引位置排序,如果希望对List中的对象按其他特定方式排序,可以借助Comparator接口和Collections类。Collections类是Java集合类库中的辅助类,它提供了操纵集合的各种静态方法,其中sort( )方法用于对List中的对象进行排序。

sort(List   list):对List中的对象进行自然排序。


ListIterator接口

List的listIterator( )方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,此外还提供了专门操纵列表的方法如下。

add( ) 向列表中插入一个元素

hasNext( ) 判断列表中是否还有下一个元素

hasPrevious( )判断列表中是否还有上一个元素

next( ) 返回列表中的下一个元素

previous( )返回列表中的上一个元素


获得固定长度的List对象

Java.util.Arrays类的asList( )方法能够把一个Java数组包装为一个List对象,这个List对象代表固定长度的数组。所有对List对象的操作都会被作用到底层的Java数组。由于数组的长度不能改变,因此不能调用这种List对象的add( )和remove( )方法,否则会抛出java.lang.UnsupportedOperationException运行时异常。(不支持操作异常)

String[ ]   s={"Tom","Mike","Jack"};

List<String>list=Arrays.asList(s);//把字符串数组转化为一个List列表

list.set(0,"Jane");//把列表中下标为0的对象改为"Jane"

System.out.println(Arrays.toString(s));//输出s数组

list.remove("Mike");//非法,因为移除后,数组长度会改变。但是规定数组长度是不可改变的,所以会抛出java.lang.UnsupportedOperationException

list.add("Mary");//非法,原因同上,数组长度不可改变。


Java数组进行随机访问和迭代操作具有最快的速度(数组再访问和迭代操作具有最快的速度,但是数组长度不可改变,不能进行插入操作和删除操作)

LinkedList进行插入和删除操作具有最快的速度

ArrayList进行随机访问速度最快



Map(映射)


Map(映射)是一种把键对象和值对象进行映射的集合,它的每一个元素都包含一对键对象和值对象,而值对象仍可以是Map类型,依次类推,这样就形成了多级映射。

向Map集合中加入元素时,必须提供一对键对象和值对象,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

Map集合中的键对象不允许重复,也就是说,任意两个键对象通过equals( )方法比较的结果都是false。对于值对象则没有唯一性要求,可以将任意多个键对象映射到同一个值对象上。

Map<String,String>map=new HashMap<String,String>();

map.put("1","Mon");

map.put("1","Monday");//因为已经存在了键为 "1"的键,所以这行代码添加的数据会覆盖前面的

map.put("one","Monday");//这行代码不会覆盖,因为只有键不能重复,重复就会覆盖

Iterator<Map.Entry<String,String>>it = map.entrySet().iterator();//创建一个Iterator迭代器对象

while(it.hasNext()){//判断是否有下一个

Map.Entry    entry = it.next();//获取下一个元素   ,    Map.Entry代表Map中的一对键与值。

System.out.println( entry.getKey + “:”+entry.getValue() );//输出这个元素的键和值分别用Map.Entry对象的getKey( )方法和getValue( )方法。

}


Map的entrySet( );方法返回一个Set集合,在这个集合中存放了Map.Entry类型的元素,每个Map.Entry对象代表Map中的一对键与值。



Map有两种常见的实现:HashMap和TreeSet();

HashMap按照哈希算法来存取键对象,有很好的存取性能,为了保证HashMap能正常工作,和HashSet一样,要求当两个键对象通过equals( )方法比较为true时,这两个键对象的hashCode( )方法返回的哈希码也一样。

TreeMap实现了SortedMap接口,能对键对象进行排序。和TreeSet一样,TreeMap也支持自然排序和客户排序两种方式。


Java遍历集合的方法

public void print(Set<String>  set){

Iterator<String>it = set.iterator();

while( it.hasNext() ){

String   s=it.next();

System.out.println(s);

}

}

简便的写法

public void print(Set<String>  set){

for(String s : set){String代表集合内元素的类型,s引用每次从集合中取出的当前元素,set表示要遍历的集合

System.out.println(s);

}

}



集合实用类


实用类java.util.Collections
以下是Collections的方法 适用于List类型集合
copy(List dest,List src)把一个List中的元素拷贝到另一个List中。
fill(List list,Object o)向列表中填充元素。
sort(List list)对List中的元素进行自然排序
binarySearch(List list,Object key)查找List中与给定对象Key相同的元素。在调用该方法时,必须保证List中的元素已经自然排序,这样才能得到正确的结果
binarySearch(List list,Object key,Comparator c)查找List中与给定对象Key相同的元素,Comparator类型的参数指定比较规则。在调用该方法时,必须保证List中的元素已经按照Comparator类型的参数的比较规则排序,这样才能得到正确的结果
shuffle(); 对List中的元素进行随机排列。

Collections的以下方法使用于Collection类型或Map类型集合
Object max(Collection coll)返回集合中的最大元素,采用自然排序的比较规则
Object max(Collection coll,Comparatorcomp)返回集合中的最大元素Comparator类型的参数指定比较规则
Object min(Collection coll)返回集合中的最小元素,采用自然排序的比较规则
Object min(Collection coll,Comparatorcomp)返回集合中的最大元素Comparator类型的参数指定比较规则
set singletonList(Object o)返回一个不可改变的Set集合,它只包含一个参数指定的对象。
List singletonList(Object o)返回一个不可改变的List集合,它只包含一个参数指定的对象。
Map singletonMap(Object Key,Object value)返回一个不可改变的Map集合,它只包含参数指定的一对键值对。
Collection synchorizedCollection(Collection c)在原来集合的基础上,返回支持同步的(即线程安全的)集合
List synchorizedList(List list)在原来List集合的基础上,返回支持同步的(即线程安全的)List集合
Map synchorizedMap(Map m)在原来Map集合的基础上,返回支持同步的(即线程安全的)Map集合
Set synchorizedMap(Set s)在原来Set集合的基础上,返回支持同步的(即线程安全的)Set集合
Collection unmodifiableCollection(Collection c)在原来集合的基础上,返回不允许修改的集合视图
List  unmodifiableList(List list)在原来集合的基础上,返回不允许修改的集合视图
Map  unmodifiableMap(Map m)在原来集合的基础上,返回不允许修改的集合视图
Set  unmodifiableSet(Set s)在原来集合的基础上,返回不允许修改的集合视图


关于集合的多线程和单线程

Java集合框架中,Set,List和Map的实现类(比如HashSet,ArrayList和HashMap等)都没有采取同步机制。在单线程环境中
这种实现方式会提高操作集合的效率,Java虚拟机不必因为管理同步锁而产生额外的开销。在多线程环境中,可能会有多个线程同时操作
同一个集合,比如一个线程在为集合排序,而另一个线程向集合中加入新的元素。为了避免并发问题,可以采取以下几种解决措施
1:在程序中对可能导致并发问题的代码块进行同步
2:利用Collections的synchorizedXXX()方法获得原始集合的同步版本。所有线程只对这个采取了同步措施的集合对象操作
3:如果集合只包含单个元素并且不允许被修改,可以用Collections的singletonXXX()方法来构造这样的集合,这可以避免集
合被线程错误的修改,而且由于不必采取同步措施,可以提高并发性能
4:如果集合的元素不允许被修改,可以用Collections的unmodifiableXXX()方法来生成原始的集合视图,让线程只访问这个集合
视图,这也可以避免集合被线程错误的修改,而且由于不必采用同步措施,可以提高并发性能。




I/O流

程序的主要任务是操纵数据。在运行时,这些数据都必须位于内存中,并且属于特定的类型,程序才能操纵它们。

Java  I/O系统负责处理程序的输入和输出,I/O类库位于java.io包中,它对各种常见的输入流和输出流进行了抽象。

如果数据流中最小的数据单元是字节,那么称这种流为字节流:

如果数据流中最小的数据单元是字符,那么称这种流为字符流。


InputStream和OutputStream分别表示字节输入流合字节输出流

Reader和Writer分别表示字符输入流和字符输出流


InputStream类提供了一些读取数据有关的方法

int   read( )       :从输入流读取数据,有如下三种重载形式

1:int  read( ) 从输入流读取一个8位的字节,把它转换为0-255之间的整数,并返回这一整数。例如,如果读取到字节为9,则返回9,如果读到的字节为-9,则返回 247.

 如果遇到输入流的结尾,则返回-1

2:int  read(byte[ ]  b)     从输入流读取若干个字节,把它们保存到参数b指定的字节数组中。返回的整数表示读取的字节数。如果遇到输入流的结尾,则返回-1

3:int  read(byte[ ]  b,int  off ,int  len)从输入流读取若干个字节,把它们保存到参数b指定的字节数组中。参数off指定在字节数组中开始保存数据的起始下标,参数len指定读取的字节数目。返回的整数表示实际读取的字节数。如果遇到输入流的结尾,则返回-1

以上第一个read方法从输入流读取一个字节,而其余两个read方法从输入流批量读取若干字节。在从文件或硬盘度数据时,采用后面两个read方法可以减少进行物理读文件或硬盘的次数,因此能提高I/O操作的效率


void  close( )     :关闭输入流。当完成所有的读操作后,应该关闭输入流。InputStream类本身的close( )方法不执行任何操作,它的一些子类覆盖了 close( )方法,在close( )    方法中释放和流有关的系统资源。

int    available( ) :返回可以从输入流中读取的字节数目。

skip( long n )  :从输入流中跳过参数n指定数目的字节

boolean  markSupported( ),void  mark(int  readLimit),void reset():这三个方法用来实现重复读取流中某一段数据。     

    先用markSupported( )方法来判断这个流是否支持重复读入数据,如果返回true,则表明可以在留上    设置标记

     接下来调用mark(int  readLimit)方法从流的当前位置开始设置标记,readLimit参数指定在流上做了     标记的字节数。然后用read( )方法读取在标记范围内的字节。

     最后调用reset( )方法,该方法使输入流重新定位到刚才做了标记的其实位置。这样可以重复读取做      过标记的数据了


OutputStream类提供了一些列与数据有关的方法

void  write(int  b) :向输出流写入数据,有如下三种重载形式

1:void  write(int  b):向输出流写入一个字节

2:void  write(byte [ ] b):把参数b指定的字节数组中的所有字节写到输出流

3:void  write(byte [ ] b,int  off,int  len):把参数b指定的字节数组中的若干字节写到输出流,参数off指定字节数组的起始下标,从这个位置开始输出由参数len指定数    目的字节

以上第一个write方法向输出流写入一个字节,而其余两个write方法向输出流批量写入若干字节。在向文件或控制台写数据时,采用后面两个write方法可以减少进行物理写文件或控制台的次数,因此可以提高I/O操作的效率。


void   close( ) :关闭输出流。当完成所有的写操作后,应该关闭输出流。OutputStream类本身close( )方法不执行任何操作。它的一些子类覆盖了close( )方法,    释放和流有关的系统资源。

void   flush( ) :OutputStream类本身的flush( )方法不执行任何操作,它的一些带有缓冲区的子类(比如BufferedOutputStream和PrintStream类)覆盖了flush( )方    法。通过带缓冲区的输出流写数据时,数据先保存在缓冲区中,积累到一定程度才会真正写到输出流中。缓冲区通常用字节数组实现,实际上是指    一块内存空间。flush( )方法强制把缓冲区内的数据写到输出流中。


(Tips:为了保证输入流或输出流被及时关闭,最好把关闭流操作放在finally代码块中)







字节数组输入流:ByteArrayInputStream类


ByteArrayInputStream类从内存中的字节数组中读取数据,因此它的数据源是一个字节数组,这个类的构造方法包括:

ByteArrayInputStream(byte[ ]   buf)参数buf指定字节数组类型的数据源

ByteArrayInputStream(byte[ ]   buf,int  offset,int  length)参数buf指定字节数组类型的数据源,参数offset指定从数组中开始读数据的起始下标位置,length指定从数组中读取的字节数。

ByteArrayInputStream类本身采用了适配器设计模式,它把字节数组类型转换为输入流类型,使得程序能够对字节数组进行读操作。

例子:

public static void main(String args[ ]){
byte[ ] b=new byte[ ]{2,4,77,13,-31};
ByteArrayInputStream inputStream=new ByteArrayInputStream(b, 2, 4);
int len=0;
while((len=inputStream.read())!=-1){
System.out.println(len);
}
}

字节数组输出流:ByteArrayOutputStream

是与ByteArrayOutStream相对应。







文件输入流:FileInputStream类


FileInputStream类从文件中读取数据。它有以下构造方法:

FileInputStream(File  file)参数file指定文件数据源

FileInputStream(String  name)参数name指定文件数据源。在参数name中包含了文件路径信息

例子: 从test.txt文件读取内容

public static void main(String args[]){
String name="D:\\test.txt";
FileInputStream in=null;
try {
in=new FileInputStream(name);
int len;
while((len=in.read())!=-1){
System.out.println(len);
}
} catch (Exception e) {
e.printStackTrace();
}
}

//  假设在test.txt文件中包含的字符内容为"abc1好",并且嘉定文件所在的操作系统默认字符编码为GBK,那么在文件中实际存放了5个字符的GBK字符编码

    字符"a","b","c","1"的GBK字符编码各占1个字节,分别是97,98,99和49,"好"的GBK字符编码占2个字节,为186和195.文件输入流的read( )方法每次读取一个字节,

     因此以上打印结果为97,98,99,49,186,195。

如果文件很大,为了提高读文件的效率,可以使用read(byte[ ]  buff)方法,它能减少物理读文件的次数。

例子:

public static void main(String args[])throws Exception{
String inName="D:\\test.txt";
String outName="D:\\out.txt";
byte[] b=new byte[1024];
FileInputStream in=new FileInputStream(inName);
FileOutputStream out=new FileOutputStream(outName);
int len;
while((len=in.read(b))!=-1){
out.write(b,0,len);//注意这里的写法,如果用out.write(b),输出的结果可能末尾会有很多空格
}
in.close();
out.close();
}


文件输出流:FileOutputStream类


FileOutputStream类向文件写数据,它有以下构造方法

FileOutputStream(File file);

FileOutputStream(String name);

FileOutputStream(String name,boolean append);//在默认情况下,当FileOutputStream向文件写数据时将覆盖文件中原有的内容。第二个参数为true,在文件末尾添加数据


在创建FileOutputStream实例时,如果相应的文件并不存在,就会自动创建一个空的文件。如果参数file或name表示的文件路径尽管存在,但是代表一个文件目录,那么会抛出FileNotFoundException。






顺序输入流:SequenceInputStream类


SequenceInputStream类可以将几个输入流串联在一起,合并为一个输入流,当通过这个类来读取数据时,它会依次从所有被串联的输入流中读取数据。构造方法如下:

SequenceInputStream(InputStream s1,InputStream s2)参数s1和s2代表两个需要被串联的输入流。顺序输入流先读取s1中的数据,再读取s2中的数据。


public static void main(String args[])throws Exception{
InputStream s1=new ByteArrayInputStream("你".getBytes());//"你" . getBytes()方法返回 “你” 的GBK字符编码
InputStream s2=new ByteArrayInputStream("好".getBytes());
InputStream in=new SequenceInputStream(s1, s2);
int len;
while((len=in.read())!=-1){
System.out.println(len+" ");//打印结果为196,227,186,195
}
in.close(); //只要关闭了SequenceInputStream流的close( )方法,也会关闭被串联的输入流。
}






DataInputStream类

DataInputStream实现了DataInput接口,用于读取基本类型数据,还可以读取采用UTF-8字符编码的字符串


readByte() :从输入流中读取1个字节,把它转换为byte类型的数据。

readLong() :从输入流中读取8个字节,把它转换为long类型的数据。

readFloat():从输入流中读取4个字节,把它转换为float类型的数据。

readUTF() :从输入流中读取若干字节,把它转换为采用UTF-8字符编码的字符串。

UTF-8字符编码是Unicode字符编码的变体。Unicode字符编码把所有字符都存储为两个字节的形式。如果实际上要存储的字符都是ASCII字符(只占7位),则采用Unicode字符编码极其浪费存储空间。UTF-8字符编码能够更加有效地利用存储空间,它对ASCII字符采用一个字节形式的编码,对非ASCII字符则采用两个或两个以上字节形式的编码。

(Tips:用DataInputStream读取的数据,要用DataOutputStream来写数据,这样可以保证数据的准确性)

DataOutputStream类

DataOutputStream实现了DataOutput接口,用于读取基本类型数据,还可以读取采用UTF-8字符编码的字符串

writeByte(byte b) :从输出流中写入1个字节,把它转换为byte类型的数据。

writeLong(long l) 从输出流中写入8个字节,把它转换为long类型的数据。

writeFloat(float f)从输出流中写入4个字节,把它转换为float类型的数据。

writeUTF(String s) 从输出流中写入若干字节,把它转换为采用UTF-8字符编码的字符串。







BufferedInputStream类

BufferedInputStream类覆盖了被装饰的输入流的读数据行为,利用缓冲区来提高读数据的效率。BufferedInputStream类先把一批数据读入到缓冲区,接下来read()方法只需要从缓冲区内获取数据,就能减少物理性读取数据的次数。

构造方法为:

BufferedInputStream(Input   in)参数in指定需要被装饰的输入流

BufferedInputStream(Input   in,int  size)参数in指定需要被装饰的输入流,参数size指定缓冲区的大小,以字节为单位。

BufferedOutputStream类

BufferedOutputStream类覆盖了呗装饰的输出流的写数据行为,利用缓冲区来提高写数据的效率。BufferedOutputStream类先把数据写到缓冲区,在默认情况下,只有当缓冲区满的时候,才会把缓冲区的数据真正写到数据汇,这样能减少物理写数据的次数。

构造方法为:

BufferedOutputStream(OutputStream out)参数out指定需要被装饰的输入流

BufferedOutputStream(OutputStream out,int  size)参数out指定需要被装饰的输出流,参数size指定缓冲区的大小,以字节为单位。

(Tips:如果想要写的数据没有达到指定缓冲区size的长度,那么就不会写入文件。当读数据时,会抛出异常java.io.EOFException。为了保证BufferedOutStream会把缓冲区中的数据写到文件中,一种办法是调用flush()方法,该方法会立即执行一次把缓冲区中的数据写到输出流中的操作,还有一种办法是执行完输出流的write()方法后,关闭输出流。过滤输出流的close( )方法会先调用本身及被装饰的输出流的flush( )方法,这样就会保证假如过滤流本身或者被装饰的流带有缓冲区,那么缓冲区的数据也会被写到数据汇中。)




Reader/Writer

InputStream和OutputStream类处理的是字节流,也就是说,数据流中最小单元为一个字节,它包括8个二进制位。

Reader/Writer类,它们分别表示字符输入流和字符输出流。

在许多应用场合,Java程序需要读写文本文件。在文本文件中存放了采用特定字符编码的字符。

在处理字符流时,最主要的问题是进行字符编码的转换。Java语言采用Unicode字符编码。对于每一个字符,Java虚拟机会为其分配两个字节的内存。

char  c= '好'  ,这个字符类型变量c,Java虚拟机为它在内存中分配了两个字节,用来存放字符“好”的Unicode字符编码。“好”的Unicode字符编码为89和125,对应的二进制序列为:01011001  01111101。

同样,对于String类型的变量s,由于它仅仅包含一个字符 “好”,Java虚拟机也为它在内存中分配两个字节,用来存放字符 “好” 的Unicode字符编码

String类的getBytes(String  encode)方法返回字符串的特定类型的编码,encode参数指定编码类型。String类的不带参数的getBytes( )方法则使用本地操作系统的默认字符编码。

如果要输入或输出采用特定类型编码的字符串,可以使用InputStreamReader类和OutputStreamWriter类。在他们的构造方法中,可以指定输入流或输出流的字符编码。



Reader类


字符数组输入流:CharArrayReader类


CharArrayReader类从内存中的字符数组中读取字符,因此它的数据源是一个字符数组。

构造方法:

CharArrayReader(char[ ]  buf)参数buf指定字符数组类型的数据源

CharArrayReader(char[ ]  buf,int  offset,int  length)参数buf指定字符数组类型的数据源,参数offset指定在数组中开始读数据的下标位置,length指定从数组中读取的字符数

字符数组输出流:CharArrayWriter类



字符串输入流:StringReader类

StringReader类的数据源是一个字符串。

构造方法:

StringReader(String  s)参数s指定字符串类型的数据源

InputStreamReader类

构造方法:

InputStreamReader(InputStream  in):按照本地平台的字符编码读取输入流中的字符。

InputStreamReader(InputStream  in,String  charsetName)按照参数charsetName指定的字符编码读取输入流中的字符。

例子:

InputStreamReaderisr = new InputStream(new FileInputStream("D:\\test.txt"),"UTF-8");

char  c = (char)isr.read();

以上代码指定输入流的字符编码UTF-8,InputStreamReader的read( )方法从输入流中读取一个UTF-8字符,在把它转换为Unicode字符。以上代码中的变量c为Unicode字符,在内存中占两个字节。假定InputStreamReader的read( )方法从输入流中读取的字符为 " 好 "  ,read( )方法实际上执行了以下步骤。

1:从输入流中读取3个字节,229,165,189。这3个字节代表字符 “ 好 ” 的UTF-8字符编码。

2:计算出字符 “ 好 ” 的Unicode字符编码为89和125。

3:为字符 “ 好 ” 分配两个字节的内存空间,这两个字节的取值分别为89和125.。为了提高读操作的效率,可以用BufferedReader来装饰InputStreamReader。

OutputStreamWriter类

构造方法:

OutputStreamWriter(OutStream  out):按照本地平台的字符编码向输出流写入字符。

OutputStreamWriter(OutStreamout,String  charsetName) :按照参数charsetName指定的字符编码向输出流写入字符。

FileWriter类

构造方法:

FileWriter(File  file):参数flie指定需要写入数据的文件

FileWriter(String  name):参数name指定于需要写入数据的文件的路径。



FileReader类

FileReader是InputStreamReader的一个子类,用于从文件中读取字符数据。该类只能按照本地平台的字符编码来读取数据,用户不能指定其他字符编码类型。

构造方法:

FileReader(File  file):参数file指定需要读取的文件

FileReader(String  name):参数name指定需要读取的文件的路径

BufferedReader类

Reader类的read( )方法每次都从数据源读入一个字符,为了提高效率,可以采用BufferedReader来装饰其他Reader。BufferedReader带有缓冲区。

BufferedReader的 readLine( )方法可以一次读一行字符,以字符串形式返回。

构造方法:

BufferedReader(Reader  in):参数in指定被装饰的Reader类。

BufferedReader(Reader  in,int  size):参数in指定被装饰的Reader类,参数size指定缓冲区的大小,以字符为单位。


BufferedWriter类

BufferedWriter类带有缓冲区,BufferedReader有readLine( )方法,但是BufferedWriter没有写一行的方法。如果要输出一行字符串,应该在用PrientWriter来装饰。

构造方法:

BufferedWriter(Writer  out):参数out指定被装饰的Writer类。

BufferedWriter(Writer  out,int   size):参数out指定被装饰的Writer类,参数size指定缓冲区的大小,以字符为单位。

随机访问文件类:RandomAccessFile

RandomAccessFile类不属于流,它具有随机读写文件的功能,能够从文件的任意位置开始执行读写操作。

构造方法:

RandomAccessFile(File  file,String  mode):参数file指定被访问的文件。mode参数指定访问模式  r  w  d等

RandomAccessFile(String  name,String  mode):参数name指定被访问的文件的路径。mode参数指定访问模式  r  w  d等

普通方法:

getFilePointer( ):返回当前读写指针所处的位置。

seek(long  pos):设定读写指针的位置,与文件开头相隔pos个字节数。

skipBytes(int  n):使读写指针从当前位置开始,跳过n个字节。

length() :返回文件包含的字节数。


Flie类

File类提供了管理文件或目录的方法。File实例表示真实文件系统中的一个文件或者目录。

构造方法:

File(String  pathname):参数pathname表示文件路径或者目录路径

File(String parent,String  child):参数parent表示根路径,参数child表示子路径

File(File parent,String  child):参数parent表示根路径,参数child表示子路径

使用何种构造方法取决于程序所处理的文件系统。一般来说,如果程序只处理一个文件,那么使用第一个构造方法;如果程序处理一个公共目录的若干子目录或文件,那么使用第二个或者第三个构造方法会更方便。


File类主要提供以下管理文件系统的方法

1:boolean canRead():测试程序是否能对该File对象所代表的文件进行读操作。

2:boolead   canWrite() :测试程序是否能写该File对象锁代表的文件。

3:boolean delete():删除该File对象所代表的文件或者目录。如果File对象代表目录,并且目录下包含子目录或文件,则不允许删除File对象代表的目录。

4:boolead exists():测试该File对象所代表的文件或者目录是否存在。

5:String  getAbsolutePath() :获取该File对象所代表的文件或者目录是否存在。

6:String  getCanonicalPaht() :获取该File对象所代表的文件或者目录的正规路径,它会解析符号   .   (当前目录), 或者符号   ..  (上级目录),并返回该File对象所代表的    文件或者目录在文件系统中的唯一标示。

7:String  getName() :获取该File对象所代表的文件或者目录的名字。

8:String  getParent() :获取该File对象所代表的文件或者目录的根路径,如果没有的话,就返回null。

9:String  getPath() :获取该File对象所代表的文件或者目录的路径。

10:boolean  isAbsolute() :测试该File对象所代表的文件或者目录是否用绝对路径来表示。

11:boolean   isDirectory():测试该File对象是否代表一个目录。

12:boolean   isFile():测试该File对象是否代表一个文件。

13:long   lastModified() :返回该File对象所代表的文件或者目录最近一次被修改的时间。

14:long  length() :返回该File对象所代表的文件长度,以字节为单位。

15:String[ ]  list(),String[ ]   list(FilenameFilter):如果该File对象代表目录,则返回该目录下所有文件和目录的名字列表。如果指定FilenameFilter参数,则返回所有满    足过滤条件的文件和目录的File对象。

16:File[ ]  listFiles(),File[ ]  listFiles(FilenameFilter):如果该File对象代表目录,则返回该目录下所有文件和目录的名字列表。如果指定FilenameFilter参数,则返回所有满    足过滤条件的文件和目录的File对象。

17:boolean    mkdir():在文件系统中创建由该File对象表示的目录。

18:boolead    mkdirs():在文件系统中创建由该File对象表示的目录。如果该目录的父目录不存在的话,那么还会创建所有的父目录。

19:boolean   renameTo(File  file) :如果该File对象代表文件,则将文件名改为file参数所表示的文件文件名。

20:boolean    createNewFile():如果该File对象代表文件,并且该文件在文件系统中不存在,就在文件系统中创建这个文件,内容为空。


小结:

Java    I/O类库对各种常见的数据源,数据汇及处理过程进行了抽象。客户程序不必知道最终的数据源或数据汇是一个磁盘上的文件还是一个内存中的数组,都可以按照统一的接口来处理程序的输入和输出。

java  I/O类库具有两个对称性

输入  输出对称

InputStream和OutputStream对称

FilterInputStream和FilterOutputStream对称

DataInputStream和DataOutputStream对称

Reader和Writer对称

InputStreamReader和OutputStreamWriter对称

BufferedReader和BufferedWriter对称

字节流和字符流对称,例如InputStream和Reader分别表示字节输入流和字符输入流,OutputStream和Writer分别表示字节输出流和字符输出流


装饰器模式:在由InputStream,OutputStream,Reader和Writer代表的层次结构中,有一些过滤流可以对另一些流起到装饰作用,从而增加新的功能,或者增强原有的功能

比如DataInputStream过滤流提供了读取格式化数据的功能,BufferedInputStream过滤流则提供了读缓冲区的功能,能够提高读操作的效率。

适配器模式:比如ByteArrayInputStream把字节数组类型转换为InputStream类型,使得客户程序可以通过InputStream接口从字节数组中读取字节:

比如StringReader把String类型转换为Reader类型,使得客户程序可以通过Reader接口从字符串中读取字符:

比如InputStreamReader把InputStream类型转换为Reader类型,使得客户程序可以通过Reader接口从底层的字节流中读取指定字符编码类型的字符。


第十九章 Java常用类



Object类


Object类有以下主要成员方法:

equals(Objecet   obj) :比较两个对象是否相等。仅当被比较的两个引用变量指向同一对象时,equals( )方法才返回true。

notify( ) :从等待池中唤醒一个线程,把它转移到锁池。

wait( ) :使当前线程进入等待状态,直到别的线程调用notify( )或notifyAll( )方法唤醒它。

hashCode( ) :返回对象的哈希码。HashTable和HashMap会根据对象的哈希码来决定它的存放位置。

toString( ) :返回当前对象的字符串表示,格式为  类名@对象的十六进制哈希码。

finalize( ) :对于一个已经不被任何引用变量引用的对象,当垃圾回收器准备回收该对象所占有内存时,将自动调用该对象的finalize()方法


String类和StringBuffer类

String类和StringBuffer类主要用来处理字符串,这两个类提供了很多字符串的实用处理方法。String类是不可变类,一个String对象所包含的字符串内容永远不会被改变。

StringBuffer类是可变类,一个StringBuffer对象所包含的字符串内容可以被添加或修改。


String类有一些构造方法:

String( ) :构建一个内容为空的字符串“”。

String(String value) :字符串参数指定字符串的内容。

String(char[ ]  value) :字符数组参数指定字符串的内容。

String(byte[ ]  bytes) :根据本地平台默认的字符编码。由字节数组构造一个字符串。

String类有以下常用方法:

length( ) :返回字符串的字符个数

char  charAt(int  index) :返回字符串中index位置上的字符,其中index的取值范围是0到字符串长度-1。

getChars(int  srcBegin,int  srcEnd,char  dst[ ],int  dstBegin) :从当前字符串中拷贝若干字符到参数指定的字符数组dst[ ]中。从srcBegin位置开始取 字符,到srcEnd-1位置结束,dstbegin为提取的字符存放到字符数组中的起始位置。如果参数dst为null,则会抛出NullPointerException。

equals(object  str)和equalsIgnoreCase(object  str) :判断两个字符串对象的内容是否相同。两个方法区别在于,后者忽略大小写,前者区分大小写。

int  compareTo(String  str) :按字典次序比较两个字符串的大小。如果源串较小,则返回小于0的值,相等返回0,否则返回大于0的值。

indexOf( )和lastIndexOf( ) :在字符串中检索特定字符或子字符串。前者从首位查找,后者从末尾开始查找。如果找到,就返回匹配成功的位置,如果没找 到,就返回-1。

concat(String  str) :把参数str字符串附加在当前字符串的末尾。

substring(int  beginIndex) :返回字符串的一个子字符串,从参数beginIndex开始到末尾。

substring(int  beginIndex,int  endIndex) :返回字符串的一个子字符串,从参数beginIndex开始到endIndex-1结束。

String[ ]  split(String  regex) :根据参数regex把原来的字符串分割为几个子字符串。

例如: String  s= “i=j”;

s.split(“=”); //输出结果为ij。

replaceAll(String  regex,String  replacement) :把源字符串中的regex替换为replacement。

replaceFirst(String  regex,String  replacement) :仅把源字符串中的第一个regex替换为replacement。

trim( ) : 把字符串首尾的空格删除。

String  valueOf( ) :把基本类型转换为String类型。

toUpperCase( )和toLowerCase( ):前者把字符串字母全部改为大写,后者把所有字符串改为小写。


"hello"与 new  String("hello")的区别

1:String s1=“hello” ;String  s2=“hello”

      hello为直接数,Java虚拟机把它作为编译时常量,在内存中只会为它分配一次内存,然后就可以重复使用,因此s1==s2

2:String  s3=new Sting(“hello”) ;String  s4=new String(“hello”)

     每个new语句都会新建一个String对象,因此s1==s2结果为false。

(Tips:从提高程序性能的角度考虑,减少java虚拟机为对象分配内存及回收内存的次数,可以减轻Java虚拟机的负担,从而提高程序性能。因此如果在编程时就明确知道字符串的内容,应该用字符串直接数来为String类型变量赋值。)


StringBuffer类

构造方法:

StringBuffer( ) :建立一个空的缓冲区,默认长度为16

StringBuffer(int length) :建立一个长度为length的空缓冲区。

StringBuffer(String str) :缓冲区的初始内容为字符串str,并提供了16个字符的空间供再次分配。

StringBuffer类有以下常用方法。

length( ) :返回字符串的字符个数,与String类的length( )用法相同。

append( ) :向缓冲区内添加新的字符串。

toString( ) :返回缓冲区内的字符串。

charAt(int index) :返回字符串中index位置的字符。

setCharAt(int index,char  ch) :在index位置放置字符ch。

getChars(int  srcBegin,int  srcEnd,char[ ]  dst,int  dstBegin) :用法与Sting类的getChars( )方法相同。

substring( ) :用法与String类的substring( )相同。

insert(int  offset,String  str) :在字符串中的offset位置插入字符串str。



String类和StringBuffer类的不同

1:String类是不可变类,而StringBuffer是可变类。String对象创建后,它的内容无法改变。一些看起来能够改变的字符串的方法,实际上是创建一个带有方 法所赋予特性的新字符串,。例如String类的substring( ),concat( ),toLowerCase( ),toUpperCase( )和trim( )等方法都不会改变字符串本身,而是创建并返 回一个包含改变后内容的新字符串对象。

     而StringBuffer的append( ),replaceAll( ),replaceFirst( ),insert( )和setCharAt( )等方法都会改变字符缓冲区中的字符串内容。

2:String类覆盖了Object类的equals( )方法,而StringBuffer类没有覆盖Object类的equals( )方法。

3:两个类都覆盖了Object类的toString( )方法,但是各自的实现方式不一样:String类的toString( )方法返回当前String实例本身的引用,而StringBuffer类的      toString( )方法返回一个以当前StringBuffer的缓冲区中的所有字符为内容的新的String对象的引用。

4:String类对象可以用操作符+进行连接,而StringBuffer类对象之间不能通过操作符+进行连接。

(Tips:例如在读取一行文本的情况下,使用StringBuffer可以减少java虚拟机创建String对象的次数,减少动态分配和回收内存的次数,提高程序的性能)

正则表达式


正则表达式用来描述特定的字符串模式,例如表达式   a{3}  表示由三个字符 a 构成的字符串,相当于普通字符串“aaa”。


特殊字符               描述

. 表示任意一个字符

[abc] 表示a  b  c中的任意一个字符

[^abc] 表示除a  b  c以外的任意一个字符

[a-zA-Z] 介于a到z,或A到Z中的任意一个字符

\s 空白符(空格,tab,换行,换页,回车)

\S 非空白符

\d 任意一个数字[0-9]

\D 任意一个非数字[^0-9]

\w 词字符[a-zA-Z_0-9]

\W 非词字符


表示字符出现次数的符号


* 0次或者多次

+ 1次或者多次

? 0次或者1次

{n} 恰好n次

{n,m} 至少n次,不多于m次



包装类

基本类型 对应包装类

boolean Boolean

byte Byte

char Charactor

short Short

int Integer

long Long

float Float

double Double


包装类的常用方法

Charactor类和Boolean类直接继承Object类,除此之外,其他包装类都是Java.Number的直接子类,因此都继承或者覆盖了Number类的方法。


Number类的主要方法如下

byteValue( ) :返回Number对象所表示的数字的byte类型值。

intValue( ) :返回Number对象所表示的数字int类型值。

   longValue( )    :返回Number对象所表示的数字的long类型值。

   shortValue( )    :返回Number对象所表示的数字short类型值。

   doubleValue( )    :返回Number对象所表示的数字的double类型值。

   floatValue( )    :返回Number对象所表示的数字float类型值。

包装类的方法

1:包装类都覆盖了Object类的toString( )方法,以字符串的形式返回包装对象所表示的基本类型数据。

2:除了Charactor类和Boolean类以外,包装类都有valueOf(String   s)静态工厂方法,可以根据String类型的参数来创建包装类对象。参数s不能为null,而且该字符串必须可以解析为相应的基本类型的数据,否则虽然编译会通过,但运行时会抛出NumberFormatException

3:除了Charactor类和Boolean类以外,包装类都有parseXXX(String  str)静态方法,把字符串转换为相应的基本类型的数据(XXX表示基本数据类型的名称)。参数str不能为null,而且该字符串必须可以解析为相应的基本数据类型的数据,否则虽然编译会通过,但运行时会抛出NumberFormatException

包装类的特点

1:所有包装类都是final类型,因此不能创建它们的子类。

2:包装类是不可变类,一个包装类的对象自创建后,它所包含的基本类型数据就不能被改变。

3:允许基本类型和包装类型进行混合数学运算。

如:

Integer  a;

Integer  b;

int  sum=a.intValue( )+b.intValue( );


Math类

是Math类有两个静态常量:E (自然对数) 和 PI (圆周率)。Math类的构造方法是private类型的,因此不能被实例化。

Math类的主要方法:

abs( ) :返回绝对值

ceil( ) :返回大于或等于参数的最小整数

floor( ) :返回小于或等于参数的最大整数

max( ) :返回两个参数的较大值

min( ) :返回两个参数的较小值

random( ) :返回0.0和1.0之间的double类型的随机数,包括0.0,但不包括1.0

round( ) :返回四舍五入的整数值

sin( ) :正弦函数

cos( ) :余弦函数

tan( ) :正切函数

exp( ) :返回自然对数的幂

sqrt( ) :平方根函数

pow( ) :幂运算


Random类

java.util.Random类提供了一系列用于生成随机数的方法。

nextInt( ) :返回下一个int类型的随机数,随机数的值大于或等于0

nextInt(int  n) :返回下一个int类型的随机数,随机数的值大于或等于0,并且小于参数n

nextLong( ) :返回下一个long类型的随机数,随机数的值位于long类型的取值范围

nextFloat( ) :返回下一个float类型的随机数,随机数的值大于或者等于0,并且小于1.0

nextDouble( ) :返回下一个double类型的随机数,随机数的值大于或等于0,并且小于1.0

nextBoolean( ) :返回下一个boolean类型的随机数,随机数的值大于或等于0








笔记不圆满完成。。。。。。。。。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值