类的继承
Java只支持单继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
这里写图片描述
子类继承了父类,就继承了父类的方法和属性。
在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
因而,子类通常比父类的功能更多。
在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
关于继承的规则:
子类不能继承父类中私有的(private)的成员变量和方法。
访问控制
可以对Java类中定义的属性和方法进行访问控制—-规定不同的保护等级: public、protected、default、private
这里写图片描述
访问控制举例
class Parent{
private int f1 = 1;
int f2 = 2; //default
protected int f3 = 3;
public int f4 = 4;
private void fm1() {
System.out.println("in fm1() f1=" + f1);
}
void fm2() { //default
System.out.println("in fm2() f2=" + f2);
}
protected void fm3() {
System.out.println("in fm3() f3=" + f3);
}
public void fm4() {
System.out.println("in fm4() f4=" + f4);
}
}
//设父类和子类在同一个包内
class Child extends Parent{
private int c1 = 21;
public int c2 = 22;
private void cm1(){
System.out.println("in cm1() c1=" + c1);
}
public void cm2(){
System.out.println("in cm2() c2=" + c2);
}
public static void main(String args[]){
int i;
Parent p = new Parent();
i = p.f2; // i = p.f3;i = p.f4;
p.fm2(); // p.fm3();p.fm4();
Child c = new Child();
i = c.f2; // i = c.f3;i = c.f4;
i = c.c1; // i = c.c2;
c.cm1(); // c.cm2(); c.fm2(); c.fm3();c.fm4()
}
}
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
访问控制分析
父类Parent和子类Child在同一包中定义时:
这里写图片描述
覆盖方法
在子类中可以根据需要对从父类中继承来的方法进行改造—覆盖方法(方法的重置、重写),在程序执行时,子类的方法将覆盖父类的方法。
覆盖方法必须和被覆盖方法具有相同的方法名称、参数列表和返回值类型。
覆盖方法不能使用比被覆盖方法更严格的访问权限。
覆盖方法举例
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "
" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() { //覆盖方法
return "Name: "+ name + "
age: "+ age
+ "
school: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
说明:
Person p1=new Person();
p1.getInfo();
//调用Person类的getInfo()方法
Student s1=new Student();
s1.getInfo();
//调用Student类的getInfo()方法
这是一种“多态性”:同名的方法,用不同的对象来区分调用的是哪一个方法。
覆盖方法举例
class Parent {
public void method1() {}
}
class Child extends Parent {
private void method1() {}
//非法,子类中的method1()的访问权限private比被覆盖方法的访问权限public弱
}
public class UseBoth {
public void doOtherThing() {
Parent p1 = new Parent();
Child p2 = new Child();
p1.method1();
p2.method1();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
关键字super
在Java类中使用super来引用父类的成分
super可用于访问父类中定义的属性
super可用于调用父类中定义的成员方法
super可用于在子类构造方法中调用父类的构造方法
super的追溯不仅限于直接父类
super举例
public class Person {
private String name;
private int age;
public String getInfo() {
return "Name: " + name + "
age: " + age;
}
}
public class Student extends Person {
private String school = "New Oriental";
public String getSchool(){ return school; }
public String getInfo() {
// 调用父类的方法
return super.getInfo() +"
school: " +school;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
构造方法不能继承
子类继承父类所有的成员变量和成员方法,但不继承父类的构造方法
在一个Java类中可以通过两种方式获得构造方法
使用系统默认的无参数构造方法
显式定义一个或多个构造方法
一旦显式定义了构造方法,则系统不再提供默认构造方法
调用父类构造方法
注意 注意 注意 重要的事说三遍:↓↓↓
在子类的构造方法中可使用super(参数列表)语句调用父类的构造方法
如果子类的构造方法中没有显示地调用父类构造方法,也没有使用this关键字调用重载的其它构造方法,则系统默认调用父类无参数的构造方法
如果子类构造方法中既未显式调用父类构造方法,而父类中又没有无参的构造方法,则编译出错
调用父类构造方法举例
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
// ……
}
public class Student extends Person {
private String school;
public Student(String name, int age, String s) {
super(name, age);
school = s;
}
public Student(String name, String s) {
super(name);
school = s;
}
public Student(String s) {
/**
* 编译出错: no super(),
* 系统将调用父类无参数的构造方法。
* */
school = s;
}
}
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
子类对象的实例化过程
这里写图片描述
思考
1).为什么super(…)和this(…)调用语句不能同时在一个构造函数中出现?
2).为什么super(…)或this(…)调用语句只能作为构造函数中的第一句出现?
看个例子解答以上两个问题。
this() super()是你如果想用传入当前构造器中的参数或者构造器中的数据调用其他构造器或者控制父类构造器时使用的,在一个构造器中你只能使用this()或者super()之中的一个,而且调用的位置只能在构造器的第一行,在子类中如果你希望调用父类的构造器来初始化父类的部分,那就用合适的参数来调用super(),如果你用没有参数的super()来调用父类的构造器(同时也没有使用this()来调用其他构造器),父类缺省的构造器会被调用,如果父类没有缺省的构造器,那编译器就会报一个错误。
假如我们允许把this和super放置到任何位置。那么请看下面代码:
class A
{
A()
{
System.out.println("You call super class non-args constructor!");
}
}
class B extends A
{
B()
{
//这里,编译器将自动加上 super();
System.out.println("You call subclass constructor!");
}
B(String n)
{
super();
this();//实际就是调用了B(){...},而在B(){...}中编译器自动加上了
//super();这样就相当于两次调用了super();也就是说对父类进//行了两次初始化。而在实例化一个对象时,一个构造方法只能调用一次,这说明this和super不能同时存在一个构造方法中。同时因为系统没有在第一行发现this()或super()调用,就会自动加上super(),如果没有将this()和super()放在第一行就会产生矛盾。
//因为总有一个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
结论
也就是说你必须在构造器的第一行放置super或者this构造器,否则编译器会自动地放一个空参数的super构造器的,其他的构造器也可以调用super或者this,调用成一个递归构造链,最后的结果是父类的构造器(可能有多级父类构造器)始终在子类的构造器之前执行,递归的调用父类构造器。无法执行当前的类的构造器。也就不能实例化任何对象,这个类就成为一个无为类。
从另外一面说,子类是从父类继承而来,继承了父类的属性和方法,如果在子类中先不完成父类的成员的初始化,则子类无法使用,应为在java中不允许调用没初始化的成员。在构造器中是顺序执行的,也就是说必须在第一行进行父类的初始化。而super能直接完成这个功能。This()通过调用本类中的其他构造器也能完成这个功能。
因此,this()或者super()必须放在第一行。
多态性
多态—在Java中,子类的对象可以替代父类的对象使用
一个变量只能有一种确定的数据类型
一个引用类型变量可能指向(引用)多种不同类型的对象
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
//父类类型的变量可以指向子类的对象
1
2
3
4
5
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
1
2
3
4
属性是在编译时确定的,编译时e为Person类型,没有school成员变量,
因而编译错误。
虚拟方法调用(Virtual Method Invocation)
// 正常的方法调用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
// 虚拟方法调用(多态情况下)
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
1
2
3
4
5
6
7
8
编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。—— 动态绑定
多态性应用举例
方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
public class Test{
public void method(Person e) {
//……
e.getInfo();
}
public static void main(Stirng args[]){
Test t = new Test();
Student m = new Student();
t.method(m); //子类的对象m传送给父类类型的参数e
}
}
1
2
3
4
5
6
7
8
9
10
11
instanceof 操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型。
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
如果x属于类A的子类B,x instanceof A值也为true。
public class Person extends Object {…}
public class Student extends Person {…}
public class Graduate extends Person {…}
public void method1(Person e) {
if (e instanceof Person)
// 处理Person类及其子类对象
if (e instanceof Student)
//处理Student类及其子类对象
if (e instanceof Graduate)
//处理Graduate类及其子类对象
}
1
2
3
4
5
6
7
8
9
10
11
12
对象类型转换 (Casting )
基本数据类型的Casting:
小的数据类型可以自动转换成大的数据类型
如long g=20; double d=12.0f
可以把大的数据类型强制转换(casting)成小的数据类型
如 floate f=(float)12.0 int a=(int)1200L
对Java对象的强制类型转换称为造型
从子类到父类的类型转换可以自动进行
从父类到子类的类型转换必须通过造型(强制类型转换)实现
无继承关系的引用类型间的转换是非法的
在造型前可以使用instanceof操作符测试一个对象的类型
对象类型转换举例
public class Test{
public void method(Person e) { //设Person类中没有getschool()方法
System.out.pritnln(e.getschool()); //非法,编译时错误
if(e instanceof Student){
Student me = (Student)e; //将e强制转换为Student类型
System.out.pritnln(me.getschool());
}
}
public static void main(Stirng args[]){
Test t = new Test();
Student m = new Student();
t.method(m);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object 类
Object类是所有Java类的根父类
如果在类的声明中未使用extends关键字指明其父类,则默认父类为Object类
public class Person {
...
}
等价于:
public class Person extends Object {
...
}
1
2
3
4
5
6
7
例:
method(Object obj){…}//可以接收任何类作为其参数
Object o=new Person;
method(o);
1
2
3
==操作符与equals方法
==操作符与equals方法的区别:
==:引用类型比较引用(是否指向同一个对象);
Person p1=new Person();
Person p2=new Person();
if (p1==p2){…}
1
2
3
基本类型比较值;
int a=5; if(a==6){…}
用”==”进行比较时,符号两边的数据类型必须一致(可自动转换的基本数据类型除外),否则编译出错;
equals()方法是Object类的方法,由于所有类都继承Object类,也就继承了equals()方法。只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。格式:obj1.equals(obj2)
特例:当用equals()方法进行比较时,对类File、String、Date及封装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;
原因:在这些类中覆盖了equals()方法。
toString 方法
toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
在进行String与其它类型数据的连接操作时,自动调用toString()方法
Date now=new Date();
System.out.println(“now=”+now);
// 相当于
System.out.println(“now=”+now.toString());
//now=Date@122345
1
2
3
4
5
可以根据需要在用户自定义类型中重写toString()方法
如String 类重写了toString()方法,返回字符串的值。
s1="hello";
System.out.println(s1);
//相当于
System.out.println(s1.toString());
1
2
3
4
在ToString1.java中的类A里覆盖toString方法,使其输出类A对象的cint属性值。
基本类型数据转换为String类型时,调用了对应封装类的 toString()方法int a=10; System.out.println(“a=”+a);
封装类
针对八种基本定义相应的引用类型—封装类
这里写图片描述
关键字static
描述
在Java类中声明变量、方法和内部类时,可使用关键字static做为修饰符。
static标记的变量或方法由整个类(所有实例)共享,如访问控制权限允许,可不必创建该类对象而直接用类名加‘.’调用。
static成员也称类成员或静态成员,如:类变量、类方法、静态方法等。
类变量(class Variable)
类变量(类属性)由该类的所有实例共享
这里写图片描述
public class Person {
private int id;
//类属性类似于全局变量
public static int total = 0;
public Person() {
total++;
id = total;
}
}
1
2
3
4
5
6
7
8
9
类属性应用举例
class Person {
private int id;
public static int total = 0;
public Person() {
total++;
id = total;
}
public static void main(String args[]){
Person Tom=new Person()
Tom.id=0;
total=100; // 不用创建对象就可以访问静态成员
}
}
public class OtherClass {
public static void main(String args[]) {
Person.total = 100; // 不用创建对象就可以访问静态成员
//访问方式:类名.类属性类名.类方法
System.out.println(Person.total);
Person c = new Person();
System.out.println(c.total); //输出101
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
类方法(class Method)
描述
在静态方法里只能直接调用同类中其它的静态成员(包括变量和方法),而不能直接访问类中的非静态成员。这是因为,对于非静态的方法和变量,需要先创建类的实例对象后才可使用,而静态方法在使用前不用创建任何对象。
静态方法不能以任何方式引用this和super关键字。与上面的道理一样,因为静态方法在使用前不用创建任何实例对象,当静态方法被调用时,this所引用的对象根本就没有产生。
main() 方法是静态的,因此JVM在执行main方法时不创建main方法所在的类的实例对象,因而在main()方法中,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在以后的例子中会多次碰到。
类名.方法名()
没有对象的实例时,可以用类名.方法名()的形式访问由static标记的类方法。
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
return total;
}
public Person() {
total++;
id = total;
}
}
public class TestPerson {
public static void main(String[] args) {
System.out.println("Number of total is " +Person.getTotalPerson());
//没有创建对象也可以访问静态方法
Person p1 = new Person();
System.out.println( "Number of total is "+ Person.getTotalPerson());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
访问类的static属性
在static方法内部只能访问类的static属性,不能访问类的非static属性。
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
id++; //非法
return total;
}
public Person() {
total++;
id = total;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
访问static方法注意
因为不需要实例就可以访问static方法,因此static方法内部不能有this,(也不能有super )
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
this.total=total; //非法,在static方法中不能有this,也不能有super
}
public Person() {
total++;
id = total;
}
}
public class TestPerson {
public static void main(String[] args) {
Person.setTotalPerson();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
类属性、类方法的设计思想
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些类属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用
静态初始化
一个类中可以使用不包含在任何方法体中的静态代码块(static block ),当类被载入时,静态代码块被执行,且只被执行一次,静态块经常用来进行类属性的初始化。
static块通常用于初始化static (类)属性
class Person {
public static int total;
static {
total = 100;//为total赋初值
}
…… //其它属性或方法声明
}
1
2
3
4
5
6
7
静态初始化举例
class Person {
public static int total;
static {
total = 100;
System.out.println("in static block!");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("total = "+ Person.total);
System.out.println("total = "+ Person.total);
}
}
//输出:
//in static block
//total=100
//total=100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
单子 Singleton 设计模板(单例,单态)
描述
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就想是经典的棋谱,不同的棋局,我们用不同的棋谱,免得我们自己再去思考和摸索。
所谓类的单态设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造方法的访问权限设置为private,这样,就不能用new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
单子Singleton 设计模板
class Single{
private static Single onlyone = new Single();//私有的,只能在类的内部访问
private String name;
public static Single getSingle() { //getSingle()为static,不用创建对象
//即可访问
return onlyone;
}
private Single() {} //private的构造器,不能在类的外部创建该类的对象
}
public class TestSingle{
public static void main(String args[]) {
Single s1 = Single.getSingle(); //访问静态方法
Single s2 = Single.getSingle();
if (s1==s2){
System.out.println("s1 is equals to s2!");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
理解main方法的语法
描述
由于java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。
这里写图片描述
命令行参数用法举例
public class CommandPara {
public static void main(String[] args) {
for ( int i = 0; i < args.length; i++ ) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
//运行程序CommandPara.java
java CommandPara lisa "bily" "Mr Brown"
//输出结果:
args[0] = lisa
args[1] = bily
args[2] = Mr Brown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
关键字 final
描述
在Java中声明类、属性和方法时,可使用关键字final来修饰。
final标记的变量(成员变量或局部变量)即成为常量,只能赋值一次。
final标记的类不能被继承。提高安全性,提高程序的可读性。
final标记的方法不能被子类重写。增加安全性。
final标记的成员变量必须在声明的同时或在每个构造方法中显式赋值,然后才能使用。
final PI=3.14;
关键字final应用举例
public final class Test{
public static int totalNumber= 5 ;
public final int id;
public Test(){
id = ++totalNumber;//只能在构造方法中给final变量赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.id);
final int i = 10;
final int j;
j = 20;
j = 30; //非法
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
抽象类(abstract class)
描述
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
用abstract关键字来修饰一个类时,这个类叫做抽象类;用abstract来修饰一个方法时,该方法叫做抽象方法。
抽象方法:只有方法的声明,没有方法的实现。以分号结束。
abstract int abstractMethod1( int a );
含有抽象方法的类必须被声明为抽象类。
抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。
不能用abstract修饰私有方法,构造方法,静态方法。
抽象类举例
abstract class A{
abstract void m1( );
public void m2( ){
System.out.println("A类中定义的m2方法");
}
}
class B extends A{
void m1( ){
System.out.println("B类中定义的m1方法");
}
}
public class Test{
public static void main( String args[ ] ){
A c = new B( );
c.m1( );
c.m2( );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
抽象类应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
这里写图片描述
在航运公司系统中,Vehicle类需要定义两个方法分别计算运输工具的燃料效率和行驶距离。
练习:
卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法,但子类可以。
解决方案
Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。
Vehicle是一个抽象类,有两个抽象方法。
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算卡车的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算卡车行驶距离的具体方法 }
}
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算驳船的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算驳船行驶距离的具体方法}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
接 口
描述
有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
接口(interface)是抽象方法和常量值的定义的集合。
从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
接口定义举例
public interface Runner {
int id = 1;
public void start();
public void run();
public void stop();
}
1
2
3
4
5
6
7
接口的特点:
用 interface 来定义。
接口中的所有成员变量都默认是由public static final修饰的。
接口中的所有方法都默认是由public abstract修饰的。接口没有构造方法。
实现接口的类中必须提供接口中所有方法的具体实现内容。
多个无关的类可以实现同一个接口
一个类可以实现多个无关的接口
与继承关系类似,接口与实现类之间存在多态性
接口也可以继承另一个接口,使用extends关键字。
实现接口的类中必须提供接口中所有方法的具体实现内容。
多个无关的类可以实现同一个接口
一个类可以实现多个无关的接口
与继承关系类似,接口与实现类之间存在多态性
定义Java类的语法格式:
< modifier> class < name> [extends < superclass>]
[implements < interface> [,< interface>]* ] {
< declarations>*
}
1
2
3
4
接口应用举例
public interface Runner {
public void start();
public void run();
public void stop();
}
public class Person implements Runner {
public void start() {
// 准备工作:弯腰、蹬腿、咬牙、瞪眼 // 开跑
}
public void run() {
// 摆动手臂
// 维持直线方向
}
public void stop() {
// 减速直至停止、喝水。
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这里写图片描述
一个类可以实现多个无关的接口
interface Runner { public void run();}
interface Swimmer {public double swim();}
class Animal {public int eat(){…}}
class Person extends Animal implements Runner,Swimmer{
public void run() {……}
public double swim() {……}
public int eat() {……}
}
1
2
3
4
5
6
7
8
与继承关系类似,接口与实现类之间存在多态性
public class Test{
public static void main(String args[]){
Test t = new Test();
Person p = new Person();
t.m1(p);
t.m2(p);
t.m3(p);
}
public String m1(Runner f) { f.run(); }
public void m2(Swimmer s) {s.swim();}
public void m3(Animal a) {a.eat();}
}
1
2
3
4
5
6
7
8
9
10
11
12
注意
如果实现接口的类中没有实现接口中的全部方法,必须将此类定义为抽象类。
接口也可以继承另一个接口,使用extends关键字。
interface MyInterface
{
String s=“MyInterface”;
public void absM1();
}
interface SubInterface extends MyInterface
{
public void absM2();
}
public class SubAdapter implements SubInterface
{
public void absM1(){System.out.println(“absM1”);}
public void absM2(){System.out.println(“absM2”);}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
实现类SubAdapter必须给出接口SubInterface以及父接口MyInterface中所有方法的实现。
接口和继承的区别
记住一句话:
接口是“有 没 有”的关系,继承是“是 不 是”的关系。
内部类
描述
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类
内部类和外层封装它的类之间存在逻辑上的所属关系
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。 Inner class的名字不能与包含它的类名相同;
Inner class可以使用包含它的类的静态和实例成员变量,也可以使用它所在方法的局部变量;
内部类举例
class A {
private int s;
public class B{
public void mb() {
s = 100;
System.out.println("在内部类B中s=" + s);
}
}
public void ma() {
B i = new B();
i.mb();
}
}
public class Test {
public static void main(String args[]){
A o = new A();
o.ma();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class A{
private int s = 111;
public class B {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(A.this.s); // 外层类对象属性s
}
}
public static void main(String args[]){
A a = new A();
A.B b = a.new B();
b.mb(333);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
内部类特性
Inner class可以声明为抽象类 ,因此可以被其它的内部类继承。也可以声明为final的。
和外层类不同,Inner class可以声明为private或protected;
Inner class 可以声明为static的,但此时就不能再使用外层封装类的非static的成员变量;
非static的内部类中的成员不能声明为static的,只有在顶层类或static的内部类中才可声明static成员;
泛型
描述
下面是那种典型用法:
List myIntList = new ArrayList();// 1
myIntList.add(new Integer(0));// 2
Integer x = (Integer) myIntList.iterator().next();// 3
1
2
3
第 3 行的类型转换有些烦人。通常情况下,程序员知道一个特定的 list 里边放的是什么类型的数据。但是,这个类型转换是必须的(essential)。编译器只能保证 iterator 返回的是 Object 类型。为了保证对 Integer 类型变量 赋值的类型安全,必须进行类型转换(这是关键)。
当然,这个类型转换不仅仅带来了混乱,它还可能产生一个运行时错误 (run time error),因为程序员可能会犯错。
程序员如何才能明确表示他们的意图,把一个 list(集合) 中的内容限制 为一个特定的数据类型(这是关键)呢?这就是 generics 背后的核心思想。这是上面程 序片断的一个泛型版本:
List<Integer> myIntList = new ArrayList<Integer>(); //1
myIntList.add(new Integer(0)); // 2
Integer x = myIntList.iterator().next(); // 3
1
2
3
注意变量 myIntList 的类型声明。它指定这不是一个任意的 List,而是 一个 Integer 的 List,写作:List<Integer>。我们说 List 是一个带一个类型 参数的泛型接口(这是关键)(a generic interface that takes a type parameter),本 例中,类型参数是 Integer。我们在创建这个 List 对象的时候也指定了一个 类型参数。
另一个需要注意的是第 3 行没了类型转换。
现在,你可能认为我们已经成功地去掉了程序里的混乱。我们用第 1 行的类型参数取代了第 3 行的类型转换。然而,这里还有个很大的不同。 编译器现在能够在编译时检查程序的正确性。当我们说 myIntList 被声明为 List<Integer>类型,这告诉我们无论何时何地使用 myIntList 变量,编译器 保证其中的元素的正确的类型。
实际结果是,这可以增加可读性和稳定性(robustness),尤其在大型的 程序中。
定义简单的泛型
下面是从 java.util 包中的 List 接口和 Iterator 接口的定义中摘录的片断:
public interface List<E> {
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E> {
E next();
boolean hasNext();
}
1
2
3
4
5
6
7
8
这些都应该是很熟悉的,除了尖括号中的部分,那是接口 List 和 Iterat or 中的形式类型参数的声明(the declarations of the formal type param eters of the interfaces List and Iterator)。
类型参数在整个类的声明中可用,几乎是所有可以使用其他普通类型的 地方
在介绍那一节我们看到了对泛型类型声明 List (the generic type decl aration List) 的调用,如 List<Integer>。在这个调用中(通常称作一个参数 化类型 a parameterized type),所有出现的形式类型参数(formal type pa rameter,这里是 E)都被替换成实体类型参数(actual type argument)(这里 是 Integer)。
你可能想象,List<Integer>代表一个 E 被全部替换成 Integer 的版本:
public interface IntegerList {
void add(Integer x)
Iterator<Integer> iterator();
}
1
2
3
4
类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形 式参数(formal value parameters)来描述它操作的参数的种类一样,一个 泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用, 实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用, 实际类型参数(actual type arguments)取代形式类型参数。
一个命名的习惯:推荐用简练的名字作为形式类型参数的名字(如果可 能,单个字符)。最好避免小写字母
泛型和子类继承
让我们测试一下我们对泛型的理解。下面的代码片断合法么?
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2
1
2
第 1 行当然合法,但是这个问题的狡猾之处在于第 2 行。
这产生一个问题:
一个 String 的 List 是一个 Object 的 List 么?
大多数人的直觉是回答: “当然!”。
好,在看下面的几行
lo.add(new Object()); // 3
String s = ls.get(0); // 4: 试图把 Object 赋值给 String
1
2
这里,我们使用 lo 指向 ls。我们通过 lo 来访问 ls,一个 String 的 list。 我们可以插入任意对象进去。结果是 ls 中保存的不再是 String。当我们试 图从中取出元素的时候,会得到意外的结果。
java 编译器当然会阻止这种情况的发生。第 2 行会导致一个编译错误。
总之,如果 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是某种 泛型声明,那么 G<Foo>是 G<Bar>的子类型并不成立!!
为了处理这种情况,考虑一些更灵活的泛型类型很有用。到现在为止我 们看到的规则限制比较大。
通配符(Wildcards)
考虑写一个例程来打印一个集合(Collection)中的所有元素。下面是在老 的语言中你可能写的代码:
void printCollection(Collection c) {
Iterator i = c.iterator();
for (int k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
1
2
3
4
5
6
下面是一个使用泛型的幼稚的尝试(使用了新的循环语法):
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
1
2
3
4
5
问题是新版本的用处比老版本小多了。老版本的代码可以使用任何类型 的 Collection 作为参数,而新版本则只能使用 Collection<Object>,我们刚 才阐述了,它不是所有类型的 collections 的父类。
那么什么是各种 collections 的父类呢?它写作: Collection
void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}
1
2
3
4
5
现在,我们可以使用任何类型的 collection 来调用它。注意,我们仍然 可以读取 c 中的元素,其类型是 Object。这永远是安全的,因为不管 colle ction 的真实类型是什么,它包含的都是 Object。
但是将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误
1
2
因为我们不知道 c 的元素类型,我们不能向其中添加对象。
add 方法有类型参数 E 作为集合的元素类型。我们传给 add 的任何参 数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我 们无法传任何东西进去。唯一的例外是 null,它是所有类型的成员。
另一方面,我们可以调用 get()方法并使用其返回值。返回值是一个未 知的类型,但是我们知道,它总是一个 Object
有限制的通配符(Bounded Wildcards)
考虑一个简单的画图程序,它可以用来画各种形状,比如矩形和圆形。 为了在程序中表示这些形状,你可以定义下面的类继承结构:
public abstract class Shape {
public abstract void draw(Canvas c);
private int x, y, radius;
public void draw(Canvas c) { // ... }
}
public class Circle extends Shape {
}
public class Rectangle extends Shape {
private int x, y, width, height; public void draw(Canvas c) {
// ... }
}
1
2
3
4
5
6
7
8
9
10
11
这些类可以在一个画布(Canvas)上被画出来:
public class Canvas {
public void draw(Shape s) {
s.draw(this);
}
}
1
2
3
4
5
所有的图形通常都有很多个形状。假定它们用一个 list 来表示,Canva s 里有一个方法来画出所有的形状会比较方便:
public void drawAll(List<Shape> shapes) {
for (Shape s : shapes) {
s.draw(this);
}
}
1
2
3
4
5
现在,类型规则导致 drawAll()只能使用 Shape 的 list 来调用。它不能, 比如说对List<Circle>来调用。这很不幸,因为这个方法所作的只是从这个 list 读取 shape,因此它应该也能对List<Circle>调用。我们真正要的是这 个方法能够接受一个任意种类的
Shape:
public void drawAll(List<? extends Shape> shapes) { //..}
这里有一处很小但是很重要的不同:我们把类型List<Shape>替换成 了List<? extends Shape>。现在 drawAll()可以接受任何 Shape 的子类的 List,所以我们可以对List<Circle>进行调用。
List<? extends Shape>是有限制通配符的一个例子。这里?代表一个 未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道 这个未知的类型实际上是 Shape 的一个子类。我们说 Shape 是这个通配符 的上限(upper bound)。
像平常一样,要得到使用通配符的灵活性有些代价。这个代价是,现在 向 shapes 中写入是非法的。比如下面的代码是不允许的:
public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle()); // compile-time error!
}
1
2
3
你应该能够指出为什么上面的代码是不允许的。因为 shapes.add 的第 二个参数类型是 ? extends Shape ——一个 Shape 未知的子类。因此我 们不知道这个类型是什么,我们不知道它是不是 Rectangle 的父类;它可 能是也可能不是一个父类,所以这里传递一个 Rectangle 不安全。
泛型方法
考虑写一个方法,它用一个 Object 的数组和一个 collection 作为参数, 完成把数组中所有 object 放入 collection 中的功能。
下面是第一次尝试:
static void fromArrayToCollection(Object[] a,Collection<?> c) {
for (Object o : a) {
c.add(o); // 编译期错误
}
}
1
2
3
4
5
现在,你应该能够学会避免初学者试图使用Collection<Object>作为集 合参数类型的错误了。或许你已经意识到使用Collection<?>也不能工作。 回忆一下,你不能把对象放进一个未知类型的集合中去。
解决这个问题的办法是使用 generic methods。就像类型声明,方法的 声明也可以被泛型化——就是说,带有一个或者多个类型参数。
static <T> void fromArrayToCollection(T[] a,Collection<T> c){
for (T o : a) {
c.add(o); // correct
}
}
1
2
3
4
5
6
我们可以使用任意集合来调用这个方法,只要其元素的类型是数组的元 素类型的父类。
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();
fromArrayToCollection(oa, co);// T 指 Object
String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();
fromArrayToCollection(sa, cs);// T inferred to be String
fromArrayToCollection(sa, co);// T inferred to be Object
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(ia, cn);// T inferred to be Number
fromArrayToCollection(fa, cn);// T inferred to be Number
fromArrayToCollection(na, cn);// T inferred to be Number
fromArrayToCollection(na, co);// T inferred to be Object
fromArrayToCollection(na, cs);// compile-time error
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
注意,我们并没有传送真实类型参数(actual type argument)给一个泛 型方法。编译器根据实参为我们推断类型参数的值。它通常推断出能使调 用类型正确的最明确的类型参数。
枚举
描述
在某些情况下,一个类的对象是有限而且固定的。例如季节类,只能有 4 个对象
手动实现枚举类:
private 修饰构造器
属性使用 private final 修饰
把该类的所有实例都使用 public static final 来修饰
使用 enum 定义枚举类
JDK 1.5 新增的 enum 关键字用于定义枚举类
枚举类和普通类的区别:
使用 enum 定义的枚举类默认继承了 java.lang.Enum 类
枚举类的构造器只能使用 private 访问控制符
枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾). 列出的实例系统会自动添加 public static final 修饰
所有的枚举类都提供了一个 values 方法, 该方法可以很方便地遍历所有的枚举值
JDK 1.5 中可以在 switch 表达式中使用枚举类的对象作为表达式, case 子句可以直接使用枚举值的名字, 无需添加枚举类作为限定
若枚举只有一个成员, 则可以作为一种单子模式的实现方式
枚举类的属性
枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰
枚举类使用 private final 修饰的属性应该在构造器中为其赋值
若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数
使用 Enum 定义的 Season
这里写图片描述
实现接口的枚举类
和普通 Java 类一样枚举类可以实现一个或多个接口
若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式, 则可以让每个枚举值分别来实现该方法
枚举类的方法
这里写图片描述
小练习
public enum SeasonEnum implements EnumInfo {
//这里相当于创建了4个对象.可以分别实现接口方法
/*SPRING(10,"春天"){
//实现接口的方法
@Override
public Integer getCode() {
return null;
}
},*/
SPRING(10,"春天"),
SUMMER(20,"夏天"),
AUTUMN(30,"秋天"),
WINTER(40,"冬天");
private final int code;
private final String desc;
//默认就是private
SeasonEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
//这是举例,其实没什么用.直接code就可以返回自己的code
@Override
public Integer getCode() {
switch (this) {
case SPRING:
return 10;
case SUMMER:
return 20;
case AUTUMN:
return 30;
case WINTER:
return 40;
}
return null;
}
/**
* 接口
* public interface EnumInfo {
public Integer getCode();
}
*/
}
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
Annotation(注释)
从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注释)
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理. 通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息.
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 “name=value” 对中.
Annotation 能被用来为程序元素(类, 方法, 成员变量等) 设置元数据
基本的 Annotation
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用. 用于修饰它支持的程序元素
三个基本的 Annotation:
@Override: 限定重写父类方法, 该注释只能用于方法
@Deprecated: 用于表示某个程序元素(类, 方法等)已过时
@SuppressWarnings: 抑制编译器警告.
自定义 Annotation
定义新的 Annotation 类型使用 @interface 关键字
Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明. 其方法名和返回值定义了该成员的名字和类型.
可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用 default 关键字
没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation
提取 Annotation 信息
JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口代表程序中可以接受注释的程序元素
当一个 Annotation 类型被定义为运行时 Annotation 后, 该注释才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取
程序可以调用 AnnotationElement 对象的如下方法来访问 Annotation 信息
这里写图片描述
JDK 的元 Annotation
JDK 的元 Annotation 用于修饰其他 Annotation 定义
@Retention:
只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留多长时间, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
RetentionPolicy.CLASS: 编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注释. 这是默认值
RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注释. 程序可以通过反射获取该注释
RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释
@Target:
用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素. @Target 也包含一个名为 value 的成员变量.
@Documented:
用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档.
@Inherited:
被它修饰的 Annotation 将具有继承性.如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注释
反射
描述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
实例
通过一整套基础测试例子,理解反射。
整体类结构如下:
这里写图片描述
Person.java
public class Person {
private String gender;
private String name;
@MyValidation(min = 18,max = 30)
private Integer age;
public Person() {
this("男人");
System.out.println("Person's this : "+ this);
System.out.println("Person constructor...");
}
public Person(String gender) {
this.gender = gender;
System.out.println("Person constructor...gender init.." + this.gender );
}
/**
* 加法
* @param a
* @param b
* @return a,b的和
*/
protected Integer add(Integer a , Integer b ){
System.out.println("arg1 : "+ a + " arg2 : "+b + " result : "+(a + b));
return a +b;
}
//getter setter ...
}
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
User.java
public class User extends Person{
private Integer id;
private String name;
//getter setter ...
private String concat(String a , String b ){
System.out.println("arg1 : "+ a + " arg2 : "+b + " result : "+a.concat(b));
return a.concat(b);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Man.java
public class Man extends Person{
private boolean girlFriend;
private String occupation;
public Man() {
//第一行肯定执行的是super()
System.out.println("Man's this : "+ this);
System.out.println("Man constructor....");
}
public Man(boolean girlFriend, String occupation) {
this.girlFriend = girlFriend;
this.occupation = occupation;
System.out.println("Man constructor....girlFriend..occupation..init...");
}
//getter setter ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BaseDao.java
public interface BaseDao<T> {
public T find();
}
1
2
3
4
5
BaseDaoImpl.java
public abstract class BaseDaoImpl<T> implements BaseDao<T> {
//clazz赋值是关键
private Class<T> clazz;
public BaseDaoImpl() {
/**
* this其实是代表的BaseDaoImpl的子类对象
* this.getClass();得到的就是子类的类名
*/
System.out.println("DAO基类构造器 子类为 : "+ this.getClass());
clazz = getEntityClass.getEntity(this.getClass(),0);
}
@Override
public T find() {
System.out.println("DAO基类 find() 具体class为 : "+ clazz);
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
UserDaoImpl.java
public class UserDaoImpl extends BaseDaoImpl<User> {
//执行父类方法
}
1
2
3
MyValidation.java
/**
*
* 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些
* 程序元素. @Target 也包含一个名为 value 的成员变量.
*/
@Target({ElementType.METHOD,ElementType.FIELD})
/**
* RetentionPolicy.CLASS: 编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注释. 这是默认值
* RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注释. 程序可以通过反射获取该注释
* RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface MyValidation {
//最小值
public int min();
//最大值
public int max();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
getEntityClass.java
public class getEntityClass {
/**
* 获取定义Class时声明的父类的泛型参数类型
* @param clazz 子类对应的Class对象
* @param index 要获取的泛型参数索引
*/
public static Class getEntity(Class clazz,int index){
/**
* 获取父类泛型类型
*/
Type type = clazz.getGenericSuperclass();
if (type != null){
/**
* ParameterizedType : 泛型类型
*/
if (!(type instanceof ParameterizedType)) {
return Object.class;
}
ParameterizedType param = (ParameterizedType) type;
/**
* 获取实际的泛型类型参数数组
*/
Type[] types = param.getActualTypeArguments();
// System.out.println(Arrays.asList(types));
if (types != null && types.length > 0){
if (index > types.length || index < 0){
return Object.class;
}
if (! (types[0] instanceof Class)){
return Object.class;
}
return (Class) types[0];
}
}
return Object.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
reflectionUtils.java
public class reflectionUtils {
/**
*
* @param obj 需要操作的对象
* @param methodName 操作的方法
* @param args 参数
* @return
*/
public static Object invoke(Object obj,String methodName,Object ... args)
throws NoSuchMethodException,
InvocationTargetException,
IllegalAccessException {
Class clazz = obj.getClass();
Class[] classes = new Class[args.length];
for (int i = 0;i<args.length;i++){
classes[i] = args[i].getClass();
}
Method method = clazz.getDeclaredMethod(methodName,classes);
method.setAccessible(true);
return method.invoke(obj,args);
}
/**
*
* @param className 需要操作的对象的类路径
* @param methodName 操作的方法
* @param args 参数
* @return
*/
public static Object invoke(String className,String methodName,Object ... args)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException, NoSuchMethodException, InvocationTargetException {
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
return invoke(obj,methodName,args);
}
/**
* @apiNote 执行当前对象的方法(包括继承父类的方法)
* @param className 需要操作的对象的类路径
* @param methodName 操作的方法
* @param args 参数
* @return
*/
public static Object invokeThisAndSuper(String className,String methodName,Object ... args)
throws ClassNotFoundException,
IllegalAccessException,
InstantiationException,
InvocationTargetException, NoSuchMethodException {
Class clazz = Class.forName(className);
Class[] classes = new Class[args.length];
for (int i = 0;i<args.length;i++){
classes[i] = args[i].getClass();
}
Method method = null;
//继承层级循环向上,一直找到所有类的父类Object,如果还找不到method就算了.
for (Class c = clazz;c != Object.class;c = c.getSuperclass()){
try {
method = c.getDeclaredMethod(methodName,classes);
if (method != null)
break;
} catch (NoSuchMethodException e) {}
}
if (method == null){
throw new NoSuchMethodException("没有这个方法...");
}
method.setAccessible(true);
Object obj = clazz.newInstance();
return method.invoke(obj,args);
}
/**
* @apiNote 设置字段的值(包括父类的字段)
* @param obj 操作的对象
* @param fieldName 对象的字段名称
* @param val 设置的值
*/
public static void setFieldVal(Object obj,String fieldName,Object val) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
Class clazz = obj.getClass();
Field field = null;
//继承层级循环向上,一直找到所有类的父类Object,如果还找不到fieldName对应的Field就算了.
for (Class f = clazz; clazz != Object.class;f = clazz.getSuperclass()){
try {
field = f.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {}
if (field != null)
break;
}
if (field == null){
throw new NoSuchFieldException("无法获取字段");
}
//Object object = clazz.newInstance();
field.setAccessible(true);
field.set(obj,val);
//return obj;
}
}
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
92
93
94
95
96
97
98
99
100
101
102
103
PersonTest.java
目的:主要测试子类和父类构造方法之间的关系。
public class PersonTest {
public static void main (String[] a){
Man m = new Man();
/**
* 执行结果:
* Person constructor...gender init..男人
* Person's this : com.iboray.javacore.reflection.Man@f6f4d33
* Person constructor...
* Man's this : com.iboray.javacore.reflection.Man@f6f4d33
* Man constructor....
*/
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
RefTest.java
目的:测试反射
public class RefTest {
public static void main(String[] a) throws Exception {
// testClass();
// testClassLoader();
// testReflection();
// testConstructor();
// testAnnotation();
testGenericReflection();
/*User u = new User();
Object obj = reflectionUtils.invoke(u,"concat","我是一个 : ","粉刷匠");
System.out.println("result : "+ obj);*/
/*Object obj = reflectionUtils.invokeThisAndSuper("com.iboray.javacore.reflection.User","add",3,4);
System.out.println("result : "+ obj);*/
/* Object obj = new User();
reflectionUtils.setFieldVal(obj,"name","我是红领巾");
reflectionUtils.setFieldVal(obj,"gender","女");
User user = (User)obj;
System.out.println("result user.getName() : "+ user.getName());
System.out.println("result user.getGender() : "+ user.getGender());*/
}
/**
* 通过子类获得泛型类型
*/
public static void testGenericReflection(){
BaseDao dao = new UserDaoImpl();
dao.find();
}
/**
* 注解测试
*/
public static void testAnnotation() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName("com.iboray.javacore.reflection.Person");
int val = 18;
Field age = clazz.getDeclaredField("age");
/**
* 获取指定类型的注解
*/
Annotation annotation = age.getAnnotation(MyValidation.class);
if (annotation != null){
/**
* 如果类型确实为MyValidation
*/
if (annotation instanceof MyValidation){
//类型转换
MyValidation myValidation = (MyValidation)annotation;
//获取注解中的属性值
if (val < myValidation.min() || val > myValidation.max())
throw new RuntimeException("invalid age .... ");
}
}
age.setAccessible(true);
Object o = clazz.newInstance();
age.set(o,val);
System.out.println("自定义验证注解后的对象 : "+ o);
}
/**
* Class 是对一个类的描述,是类的类类型,它是描述一个类的类
* 直白的说就是Class中的属性是类名,方法名,字段名,构造器,实现的接口等等这些都是Class的属性....
*
* 对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
* Class 对象只能由系统建立对象,一个类在 JVM 中只会有一个Class实例
* 每个类的实例都会记得自己是由哪个 Class 实例所生成
*
* 类的属性:Field
* 类的方法:Method
* 类的构造器:Constructor
*
*/
public static void testClass()
throws ClassNotFoundException,
NoSuchMethodException,
NoSuchFieldException,
IllegalAccessException, InstantiationException, InvocationTargetException {
//通过类的class 属性获取,该方法最为安全可靠.程序性能更高
Class clazz = User.class;
System.out.println(clazz);
User user = new User();
//通过实例对象的getClass获取
clazz = user.getClass();
System.out.println(clazz);
//通过Class对象的forName()静态方法forName()获取
//但可能会抛出ClassNotFoundException异常
clazz = Class.forName("com.iboray.javacore.reflection.User");
System.out.println(clazz);
/**
* 实例化对象
*/
Object obj = clazz.newInstance();
/**
* 获取所有声明的方法,包括private
*/
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods){
System.out.println("Method : "+m);
}
/**
* 获取指定方法
*/
Method method = clazz.getDeclaredMethod("concat",String.class,String.class);
System.out.println("获取指定的方法 concat : "+ method);
/**
* 获取所有声明字段
*/
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields){
System.out.println("Field : "+f);
}
/**
* 获取指定声明字段
*/
Field field = clazz.getDeclaredField("name");
System.out.println("field : "+ field);
/**
* 设置值
* setAccessible 设置操作权限
*/
field.setAccessible(true);
field.set(obj,"我是红领巾");
/**
* 获取指定对象的指定字段的值
*/
Object val = field.get(obj);
System.out.println("字段值为 : "+val);
/**
* 通过method找到指定对象的get方法并获取指定字段值
*/
Method nameMethod = clazz.getDeclaredMethod("getName");
Object nameVal = nameMethod.invoke(obj);
System.out.println("获取指定的方法 getName : "+ nameVal);
}
/**
* 反射构造器测试
*/
public static void testConstructor() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Class.forName("com.iboray.javacore.reflection.Person");
Constructor[] constructors = clazz.getConstructors();
for (Constructor c : constructors){
System.out.println("所有构造器之 : "+ c);
}
Constructor constructor = clazz.getConstructor(String.class);
System.out.println("指定构造器 : "+ constructor);
Object object = constructor.newInstance("男人");
System.out.println("指定构造器实例化对象 : "+ object);
}
/**
* 类装载器是用来把类(class)装载进 JVM 的。
* JVM 规范定义了两种类型的类装载器:启动类装载器(bootstrap)和用户自定义装载器(user-defined class loader)。
* JVM在运行时会产生3个类加载器组成的初始化加载器层次结构
*/
public static void testClassLoader(){
ClassLoader classloader;
//获取系统缺省的ClassLoader
classloader = ClassLoader.getSystemClassLoader();
/**
* 结果:
* 系统缺省的ClassLoader : sun.misc.Launcher$AppClassLoader@54bedef2
* 表示:
* 系统类装载器实例化自类sun.misc.Launcher$AppClassLoader
*/
System.out.println("系统缺省的ClassLoader : "+classloader);
while (classloader != null) {
//取得父的ClassLoader
classloader = classloader.getParent();
System.out.println("系统缺省的ClassLoader : "+classloader);
/**
* 结果为两条:
* 1. 系统缺省的ClassLoader : sun.misc.Launcher$ExtClassLoader@f6f4d33,
* 表示 : 系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader
* 2. 系统缺省的ClassLoader : null,
* 表示 : 系统类装载器parent的parent为bootstrap,无法直接获取
*
*
*/
}
try {
Class cl = Class.forName("java.lang.Object");
classloader = cl.getClassLoader();
/**
* 结果:
* java.lang.Object's loader is null
* 表示:
* 类Object是由bootstrap装载的
*/
System.out.println("java.lang.Object's loader is " + classloader);
cl = Class.forName("com.iboray.javacore.reflection.RefTest");
classloader = cl.getClassLoader();
/**
* 结果:
* RefTest's loader is sun.misc.Launcher$AppClassLoader@54bedef2
* 表示:
* 表示用户类是由系统类装载器装载的
*/
System.out.println("RefTest's loader is " + classloader);
} catch (Exception e) {
System.out.println("Check name of the class");
}
}
/**
* 反射概述
* Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的內部信息,
* 并能直接操作任意对象的内部属性及方法。
* Java反射机制主要提供了以下功能:
* 1. 在运行时构造任意一个类的对象
* 2. 在运行时获取任意一个类所具有的成员变量和方法
* 3. 在运行时调用任意一个对象的方法(属性)
* 4. 生成动态代理
*/
public static void testReflection() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class clazz = Class.forName("com.iboray.javacore.reflection.User");
Method method = clazz.getDeclaredMethod("concat",String.class,String.class);
/**
* 实例化操作对象
*/
Object obj = clazz.newInstance();
/**
* 设置可访问权限,private方法是无法直接执行的.
* 否则会报:
* java.lang.IllegalAccessException:
* Class XX.XX.XX can not access a member of class XX.XX.XX with modifiers "private"异常
*/
method.setAccessible(true);
/**
* 执行方法
* obj : 执行哪个对象的方法
* params : 参数列表
*/
method.invoke(obj,new 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
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
动态代理
创建动态代理
Proxy 提供用于创建动态代理类和代理对象的静态方法, 它也是所有动态代理类的父类.
Proxy 提供了两个方法来创建动态代理类和动态代理实例
使用动态代理实现 AOP
AOP(Aspect Orient Program, 面向切面编程)
非模块化的横切关注点所带来的问题
这里写图片描述
横切关注点: 跨越应用程序多个模块的功能.
这里写图片描述
代码实现片段
这里写图片描述
问题
越来越多的非业务需求(日志和验证)加入后, 原有的计算器方法急剧膨胀.
属于系统范围内的需求通常需要跨越多个模块(横切关注点), 这些类似的需求包括日志, 验证, 事务等.
这里写图片描述
非模块化的横切关注点将会导致的问题
代码混乱: 每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点.
代码分散: 以日志需求为例, 只是为了满足这个单一需求, 就不得不在多个模块里多次重复相同的日志代码. 如果日志需求发生变化, 必须修改所有模块.
使用动态代理模块化横切关注点
代理设计模式的原理: 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理. 代理对象决定是否以及何时将方法调用转到原始对象上.
这里写图片描述
练习
模拟事务处理
UserDao.java
public interface UserDao {
public int div(Integer a , Integer b);
public int mul(Integer a , Integer b);
}
1
2
3
4
5
6
7
UserDaoImpl.java
public class UserDaoImpl implements UserDao {
@Override
public int div(Integer a, Integer b) {
return a / b;
}
@Override
public int mul(Integer a, Integer b) {
return a * b;
}
}
1
2
3
4
5
6
7
8
9
10
11
TransactionProxy.java
public class TransactionProxy implements InvocationHandler{
/**
* 1. 需要一个被代理对象 target
* 2. 需要一个类加载器classLoader.一般都与目标对象的采用同一个类加载器
* 3. 一般的Proxy.newProxyInstance ()返回值是一个被代理对象实现的接口的返回类型.当然也可以是其他的接口类型
* 注意:第二个参数 Class<?>[] 必须是接口列表
* 如果代理对象不需要实现其他额外的接口,则可以使用target.getClass().getInterfaces()获取被代理对象的接口列表
* 小细节:
* target如果是代理方法中的局部变量,则需要用final修饰,因为局部变量在方法执行完后是作为垃圾被回收的.但代理对象
* 指向的是这个被代理对象,所以通过final修饰局部变量的被代理对象时,可以防止意外发生.此示例没有采用局部变量的方式
* 所以不需要final修饰被代理对象.
*/
private Object target ;
public TransactionProxy(Object target) {
this.target = target;
}
/**
* 创建动态代理类
* @param target
* @return
*/
public static Object createProxy(Object target){
return Proxy.newProxyInstance(
/**
* ClassLoader:由动态代理产生的对象由哪个类加载器加载,一般都与目标对象的采用同一个类加载器
* Class<?>[] Interfaces : 由动态代理产生的对象必须实现的接口的Class数组,必须是接口列表
* InvocationHandler : 当具体调用代理对象的方法时,将产生什么行为
*/
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TransactionProxy(target));
}
/**
* 执行方法
* @param proxy 正在被返回的代理对象,一般情况不使用它,
* 否则在方法中使用会死循环.因为代理对象又会执行invoke方法.
* @param method 正在被调用的方法
* @param args 调用方法传入的参数列表
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("事务开始......参数列表为:"+ Arrays.asList(args));
Object obj = null;
try {
/**
* 调用被代理类的目标方法
* target 为构造函数传入的目标对象
*/
obj = method.invoke(target,args);
}catch (Exception e ){
System.out.println("事务回滚......");
return 0;
}
System.out.println("事务提交......结果为:"+ obj);
return obj;
}
}
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
ProxyTest.java
public static void main(String[] args){
UserDao dao = (UserDao) TransactionProxy.createProxy(new UserDaoImpl());
int c = dao.div(15,5);
System.out.println("结果为 : "+c);
c = dao.mul(2,6);
System.out.println("结果为 : "+c);
}
/**
* 结果:
*
* 事务开始......参数列表为:[15, 5]
* 事务提交......结果为:3
* 结果为 : 3
* 事务开始......参数列表为:[2, 6]
* 事务提交......结果为:12
* 结果为 : 12
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
invocationHandler的匿名实现
这里写图片描述
多线程(基础)
概念-程序、进程与多任务
程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。
进程(process)是程序的一次执行过程,是系统运行程序的基本单位。程序是静态的,进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。
多任务(multi task)在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程。
线程
线程(thread):比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。
简单来讲,线程是一个独立的执行流,是进程内部的一个独立执行单元,相当于一个子程序。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。
操作系统给每个线程分配不同的CPU时间片,在某一时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行。
这里写图片描述
创建多线程
每个Java程序启动后,虚拟机将自动创建一个主线程
可以通过以下两种方式自定义线程类:
创建 java.lang.Thread 类的子类,重写该类的 run方 法
创建 java.lang.Runnable接 口的实现类,实现接口中的 run 方法
继承 Thread 类
Thread:代表一个线程类
这里写图片描述
Thread 类
Thread类中的重要方法:
run方法:包括线程运行时执行的代码,通常在子类中重写它。
start方法:启动一个新的线程,然后虚拟机调用新线程的run方法
Thread 类代码示例:
这里写图片描述
线程执行流程
这里写图片描述
创建多线程
这里写图片描述
问题:要定义的线程类已经显式继承了一个其他的类怎么办?
答:实现Runnable接口
Runnable 接口
Runnable 接口中只有一个未实现的 run 方法,实现该接口的类必须重写该方法。
Runnable 接口与 Thread 类之间的区别
Runnable 接口必须实现 run 方法,而 Thread 类中的run 方法是一个空方法,可以不重写
Runnable 接口的实现类并不是真正的线程类,只是线程运行的目标类。要想以线程的方式执行 run 方法,必须依靠 Thread 类
Runnable 接口适合于资源的共享
这里写图片描述
线程的生命周期
线程的生命周期:
- 指线程从创建到启动,直至运行结束
- 可以通过调用 Thread 类的相关方法影响线程的运行状态
这里写图片描述
线程的运行状态
1、 新建(New)
当创建了一个Thread对象时,该对象就处于“新建状态”
没有启动,因此无法运行
2、 可执行(Runnable)
其他线程调用了处于新建状态线程的start方法,该线程对象将转换到“可执行状态”
线程拥有获得CPU控制权的机会,处在等待调度阶段。
3、 运行(Running)
处在“可执行状态”的线程对象一旦获得了 CPU 控制权,就会转换到“执行状态”
在“执行状态”下,线程状态占用 CPU 时间片段,执行run 方法中的代码
处在“执行状态”下的线程可以调用 yield 方法,该方法用于主动出让 CPU 控制权。线程对象出让控制权后回到“可执行状态”,重新等待调度。
这里写图片描述
4、 阻塞(Blocking)
线程在“执行状态”下由于受某种条件的影响会被迫出让CPU控制权,进入“阻塞状态”。
进入阻塞状态的三种情况
- 调用sleep方法
1、 public void sleep(long millis)
2、 Thread类的sleep方法用于让当前线程暂时休眠一段时间
参数 millis 的单位是毫秒
这里写图片描述
- 调用join方法
处在“执行状态”的线程如果调用了其他线程的 join 方法,将被挂起进入“阻塞状态”
目标线程执行完毕后才会解除阻塞,回到 “可执行状态”
这里写图片描述
- 执行I/O操作
线程在执行过程中如果因为访问外部资源(等待用户键盘输入、访问网络)时发生了阻塞,也会导致当前线程进入“阻塞状态”。
4.1、 解除阻塞
睡眠状态超时
调用 join 后等待其他线程执行完毕
I/O 操作执行完毕
调用阻塞线程的 interrupt 方法(线程睡眠时,调用该线程的interrupt方法会抛出InterruptedException)
这里写图片描述
5、死亡(Dead)
死亡状态(Dead):处于“执行状态”的线程一旦从run方法返回(无论是正常退出还是抛出异常),就会进入“死亡状态”。
已经“死亡”的线程不能重新运行,否则会抛出IllegalThreadStateException
可以使用 Thread 类的 isAlive 方法判断线程是否活着
这里写图片描述
线程调度
线程调度
按照特定机制为线程分配 CPU 时间片段的行为
Java程序运行时,由 Java 虚拟机负责线程的调度
线程调度的实现方式
分时调度模型:让所有线程轮流获得CPU的控制权,并且为每个线程平均分配CPU时间片段
抢占式调度模型:选择优先级相对较高的线程执行,如果所有线程的优先级相同,则随机选择一个线程执行 。Java虚拟机采用此种调度模型。
线程的优先级
Thread类提供了获取和设置线程优先级的方法
getPriority:获取当前线程的优先级
setPriority:设置当前线程的优先级
Java语言为线程类设置了10个优先级,分别使用1~10内的整数表示 ,整数值越大代表优先级越高。每个线程都有一个默认的优先级,主线程的默认优先级是5。
Thread类定义的三个常量分别代表了几个常用的优先级:
MAX_PRIORITY::代表了最高优先级10
MIN_PRIORITY::代表了最低优先级1
NORM_PRIORITY::代表了正常优先级5
setPriority 不一定起作用,在不同的操作系统、不同的 JVM 上,效果也可能不同。操作系统也不能保证设置了优先级的线程就一定会先运行或得到更多的CPU时间。
在实际使用中,不建议使用该方法
这里写图片描述
这里写图片描述
线程同步
问题:通过多线程解决售票问题。
非线程安全示例:
TicketWindow2.java
/**
* 售票窗口
*/
public class TicketWindow2 implements Runnable {
//票数
int ticketNum = 10;
private boolean isNext(){
//是否售完标识
boolean f = false;
if (ticketNum > 0) {
//每次少一张票
ticketNum--;
f = true;
}
try {
/**
* Thread-1 暂停的时候ticketNum=9, Thread2进来也执行ticketNum--后ticketNum=9
* 此时Thread-1 在1秒之后 执行后面的打印,会直接打印出
* 窗口1 剩余 8 张票...
* 窗口2 剩余 8 张票...
*/
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticketNum == 0)
f = false;
if (!f)
System.out.println(Thread.currentThread().getName() + " 票已售完 ");
else
System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 张票...");
return f;
}
@Override
public void run() {
for (;ticketNum > 0 ;){
//执行售票
isNext();
}
}
}
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
测试方法
public static void main(String[] a ){
TicketWindow2 tw2 = new TicketWindow2();
Thread t1 = new Thread(tw2,"窗口1");
Thread t2 = new Thread(tw2,"窗口2");
t2.start();
t1.start();
}
1
2
3
4
5
6
7
8
9
10
结果:
窗口1 剩余 8 张票…
窗口2 剩余 8 张票…
窗口1 剩余 6 张票…
窗口2 剩余 6 张票…
窗口1 剩余 4 张票…
窗口2 剩余 3 张票…
窗口1 剩余 2 张票…
窗口2 剩余 1 张票…
窗口1 票已售完
窗口2 票已售完
结果已反映了一切问题。多窗口售票是不安全的有问题的。那如何解决这些问题???
线程安全
多线程应用程序同时访问共享对象时,由于线程间相互抢占CPU的控制权,造成一个线程夹在另一个线程的执行过程中运行,所以可能导致错误的执行结果。
这里写图片描述
Synchronized 关键字
为了防止共享对象在并发访问时出现错误,Java中提供了“synchronized”关键字。
1、 synchronized关键字
确保共享对象在同一时刻只能被一个线程访问,这种处理机制称为“线程同步”或“线程互斥”。Java中的“线程同步”基于“对象锁”的概念
2、 使用 synchronized 关键字
修饰方法:被“synchronized”关键字修饰的方法称为”同步方法”
当一个线程访问对象的同步方法时,被访问对象就处于“锁定”状态,访问该方法的其他线程只能等待,对象中的其他同步方法也不能访问,但非同步方法则可以访问
//定义同步方法
public synchronized void methd(){
//方法实现
}
1
2
3
4
这里写图片描述
3、 使用 ”synchronized” 关键字:修饰部分代码,如果只希望同步部分代码行,可以使用“同步块”
//同步块
synchronized(obj){
//被同步的代码块
}
1
2
3
4
同步块的作用与同步方法一样,只是控制范围有所区别
修改示例
重新修改售票的例子,将isNext方法改为同步方法
//票数
int ticketNum = 100;
/**
* 同步方法
*/
private synchronized boolean isNext(){
/**
* 同步代码块
*/
// synchronized (this) {
boolean f = false;
if (ticketNum > 0) {
ticketNum--;
f = true;
}
try {
/**
* Thread-1 暂停的时候ticketNum=9, Thread2进来也执行ticketNum--后ticketNum=9
* 此时Thread-1 在1秒之后 执行后面的打印,会直接打印出
* 窗口1 剩余 8 张票...
* 窗口2 剩余 8 张票...
*/
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticketNum == 0)
f = false;
if (!f)
System.out.println(Thread.currentThread().getName() + " 票已售完 ");
else
System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 张票...");
return 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
测试:
为了实现多线程的结果 我模拟了4个售票窗口,100张票
测试方法
public static void main(String[] a ){
/**
* 这里可以看出 TicketWindow2 为线程共享参照对象,
* 也就是说,如果需要同步的话,那synchronize(参照对象)中的参照对象即为:TicketWindow2
*/
TicketWindow2 tw2 = new TicketWindow2();
Thread t1 = new Thread(tw2,"窗口1");
Thread t2 = new Thread(tw2,"窗口2");
Thread t3 = new Thread(tw2,"窗口3");
Thread t4 = new Thread(tw2,"窗口4");
t2.start();
t1.start();
t4.start();
t3.start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
结果:
窗口2 剩余 99 张票…
窗口2 剩余 98 张票…
…
窗口2 剩余 84 张票…
窗口3 剩余 83 张票…
窗口3 剩余 82 张票…
…
窗口3 剩余 20 张票…
窗口4 剩余 19 张票…
…
窗口4 剩余 1 张票…
窗口4 票已售完
窗口1 票已售完
窗口3 票已售完
窗口2 票已售完
线程通信
wait()方法:
中断方法的执行,使本线程等待,暂时让出 cpu 的使用权,并允许其他线程使用这个同步方法。
notify()方法:
唤醒由于使用这个同步方法而处于等待线程的 某一个结束等待
notifyall()方法:
唤醒所有由于使用这个同步方法而处于等待的线程结束等待
练习
通过交叉打印,练习线程通信方法
PrintWords.java
public class PrintWords implements Runnable{
private boolean a=true,b=false;
private char abc = 'a';
private synchronized void doPrint(){
if (abc <= 'z') {
String name = Thread.currentThread().getName();
System.out.println("Thread- " + name + " : " + abc);
abc++;
//等下个线程进来就可以换新上个等待的线程
notifyAll();
try {
//当前线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (abc >= 'z'){
notifyAll();
}
}
@Override
public void run() {
while (abc <= 'z') {
doPrint();
}
}
public static void main(String[] a){
Runnable p = new PrintWords();
Thread ta = new Thread(p,"a");
Thread tb = new Thread(p,"b");
ta.start();
tb.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
36
37
38
39
40
41
42
43
测试结果
Thread- a : a
Thread- b : b
Thread- a : c
Thread- b : d
…
Thread- a : k
Thread- b : l
…
Thread- b : z
---------------------
作者:小黑手600
来源:CSDN
原文:https://blog.csdn.net/johnstrive/article/details/50601534
版权声明:本文为博主原创文章,转载请附上博文链接!