类的继承
Java只支持单继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
子类继承了父类,就继承了父类的方法和属性。
在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
因而,子类通常比父类的功能更多。
在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
关于继承的规则:
子类不能继承父类中私有的(private)的成员变量和方法。
访问控制
可以对Java类中定义的属性和方法进行访问控制—-规定不同的保护等级: 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
- 38
- 39
- 40
- 41
- 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在同一包中定义时:
覆盖方法
在子类中可以根据需要对从父类中继承来的方法进行改造—覆盖方法(方法的重置、重写),在程序执行时,子类的方法将覆盖父类的方法。
覆盖方法必须和被覆盖方法具有相同的方法名称、参数列表和返回值类型。
覆盖方法不能使用比被覆盖方法更严格的访问权限。
覆盖方法举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 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()方法
这是一种“多态性”:同名的方法,用不同的对象来区分调用的是哪一个方法。
覆盖方法举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 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举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
构造方法不能继承
子类继承父类所有的成员变量和成员方法,但不继承父类的构造方法
在一个Java类中可以通过两种方式获得构造方法
使用系统默认的无参数构造方法
显式定义一个或多个构造方法
一旦显式定义了构造方法,则系统不再提供默认构造方法
调用父类构造方法
注意 注意 注意 重要的事说三遍:↓↓↓
在子类的构造方法中可使用super(参数列表)语句调用父类的构造方法
如果子类的构造方法中没有显示地调用父类构造方法,也没有使用this关键字调用重载的其它构造方法,则系统默认调用父类无参数的构造方法
如果子类构造方法中既未显式调用父类构造方法,而父类中又没有无参的构造方法,则编译出错
调用父类构造方法举例
- 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
- 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放置到任何位置。那么请看下面代码:
- 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
- 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中,子类的对象可以替代父类的对象使用
一个变量只能有一种确定的数据类型
一个引用类型变量可能指向(引用)多种不同类型的对象
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
属性是在编译时确定的,编译时e为Person类型,没有school成员变量,
因而编译错误。
虚拟方法调用(Virtual Method Invocation)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。—— 动态绑定
多态性应用举例
方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 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。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 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操作符测试一个对象的类型
对象类型转换举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
Object 类
Object类是所有Java类的根父类
如果在类的声明中未使用extends关键字指明其父类,则默认父类为Object类
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
例:
- 1
- 2
- 3
- 1
- 2
- 3
==操作符与equals方法
==操作符与equals方法的区别:
==:引用类型比较引用(是否指向同一个对象);
- 1
- 2
- 3
- 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()方法
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
可以根据需要在用户自定义类型中重写toString()方法
如String 类重写了toString()方法,返回字符串的值。
- 1
- 2
- 3
- 4
- 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)
类变量(类属性)由该类的所有实例共享
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
类属性应用举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 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标记的类方法。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
访问类的static属性
在static方法内部只能访问类的static属性,不能访问类的非static属性。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
访问static方法注意
因为不需要实例就可以访问static方法,因此static方法内部不能有this,(也不能有super )
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
类属性、类方法的设计思想
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些类属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用
静态初始化
一个类中可以使用不包含在任何方法体中的静态代码块(static block ),当类被载入时,静态代码块被执行,且只被执行一次,静态块经常用来进行类属性的初始化。
static块通常用于初始化static (类)属性
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
静态初始化举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
单子 Singleton 设计模板(单例,单态)
描述
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就想是经典的棋谱,不同的棋局,我们用不同的棋谱,免得我们自己再去思考和摸索。
所谓类的单态设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造方法的访问权限设置为private,这样,就不能用new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
单子Singleton 设计模板
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 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命令时传递给所运行的类的参数。
命令行参数用法举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 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应用举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 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修饰私有方法,构造方法,静态方法。
抽象类举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
抽象类应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
在航运公司系统中,Vehicle类需要定义两个方法分别计算运输工具的燃料效率和行驶距离。
练习:
卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法,但子类可以。
解决方案
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
接 口
描述
有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
接口(interface)是抽象方法和常量值的定义的集合。
从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
接口定义举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
接口的特点:
用 interface 来定义。
接口中的所有成员变量都默认是由public static final修饰的。
接口中的所有方法都默认是由public abstract修饰的。接口没有构造方法。
实现接口的类中必须提供接口中所有方法的具体实现内容。
多个无关的类可以实现同一个接口
一个类可以实现多个无关的接口
与继承关系类似,接口与实现类之间存在多态性
接口也可以继承另一个接口,使用extends关键字。
实现接口的类中必须提供接口中所有方法的具体实现内容。
多个无关的类可以实现同一个接口
一个类可以实现多个无关的接口
与继承关系类似,接口与实现类之间存在多态性
定义Java类的语法格式:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
接口应用举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
一个类可以实现多个无关的接口
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
与继承关系类似,接口与实现类之间存在多态性
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
注意
如果实现接口的类中没有实现接口中的全部方法,必须将此类定义为抽象类。
接口也可以继承另一个接口,使用extends关键字。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
实现类SubAdapter必须给出接口SubInterface以及父接口MyInterface中所有方法的实现。
接口和继承的区别
记住一句话:
接口是“有 没 有”的关系,继承是“是 不 是”的关系。
内部类
描述
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类
内部类和外层封装它的类之间存在逻辑上的所属关系
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。 Inner class的名字不能与包含它的类名相同;
Inner class可以使用包含它的类的静态和实例成员变量,也可以使用它所在方法的局部变量;
内部类举例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 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成员;
泛型
描述
下面是那种典型用法:
- 1
- 2
- 3
- 1
- 2
- 3
第 3 行的类型转换有些烦人。通常情况下,程序员知道一个特定的 list 里边放的是什么类型的数据。但是,这个类型转换是必须的(essential)。编译器只能保证 iterator 返回的是 Object 类型。为了保证对 Integer 类型变量 赋值的类型安全,必须进行类型转换(这是关键)。
当然,这个类型转换不仅仅带来了混乱,它还可能产生一个运行时错误 (run time error),因为程序员可能会犯错。
程序员如何才能明确表示他们的意图,把一个 list(集合) 中的内容限制 为一个特定的数据类型(这是关键)呢?这就是 generics 背后的核心思想。这是上面程 序片断的一个泛型版本:
- 1
- 2
- 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 接口的定义中摘录的片断:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 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 的版本:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形 式参数(formal value parameters)来描述它操作的参数的种类一样,一个 泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用, 实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用, 实际类型参数(actual type arguments)取代形式类型参数。
一个命名的习惯:推荐用简练的名字作为形式类型参数的名字(如果可 能,单个字符)。最好避免小写字母
泛型和子类继承
让我们测试一下我们对泛型的理解。下面的代码片断合法么?
- 1
- 2
- 1
- 2
第 1 行当然合法,但是这个问题的狡猾之处在于第 2 行。
这产生一个问题:
一个 String 的 List 是一个 Object 的 List 么?
大多数人的直觉是回答: “当然!”。
好,在看下面的几行
- 1
- 2
- 1
- 2
这里,我们使用 lo 指向 ls。我们通过 lo 来访问 ls,一个 String 的 list。 我们可以插入任意对象进去。结果是 ls 中保存的不再是 String。当我们试 图从中取出元素的时候,会得到意外的结果。
java 编译器当然会阻止这种情况的发生。第 2 行会导致一个编译错误。
总之,如果 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是某种 泛型声明,那么 G<Foo>是 G<Bar>
的子类型并不成立!!
为了处理这种情况,考虑一些更灵活的泛型类型很有用。到现在为止我 们看到的规则限制比较大。
通配符(Wildcards)
考虑写一个例程来打印一个集合(Collection)中的所有元素。下面是在老 的语言中你可能写的代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
下面是一个使用泛型的幼稚的尝试(使用了新的循环语法):
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
问题是新版本的用处比老版本小多了。老版本的代码可以使用任何类型 的 Collection 作为参数,而新版本则只能使用 Collection<Object>
,我们刚 才阐述了,它不是所有类型的 collections 的父类。
那么什么是各种 collections 的父类呢?它写作: Collection
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
现在,我们可以使用任何类型的 collection 来调用它。注意,我们仍然 可以读取 c 中的元素,其类型是 Object。这永远是安全的,因为不管 colle ction 的真实类型是什么,它包含的都是 Object。
但是将任意元素加入到其中不是类型安全的:
- 1
- 2
- 1
- 2
因为我们不知道 c 的元素类型,我们不能向其中添加对象。
add 方法有类型参数 E 作为集合的元素类型。我们传给 add 的任何参 数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我 们无法传任何东西进去。唯一的例外是 null,它是所有类型的成员。
另一方面,我们可以调用 get()方法并使用其返回值。返回值是一个未 知的类型,但是我们知道,它总是一个 Object
有限制的通配符(Bounded Wildcards)
考虑一个简单的画图程序,它可以用来画各种形状,比如矩形和圆形。 为了在程序中表示这些形状,你可以定义下面的类继承结构:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
这些类可以在一个画布(Canvas)上被画出来:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
所有的图形通常都有很多个形状。假定它们用一个 list 来表示,Canva s 里有一个方法来画出所有的形状会比较方便:
- 1
- 2
- 3
- 4
- 5
- 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 中写入是非法的。比如下面的代码是不允许的:
- 1
- 2
- 3
- 1
- 2
- 3
你应该能够指出为什么上面的代码是不允许的。因为 shapes.add 的第 二个参数类型是 ? extends Shape ——一个 Shape 未知的子类。因此我 们不知道这个类型是什么,我们不知道它是不是 Rectangle 的父类;它可 能是也可能不是一个父类,所以这里传递一个 Rectangle 不安全。
泛型方法
考虑写一个方法,它用一个 Object 的数组和一个 collection 作为参数, 完成把数组中所有 object 放入 collection 中的功能。
下面是第一次尝试:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
现在,你应该能够学会避免初学者试图使用Collection<Object>
作为集 合参数类型的错误了。或许你已经意识到使用Collection<?>
也不能工作。 回忆一下,你不能把对象放进一个未知类型的集合中去。
解决这个问题的办法是使用 generic methods
。就像类型声明,方法的 声明也可以被泛型化——就是说,带有一个或者多个类型参数。
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
我们可以使用任意集合来调用这个方法,只要其元素的类型是数组的元 素类型的父类。
- 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
- 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 类一样枚举类可以实现一个或多个接口
若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式, 则可以让每个枚举值分别来实现该方法
枚举类的方法
小练习
- 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
- 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
- 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
- 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
Man.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
BaseDao.java
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
BaseDaoImpl.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
UserDaoImpl.java
- 1
- 2
- 3
- 1
- 2
- 3
MyValidation.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
getEntityClass.java
- 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
- 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
- 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
- 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
目的:主要测试子类和父类构造方法之间的关系。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
RefTest.java
目的:测试反射
- 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
- 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
UserDaoImpl.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
TransactionProxy.java
- 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
- 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 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
- 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
- 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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 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”关键字修饰的方法称为”同步方法”
当一个线程访问对象的同步方法时,被访问对象就处于“锁定”状态,访问该方法的其他线程只能等待,对象中的其他同步方法也不能访问,但非同步方法则可以访问
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
3、 使用 ”synchronized” 关键字:修饰部分代码,如果只希望同步部分代码行,可以使用“同步块”
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
同步块的作用与同步方法一样,只是控制范围有所区别
修改示例
重新修改售票的例子,将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
- 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张票
测试方法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 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
- 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
- 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