1 JDK5的新特性
1.1 静态导入
在API中那些不需要new对象的类,可以在类文件的开头,import static java.lang.Math.*;这里把Math中的所有的静态方法都导入了,在类中不需要调用Math类就能直接用Math的方法了
package cn.wjd.staticimport; import static java.lang.Math.*; public class StaticImport { public static void main(String[] args) { System.out.println(min(3,5));//3 System.out.println(max(3,5));//5 } }
1.2 可变参数
在方法的形参上面,可以定义B个类型的参数,比较相加的方法的话,用了可变参数的方法后,以后只要数据类型相同,有多少个数相加都可以用这个方法,而不用像以前那个去Overload方法.
可变参数的特点: 只能定义在参数的最后位置 ...位于变量类型和变量名之间 调用可变参数的时候,编译器为该方法创建了一个数组,
package cn.wjd.staticimport; public class VariableParameter { public static void main(String[] args) { System.out.println(add(1,1,1,1,1)); System.out.println(add(2,1,1,4)); } public static int add(int x,int ...args){ int sum = x; for (int arg : args){ sum = sum + arg; } return sum; } }
上面的可变参数的代码中,args这个数组中的int类型,是不包括前面的int x的.....
1.3 for循环增强
格式 for(type 变量名 : 集合变量名){..........}
注意事项:
迭代变量必须在( )中定义!
集合变量可以是数组或实现了Iterable接口的集合类。
1.4 基本数据类型的自动拆箱与装箱
(1)Integer,Byte,Double,Float,Short,Long都属于Number类的子类,Number类本身提供了一系列的返回以上6种数据类型的操作
(2)Character属于Object的直接子类
(3)Boolean属于Object的直接子类
(4)装箱:将一个基本数据类型变成包装类 拆箱:将一个包装类变成基本数据类型
(5)包装类运用的最多的是将字符串变为基本数据类型
Integer类(字符串变int) Float类(字符串变float型)
public static int parseInt(String s)throws NumberFormatException public static float parseFloat(String s)throws NumberFormatExeption
1.5 枚举
枚举就是让某些类型的变量的取值,只能是固定的几个值,不然的话编译器就会报错,枚举可以在编译器编译的时候控制源程序中填写的非法的值
如果要用普通的类来实现枚举的功能
(1)私有的构造方法 (2)每个元素分别用一个公有的静态成员变量表示。(3)可以有若干公有方法或抽象方法
枚举就是一个特殊的类,其中定义的元素,其实就是对象,一下的代码WeekDay设置为抽象类的话,在SUN和MON这两个对象上,用内部类,直接将抽象方法进行了复写
package enumdemo; //将类抽象,里面nextDay类也要抽象了,由于SUN和MON都是对象,我们直接内部类来完成功能 public abstract class WeekDay { private WeekDay(){} public static final WeekDay SUN = new WeekDay(){ public WeekDay nextDay(){ return MON; } }; public static final WeekDay MON = new WeekDay(){ public WeekDay nextDay(){ return SUN; } }; public abstract WeekDay nextDay();/*{ if(this==SUN) return MON; else return SUN; }*/ public String toString(){ return this == SUN?"SUN":"MON"; } }
实现带有构造方法的枚举
·枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。
·枚举元素必须位于枚举体中的最开始部分,枚举元素列表的最后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器会报告错误。
·带构造方法的枚举:
构造方法必须定义成私有的
如果有多个构造方法,将根据枚举元素创建时所带的参数决定选择哪个构造方法创建对象。
枚举元素MON和MON()的效果一样,都是调用默认的构造方法。
package enumdemo; public class EnumTest2 { public static void main(String[] args) { WeekDay2 day = WeekDay2.SUN; System.out.println(day.valueOf("SUN")); System.out.println(day.values().length); } public enum WeekDay2{//枚举就是对象,调用对象的时候肯定会有构造方法的调用,调用那个构造方法就看枚举的参数了 SUN,MON(1),TUE(2),WED(3),THI,FRI,SAT; private WeekDay2(){ System.out.println("11111....."); } private WeekDay2(int i){ System.out.println("22222......."); } } }
上述代码中,所有的星期都是对象,那么对象在初始化的时候,会调用类的构造方法,观察输出语句可以发现,在初始化的时候,无参数的枚举类型调用无参的构造函数,有参的构造方法会去调用有参的构造函数进行初始化.
2 JavaBean内省
java.beans中 PropertyDescriptor类中常用的方法
构造方法 | 描述 |
public PropertyDescriptor(String propertyName, Class<?> beanClass) throws IntrospectionException | 通过调用 getFoo 和 setFoo 存取方法,为符合标准 Java 约定的属性构造一个 PropertyDescriptor。因此如果参数名为 "fred",则假定 writer 方法为 "setFred",reader 方法为 "getFred"(对于 boolean 属性则为 "isFred")。注意,属性名应该以小写字母开头,而方法名称中的首写字母将是大写的。 |
方法 | 描述 |
public Method getReadMethod() | 获得应该用于读取属性值的方法。 |
public void setReadMethod(Method readMethod)throws IntrospectionException | 设置应该用于读取属性值的方法。 |
JavaBean是一种特殊的Java类,说通俗点,它就是来访问类的各种字段,并且这种字段要符合某种命名的规定.
如果两个模块之间要传递多个信息,就可以将这些信息封装在JavaBean中,这种JavaBean的实例化对象称为值对象(Value Object,简称VO)
JavaBean中的属性是根据类中的get,set方法来确定的,去掉get,set前缀,剩余的部分就是属性的名字,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
setId()的属性名为 id
setCPU()的属性名是CPU
一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
下面的代码是对JavaBean简单的内省的操作
步骤 (1)创建一个需要被访问属性的属于那个类的对象
(2)创建PropertyDescriptor类的构造函数,在构造函数中传入属性类的属性的名字,该类的class
(3)使用PropertyDescriptor类中的方法(上面表格中的方法),进行设置和获取属性值的操作.
package cn.javabean; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; class Person{ private String name; private int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class JavaBeanDemo { public static void main(String[] args)throws Exception { Person p = new Person("wjd",25); PropertyDescriptor pd1 = new PropertyDescriptor("name",p.getClass()); Method methodName = pd1.getReadMethod(); Object retVal = methodName.invoke(p); System.out.println(retVal); Object value = 8; PropertyDescriptor pd2 = new PropertyDescriptor("age",p.getClass()); Method methodAge = pd2.getWriteMethod(); methodAge.invoke(p,value); System.out.println(p.getAge()); } }
使用BeanUtils工具类来操作JavaBean
用该工具来操作JavaBean的时候,属性的类型会自动转化,这个功能真心很贴心,比如在web开发中,用户输入的数字,都是以字符串的形式发给服务器的
3,反射的加强(数组与反射)
(3.1)对接受数组类型的成员方法进行反射
步骤:(1) 得到一个数组的元素,这个假设数组是String类型的,那个得到的元素是String类型的
(2)用这个元素去得到数组的class对象,这里用的是Class.forName这个方式的反射
(3)有了class,就能得到成员方法了,用getMethod()方法,在里面传入方法的名称,和数组类型的class,比如String[].class
(4)执行方法一定要有对象,看到method,一定要想到invoke方法,在该方法中传入被反射的对象,还有参数类型,假如类是静态的话,用null,参数类型由于传入的是数组,编译器在编译的时候,会将数组拆开,解决方法可以直接在数组前面弄了Object,这样的话,编译器就认为是单个而不是多个了
package cn.reflectdemo; import java.lang.reflect.Method; //对接受数组类型的成员方法进行反射 public class ReflectArray { public static void main(String[] args)throws Exception{ String str = args[0]; Method mainMethod = Class.forName(str).getMethod("main", String[].class); mainMethod.invoke(null, (Object)new String[]{"a","b","c"}); } } class ArrayDemo{ public static void main(String[] args){ for(String arg : args){ System.out.println(arg); } } }
(3.2)数组与Object的关系以及其反射的类型
具有相同元素类型,数组的维度也相同的数组,它们的Class实例化对象相同
基本类型的一维数组,可以当作Object来使用,不能当作Object[]来使用
非基本数组类型的一维数组既能当Object使用,又能当Object[]使用
(3.3)数组之间的得到class的关系
具有相同元素类型,和相同维数的数组都属于同一个类型
package cn.reflect; public class Array { public static void main(String[] args) { int[] a1 = new int[2]; int[] a2 = new int[3]; int[][] b1 = new int[2][3]; String[] c1 = new String[3]; System.out.println(a1.getClass() == a2.getClass());//true System.out.println(a1.getClass() == b1.getClass());//false System.out.println(a2.getClass() == c1.getClass());//false } }
(3.4)Arrays.asList()方法处理int[]和String[]的区别
这个上次再做去掉重复数组元素的时候,被迷惑过,这下终于被张孝祥老师点通了.下面先从代码上分析这个问题
package cn.reflect; import java.util.Arrays; public class ArrayasList { public static void main(String[] args) { int[] a = {1,2,3}; String[] b = {"a","b","c"}; System.out.println(a);//[I@5eab4b89 System.out.println(b);//[Ljava.lang.String;@3fec3fed System.out.println(Arrays.asList(a));//[[I@5eab4b89] System.out.println(Arrays.asList(b));//[a, b, c] } }
在JDK1.4中 Arrays.asList(Object[] obj),在JDK1.5中 Arrays.asList(T... a),现在使用的是JDK1.5,这个方法将int[] a当作了一个整体来操作,而int[]是不可以当成Object[]的数组类型来处理的,所以是这个结果了[[I@5eab4b89],而String[]既可以当Object有可以当Object[].
(3.5)Array工具类来完成对数组的反射
步骤 1,得到对象的Class对象,用Class中的isArray方法对obj对象进行判断
2,如果是数组的话,使用Array这个工具类中的getLength方法来得到数组的长度
3,使用for循环,再使用Array工具类中的get()方法来打印出数组
package cn.reflectdemo; import java.lang.reflect.Array; public class ArrayReflect { public static void main(String[] args) { int[] array = {1,2,3,4,5,6,7}; String str = "abs"; printObject(array); printObject(str); } private static void printObject(Object obj) { Class clazz = obj.getClass(); if(clazz.isArray()){ int leng = Array.getLength(obj); for(int i=0;i<leng;i++){ System.out.println(Array.get(obj, i)); } }else{ System.out.println(obj); } } }
(3.6)ArrayList_HashSet和 HashCode的区别
package cn.enhancereflect; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Properties; public class ReflectTest2 { public static void main(String[] args) throws Exception{ // = new FileInputStream("config.properties"); //用类加载器来管理配置文件,类存在的时候,会通过classpath去寻找class文件,那样我们把配置文件放在class文件的目录下,用类加载器去管理它 // InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/enhancereflect/config.properties"); InputStream ips = ReflectTest2.class.getResourceAsStream("resource/config.properties"); Properties pro = new Properties(); pro.load(ips); ips.close(); String className = pro.getProperty("className"); Collection collections = (Collection) Class.forName(className).newInstance(); //Collection collections = new HashSet(); ReflectPoint pt1 = new ReflectPoint(3,3); ReflectPoint pt2 = new ReflectPoint(5,5); ReflectPoint pt3 = new ReflectPoint(3,3); collections.add(pt1); collections.add(pt2); collections.add(pt3); collections.add(pt1); pt1.y = 9; // collections.remove(pt1);//这里假如改变了Hash中的值后,由于哈希表是分区域存储数据的 //将Y变成9这个动作,修改的数据的位置肯定是在哈希表的另外的区域,所以当值修改后,HashSet中的元素会删除不掉 //这个就是内存泄漏的一个现象 System.out.println(collections.size()); } }
(3.7)利用反射的原理开发的简单框架
我的理解,建立配置文件,利用配置文件key->value的关系,我们先用get(key)来做反射等操作,这样就不需要知道类的类型,或者后期只要在配置文件中操作类的类型
4 类加载器
(4.1) 类加载器的深入研究和它的委托代理机制
我自己的理解:我们编写java类型的文件,通过编译器变成了class文件,那个在运行这些class文件的时候,需要加载中计算机中,通过一系列的处理,这样程序就能正常的运行起来,
类加载器本身就是一个类,那个在加载的时候,一级级的往上处理的话,肯定有一个不是类的类加载器,这个类加载器我们称为爷爷,由于这样的机制存在,那么就有了java这个类加载器的关系出来了,通过学习,java有这三种类加载器,它们各司其职,加载这属于自己处理的class文件,这个机制是从下面开始往上面找,优先上面,当最后在最上面都处理不了的话,会抛出
ClassNotFoundException异常.
Java虚拟机中的所有类装载器采用了具有父子关系的树形结构进行组织。
BootStrap(爷爷),ExtClassLoader(爸爸),AppClassLoader(儿子),通过上面的理解,可以判断是BootStrap这个爷爷肯定不是java类,它在JVM启动的时候,这个爷爷就可以存在了,
(4.2) 类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
注意:
1-每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
2-有一道面试题,能不能自己写个类叫java.lang.System?
答案是不能,即使写了也不会被类加载器加载。为了不让我们写System类,类加载机制采用委托机制,这样可以保证父级类加载器优先,也就是总是使用父级类加载器能找到的类,结果就是总是使用java系统自身提供的System类,而不会使用我们自己所写的System类
5 代理
(5.1) 代理的概念和作用
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
我自己的大白话理解,代理我们在编写Proxy这个代理类的时候,要去访问某个类中的方法,举个例子,我们代理了ArrayList这个类,需要去访问ArrayList类中的各个方法,这个时候API中的Proxy类和InvocationHandler接口就要出场了,在InvocationHandler接口中只有一个而且很重要的方法,如下图当Proxy类和InvocationHandler接口登场的时候,其实说白了就是反射,在invoke这个方法中,对我们代理的类,进行反射处理,来得到类的对象,方法和参数,我们如果要正常访问ArrayList中的方法的时候,直接用它的对象去访问就OK了,用了代理,在invoke中都已经将那个需要被代理的类的参数都处理好了,就避开了正常访问的形式,直接通过代理的形式去访问了.
(5.2) 创建动态类的实例化对象以及调用它的方法
package cn.proxy; // 创建动态类的实例对象及调用其方法 import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; public class Proxy01 { public static void main(String[] args)throws Exception { //拿到动态类的对象 Class classProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class); //拿构造方法,查看API,发现Proxy中没有无参构造方法,你懂的,拿构造函数参入形参 Constructor constructor = classProxy.getConstructor(InvocationHandler.class); //有构造方法后,肯定是得到类的对象了,拿对象穿实参 Collection proxy = (Collection)constructor.newInstance(new InvocationHandler(){ public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable { return null; } }); System.out.println(proxy);//null proxy.add(1); proxy.add(2); proxy.add(3); System.out.println(proxy.size());//报告异常 } }
分析上述代码出现的结果,上面的自己的大白话解释了,在InvocationHandler这个接口中,是对被我代理的类进行反射操作,来得到类的对象,方法和属性,而在上述的代码中,我只是直接复写了InvocationHandler中的invoke方法,在方法中没做任何的处理,这个时候代理Collection是不成功了.下面对InvocationHandler中的invoke方法进行代码的处理,编写目标类,这样就能成功的代理某个类了.运行结果已经可以看出proxy这个类已经访问到了目标类中的方法,已经成功代理了,具体代码如下,下面的代码实现了对于代理类创建的一步到底,非常方便.
package cn.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; public class Proxy02 { public static void main(String[] args) { //看了下API 这个方法很牛叉,可以对代理类的创建一步到位 Collection proxy = (Collection)Proxy.newProxyInstance( Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler(){ ArrayList target = new ArrayList(); public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable { Object retVal = arg1.invoke(target,arg2); return retVal; } }); proxy.add(1); proxy.add(2); proxy.add(3); System.out.println(proxy);//[1, 2, 3] System.out.println(proxy.size());// 3 } }
(5.3) 分析InvocationHandler对象的运行原理
Object
invoke(Object proxy, Method method, Object[] args) target.add(1);这个两个相互比较下.
Object proxy 与target对应; Method method方法与 add()方法对应; 方法中的参数1与args对应. 本质上代理就是在反射,看下面的代码,以add方法为例子
$Proxy0 implements Collection
{
InvocationHandler handler;
public $Proxy0(InvocationHandler handler)
{
this.handler = handler;
}
//生成的Collection接口中的方法的运行原理
boolean add(Object obj){
handler.invoke(this,this.getClass().getMethod("add"),obj);//这里invoke中,就是利用反射,来拿到目标中的方法,参数
}
}
Tips :
调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求,比如getClass方法,所以它会返回正确的结果
6,注解
(6.1) Annotation简介
对元数据(Metadata)的支持,在J2SE 5.0中,这种元数据称为注解.
在JDK1.5之后,系统中有3个内建的Annotation类型,我们可以直接使用
@Override: 覆写的Annotation 在方法覆写的时候使用,用于保证
@Deprecated:不赞成使用的Annotation 用来声明一个不建议使用的方法,如果在程序中使用了此方法,则在编译时将出现警告
@Suppress Warnings:压制安全警告的Annotation
(6.2)为注解增加各种属性
·什么是注解的属性
一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是无锡的学生,否则,就不是。如果还想区分出是无锡哪个班的学生,这时候可以为胸牌再增加一个属性来进行区分。
加了属性的标记效果为:@MyAnnotation(color="red")。
·定义基本类型的属性和应用属性:
在注解类中增加String color(); 被添加的注解设置属性值:@MyAnnotation(color="red")。
·用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(a.color());
可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象。
·为属性指定缺省值:
String color() default "yellow";
value属性:String value() default "zxx";
如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lhm")。
·数组类型的属性
int [] arrayAttr() default {1,2,3}; 被添加的注解设置属性值:@MyAnnotation(arrayAttr={2,3,4})。
如果数组属性中只有一个元素,这时候属性值部分可以省略大括号。
·枚举类型的属性
EnumTest.TrafficLamp lamp() ; 被添加的注解设置属性值:@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)。
·注解类型的属性:
MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx"); 被添加的注解设置属性值:@MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”) )
可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:
MetaAnnotation ma = myAnnotation.annotationAttr();
System.out.println(ma.value());