Java学习

类和对象

类相当于C语言的结构体,但结构体内不能有函数,而类可以。类是一个模型,而对象是类实现的具体事物。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/** TestTriangle.java
算三角形的周长与面积
*/
class Triangle
{//这是一个类
int a,b,c;
int zhouchang()
return a + b + c;
double area()
{
double p = 1.0 * (a + b + c) / 2;
return Math.sqrt(p * (p-a) * (p-b) * (p-c));
}
}

class TestTriangle
{
public static void main(String[] args)
{
Triangle t = new Triangle();//由类new出一个对象
t.a = 3;
t.b = 4;
t.c = 5;
System.out.printf("%d %f\n",t.zhouchang(),t.area());
}
}
/** 在命令行输入
javac TestTriagle.java
java TestTriangle
*/

内存分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** TestDemo.java*/
class A
{
int i;
int j;
}

class TestDemo
{
public static void main(String[] args)
{
A aa = new A();
//new A();相当于(A *)malloc(sizeof(A));
//new A();在堆中动态分配一块区域,被当作A对象
//aa本身是在栈中分配的
//堆中内存地址赋给了aa,aa指向堆中的内存,aa代表了堆中的内存
//aa.i代表aa这个静态指针所指向的动态内存中的A对象的i成员
aa.i = 10;
aa.j = 20;
System.out.printf("%d,%d\n",aa.i,aa.j);
}
}

访问控制符

将类和对象的例子修改一下,在Triangle中添加一个方法访问a,b,c,使得main函数通过这个方法间接访问a,b,c,这个方法相当于一个按钮,一个接口,一个黑匣子,不能让程序员或用户知道里面的具体实现,保证安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/** TestTriangle_2.java
算三角形的周长与面积
*/
class Triangle
{
private int a,b,c;//私有表示只能在Triangle类里面访问
void set(int i, int j, int k)
{//方法
a = i;
b = j;
c = k;
}
int zhouchang()
return a + b + c;
double area()
{
double p = 1.0 * (a + b + c) / 2;
return Math.sqrt(p * (p-a) * (p-b) * (p-c));
}
}

class TestTriangle_2
{
public static void main(String[] args)
{
Triangle t = new Triangle();
t.set(3, 4, 5);
System.out.printf("%d %f\n",t.zhouchang(),t.area());
}
}
/** 在命令行输入
javac TestTriagle_2.java
java TestTriangle_2
*/

类的访问控制符

访问控制符 意义
public 可以通过外部访问方式访问类内部的public成员
private 不可以通过外部访问方式访问类内部的private成员
protected
default(默认)

权限高低:protected < public < private

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class A
{
private int i;
public int j;
protected int m;

private void f()
{
i = 1;
j = 2;
m = 3;
k();
g();
}
protected void k()
{
i = 1;
j = 2;
m = 3;
f();
g();
}
public void g()
{
i = 1;
j = 2;
m = 3;
f();
k();
}
}
class TestAccess
{
public static void main(String[] args)
{
A aa = new A();
//aa.i = 10;//不可行,因为i是私有的
aa.j = 20;//可以,因为j是公有的
aa.m = 22;//可以,因为m是保护型的
}
}

不同访问控制符

public protected default private
同包同类
同包不同类
同包不同类继承
不同包继承
不同包无任何关系的类

包相当于文件夹,类相当于文件。

构造函数

构造函数要跟类名一样,构造函数没有返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class A
{
private int i;
private int j;

//构造函数
public A(int a, int b)
{
i = a;
j = b;
System.out.printf("有参构造函数被调用了!\n");
}
//构造函数
public A()
{
System.out.printf("无参构造函数被调用了!\n");
}
//方法
public void show()
{
System.out.printf("i = %d, j = %d\n",i ,j);
}
}

class TestConst
{
public static void main(String[] args)
{
A aa = new A(1, 2);//在定义时赋值,相当于int i = 0;
aa.show();
A ab = new A();
ab.show();//正确,默认i=0,j=0,因为i和j不在函数内部,不是局部变量,而是类的属性
//boolean类型默认为flase,用%b输出
int k;
System.out.printf("%d\n", k);//错误,k是局部变量,未初始化,在java里面局部变量在使用前需初始化,否则报错
}
}

函数的重载

同名的函数通过不同的形参做类似的事情。

函数重载要求:

  1. 函数的形参个数
  2. 函数的形参顺序
  3. 函数的形参数据类型

这三个至少有一个是不一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Testadd
{
static int add(int a, int b)
{
return a + b;
}

static int add(int a, int b, int c)
{
return a + b + c;
}

static double add(double a, double b)
{
return a + b;
}

public static void main(String args[])
{
System.out.printf("%d\n",add(1,2));
System.out.printf("%d\n",add(1,2,3));
System.out.printf("%f\n",add(1.2,2.4));
}
}

关键字

this指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class A
{
public int i;
public A(int j)
{
i = j;
}
public void show(/* A * this*/)//①this代表当前正在调用show方法的对象
{
System.out.printf("i = %d\n",/*(*this).*/i);
}
}

public class TestThis
{
public static void main(String[] args)
{
A aa1 = new A(10);
A aa2 = new A(20);
aa1.show();//aa1.show(aa1);
aa2.show();//aa2.show(aa2);
}
}

aa1和aa2在内存中分别由各自的数据成员i,但是aa1和aa2公用show()方法,show方法如何知道输出的i应该是哪个对象中的i?实际上每个非static方法中都隐含着一个this指针,指向当前正在调用该方法的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A
{
public int i = 99;
public A(int i)
{
System.out.printf("%d\n", i);//输出2
}
}
public class TestThis_2
{
public static void main(String[] args)
{
A aa = new A(2);
System.out.printf("%d\n", aa.i);//输出99
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A
{
public int i = 99;
public A(int i)
{
this.i = i;//②this代表当前时刻正在创建的对象,即this.i是属性,i是局部变量
System.out.printf("%d\n", i);//输出2
}
}
public class TestThis_2
{
public static void main(String[] args)
{
A aa = new A(2);
System.out.printf("%d\n", aa.i);//输出2
}
}

static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A 
{
public static int i = 10;//①在属性前加static,意味着所有对象都共用一个属性
public void show()
{
System.out.printf("%d\n",i);
}
}
class M
{
public static void main(String[] args)
{
A aa1 = new A();
A aa2 = new A();
aa1.i = 20;
aa2.show();//输出20,意味着aa1.i与aa2.i是同一个
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A 
{
public static int i = 10;//②在属性前加static,没有对象也仍然可以通过类名的方式访问内部的static属性
public static void f()
{//方法也可以访问
System.out.printf("2022年1月20日\n");
}
}
class M
{
public static void main(String[] args)
{
System.out.printf("%d\n",A.i);//输出10
A.f();//输出日期
A aa = new A();
aa.f();//输出日期
System.out.printf("%d\n",aa.i);//输出10
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A 
{
public static int i = 10;
private static void f()//③static可以通过类名访问,但是在非private的情况下才可以访问
{
System.out.printf("2022年1月20日\n");
}
}
class M
{
public static void main(String[] args)
{
System.out.printf("%d\n",A.i);//输出10
A.f();//错误
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A 
{
public static int i = 10;
public static void f()
{
//g();//④静态方法不能访问非静态成员
System.out.printf("2022年1月20日\n");
}
public void g()
{
f();//非静态方法可以访问静态成员
System.out.printf("GGGG\n");
}
}

用static求出由某个类造出几个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A
{
private int i;
private static int cnt = 0;
public A()
{
++cnt;
}
public A(int i)
{
this.i = i;
++cnt;
}
public static int getCnt()
{
return cnt;//返回的是A对象的个数,静态属性不能加this,所以不能写this.cnt
}
}

public class TestStatic
{
public static void main(String[] args)
{
System.out.printf("%d\n",A.getCnt());//输出0
A aa1 = new A();
System.out.printf("%d\n",A.getCnt());//输出1
A aa2 = new A(2);
System.out.printf("%d\n",A.getCnt());//输出2
}
}

某类只能造一个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A
{
public int i = 20;
private static A aa = new A();//aa是A类的属性也是A类造出的对象
private A()
{//将构造函数私有,使得外部不能new出一个对象

}
public static A getA()
{//通过这个方法只能调用A类里面构造的对象
return aa;
}
}

public class TestStatic_2
{
public static void main(String[] args)
{
//A aa1 = new A();//错误
A aa1 = A.getA();
A aa2 = A.getA();
aa1.i = 99;
System.out.printf("%d\n",aa2.i);//输出99,也就是说aa1和aa2是同一个
}
}

一个类的属性可以是个类对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A
{
public void f()
{
System.out.printf("哈哈\n");
}
}
class C
{
public int i;
public A aa = new A();//aa一定是个对象,aa也是C类的属性
public void g()
{//g()方法可以访问aa
aa.f();
}
}
class M
{
public static void main(String[] args)
{
C cc = new C();
cc.g();
}
}

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class human
{
public String name;
public int age;
}
class student
{
public String name;
public int age;
public double score;
}
class graduate
{
public String name;
public int age;
public double score;
public String tutor;
}

这样写很累赘,因为人包括学生,学生包括研究生,所以利用继承是一个很好的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class human
{
public String name = "张三";
public int age = 22;
}
class student extends human
{
public double score = 99.9;
}
class graduate extends student
{
public String tutor = "李四";
}
public class TestExtends
{
public static void main(String[] args)
{
graduate gd = new graduate();
System.out.printf("%s %f\n",gd.name, gd.score);//输出正确
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class A
{
public int i;
protected int j;
private int k;
public void g()
{}
private void s()
{}
protected void b()
{}
}
class B extends A
{
public void f()
{
i = 10;
j = 20;
//k = 30;//错误,私有属性不能被继承
g();
//s();//错误,私有方法不能被继承
b();
}
private void m()
{
i = 10;
j = 20;
//k = 30;//错误,私有属性不能被继承
g();
//s();//错误,私有方法不能被继承
b();
}
}
class M
{
public static void main(String[] args)
{
B bb = new B();
bb.i = 20;
bb.j = 30;
bb.b();
bb.g();
//bb.s();//错误
//bb.k();//错误,通过子类对象名只能访问从父类继承过来的非private成员
//私有不能被继承,私有从物理上会被继承,但是程序员不能访问,因此继承要慎重,否则会浪费内存
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A
{
public static int i = 20;
protected int j;
private int k;
}
class B extends A
{
private void g()
{

}
}
class M
{
public static void main(String[] args)
{
B.i = 99;
System.out.printf("%d\n",B.i);//输出99,说明可以A.i,也可以通过继承实现B.i
}
}

子类访问父类成员的三种方式:

  1. 在子类内部访问父类成员
  2. 通过子类对象名访问父类成员
  3. 通过子类的类名访问父类成员

    Java只支持单继承,不允许多继承。单继承就是一个类只能有一个父类。

子类可以继承父类所有成员变量和成员方法,但子类永远也无法继承父类的构造函数。在子类的构造方法中可使用语句super(参数列表)调用父类的构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class A
{
public int i;
public int j;
public A()
{

}
public A(int i, int j)
{
this.i = i;
this.j = j;
}
}
class B extends A
{
public int k;
public B()
{

}
public B(int i, int j, int k)
{//只写int k不对,因为B类包括A类的属性,所以一共三个
this.i = i;//i,j可以用this,因为B类继承了A类的属性
this.j = j;//即A类属性已经包含在B类中
//如果A类有100个属性,不可能一直这么写,要用super
this.k = k;
}
}
public class TestSuper
{
public static void main(String[] args)
{
B bb = new B(1,2,3);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class A
{
public int i;
public int j;
public A()
{

}
public A(int i, int j)
{
this.i = i;
this.j = j;
}
}
class B extends A
{
public int k;
public B()
{

}
public B(int i, int j, int k)
{//只写int k不对,因为B类包括A类的属性,所以一共三个
//A(i, j);//错误,父类构造函数无法继承
super(i, j);//正确
this.k = k;
//super(i, j);//错误,super要放在构造函数的第一个语句
}
}
public class TestSuper
{
public static void main(String[] args)
{
B bb = new B(1,2,3);
System.out.printf("%d,%d\n",bb.i,bb.j);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A
{
public int i;
}
class B
{
public int j;
public B(int i, int j)
{
//super();③如果只写this.j = j就默认调用super();
//super(i);//②错误,A类没有只有一个参数的构造函数
//④不能同时调用两个构造函数
this.j = j;
}
public void f(int i)
{
//super(i);//①错误,方法不能调用父类的构造函数
}
}
public class TestSuper_2
{
public static void main(String[] args)
{

}
}

重写父类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
public void f()
{
//f();//死递归
super.f();//调用从父类继承过来的f方法
System.out.printf("BBBB\n");
}
}
public class TestOver
{
public static void main(String[] args)
{
B bb = new B();
bb.f();//输出AAAA\nBBBB\n
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
public void f(int i)
{
System.out.printf("hhhh\n");
}
}
class B extends A
{
public void f()
{
super.f();//调用从父类继承过来的f方法
f(10);//正确,因为B类没有f(int i)所以去A类找,也可以写成super.f(10);
System.out.printf("BBBB\n");
}
}
public class TestOver
{
public static void main(String[] args)
{
B bb = new B();
bb.f();//输出AAAA\nhhhh\nBBBB\n
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
private void f()
{//重写只能权限相等或更高
System.out.printf("BBBB\n");
}
}
public class TestOver
{
public static void main(String[] args)
{
B bb = new B();
bb.f();
}
}

方法重写指在子类中重新定义父类中已有的方法。重写方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class human
{
private String name;
private int age;
public human()
{

}
public human(String name, int age)
{
this.name = name;
this.age = age;
}
public void setName(String name)
{
this.name = name;
}
public void setAge(int age)
{
this.age = age;
}
public String getInfor()
{
String strInf = name + ": " +age;
return strInf;
}
}
class student extends human
{
public String school;
public student()
{}
public student(String name, int age, String school)
{
super(name, age);
this.school = school;
}
public void setschool(String school)
{
this.school = school;
}
public String getInfor()
{
//String strInf = name + ": " + age + ": " + school;//因为name和age都是私有的,所以在student类不能这么写
String strInf = super.getInfor() + ": " + school;
return strInf;
}
}
public class TestStudent
{
public static void main(String[] args)
{
student st1 = new student("张三", 22, "星星大学");
System.out.printf("%s\n", st1.getInfor());
}
}

多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A
{
protected void f()
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
public void f()
{//重写方法的权限不能低于被重写方法的访问权限,是因为多态的存在
System.out.printf("BBBB\n");
}
}
public class TestPoly_2
{
public static void main(String[] args)
{
A aa = new A();
B bb = new B();
aa.f();//输出AAAA\n
aa = bb;//把bb当作aa来看待(把特殊当作一般看待)
//bb = aa;//错误
aa.f();//输出BBBB\n
//同一条代码做不同的事情,这就是多态
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
public void f()
{
System.out.printf("BBBB\n");
}
}
class C extends B
{
public void f()
{
System.out.printf("CCCC\n");
}
}
class D extends C
{
public void f()
{
System.out.printf("DDDD\n");
}
}
public class TestPoly_3
{
public static void g(A aa)
{
aa.f();
}
public static void main(String[] args)
{
A aa = new A();
B bb = new B();
C cc = new C();
D dd = new D();
g(aa);
g(bb);
g(cc);
g(dd);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
public void f()
{
System.out.printf("BBBB\n");
}
public void g()
{
System.out.printf("GGGG\n");
}
}
public class TestPoly_4
{
public static void main(String[] args)
{
A aa = new A();
B bb = new B();
//bb = aa;//如何实现?只有在父类引用本身指向的就是一个子类对象时,才可以把父类引用强制转化为子类引用
aa = bb;
bb = (B)aa;
bb.g();//输出GGGG\n
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
public void f()
{
System.out.printf("BBBB\n");
}
public void g()
{
System.out.printf("GGGG\n");
}
}
public class TestPoly_4
{
public static void main(String[] args)
{
A aa = new A();
B bb = new B();
aa = bb;
//aa.g();//错误,只能调用A类和B类的共有部分
}
}

子类对象可以直接赋给父类引用,但父类对象在任何情况下都不可以直接赋给子类使用。通过父类引用只能访问子类对象从父类继承过来的成员,不能访问子类对象所特有的成员。

抽象类

抽象类通常用来作为一个类族的最顶层的父类,用最底层的类表示现实中的具体事物,用最顶层的类表示该类族所有事物的共性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class A
{//有抽象方法一定是抽象类
abstract public void f();//没有方法体的方法叫做抽象方法,抽象方法要求末尾必须得加分号,前面必须得加abstract
}
abstract class B
{//抽象类不一定要有抽象方法
public void g()
{}
}
public class TestAbstract
{
public static void main(String[] args)
{}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
class A
{

}
abstract class B extends A
{//抽象类可以继承非抽象类

}
public class TestAbsPoly
{
public static void main(String[] args)
{}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class A
{
abstract public void f();
}
class B extends A
{//B类是非抽象类,所以要实现f方法,即要具体写f方法
public void f()
{
System.out.printf("BBBB\n");
}
}
abstract class C extends A
{//C类是抽象类,所以可以不具体实现f方法

}
public class TestAbsPoly
{
public static void main(String[] args)
{
//A aa = new A();//错误,抽象类不能创建对象
B bb = new B();
bb.f();//输出BBBB\n
A aa;//可以定义一个抽象类的引用,但不可以定义一个抽象类的对象
aa = bb;//OK
aa.f();//输出BBBB\n,利用多态调用子类的具体实现方法
}
}

Final

Final可以修饰

  1. 整个类
  2. 类中的若干个属性
  3. 类中的若干个方法:表示该方法可以被子类继承,但不可以被子类重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//final class A
class A
{//如果A类是最终的,则不能被继承

}
class B extends A
{

}
public class TestFinal
{
public static void main(String[] args)
{

}
}
1
2
3
4
5
6
7
8
9
10
11
12
class A
{
//final public int i;//常变量,变量必须赋一个固定值,以后不能再改变,相当于C语言的const
final public int i = 10;
}
public class TestFinal
{
public static void main(String[] args)
{

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A
{
final public int i;
public A()
{//②创建对象一定调用构造函数,所以在构造函数里写正确
i = 22;
}
public void f()
{//①f方法可能不被调用,i也就没有赋值
//i = 22;//错误
}
}
public class TestFinal
{
public static void main(String[] args)
{

}
}

接口

接口就是抽象方法和常量值的集合。从本质上讲,接口是一种特殊的抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface It
{
public void f();
//public void f()
//{}//错误,接口不能有实现方法
}
abstract class B
{
public void f()
{//抽象类可以有实现方法

}
}
public class A
{
public static void main(String[] args)
{

}
}

接口的注意事项

  1. 接口中定义的属性必须是public static final的,而接口中定义的方法则必须是public abstract的,因此这些修饰符可以部分或全部省略。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    interface It
    {
    public static final int i = 20;
    //可以写成int i = 20;
    public abstract void f();
    //可以写成void f();
    }
    public class A
    {
    public static void main(String[] args)
    {

    }
    }
  2. 接口中定义的属性的值在实现类中不能被更改。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    interface It
    {
    int i = 10;//不能为int i;
    }
    class A implements It
    {
    public A(int j)
    {
    //this.i = j;//错误,接口中的属性值不能被修改
    }
    }
    public class B
    {
    public static void main(String[] args)
    {}
    }
  3. 一个类只能实现(implements)某个接口,不能继承(extends)某个接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface It
    {
    public static final int i = 20;
    public abstract void f();
    }
    abstract class A implements It
    {//如果接口中有抽象方法且A类没有实现接口的全部方法,A必须为抽象类

    }
    class C implements It
    {//如果没有抽象方法或C类实现接口的全部方法,则可以是非抽象类
    public void f()
    {
    System.out.printf("i = %d\n", i);
    }
    }
    public class B
    {
    public static void main(String[] args)
    {
    C cc = new C();
    cc.f();//输出i = 20
    }
    }
  4. 接口可以继承接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    interface It1
    {

    }
    interface It2
    {

    }
    interface It3 extends It1, It2
    {

    }
    public class C
    {
    public static void main(String[] args)
    {

    }
    }
  5. 接口允许多继承。(看4)

  6. 如果一个类只实现了一个接口的部分方法,则该类必须得声明为抽象类。(看3)

  7. 一个类可以在继承一个父类的同时实现一个或多个接口,但extends关键字必须得在implements之前。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class A
    {}
    interface It1
    {}
    interface It2
    {}
    interface It3 extends It1, It2
    {}
    interface It4
    {
    int i = 20;
    }
    class T extends A implements It4, It3
    {}
    public class TestIter
    {
    public static void main(String[] args)
    {}
    }
  8. 不可以new接口对象,但可以定义一个接口引用类型的变量并将其指向实现接口的对象,达到多态的目的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface It
    {
    void f();
    }
    class A implements It
    {
    public void f()
    {
    System.out.printf("AAAA\n");
    }
    public void g()
    {}
    }
    class D
    {
    publc static void main(String[] args)
    {
    It it;
    it = new A();
    it.f();//输出AAAA\n
    //it.g();//错误,不能调用子类特有成员
    //It it2 = new It();//不可以创建接口对象
    }
    }

接口的作用

  1. 通过接口可以实现不相关类的相同行为
  2. 接口提供了不同对象进行协作的平台
  3. 接口可以实现多继承,从一个程度上弥补了类只能单继承的缺陷
  4. 接口是我们了解一个类功能的重要途径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/** package.java*/
package zhangsan.lisi;
//package语句必须是第一条语句,注释不算
//把下面的类放在了zhangsan文件夹的子文件夹lisi里面
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
class M
{
public static void main(String[] args)
{
A aa = new A();
aa.f();
//上面等同于下面
new A().f();
}
}
/**
javac package.java
如果在命令窗口运行,必须建立一个zhangsan文件夹,在里面建一个lisi子文件夹,把编译好的A.class和M.class放进lisi文件夹里才能正常运行
java zhangsan.lisi.M
编译时建议用javac -d . package.java,可以自动建立文件夹,运行同上
-d表示自动生成包层,.表示这个包层是在当前目录下建立的
*/

同包不同类的相互访问

1
2
3
4
5
6
7
8
9
//A.java文件
//package lang;//默认有lang包
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//B.java文件
class B
{
public static void main(String[] args)
{
A aa = new A();
aa.f();//输出AAAA\n
}
}
/**
javac A.java B.java
java B
*/

因为类A和类B默认是在同一个无名的包中,所以彼此可以相互访问。只要是非私有成员都可以被同包的另一个类访问。

不同包类的相互访问

1
2
3
4
5
6
7
8
9
//A.java
package zhangsan.lisi;
public class A
{//只有公有的类公有的成员才可以被不同包访问
public void f()
{
System.out.printf("AAAA\n");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//B.java
package v5le0n9;
//import zhangsan.lisi.*;//导入包层太多,可以选择直接导入类
import zhangsan.lisi.A;
public class B
{
public static void main(String[] args)
{
//A aa = new A();//错误,不在同一个包中
//zhangsan.lisi.A aa = new zhangsan.lisi.A();//A类如果是公有的则正确,但一般不这么用,更多用导包,此时,A aa = new A();可用
A aa = new A();
aa.f();//如果f方法是公有的则正确,输出AAAA\n
}
}
/**
javac -d . A.java B.java或javac -d . B.java A.java
java v5le0n9.B
*/

不同包的类继承

1
2
3
4
5
6
7
8
9
10
11
12
13
//A.java
package zhangsan.lisi;
public class A
{
public void g()
{
System.out.printf("GGGG\n");
}
protected void b()
{
System.out.printf("BBBB\n");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//B.java
package v5le0n9;
import zhangsan.lisi.*;
class B extends A
{
public void f()
{//在子类内部可以访问从另一个包继承过来的父类的public成员和protected成员
g();
b();
}
}
class M
{
public static void main(String[] args)
{
B bb = new B();
bb.f();//输出GGGG\nBBBB\n
bb.g();//输出GGGG\n
//bb.b();//错误
//在子类外部只能访问从另一个包继承过来的父类的public成员而不能访问protected成员
}
}

普通jar包的生成

假设需要将v5le0b9zhangsan打包,就新建一个文件夹将v5le0n9zhangsan放进去,确保文件夹只有需要打包的内容。

在命令窗口进入新文件夹目录,输入jar -cvf T.jar *,生成叫作T.jar的包。

如何使用jar包中的类

1
2
3
4
5
6
7
8
9
import zhangsan.lisi.A;//导入即可
public class Test
{
public static void main(String[] args)
{
A aa = new A();
aa.g();
}
}

如果jar包和新编辑的java文件不在同一个目录下,则在命令窗口编译前,先设置classpath,再编译运行。

1
set classpath=xxxx\xxx\T.jar;//jar包的绝对路径

异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package v5le0n9;
class A
{
int divide(int a, int b)
{
int m;
m = a / b;
return m;
}
}
public class TestExcep
{
public static void main(String[] args)
{
A aa = new A();
aa.divide(6, 2);//正确
//aa.divide(6, 0);//异常
System.out.printf("看得见我吗?");//上一句异常直接终止程序,所以这一条不输出
}
}
/**
Exception in thread "main" java.lang.ArithmeticException: / by zero
at v5le0n9.A.divide(TestConst.java:7)
at v5le0n9.TestConst.main(TestConst.java:17)
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package v5le0n9;
class A
{
int divide(int a, int b)
{
int m;
m = a / b;
return m;
}
}
public class TestExcep_2
{
public static void main(String[] args)
{
A aa = new A();
try
{
aa.divide(6, 0);
}
catch(ArithmeticException e)//算数异常,e用来接收第18行抛出的异常对象,如果不知道try语句抛出什么异常,直接写Exception e也可
{//如果try里面的语句出错,则输出
System.out.printf("除数不能为零\n");
}
System.out.printf("看得见");//上面try和catch捕捉到异常,然后继续往下执行,正常输出
}
}
/**
除数不能为零
看得见
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestExcep_3
{
public static void main(String[] args)
{
int m;
try
{
m = 2;
System.out.printf("m = %d\n", m);//正确
}
catch(Exception e)
{

}
//System.out.printf("m = %d\n", m);//错误,可能未初始化变量
//放在try里面的语句很有可能执行不成功,编译器做最坏的打算,即没有赋值
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package v5le0n9;
class A
{
int divide(int a, int b)
{
//int m;
int m = 0;
try
{
m = a / b;
}
catch(ArithmeticException e)
{
System.out.printf("除数不能为零\n");
}
return m;//错误,可能未初始化变量,所以要给m赋个初始值
}
}
public class TestExcep_4
{
public static void main(String[] args)
{
new A().divide(6, 0);
}
}

Error和Exception的区别:Error表示语法错误,而Exception表示程序运行时出现的错误。

为什么需要异常

根据上面那个例子,用if…else…语句也可实现,所以为什么需要异常?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.*;//导入util包才能用Scanner
public class TestExcep_5
{
public static void main(String[] args)
{
int i;
Scanner sc = new Scanner(System.in);//System.in表示键盘输入
i = sc.nextInt();//黑窗口输入一个Int数据类型赋值给i
System.out.printf("i = %d\n", i);//输出
}
}
/**
86
i = 86
*/

假如输入的不是整型,会报异常。此时,用if…else…语句显得很困难。

1
2
3
4
5
6
7
8
9
/**
qwe
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Unknown Source)
at java.util.Scanner.next(Unknown Source)
at java.util.Scanner.nextInt(Unknown Source)
at java.util.Scanner.nextInt(Unknown Source)
at v5le0n9.TestConst.main(TestConst.java:9)
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.*;//导入util包才能用Scanner
public class TestExcep_6
{
public static void main(String[] args)
{
int i;
Scanner sc = new Scanner(System.in);//System.in表示键盘输入
try
{
i = sc.nextInt();//终端输入一个Int数据类型赋值给i
System.out.printf("i = %d\n", i);//输出
}
catch(InputMismatchException e)
{//从上面的报错可以看到是输入匹配数据类型异常
System.out.printf("输入数据不合法\n");
}
}
}
/**
qwe
输入数据不合法
*/

异常的处理机制

  1. 当Java程序运行时出现问题时,系统会自动检测到该错误,并立即生成一个与该错误对应的异常对象。
  2. 然后把该异常对象提交给Java虚拟机。
  3. Java虚拟机会自动寻找相应的处理代码来处理这个异常,如果没有找到,则由Java虚拟机做一些简单的处理后,程序被强行终止。
  4. 程序员可以自己编写代码来捕捉可能出现的异常,并编写代码来处理相应的异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package v5le0n9;
class A
{
int divide(int a, int b)
{
return a / b;
}
public void f()
{
g();
}
public void g()
{
divide(6, 0);
}
}
public class TestExcep_7
{
public static void main(String[] args)
{
new A().f();
}
}
/**
Exception in thread "main" java.lang.ArithmeticException: / by zero
at v5le0n9.A.divide(TestConst.java:6)
at v5le0n9.A.g(TestConst.java:14)
at v5le0n9.A.f(TestConst.java:10)
at v5le0n9.TestConst.main(TestConst.java:21)
*/
/**
异常解释:
在main方法有java.lang包下的算术错误:除零错误
第21行导致第10行错误,第10行导致第14行错误,第14行导致第6行错误
*/

在调试时,可以用_printStackTrace观察哪里出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package v5le0n9;
class A
{
int divide(int a, int b)
{
return a / b;
}
public void f()
{
g();
}
public void g()
{
divide(6, 0);
}
}
public class TestExcep_8
{
public static void main(String[] args)
{
try
{
new A().f();
}
catch(Exception e)
{
e.printStackTrace();//捕获异常函数
}
}
}
/**
java.lang.ArithmeticException: / by zero
at v5le0n9.A.divide(TestConst.java:6)
at v5le0n9.A.g(TestConst.java:14)
at v5le0n9.A.f(TestConst.java:10)
at v5le0n9.TestConst.main(TestConst.java:23)
*/
/**
此程序与Excep_7的输出几乎一致,只是第一行前面没有Exception显示
如果想验证语句是否有错,可以用try...catch()语句,用_printStackTrace找出具体的错误位置
*/

常见异常

空指针异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package v5le0n9;
class Person
{
public int age;
}
public class TestNullPointerException
{
public static void main(String[] args)
{
Person p = null;
System.out.println(p.age);
}
}
/**
Exception in thread "main" java.lang.NullPointerException
at v5le0n9.TestConst.main(TestConst.java:11)
*/

下标越界异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package v5le0n9;
public class TestIndexOutOf
{
public static void main(String[] args)
{
String friends[] = {"Lisa", "Bily", "Kessy"};
for(int i=0; i<5; i++)
{
System.out.println(friends[i]);
}
System.out.println("\nThis is the end.\n");
}
}
/**
Lisa
Bily
Kessy
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at v5le0n9.TestConst.main(TestConst.java:9)
*/

算数异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package v5le0n9;
class A
{
int divide(int a, int b)
{
int m;
m = a / b;
return m;
}
}
public class TestExcep
{
public static void main(String[] args)
{
A aa = new A();
aa.divide(6, 2);//正确
//aa.divide(6, 0);//异常
System.out.printf("看得见我吗?");//上一句异常直接终止程序,所以这一条不输出
}
}
/**
Exception in thread "main" java.lang.ArithmeticException: / by zero
at v5le0n9.A.divide(TestConst.java:7)
at v5le0n9.TestConst.main(TestConst.java:17)
*/

异常的分类

  1. Error是系统错误,程序员无法处理这些异常
  2. Exception是程序员可以捕获并处理的异常
  3. RuntimeException的子类异常是可以处理也可以不处理的异常
  4. 凡是继承自Exception但又不是RuntimeException子类的异常我们都必须捕捉并进行处理

有些异常在编译时就报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;
class A
{
public void f()
{//在编译时就报错,必须处理
throw new IOException();//throw 抛出异常,这个异常是非RuntimeException子类
}
public void g()
{//在运行时报错
throw new ArithmeticException();//RuntimeException子类
}
}
public class Exception
{
public static void main(String[] args)
{
A aa = new A();
}
}

处理异常的两种方式

try…catch…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.*;
class A
{
public void f()
{
try
{
throw new IOException();
}
catch(IOException e)
{
//...
}
}
}

throws

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;
class A
{
public void f() throws IOException
{//throws IOException表示调用f方法时f方法可能会抛出IOException异常,建议调用f方法时最好对f方法可能抛出的IOException异常进行捕捉。如果抛出异常,本方法不处理,交给调用者处理。如果抛出的异常是RuntimeException子类,则调用者可以不处理。
//throw new IOException();//也可以不抛出异常
}
}
public class Exception_2
{
public static void main(String[] args)
{
A aa = new A();
try
{
aa.f();
}
catch(IOExeption e)
{
//...
}
}
}

异常处理步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try
{
//可能出现异常的代码块
//语句1;
//语句2;
}
catch(ExceptionName1 e)
{
//当产生ExceptionName1异常时的处理措施
}
catch(ExceptionName2 e)
{
//当产生ExceptionName2异常时的处理措施
}
finally
{
//无论是否捕捉到异常都必须处理的代码
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A
{
int divide(int a, int b)
{
int m;
m = a / b;
return m;
}
}
public class TestExcep_9
{
try
{
new A().divide(6, 0);
}
catch(ArithmeticException e)
{
System.out.printf("huuu\n");//输出
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.printf("yooooo\n");//不输出
}
finally
{
System.out.printf("hhhh\n");//输出
}
}

Finally的作用

无论try所指定的程序块中是否抛出异常,也无论catch语句的异常类型是否与所抛出的异常类型一致finally中的代码都会执行。finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序的其它部分之前,能够对程序的装药作统一的管理。通常在finally语句中可以进行资源的清除工作,如关闭打开的文件、删除临时文件等。

自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package v5le0n9;
class DivisorIsZeroException extends Exception
{//自定义异常必须是Throwable的一个子类,更确切是Exception的子类
public DivisorIsZeroException(String name)
{
super(name);
}
}
class A
{
public int divide(int a, int b) throws DivisorIsZeroException
{
int m = 0;
if(0 == b)
throw new DivisorIsZeroException("除数不能为零");
else
m = a / b;
return m;
}
}
public class TestExcep_10
{
public static void main(String[] args)
{
A aa = new A();
try
{
aa.divide(6, 0);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
/**
v5le0n9.DivisorIsZeroException: 除数不能为零
at v5le0n9.A.divide(TestConst.java:15)
at v5le0n9.TestConst.main(TestConst.java:28)
*/

异常的范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class A extends Exception
{}
class B extends Exception
{}
class C extends Exception
{}
class M
{
void f() throws A,B
{}
}
class N extends M
{
//void f() throws A,B,C//范围过大
//void f() throws A//可以
void f() throws A,B
{//可以throws A或B,也可以throws A,B,也可以不throws

}
}
class Test
{
public void k(M mm)
{
try
{
mm.f();
}
catch(A aa)
{}
catch(B bb)
{}
}
}
class TestExtendExce
{
public static void main(String[] args)
{
M m = new M();
N n = new N();
}
}

注意事项

  • 所有的catch只能有一个被执行

  • 有可能所有的catch都没被执行

  • 先catch子类异常再catch父类异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class A extends Exception
    {}
    class B extends A
    {}
    class C extends B
    {}
    class M
    {
    public void compare(int i, int j) throws A,B
    {
    if(i > j)
    throw new A();
    else
    throw new B();
    }
    }
    public class void main(String[] args)
    {
    M mm = new M();
    try
    {
    mm.compare(-4, 1);
    }
    catch(B bb)
    {//因为B是A的子类,先catch子类再catch父类
    System.out.println("左边不能小于右边");
    }
    catch(A aa)
    {
    System.out.println("左边不能大于右边");
    }
    }
  • catch与catch之间不能有其它代码

  • 重写方法抛出异常的范围不能大于被重写方法排除的异常范围

toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A// extends Object
{//A类默认继承Object类,Object类有toString方法
public String toString()
{
return "hhhh\n";
}
}
public class TestObject
{
public static void main(String[] args)
{
A aa = new A();
System.out.printf("%s\n", aa.toString());//输出hhhh\n,如果不重写,返回“类名@该对象在堆中地址的哈希码”
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package v5le0n9;
class Point_
{
public int x, y;
public Point_(int x, int y)
{
this.x = x;
this.y = y;
}
}
public class TestPoint
{
public static void main(String[] args)
{
Point_ p = new Point_(1, 2);
System.out.printf("%s\n", p);//输出v5le0n9.Point_@28d93b30
//相当于父类的_toString方法,返回“类名@该对象在堆中地址的哈希码”
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package v5le0n9;
class Point_
{
public int x, y;
public Point_(int x, int y)
{
this.x = x;
this.y = y;
}
public String toString()
{
return "[" + x + "," + y + "]";
}
}
public class TestPoint
{
public static void main(String[] args)
{
Point_ p = new Point_(1, 2);
System.out.printf("%s\n", p);//输出[1,2]\n
System.out.println(p);//输出[1,2]\n
}
}

equals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A
{
public int i;
public A(int i)
{
this.i = i;
}
}
public class TestStringEquals
{
public static void main(String[] args)
{
A aa1 = new A(2);
A aa2 = new A(2);
System.out.println(aa1);//A@15db9742,相当于System.out.println(aa1.toString());
System.out.println(aa2);//A@6d06d69c,aa1和aa2指向不同的堆地址
//equals指示其他某个对象是否与此对象“相等”
aa1.equals(aa2);//编译运行无错误
System.out.println(aa1.equals(aa2));//输出flase,表明equals是用来判断对象地址是否一样,而不是判断地址存的数值是否一样
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A
{
public int i;
public A(int i)
{
this.i = i;
}
public boolean equals(Object obj)
{
//if(this.i == obj.i)//错误,obj是指针,局部变量,形参,因为父类引用不能调用子类特有成员i(利用多态知识)
A aa = (A)obj;//形参obj已经指向子类了,所以只要强制转换即可,相当于B bb = (B)aa;
if(this.i == aa.i)
return true;
else
return false;
}
}
public class TestStringEquals_2
{
public static void main(String[] args)
{
A aa1 = new A(2);
A aa2 = new A(2);
System.out.println(aa1.equals(aa2));//如何让这个返回true,需要重写equals方法,地址存的数值一样则返回true
}
}

String类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TestString_1
{
public static void main(String[] args)
{//_toString已经重写了equals方法
String str1 = new String("China");
String str2 = new String("China");

System.out.println(str1.equals(str2));//true
//equals表示此对象的值与形参的值是否相等

if(str1 == str2)
//==表示对比两个对象的地址是否相等
System.out.println("str1 == str2");
else
System.out.println("str1 != str2");//输出

String str3 = "Chinese";
String str4 = "Chinese";
//str3与str4都指向了数据区块的同一块内存“Chinese”
if(str3 == str4)
System.out.println("str3 == str4");//输出
else
System.out.println("str3 != str4");
}
}

String类常用方法

String类方法 用途
public char charAt(int index) 返回字符串中第index个字符
public int length() 返回字符串的长度
public int indexOf(String str) 返回字符串中出现str的第一个位置
public int indexOf(String str, int fromIndex) 返回字符串中从fromIndex开始出现str的第一个位置
public boolean equalsIgnoreCase(String another) 比较字符串与another是否一样(忽略大小写)
public String replace(char oldChar, char newChar) 在字符串中用newChar字符替换oldChar字符
public boolean startsWith(String prefix) 判断字符串是否以prefix字符串开头
public booleab endsWith(String suffix) 判断字符串是否以suffix字符串结尾
public String toUpperCase() 返回一个字符串为该字符串的大写形式
public String toLowerCase() 返回一个字符串为该字符串的小写形式
public String substring(int beginIndex) 返回该字符串从beginIndex开始到结尾的子字符串
public String substring(int beginIndex, int endIndex) 返回该字符串从beginIndex开始到endIndex结尾的字符串
public static String valueOf(…) 将基本类型数据转换为字符串
public String[] split(String regex) 将一个字符串按照指定的分隔符分分隔,返回分隔后的字符串数组

常用方法举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestString_2
{
public static void main(String[] args)
{
//整型转换为字符串
int i = 123;
//String str = i;//类型不一致
String str = String.valueOf(i);
System.out.printf("str = %s\n", str);//str = 123

//字符串转换为整型
str = "456";
try
{
i = Integer.parseInt(str);//这个会抛NumberFormatException异常,比如“45a”
System.out.printf("i = %d\n", i);
}
catch(NumberFormatException e)
{
System.out.println("数字格式化异常!");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Test
{
public static void main(String[] args)
{
String s1 = "v5le0n9", s2 = "V5LE0N9";
System.out.println(s1.charAt(1));//5
System.out.println(s2.length());//7
System.out.println(s1.indexOf("0n9"));//4
System.out.println(s1.indexOf("0N9"));//-1
System.out.println(s1.equals(s2));//false
System.out.println(s1.equalsIgnoreCase(s2));//true
System.out.println(s1.toUpperCase());//V5LE0N9
System.out.println(s2.toLowerCase());//v5le0n9
System.out.println(s2.substring(3));//EN09
System.out.println(s2.substring(3,5));//E0

String s = "世界如此美好!";
String sr = s.replace("世界", "生活");
System.out.println(sr);//生活如此美好!
System.out.println(s.startsWith("世界"));//true
System.out.println(s.endsWith("美好"));//false

String st = " hello world ";
System.out.println(st.trim());//hello world

int j = 1234567;
String sNumber = String.valueOf(j);
System.out.println("j是"+sNumber.length()+"位数");

String str = "Mary,F,1976";
String[] sPlit = str.split(",");
for(int i=0; i<sPlit.length; i++)
{//数组的length不用()
System.out.println(sPlit[i]);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class TestString_3
{
public static void main(String[] args)
{
String str = "abAM,!123";
int cntU = 0;//大写字母个数
int cntL = 0;//小写字母个数
int cntOther = 0;//其它
int i;

//方法一
for (i=0; i<str.length(); i++)
{
char ch = str.charAt(i);
if(ch>='a' && ch<='z')
cntL++;
else if(ch>='A' && ch<='Z')
cntU++;
else
cntOther++;
}

//方法二
for(i=0; i<str.length(); i++)
{
char ch = str.charAt(i);
if(Character.isUpperCase(ch))
cntU++;
else if(Character.isLowerCase(ch))
cntL++;
else
cntOther++;
}

//方法三
String s1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
String s2 = "abcdefghijklmnopqrstuvwxyz";
for(i=0; i<str,length(); i++)
{
char ch = str.charAt(i);
if(-1 != s1.indexOf(ch))//如果不是-1,说明str中有这个字符
cntU++;
else if(-1 != s2.indexOf(ch))
cntL++;
else
cntOther++;
}

System.out.printf("大写字母个数为%d\n",cntU);
System.out.printf("小写字母个数为%d\n",cntL);
System.out.printf("其它字符个数为%d\n",cntOther);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestString_4
{
public static void main(String[] args)
{
String str1 = "abcabcbacbac";
String str2 = "abc";
int index = -1;
int cnt = 0;
index = str1.indexOf(str2);
while(-1 != index)
{
cnt++;
index = str1.indexOf(str2, index+str2.length());
}
System.out.printf("%d\n",cnt);//2
}
}

printf和println的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Dian
{
public int x, y;
public Dian(int x,int y)
{
this.x = x;
this.y = y;
}
public String toString()
{
return "[" + x + "," + y + "]";
}
}
public class TestPrint
{
public static void main(String[] args)
{
Dian d = new Dian(3, 2);
System.out.printf("%s\n", d);
System.out.println(d);

int i, j, k;
i = 1;
j = 2;
k = 3;
System.out.printf("%d的值 + %d的值 是 %d\n", i, j, k);
System.out.println(i + "的值" + j + "的值 是 " + k);

int m = 47;
System.out.println(m);
System.out.printf("%d\n", m);
System.out.printf("十进制数字%d用十六进制表示是:%#X\n", m, m);
System.out.println("十进制数字" + m + "用十六进制表示是:0X" + Integer.toHexString(m).toUpperCase());
//Integer.toHexString()返回的是字符串,将字符串改成大写就直接在后面加toUpperCase()

System.out.printf("%b\n", "abc".equals("zhangsan"));
System.out.printf("%d\n", "abc".length());
}
}

StringBuffer

String类对象一旦创建就不可更改,因为String类用final修饰了。如果经常对字符串内容进行修改,则使用StringBuffer。

StringBuffer类的构造函数

构造函数 含义
public StringBuffer() 创建一个空的没有任何字符的StringBuffer对象
public StringBuffer(int capacity) 创建一个不带字符,但具有指定初始容量的字符串缓冲区
public StringBuffer(String str) 创建一个StringBuffer对象,包含与str对象相同的字符序列

StringBuffer常用方法

常用方法 含义
public StringBuffer append(…) 为该StringBuffer对象添加字符序列,返回添加后的该StringBuffer对象引用
public StringBuffer insert(…) 为该StringBuffer对象在指定位置插入字符序列,返回修改后的该StringBuffer对象引用
public StringBuffer delete(int start, int end) 删除从start开始到end-1为止的一段字符序列,返回修改后的该StringBuffer对象引用
public StringBuffer reverse() 将字符序列逆序,返回修改后的该StringBuffer对象引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestStringBuffer
{
public static void main(String[] args)
{
StringBuffer sb = new StringBuffer();
sb.append("abc");
sb.append("123");
System.out.println("sb = " + sb);//sb = abc123
sb.insert(3,"--");
System.out.println("sb = " + sb);//sb = abc--123
sb.delete(2, 6);//把下标[2,6)的字符删除
System.out.println("sb = " + sb);//sb = ab23
sb.reverse();
System.out.println("sb = " + sb);//sb = 32ba
String str = sb.toString();
System.out.println("str = " + str);//str = 32ba
}
}
1
2
3
4
5
6
7
8
9
public class TestStringBuffer_2
{
public static void main(String[] args)
{
StringBuffer sb = new StringBuffer("zhangsan");
//StringBuffer sb2 = "张三";//错误,前面是StringBuffer类,而张三是String类,类型不一致
System.out.println(sb);//zhangsan
}
}

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class TestArray
{
public static void mian(String[] args)
{//定义数组的三种方法
//方式一
int[] arr1;
arr1 = new int[3];
arr1[0] = 0;
arr1[1] = 1;
arr1[2] = 2;
showArr(arr1);

//方式二
int[] arr2 = new int[]{0,1,2};
showArr(arr2);
//System.out.println(arr2);//错误,一维数组的内容不能通过println直接输出

//错误写法
//int[3] arr3 = new int[]{0,1,2};
//int[] arr4 = new int[3]{0,1,2};
//int[3] arr5 = new int[3]{0,1,2};

//方式三
int[] arr6 = {0,1,2};
showArr(arr6);
arr6 = new int[]{5,4,3,2,1};
showArr(arr6);
}
public static void showArr(int[] arr)
{
for(int i=0; i<arr.length; i++)
System.out.println(arr[i]);
}
}

创建并使用基本类型数组

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestArray_2
{
public static void main(String[] args)
{//数组直接存数据
int[] s;//在栈中生成一个局部变量s
s= new int[10];//在堆中生成一个数组对象,长度为10,值默认为0
for(int i=0; i<10; i++)
{
s[i] = 2 * i + 1;//给堆中的每个元素赋值
System.out.println(s[i]);
}
}
}

创建并使用引用类型数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyDate
{
private int day;
private int month;
private int year;
public MyDate(int d, int m, int y)
{
day = d;
month = m;
year = y;
}
public void display()
{
System.out.println(day + "-" + month + "-" + year);
}
}
public class TestArray_3
{
public static void main(String[] args)
{//数组存指针变量
MyDate[] m;//在栈中生成一个局部变量m
m = new MyDate[10];//在堆中生成一个数组对象,长度为10,值默认为NULL
for(int i=0; i<10; i++)
{
m[i] = new MyDate(i+1, i+1, 1990+i);//第i个指针对象指向对应的数据(也在堆中)
m[i].display();
}
}
}

数组的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TestArrayCopy
{
public static void main(String[] args)
{
int[] a = {1,2,3,4,5};
int[] b = {-1,-2,-3,-4,-5};
System.arraycopy(a,0,b,1,2);//把a数组从下标为0的元素开始,长度为2的一串数,从b数组下标为1开始覆盖
System.out.println("a = ");
for(int i=0; i<a.length; i++)
System.out.println(a[i]);//a = 12345

System.out.println("b = ");
for(i=0; i<b.length; i++)
System.out.println(b[i]);//b = -112-4-5
}
}

数组的排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.*;
public class TestArraySort
{
public static void main(String[] args)
{
int[] data = {1,3,5,7,2,4,6,8,10,9};
System.out.println("排序前数组data中的内容是:");
showArray(data);

Arrays.sort(data);

System.out.println("排序后数组data中的内容是:");
showArray(data);
}
public static void showArray(int[] data)
{
for(int e : data)//把data中的每个元素取出来赋给e
System.out.printf("%d\t", e);
System.out.println("");
}
}

线程

线程是一个程序里的不同执行路径。

创建线程的两种方式

创建线程的第一种方式:

  1. 创建一个继承Thread的类(假定类名为A),并重写Thread的run方法
  2. 构造一个A类对象(假定对象名为aa)
  3. 调用aa的start方法(start方法是从Thread继承过来的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class A extends Thread
{
public void run()
{//重写父类的run方法
while(true)
System.out.println("AAAA");
}
}
public class TestThread
{
public static void main(String[] args)
{
A aa = new A();
//aa.run();//陷入死循环,一直输出A(单线程)
aa.start();//A,B交替执行(多线程)
//start()的功能:创建一个新的线程,执行run方法里的代码。此时,run方法里的代码与下面代码交替执行
//一个Thread对象只能调用一次start方法
while(true)
{
System.out.println("BBBB");
}
}
}

创建线程的第二种方式:

  1. 定义一个实现了Runnable接口的类(假定为A)
  2. 创建A类对象aa
  3. 利用aa构造一个Thread对象t
  4. 调用t中的start方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A implements Runnable
{
public void run()
{//重写父类的run方法
while(true)
System.out.println("AAAA");
}
}
public class TestThread_2
{
public static void main(String[] args)
{
A aa = new A();
Thread t = new Thread(aa);
t.start();
while(true)
{
System.out.println("BBBB");
}
}
}

线程的常用方法

常用方法 含义
public final void setName(String name) 设置当前线程的名字
public static Thread currentThread() 返回对当前正在执行的线程对象的引用
public final String getName() 返回当前线程的名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class A extends Thread
{
public void run()
{
System.out.printf("%s在执行\n", Thread.currentThread().getName());
}
}
public class TestThread_3
{
public static void main(String[] args)
{
A aa1 = new A();
aa1.setName("v5le0n9");
aa1.start();

A aa2 = new A();
aa2.setName("l30n9ry0n");
aa2.start();

A aa3 = new A();
aa3.setName("凉凉");
aa3.start();

System.out.printf("%s在执行\n", Thread.currentThread().getName());
}
}
/**
l30n9ry0n在执行
凉凉在执行
main在执行
v5le0n9在执行
*/

线程的控制

方法 功能
isAlive() 判断线程是否还“活”着,即线程是否还未终止
getPriority() 获得线程的优先级数值
setPriority() 设置线程的优先级数值
Thread.sleep() 将当前线程睡眠指定毫秒数
join() 调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行
yield() 让出CPU,当前线程进入就绪队列等待调度
wait() 当前线程进入对象的wait pool
notify()/notifyAll() 唤醒对象的wait pool中的一个/所有等待线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestPriority
{
public static void main(String[] args)
{
Thread t1 = new Thread(new T1());
Thread t2 = new Thread(new T2());
//最高优先级为10,最低优先级为1,默认优先级为5
t1.setPriority(Thread.NORM_PRIORITY + 3);
t1.start();
t2.start();
}
}
class T1 implements Runnable
{
public void run()
{
for(int i=0; i<100; i++)
System.out.println("T1: " + i);
}
}
class T2 implements Runnable
{
public void run()
{
for(int i=0; i<100; i++)
System.out.println("----T2: " + i);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TestSleep
{
public static void main(String[] args)
{
A aa = new A();
Thread tt = new Thread(aa);
tt.start();
}
}
class A implements Runnable
{
public void run()
{
for(int i=0; i<10; i++)
{
System.out.println(Thread.currentThread().getName() + " " + i);
try
{//sleep会抛异常,是必须要处理的异常,所以要用try...catch语句
Thread.sleep(1000);//1000ms=1s
}
catch(Exception e)
{}
}
}
}

无论是继承Thread类的run方法还是实现Runnable接口的run方法,都不能抛出任何异常。因为重写方法抛出异常的范围不能大于被重写方法抛出的异常范围。

1
2
3
4
5
6
7
8
9
10
class A implements Runnable
{
public void run()// throws Exception
{}
}
class B extends Thread
{
public void run()// throws Exception
{}
}

线程的让步

让出CPU,给其它线程执行的机会。让运行中的线程主动放弃当前获得的CPU处理机会,但不是使该线程阻塞,而是使之转入就绪状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class TestYield 
{
public static void main(String[] args)
{
MyThread mt = new MyThread();
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
t1.setName("线程A");
t2.setName("线程B");
t1.start();
t2.start();
}
}
class MyThread implements Runnable
{
public void run()
{
for(int i=1; i<=100; i++)
{
System.out.println(Thread.currentThread().getName() + ": " + i);
if(0 == i%10)
{
Thread.yield();
}
}
}
}

线程的串行化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestJoin
{
public static void main(String[] args)
{
MyRunner r = new MyRunner();
Thread t = new Thread(r);
t.start();
try
{
t.join();//暂停当前线程,直到其他线程执行完才继续执行当前线程
//在这程序中,执行完子线程再执行主线程
}
catch(InterruptedException e)
{
e.printStackTrace();
}
for(int i=0; i<50; i++)
System.out.println("主线程:" + i);
}
}
class MyRunner implements Runnable
{
public void run()
{
for(int i=0; i<50; i++)
System.out.println("子线程:" + i);
}
}

生命周期控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class TestShutThread
{
public static void main(String[] args)
{
A aa = new A();
Thread tt = new Thread(aa);
tt.start();
try
{
Thread.sleep(1000);
}
catch(Exception e)
{
e.printStackTrace();
}
aa.shutDown();
}
}
class A implements Runnable
{
private boolean flag = true;
public void run()
{
while(flag)
System.out.println("AAAA");
}
public void shutDown()
{
this.flag = false;
}
}

线程同步问题产生的原因

假设一个买票程序

1
2
3
4
5
if(票数 > 0)
{
买一张票;
票数减一;
}

当票数只剩一张时,A买了一张票,此时线程切换到B,B显示剩一张票,他也买了一张票。这样就会导致一张票卖给了两个人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A implements Runnable
{
private static int tickets = 100;//确保aa1和aa2共有100张票
public void run()
{
while(true)
{
if(tickets > 0)//8行
{
System.out.printf("%s线程正在卖出第%d张票\n", Thread.currentThread().getName(), tickets);
tickets--;
}
else
break;//14行
}
}
}
public class TestTickets
{
public static void main(String[] args)
{
A aa1 = new A();
Thread t1 = new Thread(aa1);
t1.start();

A aa2 = new A();
Thread t2 = new Thread(aa2);
t2.start();
}
}

数据库有“事务”的概念,也就是将所有操作放到事务里,这些操作要不就全部执行成功,要不就全部执行失败。推广到上面代码,即将第8行到第14行放到“事务”里。

买票程序

synchronized关键字

synchronized可以用来修饰一个方法,也可以修饰一个方法内部的某个代码块。

1
2
3
4
5
//synchronized修饰代码块
synchronized(类对象名aa)
{
同步代码块
}

功能:判断aa是否已经被其他线程霸占,如果发现已经被其他线程霸占,则当前线程陷入等待中,如果发现aa没有被其他线程霸占,则当前线程霸占aa对象,并执行同步代码块,在当前线程执行代码块时,其他线程将无法再执行同步代码块,当前线程执行完同步代码块后,会自动释放对aa对象的霸占,此时其他线程会相互竞争对aa的霸占,最后CPU会选择其中的某一个线程执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class A implements Runnable
{
private int tickets = 100;//static可加可不加,因为都是调用同一个对象
public void run()
{
while(true)
{
synchronized(this)
{
if(tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n", Thread.currentThread().getName(), tickets);
tickets--;
}
else
break;
}
}
}
}
public class TestTickets_2
{
public static void main(String[] args)
{
A aa = new A();
Thread t1 = new Thread(aa);
t1.start();

Thread t2 = new Thread(aa);
t2.start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class A extends Thread
{
private static int tickets = 100;//确保aa1和aa2共有100张票
private static String str = new String("v5le0n9");
public void run()
{
while(true)
{
synchronized(str)
{//不能用this,因为不同对象this不同,也就不能实现同步,所以要在A类固定一个静态的str
if(tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n", Thread.currentThread().getName(), tickets);
tickets--;
}
else
break;
}
}
}
}
public class TestTickets_3
{
public static void main(String[] args)
{
A aa1 = new A();
aa1.start();
A aa2 = new A();
aa2.start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class A extends Thread
{
private static int tickets = 100;
private static String str = new String("v5le0n9");

//public synchronized void run()//错误,只有一个站点卖票
public void run()
{
//String str = new String("v5le0n9");//错误,两个站点没有共用局部变量
//String str = "v5le0n9";//正确,两个str指向同一个数据段地址
while(true)
{
synchronized(str)
{
if(tickets > 0)
{
System.out.printf("%s线程正在卖出第%d张票\n", Thread.currentThread().getName(), tickets);
tickets--;
}
else
break;
}
}
}
}
public class TestTickets_4
{
public static void main(String[] args)
{
A aa1 = new A();
aa1.start();
A aa2 = new A();
aa2.start();
}
}

生产消费

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Stack
{
int index = 0;
char[] data = new char[6];
public void push(char c)
{
data[index] = c;
index++;
}
public char pop()
{
index--;
return data[index];
}
}

压栈出栈如果不同步会出错。当push执行的时候不能执行pop,反之同理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class SynStack
{
private char[] data = new char[6];
private int cnt = 0;//表示数组有效元素的个数
public synchronized void push(char ch)
{
while(cnt == data.length)
{
try
{
this.wait();//暂停
}
catch(Exception e)
{}
}
this.notify();//唤醒
data[cnt] = ch;
++cnt;
System.out.printf("生产线程正在生产第%d个产品,该产品是:%c\n", cnt, ch);
}
public synchronized char pop()
{
char ch;
while(cnt == 0)
{
try
{
this.wait();//暂停当前线程
}
catch(Exception e)
{}
}
this.notify();//唤醒其它wait线程
ch = data[cnt-1];
System.out.printf("---消费线程正在消费第%d个产品,该产品是:%c\n", cnt, ch);
--cnt;
return ch;
}
}
class Producer implements Runnable
{
private SynStack ss = null;
public Producer(SynStack ss)
{
this.ss = ss;
}
public void run()
{
//push('a');//错误,不能用别的类的方法
char ch;
for(int i=0; i<20; i++)
{
ch = (char)('a' + i);
ss.push(ch);
}
}
}
class Consumer implements Runnable
{
private SynStack ss = null;
public Consumer(SynStack ss)
{
this.ss = ss;
}
public void run()
{
for(int i=0; i<20; i++)
{
try
{
Thread.sleep(200);
}
catch(Exception e)
{}
ss.pop();
}
}
}
public class TestPC
{
public static void main(String[] args)
{
SynStack ss = new SynStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
Thread t1 = new Thread(p);
t1.start();
Thread t2 = new Thread(c);
t2.start();
}
}

awt展望

1
2
3
4
5
6
7
8
9
10
11
import java.awt.*;
public class TestCom
{//显示绿色窗口
public static void main(String[] args)
{
Frame f = new Frame();
f.setSize(400, 400);
f.setBackground(Color.GREEN);
f.setVisible(true);
}
}

GUI(图形化用户界面)

组件

组件是图形用户界面的基本组成元素,凡是能够以图形化方式显示在屏幕上并能够与用户进行交互的对象均为组件,如菜单、按钮、标签、文本框、滚动条等。

组件分类

  • java.awt.Component
  • java.awt.MenuComponent

抽象类java.awt.Component是除菜单相关组件之外所有Java AWT组件类的根父类,该类规定了GUI组件的基本特性,如尺寸、位置和颜色效果等,并实现了作为一个GUI部件所应具备的基本功能。

容器

组件通常不能独立地显示出来,必须将组件放在一定的容器中才可以显示出来。

有一类特殊的组件是专门用来包含其他组件的,这类组件叫容器,java.awt.Container是所有容器的父类,java.awt.Container继承自java.awt.Component

容器类对象本身也是一个组件,具有组件的所有性质,但组件不一定是容器。

Frame常用方法

常用方法 功能
public void setBounds(int x, int y, int width, int height) 设置窗体的位置和大小,x和y表示窗体左上角距离屏幕水平和垂直距离,width和height是窗体自身的宽度和高度
public void setSize(int width, int heigth) 设置窗体的大小,width和height是窗体自身的宽度和高度
public void setVisible(boolean flag) 设置窗体是否可见,true表示可见。false表示不可见
public void setBackground(Color c) 设置窗体的背景色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.awt.*;
public class TestFrame
{
public static void main(String[] args)
{
Frame f = new Frame("v5le0n9");//标题
//Button bn = new Button("张三");//按钮中显示张三
//f.add(bn);
f.setSize(200, 200);
f.setLocation(300, 300);//距离屏幕(x,y)
//f.setBounds(300,300,200,200)//相当于上面两个
f.setBackground(Color.RED);
f.setVisible(true);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.awt.Frame;
public class TestFrame_2
{
public static void main(String[] args)
{
Frame f = new Frame("v5le0n9");
f.setize(200,200);
f.setVisible(true);
try
{
Thread.sleep(2000);
}
catch(InterruptedException e)
{
System.out.println(e.getMessage());
}
f.setLocation(200, 200);
try
{
Thread.sleep(2000);
}
catch(InterruptedException e)
{
System.out.println(e.getMessage());
}
f.setVisible(false);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.awt.*;
public class TestFrame_3
{
public static void main(String[] args)
{
MyFrame f1 = MyFrame(100,100,200,200,Color.BLUE);
MyFrame f2 = MyFrame(300,100,200,200,Color.YELLOW);
MyFrame f3 = MyFrame(100,300,200,200,Color.GREEN);
MyFrame f4 = MyFrame(300,300,200,200,Color.MAGENTA);
}
}
class MyFrame extends Frame
{
public static int id = 0;
MyFrame(int x, int y, int w, int h, Color color)
{
super("MyFrame " + (id++));
setBackground(color);
setLayout(null);
setBounds(x, y, w, h);
setVisible(true);
}
}

Panel

panel是容纳其他组件的组件,即容器。panel不能单独存在,必须被添加到其他容器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.awt.*;
public class TestPanel
{
public static void main(String[] args)
{
Frame f = new Frame("Java Frame with Panel");
Panel p = new Panel();
f.setLayout(null);
f.setBounds(300,300,500,500);
f.setBackground(new Color(100,100,102));
p.setBounds(300/2,300/2,500/2,500/2);//相对于父窗口而言
p.setBackground(new Color(204,204,255));
f.add(p);//p容器要放到f容器中
f.setVisible(true);
}
}

布局管理器

容器对其中所包含组件的排列方式,包括组件的位置和大小设定,被称为容器的布局(Layout)。

为了使图形用户界面具有良好的平台无关性,Java语言提供了布局管理器来管理容器的布局,而不建议直接设置组件在容器中的位置和尺寸。

每个容器都有一个默认的布局管理器,当容器需要对某个组件进行定位或判断大小尺寸时,就会自动调用其对应的布局管理器。

在AWT中,常见的布局管理器有:

  • BorderLayout
  • FlowLayout
  • GridLayout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.awt.*;
public class TestFlowLayout
{
public static void main(String[] args)
{
Frame f = new Frame("Flow Layout");
Button button1 = new Button("OK");
Button button2 = new Button("Open");
Button button3 = new Button("Close");
f.setLayout(new FlowLayout(FlowLayout.CENTER));
f.add(button1);
f.add(button2);
f.add(button3);
f.setSize(300,400);
f.setVisible(true);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.awt.*;
public class TestFlowLayout_2
{
public static void main(String[] args)
{
Frame f = new Frame("Java Frame");
FlowLayout l = new FlowLayout(FlowLayout.CENTER, 30, 30);
f.setLayout(l);
f.setLocation(300,400);
f.setSize(300,100);
f.setBackgrond(new Color(255,255,255));
for(int i=0; i<7; i++)
{
f.add(new Button(".BUTTON"));
}
f.setVisible(true);
}
}

BorderLayout

BorderLayout是Frame类的默认布局管理器,将整个容器布局划分成东(EAST)、西(WEST)、南(SOUTH)、北(NORTH)、中(CENTER)。如不指定组件的加入部位,则默认加入到CENTER区。每个区域只能加入一个组件,如果加入多个,则先前加入的会被覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.awt.*;
public class TestBorderLayout
{
public static void main(String[] args)
{
Frame f;
f = new Frame("Border Layout");
Button bn = new Button("BN");
Button bs = new Button("BS");
Button bw = new Button("BW");
Button be = new Button("BE");
Button bc = new Button("BC");
//f.add(bn, "North");//把bn放在北
//f.add(bs, "South");
//f.add(bw, "West");
//f.add(be, "East");
//f.add(bc, "Center");

//更准确的写法
f.add(bn, BoderLayout.NORTH);
f.add(bs, BoderLayout.SOUTH);
f.add(bw, BoderLayout.WEST);
f.add(be, BoderLayout.EAST);
f.add(bc, BoderLayout.CENTER);

f.setSize(200,200);
f.setVisible(true);
}
}

GridLayout

GridLayout型布局管理器将空间划分成规则的矩形网格,每个单元格区域大小相等。组件被添加到每个单元格中,先从左到右添满一行后换行,再从上到下。

在GridLayout构造方法中指定分割的行数和列数,如GridLayout(3,4)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.awt.*;
public class TestGridLayout
{
public static void main(String[] args)
{
Frame f = new Frame("GridLayout Example");
Button b1 = nee Button("b1");
Button b2 = nee Button("b2");
Button b3 = nee Button("b3");
Button b4 = nee Button("b4");
Button b5 = nee Button("b5");
f.setLayout(new GridLayout(2,10));//行一定会显示2行,但列数根据行数调整
f.add(b1);
f.add(b2);
f.add(b3);
f.add(b4);
f.add(b5);
f.pack();//如果没有这语句,只显示一个有标题的小窗口
f.setVisible(true);
}
}

十个按钮的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.awt.*;
public class TestGUI
{
public static void main(String[] args)
{
Frame f = new Frame();
f.setSize(400,400);
f.setLayout(new GridLayout(2,1));

Panel p1 = new Panel();
p1.setLayout(new BorderLayout());
Panel p1_1 = new Panel();
p1_1.setLayout(new GridLayout(2,1));

Button bn1 = new Button("Button1");
Button bn2 = new Button("Button2");
Button bn3 = new Button("Button3");
Button bn4 = new Button("Button4");

p1.add(bn1, BorderLayout.WEST);
p1_1.add(bn3);
p1_1.add(bn4);
p1.add(p1_1, BorderLayout.CENTER);
p1.add(bn2, BorderLayout.EAST);

Panel p2 = new Panel();
p2.setLayout(new BorderLayout());
Panel p2_2 = new Panel();
p2_2.setLayout(new GridLayout(2,2));
Button bn5 = new Button("Button5");
Button bn6 = new Button("Button6");
Button bn7 = new Button("Button7");
Button bn8 = new Button("Button8");
Button bn9 = new Button("Button9");
Button bn10 = new Button("Button10");
p2.add(bn5, BorderLayout.WEST);
p2.add(bn6, BorderLayout.EAST);
p2_2.add(bn7);
p2_2.add(bn8);
p2_2.add(bn9);
p2_2.add(bn10);
p2.add(p2_2);

f.add(p1);
f.add(p2);
f.pack();
f.setVisible(true);
}
}

事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.awt.*;
import java.awt.event.*;
public class TestButton
{
public static void main(String[] args)
{
Frame f = new Frame();
Button bn = mew Button("OK");//事件源
f.add(bn);
A aa = new A();//事件监听器
bn.addActionListener(aa);//鼠标单击OK在黑窗口显示hello world!
f.pack();
f.addWindowListerner(new B());//点程序的×就可以关闭程序
f.setVisible(true);
}
}
class B extends WindowAdapter //implements WindowListener//这个里面有很多抽象类,所以必须要重写所有的抽象类才能是普通类,否则B需定义为抽象类。而WindowAdapter只需重写需要的
{
public void windowClosing(WindowEvent e)
{
System.exit(-1);
}
}
class A implements ActionListener
{
public void actionPerformed(ActionEvent e)
{//方法重写
System.out.println("hello world!");
//System.exit(-1);//退出程序
}
}

默认情况下事件源不会自动产生任何事件,程序员需要做两件事:

  1. 告诉事件源可以自动产生哪类事件,即向事件源注册某种事件的事件监听器对象
  2. 设计好可以处理这种事件的事件监听器

一旦完成这两步操作,当用户对事件源进行操作时。事件源就会自动产生事件,事件源就会自动把产生的事件封装成一个事件对象,事件源就会自动把封装好的事件对象传递给事件监听器。

事件监听器收到事件源发送过来的事件时,事件监听器就会自动调用相应的事件处理方法来对该事件进行相应的处理。

事件种类

java.awt.event包中含有所有的事件,常用的事件有:

  • ActionEvent:激活组件时发生的事件
  • KeyEvent:操作键盘时发生
  • MouseEvent:操作鼠标时发生
  • WindowEvent:操作窗口时发生的事件,如最大化或最小化某一窗口

一个事件源会产生哪些事件,一般第三方软件会自动显示,不需要特地记忆。

三个文本框相加运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.awt.*;
import java.awt.event.*;
public class TestTextField
{
public static TextField tf1, tf2, tf3;//MyMonitor类需要调用这几个属性,所以不能写在main里
public static void main(String[] args)
{
tf1 = new TextField(30);
tf2 = new TextField(30);
tf3 = new TextField(30);
Button bn = new Button("=");
Label lb = new Label("+");
Frame f = new Frame("文本框相加示例");
f.setLayout(new FlowLayout());
f.add(tf1);
f.add(lb);
f.add(tf2);
f.add(bn);
f.add(tf3);
bn.addActionListener(new MyMonitor());
f.pack();
f.setVisible(true);
}
}
class MyMonitor implements ActionListener
{
@Override//用法看下个例子
public void actionPerformed(ActionEvent e)
{
String str1 = TestTextField.tf1.getText();
String str2 = TestTextField.tf2.getText();
int num1 = Integer.parseInt(str1);
int num2 = Integer.parseInt(str2);
int num3 = num1 + num2;

//第一种方法
Integer it = new Integer(num3);
String str3 = it.toString();
TestTextField.tf3.setText(str3);

//第二种方法
String str3 = num3 + "";
TestTextField.tf3.setText(str3);

//第三种方法
String str3 = Integer.toString(num3);//整型以字符串输出
TestTextField.tf3.setText(str3);

//第四种方法
String str3 = String.ValueOf(num3);
TestTextField.tf3.setText(str3);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class A
{
public void dsvdknslsvjfnhvfbcj()
{
System.out.println("AAAA");
}
}
class B extends A
{
@Override//这个提示下面的方法是重写父类的方法,如果不是重写父类的方法则会提示有错
public void dsvdknslsvjfnhvfbcj()
{
System.out.println("BBBB");
}
}
public class TestOverride
{
public static void main(String[] args)
{
A aa = new B();//父类创建一个子类对象
aa.dsvdknslsvjfnhvfbcj();//输出BBBB
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.awt.*;
import java.awt.event.*;
public class TestTextField_2
{//改良版
public static void main(String[] args)
{
new TF().launch();
}
}
class TF
{
public TextField tf1, tf2, tf3;
public void launch()
{
tf1 = new TextField(30);
tf2 = new TextField(30);
tf3 = new TextField(30);
Button bn = new Button("=");
Label lb = new Label("+");
Frame f = new Frame("文本框相加示例");
f.setLayout(new FlowLayout());
f.add(tf1);
f.add(lb);
f.add(tf2);
f.add(bn);
f.add(tf3);
bn.addActionListener(new MyMonitor(this));
f.pack();
f.setVisible(true);
}
}
class MyMonitor implements ActionListener
{
private TF tf;
public MyMonitor(TF tf)
{
this.tf = tf;
}
@Override
public void actionPerformed(ActionEvent e)
{
String str1 = tf.tf1.getText();
String str2 = tf.tf2.getText();
int num1 = Integer.parseInt(str1);
int num2 = Integer.parseInt(str2);
int num3 = num1 + num2;

String str3 = num3 + "";
tf.tf3.setText(str3);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.awt.*;
import java.awt.event.*;
public class TestTextField_3
{//再次改良版内部类
public static void main(String[] args)
{
new TF().launch();
}
}
class TF
{
private TextField tf1, tf2, tf3;
public void launch()
{
tf1 = new TextField(30);
tf2 = new TextField(30);
tf3 = new TextField(30);
Button bn = new Button("=");
Label lb = new Label("+");
Frame f = new Frame("文本框相加示例");
f.setLayout(new FlowLayout());
f.add(tf1);
f.add(lb);
f.add(tf2);
f.add(bn);
f.add(tf3);
bn.addActionListener(new MyMonitor());
f.pack();
f.setVisible(true);
}

class MyMonitor implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e)
{
String str1 = tf1.getText();
String str2 = tf2.getText();
int num1 = Integer.parseInt(str1);
int num2 = Integer.parseInt(str2);
int num3 = num1 + num2;
String str3 = num3 + "";
tf3.setText(str3);
}
}
}

内部类

内部类定义:在A类的内部但是所有方法的外部定义了一个B类,则B类就是A类的内部类,A是B的外部类。

内部类的方法可以访问外部类所有成员,外部类的方法不可以直接访问内部类的成员。

内部类的优点:①可以让一个类方便地访问另一类中的所有成员;②增加程序的安全性,有效避免其他部相关类对该类的访问。

匿名类

匿名类是一种特殊的内部类。如果在一个方法内部定义了一个匿名类,则该匿名类可以访问:①外部类的所有成员;②包裹该匿名类的方法中的所有final类型的局部变量,非final类型的局部变量无法被匿名类访问。

创建匿名类的三种方式:继承父类、实现接口、实现抽象类。

创建匿名类之实现接口

假设A是接口名,格式:

1
2
3
4
new A()
{
//实现接口中方法的代码
};

生成一个实现了A接口的匿名类对象。

创建匿名类之实现抽象类

假设A是抽象类,格式:

1
2
3
4
5
new A()
{
//实现了A类的所有抽象类的方法代码
//添加自己的方法或属性代码(不建议,没有实际意义)
}

生成一个匿名类,该匿名类必须得实现了A类的所有抽象方法,当然该匿名类也可以定义自己的属性和方法。

创建匿名类之继承父类

假设A是个类名,格式:

1
2
3
4
5
new A()
{
//重写了A类的方法代码
//添加自己的属性和方法(不建议,没有实际意义)
}

生成一个A类的子类对象,该匿名类对象继承了A的所有非private成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.awt.*;
import java.awt.event.*;
public class TestWindow
{
public static void main(String[] args)
{
Frame f = new Frame();
f.setSize(300,300);
f.addWindowListener(
new WindowAdapter()
{//匿名类
@Override
public void windowClosing(WindiwEvent e)
{
//f.setVisible(false);//错误,如果final Frame f = new Frame();才对
System.exit(-1);
}
}
);
f.setVisible(true);
}
}

可运行jar包生成步骤

  1. 新建一个记事本文件,假设为1.txt,文件内容:
  • Main-Class: 可运行类的名字(启动类的名字)
  • 附注:记得敲回车
  1. dos下命令:
  • ```
    jar cvfm haha.jar 1.txt *.class
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    注意:只有GUI程序生成的class文件才可以作为main class

    # 流

    ```java
    import java.io.*;
    public class TestFileReader
    {
    public static void main(String[] args) throws Exception
    {
    FileReader fr = new FileReader("D:\\Java\\TestFileReader.java");//某个文件路径
    int ch;
    int cnt = 0;
    ch = fr.read();//从文件读取字符编码,用整型变量存储
    while(-1 != ch)
    {
    ++cnt;
    System.out.printf("%c",(char)ch);
    ch = fr.read();
    }
    System.out.printf("该文件字符的个数是%d\n", cnt);
    fr.close();
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;
public class TestFileInputStream
{
public static void main(String[] args) throws Exception
{
FileInputStream fis = new FileInputStream("D:\\Java\\TestFileReader.java");
int ch;
int cnt = 0;
ch = fis.read();
while(-1 != ch)
{
++cnt;
System.out.printf("%c",(char)ch);
ch = fis.read();
}
System.out.printf("该文件字节的个数是%d\n", cnt);//汉字显示乱码,因为汉字占两个字节
fis.close();
}
}

流就是程序和设备之间嫁接起来的一根用于数据传输的管道,这个管道上有很多按钮,不同的按钮可以实现不同的功能。这根带按钮的用于数据传输的管道就是流。

流的分类

java.io包中定义了多个流类型(类或抽象类)来实现输入/输出功能,可以从不同的角度对其进行分类:

  • 按数据流的方向不同可以分为输入流和输出流
  • 按处理数据单位不同可以分为多字节流和字符流
  • 按照功能不同可以分为节点流和处理流

J2SDK所提供的所有流类型位于包java.io内部分别继承自以下四种抽象流类型:

字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

四大基本流

InputStream

1
public int read() throws IOException

读取一个字节并以整数形式返回,如果读取到输入流的末尾则返回-1。

1
public int read(byte[] b) throws IOException

从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。以整数形式返回实际读取的字节数。

如果b的长度为0,则不读取任何字节并返回0;如果因为流位于文件末尾而没有可用的字节,则返回值为-1。

1
2
3
4
//例子
byte[] buf = new byte[1000];
FileInputStream fis = new FileInputStream("D:\\Java\\errorlog.txt");
int len = fis.read(buf);//读取出来的数据存到buf数组,返回数据长度len
1
public int read(byte[] b, int off, int len) throws IOException

从输入流中最多读取len个字节的数据并存入byte数组中,b表示读取的数据要存入的数组的名字,off表示第一个读出的数据要存入的位置,即下标,len表示最多能读取的字节数。

1
void close() throws IOException

关闭此输入流并释放与该流关联的所有系统资源。

1
long skip(long n) throws IOException

跳过和丢弃此输入流中数据的n个字节。

OutPutStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//向输出流中写入一个字节数据,该字节数据为参数b的低8位
void write(int b) throws IOException

//将一个字节类型的数组中的数据写入输出流
void write(byte[] b) throws IOException

//将一个字节类型的数组中的从指定位置(off)开始的len个字节写入到输出流
void write(byte[] b, int off, int len) throws IOException

//关闭流释放内存资源
void close() throws IOException

//将输出流中缓冲的数据全部写出到目的地
void flush() throws IOException

Reader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//读取一个字符并以整数形式返回
int read() throws IOException

//读取一系列字符并存储到一个数组cbuf
int read(char[] cbuf) throws IOException

//从offset位置开始,最多读取length个字符,并存储到数组cbuf
int read(char[] cbuf, int offset, int length) throws IOException

//关闭流释放内存资源
void close() throws IOException

//跳过n个字符不读,返回实际跳过的字节数
long skip(long n) throws IOException

Writer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//向输出流中写入一个字符数据,该字符数据为参数c的低16位
void write(int c) throws IOException

//将一个字符类型的数组中的数据写入输出流
void write(char[] cbuf) throws IOException

//将一个字符类型的数组中的从指定位置(off)开始的len个字符写入到输出流
void write(char[] cbuf, int off, int len) throws IOException

//将一个字符串中的字符写入到输出流
void write(String string) throws IOException

//将一个字符串从指定位置(off)开始的len个字符写入到输出流
void write(String string, int off, int len) throws IOException

//关闭流释放内存资源
void close() throws IOException

//将输出流中缓冲的数据全部写出到目的地
void flush() throws IOException

字节流和字符流的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;
public class TestFileReaderWriterCopy
{//字符流,复制文本文件可以,但视频音频照片等字节数据复制会出错
public static void main(String[] args) throws Exception
{
FileReader fr = new FileReader("D:\\Java\\TestFileReaderWriterCopy.java");
FileWriter fw = new FileWriter("D:\\v5le0n9.txt");
int ch;
ch = fr.read();
while(-1 != ch)
{
fw.write(ch);
ch = fr.read();
}
fw.flush();//刷新
fr.close();
fw.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;
public class TestFileInputStreamOutputStreamCopy
{//字节流,可复制文本文件和非文本文件
public static void main(String[] args) throws Exception
{
FileInputStream fis = new FileInputStream("D:\\Java\\TestFileReaderWriterCopy.java");
FileOutputStream fos = new FileOutputStream("D:/v5le0n9.txt");
int ch;
ch = fis.read();
while(-1 != ch)
{
fos.write(ch);
ch = fis.read();
}
fos.flush();//刷新
fis.close();
fos.close();
}
}

例:

1
2
3
AF ED CB FF 12 CE
//字节流一个一个字节读(AF)
//字符流两个字节读(AF ED),把二进制数据转换为字符,返回整型,存在解码和编码的问题

缓冲流

缓冲流就是带有缓冲区的输入输出流。缓冲流可以显著地减少对IO的访问次数,保护硬盘。

缓冲流是处理流,必须依附于节点流。

J2SDK提供了四种缓冲流,其常用的构造方法为:

1
2
3
4
5
6
7
8
9
10
11
BufferedReader(Reader in)
BufferedReader(Reader in, int sz)//sz为自定义缓冲区大小

BufferedWriter(Writer out)
BufferedWriter(Wreiter out, int sz)

BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)

BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size)

缓冲输入流支持其父类的mark和reset方法。

BufferedReader提供了readLine方法用于读取一行字符串(以\r或\n分隔)。

BufferedWriter提供了newLine用于写入一个行分隔符。

对于输出的缓冲流,写出的数据会现在内存中缓存,使用flush方法将会是内存中的数据立刻写出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;
public class TestBufferedInputStreamOutputStreamCopy
{
public static void main(String[] args) throws Exception
{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Java\\TestFileReaderWriterCopy.java"));
BufferedOutputStream bos = new BufferedOutpuntStream(new FileOutputStream("D:/v5le0n9.txt"));
byte[] buf = new byte[1024];//每次缓存1024个字节
int len;
len = bis.read(buf);
while(-1 != len)
{
//bos.write(buf);//很可能错误,因为最后可能没有1024个字节
bos.write(buf, 0, len);
len = bis.read(buf);
}
bos.flush();
bis.close();
bos.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.io.*;
public class TestBufferedReaderWriterCopy
{
public static void main(String[] args)
{
BufferedReader br = null;
BufferedWriter bw = null;
try
{
br = new BufferedReader(new FileReader("D:/Java/TestBufferedReaderWriterCopy.java"));
bw = new BufferedWriter(new FileWriter("D:\\Java\\v5le0n9.heihei"));
String str = null;
while(null != (str=br.readLine()))//一行行读
{
bw.write(str);
bw.newLine();//写入一个换行符,这行不能省
}
bw.flush();
}
catch(FileNotFoundException e)
{
e.printStackTrace();
Sytsem.exit(-1);
}
catch(IOException e)
{
e.printStackTrace();
Sytsem.exit(-1);
}
finally
{
try
{
bw.close();
br.close();
}
catch(IOException e)
{
e.printStackTrace();
Sytsem.exit(-1);
}
}
}
}

数据流

DataInputStream

DataInputStream能够以一种有与机器无关的方式,直接从底层字节输入流读取Java基本类型和String类型的数据,常用的方法包括:

1
2
3
4
5
6
7
8
9
10
public DataInputStream(InputStream in)
public final boolean readBoolean()
public final byte readByte()
public final char readChar()
public final double readDouble()
public final float readFloat()
public final int readInt()
public final long readLong()
public final short readShort()
public final String readUTF()

DataInputStream是包裹流(处理流),必须依附于InputStream

DataOutputStream

DataOutputStream能够以一种与机器无关的方式,直接将Java基本类型和String类型数据写出到其他的字节输出流。常见方法:

1
2
3
4
5
6
7
8
9
10
public DataOutputStream(OutputStream out)
public final boolean writeBoolean()
public final byte writeByte()
public final char writeChar()
public final double writeDouble()
public final float writeFloat()
public final int writeInt()
public final long writeLong()
public final short writeShort()
public final String writeUTF()

DataOutputStream是包裹流(处理流),必须依附于OutputStream

数据流实例

编程实现将long类型数据写入byte数组,然后再从byte数组中把该数据读出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;
public class TestByteArrayOutputStream
{
public static void main(String[] args) throws Exception
{
long n = 9876543210L;
ByteArrayOutputStream baos = new ByteArrayOutputStream();//字节数组输出流,字节数组输出流自动生成一个字节数组
DataOutputStream dos = new DataOutputStream(baos);
dos.writeLong(n);//把长整型写入字节数组里面去
dos.flush();
byte[] buf = baos.toByteArray();//把自动生成的字节数组复制到toByteArray()里去,再赋给buf
ByteArrayInputStream bais = new ByteArrayInputStream(buf);//把数组buf作为形参生成一个字节数组输入流对象
DataInputStream dis = new DataInputStream(bais);
long l = dis.readLong();//把字节数组读出来,返回长整型
//dis.readLong(l);//错误,读出来不需要参数
System.out.println("l = " + l);
dos.close();
}
}

转换流

OutputStreamWriter流是把OutputStream流转化成Writer流的流,InputStreamReader流是把InputStream流转化成Reader流的流。

OutputStreamWriter和InputStreamWriter都是处理流。

转换流实例

如何将键盘输入的字符组成字符串直接赋给String对象。

1
2
3
4
5
6
7
8
9
10
11
import java.io.*;
public class TestString
{
public static void main(String[] args) throws Exception
{
String str = null;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
str = br.readLine();//readLine不包含换行符
System.out.println("str = " + str);
}
}

Print流

Print流只有输出,没有输入。

PrintWriter输出字符,PrintStream输出字节。

PrintStream

PrintStream在OutputStream基础之上提供了增强的功能,即可以方便地输出各种类型数据(而不仅限于byte型)的格式化表示形式。

PrintStream重载了printf和println方法,用于各种不同类型数据的格式化输出,格式化输出是指将一个数据用其字符串格式输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.*;
public class TestPrintString
{
public static void main(String[] args) throws Exception
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:/Java/1.txt"));
dos.writeLong(12345);//实际写入文件的是00 00 00 00 00 00 30 39
dos.close();
System.out.printf("%#X\n", 12345);//0X3039

PrintStream ps = new PrintStream(new FileOutputStream("D:/Java/2.txt"));
ps.println(12345);//实际写入文件的是‘1’,‘2’,‘3’,‘4’,‘5’
ps.close();
}
}

PrintWriter

PrintWriter提供了PrintStream的所有打印方法,其方法也从不抛出IOException

与PrintStream的区别:①作为处理流使用时,PrintStream只能封装OutputStream类型的字节流,而PrintWriter既可以封装OutputStream还能够封装Writer;②PrintWriter中的println()方法具有更好的跨平台性。

标准输入输出的重定向

1
2
3
4
5
6
7
8
9
try
{
...
}
catch(Exception e)
{
e.printStackTrace();//默认把错误信息输出到System.err所关联的设备中
...
}
1
2
3
4
5
6
7
8
9
public class TestSys
{
public static void main(String[] args) throws Exception
{
PrintStream ps = new PrintStream("D:/heihei.aaa");
System.setOut(ps);//设置输出,原本输出是在终端,现在改变输出设备,输出到ps里去
System.out.println("哈哈");//哈哈放到了heihei.aaa
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TestSetSystemOut
{
public static void main(String[] args)
{//包含异常的写法
PrintStream ps_out = null;
try
{
ps_out = new PrintStream(new FileOutputStream("D:/Java/hello"));
System.setOut(ps_out);
System.out.println(12);
System.out.println(55.5);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
ps_out.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}

实例

编程实现将键盘输入整型数据到A文件中,如果输入有误,则把出错信息输出到B文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.*;
import java.util.*;
public class TestSetOutErr
{
public static void main(String[] args)
{
PrintStream psOut = null;
PrintStream psError = null;
Scanner sc = null;
try
{
psOut = new PrintStream("D:/Out.txt");
psError = new PrintStream("D/Error.txt");
sc = new Scanner(System.in);
int num;
System.setOut(psOut);
System.serErr(psError);
while(true)
{
num = sc.nextInt();
System.out.println(num);
}
}
catch(Exception e)
{
System.err.println("出错的信息是:");
e.printStackTrace();
}
}
}

对象的序列化(Object流)

Serializable接口中没有任何方法,这种类型的接口被称为标记接口。如果一个类实现了Serializable接口,潜在含义就是告诉编译器这个类是允许被序列化的,如果程序中存在序列该对象的代码,编译器就会自动进行相应的处理以完成该对象的序列化,如果该对象没有实现Serializable接口,程序中却存在该对象被序列化的代码,编译器编译时就会报错。

在Java中transient修饰的成员变量在对象序列化时不会被序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import java.io.*;
public class TestObjectIO
{
public static void main(String[] args)
{
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
Student ss = new Student("zhangsan", 1000, 88.8f);
Student ss2 = null;
try
{
FileOutputStream fos = new FileOutputStream("D:\\Java\\dudu");
oos = new ObjectOutputStream(fos);
oos.writeObject(ss);

ois = new ObjectInputStream(new FileInputStream("D:/Java/dudu"));
ss2 = (Student)ois.readObject();//(Student)不能省

System.out.println("ss2.sname = " + ss2.sname);
System.out.println("ss2.sid = " + ss2.sid);
System.out.println("ss2.sscore = " + ss2.sscore);
}
catch(FileNotFoundException e)
{
System.out.println("文件没有找到!");
System.exit(-1);
}
catch(Exception e)
{
e.printStackTrace();
System.exit(-1);
}
finally
{
try
{
oos.close();
ois.close();
}
catch(Exception e)
{
e.printStackTrace();
System.exit(-1);
}
}
}
}
class Student implements Serializable//这个接口为空,用作标识
{
public String sname = null;
public int sid = 0;
transient public float sscore = 0;//表示sscore成员不能被序列化
public Student(String name, int id, float score)
{
this.sname = name;
this.sid = id;
this.sscore = score;
}
}
/**
ss2.sname = zhangsan
ss2.sid = 1000
ss2.sscore = 0.0
*/

容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.*;
class A
{
public String toString()
{
return "Haha";
}
}
public class Test
{
public static void main(String[] args)
{
ArrayList al = new ArrayList();//数组线性结构
al.add(12345);
al.add("张三");
al.add(66.6);
al.add(new A());
System.out.println(al);//[12345, 张三, 66.6, Haha]
//println调用的是al里面的各个对象的toString方法
//12345是个值,但添加到ArrayList里面的值就转换成了对象
//System.out.println(al[2]);//不是数组,不能用下标
System.out.println(al.get(2));//66.6

Object[] obArr = al.toArray();//把集合转化为数组
System.out.println(al[2]);//66.6
}
}

为什么需要容器

数组存在两个缺陷:

  • 数组长度难以扩充
  • 数组中元素类型必须相同

容器可以弥补数组的这两个缺陷。

容器与现实的对应关系

集合(容器)就是将若干用途、性质相同或相近的“数据”组合而成的一个整体。

数学上,集合类型可以归纳为三种

  • 集(Set):Set集合不区分元素的顺序,不允许出现重复元素
  • 列表(List):List集合区分元素顺序,且允许包含重复元素
  • 映射(Map):映射中保存成对“键-值”(Key-Value)信息,映射中不能包含重复的键,每个键最多只能映射一个值

Java设计了三个接口来对应数学上的三种集合类型,这三个接口名字分别为Set、List、Map。

Collection接口中的方法介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//返回此collection中的元素数
int size();

//判断是否为空
boolean isEmpty();

//判断形参c所指向的集合中的所有元素是不是已经全部包含在了当前集合中
boolean containesAll(Collection c);

//返回能够遍历当前集合所有元素的迭代器
Iterator iterator();

//返回一个包含此collection中所有元素的数组
Object[] toArray();

//把e添加到当前集合中
boolean add(Object e);

//把o移除
boolean remove(Object o);

//把c中所有的元素添加到当前集合中
boolean addAll(Collection c);

//把c中所有元素移除
boolean removeAll(Collection c);

//把当前容器中的所有元素清除
void clear();

//判断是否相等
boolean equals(Object o);

//返回哈希码
int hashCode();

Collection接口的子接口——List接口

List接口是Collection的子接口,实现List接口的容器类中的元素是有顺序的,而且可以重复。

List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

J2SDK所提供的List容器类有ArrayList, LinkList等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//获取下标为index的元素
Object get(int index);

//把index位置的元素设置成element元素
Object set(int index, Object element);

//在index位置添加element元素
void add(int index, Object element);

//删除下标为index的元素
Object remove(int index);

//获取o元素出现的第一个下标
int indexOf(Object o);

//获取o元素出现的最后一个下标
int lastIndexOf(Object o);

ArrayList与LinkedList的比较

ArrayList和LinkedList都实现了List接口中的方法,但两者内部实现不同。ArrayList底层采用数组完成,而LinkedList则是以一般的双向链表完成,其内每个对象除了数据本身外,还有两个引用,分别指向前一个元素和后一个元素。

它们两个的性质与C语言的数组和链表的性质一样。

Collections类

Collection接口的实现类,如ArrayList、LinkedList本身并没有提供排序、倒置、查找等方法,这些方法是由Collesctions类来实现的,该类有很多public static方法,可以直接对Collection接口的实现类进行操作。

Collections类常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//对List容器内的元素排序
void sort(List);

//对List容器内的对象进行随机排列
void shuffle(List);

//对List容器内的对象进行逆序排列
void reverse(List);

//用一个特定的对象重写整个List容器
void fill(List, Object);

//将src List容器内容拷贝到dest List容器
void copy(List dest, List src);

//对于顺序的List容器,采用折半查找的方法查找特定对象的下标
int binarySearch(List, Object);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.*;
public class TestCollections
{
public static void main(String[] args)
{
List lt = new LinkedList();//利用多态
for(int i=0; i<7; i++)
{
lt.add("a" + i);
}
System.out.println(lt);

Collections.shuffle(lt);//乱序
System.out.println(lt);

Collections.sort(lt);//排序
System.out.println(lt);

Collections.reverse(lt);//倒置
System.out.println("倒置之后:" + lt);
System.out.println(Collections.binarySearch(lt, "a5"));//-8,倒置之后找不到,因为binarySearch方法只针对升序排序

Collections.sort(lt);
System.out.println("重新排序之后:" + lt);
System.out.println(Collections.binarySearch(lt, "a5"));//5

Collections.fill(lt, "0");
System.out.println(lt);
}
}

Comparable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.*;
class Student
{
private int id;
private String name;
public Student(inti id, String name)
{
this.id = id;
this.name = name;
}
@Override
public String toString()
{
return id + " " + name;
}
}
public class TestList
{
public static void main(String[] args)
{
List L = new ArrayList();
L.add(new Student(1000, "张三"));
L.add(new Student(1001, "李四"));
L.add(new Student(1002, "王五"));
//Collections.sort(L);//错误,不知道对对象的哪个属性排序
System.out.println(L);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.*;
class Student implements Comparable
{//使用comparable接口调用compareTo方法
private int id;
private String name;
public Student(int id, String name)
{
this.id = id;
this.name = name;
}
@Override
public String toString()
{
return id + " " + name;
}
@Override
public int compareTo(Object o)
{//Obejct o是父类,需转换为子类才能调用子类的属性
Student st = (Student)o;
if(this.id == st.id)
return 0;
else if(this.id > st.id)
return 1;
else
return -1;
}
}
public class TestList_2
{
public static void main(String[] args)
{
List L = new ArrayList();
L.add(new Student(1000, "张三"));
L.add(new Student(1001, "李四"));
L.add(new Student(1002, "王五"));
L.add(new Student(1002, "王五"));
Collections.sort(L);//sort自动调用compareTo方法
System.out.println(L);//此时输出两个王五,这是我们不希望的,List接口有顺序可重复,而Set接口无顺序不重复。如果不希望输出两个王五,则使用Set接口
}
}

Collection接口的子接口——Set接口

因为Set和List都是继承自Collection接口,所以Set和List中有很多方法是一样的。

List接口中有add, set, indexOf方法,但Set接口中却只有add方法,没有set, indexOf方法。因为Set是无序且不能重复的,不存在某元素具体位置这个概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.util.*;
class Student
{
private int id;
private String name;
public Student(int id, String name)
{
this.id = id;
this.name = name;
}
@Override
public String toString()
{
return id + " " + name;
}
@Override
public boolean equals(Object ob)
{//id和name都相等返回true,即st.id和st.name相乘的哈希码与this.id和this.name相乘的哈希码一样则返回true
Student st = (Student)ob;
return this.id == st.id && this.name == st.name;
}
@Override
public int hashCode()
{//返回id和name相乘的哈希码
return id * this.name.hashCode();
}
}
public class TestSet
{
public static void main(String[] args)
{
Set S = new HashSet();//TreeSet不需要重写equals和hashCode方法,但需要Comparable接口,且在插入数据时自动排序
//使用hashSet实现不重复要重写equals和hashCode方法,二者缺一不可
S.add(new Student(1000, "张三"));
S.add(new Student(1001, "李四"));
S.add(new Student(1002, "王五"));
System.out.println(S);

S.add(new Student(1002, "王五"));
S.add(new Student(1001, "王五"));
S.add(new Student(1004, "王五"));
System.out.println(S);
}
}
/**
[1000 张三, 1001 李四, 1002 王五]
[1000 张三, 1001 李四, 1001 王五, 1002 王五, 1004 王五]
*/
//这个程序只解决了[id,name]的唯一性,却没有解决id的唯一性

equals和hashCode方法的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A
{
private int i;
public A(int i)
{
this.i = i;
}
}
public class TesthashCode
{
public static void main(String[] args)
{
A aa1 = new A(1);
A aa2 = new A(1);
System.out.println(aa1 == aa2);//flase
System.out.println(aa1.equals(aa2));//flase
System.out.println(aa1.hashCode() == aa2.hashCode());//flase

Integer it1 = new Integer(1);
Integer it2 = new Integer(1);
System.out.println(it1 == it2);//flase
System.out.println(it1.equals(it2));//true
//equals显示true表示Integer已经重写了equals方法,Java所有自带的数据类型都重写了equals方法,下面的hashCode同理
System.out.println(it1.hashCode() == it2.hashCode());//true
}
}

如果不重写equals和hashCode方法,equals比较aa1和aa2的对象的地址的十六进制是否相等,hashCode比较aa1和aa2的对象的地址的十进制是否相等。

如何重写equals和hashCode方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class A
{
private int i;
public A(int i)
{
this.i = i;
}
@Override
public boolean equals(Object ob)
{
A aa = (A)ob;
return this.i == aa.i;
}
@Override
public int hashCode()
{
return new Integer(i).hashCode();
}
}
public class TesthashCode_2
{
public static void main(String[] args)
{
A aa1 = new A(1);
A aa2 = new A(1);
System.out.println(aa1 == aa2);//false
System.out.println(aa1.equals(aa2));//true
System.out.println(aa1.hashCode() == aa2.hashCode());//true

Integer it1 = new Integer(1);
Integer it2 = new Integer(1);
System.out.println(it1 == it2);//flase
System.out.println(it1.equals(it2));//true
System.out.println(it1.hashCode() == it2.hashCode());//true
}
}

Iterator接口

常用方法:

1
2
3
4
5
6
7
8
//判断当前游标的后面是否还存在元素,如果存在返回true
boolean hasNext();

//先返回当前游标右边的元素,然后游标后移一个位置
Object next();

//删除最近返回的元素,在调用remove之前,我们至少保证先调用一次next方法,而且调用next之后只能调用一次remove方法,remove方法不推荐使用
void remove();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.*;
public class TestIterator
{
//可以遍历所有Collection接口的实现类
public static void showCollection(Collection c)
{
Iterator it = c.iterator();//it相当于指针,指向容器c的第一个元素
while(it.hasNext())//相当于遍历
{
System.out.println(it.next());
}
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();
al.add("one");
al.add(22);
al.add(new Point(1,1));
System.out.println("al容器的内容是:");
showCollection(al);

HashSet hs = new HashSet();
hs.add("one");
hs.add(22);
hs.add(new Point(1,1));
System.out.println("hs容器的内容是:");
showCollection(hs);
}
}
class Point
{
private int i, j;
public Point(int i, int j)
{
this.i = i;
this.j = j;
}
public String toString()
{
return "[" + i + "," + j + "]";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.*;
public class TestIterator_2
{
public static void main(String[] args)
{
Collection c = new TreeSet();//TreeSet自动排序
c.add("123");
c.add("456");
c.add("234");
c.add("111");
c.add("678");
Iterator i = c.iterator();
while(i.hasNext())
{
System.out.println(i.next());
}
}
}
/**
111
123
234
456
678
*/

Map接口

java.util.Map接口描述了映射结构,Map结构允许以键集、值集合或键-值映射关系集的形式查看某个映射的内容。

主要方法:

1
2
3
4
5
6
7
8
9
10
11
Object put(Object key, Object value);
Object get(Object key);

//把m容器中所有元素存入当前容器中
void putAll(Map m);
boolean isEmpty();
void clear();
int size();
boolean containsKey(Object key);
boolean containsValue(Object value);
Object remove(Object key);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.*;
public class TestHashMap
{
public static void main(String[] args)
{
Map m = new HashMap();
m.put("one", "zhangsan");//前面是Key,后面是Value
m.put(66.6, 70);
m.put(new A(), "12");
//System.out.println(m.get("12"));//null,get方法不能对value使用,只能对key使用
System.out.println(m.get(66.6));//70
System.out.println(m);//{66.6=70, one=zhangsan, A@15db9742=12}
m.put(66.6, 80);
System.out.println(m);//{66.6=80, one=zhangsan, A@15db9742=12}
}
}
class A
{}

Map(映射)是一种把键对象和值对象进行映射的集合,它的每一个元素都包含一对键对象和值对象。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import java.util.*;
class Student
{
private int id;
private String name;
private int age;
public Student()
{}
public Student(int id, String name, int age)
{
this.id = id;
this.name = name;
this.age = age;
}
public int hashCode()
{
return this.name.hashCode() * id * age;
}
public boolean equals(Object o)
{
Student s = (Student)o;
return this.name.equals(s.name) && this.id == s.id && this.age == s.age;
}
public String toString()
{
return id + " " + name + " " + age;
}
}
public class TestHashMap_2
{
public static void main(String[] args)
{
HashMap hm = new HashMap();//需要重写equals和hashCode
hm.put(1001, new Student(1001, "zhangsan", 20));//自动封装,1001被认为是Integer的一个对象
hm.put(1003, new Student(1003, "lisi", 30));
hm.put(new Integer(1004), new Student(1004, "wangwu", 10));
hm.put(1002, new Student(1002, "v5le0n9", 20));

//遍历所有元素
System.out.println("hm容器中所有的元素是:");
Set s = hm.keySet();//获取当前容器键的集合
Iterator it = s.iterator();
while(it.hasNext())
{
Integer key = (Integer)it.next();//(Integer)不能省
System.out.println(hm.get(key));
}

System.out.println("直接查找某一元素");
System.out.println(hm.get(1003));
System.out.println(hm.get(1005));//找不到返回null
}
}
/**
hm容器中所有的元素是:
1001 zhangsan 20
1002 v5le0n9 20
1003 lisi 30
1004 wangwu 10
直接查找某一元素
1003 lisi 30
null
*/

泛型

泛型是用来限制传入容器、接口中的数据类型。

参考TestHashMap_2

1
2
3
4
5
6
7
8
HashMap<Integer, Student> hm = new HashMap<Integer, Student>();//容器中的东西全都是对象,所以Integer不能改为int
//因为1001,1002都是Integer自动封装的,所以是Integer类型,Set同理
Set<Integer> s = hm.keySet();
//这样写保证了程序的安全性
Iterator<Integer> it = s.iterator();
//Iterator it是键的集合,所以也是Integer类型,此时
//Integer key = (Integer)it.next();可以省略(Integer)
Integer key = it.next();
1
2
3
4
5
6
7
8
9
10
11
/**
Comparable<T> 接口中有如下方法:
int compareTo(T o)
要想限制传入Comparable接口的数据类型,可以利用泛型来实现:
class Student implements Comparable<Student>
{
...
public int compareTo(Student ob)//Student不能写成Object
{}
}
*/

网络编程

UDP编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.net.*;
import java.io.*;
public class TestUDPServer
{
public static void main(String[] args) throws Exception
{
//定义码头
DatagramSocket ds = new DatagramSocket(5678);//表示该码头占用的是5678这个编号,即服务端的端口号为5678

//定义可以用来接受数据的集装箱
byte buf[] = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
try
{
while(true)
{
//在码头上用集装箱接受对方发送过来的数据
ds.receive(dp);

//从集装箱中取出对方发送过来的数据
ByteArrayInputStream bais = new ByteArrayInputStream(dp.getData());
DataInputStream dis = new DataInputStream(bais);
System.out.println(dis.readLong());
}
}
catch(Exception e)
{
e.printStackTrace();
ds.close();//关闭码头
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.net.*;
import java.io.*;
public class TestUDPClient
{
public static void main(String[] args) throws Exception
{
//定义码头ds
DatagramSocket ds = new DatagramSocket();

//定义可以发送数据的集装箱dp,dp中保存着数据,是n的二进制代码
long n = 10000L;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeLong(n);//把10000写到字节数组里去
byte[] buf = baos.toByteArray();
DatagramPacket dp = new DatagramPacket(buf, buf.length, new InetSocketAddress("127.0.0.1", 5678));//把字节数组包装成一个集装箱,发送到本地5678端口

//在码头上把集装箱中的数据发送给对方
ds.send(dp);

//关闭码头
ds.close();
}
}

TCP编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.net.*;
import java.io.*;
public class TCPServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(6666);//6666是端口号
while(true)
{
Socket s = ss.accept();//等待客户端连接
System.out.println("一个连接已经建立!");
DataInputStream dis = new DataInputStream(s.getInputStream());
System.out.println(dis.readUTF());
dis.close();
s.close();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.net.*;
import java.io.*;
public class TCPClient
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("127.0.0.1", 6666);
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("v5le0n9");
dos.flush();
dos.close();
s.close();
}
}