JDK1.5的新特性:枚举,内省,泛型
一、枚举
什么是枚举?
枚举就是要让某个类型变量的取值只能为若干个固定值中的一个,否则编译器就会报错,枚举可以让编译器在编译的时候就可以控制源程序中的非法值,普通变量的方式在开发阶段无法实现这一目标。
首先我们用普通类来模拟一下枚举的功能。就以定义星期几的例子来说:
public class Weekday{
private Weekday(){} //使外部不能再创建对象
public abstract Weekday nextDay();
public final static Weekday SUN = new Weekday(){ //创建固定的对象供外部使用
public Weekday nextDay(){ //将nextDay方法分别由每个子类自己去完成,简化代码
return MON;
}
};
public final static Weekday MON= new Weekday(){
public Weekday nextDay(){
return SUN;
}
};
public String toString(){
return this==MON?”MON”:”SUN”;//实例只有2天,实际需要写大量判断语句
}
}
public class EnumTest{
public static void main(String[] args){
Weekday weekday = new Weekday.MON;//只能调用已经定义好的对象,不能再创建其他的对象,否则编译器编译不通过,这就是枚举的功能。
System.out.println(weekday.nextDay());
}
}
刚刚用普通方法演示了枚举的功能,下面写一个基本的枚举类来演示枚举的应用。
public class EnumTest {
public static void main(String[] args) {
WeekDay weekDay1 = WeekDay.FRI;
System.out.println(weekDay2);
System.out.println(weekDay2.ordinal());//获取对象的位置
}
public enum WeekDay //enum就代表这里是一个枚举
SUN,MON(1),TUE(1),WED,THI,FRI,SAT;//枚举也可以带有构造方法
Weekday(){“无参构造方法”};
Weekday(int x){System.out.println(“有参构造方法”)};
}
枚举使用起来很方便,但是也有一些注意点需要关注:
枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法
枚举元素必须位于枚举体中的最开始部分,枚举元素列表的最后要有分号与其他的成员隔开,否则编译器会报错。
枚举只有一个成员时,我们可以把它看做单例设计模式的实现方式。
二、反射
学习反射,我们首先来了解一个类:Class。
它是反射的基石,它是一个类名,跟我们以前学习的class是不同的概念,一定得区分开,否则会形成误导。Class它是一个类,它代表了一类事物,java中用类(class)来描述一类事物,再创建实例对象来确定某一个事物自身的属性,所以java中会有各种不同的类(class),可是谁来描述java中这些N多的类呢?我们也需要用一个类来描述java中的这些类,它就是Class。
既然Class代表java类,那么它的实例对象就像我们写过的Person类一样,对应各个类在内存中的字节码。
那么Class是如何“创建”这些字节码的实例对象的呢?
方式1:类名.Class 例:Person.class
方式2:对象.getClass() 例:new Person().getClass()
方式3:Class.forName(“类名”); 例:Class.forName(“java.util.Date”);
Java中的类可以用Class来描述,那么类中的方法、变量、构造方法等信息也可以用相对应的实例对象来描述
public class ReflectPoint{
public int x;
public int y;
public String str1 = “abc”;
public ReflectPoint(int x,int y){
This.x=x;
This.y=y;
}
}
用反射的方式来创建实例对象:
Constructor con = ReflectPoint.class.getConstructor();
ReflectPoint rp = (ReflectPoint)con.newInstance(3,4));
变量:Field
ReflectPoint pt1 = new ReflectPoint(2,3);
Field fieldX = pt1.getClass().getField(“x”);
fieldX.get(pt1);
方法:Method
Method methodCharAt = String.class.getMethod(“charAt”,int.class);
methodCharAt.invoke(str1,2); //方法执行调用(invoke)动作
构造方法:Constructor
String.class.getConstructor();//得到String类中的所有构造方法
String.class.getConstructor(int.class);//得到String类中的所有构造方法
反射加强1:用反射的方式调用别人的main方法
class TestArguments{
public static void main(String[] args){
For(String arg:args){
System.out.println(arg);
}
}
}
ReflectTest{
public static void main(String[] args){
String className = agrs[0];
Class class = Class.forName(className);
Method mainMethod = class.getMethod(“main”,String[].class);
mainMethod.invoke(null,(object)new String[]{“qwe”,”asd”,”zxc”});
}
}
}
反射加强2:数组的反射
使用反射打印数组中的内容
class ArraysTest{
public static void main(String[] args){
int[] a1 = new int[]{1,2,3};
String a2 = new String[]{‘a’,’b’,’b’};
printObj(a1);
printObj(a2);
}
static void printObj(Object obj){
Class class = obj.getClass();
if(class.isArray()){
int len = Array.getLength(obj);
for(int x=0;x<len;x++){
System.out.println(Array.get(obj),x);
}
}else{
System.out.println(obj);
}
}
}
看完了反射,可能还不知道反射是用来干什么的,反射的存在主要是为了实现框架这个功能,实际开发跟我们学习不一样,写程序的时候我们还不知道被调用的类名,所以在程序中无法直接new 某个实例对象,而要用反射来做。
举个例子看一下:
class TestArguments{
public static void main(String[] args)throws Exception{
InputStream ips = new FileInputStream (“config.properties”);
Properties props = new Properties();
prop.load(ips);
ips.close();
String className = props.getProperties(“className”);
Collection collections = (collections)Class.forName(className);
ReflectPoint pt1 = new ReflectPoint(4,3);
ReflectPoint pt2 = new ReflectPoint(2,3);
ReflectPoint pt3 = new ReflectPoint(4,3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
System.out.println(collections.size());
}
}
三、内省
了解JavaBean
JavaBean是一种特殊的类,主要用于数据信息的传递,这种java类中的方法主要是为了访问私有的字段,并且方法名符合某种字段。
如果要在两个模块之间传递信息,可以将这些信息封装到一个JavaBean中,这些信息在类中是私有的,如果要获取或者设置这些信息的时候则需要通过一些特殊的方法来去实现,JavaBean中就有通过属性名来获取属性的一些get和set方法,而不需要知道类中的方法,下面就通过一个实例来看一下:
*ReflectPoint类的代码略。
import java.beans.*;
import java.lang.*;
import java.lang.reflect.Method;
public class IntroSpector {
public static void main(String[] args) throws Exception {
ReflectPoint pt1 = new ReflectPoint(3, 4);
String propertyName = "x";
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal);
int value = 8;
setProperty(pt1, propertyName, value);
System.out.println(pt1.getX());
}
private static void setProperty(ReflectPoint pt1, String propertyName,int value) throws Exception{
//通过属性名获取这个属性的描述
PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1.getClass());
//通过属性描述获取设置方法
Method setMethod = pd.getWriteMethod();
setMethod.invoke(pt1, value);
}
private static Object getProperty(ReflectPoint pt1, String propertyName)
throws Exception {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1.getClass());
Method getMethod = pd.getReadMethod();
Object retVal = getMethod.invoke(pt1);
return retVal;
}
}
使用BeanUtils工具包我们能够更简便的操作以上的代码,但前提是我们首先要将工具包拷贝到工程中,然后再添加到Build Path中,同时我们还需要拷贝阿帕奇的日志包到工程中,再添加到Build Path中。
BeanUtils.getProperty(pt1,“x”);
BeanUtils.setProperty(pt1,“x”,”9”);//注意”9”用的是字符串,不是int基本类型
四、注解
注解相当于某种标记,加了注解相当于打了某种标记,没加,则等于没有某种标记,C编译器和开发工具都可以用反射的方式来判断你的类和其他的元素上有没有加标记,并根据读到的标记去做相对应的事,,标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
Java为我们提供了3个最基本的标记:
@SuppressWarnings 取消提示已过时
@Deprecated 已过时
@Override 提示是重写方法
上面是java为我们提供的注解,面对开发是远远不够的,接下来我们尝试自己自定义一个简单的注解。
public @interface MyAnnotation { //注解用@interface来表示
}
将自定义注解加载在某个类上
@MyAnnotation
Public class MyAnnotationTest{
}
用反射进行测试这个类上是否有注解
import java.lang.annotation.*;
@MyAnnotation
public class MyAnnotationTest {
public static void main(String[] args)throws Exception {
if(MyAnnotationTest.class.isAnnotationPresent(MyAnnotation.class))
MyAnnotation annotation=
(MyAnnotation)MyAnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation);
}
}
根据反射引出@Retention元注解的讲解,它有三种取值:
RetentionPolicy.SORSE 对应源文件
RetentionPolicy.CLASS 对应class文件
RetentionPolicy.RUNTIME 对应内存中字节码
元注解是指这个注解要在哪个时期出现,采用了枚举的方式,只有三个取值,是注解的注解
有了注解,并且将注解加载到类当中,那么下面给注解添加一些属性,不添加属性的注解是没有意义的。注解可以添加哪些类型的属性呢?
可以是8种基本类型,String类型,Class类型,枚举类型,注解类型,以及前面这些类型的数组类型,如果不是这些类型,编译器就会报错。
举例:
数组类型:int[] arrayAttr() default {1,2,3}; //属性值在注解中以方法的形式存在,default用来设置默认值
枚举类型属性:
Enumtest.TrafficLamp lamp();
@MyAnnotation(lamp=Enumtest.TrafficLamp.GREEN)
五、泛型
泛型的深入理解和高级应用:泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一致。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,使用反射得到集合,再调用其add方法即可:
ArrayList<Integer> al = new ArrayList<Integer>();
al.getClass().getMethod("add", Object.class).invoke(al, "asd");
System.out.println(al.get(0));
泛型通配符?的使用:
// 打印任意类型的泛型集合
public static void printCollection(Collection<?> collection){
System.out.println(collection.size());
for(Object obj : collection){
System.out.println(obj);
}
}
通配符?的扩展:
限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer > x = new Vector<Byter>();
限定通配符包括自己。
遍历一个泛型集合:
HashMap<String,Integer> maps = new HashMap<String, Integer>();
maps.put("zxx", 28);
maps.put("lhm", 35);
maps.put("flx", 33);
Set<Map.Entry<String,Integer>> entrySet = maps.entrySet();
for(Map.Entry<String, Integer> entry : entrySet){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
自定义泛型方法及其应用:
private static <T> T[] swap(T[] a,int i,int j){
T tmp = a[i];
a[i] = a[j];
a[j] = tmp;
return a;
}
只用引用类型才能作为自定义泛型方法的实际参数。swap(new int[]{1,3,5,4,5},3,4)是不对的
如果类的实例对象中的多处都要使用同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型飞方式进行定义,也就是类级别的泛型,语法格式:
public class BaseDao<T>{
private T filed;
public void save(T obj){}
public T findById(int id);
}
通过反射获得泛型的实际类型参数:
Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
Type[] types = applyMethod.getGenericParameterTypes();
ParameterizedType pType = (ParameterizedType)types[0];
System.out.println(pType.getRawType());
// 获得实际参数类型
System.out.println(pType.getActualTypeArguments()[0]);
public static void applyVector(Vector<Date> v1 ){}
编译器编译带类型说明的集合时会去掉“类型”信息,所以只能使用方式的反射。
六、类加载器
类加载器就是加载类的工具,java中用到一个类的时候,jvm首先要把这个类的字节码加载到内存当中来,再进行一些处理生成一些字节码,这就是类加载器的作用。
Java虚拟机中有多个类加载器,系统默认的三个主要类加载器:
BootStrap, ExtClassLoader, AppClassLoader
类加载器也是java类,但是BootStrap不是java类,它可以去加载别的类,别的类就包括了其他的加载器,同时它们还有自己的管辖范围
(爷爷)BootStrap JRE/lib/rt.jar
(儿子)ExtClassLoader RE/lib/ext/*.jar
(孙子)AppClassLoader classpath指定的所有jar或目录
为什么虚拟机加载类的时候能够派不同的加载器去加载呢?因为加载器它有自己的加载机制,如果用指定的加载器去加载一个类的时候,它不会直接去加载,而是先让父加载器去加载,父加载器找不到这个类的时候才自己动手去加载,这样就避免了生成多分字节码,减少了内存消耗。