Java期末自救篇
0x01 Java语言基础知识
Java程序的特点
- Java是面向对象的程序设计语言:可重用性、可靠性
将客观事物看作具有状态和行为的对象,通过抽象找出同一类对象的共同状态和行为,构成类。
- 安全性
- 平台无关性:编译后可在不同平台上运行
- 多线程
- 内存管理:Java对内存自动进行管理并进行垃圾回收
- 编译型语言(Java的开发环境)
先通过编译器(Javac)编译为 .class文件,再由解释器(Java)从 .class文件中读一行解释一行
Java的特征(面向对象语言特征)
- 抽象和封装:抽象的结果形成类。可以根据需要设置不同的访问控制属性
- 继承性:可以对已有类增加属性和功能,或进行部分修改来建立新的类。实现代码的重用
- 多态性:在面向对象的程序中,同一个消息被不同的对象接收后可以导致不同的行为
基本数据类型与表达式
种类
基本数据类型:数字型、字符型、布尔型
引用数据类型:类、接口、数组
标识符
第一个字符必须是大写字母、小写字母、_或$,后面的字符可以是字母,下划线,$或数字
数字不能作为标识符第一个字母
不能含有非法字符
关键字不能作为标识符
变量与常量
与C语言基本相同,在常量前加final关键字
//几种文字量类型
System.out.println(100);
System.out.println(3.14);
System.out.println(true);
System.out.println(false);
System.out.println('a');
System.out.println('中');
System.out.println("abc");
// System.out.println('ab'); 会编译报错,因为ab为字符串型要用双引号
System.out.println("国");// 这不属于字符型,因为使用双引号括起来了,所以是字符串型
命名规范
- 包名:全小写,中间可以由点分隔开,独一无二
如:java.io.* ; java.awt.event; - 类名、接口名:首字母大写,通常由多个单词合成一个类名,要求每个单词的首字母也要大写
如:class HelloWorld; interface Collection - 方法名:由多个单词合成,第一个单词为动词,首字母小写,后面的每个单词首字母大写
如:setName;getScore - 变量名:全小写,要有具体含义
如:length - 常量名:一般为全大写,如果由多个单词合成,可以用下划线隔开
如:int YEAR;int WEEK_OF_MONTH
类型转换
扩展转换
byte, char, short, int, long, float, double:整数向float或double转换时会损失精度
窄化转化
double, float, long, int, short, byte,char:可能会丢失信息
转换方法(Java是强类型语言,赋值操作时对类型检查):赋值转化(将表达式类型转换为指定变量的类型,只能扩展转换)、方法调用转换、强制转换、字符串转化(任何类型都可以转换为字符串类型)
表达式
表达式是由一系列变量、运算符、方法调用构成
标准输入输出
标准输入流:System.in
标准输出流:System.out
数组
数组由同一类型的一连串对象或基本数据组成,并封装在同一个标识符。
数组是对象,数组名是一个引用。
在数组中可以调用Object类的所有方法
数组声明:Type[] arrayName;
、Type arrayName[]
int[] intArray;
String[] stringArray;
int intArray[]
String stringArray[]
数组的创建:arrayName = new Type[componets number];
int [] ai;
ai = new int[10];//元素个数可以是常量也可以是变量
int ai[] = new int[10];
数组初始化:如果在声明时给出了数组的初始值,则程序将利用初始值创建数组并进行初始化;如果没有指定初始值则默认为0,false,null
数组的引用:arrayName[index]
index类型为int short byte char
元素的个数:arrayName.length;下标最大值 length-1(从0开始计数),当下标超过最大值会产生ArrayIndexOutOfBoundsException
多维数组:可以为整个数组分配对象、也可以为每一行单独分配内存(当每行的元素个数不同时),数组中每行的个数可以不同
0x02 类与对象
类
将属性及行为相同或相似的对象归为一类,可以看做是对象的抽象
- 抽象:忽略问题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。
- 封装:利用抽象数据类型将数据和基于数据的操作封装在一起,对象的内部细节对用户是透明的。清楚的边界、有接口与外界交互、受保护的内部实现
- 继承:新的类可以获得已有类(称为超类、基类或父类)的属性和行为,称新类为已有类的派生类(也称为子类) 在继承过程中派生类继承了基类的特性,包括方法和实例变量,派生类也可修改继承的方法或增加新的方法,使之更适合特殊的需要。单继承
- 多态:一个程序中同名的不同方法共存,子类对父类方法的覆盖
类VS对象:对象是类的具体实例。类是构造对象的模板,对象在Stack中存储,类在Heap中创建
具体应用代码
- 类的声明
[public][abstract | final] class classname [extends father_class] [implements interfaces_name]{
//变量成员声明及初始化
//方法声明及方法体
}
注:每个编译单元(文件)只能有一个public类。即每个编译单元只能有一个公开的接口。public类的名字必须和这个编译单元的文件名完全相同。
- 对象的创建
Clock aclock; //对象的声明,并没有对象生成
aclock = new Clock();//在内存中为此对象分配内存空间
//aclock变量存储对象的引用,相当于对象的存储地址
- 数据成员:
实例变量:没有static修饰的变量
类变量(静态变量):声明时要加static修饰符,在初始化的同时被赋值,不管类的对象有多少,类变量只存在一份,在整个类中只有一个值
[public| protected| private]//访问控制符
[static]//静态成员变量
[final]//常量的值不能被改变
[transient]//变量是临时状态
[volatile] //变量是一个共享变量
数据类型 变量名1[=value1];
//类变量实例
public class Point
{
private int x;
private int y;
public static int pointCount=0;
public Point(int x, int y){
this.x = x;
this.y = y;
pointCount++;
}
public static void main(String[] args)
{
Point p = new Point(1,1);
System.out.println(p.pointCount);// 1
Point q = new Point(2,2);
System.out.println(q.pointCount);// 2
System.out.println(q.pointCount == Point.pointCount);// true
System.out.println(Point.pointCount);// 2
}
}
- 方法成员:定义类的行为
[public | protected | private]
[static][final][abstract][native][synchronized] 返回类型 function_name(argv[0] value0,[argv[1] value1...]) [throw exception1,exception2...]{
方法体
}
实例方法:表示特定对象的行为,声明时前面不加static修饰符,需要在一个类实例上调用
public class Circle {
static double PI = 3.14159265;
int radius;
public double circumference() {
return 2 * PI * radius;
}
}
类方法:静态方法,表示类中对象共有的行为,需要加static修饰符,可以在不建立对象的情况下利用类名直接调用
不能使用this/super,使用时可用ClassName.var/ClassName.method()调用
不能被重写(但可以被隐藏(即除方法体其他均相同))
在静态方法中只能直接调用静态变量,静态方法或对象的属性和方法,不能直接使用非静态方法和非静态变量
public class Converter {
public static int centigradeToFahrenheit(int cent)
{
return (cent * 9 / 5 + 32);
}
}
//方法调用
Converter.centigradeToFahrenheit(40)
- 包的概念
将相关的源代码文件组织在一起
用import导入外部的类、同一个包中的类不需要import
关键字顺序:package import class - 类的访问控制
要修改private变量只能通过添加set和get方法得到
public class Circle {
static double PI = 3.14159265;
private int radius;
public double circumference() {
return 2 * PI * radius;
}
public int getRadius(){
return radius;
}
public void setRadius(int r){
radius = r;
}
}
public class CircumferenceTester {
public static void main(String args[]) {
Circle c1 = new Circle();
// c1.radius = 50; 编译器会报错,无法修改
c1.setRadius(50);
Circle c2 = new Circle();
// c2.radius = 10; 编译器会报错,私有变量无法从外部类直接修改
c2.setRadius(10); //正确方法
double circum1 = c1.circumference();
double circum2 = c2.circumference();
System.out.println("Circle 1 has circumference " + circum1);
System.out.println("Circle 2 has circumference " + circum2);
}
}
- final修饰符
可以修饰类、方法、变量
修饰类时,此类不可被继承
修饰方法时,方法不能被重写
修饰变量时,变量变为常量,必须有初始化,不能被改变 - 构造方法
如果在类的声明中没有声明构造方法,Java编译器会默认一个构造方法,没有参数,方法体为空,对象的属性值为0或null(无初始化时)
但当声明构造方法时,就不在有默认的构造方法 - 方法重载
在Java中,同一个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载
参数列表不同包括:个数不同、顺序不同、类型不同。
仅仅参数变量名称不同是不可以的。跟成员方法一样,构造方法也可以重载。声明为final的方法不能被重载。
声明为static的方法不能被重载,但是能够被再次声明。
方法的重载的规则:
方法名称必须相同。参数列表必须不同。方法的返回类型可以相同也可以不相同。仅仅返回类型不同不足以称为方法的重载。 - this关键字
- 可以使用this关键字在一个构造方法中调用另外的构造方法
- 对this的调用必须是构造函数中的第一个语句
- 通常用参数个数比较少的构造方法调用参数个数最多的构造方法
- 通常也用在区分本类中的参数与引用的参数(当变量名相同时)
public BankAccount() {
this("", 999999, 0.0f);
}
public BankAccount(String initName, int initAccountNumber) {
this(initName, initAccountNumber, 0.0f);
}
public BankAccount(String initName, int initAccountNumber,
float initBalance) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = initBalance;
}
public void saveMoney(double balance){
this.balance = balance;
}
UML
- 类图
- 类的关系表示
聚集:表示部分与整体关系的关联
组成:部分类完全隶属于整体类,部分与整体共存
泛化:也就是常说的继承
0x03 继承、抽象、组合
继承
语法:
class childClass extends parentClass{
类体
}
public class Person {
public String name;
public String getName() {
return name;
}
}
public class Employee extends Person {
public int employeeNumber;
public int getEmployeeNumber() {
return employeeNumber;
}
}
public class Manager extends Employee {
public String responsibilities;
public String getResponsibilities() {
return responsibilities;
}
}
子类不能直接访问从父类中继承的私有属性及方法,但可使用公有(及保护)方法进行访问
public class B {
public int a = 10;
private int b = 20;
protected int c = 30;
public int getB() {
return b; }
}
public class A extends B {
public int d;
public void tryVariables() {
System.out.println(a); //允许
System.out.println(b); //不允许
System.out.println(getB()); //允许
System.out.println(c); //允许
}
}
- 属性的隐藏:子类中声明了与父类相同的成员变量名,则从父类继承的变量被隐藏。可以利用super.属性(方法)访问被隐藏的父类属性和方法。是针对于成员变量和静态方法而言的
- 方法的覆盖:覆盖方法的返回类型,方法名称,参数的个数及类型必须和被覆盖的方法一摸一样,是针对于实例方法而言的
注:有抽象类必有覆盖方法,final和static方法不能被覆盖,若父子有同为static方法则为隐藏
终结类与终结方法
被final修饰符修饰的类和方法
终结类不能被继承
终结方法不能被当前类的子类重写
抽象类(abstract)
不能被实例化,必须有子类覆盖其声明的方法
包的应用
Java基础类库:java.lang、java.util、java.io
泛型
本质是参数化类型,所有类型转换都是自动和隐式的
泛型类:在类名后面加上<Type>
泛型方法:在方法名前加上<Type>
class GeneralType <Type> {
Type object;
public GeneralType(Type object) {
this.object = object;
}
public Type getObj() {
return object;
}
}
public class Test {
public static void main(String args[]){
GeneralType<Integer> i = new GeneralType<Integer> (2);
GeneralType<Double> d = new GeneralType<Double> (0.33);
System.out.println("i.object=" + (Integer)i.getObj());
// System.out.println("i.object=" + (Integer)d.getObj()); 不能通过编译
}
}
- 通配符泛型
class GeneralType <Type> {
Type object;
public GeneralType(Type object) {
this.object = object;
}
public Type getObj() {
return object;
}
}
class ShowType {
public void showType(GeneralType<?> o) {
//"?"表示通配符,可以传入任意类型的数据
System.out.println(o.getObj().getClass().getName());
}
}
public class Test {
public static void main(String args[]){
ShowType st = new ShowType();
GeneralType<Integer> i = new GeneralType<Integer> (2);
GeneralType<String> s = new GeneralType<String> ("hello");
st.showType(i);
st.showType(s);
}
}
//程序的运行结果如下:
//java.lang.Integer
//java.lang.String
- 有限制的泛型
在参数“Type”后面使用“extends”关键字并加上类名或接口名,表明参数所代表的类型必须是改类的关键字或者实现了该接口
class GeneralType <Type extends Number> {
Type object;
public GeneralType(Type object) {
this.object = object;
}
public Type getObj() {
return object;
}
}
public class Test {
public static void main(String args[]){
GeneralType<Integer> i = new GeneralType<Integer> (2);
//GeneralType<String> s = new GeneralType<String> ("hello");//非法,
//T只能是Number或Number的子类
}
}
0x04 接口与多态
接口
是一个纯抽象类,允许创建者规定方法的基本形式:方法名、参数列表以及返回类型,但不规定方法主体,也可以包含基本数据类型的数据成员,但都默认为static和final
为了实现多继承
注:接口数据成员为static/final,方法成员为abstract
接口作用:允许我们在看起来不相干的对象之间定义共同行为
//接口定义
interface 接口名称 [extends 父接口名]{
/*方法的原型声明或静态常量,数据成员一定要赋值,可以忽略public和abstract关键字*/
}
public interface Insurable {
public int getNumber();
public int getCoverageAmount();
public double calculatePremium();
public Date getExpiryDate();
}
//接口实现
public class 类名称 implements 接口名称{
/* Bodies for the interface methods */ /* Own data and methods. */
}
//必须实现接口中所有方法,来自接口的方法必须声明成public
塑性(类型转换)
- 塑造对象包括:基本数据类型、引用变量(父类、接口,本身的类)
- 方法:
- 隐式的类型转换
相容类型之间存储容量低的自动向存储容量高的类型转换
//被塑性成更一般的类
Employee emp;
emp = new Manager();//将Manager类型的对象直接赋给Employee类的引用变量,将Manager对象塑性为Employee类
//被塑性为对象所属类实现的接口类型
Car jetta = new Car();
Insurable item = jetta;
- 显式的类型转换
//基本数据类型
(int)871.34354; //871
(char)65;//'A'
(long)453; // 453L
//引用变量:还原为本来的类型
Employee emp;
Manager man;
emp = new Manager();//将Manager向上塑型为Employee,此时emp中如果有被重写的方法,则调用Manager类中的方法,但此时不能调用Manager类中的特有方法
man = (Manager)emp;//将Manager还原回去,此时可以调用Manager类中的特有方法
//在向上和向下的塑型过程中一直只有一个Manager被转换,不能转换为Employee的其他子类,只能向下塑型回Manager类
-
应用场合
- 赋值转化:赋值号右边的表达式或对象转换为左边的类型
- 方法调用转化:实参的类型转换为形参的类型
- 算数表达式转换
- 字符串转化
-
方法的查找
- 实例方法:从对象创建时的类开始,沿类层次向上查找
- 类方法:总是在引用变量声明时所属的类中进行查找
多态
- 指不同类型的对象可以响应相同的消息
- 目的:所有的对象都可被塑性为相同的类型,响应相同的消息、代码简单易于理解、很好的扩展性
- 绑定:将一个方法调用同一个方法主体连接到一起
动态绑定:基于对象的类别,在程序运行时执行绑定 - 原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法,但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。通过继承和接口实现多态
public class BindingTester{
public static void main(String[] args) {
Shape[] s = new Shape[9]; //Shape为Circle、Square、Triangle的基类
int n;
for(int i = 0; i < s.length; i++) {
n = (int)(Math.random() * 3);
switch(n) {
case 0:
s[i] = new Circle();
break;
case 1:
s[i] = new Square();
break;
case 2:
s[i] = new Triangle();
}
}
for(int i = 0; i < s.length; i++) s[i].draw();
}
}//编译时无法知道s数组元素的具体类型,运行时才能确定类型,所以是动态绑定
0x05 对象群体的组织
对象数组
数组元素都是类的对象、所有元素具有相同的类型、每个元素都是一个对象的引用
- 对象数组的初始化
//在声明和定义数组的同时对数组元素进行初始化
BankAccount[] accounts = {
new BankAccount(“Zhang", 100.00),
new BankAccount(“Li", 2380.00),
new BankAccount(“Wang", 500.00),
new BankAccount(“Liu", 175.56),
new BankAccount(“Ma", 924.02)};
//先定义再初始化
//首先给数组分配空间
type arrayName[] = new type[arraySize];
//然后给每一个数组元素分配空间
arrayName[0] = new type(paramList);
...
arrayName[arraySize-1] = new type(paramList);
集合
可以动态改变其大小,在序列中存储不同类型的数据
Collection集合
List
元素有顺序,每个元素都有对应的index值,元素可以重复
非同步的
- ArrayList
有序、元素可以为null、效率高、占用空间小 - LinkedList
双向链表,只能从头开始遍历 - List与Array的区别
相同 | 不同 |
---|---|
都可以表示一组同类型的对象 | 数组可以存任何类型元素,List不可以存基本数据类型 |
都使用下标进行索引 | 数组容量固定,List容量可变 |
数组效率高,List效率低一些 |
- Vector
与ArrayList并列
同步的
Stack
push入栈
pop栈顶元素出栈,并返回
peek获取栈顶元素
Set
禁止重复元素,HashSet,其中哈希函数为hashCode
常用方法
声明了一组操作成批对象的抽象方法:查询方法、修改方法
查询方法
int size() – 返回集合对象中包含的元素个数
boolean isEmpty() – 判断集合对象中是否还包含元素,如果没有任何元素,则返回true
boolean contains(Object obj) – 判断对象是否在集合中
boolean containsAll(Collection c) – 判断方法的接收者对象是否包含集合中的所有元素
修改方法包括
boolean add(Object obj) – 向集合中增加对象
boolean addAll(Collection<?> c) – 将参数集合中的所有元素增加到接收者集合中
boolean remove(Object obj) –从集合中删除对象
boolean removeAll(Collection c) -将参数集合中的所有元素从接收者集合中删除
boolean retainAll(Collection c) – 在接收者集合中保留参数集合中的所有元素,其它元素都删除
void clear() – 删除集合中的所有元素
Map
不是Collection接口的继承
存在键值对,不能有重复关键字,每个关键字映射一个值
HashMap速度很快,但没有顺序
Java集合框架
- Collection接口
- Set、SortedSet接口:禁止重复的元素
- List接口:可包含重复元素,有顺序
- Map、SortedMap接口:不是Collection接口的继承,描述了从不重复的键到值的映射
集合遍历(Iterator)
hasNext():判断是否还有元素
next():取得下一个元素
remove():去除一个元素,是从集合中去除最后调用next()返回的元素,而不是从Iterator类中去除
import java.util.Vector;
import java.util.Iterator;
public class IteratorTester {
public static void main(String args[]) {
String[] num = {
"one", "two", "three", "four", "five",
"six", "seven", "eight", "nine", "ten"};
Vector<String> aVector = new Vector<String> (java.util.Arrays.asList(num));
System.out.println("Before Vector: " + aVector);
Iterator<String> nums = aVector.iterator();
while(nums.hasNext()) {
String aString = (String)nums.next();
System.out.println(aString);
if (aString.length() > 4) nums.remove();
}
System.out.println("After Vector: " + aVector);
}
}
0x06 IO流(java.io包)
在Java中将信息的输入与输出过程抽象为I/O流
流的分类
-
从流的处理来分
- 节点流:从数据源读入或往目的地写出数据
- 处理流:对数据执行某种处理(不直接与数据源或目标相连,而是基于另一个流来构造) -
从流的内容划分
- 面向字符的流:专门用于字符数据(文本文件)
- 面向字节的流:用于一般目的
- 面向字符的流:专门用于字符数据(文本文件)
基本输入输出
//输入单个字符
import java.util.Scanner;
public class Demo59 {
public static void main(String[] args) {
//创建Scanner对象,接受从控制台输入
Scanner input=new Scanner(System.in);//默认为键盘;System.in 程序启动时自动创建的流对象,是原始的字节流
// setIn(InputStream);设置标准输入流
// setOut(PrintStream); 设置标准输出流
//接受String类型
String str=input.next();
//输出结果
System.out.println(str);//默认为屏幕输出
}
//输入一串字符
import java.util.Scanner;
public class Demo59 {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
//输入数组的长度
int len=input.nextInt();//根据返回类型不同,进行更改,nextByte(),nextDouble(),nextFloat,nextInt(),nextLine(),nextLong(),nextShort()
//创建一个数组
int[] array=new int[len];
//遍历数组,并给其赋值
for(int i=0;i<len;i++){
array[i]=input.nextInt();
}
//打印数组
for(int i:array){
System.out.print(i+" ");
}
}
}
//持续输入并随之回车输出
import java.io.*;
public class Echo {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
//BufferedReader 对InputStreamReader处理后的信息进行缓冲,InputStreamReader属于处理流
String s;
while((s = in.readLine()).length() != 0)
System.out.println(s);
}
}
文件读写
写入文本文件(Reader、Writer)
常用类:FileWriter(节点流)、BufferedWriter、OutputStreamWriter
FileWriter类
//写入文件中
import java.io.*;
class FileWriterTester {
public static void main ( String[] args ) throws IOException {
//main方法中声明抛出IO异常
String fileName = "C:\\Hello.txt";
//每次运行都将删除原有的文件,创建同名文件,重新写入
FileWriter writer = new FileWriter( fileName ); //一共有五种构造方法
//FileWriter writer = new FileWriter( fileName ,true );
//运行此句程序,会发现在原文件内容后面又追加了重复的内容,这就是将构造方法的第二个参数设为true的效果
writer.write( "Hello!\n");
writer.write( "This is my first text file,\n" );
writer.write( "You can see how this is done.\n" );
writer.write("输入一行中文也可以\n");
writer.close();
}
}
BufferedWriter类
当需要写入的内容很多时,利用更高效的缓冲器流类BufferedWriter
import java.io.*;
class BufferedWriterTester {
public static void main ( String[] args ) throws IOException {
String fileName = "C:/newHello.txt" ;
BufferedWriter out = new BufferedWriter( new FileWriter( fileName ) );
out.write( "Hello!" );
out.newLine() ; //换行
out.write( "This is another text file using BufferedWriter," );
out.newLine(); ;
out.write( "So I can use a common way to start a newline" );
out.close();
}
}
读出文本文件
常用类:FileReader(节点流)、BufferedReader、InputStreamReader
FileReader类
继承自Reader抽象类的子类InputStreamReader,从文本文件中读取字符
BufferedReader类
读文本文件的缓冲器类,继承自Reader
//文件读入
import java.io.*;
class BufferedReaderTester {
public static void main ( String[] args ) {
String fileName = "C:/Hello.txt" , line;
try {
BufferedReader in = new BufferedReader(new FileReader( fileName ) );//若文件不存在会抛出IOException
line = in.readLine(); //读取一行内容
while ( line != null ) {
System.out.println( line );
line = in.readLine();
}
in.close(); //一定要记得关闭文件
//int c;
//while((c=in.read())!= -1) System.out.print((char)c);
//Reader类的read()方法也可用来判别文件结束。该方法返回的一个表示某个字符的int型整数,如果读到文件末尾,返回 -1
}
catch ( IOException iox ) {
System.out.println("Problem reading " + fileName );
}
}
}
写二进制文件(OutputStream、InputStream)
常用类:FileOutputStream、BufferedOutputStream、DataOutputStream
import java.io.*;
class FileOutputstreamTester {
public static void main ( String[] args ) {
String fileName = "c:/data1.dat" ;
int value0 = 255, value1 = 0, value2 = -1;
try {
DataOutputStream out = new DataOutputStream(new FileOutputStream( fileName ) );
out.writeInt( value0 );
out.writeInt( value1 );
out.writeInt( value2 );
out.close();
}
catch ( IOException iox ){
System.out.println("Problem writing " + fileName ); }
}
}
//写完后查看二进制信息,发现内容为00 00 00 FF 00 00 00 00 FF FF FF FF
BufferedOutputStream类
类似于BufferedWriter,对于大量数据的写入可以提高效率
//向文件中写入各种数据类型的数,并统计写入的字节数
import java.io.*;
class BufferedOutputStreamTester {
public static void main ( String[] args ) throws IOException {
String fileName = "mixedTypes.dat" ;
DataOutputStream dataOut = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream( fileName ) ) );
dataOut.writeInt( 0 );
System.out.println( dataOut.size() + " bytes have been written.");//4
dataOut.writeDouble( 31.2 );
System.out.println( dataOut.size() + " bytes have been written.");//12
dataOut.writeBytes("JAVA");
System.out.println( dataOut.size() + " bytes have been written.");//16
dataOut.close();
}
}
读二进制文件
常用类:FileInputStream、DataInputStream、BufferedInputStream
import java.io.*;
class DataInputStreamTester {
public static void main ( String[] args ) {
String fileName = "c:/data1.dat" ; long sum = 0;
try {
DataInputStream instr = new DataInputStream(
new BufferedInputStream(new FileInputStream(fileName)));
try {
while ( true )
sum += instr.readInt();
}
catch ( EOFException eof ) {
//遇到文件结尾会抛出此异常
System.out.println( "The sum is: " + sum );
instr.close();
}
}
catch ( IOException iox ) {
System.out.println("IO Problems with " + fileName ); }
}
}
}
//文本文件的存储方式也是二进制代码,也可以用InputStream类的方法读取
import java.io.*;
public class InputStreamTester {
public static void main(String[] args) throws IOException {
FileInputStream s=new FileInputStream("c:/Hello.txt");
int c;
while ((c = s.read()) != -1) //读取1字节,结束返回-1
System.out.write(c);
s.close();
}
}
0x07 异常处理
异常处理的分类
总的分类
- 编译错误(Syntax errors):是编译器能够检测到的错误,一般是语法错误
- 运行错误(Runtime errors):运行过程中出现无法执行的操作(如被零除,数组下标越界等)
- 逻辑错误(Logic errors):机器无法检测,需要人对运行结果进行分析
错误
在Error类下的错误都是用户程序无法解决的
异常
- 非检查型异常(Error和RuntimeException类的派生类):不需要进行声明,主要是程序错误
- 检查型异常:通常是程序的逻辑错误,必须在方法签名中声明所抛出的异常
异常的处理(检查型异常)
- 声明抛出异常(throws):不在当前方法内处理,而是把异常抛出到调用方法中
使用情况:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责
public void openThisFile(String filename) throws FileNotFoundException{
}
//也可以同时抛出多个异常
public void openThisFile(String filename) throws FileNotFoundException,ClassNotFoundException{
}
//可以通过手动显式抛出一个异常(throw)
public void setRadius(double newRadius) throws IllegalArgumentException {
if (newRadius >= 0)
radius = newRadius;
else
throw new IllegalArgumentException("Radius cannot be negative");
}
- 异常捕获:使用try{}catch(){}finally{},捕获到所发生的的异常,并进行相应的处理
//可以同时catch多个异常
try{
statements;
}
catch(Exception1 exVar1){
handler for exception1;//可用throw语句生产异常对象
// throw new Exception(); 重复抛出异常
}
catch(Exception2 exVar2){
handler for exception2;
}
//finally用法
//some codes
try {
statements;
}
catch(TheException ex) {
handling ex;
}
finally {
finalStatements;
}
Next Statements;
- 在调用的方法中处理-> 抛出(当此方法是被调用的时候,发送的异常选择抛出,返回到上一层中去处理)
- 在方法内部处理 -> 直接处理(如果是在此方法内部出现异常,则直接处理)
创建异常类
要extends Exception
同时在构造函数里面要super("");
Finally使用注意
- 当Exception在方法内解决后,会执行finally中的语句,然后继续执行Next Statements的语句;当catch中的解决办法是再次抛出异常时,会先执行finally中的语句,然后再抛出异常**(必须知道)**
- 但是当try前面的语句出现异常时,就不会再执行finally中的语句
- 在 try块中即便有return,break,continue等改变执行流的语句,finally也会执行
- finally中的return会抑制(消灭)前面try或者catch块中的异常
- finally中的return 会覆盖 try 或者catch中的返回值
- 最好使用finally块来释放资源
0x08 线程
线程的概念
一个程序中多段代码同时并发执行
Thread类
实现了Runnable接口,位于java.lang包中
public class FactorialThreadTester{
public static void main( String [] args) {
System.out.println("main thread starts");
FactorialThreadthread=new FactorialThread(10);
thread.start(); //将自动进入run()方法System.out.println("main thread ends " );
}
}
class FactorialThread extends Thread{
private int num;
public FactorialThread( int num ) {
this.num=num;
}
public void run() {
int i=num;
ntresult=1;
System.out.println("new thread started" );
while(i>0) {
result=result*i;
i=i-1;
}
System.out.println("The factorial of "+num+" is "+result);
System.out.println("new thread ends");
}
}
//运行结果
//main thread starts 说明main函数启动后没有等待run()返回完成,就继续运行
//main thread ends
//new thread started
//The factorial of 10 is 3628800
//new thread ends
在启动多个线程的时候,程序启动线程的顺序是随机的
Runnable接口
- 只有一个run()方法
- 更便于多个线程共享资源(使用同一个数据)
//共用一个数据
public class TestThread implements Runnable{
private int sleepTime;
public TestThread() {
sleepTime= (int)(Math.random()*6000);
}
public void run() {
try {
System.out.println(Thread.currentThread().getName()+" going to sleep for "+sleepTime);
Thread.sleep(sleepTime);
}catch(InterruptedException exception) {
};
System.out.println(Thread.currentThread().getName()+" finished");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
TestThread thread1 = new TestThread();
System.out.println("Starting threads");
//使用Runnable接口的类要用Thread(Runnable target[,name]),方法创建对象
new Thread(thread1,"Thread1").start();
new Thread(thread1,"Thread2").start();
new Thread(thread1,"Thread3").start();
System.out.println("Threads strat,main ends\n");
}
}
// 运行结果
//Starting threads
//Threads strat,main ends
//Thread1 going to sleep for 4506
//Thread2 going to sleep for 4506
//Thread3 going to sleep for 4506
//Thread3 finished
//Thread1 finished
//Thread2 finished
创建多线程的方法
- 继承Thread类,重写Thread类中的run方法,并创建子类的对象,调用strat()方法启动新线程
- 实现Runnable接口,利用Thread(target,name)创建,其中target为实现了Runnable接口的对象
多线程的同步控制
线程同步:
- 互斥:许多线程在同一个共享数据上操作而互不干扰,同一时刻只能有一个线程访问该共享数据,有些方法或程序段在同一时刻只能被一个线程执行,称之为监视区
Synchronized关键字
实现互斥,用于指点需要同步的代码段或方法
功能:判断这个对象可以不可以被使用(锁旗标在不在),如果可以(锁旗标在)就执行后面的代码段(拿走锁旗标);如果不能被使用(锁旗标不在,此数据被其他线程使用),就进入等待状态,直到可以使用此对象。用完后释放锁旗标
//将需要互斥的语句段放入synchronized(object){}语句中,且两处的object是相同的
class Producer extends Thread {
Tickets t=null;
public Producer(Tickets t) {
this.t=t;
}
public void run() {
while((t.number)<t.size) {
synchronized(t){
// 申请对象t的锁旗标
System.out.println("Producer puts ticket" +(++t.number));
t.available=true;
} // 释放对象t的锁旗标
}
System.out.println("Producer ends!");
}
}
class Consumer extends Thread {
Tickets t=null;
int i=0;
public Consumer(Tickets t) {
this.t=t;
}
public void run() {
while(i<t.size) {
synchronized(t) {
//申请对象t的锁旗标
if(t.available==true && i<=t.number)
System.out.println("Consumer buys ticket "+(++i));
if(i==t.number) {
try{
Thread.sleep(1);
} catch(Exception e){
}
t.available=false;
}
}//释放对象t的锁旗标
}
System.out.println("Consumer ends");
}
}
除了对指定的代码段进行同步控制,还可以定义整个方法在同步控制下进行
class Tickets {
int size; //票总数
int number=0; //存票序号
int i=0; //售票序号
boolean available=false; //是否有待售的票
public Tickets(int size) {
this.size=size;
}
public synchronized void put(){
//同步方法,实现存票的功能
System.out.println("Producer puts ticket "+(++number));
available=true;
}
public synchronized void sell(){
//同步方法,实现售票的功能
if(available==true && i<=number)
System.out.println("Consumer buys ticket "+(++i));
if(i==number) available=false;
}
}
class Producer extends Thread{
Tickets t=null;
public Producer(Tickets t) {
this.t=t;
}
public void run(){
//如果存票数小于限定总量,则不断存入票
while(t.number<t.size) t.put();
}
}
class Consumer extends Thread{
Tickets t=null;
public Consumer(Tickets t){
this.t=t;
}
public void run(){
//如果售票数小于限定总量,则不断售票
while(t.i<t.size) t.sell();
}
}
线程的生命周期
线程的几种状态:
诞生状态、就绪状态(start方法已被执行)、运行状态、阻塞状态(发出请求且必须等待其返回,遇到synchronized但不能使用时)、休眠状态(sleep方法)、死亡状态(线程已完成或退出)