、类与对象
1、面向对象的特征有哪些方面,OOP的好处
类是具备某些共同特征的实体的集合,它是一种抽象的概念,用程序设计的语言来说,类是一种抽象的数据类型,它是对所具有相同特征实体的抽象。类定义了类的元素(属性和方法),实际上,类也可理解为带方法的类型。
1.抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象包括两个方面,一是过程抽象,二是数据抽象。
2.继承:
继承是一种类的层次模型,允许和鼓励类的重用,它提供了一种明确表述共性的方法。客观事物既有共性,也有特性。如果只考虑事物的共性,而不考虑事物的特性,就不能反映出客观世界中事物之间的层次关系。抽象机制是考虑事物的共性,继承机制是考虑事物的特性,这样才能完整地描述客观世界的层次关系。继承能使软件模块具有可重用性、独立性,缩短软件开发周期,提高软件开发效率,同时使软件易于维护。
3.封装:
封装是把过程和数据包围起来,没必要直接去操作对象属性,只要调用这些方法就可以实现要完成的任务,这种现象称为封装,它把对象属性封装在一个对象内部,对象与外界打交道全部通过其自身的方法来实现,有效的把对象属性隐藏在对象内部。但是如果一味地强调封装,对象的任何属性都不允许外部直接存取,则要增加许多没有其他意义、只负责读或写的行为。这会为编程工作增加负担,增加运行开销,并且使程序显得臃肿。为了避免这一点,在程序的具体实现过程中应使对象有不同程度的可见性,进而与客观世界的具体情况相符合。
4. 多态性:
多态性是指在一个程序中同名的不同方法共存的情况(类继承中父类和子类中可以有多个同名但不同意义或实现方式的属性和方法),允许不同类的对象对同一消息作出不同响应。
2.面向对象与面向过程的区别
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。算法第一,方法第二。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。方法第一,算法第二。
面向对象与面向过程程序设计有如下不同:
(1)面向过程采用函数(或过程)来描述对数据的操作,但又将函数与其操作的数据分离开来;面向对象程序设计方法将数据和对数据的操作封装在一起,作为一个整体来处理。
(2)面向过程以功能为中心来设计功能模块,难于维护;而面向对象程序设计方法以数据为中心来描述系统,数据相对于功能而言具有较强的稳定性,因此更易于维护。
(3)面向过程程序的控制流程由程序中预定顺序来决定;面向对象程序的控制流程由运行时各种事件的实际发生来触发,而不再由预定顺序来决定,更符合实际需要。
面向对象技术具有程序结构清晰,实现简单,可有效地减少程序的维护工作量,代码重用率高,软件开发效率高等优点。
3. 什么是OOP?什么是类?请对比类和实例之间的关系。
OOP指的是用对象的观点来组织与构建系统,它综合了功能抽象和数据抽象,这样可以减少数据之间的耦合性和代码的出错几率。
类:即用户定义的抽象数据类型。它将具有相同状态、操作和访问机制的多个对象进行了抽象。类具有继承、封装和多态三种主要特性。利用类的这三种特性可以更好地表示现实世界中事物。
类是同一类对象实例的共性的抽象,对象是类的实例化。类定义了对象的实现细节或数据结构。类是静态的,对象是动态的,对象可以看作是运行中的类。
4.抽象类和接口
4.1abstract class和interface有什么区别?
抽象类用于要创建一个体现某些基本行为的类,不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类。abstract方法不能被声明为private,因为abstract类会被其他类继承。无法生成一个abstract类的对象,但可声明一个abstract类类型的变量。(is-a)
接口中所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口只可以定义static final成员变量。允许使用接口名作为引用变量的类型。通常的动态联编将生效。instanceof 运算符可以用来决定某对象的类是否实现了接口。(has-a)
以下情况,此类必须声明为abstract:
该类有一个或超过一个abstract方法(声明但没有定义的方法)
该类从一个abstract类继承一个或超过一个abstract方法,但没有提供这些方法的实现方法
该类实现一个接口,但没有将该接口所有的方法加以实现
interface的特性整理如下:
1. 接口中的方法可以有参数列表和返回类型,但不能有任何方法体。
2. 接口中可以包含字段,但是会被隐式的声明为static和final。
3.接口中的字段只是被存储在该接口的静态存储区域内,而不属于该接口。
4. 接口中的方法可以被声明为public或不声明,但结果都会按照public类型处理。
5. 扩展一个接口来生成新的接口应使用关键字extends,实现一个接口使用implements。
interface在某些地方和abstract有相似的地方,但是采用哪种方式来声明类主要参照以下两点:
1.如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。
2.如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。
4.2 Java的接口和C++的虚类的相同和不同处
在java中,只含有抽象方法的类叫做接口,含有抽象方法的类是抽象类。
C++中,含有纯虚函数的类被称为抽象类,虚类相当于java 中的抽象类。纯虚函数和虚函数的区别在于前者不包含定义,而后者包含函数体。那么不包含任何实现、不包含任何算法和结构的类叫做纯虚类。
4.3 接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concreteclass)?
一个接口是一群常量和abstract方法的组合,接口内的常量总是为public和static以及final,而方法总为public和abstract,即使没有明确地声明。一个接口可通过extends来继承其他接口,不同的是,接口支持多重继承,这是由于接口内方法未定义,并不会产生类多重继承的问题。
接口可以继承接口。抽象类可以实现(implements)接口,抽象类是否可继承实体类可以继承其他类,比如默认继承Object。但前提是实体类必须有明确的构造函数 。所谓“明确的构造函数”跟子类是否是抽象类无关,指的是“如果父类没有缺省构造函数,则子类必须声明一个同父类构造函数同样签名的构造函数”。
5.类的特性
5.1 访问权限
作用域 当前类 同一package 子孙类 其他package
public √ √ √ √
protected √ √ √ ×
friendly √ √ × ×
private √ × × ×
C++中不写时默认为friendly,指包访问权限,意味着当前的包中的所有其他类对那个成员都有访问权限。Java中没有友元的概念,可以借用内部类实现。(how to do it?)
java有四种访问权限:1. private 私有;2.Default (默认的) 只有包内的类才能被访问,所以有"包访问权限"的称呼;3.protected 不但包内的类可以访问,而且继承的子类也可以访问;4. public 公有,都可以访问。Java中所谓的“friendly”和“default”都只是一种说法,并不是说真有那么一个指定默认访问权限的关键字(default倒的确是个语言关键字,但那是用在switch语句中的)。default 只能在自己的包里可见;protected在任何包里的子类里可见;java中默认的访问级别是同类和同包中可见。
class Base{
protected void print(){System.out.println("Base");}
public void srint(){System.out.println("Base");}
}
public class Derived extends Base{
public void rrint(){System.out.println("Derived");}
//void print(){System.out.println("Derived");}错误,不能降低原有父类方法中的权限
//private void print(){System.out.println("Derived");}错误,不能降低原有父类方法中的权限
//protected void print(){System.out.println("Derived");} 正确
public void print(){System.out.println("Derived");} 正确
//default void print(){System.out.println("Derived");} 错误不存在default权限关键字
//friendly void print(){System.out.println("Derived");} 错误 不存在friendly权限关键字
public static void main(String[]args){
Base p=new Derived();
p.print();//resultis Derived
p.srint();//result isBase
//p.rrint();//错误,不能访问只在子类中存在的方法
} }
------------------------------------------------------------------------
class Cleanser {
private String s = new String("Cleanser"); //私有变量无法在父类中继承
public void append(String a) { s += a; }
public void scrub() { append(" scrub()"); }
public void dilute() { append(" dilute()"); }
public String toString(){return s;}
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute();
System.out.print(x);
} }
public class Detergent extends Cleanser {
public void scrub() {
append(" Detergent.scrub()");
super.scrub();
}
public void foam() { append("foam()"); }
public static void main(String[] args) { //允许多个main方法
Detergent x = new Detergent();
x.dilute();
x.scrub();
x.foam();
System.out.print(x);
System.out.println("Testing base class:");
Cleanser.main(args);
}
}
//程序的输出结果是:
Cleanser dilute() Detergent.scrub() scrub()foam()
Testing base class:
Cleanser dilute()
5.2常用的类,接口
常用的接口:
1.java.lang.Cloneable可克隆接口。可以通过Object.clone()方法将类的实例对象的域(field)逐个复制到同一个类的另外一个实例中。
2.java.lang.Comparable可比较接口。实现了该接口的类的两个实例对象之间可以进行比较。比较结果负数(-1)、0、正数(1)分别代表比较对象与被比较对象之间的关系分别是小于、等于、大于。可对实现了该接口的类的多个实例对象进行排序。
3.java.lang.Iterable可迭代接口。实现了该接口的类一般作为容器。
4.java.lang.Runnable可运行接口。实现了该接口的类的实例对象将在一个单独的线程(thread)中被运行。没有扩展Thread的类可以通过实现该接口,开发出多线程服务的能力。
常用的类:
1.java.lang.Class该类的实例对象表达Java应用中正在运行的类或者接口。该类没有公共的构造方法,所以不能由Java虚拟机自动实例化,而是由ClassLoader实例化。
2.java.lang.ClassLoader该类是Java类加载器,负责根据制定的二进制名称加载相应的类。读取二进制的类“*.class”信息,并生成Class对象。
3.java.lang.Integer将原始数据类型int对象化的类。
4.java.lang.Math该类提供了基本的数学运算方法。譬如:乘方、开方、指数、角度运算等。
5.java.lang.Object该类的对象是Java平台中所有其他类都直接或者间接地扩展了该类。该类提供了缺省的对象操作方法。
5.3 Java类库中的包
A. java.lang 提供常用的类、接口、一般异常、系统等编程语言的核心内容。如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包。
1.java.lang包中的元素:
(1)接口摘要:
Cloneable接口;Comparable接口;Iterable接口;Runnable接口。
(2)类摘要
Boolean Boolean 类将基本类型为 boolean 的值包装在一个对象中。
Byte Byte 类将基本类型 byte 的值包装在一个对象中。
Class<T> Class 类的实例表示正在运行的 Java 应用程序中的类和接口。
ClassLoader 类加载器是负责加载类的对象。
Double 类在对象中包装了一个基本类型 double 的值。
Float 类在对象中包装了一个 float 基本类型的值。
Integer 类在对象中包装了一个基本类型 int 的值。
Long 类在对象中封装了基本类型 long 的值。
Math 类包含基本的数字操作,如指数、对数、平方根和三角函数。
Number 抽象类Number 是 BigDecimal、BigInteger、Byte、Double、Float、Integer、Long 和 Short 类的超类。
Object 类 Object是类层次结构的根类。
String 类代表字符串。
StringBuffer线程安全的可变字符序列。
StringBuilder 一个可变的字符序列。
System 类包含一些有用的类字段和方法。
Thread类线程。
Throwable类是 Java 语言中所有错误或异常的超类。
Void 类是一个不可实例化的占位符类。
(3)异常摘要
ArithmeticException 当出现异常的运算条件时,抛出此异常。
ArrayIndexOutOfBoundsException 用非法索引访问数组时抛出的异常。
ClassCastException 将对象强制转换为不是实例的子类时,抛出该异常。
ClassNotFoundException 当应用程序试图使用以下方法通过字符串名加载类时,抛出该异常: Class类中的 forName 方法。
IllegalAccessException 当应用程序试图创建一个实例(而不是数组)、设置或获取一个字段,或者调用一个方法,但当前正在执行的方法无法访问指定类、字段、方法或构造方法的定义时,抛出 IllegalAccessException。
IllegalArgumentException抛出的异常表明向方法传递了一个不合法或不正确的参数。
IndexOutOfBoundsException指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
InterruptedException 当线程在很长一段时间内一直处于正在等待、休眠或暂停状态,而另一个线程用 Thread 类中的 iterrupt 方法中断它时,抛出该异常。
NegativeArraySizeException 如果应用程序试图创建大小为负的数组,则抛出该异常。
NoSuchFieldException 类不包含指定名称的字段时产生的信号。
NoSuchMethodException 无法找到某一特定方法时,抛出该异常。
NullPointerException 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
RuntimeException RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
SecurityException 由安全管理器抛出的异常,指示存在安全侵犯。
(4)错误
Error Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。
IllegalAccessError 当应用程序试图访问或修改它不能访问的字段,或调用它不能访问的方法时,抛出该异常。
InternalError 该异常指示 Java 虚拟机中出现一些意外的内部错误。
NoSuchFieldError ;NoSuchMethodError ;
OutOfMemoryError 因为内存溢出或没有可用的内存提供给垃圾回收器时,Java 虚拟机无法分配一个对象,这时抛出该异常。
StackOverflowError当应用程序递归太深而发生堆栈溢出时,抛出该错误。
UnknownError 当 Java 虚拟机中出现一个未知但严重的异常时,抛出该错误。
VirtualMachineError 当 Java 虚拟机崩溃或用尽了它继续操作所需的资源时,抛出该错误。
2.java.lang.reflect包:提供用于反射对象的工具
3.java.util 提供了丰富的常用工具类。包含日期、日历、向量、堆栈等实用工具。例如,Java提供日期(Data)类、日历(Calendar)类来产生和获取日期及时间,提供随机数(Random)类产生各种类型的随机数,还提供了堆栈(Stack)、向量(Vector) 、位集合(Bitset)以及哈希表(Hashtable)等类来表示相应的数据结构。包含如处理时间的date类,处理变成数组的Vector类,以及stack和HashTable类
接口:Collection;Iterator ;List ;ListIterator;Map ;Queue ;Set;SortedMap ;SortedSet
类:AbstractCollection;AbstractList;AbstractMap;AbstractQueue ;AbstractSet ;ArrayList ;
Arrays ; Calendar ;Collections ;Date ;Dictionary ; EventObject ;Formatter;HashMap ;
HashSet ;Hashtable ;LinkedList ;Locale ;Random ;Scanner ;Stack ;StringTokenizer ;Timer ;TreeMap;TreeSet ;Vector
异常:IllegalFormatException;NoSuchElementException
4.java.io java语言的标准输入/输出类库,如基本输入/输出流、文件输入/输出、过滤输入/输出流等等
5.java.awt 包含窗口和屏幕元素类,事件处理接口等与图形用户界面有关的内容。其部分功能正被javax.swing取代。构建图形用户界面(GUI)的类库,低级绘图操作Graphics类,图形界面组件和布局管理如Checkbox类、Container类、LayoutManger接口等,以及界面用户交互控制和事件响应,如Event类。
6.java.awt.event包:GUI事件处理包。
7.java.awt.image包:处理和操纵来自于网上的图片的java工具类库
8.java.applet 提供为编写applet小程序所需要的类。
9.java.net 包含url类等与网络传输有关的东西。实现网络功能的类库有Socket类、ServerSocket类
10.java.sql 提供与数据库应用相关的类和接口。
B. javax.* 包概述
javax.*包是对核心java.*包的有力扩展。提供了援助、加密、图像IO、管理、命名服
务、网络、打印、远程方法调用、安全、声音、SQL、GUI、事务以及XML等扩展功能。
javax.naming提供了访问命名服务所需的类和接口。
javax.net提供了网络应用所需的类。
javax.rmi包含了RMI-IIOP(Remote Method Invocation - Internet Inter-Operability
Protocol )的用户API。
javax.sql 提供了服务期端访问和处理数据源所使用的API。
javax.swing 提供了支持Java GUI程序能够最大限度地在所有平台上,以同样方式工作所需的一系列
javax.transaction定义了事务管理器与资源管理器之间的规约。以及ORB(object request broker)在解码是可能抛出的三异常。
javax.xml定义了XML规范描述的核心常量和功能性需求。
3.3构造器Constructor
答:构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。
构造器用来确保每个对象都会得到初始化。当对象被创建时,如果该类具有构造器,java就会在用户有能力操作对象之前自动调用相应的构造器,所以保证了初始化地进行。在java中创建和初始化被捆绑在了一起,两者不能分离。注意构造器没有返回值,与返回值为空(void)明显不同。如果不想被实例化,可以将构造函数声明为private类型。
定义一个Java类时,如果没有显式定义一个不带参数的构造函数,则可以用不带参数的构造函数创建一个该类的实例。如果没有显式定义一个不带参数的构造函数但定义了一个带参数的构造函数,则不能再以不带参数的构造函数创建一个该类的实例:[即如果已经定义了一个构造函数(无论有无参数),编译器就不会帮你自动创建缺省构造函数]
这里提到this关键字,它只能在方法内部使用,表示对“调用方法的那个对象”的引用。可以使用this关键字在构造器中调用构造器。例:
Publicclass Flower{
Static Test monitor=new Test();
Int petalCount=0;
String s=new String(“null”);
Flower(intpetals){petalCount=petals;}
Flower(Stringss){S=ss;}
Flower(Strings,int petals){
This(petals);
//This(s);这里是不允许的,因为尽管可以用this调用一个构造器,但是不能调用2个
This.s=s;}
}
publicclass A {
public A(String s) { }
public static void main(String[] args) {
A a = new A(); // Incorrect,抱错显示没有找到匹配的构造器
A a = new A("a"); //Correct
}}
所有类都是继承于Object的,在所有类实例化为对象的时候,必然会先调用Object的默认构造函数。static方法就是没有this的方法,static方法内部不能调用非静态方法。如果一个类的构造函数是静态的,那么它将无法调用Object的构造函数。因此构造函数不能声明为static类型。如果你想让一个类的对象只有指定的静态方法可以返回,你可以这样。
class Test {
private Test(){ }//私有构造方法,这样外部将无法创建此类的对象。联想单例模式
public static Test getNewTest(){
return new Test(); //静态公共方法,返回Test的对象。
} }
(1) 类可以没有构造方法,但如果有多个构造方法,就应该要有默认的构造方法,否则在继承此类时,需要在子类中显式调用父类的某一个非默认的构造方法了。(2) 在一个构造方法中,只能调用一次其他的构造方法,并且调用构造方法的语句必须是第一条语句。
一个类继承了另一个类的内部类,因为超类是内部类,而内部类的构造方法不能自动被调用,这样就需要在子类的构造方法中明确的调用超类的构造方法。
class Cextends A.B {
C(){ new A().super(); // 这一句就实现了对内部类构造方法的调用。
}}
构造方法也可这样写:
C(A a) {
a.super();
} //使用这个构造方法创建对象,要写成C c = new C(a); a是A的对象。
-------------------------------------------------------------------------------------------------------------------------易错题:
class A {
publicint Avar;
publicA() {
System.out.println("AAA");
doSomething();
}
publicvoid doSomething() {
Avar =1111;
System.out.println("A.doSomething()");
}
}
publicclass B extends A {
publicint Bvar = 2222;
publicB() {
System.out.println("BBB");
doSomething();
System.out.println("Avar="+ Avar);
}
publicvoid doSomething() {
System.out.println("Bvar="+ Bvar);
}
publicstatic void main(String[] args) {
new B();
}
}
顺序是这样得,首先生成B就得先生成A,所以调用A的构造器,输出AAA,然后调用方法dosomething,注意:A的该方法被B覆盖,而你生成的是B的对象,所以它调用B的方法,由于BVAR目前没有给定值,所以自动初始化为0;
然后生成B对象,先初始化变量BVAR,然后调用构造器输出BBB,然后调用方法,这时BVAR已初始化,所以输出BVAR=2222,而对象A中变量AVAR由于没有调用对象A的方法dosomething,所以其值为0,则输出0
全部输出就如下:
AAA
Bvar=0
BBB
Bvar=2222
Avar=0
注意:初始化顺序,当继承时,先生成超类对象,生成对象时,先生成静态变量,然后是一般变量,然后调用构造器!当所有超类对象生成后,生成本对象,顺序一样! 当方法被覆盖时,调用目前对象的方法!这得注意。
3.5 类变量,实例变量
1)实例变量和类变量
每个对象的实例变量都分配内存;
类变量(即static变量)仅在生成第一个对象时分配内存,所有实例对象共享同一个类变量,每个实例对象对类变量的改变都会影响到其它的实例对象。类变量可通过类名直接访问,无需先生成一个实例对象,也可以通过实例对象访问类变量。
区别在于:类变量是所有对象共有,其中一个对象将它值改变,其他对象得到的就是改变后的结果;而实例变量则属对象私有,某一个对象将其值改变,不影响其他对象;
2)实例方法和类方法
实例方法可以对当前对象的实例变量进行操作,也可以对类变量进行操作,实例方法由实例对象调用。
但类方法不能访问实例变量,只能访问类变量。类方法可以由类名直接调用,也可由实例对象进行调用。类方法中不能使用this或super关键字。
3.6全局和局部变量的区别,程序中怎么实现?编译器和操作系统又是怎么去实现的?
程序执行时关于对象放置的问题:有6个地方可以存放数据:
1.寄存器; 2.栈;3.堆 ; 4.静态存储空间 ; 5.常量存储空间 ;6.Non-RAM存储空间
栈是方法调用和临时变量(方法中的局部变量)的空间,栈的速度仅次于寄存器[寄存器中存放操作数地址,执行完后,寄存器内地址自动加1]。
堆是java所有对象的存储空间。
静态存储 static 位置不变 静态数据和静态方法在类的任何对象初始化之前就被载入到静态内存中。
常量存储 数据不变。final常量效率高的原因是它们都是内联的(inlined),即每次final常量的标识符出现时都立即被替代成实际数值,这样就减少了的内存的一次访问,因此速度快。
基本数据类型都存放在栈中,是错误的,基本数据类型,如果是数据成员,毫无疑问是放在堆中的。如果是临时变量,则放在栈中。所有的局部变量,形式参数都是从栈中分配内存空间的。
3.7多态的机制是什么?
多态性是指在一个程序中同名的不同方法共存的情况,允许不同类的对象对同一消息作出不同响应。
静态多态性即是编译时的多态 这种多态采用函数重载(模板)来实现,编译器对对象静态绑定,编译之后就决定了调用哪一个成员函数。
覆盖:在子类中直接定义的和父类同名的属性和方法,在子类的使用中,子类覆盖父类 ;
重载:在同一个类中可以有多个同名的方法(不同的形参,不同的实现)。 调用时根据形参的个数和类型来决定调用那个方法,如多个构造函数的重载。在使用方法重载时无法在同一个类中声明签名(方法名、参数数目、参数类型)相同但返回类型不同的方法。 将方法重载称为静态方法绑定或先期绑定,这是由于在编译时编译器会根据参数的类型和数目来决定调用哪个方法,而不是在执行时决定。
动态多态性是由继承和虚函数实现的,编译器对对象动态绑定,具体调用哪一个函数只有到执行的时候才知道。
C++中,多态通过虚函数实现,Java使用abstract类实现多态。
动态多态如:加工厂模式的代码结构,里面有基类,在基类里面定义了一个虚函数,然后这个虚函数的多个具体的实现是在基类的各个派生类里面实现的。在调用的地方还是同一个函数调用,然后根据具体的场合调用不同的对象中的相应的函数,这种代码形式实现了运行时的动态
3.8 Overload和Override的区别。
重写Overriding是父类与子类之间多态性的一种表现,如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。
重载Overloading是一个类中多态性的一种表现。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。参数顺序的不同也可以区分两个方法,不过一般情况下不要这么做,因为会使代码难以维护。
注意:用返回值(类型)来区分重载方法是行不通的。Overloaded的方法是可以改变返回值的类型。
3.10 abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?
都不能。abstract需要重载,static为类方法,没有重载一说;abstract为没有实现的方法,native为本地实现的方法,自相矛盾;abstract方法没有实现,也不可能实际调用抽象方法;没有必要synchronized修饰,当然子类可以根据需要同步该方法.
3.11哪些类可以被继承?(T--能,F--不能).
java.lang.Thread ()
java.lang.Number ()//是java.lang.Byte、java.lang.Double、java.lang.Float、java.lang.Integer、java.lang.Long、java.lang.Short等类的父类。
java.lang.Double ( )
java.lang.Math ( )
java.lang.Void ( )
java.lang.Class ( )
java.lang.ClassLoader ( )
答案:T T F F F F T
___________________________________________________________________________
3.12继承时类的构造函数?
当一个类继承另一个类的时候,它调用构造函数,一般是先调用被继承那个类的构造函数,如果父类中没有带参数的构造函数的话,子类会调用默认的构造函数;如果你的父类有带参数的构造函数的话,这个带参数的构造函数就会覆盖默认的构造函数,这样,你的子类就必须会去调用带有参数的父类的构造函数。
派生类必须调用基类的构造函数,但不继承基类的构造函数,实际上不继承是句废话,因为构造函数不能通过类的实例来调用。任何一个类都有构造函数,如果你不写构造函数,就生成默认的构造函数,如果你写了一个任何形式的构造函数,那么系统都不会为你生成默认构造函数。
构造函数的继承有如下限制:
1、子类无条件地继承父类的默认构造函数(即无形参的构造函数)。
2、如果子类定义了自己的构造函数,则子类在创建新对象时,构造函数的执行顺序是:先执行继承自父类的无参构造函数---> 再执行自己的构造函数,即执行了两次;
3、如父类没有默认的构造函数,子类也不能定义自己的无参的构造函数;对于父类的含参构造函数,子
类可以通过在自己的构造函数中使用super 来显式调用父类的构造函数,但这个调用语句必须是子类构造
函数的第一个可执行语句。
SubClass sc = newSubClass(); BaseClass bc = (BaseClass)sc ;---是正确的
而 BaseClass bc = new BaseClass(); SubClass sc = (SubClass)bc ;---是错误的
BaseClass bc = new SubClass()也是正确的,并且在调用bc中的方法时执行的方法体是子类的方法体,但该方法必须同时在子类,父类中同时存在,若子类中有,而父类中没有,则不可以这样调用bc.subMethod();
classBase{
public voidprint(){System.out.println("Base");}
}
public class Derived extends Base{
public void print(){System.out.println("Derived");}
public static void main(String[]args){
Base p=new Derived();
p.print();//result is Derived 调用的是子类中的方法
} }
注意:若两个类都继承于同一个类(必须是直接继承,否则不对),则这两个类可以互相赋值,如:Panel和Frame 同继承于Container,所以Panel p = new Frame() ;和Frame f =new Panel()都是正确的
父类:
class FatherClass{
public int i=0;
public FatherClass() {
System.out.println("FatherClass Create");
}
public FatherClass(Stringtext) {
i=1;
System.out.println("FatherClass Create");
}}
子类 :
public class ChildClass extends FatherClass{
public ChildClass(String text) {//子类的构造函数第一行代码就是调用父类构造函数,如果子类构造函数第一行不是调用父类构造函数,则编译器自动调用父类无参构造函数(这也就是所谓的隐式调用父类构造函数),而如果父类里面没有无参构造函数,所以编译器会报错。解决方法:在父类里添加一个无参构造函数
System.out.println("ChildClass Create");
}
public static void main(String[] args) {
FatherClass fc = new FatherClass();
ChildClass cc = new ChildClass();
}}
输出结果:
FatherClass Create
FatherClass Create
ChildClass Create
3.13多重继承有什么缺陷。
在《设计模式》一书中,作者在开篇中就提出了面向对象的两条基本设计原则:
1、多对接口编程,少对实现编程。
2、多使用聚合,少使用继承。
继承的过多使用当然会带来高耦合,当然聚合的过分会使模块过多。
多重继承指的是一个类别可以同时从多于一个父类继承行为与特征的功能,是一种图状层次结构(有向无环图)。功能强大,但易引起二义性,占用内存比较多。有时多重继承会出现二义性,比如
class A{protected: bool flag;}
class B: public A{}
class C: public A{}
class D: public B, public C{
public:
vid setFlag(bool nflag){
flag=nflag;//这里出现了flag两个版本,因为在D的继承层次中有两个A的实例。编译器会指出对flag引用存在的二义性。一种方法是显示消除引用的二义性:B::flag=flag;另一种方法是将B和C声明为虚基类,这意味着类层次中将只存在一份A的拷贝。
}
}//多继承还存在其他复杂问题,如:构造派生对象时基类的初始化顺序,以及成员会不小心再派生类中隐藏的问题。
4.对象
4.1 Object类中的方法
public class Object{
public Object();
//7个公共实例方法
public booleanequals(Object obj);// 用来比较以参数传递过来的对象和当前对象是否相等(必须是同一对象,才算相等。而在String[String 的equals方法较特殊]中使用equals()方法时,只要字符串一样就算时相等,不要求是同一字符串的不同引用,只有用String类中“==”运算符时,才比较其是否是同一对象的引用,即是否占有同一内存地址)。
public native inthashCode();//用于计算一个对象的散列编码值并把结果作为int类型返回。
public finalnative Class getClass();//返回一个Class类型的对象,以识别出当前对象的类。
public String toString();//返回文本化描述当前对象的String数据,默认情况下,返回:类名后面跟一个@和该对象hash码的十六进制表示。
public finalnative void notify();//唤醒一个正在等待队列中等待的线程,并他们移到等待同一个“对象互斥锁”的队列。
public final native void notifyAll();//唤醒所有正在等待队列中等待的线程
public final void wait()throws InterruptedException;// 释放其所持有的“对象互斥锁”,并进入wait()队列(等待队列)
public final native void wait(long timeout)throws InterruptedException; //暂停当前线程,并把线程放到对象的等候线程集中,直到当前线程被唤醒或者经过了规定的超时时间而继续执行。
public final void wait(long timeout, intnanos) throws InterruptedException;
//2个protected方法
protected nativeObject clone();
protected voidfinalize() throws Throwable; }//用于销毁对象前的清理工作
4.2equals与hashcode
例:两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
不对,有相同的hash code。
首先equals()和hashcode()这两个方法都是从object类中继承过来的。
equals()方法在object类中定义如下:
public booleanequals(Object obj) {
return (this ==obj);
}
很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。但是我们必需清楚,当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。都是进行的内容比较,而已经不再是地址的比较。
我们还应该注意,Java语言对equals()的要求如下,这些要求是必须遵循的:
• 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
•反射性:x.equals(x)必须返回是“true”。
• 传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
• 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
• 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
以上这五点是重写equals()方法时,必须遵守的准则,如果违反会出现意想不到的结果,请大家一定要遵守。
再看hashcode()方法,在object类中的定义如下:
public native int hashCode();
说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashcode()方法,比如String、Integer、Double。。。。等等这些类都是覆盖了hashcode()方法的。
想要明白hashCode的作用,你必须要先知道Java中的集合。总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。于是,Java采用了哈希表的原理。hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
3.这里我们首先要明白一个问题:
equals()相等的两个对象,hashcode()一定相等;
hashcode()不等,一定能推出equals()也不等;
hashcode()相等,equals()可能相等,也可能不等。
equals()方法会自动调用hashCode方法么?不会.除非你设计一个equals()方法 让它调用hashCode()方法. HashSet里的add()方法会通过一系列的调用使用到 hashCode()方法,因为它要根据hash值来确定存储位置,不是equals方法调用的.
凡是用哈希表实现的集合先比较的是hashCode方法,这是为了程序性能的问题。
当一个对象你只重写equals方法或hashCode方法,那么势必在一种情况下成立一种情况下不成立,所以只有2个方法同时改的情况下就会在各种情况下都出现你要的结果。我想这就是重写equals()方法必须重写hashCode方法的原因!
问hashcode()的函数,如果写成return 1;行不行。
我说是可以的,只是性能很差,不停的冲突。看得出来这个回答还是让他很满意的。 hashCode()返回该对象的哈希码值,该值通常是一个由该对象的内部地址转换而来的整数,它的实现主要是为了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。哈希函数,解决冲突。
4.3 equals()与“==”
"==" 比较的是两个对象的引用 (references),并不是他们的内容,(只是针对于对象类型,基本类型直接使用==或!=比较内容)
在Object 中的 equals(Object) 方法其标准形式为 public boolean equals(Object obj);返回类型为boolean ,即 true/false 与"==" 返回类型一样。
Object 类中定义的 equals(Object) 方法是直接使用 "==" 比较的两个对象,所以在没有覆盖 (override,或称改写、重写) equals(Object) 方法的情况下,equals(Object) 与 "==" 一样是比较的引用。
equals(Object) 方法与 "==" 相比的特殊之处就在于它可以覆盖,所以我们可以通过覆盖的办法让它不是比较引用而是比较数据内容。
当然 JDK 中也有覆盖过 equals(Object) 方法的类,如 java.lang.String,它就覆盖了从 Object 继承来的的 equals(Object) 方法,用以比较字符串内容是否相同。看看下面这个例子:
public classExample1 {
public static void main(String[] args){
String s1=new String("abc");
String s2=new String("abc");
System.out.println("用 == 比较结果");
System.out.println(s1==s2);//false
System.out.println("用equals(Object) 比较结果");
System.out.println(s1.equals(s2));//true
}
}
4.4 java中创建对象的方法
有4种显式地创建对象的方式:
1.用new语句创建对象,这是最常用的创建对象的方式。
2.运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。[ 可降低耦合???]
3.调用对象的clone()方法。
[java.lang.Cloneable可克隆接口。可以通过Object.clone()方法将类的实例对象的域(field)逐个复制到同一个类的另外一个实例中。]
4.运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法.
下面演示了用前面3种方式创建对象的过程。
public class Customer implements Cloneable{
private String name;
private int age;
public Customer(){
this("unknown",0);
System.out.println("call default constructor");
}
public Customer(String name,int age){
this.name=name;
this.age=age;
System.out.println("call second constructor");
}
public Object clone()throwsCloneNotSupportedException{
return super.clone();
}
public boolean equals(Objecto){
if(this==o)return true;
if(! (o instanceof Customer)) return false;
finalCustomer other=(Customer)o;
if(this.name.equals(other.name) && this.age==other.age)
return true;
else
return false;
}
public String toString(){
return"name="+name+",age="+age;
}
public static void main(Stringargs[])throws Exception{
//运用反射手段创建Customer对象
Class objClass=Class.forName("Customer"); //此处一定写上完全路径名。
Customer c1=(Customer)objClass.newInstance(); //会调用Customer类的默认构造方法
System.out.println("c1: "+c1); //打印name=unknown,age=0
//用new语句创建Customer对象
Customer c2=new Customer("Tom",20);
System.out.println("c2: "+c2); //打印name=tom,age=20
//运用克隆手段创建Customer对象
Customer c3=(Customer)c2.clone(); //将不会调用Customer类的构造方法
System.out.println("c2==c3 :"+(c2==c3)); //打印false
System.out.println("c2.equals(c3): "+c2.equals(c3)); //打印true
System.out.println("c3: "+c3); //打印name=tom,age=20
}
}
以上程序的打印结果如下:
call second constructor
call default constructor
c1: name=unknown,age=0
call second constructor
c2: name=Tom,age=20
c2==c3 : false
c2.equals(c3) : true
c3: name=Tom,age=20
从以上打印结果看出,用new语句或Class对象的newInstance()方法创建Customer对象时,都会执行Customer类的构造方法,
而用对象的clone()方法创建Customer对象时,不会执行Customer类的构造方法。
除了以上4种显式地创建对象的方式以外,在程序中还可以隐含地创建对象,包括以下几种情况:
1.对于java命令中的每个命令行参数,Java虚拟机都会创建相应的String对象,并把它们组织到一个String数组中,再把该数组作为参数传给程序入口main(String args[])方法。
2.程序代码中的String类型的直接数对应一个String对象,例如:
String s1="Hello";
String s2="Hello"; //s2和s1引用同一个String对象
String s3=new String("Hello");
System.out.println(s1==s2); //打印true
System.out.println(s1==s3); //打印false
执行完以上程序,内存中实际上只有两个String对象,一个是直接数,由Java虚拟机隐含地创建,还有一个通过new语句显式地创建。
3.字符串操作符“+”的运算结果为一个新的String对象。
例如:
String s1="H";
String s2=" ello";
String s3=s1+s2; //s3引用一个新的String对象
System.out.println(s3=="Hello");//打印false
System.out.println(s3.equals("Hello"));//打印true
4.当Java虚拟机加载一个类时,会隐含地创建描述这个类的Class实例.(???)
4.5 对象创建过程/初始化顺序
Java虚拟机创建一个对象都包含以下步骤。
(1)给对象分配内存。
(2)将对象的实例变量自动初始化为其变量类型的默认值。 (int 类型的被设置为0)
(3)初始化对象,给实例变量赋予正确的初始值。
对于以上第三个步骤,Java虚拟机可采用3种方式来初始化对象,到底采用何种初始化方式取决于创建对象的方式。
(1)如果对象是通过clone()方法创建的,那么Java虚拟机把原来被克隆对象的实例变量的值拷贝到新对象中。
(2)如果对象是通过ObjectInputStream类的readObject()方法创建的,那么Java虚拟机通过从输入流中读入的序列化数据来初始化那些非暂时性(non-transient)的实例变量。
***(3)在其他情况下,如果实例变量在声明时被显式初始化,那么就把初始化值赋给实例变量,接着再执行构造方法。这是最常见的初始化对象的方式。
1.初始化的顺序是:先静态对象(如果它们尚未因前面的对象创建过程而被初始化),而后是非静态对象。
[在类的内部,初始化时,概括来说,就是先变量后方法 ]
【2011/6/21—12:00】
总结对象创建的过程:
(1)首次创建对象时,类中的静态方法/静态字段首次被访问时,java解释器必须先查找类路径,以定位.class文件;(2)然后载入.class(这将创建一个class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。(3)当用new XX()创建对象时,首先在堆上为对象分配足够的存储空间。(4)这块存储空间会被清0,这就自动地将对象中的所有基本类型数据都设置成了缺省值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成了null。(5)执行所有出现于字段定义处的初始化动作(非静态对象的初始化)。(6)执行构造器。
1、 对象的初始化
(1)非静态对象的初始化
在创建对象时,对象所在类的所有数据成员会首先进行初始化。基本类型:int型,初始化为0。如果为对象:这些对象会按顺序初始化。
※在所有类成员初始化完成之后,才调用本类的构造方法创建对象。构造方法的作用就是初始化。
(2)静态对象的初始化
程序中主类的静态变量会在main方法执行前初始化。不仅第一次创建对象时,类中的所有静态变量都初始化,并且第一次访问某类(注意此时未创建此类对象)的静态对象时,所有的静态变量也要按它们在类中的顺序初始化。
2、 继承时,对象的初始化过程
(1) 主类的超类由高到低按顺序初始化静态成员,无论静态成员是否为private。
(2) 主类静态成员的初始化。
(3) 主类的超类由高到低进行默认构造方法的调用。注意,在调用每一个超类的默认构造方法前,先进行对此超类进行非静态成员的初始化。
(4) 主类非静态成员的初始化。
(5) 调用主类的构造方法。
4.5写clone()方法时,通常都有一行代码,是什么?[ 浅克隆---深克隆]
在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。Clone 有缺省行为,super.clone();他负责产生正确大小的空间,并逐位复制。使用clone()来复制一个对象,clone()从Object类继承。所有具有clone功能的类都有一个特性,那就是它直接或间接地实现了Cloneable接口。
protected native Object clone() throwsCloneNotSupportedException;
可以看出它是一个protected方法,所以我们不能简单地调用它;关键字native,表明这个方法使用java以外的语言实现。
对于 object x,
x.clone() != x
x.clone().getClass() ==x.getClass()
x.clone().equals(x)
以上返回的值都为true
要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。
1.浅复制与深复制概念
⑴浅复制(浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
⑵深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
public class TestClone1 implements Cloneable{
int count;
TestClone1 next;
public TestClone1(intcount) {
this.count=count;
if(count>0)
next=newTestClone1(count-1);
}
void add(){
count++;
if(next!=null)
next.count++;
}
public String toString(){
Strings=String.valueOf(count)+" ";
if(next!=null)
s+=next.toString();
return s;
}
public Object clone(){
Object o=null;
try{
o=super.clone();//如果没有实现cloneable,将会抛出CloneNotSupported异常
}
catch(CloneNotSupportedException e){
System.err.println("cannot clone");
}
return o;
}
public static voidmain(String[] args){
TestClone1 t=newTestClone1(1);
System.out.println("t="+t);
TestClone1t1=(TestClone1)t.clone();
System.out.println("t1="+t1);
t.add();
System.out.println("after added\nt t="+t+"\nt1="+t1);
}
}
在这个例子中创建t相当于两个相连的TestClone1实例,而在调用了t的add方法之后,意想不到的结果出现了:
t=1 0
t1=1 0
after added
t t=2 1
t1=1 1
t1也发生了改变。实际上Object.clone()进行的复制有着"bitwise"原则,也就是逐位复制。对于一个对象中定义的对象,它只是简单的复制这个对象的引用。这也就是常说的浅层拷贝(shallow copy)。想要执行深层拷贝(deep copy),只需要在TestClone1t1=(TestClone1)t.clone();后面加上t1.next=(TestClone1)t.next.clone();就能得到:
t=1 0
t1=1 0
after added
t t=2 1
t1=1 0
这个正确的结果。
4.6反射机制
反射机制最重要的部分是准许你检查类的结构。java.lang.reflect包中的三个类Field、Method、Constructor相应的描述了一个类的字段、方法、构造函数。使用这些类的时候必须要遵循下面步骤:
第一步是获得你想操作的类的 java.lang.Class对象。下面就是获得一个 Class 对象的方法之一:Class c =Class.forName("java.lang.String"); //这条语句得到一个 String 类的类对象。还有另一种方法:Class c = int.class; 或者Classc = Integer.TYPE; //可获得基本类型的类信息。
第二步是调用诸如 getDeclaredMethods 的方法,以取得该类中定义的所有方法的列表。
Method m[] =c.getDeclaredMethods();
System.out.println(m[0].toString());//以文本方式打印出String 中定义的第一个方法的原型。
4.6 Class.forName 的作用 ? 为什么要用 ?
答: Class是运行中的class类,forName(className)是将这个名为className的类装入JVM,这样就可以动态的加载类,通过Class的反射机制可以获得此类的一些信息。Class.forName的作用动态加载和创建Class 对象。
类加载器是用来加载.class文件,读取.class文件的字节码并加载到内存中。
关于类的初始化(执行static程序段):
1、A a = new A();//在类加载的时候即进行初始化
2、Class.forName(A);//在类加载(载入class)的时候即进行初始化
3、Class.forName(A,false,classLoader);//在newInstance的时候进行初始化
4、classLoader.loadClass(A);//在newInstance的时候进行初始化
static块仅执行一次
(1) 使用Class.forName()
+- public static ClassforName(String className)
+- publicstatic Class forName(String className, boolean initialize,ClassLoader loader)
参数说明:
className - 所需类的完全限定名(包括全路径)
initialize - 是否必须初始化类(静态代码块的初始化)
loader - 用于加载类的类加载器
不管使用的是new 来实例化某个类、或是使用只有一个参数的Class.forName()方法,内部都隐含了“载入类+运行静态代码块”的步骤。而使用具有三个参数的Class.forName()方法时,如果第二个参数为false,那么类加载器只会加载类,而不会初始化静态代码块,只有当实例化这个类的时候,静态代码块才会被初始化,静态代码块是在类第一次实例化的时候才初始化的。
4.7 Class类和对象
类是程序的一部分,每个类都有一个Class对象。换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当地说,是保存在一个完全同名的.class文件中)。在运行期,一旦我们想生成这个类的对象,运行这个程序的Java虚拟机(JVM)首先就会检查这个类的Class对象是否已经载入。若尚未载入,JVM就会根据类名查找.class文件,并将其载入。所以Java程序并不是一开始就被完全加载的,这一点与许多传统语言都不同。
一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。注意:Class对象仅在需要的时候才被加载。Class类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
有三种方法可以获取Class的对象:
1、调用Object类的getClass()方法来得到Class对象。例如:
MyObject x; Class c1 =x.getClass();
2、使用Class类的中静态forName()方法获得与字符串对应的Class对象。例如:
Class c2=Class.forName("MyObject");//MyObject必须是接口或者类的名字。
3、如果T是一个Java类型,那么T.class就代表了匹配的类对象。例如
Class cl1 = Manager.class; Class cl2 = int.class; Class cl3 = Double[].class;
注意:Class对象实际上描述的只是类型,而这类型未必是类或者接口。例如上面的int.class是一个Class类型的对象。
二、Class类的常用方法
1、getName() 以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
2、newInstance() 为类创建一个实例。例如: x.getClass.newInstance()。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。
3、getClassLoader() 返回该类的类加载器。
4、getSuperclass() 返回表示此 Class 所表示的实体的超类的 Class。
5、isArray() 判定此 Class 对象是否表示一个数组类。
三、Class的一些使用技巧
1、forName和newInstance结合起来使用,可以根据存储在字符串中的类名创建对象。例如Object obj = Class.forName(s).newInstance();
2、虚拟机为每种类型管理一个独一无二的Class对象。因此可以使用==操作符来比较类对象。例如: if(e.getClass() == Employee.class)...
4.8 java classLoader原理
Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。类装载器是用来把类 (class) 装载进 JVM 的。JVM 规范定义了两种类型的类装载器:启动内装载器 (bootstrap) 和用户自定义装载器。
bootstrap 是 JVM 自带的类装载器,用来装载核心类库,如 java.lang.* 。java.lang.Object 是由 bootstrap 装载的。Java 提供了抽象类ClassLoader ,所有用户自定义类装载器都实例化自 ClassLoader 的子类。
System Class Loader 是一个特殊的用户自定义类装载器,由 JVM 的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过 ClassLoader.getSystemClassLoader() 方法得到。
类装载器把一个类装入 Java 虚拟机中,要经过三个步骤:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
(1)装载:寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的class 对象的过程。
(2)链接:就是把class类型数据合并到JVM得运行时状态中去。执行校验、准备和解析步骤;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
(3)初始化:初始化 Java 代码和静态 Java 代码块。初始化在JVM第一次主动使用该类型时进行的。
所谓主动使用包括以下几种情况:
[1]创建类的新实例时(new指令或通过不明确的创建,反射,克隆或反序列化)
[2]调用类的静态方法时
[3]使用类的静态字段,或对该字段赋值时(final修饰的静态字段除外)
[4]初始化某个类的子类时
[5]JVM启动时某个被标明为启动类的类即含有main()方法的类
数组类的Class 对象不是由类装载器创建的,而是由 Java 运行时根据需要自动创建。数组类的类装载器由 Class.getClassLoader()返回,该装载器与其元素类型的类装载器是相同的;如果该元素类型是基本类型,则该数组类没有类装载器。
虚拟机加载类的途径:
1、Dog dog = new Dog();
2、Class clazz = Class.forName(“Dog”);
Object dog =clazz.newInstance();
3、Class clazz = classLoader.loadClass(“Dog”);
Object dog =clazz.newInstance();
那么,1和2和3究竟有什么区别呢?分别用于什么情况呢?
1和2使用的类加载器是相同的,都是当前类加载器。(即:this.getClass.getClassLoader)。
3由用户指定类加载器。如果需要在当前类路径以外寻找类,则只能采用第3种方式。第3种方式加载的类与当前类分属不同的命名空间。
另外,第1种和第2种都会导致执行类的静态初始化语句,而第3种情况不会。另外第1种抛出Error,第2、3种抛出Exception,它们分属于不同的异常/错误分支。
4.9什么是java序列化,如何实现java序列化?
Java 串行化技术可以使你将一个对象的状态写入一个Byte 流里,并且可以从其它地方把该Byte 流里的数据读出来,重新构造一个相同的对象。这种机制允许你将对象通过网络进行传播,并可以随时把对象持久化到数据库、文件等系统里。Java的串行化机制是RMI、EJB等技术的技术基础。用途:利用对象的串行化实现保存应用程序的当前工作状态,下次再启动的时候将自动地恢复到上次执行的状态。
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现Serializable接口,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
2、串行化的特点:
(1)如果某个类能够被串行化,其子类也可以被串行化。
如果该类有父类,则分两种情况来考虑,
如果该父类已经实现了可串行化接口。则其父类的相应字段及属性的处理和该类相同;
如果该类的父类没有实现可串行化接口,则该类的父类所有的字段属性将不会串行化。
(2)声明为static和transient类型的成员数据不能被串行化。因为static代表类的状态, transient代表对象的临时数据;
(3)相关的类和接口:在java.io包中提供的涉及对象的串行化的类与接口有
ObjectOutput接口、ObjectOutputStream类、ObjectInput接口、ObjectInputStream类。
(1)ObjectOutput接口:它继承DataOutput接口并且支持对象的串行化,其内的writeObject()方法实现存储一个对象。
ObjectInput接口:它继承DataInput接口并且支持对象的串行化,其内的readObject()方法实现读取一个对象。
(2)ObjectOutputStream类:它继承OutputStream类并且实现ObjectOutput接口。利用该类来实现将对象存储(调用ObjectOutput接口中的writeObject()方法)。
ObjectInputStream类:它继承InputStream类并且实现ObjectInput接口。利用该类来实现读取一个对象(调用ObjectInput接口中的readObject()方法)。
对于父类的处理,如果父类没有实现串行化接口,则其必须有默认的构造函数(即没有参数的构造函数)。否则编译的时候就会报错。在反串行化的时候,默认构造函数会被调用。但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。
这是为什么呢?
这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。
importjava.io.*;
publicclass Cat implements Serializable {
private String name;
public Cat () {
this.name = "newcat";
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args){
Cat cat = new Cat();
try {
FileOutputStream fos = new FileOutputStream("catDemo.out");
ObjectOutputStream oos= new ObjectOutputStream(fos);
System.out.println("1> " + cat.getName());
cat.setName("MyCat");
oos.writeObject(cat);
oos.close();
} catch (Exception ex) { ex.printStackTrace(); }
try {
FileInputStream fis = new FileInputStream("catDemo.out");
ObjectInputStream ois =new ObjectInputStream(fis);
cat = (Cat) ois.readObject();
System.out.println("2> " + cat.getName());
ois.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}//writeObject和readObject本身就是线程安全的,传输过程中是不允许被并发访问的。所以对象能一个一个接连不断的传过来.
4.10 类型比较instanceof运算符、Class的isInstance( )与isAssignableFrom区别 (略过)
两个Class对象不论是通过equals()函数还是直接用==运算符进行比较,比较的都是类型是否相同。
1.通过Class.isInstance(object)函数进行比较。Class类的isInstance(Object obj)方法,obj是被测试的对象,如果obj是调用这个方法的class或接口的实例,则返回true。这个方法是instanceof运算符的动态等价。形象地:自身类.class.isInstance(自身实例或子类实例) 返回true。
2.通过instanceof关键字进行比较。但是两个处于不同的继承体系中的类对象和Class对象进行比较,会产生编译错误。instanceof运算符 检查左边的被测试对象是不是右边类或接口的实例。如果被测对象是null值,则测试结果总是false。形象地:自身实例或子类实例 instanceof 自身类,返回true。
3.Class类的isAssignableFrom(Class cls)方法,用来判断一个类Class1和另一个类Class2是否相同或是另一个类的超类或接口。如果调用这个方法的class或接口与参数cls表示的类或接口相同,或者是参数cls表示的类或接口的父类,则返回true。形象地:自身类.class.isAssignableFrom(自身类或子类.class) 返回true。
class Cat{}
class Dog{}
class Duck{}
public class Test{
public static void main(String[] args){
Class c3;
//Object d = new Duck(); (1)
Duck d = new Duck(); //(2)
c3 = d.getClass();
System.out.println("d instanceof Dog:" + (d instanceof Dog));//(3)
}
}
由于Duck和Dog处于两个不同的继承体系中,所以代码(3)会发生编译错误。如果把代码(2)注释掉,并去掉代码(1)的注释,编译将通过。这是因为所有class都是继承自Object的,所以Object与Dog处于同一个继承体系中,可以进行比较。
/*** instanceof 用于检测对象的类型。
* (1)类的对象与类作instanceof操作,结果为true
* (2)子类对象与父类作instance of操作,结果为true。
* 因此,所有对象与Object作instance of操作,结果都为true。
* (3)其他情况下,结果都为false。
*/
class Base {
Base(){System.out.println("ConstructBase.\n");}
}
class Pro extends Base {
Pro(){System.out.println("ConstructPro.\n");}
}
public class C {
public static void main(String[] args)throws ClassNotFoundException {
Base b = new Base();
Pro pro = new Pro();
//输出Base类的描述
System.out.println(Base.class);
//输出对象的类
System.out.println(b.getClass());
//通过Class.forName(“类名”)得到Class对象
System.out.println(Class.forName("ch14.Base"));
System.out.println(b instanceof Base);
System.out.println(pro instanceof Base);
System.out.println(b.getClass().isInstance(pro));
System.out.println(pro.getClass().isInstance(b));
}
}
输出:
Construct Base.
Construct Base.
Construct Pro.
class ch14.Base
class ch14.Base
class ch14.Base
true
true
true
false
4.11 Java对象大小,没有sizeof
Java没有提供sizeof()的操作器。因为所有的数据类型在所有的机器中的大小都是相同的。我们不必考虑移植问题。 C++中需要sizeof()的最大原因是为了“移植”,不同的数据类型在不同的机器上可能有不同的大小,所以在进行与存储相关的运算时,程序员必须清楚那些类型具体有多大。相比C/C++而言,同样的Java数据结构往往占据更多的物理存储。
1.简单的Object对象要占用8个字节的内存空间,因为每个实例都至少必须包含一些最基本操作,比如:wait()/notify(),equals(),hashCode()等
2.使用Integer对象占用了16个字节,而int占用4个字节,说了封装了之后内存消耗大了4倍
3. Long所占的空间也是16个字节.
JVM强制使用8个字节作为边界. 所有基本类型封装对象所占内存的大小都是16字节.
4.12 句柄与引用
1.句柄(handle) 为了区别引用类型的变量标识符和基本数据类型变量标识符,我们特别的使用Handle来称呼引用类型的变量标识符。
A a; //a里面存放地址。但未初始化,所以里面的值为null。
B b1,b2;// b1, b2里面存放地址。但未初始化,所以里面的值为null。
String s; //s里面存放地址。但未初始化,所以里面的值为null。
2.引用(reference)对象的引用是创建对象时的返回值!引用是new表达式的返回值。
new A(); 这里真正创建了一个对象,但我们没有用句柄去持有该引用。从微观上看,new表达式完成了对象初始化的任务(三步曲),整体上看则返回一个引用。
3.句柄与引用的关系
A a;//声明句柄a,值为null
a=new A();//句柄的初始化
引用:new A()的值。引用可以简单的看作对象占据内存空间的地址;引用就是对象独特的身份标识。
完成句柄的初始化后,就可以用句柄遥控对象了。
【2011/6/22-17:00】
4.12 数组
无论使用的数组属于什么类型,数组标识符实际都是指向真实对象的一个句柄(浅复制)。那些对象本身是在内存“堆”里创建的。堆对象既可“隐式”创建(即默认产生),亦可“显式”创建(即明确指定,用一个new表达式)。
Weeble[] a; // 句柄a未初始化。此时,编译器会禁止我们对这个句柄作任何实际操作,除非已正确地初始化了它。
Weeble[] b = new Weeble[5]; // 数组b被初始化成指向由Weeble句柄构成的一个数组,但那个数组里实际并未放置任何Weeble对象。然而,我们仍然可以查询那个数组的大小,因为b指向的是一个合法对象。
//!System.out.println("a.length=" + a.length);//编译出错,因为数组里的句柄没有被初始化
System.out.println("b.length = " + b.length);//数组里的句柄默认初始化为null,b.length = 5
Weeble[] c = new Weeble[4];
for(int i = 0; i < c.length; i++)
c[i] = new Weeble();//对c初始化,数组c显示出我们首先创建一个数组对象,再将Weeble对象赋给那个数组的所有“空位”。
Weeble[] d = {//对d初始化,数组d揭示出“集合初始化”语法,全部工作在一条语句里完成。
new Weeble(), new Weeble(), new Weeble()
};
System.out.println("c.length = "+ c.length);//c.length = 4
System.out.println("d.length = " + d.length);//d.length = 3
a= d;// a和d都指向内存堆内同样的数组对象。
System.out.println("a.length= " + a.length);//a.length = 3
4.12 Java中的数组与C/C++的不同
如果你熟悉C/C++,Java数组的工作原理与它们不同。Java中的数组是对象,这就意味着与C++中的数组的根本不同.
1、数组不是集合,它只能保存同种类型的多个原始类型或者对象的引用。数组保存的仅仅是对象的引用,而不是对象本身。数组声明的两种形式:一、int[] arr; 二、int arr[]; 推荐使用前者,这是一个int数组对象,而不是一个int原始类型。
2、数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。
4、在数组声明中包含数组长度永远是不合法的!如:int[5] arr; 。因为,声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。
5、在数组构造的时候必须指定长度,因为JVM要知道需要在堆上分配多少空间。例:int[] arr = new int[5];
7、一维数组的构造。形如:String[] sa = newString[5]; 或者分成两句:String[] sa; sa = new String[5];
8、原始类型数组元素的默认值。对于原始类型数组,在用new构造完成而没有初始化时,JVM自动对其进行初始化。默认值:byte、short、 int、long--0 float--0.0f double--0.0 boolean--false char--'"u0000'。(无论该数组是成员变量还是局部变量)
10、对象类型的数组虽然被默认初始化了,但是并没有调用其构造函数。也就是说:Car[] myCar = new Car[10];只创建了一个myCar数组对象!并没有创建Car对象的任何实例!
11、多维数组的构造。float[][]ratings = new float[9][]; 第一维的长度必须给出,其余的可以不写,因为JVM只需要知道赋给变量ratings的对象的长度。
12、数组索引的范围。数组中各个元素的索引是从0开始的,到length-1。每个数组对象都有一个length属性,它保存了该数组对象的长度。(注意和String对象的length()方法区分开来)
13、Java有数组下标检查,当访问超出索引范围时,将产生ArrayIndexOutOfBoundsException运行时异常。注意,这种下标检查不是在编译时刻进行的,而是在运行时!也就是说int[] arr = new int[10]; arr[100] = 100; 这么明显的错误可以通过编译,但在运行时抛出!
Java中的数组中既可以存储基本的值类型,也可以存储对象。对象数组和原始数据类型数组在使用方法上几乎是完全一致的,唯一的差别在于对象数组容纳的是引用而原始数据类型数组容纳的是具体的数值。在讨论关于数组的问题时,一定要先确定数组中存储的是基本值类型还是对象。特别是在调试程序时,要注意这方面。
4.12 数组与容器的区别?
数组排序:java使用内置的排序算法可以对任意的基本类型数组或对象数组进行排序。String数组排序算法依据词典编排顺序排序,对基本类型数组采用快速排序;针对对象采用“稳定归并排序”
数组与容器的区别体现在三个方面:效率,类型识别以及可以持有基本类型primitives。
1.数组只能保存特定类型。数组可以保存基本类型,容器则不能。创建一个数组时可让它容纳一种特定的类型。这意味着可进行编译时间的类型检查防范自己设置了错误的类型或者错误地提取了一种类型,而不是运行时的Exception。容器不以具体的类型来处理对象,它们将所有对象都按Object类型处理。Java对数组和容器都做边界检查;如果过了界,它就会给一个RuntimeException。
2.数组是一种高效的存储和随机访问对象引用序列的方式,但是当创建一个数组对象(注意和对象数组的区别)后,数组的大小也就固定了,当数组空间不足的时候就再创建一个新的数组,把旧的数组中所有的引用复制到新的数组中。(数组是一种内存结构,而容器是一种数据结构)[知道数组的长度,而且以后也不会再增加,那肯定就使用数组了;如果数组的长度不定或者说是长度会增加,为了方便起见使用容器]
3.容器类只能保存对象的引用。而数组既可以创建为直接保存基本类型,也可以保存对象的引用。在容器中可以使用包装类,如Integer、Double等来实现保存基本数据类型值。
一般情况下,考虑到效率与类型检查,应该尽可能考虑使用数组。如果要解决一般化的问题,数组可能会受到一些限制,这时可以使用Java提供的容器类。
4.13值传递还是引用传递?
JAVA中的参数传递,不管你传的是什么,传过去的都只是一个副本而已,这个副本作为方法的局部变量保存在栈中。
如果传的是基本数据类型,修改这个值并不会影响作为参数传进来的那个变量,因为你修改的是方法的局部变量,是一个副本。
如果传的是一个对象的引用,也是一样的,也是一个副本,但是这个副本和作为参数传进来的那个引用指向的是内存中的同一个对象,所以你通过这个副本也可以操作那个对象。但是如果你修改这个引用本身,比如让他指向内存中的另外一个对象,原来作为参数传进来的那个引用不会受到影响。所以JAVA中只有值传递。
public class Main{
privatestatic void change(String s, StringBuffer sb) {
s = "aaaa";
sb.setLength(0);
sb.append("aaaa");
}
public static void main(String[] args) {
String s = "bbbb";
StringBuffer sb = newStringBuffer("bbbb");
change(s, sb);
System.out.print(s+sb);
}
}//输出结果为:“bbbbaaaa”,而不是“aaaaaaaa”
public static void add(StringBufferx,StringBuffer y){
x.append(y);
y=x;
}
public static void main(String[] args) {
StringBuffer a=newStringBuffer("A");
StringBuffer b=new StringBuffer("B");
add(a,b);
System.out.println(a+","+b);
}//输出:AB,B
4.14除8种基本类型外,在虚拟机里还有哪一种,有什么作用?
JAVA中除了8种基本类型(boolean、byte、short、int、long、char、float、double)外,其它的类型是引用类型,包括类类型(含数组)、接口类型,像STRING,数组,文件流等。引用变量在JAVA中是一个存储对象在内存中的地址的变量。所以字符串内容的比较不是直接用等号,而是用字符串的方法equeals()来比较内容的。
还有一个是null类型。
Java中的保留字:
Java 关键字列表 (依字母排序 共51组):
abstract, assert,boolean, break, byte, case, catch, char, class, const,continue, default, do, double, else,enum,extends, final, finally, float, for, if, implements, import, instanceof,int, interface, long, native, new, package, private, protected, public, return,short, static, strictfp, super, switch, synchronized, this, throw, throws,transient, try, void, volatile, while (friendly不是保留字)
Java 保留字列表 (依字母排序 共14组) : Java保留字是指现有Java版本尚未使用 但以后版本可能会作为关键字使用。
byValue, cast, false, future, generic, inner, operator, outer, rest,true, var , goto ,const,null
注意default用于switch循环中,当case语句里面没有switch所对应的结果,那么就会执行default后面的代码
4.16在JAVA中,如何跳出当前的多重嵌套循环?
用break; return 方法。
4.17 final, finally, finalize的区别。
Final--修饰符(关键字)。如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载。
finally—在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
finalize —方法名。Java 技术允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。一旦垃圾回收器准备好释放对象占用的空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。垃圾回收只与内存有关,只所以会用finalize()方法,是由于在分配内存时采用了类似C语言的做法,这种情况主要发生在使用“本地方法”的情况下,本地方法采用的是非java代码方式,本地方法目前只支持C和C++,在非java代码中也许会调用到C的malloc()函数系列来分配存储空间,而且除非调用了free()函数,否则存储空间将得不到释放。
4.18 final关键字
采用final方法的原因:1、把方法锁定,防止任何继承类修改它的含义;2、效率,final方法是内联的,以方法体中的实际代码的复本代替方法调用,省去参数压栈,跳至方法代码处并执行,然后跳回并清理栈中的参数,处理返回值等步骤。
final成员
当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,final变量定义的时候,可以先声明,而不给初值,这种变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。其初始化可以在两个地方,一是其定义处,也就是说在final变量定义时直接给其赋值,二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时给了值,又在构造函数中给另外的值。
final参数
当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
final方法
将方法声明为final,那就说明你已经知道这个方法提供的功能已经满足你要求,不需要进行扩展,并且也不允许任何从此类继承的类来覆写这个方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。另外有一种被称为inline的机制,它会使你在调用final方法时,直接将方法主体插入到调用处,而不是进行例行的方法调用,例如保存断点,压栈等,这样可能会使你的程序效率有所提高,然而当你的方法主体非常庞大时,或你在多处调用此方法,那么你的调用主体代码便会迅速膨胀,可能反而会影响效率,所以你要慎用final进行方法定义。
public class Test3 {
private final String s="final实例变量S";
private final int A=100;
public final int B=90;
public static final int C=80;
public static int G=60;
private static final int D=70;
public final int E; //final空白,必须在初始化对象的时候赋初值
public Test3(int x){
E=x;
}
public static void main(String[]args) {
Test3 t=new Test3(2);
System.out.println("C的值:"+C);//允许
System.out.println("D的值:"+D);//允许
System.out.println("G的值:"+G);//允许
//System.out.println(s); //错误,s是非静态的
System.out.println("s的值:"+t.s);
System.out.println(t.A);
System.out.println(t.B);
System.out.println(t.C); //不推荐用对象方式访问静态字段
System.out.println(t.D); //不推荐用对象方式访问静态字段
System.out.println(Test3.C);
System.out.println(Test3.D);
//System.out.println(Test3.E); //出错,因为E为final空白,依据不同对象值有所不同.
System.out.println(t.E);
Test3 t1=new Test3(3);
System.out.println(t1.E); //final空白变量E依据对象的不同而不同
}
public void test2(){
final int a; //final空白,在需要的时候才赋值
final int b=4; //局部常量--final用于局部变量的情形
final int c; //final空白,一直没有给赋值.
a=3;
//a=4; 出错,已经给赋过值了.
//b=2; 出错,已经给赋过值了.
} }
4.19 static关键字
被static修饰的成员变量和成员方法独立于该类的任何对象。它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问。
用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。
static变量前可以有private修饰,表示不能在其他类中通过类名来直接引用,这一点很重要。实际上你需要搞明白,private是访问权限限定,static表示不要实例化就可以使用,这样就容易理解多了。静态的类会在内存静态数据区来存储数据,并且会一直占据内存,占用内存大小是根据数据类型决定的,直到程序结束才被释放,乱用的话会导致内存的浪费。静态存储类型的,一定是全局变量。不会在程序执行完之前释放内存,而一般局部变量在生命周期结束后,会释放内存。(不过全局变量不一定就是静态的,如final)
1、static变量
按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。两者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
2、静态方法
静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为实例成员与特定的对象关联! static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
3、static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。static代码块可以访问非静态变量或方法,例如:
public class Test5 {
private static int a;
private int b;
static{
Test5.a=3;
System.out.println(a);
Test5 t=new Test5();
t.f();
t.b=1000;
System.out.println(t.b);
}
static{
Test5.a=4;
System.out.println(a);
}
public static void main(String[] args) {
// TODO 自动生成方法存根
}
static{
Test5.a=5;
System.out.println(a);
}
public void f(){
System.out.println("hhahhahah");
}
}
运行结果:
3
hhahhahah
1000
4
5
利用静态代码块可以对一些static变量进行赋值,这样JVM在运行static main方法的时候可以直接调用而不用创建实例。
4、static和final一块用表示什么
static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
对于方法,表示不可覆盖,并且可以通过类名直接访问。
通常一个普通类不允许声明为静态的,只有一个内部类才可以。这时这个声明为静态的内部类可以直接作为一个普通类来使用,而不需实例一个外部类。如下代码所示:
public class StaticCls{
public static void main(String[] args){
OuterCls.InnerCls oi=new OuterCls.InnerCls();
}}
class OuterCls{
public static class InnerCls{
InnerCls(){
System.out.println("InnerCls");
} }}
输出结果会如你所料:
InnerCls
4.20 static和构造函数执行顺序
public class Test2 extends TestStatic{
static{ System.out.println("b"); }
Test2(){ System.out.println(2); }
public static void main(String args[]){
TestStaticA=new Test2();
A=newTest2();}
}
class TestStatic {
static{System.out.println("a");}
TestStatic(){ System.out.println(1);}
}
执行结果:a b 1 2 1 2
可以看出执行顺序为:先执行父类中静态代码块,然后是子类静态块,其次父类构造函数,最后是子类构造函数。另外注意静态代码块只执行一次,所以后面语句A=newTest2();执行结果只有1 2
4.String对象创建个数
s = new String("xyz");创建了几个String Object?两个对象,一个是“xyx”,一个是指向“xyx”的引用对象s。
Strings="你好";int i=3; s=i+s; 这个表达式对吗?在java中会提示数据类型不匹配。因为string是类!正确做法: s+="3" 或者 s+='3'或者 s+=(char)i;
我们要引入另外一种创建String对象的方式的讨论——引号内包含文本。这种方式是String特有的,并且它与new的方式存在很大区别。
在JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,因此它提高了效率。String a="abc";,这行代码被执行的时候,JAVA虚拟机首先在字符串池中查找是否已经存在了值为"abc"的这么一个对象,判断依据是String类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。
字符串对象的创建:由于字符串对象的大量使用[它是一个对象,一般而言对象总是在heap分配内存],Java中为了节省内存空间和运行时间[如比较字符串时,==比equals()快],在编译阶段就把所有的字符串文字放到一个文字池中,而运行时文字池成为常量池的一部分。文字池的好处,就是该池中所有相同的字符串常量被合并,只占用一个空间。我们知道,对两个引用变量,使用==判断它们的值[引用]是否相等,即指向同一个对象:
现在看String s= new String("abc");语句,这里"abc"本身就是pool中的一个对象,而在运行时执行new String()时,将pool中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s持有。ok,这条语句就创建了2个String对象。
String s1 = new String("abc");String s2 = new String("abc") ;if( s1 == s2 ){ //不会执行的语句}
//创建了几个StringObject? [三个,pool中一个,heap中2个。]
只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。
1.==表示引用自同一对象,equals()表示值相等。
String str1= "abc";引用的对象在栈(或者叫String池)中。
String str1=new String ("abc"); 引用的对象在内存/堆中。
2.Stringstr1 = "string";在栈中
String str3 = "str";在栈中
String str4 = "ing";在栈中
String str2 = str3+str4; 在堆中,因为+号的作用是返回另外一个新建的String对象,而不是在栈中找string这个值。如果是String str2 = "str"+"ing";那最后的结果就在栈中。str1==str2为true。
但是有一种情况需要引起我们的注意。请看下面的代码:
publicclass StringStaticTest {
public static final String A ="ab"; // 常量A
public static final String B ="cd"; // 常量B
public static void main(String[] args) {
String s = A + B; // 将两个常量用+连接对s进行初始化
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
}
这段代码的运行结果如下:
s等于t,它们是同一个对象
原因是在上面的例子中,A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。也就是说:String s=A+B; 等同于:String s="ab"+"cd";
我对上面的例子稍加改变看看会出现什么情况:
publicclass StringStaticTest {
public static final String A; // 常量A
public static final String B; // 常量B
static {
A = "ab";
B = "cd";
}
public static void main(String[] args){
// 将两个常量用+连接对s进行初始化
String s = A + B;
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
}else{
System.out.println("s不等于t,它们不是同一个对象");
}
}
}
它的运行结果是这样:
s不等于t,它们不是同一个对象
只是做了一点改动,结果就和刚刚的例子恰好相反。我们再来分析一下。A和B虽然被定义为常量(只能被赋值一次),但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。
最后我们再来说说String对象在JAVA虚拟机(JVM)中的存储,以及字符串池与堆(heap)和栈(stack)的关系。我们首先回顾一下堆和栈的区别:
栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。
堆(heap):用于存储对象。
我们查看String类的源码就会发现,它有一个value属性,保存着String对象的值,类型是char[],这也正说明了字符串就是字符的序列。当执行String a="abc";时,JAVA虚拟机会在栈中创建三个char型的值'a'、'b'和'c',然后在堆中创建一个String对象,它的值(value)是刚才在栈中创建的三个char型值组成的数组{'a','b','c'},最后这个新创建的String对象会被添加到字符串池中。
如果我们接着执行String b=new String("abc");代码,由于"abc"已经被创建并保存于字符串池中,因此JAVA虚拟机只会在堆中新创建一个String对象,但是它的值(value)是共享前一行代码执行时在栈中创建的三个char型值值'a'、'b'和'c'。
说到这里,我们对于篇首提出的String str=new String("abc")为什么是创建了两个对象这个问题就已经相当明了了。
5.String 和StringBuffer,StringBuilder的区别
String是不可改变长度的,定长;
StringBuffer是不定长,可改变,通过分配更大的内存实现,StringBuffer是线程安全的可变字符序列。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步。
StringBuilder是单个线程使用的StringBuffer等价类,一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
在 StringBuilder 上的主要操作是 append 和 insert 方法, append 方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。将 StringBuilder 的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用 StringBuffer。
注意:本来以为StringBuilder和StringBuffer的equals方法是可以比较两个字符串的内容是否相等,今天才发现不是这么回事。这两个类都直接继承自Object,并且没有重写equals方法。
StringBuildersb1 = new StringBuilder("123");
StringBuildersb2 = new StringBuilder("123");
System.out.println(sb1.equals(sb2));
输出结果是:false
若要比较内容是否相同,sb1.toString().equals(sb2.toString())
比较:
StringBuffer str = new StringBuffer("a");
str.append("bc");
String str2 = "a";
str2 = str2 + "bc";
str引用的一直是同一对象;而str2引用的在两句中是完全不同的对象。
Java的StringBuffer类
(1)构造方法:
a. StringBuffers0=new StringBuffer();分配了长16字节的字符缓冲区
c. StringBuffers2=new StringBuffer("You are good!"); 在字符缓冲区中存放字符串"Happynew year!",另外,后面再留了16字节的空缓冲区。
(2)常用方法:
a.字符设置和替换
setCharAt(intindex,char ch),将指定的字符ch放到index指出的位置。
charAt(intindex) 获得指定位置的字符
例如: s="stedent";
s.setCharAt(2,"u"),则s为"student"
b.字符的插入
insert(int offset,charch),在offset位置插入字符ch。
例如:StringBuffer s=new StringBuffer("wecome");
s.insert(2,'l'),则s为"welcome"
c.在字符串末尾添加内容(Append方法)
StringBuffers=newStringBuffer("we");
chard={"l","c","o","m","e"};
s.append(d);则s为"welcome"。
d.转换为不变字符串:toString()。
StringBuffersb=new StringBuffer("How are you?");
Label l1=newLabel(sb.toString());
e.获取字符串的长度: length()
StringBuffers = new StringBuffer("www");
int i=s.length();
j.字符串反转 s.reverse();
k.删除指定字符串着中的字符
delete(intstart,int end)
s.delete(0,s.length()); //删除字符串s的全部字符
deleteCharAt(int index)
s.deleteCharAt(4); //删除字符串s索引为4的字符
l.替换字符串
replace(int start,intend,String str)
s.replace(0,1,"qqq");
m.返回字符串的一部分值
substring(int start) //返回从start下标开始以后的字符串
substring(int start,int end) //返回从start到 end-1字符串
public class TestStringBuffer {
public staticvoid main(String args[]) {
String s= "dengkehai" ;
char[] a= {'a','b','c'} ;
StringBuffer sb1 = new StringBuffer(s) ;
sb1.append('/').append("DKH").append('/').append("J2SE");
System.out.println(sb1) ;
StringBuffer sb2 = new StringBuffer("Number:") ;
for(inti=0 ;i<10 ;i++)
sb2.append(i) ;
System.out.println(sb2) ;
sb2.delete(10,sb2.length()).insert(0,a) ;
System.out.println(sb2);
System.out.println(sb2.reverse()) ;
}
}//结果:
dengkehai/DKH/J2SE
Number:0123456789
abcNumber:012
210:rebmuNcba
22.C和Java字符串和字符串数组的异同点
1. C 语言
在C语言中字符串和字符数组基本上没有区别,都需要结束符;如:char s[4]={'a','b','c','d'};此字符数组的定义编译可以通过,但却没有关闭数组,若其后需要申请内存,那么以后的数据均会放入其中,尽管它的长度不够,但若为 char s[5]={'a','b','c','d'};则系统会自动在字符串的最后存放一个结束符,并关闭数组,说明字符数组是有结束符的;
而字符串定义的长度必须大于字符序列的长度,如:char s1[4]={"abcd"};编译不能通过,而应写成char s1[5]={"abcd"};并且系统会自动在字符串的最后存放一个结束符,说明字符串有结束符;
在C语言中使用strlen()函数可以测数组的长度,strlen()函数计算的时候不包含结束符'\0'。
chars[5]={'a','b','c','d'};
chars1[5]={"abcd"};
int a=strlen(s);
int b=strlen(s1);
结果是a,b均为4;
2.Java语言
字符串和字符串数组都是不需要结束符的;
如:char[] value={'j','a','v','a','语','言'};
String s1=new String(value);
String s2="java语言";
int a=value.length;
int b=s1.length();
int c=s2.length();
运行得到的结果a,b,c都是6,说明字符串和字符串数组都不需要结束符。但注意此处value.length和s1.length(),在数组中有名常量length可以记录数组对象的长度,而length()是File类中的一个实例方法,用于返回文件的大小,当然也可以返回字符串的大小。
14.字符串分割问题
public String[]split(String regex, int limit)根据匹配给定的正则表达式来拆分此字符串。SPLIT参数支持正则表达式,s.split("\\|+");//以|或多个|做为分割符号分割字符串,如果表达式不匹配输入的任何部分,则结果数组只具有一个元素,即此字符串。limit 参数控制模式应用的次数,因此影响结果数组的长度。如果该限制 n 大于 0,则模式将被最多应用 n - 1 次,数组的长度将不会大于 n,而且数组的最后项将包含超出最后匹配的定界符的所有输入。如果 n 为非正,则模式将被应用尽可能多的次数,而且数组可以是任意长度。如果 n 为零,则模式将被应用尽可能多的次数,数组可有任何长度,并且结尾空字符串将被丢弃。
split(String regex)中将默认第二个参数为0,会取消掉最后的空字符串。
public classSplitTest {
public static void main(String[] args) {
String[] num = null; //这里不用创建数组,split 方法会帮你创建大小合适的数组
String sLine="101494|360103660318444|2008/06/17|周润英|1292.0|3085.76|2778.28|912.91|106.0|||";
num = sLine.split("\\|", -1);
for (int i = 0; i < num.length; ++i){
System.out.println(i + ":" + num[i]);
}}}
结果
0: 101494
1: 360103660318444
2: 2008/06/17
3: 周润英
4: 1292.0
5: 3085.76
6: 2778.28
7: 912.91
8: 106.0
9:
10:
11:
public staticString[] Split(String str, String sp) {// str要分割的字符串; sp分隔符;return 分割后的字符串;
StringTokenizer st = newStringTokenizer(str, sp);
String strSplit[];
try {
int stLength=st.countTokens();//获取分割后的数量
if(stLength<=1) { returnnull; }
strSplit=new String[stLength];
int i=0;
while(st.hasMoreTokens()) {
strSplit[i]=st.nextToken().toString();
i++;
} }
catch(Exception e) { return null; }
return strSplit;
}
15.字符串读取函数
我把文件名存在数据库中,现在我想通过"."后面的后缀名来判断是什么类型的文件比如:TXT,JPG
可以先讀出這個字符串,比如str ;再用int i=str.indexOf('.') ;再用String str1=str.substring(i,i+3); 再用str1 跟TXT,JPG等匹配判斷是甚麼類型的
13.java中有几种类型的流?
在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。C/C++只能提供字节流。Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的
字符流和字节流是根据处理数据的不同来区分的。字节流按照8位传输,字节流是最基本的,所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。
1.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
读文本的时候用字符流,例如txt文件。读非文本文件的时候用字节流,例如mp3。理论上任何文件都能够用字节流读取,但当读取的是文本数据时,为了能还原成文本你必须再经过一个转换的工序,相对来说字符流就省了这个麻烦,可以有方法直接读取。
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节, 操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!
1.字节流:继承于InputStream\ OutputStream。
OutputStream提供的方法:
void write(int b):写入一个字节的数据
void write(byte[] buffer):将数组buffer的数据写入流
void write(byte[] buffer,int offset,int len):从buffer[offset]开始,写入len个字节的数据
void flush():强制将buffer内的数据写入流
void close():关闭流
InputStream提供的方法:
int read():读出一个字节的数据,如果已达文件的末端,返回值为-1
int read(byte[] buffer):读出buffer大小的数据,返回值为实际所读出的字节数
int read(byte[] buffer,int offset,int len)
int available():返回流内可供读取的字节数目
long skip(long n):跳过n个字节的数据,返回值为实际所跳过的数据数
void close():关闭流
2.字符流,继承于InputStreamReader \ OutputStreamWriter。
字符流的类:1),BufferedReader是一种过滤器(filter)(extends FilterReader)。过滤
器用来将流的数据加以处理再输出。构造函数为:
BufferedReader(Reader in):生成一个缓冲的字符输入流,in为一个读取器
BufferedReader(Reader in,int size):生成一个缓冲的字符输入流,并指定缓冲区的大小为size
public class IOStreamDemo {
public voidsamples() throws IOException { //1.这是从键盘读入一行数据,返回的是一个字符串
BufferedReader stdin =newBufferedReader(new InputStreamReader(System.in));
System.out.print("Enter a line:");
System.out.println(stdin.readLine());
//2. 这是从文件中逐行读入数据
BufferedReader in = newBufferedReader(new FileReader("IOStreamDemo.java"));
String s, s2= new String();
while((s = in.readLine())!= null)
s2+= s + "\n";
in.close();
//3. 这是从一个字符串中逐个读入字节
StringReaderin1 = new StringReader(s2);
int c;
while((c = in1.read()) != -1)
System.out.print((char)c);
//4. 这是将一个字符串写入文件
try {
BufferedReaderin2 = new BufferedReader(newStringReader(s2));
PrintWriterout1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));
intlineCount = 1;
while((s = in2.readLine()) != null)
out1.println(lineCount++ + ": " + s);
out1.close();
}catch(EOFException e) {
System.err.println("End of stream");
}
} }
对于上面的例子,需要说明的有以下几点:
1. InputStreamReader是InputStream和Reader之间的桥梁,由于System.in是字节流,需要用它来包装之后变为字符流供给BufferedReader使用。
3. PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));
这句话体现了Java输入输出系统的一个特点,为了达到某个目的,需要包装好几层。首先,输出目的地是文件IODemo.out,所以最内层包装的是FileWriter,建立一个输出文件流,接下来,我们希望这个流是缓冲的,所以用BufferedWriter来包装它以达到目的,最后,我们需要格式化输出结果,于是将PrintWriter包在最外层。
Java流有着另一个重要的用途,那就是利用对象流对对象进行序列化。
在一个程序运行的时候,其中的变量数据是保存在内存中的,一旦程序结束这些数据将不会被保存,一种解决的办法是将数据写入文件,而Java中提供了一种机制,它可以将程序中的对象写入文件,之后再从文件中把对象读出来重新建立。这就是所谓的对象序列化。Java中引入它主要是为了RMI(Remote Method Invocation)和Java Bean所用,不过在平时应用中,它也是很有用的一种技术。
14.数据流,对象流,字节流,字符流
1.什么是数据流 ?
数据流是指所有的数据通信通道
有两类流,InputStream and OutputStream,Java中每一种流的基本功能依赖于它们
InputStream 用于read,OutputStream 用于write, 读和写都是相对与内存说的,读就是从其他地方把数据拿进内存,写就是把数据从内存推出去这两个都是抽象类,不能直接使用
4.File 类
File 可以表示文件也可以表示目录,File 类控制所有硬盘操作
构造器:
File(File parent,String child) 用父类和文件名构造
File(String pathname) 用绝对路径构造
File(String parent,String child) 用父目录和文件名构造
File(URI uri) 用远程文件构造
常用方法:
boolean createNewFile();
boolean exists();
5.文件流的建立
File f=newFile("temp.txt");
FileInputStream in=newFileInputStream(f);
FileOutputStream out=newFileOutputStream(f);
例子:文件拷贝
import java.io.*;
public class Copy{
public static void main(String args[]){
FileInputStream fis=null;
FileOutputStream fos=null;
try{
fis=new FileInputStream("c2.gif");
fos=newFileOutputStream("c2_copy.gif");
int c;
while((c=fis.read()) != -1)
fos.write(c);
}catch(Exception e){
e.printStackTrace();
}finally{
if(fis != null) try{ fis.close();}catch(Exception e){ e.printStackTrace(); }
if(fos!= null) try{ fos.close(); }catch(Exceptione){ e.printStackTrace(); }
}}}
6.缓冲区流
BufferedInputStream
BufferedOutputStream
他们是在普通文件流上加了缓冲的功能,所以构造他们时要先构造普通流
例子:文件拷贝的缓冲改进
import java.io.*;
public class Copy{
public static void main(String args[]){
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
byte buf[]=new byte[100];
try{
bis=new BufferedInputStream(newFileInputStream("persia.mp3"));
bos=new BufferedOutputStream(newFileOutputStream("persia_copy.mp3"));
int len=0;
while( true ){
len=bis.read(buf);
if(len<=0) break;
bos.write(buf,0,len);
}
bos.flush();//缓冲区只有满时才会将数据输出到输出流,用flush()将未满的缓冲区中数据强制输出
}catch(Exception e){
e.printStackTrace();
}finally{
if(bis != null) try{ bis.close();}catch(Exception e){ e.printStackTrace(); }
if(bos!= null) try{ bos.close();}catch(Exception e){ e.printStackTrace(); }
}}}
7.原始型数据流
DataInputStream
DataOutputStream
他们是在普通流上加了读写原始型数据的功能,所以构造他们时要先构造普通流方法:
readBoolean()/writeBoolean()
readByte()/writeByte()
readChar()/writeByte()
......
例子://这个流比较简单,要注意的就是读时的顺序要和写时的一样
import java.io.*;
public class DataOut{
public static void main(String args[]){
DataOutputStream dos=null;
try{
dos=new DataOutputStream(newFileOutputStream("dataout.txt"));
dos.writeInt(1);
dos.writeBoolean(true);
dos.writeLong(100L);
dos.writeChar('a');
}catch(Exception e){
e.printStackTrace();
}finally{
if(dos!=null)
try{
dos.close();
}catch(Exception e){
}}}}
import java.io.*;
public class DataIn{
public static void main(String args[]){
DataInputStream dis=null;
try{
dis=new DataInputStream(newFileInputStream("dataout.txt"));
System.out.println(dis.readInt());
System.out.println(dis.readBoolean());
System.out.println(dis.readLong());
System.out.println(dis.readChar());
}catch(Exception e){
e.printStackTrace();
}finally{
if(dis!=null)
try{
dis.close();
}catch(Exception e){
}}}}
8.对象流
串行化:对象通过写出描述自己状态的数值来记述自己的过程叫串行化
对象流:能够输入输出对象的流
将串行化的对象通过对象流写入文件或传送到其他地方,对象流是在普通流上加了传输对象的功能,所以构造对象流时要先构造普通文件流
注意:只有实现了Serializable接口的类才能被串行化
例子:
import java.io.*;
class Student implements Serializable{
private String name;
private int age;
public Student(String name,int age){
this.name=name;
this.age=age;
}
public void greeting(){
System.out.println("hello ,my name is"+name);
}
public String toString(){
return"Student["+name+","+age+"]";
}}
public class ObjectOutTest{
public static void main(String args[]){
ObjectOutputStream oos=null;
try{
oos=new ObjectOutputStream(newFileOutputStream("student.txt"));
Student s1=newStudent("Jerry",24);
Student s2=newStudent("Andy",33);
oos.writeObject(s1);
oos.writeObject(s2);
}catch(Exception e){
e.printStackTrace();
}finally{
if(oos!=null)
try{
oos.close();
}catch(Exception e){
e.printStackTrace();
}}}}
import java.io.*;
public class ObjectInTest{
public static void main(String args[]){
ObjectInputStream ois=null;
Student s=null;
try{
ois=newObjectInputStream(new FileInputStream("student.txt"));
System.out.println("--------------------");
s=(Student)ois.readObject();
System.out.println(s);
s.greeting();
System.out.println("--------------------");
s=(Student)ois.readObject();
System.out.println(s);
s.greeting();
}catch(Exception e){
e.printStackTrace();
}finally{
if(ois!=null)
try{
ois.close();
}catch(Exception e){
e.printStackTrace();
}}}}
9.字符流InputStreamReader/OutputStreamWriter
上面的几种流的单位是 byte,所以叫做字节流,写入文件的都是二进制字节,我们无法直接看,下面要学习的是字节流。Java采用Unicode 字符集,每个字符和汉字都采用2个字节进行编码,ASCII 码是 Unicode 编码的子集。
InputStreamReader 是 字节流 到 字符桥的桥梁 ( byte->char 读取字节然后用特定字符集编码成字符)
OutputStreamWriter是 字符流 到 字节流的桥梁 ( char->byte )
他们是在字节流的基础上加了桥梁作用,所以构造他们时要先构造普通文件流
我们常用的是:
BufferedReader 方法:readLine()
PrintWriter 方法:println()
例子:
import java.io.*;
public class PrintWriterTest{
public static void main(String args[]){
PrintWriter pw=null;
try{
pw=newPrintWriter(new OutputStreamWriter(newFileOutputStream("bufferedwriter.txt")));
pw.println("hello world");
}catch(Exception e){
e.printStackTrace();
}finally{
if(pw!=null)
try{
pw.close();
}catch(Exception e){
e.printStackTrace();
}}}}
import java.io.*;
public class BufferedReaderTest{
public static void main(String args[]){
BufferedReader br=null;
try{
br=newBufferedReader(new InputStreamReader(newFileInputStream("bufferedwriter.txt")));
System.out.println(br.readLine());
}catch(Exception e){
e.printStackTrace();
}finally{
if(br!=null)
try{
br.close();
}catch(Exception e){
e.printStackTrace();
}}}}
11.小结
a. 字节流:
InputStream
|-- FileInputStream (基本文件流)
|-- BufferedInputStream
|-- DataInputStream
|--ObjectInputStream
OutputStream 同上图
BufferedInputStream,DataInputStream,ObjectInputStream 只是在FileInputStream 上增添了相应的功能,构造时先构造FileInputStream
b. 字符流:
Reader
|-- InputStreamReader (byte->char 桥梁)
|-- BufferedReader (常用)
Writer
|-- OutputStreamWriter (char->byte 桥梁)
|-- BufferedWriter
|-- PrintWriter (常用)