您的位置:首页 > 编程语言 > Java开发

黑马程序员---JDK5.0新特性(下)

2013-10-24 16:05 681 查看
------- android培训、java培训、期待与您交流!
----------

1 、反射

1.1、什么是反射、以及Class类

定义:反射就是加载类,并解剖出这个类的各个组成部分,例如,一个类有:成员变量,方法,构造方法,包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象

Class类:Class类用于表示.class文件,加载类有三种方法:Class.forName(类的完整名称); 对象.getClass; 对象.class.以上三种方式,JVM都会把相应class文件装载到一个class对象中(只装载一次)。

创建类的实例:Class.newInstance方法。

数组类型的Class实例对象

Class.isArray()

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int,void…反射就是把Java类中的各种成分映射成一个个的java对象。例如,一个类有:成员变量,方法,构造方法,包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部 分映射成一个个对象。掌握反射技术的要点在于:如何从一个class中反射出各个组成部分。反射出来的对象怎么用

1.2、Constructor类

Constructor类代表某个类中的一个构造方法

得到某个类所有的构造方法:

例子:Constructor []constructors= Class.forName("java.lang.String").getConstructors();

得到某一个构造方法:

例子:       //获得方法时要用到类型

  Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);       

创建实例对象:

通常方式:String str = newString(new StringBuffer("abc"));

反射方式: String str =(String)constructor.newInstance(new StringBuffer("abc"));

class.newInstance()方法:例子:String obj =(String)Class.forName("java.lang.String").newInstance();

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

1.3、Filed类

Field类代表某个类中的一个成员变量

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段field X 代表的是x的定义,而不是具体的x变量。(注意访问权限的问题)

示例代码:

         ReflectPointpoint = new ReflectPoint(1,7);

         Fieldy =Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");

         System.out.println(y.get(point));

         //Fieldx = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");

         Fieldx =Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");

         x.setAccessible(true);

         System.out.println(x.get(point));

1.4、Method类

Method类代表某个类中的一个成员方法

得到类中的某一个方法:

例子:        Method charAt = Class.forName("java.lang.String").getMethod("charAt",int.class);

调用方法:

通常方式:System.out.println(str.charAt(1));

反射方式:System.out.println(charAt.invoke(str,1));

如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!

jdk1.4和jdk1.5的invoke方法的区别:

Jdk1.5:public Object invoke(Object obj,Object... args)

Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。

1.5、反射并执行一个类中的main方法

目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?

问题:

启动Java程序的main方法的参数是一个字符串数组,即publicstatic void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,newString[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

解决办法:

mainMethod.invoke(null,new Object[]{newString[]{"xxx"}});

mainMethod.invoke(null,(Object)newString[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

1.6、内省

·内省(Introspector) — JavaBean

Introspector 类为通过工具学习有关目标 Java Bean 支持的属性、事件和方法的知识提供了一个标准方法。

什么是JavaBean和属性的读写方法?

有get或set方法就是一个属性,另外所有类继承了Object类的getClass()方法,所以还有一个属性class。

访问JavaBean属性的两种方式:

直接调用bean的setXXX或getXXX方法。

通过内省技术访问(java.beans包提供了内省的API),内省技术访问也提供了两种方式。

通过PropertyDescriptor类操作Bean的属性

通过Introspector类获得Bean对象的 BeanInfo,然后通过 BeanInfo 来获取属性的描述器(PropertyDescriptor ),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后通过反射机制来调用这些方法。

例:

package cn.itcast.introspector;

//该类共有5个属性
public class Person {
private String name;
private String password;
private int age;
public void setAb(int a){

}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public void setAge(int age) {
this.age = age;
}

}


package cn.itcast.introspector;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import o
f70a
rg.junit.Test;

public class Demo1 {
//得到bean所有属性
public void test1() throws IntrospectionException{
BeanInfo info=Introspector.getBeanInfo(Person.class);
//去掉Object里的属性
BeanInfo info2=Introspector.getBeanInfo(Person.class,Object.class);
PropertyDescriptor[] pds=info.getPropertyDescriptors();
for(PropertyDescriptor pd:pds){
System.out.println(pd.getName());
//ab age class name	password
}
}
//操纵bean的指定属性:age
@Test
public void test2() throws  Exception{
Person p=new Person();
PropertyDescriptor pd=new PropertyDescriptor("age", Person.class);
//得到属性的写方法,为属性赋值
Method method=pd.getWriteMethod();
method.invoke(p, 45);
System.out.println(p.getAge());//45

//获取属性的值
method=pd.getReadMethod();
System.out.println(method.invoke(p, null));//45
}
//高级内容,获取当前操作的属性的类型
@Test
public void test3() throws  Exception{
Person p=new Person();
PropertyDescriptor pd=new PropertyDescriptor("age", Person.class);

//得到属性的写方法,为属性赋值
Method method=pd.getWriteMethod();
System.out.println(pd.getPropertyType());//int
method.invoke(p, 45);
System.out.println(p.getAge());//45

//获取属性的值
method=pd.getReadMethod();
System.out.println(method.invoke(p, null));//45
}
}


1.7、内省---beanutils工具包

Apache组织开发了一套用于操作JavaBean的API,这套API考虑到了很多实际开发中的应用场景,因此在实际开发中很多程序员使用这套API操作JavaBean,以简化程序代码的编写。

在工程下新建lib目录,导入commons-beanutils-1.8.3.jar 和支持包commons-logging-1.1.1.jar

选中两个包,右键build path/addto build path

Beanutils工具包的常用类:

BeanUtils

PropertyUtils

ConvertUtils.regsiter(Converter convert,Class clazz)

自定义转换器:

package cn.itcast.beanutils;

import java.util.Date;

public class Person {
private String name;
private String password;
private int age;
private Date birthday;
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public void setAge(int age) {
this.age = age;
}

}


package cn.itcast.beanutils;

import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
import org.junit.Test;

//使用beanUtils操纵bean的属性 ( 第三方)
public class Demo1 {
@Test
public void test1() throws Exception{
Person p=new Person();
BeanUtils.setProperty(p, "age", 456);
System.out.println(p.getAge());//456
}
@Test
public void test2() throws Exception{
String name="aaaa";
String age="123";
String password="pw";

Person p=new Person();
//支持8种基本类型自动转换
BeanUtils.setProperty(p, "name", name);
BeanUtils.setProperty(p, "age", age);
BeanUtils.setProperty(p, "password", password);

System.out.println(p.getName());//aaaa
System.out.println(p.getAge());//123
System.out.println(p.getPassword());//pw
}
@Test
public void test3() throws Exception{
String birthday="1983-12-1";
//为了让日期赋值到bean的birthday属性上,给beanUtils注册一个日期转换器
//ConvertUtils.register(converter, clazz);
ConvertUtils.register(new Converter(){
public Object convert(Class type, Object value) {
if(value==null) return null;
if(!(value instanceof String)){
throw new ConversionException("只支持String类型的转换");
}
String str=(String)value;
if(str.trim().equals("")) return null;
SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd",Locale.US);
try {
return df.parse(str);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}, Date.class);
Person p=new Person();
BeanUtils.setProperty(p, "birthday", birthday);
System.out.println(p.getBirthday());//pw
System.out.println("___"+BeanUtils.getProperty(p, "birthday"));
}
public void test5() throws Exception {
Map map=new HashMap();
map.put("name", "aaa");
map.put("password", "123");
map.put("brithday", "1980-09-09");
ConvertUtils.register(new DateLocaleConverter(), Date.class);
Person p=new Person();
//用map集合填充bean属性,map关键字和bean属性要一致
BeanUtils.populate(p, map);
}
}


2、泛型

2.1、泛型的作用

JDK5以前,对象保存到集合中就会失去其特性,取出时通常要程序员手工进行类型的强制转换,这样不可避免就会引发程序的一些安全性问题。例如:

ArrayList list = new ArrayList();

list.add("abc");

Integer num = (Integer) list.get(0);  //运行时会出错,但编码时发现不了

  list.add(new Random());

list.add(new ArrayList());

for(int i=0;i<list.size();i++){

         (?)list.get(i);          //此处取出来的对象应转换成什么类型

}

JDK5中的泛形允许程序员在编写集合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生问题,转变为编译时的问题,以此提高程序的可读性和稳定性(尤其在大型程序中更为突出)。

注意:泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。

泛形的基本术语,以ArrayList<E>为例:<>念着typeof

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

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

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

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

2.2、泛型的典型应用

使用迭代器迭代泛形集合中的元素。

使用增强for循环迭代泛形集合中的元素。

存取HashMap中的元素。

使用泛形时的几个常见问题:

使用泛形时,泛形类型须为引用类型,不能是基本数据类型

//使用泛型时,如果两边都使用到泛型,两边必须一样

ArrayList<String> list = newArrayList<Object>(); //bad

ArrayList<Object> list = newArrayList<String>(); //bad

ArrayList<String> list = newArrayList ();//ok

ArrayList list = newArrayList<String>();//ok

代码示例:

package cn.itcast.generic;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

public class Demo1 {
@Test
public void test1(){
List list=new ArrayList();
list.add("111");
list.add("222");
list.add("333");
//传统方式手工转换
String i=(String) list.get(0);
//下面注释代码手工转换编辑不报错,运行错误
//Integer ii=(Integer) list.get(0);
System.out.println(i);
}
@Test
public void test2(){
List <String>list=new ArrayList<String>();
list.add("111");
list.add("222");
list.add("333");
//现在不需要强制转换
String i=list.get(0);
//下面注释代码编译通不过
//Integer ii=(Integer) list.get(0);
System.out.println(i);
}


package cn.itcast.generic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.junit.Test;

public class Demo2 {
@Test
public void test1(){
List <String>list=new ArrayList<String>();
list.add("111");
list.add("222");
list.add("333");
//传统
Iterator<String> it=list.iterator();
while(it.hasNext()){
String value=it.next();
System.out.println(value);
}
//增强for
for(String s:list)
System.out.println(s);
}
@Test
public void test2(){
Map<Integer,String> map=new HashMap<Integer,String>();
map.put(1, "aaa");
map.put(2, "bbb");
map.put(3, "ccc");
//传统 keyset  entryset
Set <Map.Entry<Integer, String>> set=map.entrySet();
Iterator <Map.Entry<Integer, String>>it=set.iterator();
while(it.hasNext()){
Map.Entry<Integer, String>entry=it.next();
int key=entry.getKey();
String value=entry.getValue();
System.out.println(key+"="+value);
}
//增强for(重点)
for(Map.Entry<Integer, String> entry:map.entrySet()){
int key=entry.getKey();
String value=entry.getValue();
}
}
}


2.3、自定义泛型---泛型方法

Java程序中的普通方法、构造方法和静态方法中都可以使用泛型。方法使用泛形前,必须对泛形进行声明,语法:<T>,T可以是任意字母,但通常必须要大写。<T>通常需放在方法的返回值声明之前。例如:  public static <T> voiddoxx(T t);

在泛型中可以同时有多个类型,例如:

        publicstatic <K,V> V getValue(K key) { return map.get(key);}

2.4、自定义泛型---泛型类

如果一个类多处都要用到同一个泛型,可以把泛形定义在类上(即类级别的泛型),语法格式如下:

         publicclass GenericDao<T> {

                   privateT field1;

                   publicvoid save(T obj){}

                   publicT getId(int id){}

         }

注意,静态方法不能使用类定义的泛形,而应单独定义泛形。

经典应用:hibernate基础Dao

2.5、泛型的高级应用---通配符

定义一个方法,接收一个集合,并打印出集合中的所有元素,如下所示:

void print (Collection<String> c) {

          for(String e : c) {

                System.out.println(e);

          }



问题:该方法只能打印保存了Object对象的集合,不能打印其它集合。通配符用于解决此类问题,方法的定义可改写为如下形式:

void print (Collection<?> c)  {   //Collection<?>(发音为:"collectionof unknown")

for (Object e : c) {

         System.out.println(e);

}

}

此种形式下需要注意的是:由于print方法c参数的类型为Collection<?>,即表示一种不确定的类型,因此在方法体内不能调用与类型相关的方法,例如add()方法。

总结:使用?通配符主要用于引用对象,使用了?通配符,就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。

2.6、泛型的高级应用---有限制的通配符

限定通配符的上边界:

正确:Vector<?extendsNumber> x = new Vector<Integer>();

错误:Vector<?extendsNumber> x = new Vector<String>();

限定通配符的下边界:

正确:Vector<?superInteger> x = new Vector<Number>();

错误:Vector<?superInteger> x = new Vector<Byte>();

问题:以下代码行不行?

public void add(List<? extendsString> list){

         list.add("abc");

}

行、因为?代表String以及其子类。

3、Annotation(注解)

3.1、概述

从 JDK 5.0 开始, Java 增加了对元数据(MetaData)的支持, 也就是 Annotation(注解)。

什么是Annotation,以及注解的作用?三个基本的 Annotation:

@Override: 限定重写父类方法, 该注解只能用于方法

@Deprecated: 用于表示某个程序元素(类, 方法等)已过时

@SuppressWarnings: 抑制编译器警告.

Annotation 其实就是代码里的特殊标记, 在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。

掌握注解技术的要点:

如何定义注解

如何反射注解

3.2、自定义注解

定义新的 Annotation 类型使用 @interface 关键字

声明注解的属性:

Annotation 的属性声明方式:String name();

Annotation 属性默认值声明方式:

String name() default “xxx”;

特殊属性value:如果注解中有一个名称为value的属性,那么使用注解时,可以省略value=部分,例如:@MyAnnotation(“xxx")。

3.3、JDK的元注解

元 Annotation指修饰Annotation的Annotation。JDK中定义了如下元Annotation:

@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留的域, @Retention包含一个 RetentionPolicy 类型的成员变量, 通过这个变量指定域。

RetentionPolicy.CLASS: 编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注释. 这是默认值

RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注释. 程序可以通过反射获取该注释

RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释

@Target: 指定注解用于修饰类的哪个成员. @Target 包含了一个名为 value 的成员变量.

@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档.

@Inherited: 被它修饰的 Annotation 将具有继承性.如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解

3.4、反射注解信息

JDK 5.0 在 java.lang.reflect 包下新增了AnnotationElement 接口, 该接口代表程序中可以接受注释的程序元素

当一个 Annotation 类型被定义为运行时 Annotation 后, 该注释才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取

程序可以调用 AnnotationElement对象的如下方法来访问 Annotation 信息

4、动态代理

在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。

5、类加载器

5.1、类加载器

类加载器负责将 .class 文件(可能在磁盘上, 也可能在网络上) 加载到内存中, 并为之生成对应的 java.lang.Class 对象,当 JVM 启动时,会形成由三个类加载器组成的初始类

类加载器的结构为:



5.2、bootstarp classloader

bootstrap classloader:引导(也称为原始)类加载器,它负责加载Java的核心类。这个加载器的是非常特殊的,它实际上不是java.lang.ClassLoader的子类,而是由JVM自身实现的。可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:

URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();

for (int i = 0; i < urls.length; i++) {

         System.out.println(urls[i].toExternalForm());

}

因为JVM在启动的时候就自动加载它们,所以不需要在系统属性CLASSPATH中指定这些类库

5.3、extension classloader

extension classloader -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中的JAR包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。

5.4、system classloader

system classloader -系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。

可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。

5.5、全盘负责委托机制(父类委托机制)

classloader 加载类用的是全盘负责委托机制。

全盘负责:即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。

委托机制:先让parent(父)类加载器寻找,只有在parent找不到的时候才从自己的类路径中去寻找。

类加载还采用了cache机制:如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。

------- android培训、java培训、期待与您交流!
----------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: