`
manwuyuantao
  • 浏览: 7765 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

Java编程语言-基础加强

阅读更多

 

5.1 Eclipse的使用技巧

lWorkspaceproject

1,切换工作间可以在file-switch workspace

2,设置整个工作间的javacjavawindow-preferences-java-CompilerInstalled JREs

3,设置快捷键:window-preferences-General-Keys

lPerspective(透视图)view(视图)

预定义的视图集合就是透视图

l设置单个工程的javacjava

1在工程文件上右击选择preferences- java-Compiler可以设置javac也就是编译环境。

2右击选择Run As-Run Configurations可以设置java也就是运行环境版本。

注:

1,高版本的java能运行底版本的javac编译的程序

2,底版本的java不能运行高版本的javac编译的程序

l代码模板

选中需要处理的代码,右击选择surround with就可以选择已有的代码模板,同时也可以选择Configure Templates自行添加模板。

l导入工程

File-Import,如果导入的工程与自己的JRE目录不一致,可以在工程上右击选择Build Path-Configure Build Path-Libraries下删除原来的改为自己的JRE目录。

 

5.2 JDK1.5的新特性

5.2.1静态导入

静态导入:可以导入类中的静态成员,编程时就可以不加类名,而直接调用静态成员名。

5.2.2可变参数

特点:

只能出现在参数列表的最后;

...位于变量类型和变量名之间,前后有无空格都可以;

调用可变参数的方法时,编译器为该可变参数应隐式创建一个数组,在文体中以数组的形式访问可变参数。

5.2.3增强for循环

1,语法:fortype变量名集合变量名){。。。}

2,注意事项:

type前可以加修饰符如:final

迭代变量必需在()中定义;

集合变量可以是数组或实现了Iterable接口的集合类

5.2.4基本数据类型的自动装箱与拆箱

一个小知识点:多个Integer类型的对象的值如果相同并在(-128~127一个字节)之间那么对象所引用的是同一个数据即同一个内存地址,这种设计模式叫做享元模式:flyweight(很多很小的对象如果有很多相同的东西,那么就可以把他们转成一个对象,将不同的部分作为外部属性设为方法参数传入)

 

5.2.5枚举(1.5以后的新特性)

枚举就是要让某个类型的变量的取值只能是若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。

举例:

//定义一个类

package cn.jichu.day1;

 

public abstract class WeekDay {

public final static WeekDay SUN = new WeekDay(){

 

@Override

public WeekDay nextDay() {

// TODO Auto-generated method stub

return MON;

}};

public final static WeekDay MON = new WeekDay(){

 

@Override

public WeekDay nextDay() {

// TODO Auto-generated method stub

return SUN;

}};

 

//采用抽象方法就是把大量的IF else语句转换成一个个独立的内部类。

public abstract WeekDay nextDay();

 

public String toString(){

return this==SUN?"SUN":"MON";

}

 

/*

public WeekDay nextDay(){

if(this == SUN){

return MON;

}else{

return SUN;

}

}*/

}

//在其它类中调用

package cn.jichu.day1;

public class StaticImport {

public static void main(String[] args){

WeekDay day1 = WeekDay.SUN;

WeekDay day2 = day1.nextDay();

System.out.println(day2.toString());

}

}

 

枚举的基本应用:

例:public enum WeekDay{

SUN,MON,TUE,WED,THI,FRI,SAT

}

WeekDay weekDay = WeekDay.FRI

weekDay.name();

weekDay.ordinal();//返回的是FRI在类中是第几个

wekkDay.toSring();

WeekDay.valueOf("SUN");

WeekDay[] days = WeekDay.values();//返回类中所有的元素

 

总结:修饰枚举类的关键字:enum枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象。

 

实现带有构造方法的枚举:

例:public enum WeekDay{

SUN(),MON(1),TUE,WED,THI,FRI,SAT

private WeekDay(){System.out.println("first");};

private WeekDay(int day){System.out.println("second");};

}

总结:在枚举类中可以通过在元素后跟上()传入参数来指定调用哪个构造函数。

 

实现带有抽象方法的检举类:

Public enum TrafficLamp{

RED(30){

public TrafficLamp nextLamp(){

return GREEN;

}

},

GREEN(45){

public TrafficLamp nextLamp(){

return YELLOW;

}

},

YELLOW(5){

public TrafficLamp nextLamp(){

return RED;

}

};

public abstract TrafficLamp nextLamp();

private int time;

private TrafficLamp(int time){

This.time = time;

}

}

总结:RED(30){}的意思是匿名内部类重写了抽象方法,并调用了父类的构造方法。

枚举只有一个成员时,就可以作为一种单例的实现方式。

 

5.3反射

5.3.1 Class

Java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class。要注意与小写class关键字的区别。Class类描述的信息:类的名字、类的访问属性、类所属于的包名、字段名称的列表、方法名称的列表等等。

Class类代表java类,它的各个实例对象就是各java类在内存中的字节码。

得到字节码的方式:

1,类名.Class例如:System.Class

2,对象.getClass()例如:new Date().getClass()

3Class.forName("类名")例如:Class.forName("java.util.Date");

//注意,第3个方法有两种意思:

//A:如果类在之前已加载到内存中,那么直接返回类的字节码;

//B:如果类没有加载,那么此方法会先调用类加载器将类加载到内存,然后再返回此类的字节码。

 

九个预定义的Class实例对象:

8个基本数据类型和一个void((booleanbytecharshortintlongfloatdouble)和关键字void也表示为Class对象)

 

基本类型类的Class实例对象:

Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE,

Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE

 

Class字节码类中有一个方法可以判断字节码是否是基本数据类型:isPrimitive()

int.class == Integer.class -->false

int.class == Integer.TYPE -->true

 

总结:只要是在源程序中出现的类型,都有各自的Class实例对象,例如:int[]void...

 

5.3.2理解反射

反射就是把java类中的各种成分映射成相应的java类。

表示java类的Class类显然要提供一系列的方法来获取其中的变量、方法、构造函数、修饰符、包等信息,这些信息就是用相应类的实例对象来表示。相应的类就是:

FieldMethodConstructorPackage等等。

 

5.3.3 Constructor(构造函数类反射)

例:

//获取一个类中的所有构造方法对象

Constructor[] cott = Class.forName("java.lang.String").getConstructors();

//获取一个类中的单个指定的构造方法对象,需要指定构造方法参数

Constructor cott = Class.forName("java.lang.String").getConstructor(StringBuffer.class);

//cott来创建一个String类实例对象并进行指定构造函数初始化

String str = (String)cott.newInstance(new StringBuffer("abc"));//注意这里指定的构造函数参数类型对象要与创建Constructor类对象指定的构造函数参数类型一致。

 

Class.newInstance()方法:

该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

该方法内部用到了缓存机制来保存默认构造方法的实例对象。因反射比较占用资源

例:String obj = (String)Class.forName("java.lang.String").newInstance();

 

5.3.4 Field(成员变量类反射)

例:

//自定义一个类ReflectPoint

public class ReflectPoint {

private int x;

public int y;

public ReflectPoint(int x, int y) {

super();

this.x = x;

this.y = y;

}

}

//创建一个对象

ReflectPoint rp1 = new ReflectPoint(3,5);

//创建一个Field对象并指定是变量y

Field fielY = rp1.getClass().getField("y");

//获取rp1对象的y的值

int fY = fielY.getInt(rp1);

 

//创建一个Field对象并指定是变量x

//x是私有的所以需使用getDeclaredField方法

Field fielX = rp1.getClass().getDeclaredField("x");

//因为x是私有的,所以需要设置成可获取的

fielX.setAccessible(true);

//获取变量rp1对象的x的值

int fX = fielX.getInt(rp1);

 

总结:fielXfieldY不是对象身上的变量而是类的变量,通过它来取指定对象身上的变量值。如果类中的变量为私有的那么需要进行暴力设置后才能取出值。

 

5.3.5 Method(方法类反射)

例:用反射调用String类的charAt方法

String str = "abc";

//创建方法对象,并指定方法名和参数类型字节码

Method methodCharAt = str.getClass().getMethod("charAt",int.class);

//使用方法对象的invoke方法调用charAt方法并指定String对象和方法所需的参数

char c = (Character) methodCharAt.invoke(str,1);

 

总结:用类的字节码获取类方法并指定方法名和参数类型字节码;通过Method对象的invoke方法调用指定的类方法并传入希望作用于哪个类对象和方法的具体参数,如果第一个参数是null,则代表调用方法是静态方法。

 

5.3.6数组的反射

数组的类型相同并且数组的维数一致那么就是同一字节码,反之不是。

数组与Object的关系:

Int[] a1 = new int[3];

Int[][] a2 = new int[2][3];

String[] a3 = new String[3];

Object objA1 =a1;

Object objA2 =a3;

//Object[] objA3 =a1;这个不能因为int基本类型不属于Object

Object[] objA4 =a2;

Object[] objA1 =a3;

 

5.3.7反射的作用是用来实现框架功能

框架与工具类的区别:

工具类是被用户的类调用,而框架是调用用户所提供的类。

注:可以通过类加载器加载ClassPath指定目录下的普通文件。

但是这样加载的文件是只读的不能写。

1InputStream is =类名.class.getClassLoader().getResourceAsStream("包名/包名/文件名");//这里的目录是从工程的根目录开始指定,但顶头包前不用加/

2InputStream is =类名.class.getResourceAsStream("文件名");//这种方法不用写包名的原因是系统会在调用这个方法的类名的包下直接找这个文件

注:如果在文件名前加/那么代表的是绝对路径,就必需指定从工程的根目录开始指定,顶头包前加/

 

5.4 ArrayListHashSet的比较及HashCode分析

ArrayList集合的元素有序并可以重复。

HashSet集合的元素无序并不可以重复。

HashCode的作用:

当从HashSet集合中查找某个对象时,java系统首先会调用对象的HashCode方法获取该对象的哈希码,然后根据哈希码找到相应的存储区域最后取出该存储区域的所有元素与该对象进行equals方法比较,这样不用遍历集合就可以得到结论。

注:在对HashSet集合添加元素后不要再对添加元素的相关变量等(指的是HashCode方法中所用到的相关变量等)进行修改,否则可能会造成内存泄露。

 

5.5内省(Introspector)和了解JavaBean

内省主要是用来操作JavaBean类。

JavaBean是一个特殊的java类,主要用于传递数据信息,这种java类中的方法主要用来访问私有的字段,且方法名符合某种命名规则。

方法命名规则举例:

int getAge(){....}

void setAge(int age){....}

总结:只要类中的方法有getset打头该类就可以看作是JavaBean类。

JavaBean的属性是根据方法名得来的:去掉setget后,如果第二个字母是小写,则把第一个字母变成小写-->age

 

5.5.1利用内省对JavaBean简单操作

例:

Person p = new Person();

String propertyName = "age";

//创建属性描述类对象,并输入属性名和属性所属类的字节码

PropertyDescriptor pd = new PropertyDescriptor(propertyName,p.getClass());

//创建方法类对象,获取与属性相对应的getset方法

Method methodGetAge = pd.getReadMethod();

Method methodSetAge = pd.getWriteMethod();

//调用这些方法,并输入属性所属的类对象和相关参数

methodGetAge.invoke(p);

methodSetAge.invoke(p,36);

 

5.5.2利用内省对JavaBean复杂操作

Person p = new Person();

String propertyName = "age";

BeanInfo beanInfo = Introspector.getBeanInfo(p.getClass());

PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

for(PropertyDescriptor pd : pds){

If(pd.getName().equals(propertyName)){

Method methodGetAge = pd.getReadMethod();

Method methodSetAge = pd.getWriteMethod();

methodGetAge.invoke(p);

methodSetAge.invoke(p,36);

break;

}

}

5.6 BeanUtils工具包的使用

将工具包复制到工程目录下后,右击将导入到Build path中。BeanUtils工具包中的方法基本都是静态方法,getPropertysetProperty方法中的值都是String类型。

PropertyUtils工具包里面的getPropertysetProperty方法参数值的类型不变还是原来的。

 

5.7注解(JDK1.5新特性)

5.7.1注解格式:@注解类型

@Override:方法覆盖,表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。

@Deprecated:表示过时。

@SuppressWarnings:压制警告,

总结:

注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

 

5.7.2注解的应用结构

1,注解类

@interface A{.....}

2,应用了“注解类”的类

@A

Clas B{。。。。。}

3,对应用了注解类的类进行反射操作的类

Class C{

B.class.isAnnotationPresent(A.class);

A a = (A)B.class.getAnnotation(A.class);

}

@Retention(RetentionPolicy value):元注解,指示注释类型的注释要保留多久。

其三种取值:

1RetentionPolicy.SOURCEjava源文件

2RetentionPolicy.CLASSclass文件

3RetentionPolicy.RUNTIME:内存中的字节码

@Target(ElementType[] value):元注解,注解的注解类所能运用到哪些成分上。

取值:

ElementType.ANNOTATION_TYPE

注释类型声明

ElementType.CONSTRUCTOR

构造方法声明

ElementType.FIELD

字段声明(包括枚举常量)

ElementType.LOCAL_VARIABLE

局部变量声明

ElementType.METHOD

方法声明

ElementType.PACKAGE

包声明

ElementType.PARAMETER

参数声明

ElementType.TYPE

类、接口(包括注释类型)或枚举声明

 

5.7.3为注解添加基本属性

1,定义和应用属性:

在注解类中添加String color();方法

注解时应用属性:@MyAnnotation(color="red")

2,用反射方式获取注解对应的实例对象后,通过该对象调用属性对应的方法:

MyAnnotation a = (MyAnnotation)AnnotationTest.calss.getAnnotation(MyAnnotation.class);

System.out.println(a.color());

3,为属性指定缺省值:

在注解类中:String color()default "yellow";

4value属性:

String value() default "zzz";

如果注解中有一个名称为value的属性,且你只想设置value属性的值(即其它属性都使用默认值或注解类中只有value属性),那么可以省略value=部分,例:@MyAnnotation("cccc")

 

5.7.4为注解添加高级属性

1,数组类型的属性:

Int[] arrayAttr default{1,2,3};

@MyAnnotation(arrayAttr={2,3,4})

如果数组属性中只有一个元素,这时候属性值部分可以省略大括号

2,枚举类型的属性:

EnumTest.TrafficLamp lamp();

@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)

3,注解类型的属性:

MetaAnnotation annotationAttr() default @MetaAnnotation("xxx");

@MyAnnotation(annotationAttr=@MetaAnnotation("ttttt"))

可以认为上面这个@MyAnnotationMyAnnotation类的一个实例对象,同样的道理也可以认为上面这个@MetaAnnotationMetaAnnotation类的一个实例对象,调用代码如下:

MetaAnnotation ma = myAnnotation.annotationAttr();

System.out.println(ma.value());

 

注:注解的详细语法可以查看java语言规范,即查看javaLanguageSpecification

注解类属性支持的类型8大基本数据类型、StringClassenum(枚举)annotation(注解)array(前面那些类型的类型数组,否则将会报错)

 

5.8泛型(Generic)(JDK1.5新特性)

5.8.1泛型的定义与理解

泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住在程序中的非法输入,编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一致。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如:用反射得到集合的实例对象,再调用其add方法即可。

 

ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:

整个称为ArrayList<E>泛型类型

ArrayList<E>中的E称为类型变量或类型参数

整个ArrayList<Integer>称为参数化的类型

ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数

ArrayList<Integer>中的<>念着typeof

ArrayList称为原始类型

参数化类型与原始类型的兼容性:

参数化类型可以引用一个原始类型的对象,编译报告警告,例如,
Collection<String> c = new Vector();//
可不可以,不就是编译器一句话的事吗?

原始类型可以引用一个参数化类型的对象,编译报告警告,例如,
Collection c = new Vector<String>();//
原来的方法接受一个集合参数,新的类型也要能传进去

参数化类型不考虑类型参数的继承关系:

Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯

Vector<Object> v = new Vector<String>(); //也错误!

编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:

Vector<Integer> vectorList[] = new Vector<Integer>[10];

 

泛型中的?通配符:

总结:使用?通配符可以引用其它各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

 

<E>中的E称为类型变量或类型参数,但这个类型变量只能是对象类型不能是基本数据类型。

 

5.8.2类型参数的类型推断

编译器判断泛型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。

根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:

当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:

swap(new String[3],3,4)static <E> void swap(E[] a, int i, int j)

当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:

add(3,5) static <T> T add(T a, T b)

当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:

fill(new Integer[3],3.5f) static <T> void fill(T[] a, T v)

当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:

int x =add(3,3.5f)static <T> T add(T a, T b)

参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:

copy(new Integer[5],new String[5]) static <T> void copy(T[] a,T[]b);

copy(new Vector<String>(), new Integer[5]) static <T> void copy(Collection<T> a , T[] b);

 

5.8.3通过反射获得泛型的参数化类型

示例代码:

Class GenericalReflection {

private Vector<Date> dates = new Vector<Date>();

public void setDates(Vector<Date> dates) {

this.dates = dates;

}

public static void main(String[] args) {

Method methodApply = GenericalReflection.class.getDeclaredMethod("setDates", Vector.class);

ParameterizedType pType = (ParameterizedType)

(methodApply .getGenericParameterTypes())[0];

System.out.println("setDates("

+ ((Class) pType.getRawType()).getName() + "<"

+ ((Class) (pType.getActualTypeArguments()[0])).getName()

+ ">)" );

}

}

 

5.9类加载器

5.9.1理解类加载器

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个加载器负责加载特定位置的类:BootStrapExtClassLoaderAppClassLoader

类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这就是BootStrap

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级加载器对象或者默认采用系统类加载器为其父级类加载。

 

 

5.9.2类加载器的委托机制

Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

首先当前线程的类加载器去加载线程中的第一个类。

如果类A中引用了类BJava虚拟机将使用加载类A的类装载器来加载类B

还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

 

每个类加载器加载类时,会先委托给其上级类加载器。

当所有上级类加载器没有加载到类,回到发起者类加载器,如果还加载不了,则抛ClassNotFoundException,不会再去找发起者类加载器的下级类加载器。

 

5.9.3编写自己的类加载器

原理分析:

1,必须继承抽象类ClassLoader

2loaderClass方法:是调用父类的此方法来加载类,所以不用复写它(在用自己写的类加载器时则调用这个方法)。

3findClass方法:自己去查找(复写此方法)。

4deFindClass方法:将字节数组转成Class的实例(在findClass方法中调用此方法)。

 

5.9.4一个类加载器的高级问题分析

JavaWeb工程中,使用的tomcat服务器,web工程所加载的类是tomcat自己编写的类加载器来加载的,如:WebAppClassloader

如果把自己创建的MyServlet.class文件打jar包后放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。

servlet.jar也放到ext目录中,问题解决了。

5.10代理

5.10.1理解代理

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。

如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

 

5.10.2 AOPAspect oriented program

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

安全事务日志

StudentService------|----------|------------|-------------

CourseService------|----------|------------|-------------

MiscService------|----------|------------|-------------

用具体的程序代码描述交叉业务:

method1method2method3

{{{

------------------------------------------------------切面

..............

------------------------------------------------------切面

}}}

交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:

------------------------------------------------------切面

func1func2func3

{{{

..............

}}}

------------------------------------------------------切面

使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

 

5.10.3动态代理技术

JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库(CGLIB不是官方的)。

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

1.在调用目标方法之前

2.在调用目标方法之后

3.在调用目标方法前后

4.在处理目标方法异常的catch块中

 

创建动态代理类所用的类Proxy

Proxy类中的方法都是静态方法:

static InvocationHandlergetInvocationHandler(Object proxy)

返回指定代理实例的调用处理程序。

static Class<?>getProxyClass(ClassLoader loader, Class<?>... interfaces)

返回代理类的 java.lang.Class对象,并向其提供类加载器和接口数组。

static booleanisProxyClass(Class<?> cl)

当且仅当指定的类通过 getProxyClass方法或 newProxyInstance方法动态生成为代理类时,返回 true

static ObjectnewProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

方法中用到的InvocationHandler是一个接口,该接口中只有一个方法:

Object invoke(Object proxy, Method method, Object[] args)

在代理实例上处理方法调用并返回结果。

 

初步了解创建代理类并且实例化:

Class clazz = Proxy.getProxyClass(ConllectionProxy.class.getClassLoader(),Collection.class);

System.out.println(clazz.getName());

Constructor constructor = clazz.getConstructor(InvocationHandler.class);

Collection proxy = (Collection)constructor.newInstance(new InvocationHandler(){

 

//这里的三个参数正好就是proxy.add(obj)中的三部分

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

return null;

}

});

//proxy.add("abc");

 

//下面使用newProsyInstance方法直接创建Collenction的代理类对象

Collection proxy2 = (Collection)Proxy.newProxyInstance(

Collection.class.getClassLoader(),

new Class[]{Collection.class},

new InvocationHandler() {

//定义目标,也就是代理类代理的是哪个类

ArrayList target = new ArrayList();

Long startTime;

Long endTime;

@Override

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

//添加相关的系统功能

startTime = System.currentTimeMillis();

//执行原类中的方法

Object reValue = method.invoke(target, args);

//添加相关的系统功能

endTime = System.currentTimeMillis();

System.out.println(endTime - startTime );

//返回原类方法的返回值

return reValue;

}

});

proxy2.add("abc");

proxy2.add("bca");

System.out.println(proxy2.size());

 

总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?

三个方面:

生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;

产生的类字节码必须有个一个关联的类加载器对象;

生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。

 

5.10.4动态生成代理类的内部代码原理简单分析

动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个接受InvocationHandler参数的构造方法。

$Proxy0 implements Collection

{

InvocationHandler handler;

public $Proxy0(InvocationHandler handler)

{

this.handler = handler;

}

}

 

代理类中的构造方法接受一个InvocationHandler对象,其实就是在为实现Collection接口中的所有方法用准备,方法代码如下:

int size()

{

return handler.invoke(this,this.getClass().getMethod("size"),null);

}

void clear(){

handler.invoke(this,this.getClass().getMethod("clear"),null);

}

boolean add(Object obj){

handler.invoke(this,this.getClass().getMethod("add"),obj);

}

总结:调用代理动态类的方法其实是它转发给了InvocationHandler对象中的invoke方法来执行(其实转发给invoke方法就是在调用目标类中相应的方法额外的在invoke方法中添加其它的系统功能即自己想要添加的代码),但是有一点需要注意:代理对象从Object类继承的hashCode, equals, toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于代理对象从Object类继承的其他方法,则不转发调用请求。

 

 

5.11线程

5.11.1创建线程的两种方式

Thread子类覆盖的run方法中编写运行代码

在传递给Thread对象的Runnable对象的run方法中编写代码

总结:查看Thread类的run()方法的源代码,可以看到其实这两种方式都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法;如果Thread类的run方法被覆盖了并传入了Runnable对象,那么会调用Thread子类的run方法执行。

 

5.11.2定时器类

Timer

TimerTask

 

经验:要用到共同数据(包括同步锁)或共同算法的若干个方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性。

 

开源工具:quartz工作调度框架

Quartz是一个开源的作业调度框架,它完全由Java写成,并设计用于J2SEJ2EE应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB作业预构建,JavaMail及其它,支持cron-like表达式等等。

5.11.3多线程访问共享对象和数据的方式

如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:

1,将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

2,将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。

总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。

 

5.11.4 ThreadLocal类:线程范围内的共享变量

作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。

每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

ThreadLocal的应用场景:

订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。

银行转账包含一系列操作:把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。

例如Strut2ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。

总结:一个ThreadLocal对象代表一个变量,故其中里只能放一个数据,有两个变量都要线程范围内部共享,则要定义两个ThreadLocal对象。如果有太多的变量需要线程范围共享,那私可以先定义一个对象来装这些变量,然后在ThreadLocal中存储这一个对象。

扩展:线程范围内多个共享变量的优化处理方式:利用仿照单利模式进行设计优化。

举例:

publicclass ThreadLocalTest {

publicstaticvoid main(String[] args) {

for(int i=0;i<3;i++){//创建三个线程

new Thread(new Runnable(){

@Override

publicvoid run() {

int data =new Random().nextInt();

System.out.println(Thread.currentThread().getName()+"共享数据是:"+data);

MyThreadScopeData.getThreadInstance().setAge(data);

MyThreadScopeData.getThreadInstance().setName("name"+data);

new A().get();

new B().get();

}

}).start();

}

 

}

 

}

 

//线程中的A模块

class A{

publicvoid get(){

MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();

System.out.println(Thread.currentThread().getName()+"中的A模块取出的共享数据是:"+myData.getName()+myData.getAge());

}

}

 

//线程中的B模块

class B{

publicvoid get(){

MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();

System.out.println(Thread.currentThread().getName()+"中的B模块取出的共享数据是:"+myData.getName()+myData.getAge());

}

}

 

//线程范围内多个共享变量进行封装成类中的成员,

//并利用仿照单利模式进行设计优化,将ThreadLocal对象封装到此类中。

class MyThreadScopeData{

private MyThreadScopeData(){}

privatestatic ThreadLocal<MyThreadScopeData>map =new ThreadLocal<MyThreadScopeData>();

publicstatic MyThreadScopeData getThreadInstance(){

MyThreadScopeData instance = map.get();

if(instance ==null){

instance = new MyThreadScopeData();

map.set(instance);

}

return instance;

}

private Stringname;

privateintage;

public String getName() {

returnname;

}

publicvoid setName(String name) {

this.name = name;

}

publicint getAge() {

returnage;

}

publicvoid setAge(int age) {

this.age = age;

}

 

}

 

 

5.11.5 Java5中的线程并发库

java.util.concurrent包及子包

Java.util.concurrent.atomic中的类:

AtomicBoolean

AtomicInteger

AtomicIntegerArray

AtomicIntegerFieldUpdater:操作哪类的哪个对象身上的整数变量。

此类中的方法:

static <U> AtomicIntegerFieldUpdater<U>newUpdater(Class<U> tclass, String fieldName):返回本类的一个实例。

参数说明:哪个类中的哪个字段

addAndGet(T obj, int delta):对变量进行增加多少(delta)。

参数说明:对哪个对象身上的变量进行增加。

 

AtomicLong

AtomicLongArray

AtomicLongFieldUpdater

AtomicMarkableReference

AtomicReference

AtomicReferenceArray

AtomicReferenceFieldUpdater

AtomicStampedReference

总结:此包中的类其实是对基本数据,数组和对类中的基本数据等进行操作,这些类new出来的对象都是同步的。

 

5.11.6 Java5中的线程池:java.util.concurrent.Executors

Executors类的应用

创建固定大小的线程池static ExecutorServicenewFixedThreadPool(int nThreads)

创建缓存线程池static ExecutorServicenewCachedThreadPool()

根据具体需求自动创建线程数。

创建单一线程池static ExecutorServicenewSingleThreadExecutor()

可以实现单个线程死掉后重新创建线程。

给线程池中的线程添加执行代码:execute(Runnable task)

关闭线程池

shutdown():任务执行完毕后关闭。

shutdownNow():不管任务有没有执行完毕都将立即关闭。

线程池定时器

调用ScheduledExecutorServiceschedule方法,返回的ScheduleFuture对象可以取消任务。

支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。

 

接口Callable&Futurejava.util.concurrent

Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的。

Callable要采用ExecutorSevicesubmit方法提交,返回的future对象可以取消任务。

CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象的返回值。好比我同时种了几块地的麦子,然后就等待收割。收割时,则是那块先成熟了,则先去收割哪块麦子。

 

 

5.11.7 Lock&Condition&ReadWriteLock实现线程同步通信

java.util.concurrent.locks

Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。

 

读写锁ReadWriteLock分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

扩展:读写锁ReadWriteLock用于缓存设计上效果更加,代码举例(非具体实现只是抽象举例)如下:

class CachedData {

Object data;

volatile boolean cacheValid;

ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

 

void processCachedData() {

rwl.readLock().lock();

if (!cacheValid) {

// Must release read lock before acquiring write lock

rwl.readLock().unlock();

rwl.writeLock().lock();

// Recheck state because another thread might have acquired

//write lock and changed state before we did.

if (!cacheValid) {

data = ...

cacheValid = true;

}

// Downgrade by acquiring read lock before releasing write lock

rwl.readLock().lock();

rwl.writeLock().unlock(); // Unlock write, still hold read

}

 

use(data);

rwl.readLock().unlock();

}

}

 

Condition

Conditionawaitsignal方法类似在传统线程技术中的Object.waitObject.notify的功能。在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

一个锁内部可以有多个Condition,即有多路等待和通知。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)

5.11.8 Semaphore实现信号灯

Semaphore可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。

Semaphore实现的功能就类似厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。

另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。

单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

此类中的方法:

void acquire()

从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。

void acquire(int permits)

从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。

void acquireUninterruptibly()

从此信号量中获取许可,在有可用的许可前将其阻塞。

void acquireUninterruptibly(int permits)

从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。

int availablePermits()

返回此信号量中当前可用的许可数。

int drainPermits()

获取并返回立即可用的所有许可。

protected Collection<Thread> getQueuedThreads()

返回一个 collection,包含可能等待获取的线程。

int getQueueLength()

返回正在等待获取的线程的估计数目。

boolean hasQueuedThreads()

查询是否有线程正在等待获取。

boolean isFair()

如果此信号量的公平设置为 true,则返回 true

protected void reducePermits(int reduction)

根据指定的缩减量减小可用许可的数目。

void release()

释放一个许可,将其返回给信号量。

void release(int permits)

释放给定数目的许可,将其返回到信号量。

String toString()

返回标识此信号量的字符串,以及信号量的状态。

booleantryAcquire()

仅在调用时此信号量存在一个可用许可,才从信号量获取许可。

boolean tryAcquire(int permits)

仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。

boolean tryAcquire(int permits, long timeout, TimeUnit unit)

如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。

boolean tryAcquire(long timeout, TimeUnit unit)

如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可

 

 

 

5.11.9 CyclicBarrier:循环使用路障

表示大家彼此等待,大家集合好后才开始出发,分散活动后又在指定地点集合碰面,这就好比整个公司的人员利用周末时间集体郊游一样,先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐,…。

此类的方法:

int await()

在所有参与者都已经在此 barrier 上调用 await方法之前,将一直等待。

int await(long timeout, TimeUnit unit)

在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。

int getNumberWaiting()

返回当前在屏障处等待的参与者数目。

int getParties()

返回要求启动此 barrier 的参与者数目。

boolean isBroken()

查询此屏障是否处于损坏状态。

void reset()

将屏障重置为其初始状态。

 

5.11.10 CountDownLatch:倒计时计数器

犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。

可以实现一个人(也可以是多个人)等待其他所有人都来通知他,这犹如一个计划需要多个领导都签字后才能继续向下实施。还可以实现一个人通知多个人的效果,类似裁判一声口令,运动员同时开始奔跑。用这个功能做百米赛跑的游戏程序不错哦!

此类中的方法:

void await()

使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。

boolean await(long timeout, TimeUnit unit)

使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。

void countDown()

递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

long getCount()

返回当前计数。

String toString()

返回标识此锁存器及其状态的字符串。

 

 

5.11.11 Exchanger:两个线程之间的数据交换

用于实现两个线程之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。

此类中的方法:

V exchange(V x)

等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。

V exchange(V x, long timeout, TimeUnit unit)

等待另一个线程到达此交换点(除非当前线程被中断,或者超出了指定的等待时间),然后将给定的对象传送给该线程,同时接收该线程的对象。

 

 

5.11.12 SynchronizedQueue同步阻塞队列

一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。队列的是尝试添加到队列中的首个已排队插入线程的元素;如果没有这样的已排队线程,则没有可用于移除的元素并且 poll()将会返回 null。对于其他 Collection方法(例如 contains),SynchronousQueue作为一个空 collection。此队列不允许 null元素。

同步队列类似于 CSP Ada 中使用的 rendezvous信道。它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。

对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。但是,使用公平设置为 true所构造的队列可保证线程以 FIFO 的顺序进行访问。

方法的使用与ArrayBlockingQueue相同,只是些队列不能定义大小,其实它的原理就是在有一个线程存入元素时必须要有一个线程要读取元素才可以放入,其实队列始终没有存入元素。还可在构造时传入公平排序策略,意思就是让线程按排队的先后进行取。

 

 

5.11.13 ArrayBlockingQueue可阻塞的队列

阻塞队列与Semaphore有些相似,但也不同,阻塞队列是一方存放数据,另一方释放数据,Semaphore通常则是由同一方设置和释放信号量。

ArrayBlockingQueue

只有put方法和take方法才具有阻塞功能

 

 

抛出异常

特殊值

阻塞

超时

插入

add(e)

offer(e)

put(e)

offer(e, time, unit)

移除

remove()

poll()

take()

poll(time, unit)

检查

element()

peek()

不可用

不可用




 

 

可以用两个具有1个空间的队列来实现同步通知的功能。

此类中的方法:

booleanadd(E e)

将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException

void clear()

自动移除此队列中的所有元素。

boolean contains(Object o)

如果此队列包含指定的元素,则返回 true

int drainTo(Collection<? super E> c)

移除此队列中所有可用的元素,并将它们添加到给定 collection中。

int drainTo(Collection<? super E> c, int maxElements)

最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection中。

Iterator<E> iterator()

返回在此队列中的元素上按适当顺序进行迭代的迭代器。

booleanoffer(E e)

将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false

boolean offer(E e, long timeout, TimeUnit unit)

将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。

E peek()

获取但不移除此队列的头;如果此队列为空,则返回 null

E poll()

获取并移除此队列的头,如果此队列为空,则返回 null

E poll(long timeout, TimeUnit unit)

获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。

void put(E e)

将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。

int remainingCapacity()

返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的其他元素数量。

boolean remove(Object o)

从此队列中移除指定元素的单个实例(如果存在)。

int size()

返回此队列中元素的数量。

E take()

获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。

Object[] toArray()

返回一个按适当顺序包含此队列中所有元素的数组。

<T> T[]

toArray(T[] a)

返回一个按适当顺序包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。

String toString()

返回此 collection 的字符串表示形式。

 

 

 

3.11.14同步集合(Java1.5

Java5中提供了如下一些同步集合类:

通过看java.util.concurrent包下的介绍可以知道有哪些并发集合

ConcurrentHashMap:并发的,是线程安全的HashMap

CopyOnWriteArrayList:并发的,线程同步安全的,可以在迭代时进行元素修改。

CopyOnWriteArraySet:并发的,线程同步安全的,可以在迭代时进行元素修改。

ConcurrentSkipListMap:并发的,有序的(映射可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的Comparator进行排序,具体取决于使用的构造方法。)

ConcurrentSkipListSet:并发的,有序的(映射可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的Comparator进行排序,具体取决于使用的构造方法。)

 

。。。

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics