可变参数:(1.5)
只能出现在参数列表的最后;
...位于变量类型和变量名之间,前后有无空格都可以;
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
OverLoad-重载 与 Override-重写(经典面试题)
public static int add(int x,int y,int ... args){ int sum=x; for(int i=0;i<args.length;i++) { sum+=args[i]; } return sum; }
增强for循环
语法
for(type 变量名 : 集合变量名){...}
注意:
迭代变量必须在()中定义;
集合变量可以是数组或实现了Iterable接口的集合类;
public static int add(int x,int y,int ... args){ int sum=x; for(int arg:args){ sum+=arg; } return sum; }
基本数据类型的自动拆箱与装箱(1.5)
package cn.itcsat.day1; public class AutoBox { public static void main(String[] args){ Integer iObj=3; System.out.println(iObj+12);//不需要转换 String s1=new String("abc"); String s2=new String("abc");//两个String对象不是同一个 Integer i1=137; Integer i2=137; //享元模式 flyweight //自动拆箱、装箱,如果在一个字节之内(-128~127),则对象是同一个※ System.out.println(i1==i2); Integer i3=Integer.valueOf(3);//将基本类型转换为Integer Integer i4=Integer.valueOf(3);//这里i3,i4对也是同一个 } }
枚举(1.5)
package cn.itcsat.day1; public class EnumTest { /** * @param args */ public static void main(String[] args) { //普通方法模拟枚举 MyWeekDay weekDay=MyWeekDay.SUN; System.out.println(weekDay.nextDay()); WeekDay weekDay2=WeekDay.FRI; System.out.println(weekDay2);//枚举类提供了toString方法 System.out.println(weekDay2.name());//FRI,返回此枚举常量的名称,在其枚举声明中对其进行声明 System.out.println(weekDay2.ordinal());//5,返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。 System.out.println(weekDay2.getClass());//class cn.itcsat.day1.EnumTest$WeekDay //枚举类中的静态方法 System.out.println(WeekDay.valueOf("SUN"));//返回带指定名称的指定枚举类型的枚举常量。 System.out.println(WeekDay.values().length);//7 } public enum WeekDay{//枚举使用enum关键字 SUN(1),MON(2),TUE(3),WED(4),THI(5),FRI(6),SAT(7); private WeekDay(){//枚举构造函数只能使用private System.out.println("first"); } private WeekDay(int day){ System.out.println("second"); } } public enum TrafficLamp{ RED(30){ @Override public TrafficLamp nextLamp() { return GREEN; } }, GREEN(45){ @Override public TrafficLamp nextLamp() { return YELLOW; } }, YELLOW(5){ @Override public TrafficLamp nextLamp() { return RED; } }; public abstract TrafficLamp nextLamp(); private int time; private TrafficLamp(int time){this.time=time;} } }
package cn.itcsat.day1; public abstract class MyWeekDay { private MyWeekDay() {}; public abstract MyWeekDay nextDay();//抽象方法 /* * public WeekDay nextDay(){ * if(this==SUN){ * return MON; * } * else{ * return SUN; * } * } */ public final static MyWeekDay SUN = new MyWeekDay() { // 匿名内部类 // 定义了一个WeekDay的子类,覆写nextDay方法并实例化对象 //这样实现nextDay方法,避免了在同一个nextDay方法中使用大量的if-else语句 //采用抽象方法定义nextDay就将大量的if-else语句转移成了一个独立的类※ @Override public MyWeekDay nextDay() { return MON; } }; public final static MyWeekDay MON = new MyWeekDay() { @Override public MyWeekDay nextDay() { return SUN; } }; public String toString() { return this == SUN ? "Sunday" : "Monday"; } }
反射(1.2)
反射的基石——>Class类
Person p1=new Person();
Person p2=new Person();
Date
Math
获取字节码的方式:
1.类名.class,如下:
Class cls1=Date.class;//获得字节码1;
Class cls2=Person.class;//获得字节码2;
2.getClass,例如:new Date().getClass();
p1.getClass();//获得字节码
3.Class.forName("类名");
Class.forName("java.lang.String");//获得字节码。如果曾经被加载过,直接返回;如果虚拟机没有加载该字节码,则加载器加载字节码,返回;
九个预定义Class实例对象:
8个基本类型+void
Int.class=Integer.TYPE
System.out.println(Integer.TYPE);//int
Class cls=void.class;
Class类
boolean isPrimitive()
判定指定的 Class 对象是否表示一个基本类型。
package cn.itcsat.day1; public class ReflectTest { public static void main(String[] args) throws Exception{ String str1="abc"; Class cls1=str1.getClass(); Class cls2=String.class; Class cls3=Class.forName("java.lang.String"); System.out.println(cls1==cls2);//true System.out.println(cls1==cls3);//true //以上两个结果都为true,证明字节码是同一个 System.out.println(cls1.isPrimitive());//false,String不是基本类型,cls1不是基本类型的字节码 System.out.println(int.class.isPrimitive());//true,int是基本类型 System.out.println(int.class==Integer.class);//false System.out.println(int.class==Integer.TYPE);//true,TYPE中包装的是基本类型的字节码 System.out.println(int[].class.isPrimitive());//false System.out.println(int[].class.isArray());//true } }
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如:int,void。
反射就是把Java类中的各种成分映射成相应的Java类。
Constructor类
Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
Constructor[] constructor=Class.forName("java.lang.String").getConstructors();
得到某一个构造方法
Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class); //new String(new StringBuffer("abc")); Constructor constructor1= String.class.getConstructor(StringBuffer.class);//获得Constructor对象 String str2=(String) constructor1.newInstance(new StringBuffer("abc"));//newInstance 创建此 Class 对象所表示的类的一个新实例。 //调用获得的方法时,要用到与上面相同类型的实例对象 System.out.println(str2.charAt(2));//c
class——>constructor——>new object
Field类
Field fieldX = pt1.getClass().getDeclaredField("x");//getDeclaredFields获得所有字段(包括公有和私有)
fieldX.setAccessible(true);//暴力反射,访问私有成员
System.out.println(fieldX.get(pt1));//3
private static void changeStringValue(Object obj) throws Exception{ Field[] fields = obj.getClass().getFields();// 获得所有字段 for (Field field : fields) { // field.getType().equals(String.class); if (field.getType() == String.class) {//getType(),返回一个 Class 对象 //此处是同一个字节码,使用 == 进行比较 String oldValue=(String)field.get(obj); String newValue=oldValue.replace('b', 'a'); field.set(obj, newValue);//重新设置obj的值 } } }
package cn.itcsat.day1; public class ReflectPoint { public String str1="ball"; public String str2="basketball"; public String str3="itcast"; @Override public String toString() { return str1+":"+str2+":"+str3; } }
Method类
// str1.charAt(1);使用字节码方式调用对象 Method methodCharAt = String.class.getMethod("charAt", int.class); System.out.println(methodCharAt.invoke(str1, 1));// b // System.out.println(methodCharAt.invoke(null,1));//第一个参数为null时调用静态方法,因为静态方法调用不需要对象 System.out.println(methodCharAt.invoke(str1,new Object[]{2});//Object[]{}--对象数组,1.4用法
用反射方式执行某个类中的main方法
package cn.itcsat.day1; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectTest { public static void main(String[] args) throws Exception { // 普通方式(静态方式)调用其他类中的main方法 TestArguments.main(new String[] { "111", "222", "333" }); // 使用反射方式调用其他类中的main方法 String startingClassName = args[0];//需要传入参数 cn.itcast.day1.TestArguments Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class); // mainMethod.invoke(null,new String[]{"111","222","333"});//静态方法不需要传递对象。编译器会把数组对象拆包,发生参数数量错误 // mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});//所以再打一次包 mainMethod.invoke(null, (Object) new String[] { "111", "222", "333" });// 强转为Object,会认为是一个对象,不会拆包 } }
class TestArguments { public static void main(String[] args) { for (String arg : args) { System.out.println(arg); } } }
数组的反射
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象(此处比较与值无关)。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
package cn.itcsat.day1; import java.util.Arrays; public class ReflectTest2 { public static void main(String[] args) { int[] a1 = new int[] { 1, 2, 3 }; int[] a2 = new int[4]; int[][] a3 = new int[2][3]; String[] a4 = new String[] { "a", "b", "c" }; // System.out.println(a1.getClass() == a2.getClass());//true // System.out.println(a1.getClass()==a4.getClass());//类型不兼容,不能比较(jdk1.5以后) // System.out.println(a1.getClass()==a3.getClass()); // 如需比较,采用如下方式 Class c1 = a1.getClass(); Class c2 = a2.getClass(); Class c3 = a3.getClass(); Class c4 = a4.getClass(); System.out.println(c1 == c2);// true System.out.println(c1 == c4);// false System.out.println(c1 == c3);// false System.out.println(a1.getClass().getName());// [I----[ 表示数组,I表示int System.out.println(a1.getClass().getSuperclass());// class // java.lang.Object,获得父类 System.out.println(a4.getClass().getSuperclass());// class // java.lang.Object Object aObj1 = a1; Object aObj2 = a4; // Object[] aObj3=a1;//不行,数组中装的是int,int是基本数据类型,父类不是Object Object[] aObj4 = a3;// 数组中装的是int[]数组类型,int[]的父类是Object Object[] aObj5 = a4;// 同上,String的父类也是Object System.out.println(a1);// [I@32a88bc2 System.out.println(a4);// [Ljava.lang.String;@576f8789 //Arrays.asList()方法处理int[]和String[]时的差异 // [[I@32a88bc2],a1中存储的是int,基本数据类型不是对象,asList无法返回列表视图 System.out.println(Arrays.asList(a1)); // [a,b,c],a4是一个String数组,数组中存储的是String对象,asList方法返回列表视图 System.out.println(Arrays.asList(a4)); } }
数组的反射
int[] a=new int[3]; a[0].getClass().getName();//可以得到单个元素的类型,但是不能得到数组的元素类型
package cn.itcsat.day1; import java.lang.reflect.Array; public class ReflectTest3 { public static void main(String[] args){ int[] a1 = new int[] { 1, 2, 3 }; int[] a2 = new int[4]; int[][] a3 = new int[2][3]; String[] a4 = new String[] { "a", "b", "c" }; printObject(a1); printObject(a4); printObject("xyz"); } private static void printObject(Object obj) { Class clazz=obj.getClass(); if(clazz.isArray()){//如果是数组,则取出每个元素 int len=Array.getLength(obj); for(int i=0;i<len;i++){ System.out.println(Array.get(obj, i));//此处obj是数组,Array.get()方法取数组中的元素 } }else{//如果不是数组,则直接打印对象 System.out.println(obj); } } }
反射的作用——>实现框架功能
hashCode方法,不存储到HashSet集合中时,不需要覆写hashCode方法。
当一个对象被存储进HashSet中后,就不能修改这个对象中的那些参与计算哈希值的字段了,
否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。这种情况下,即
使在contains方法是用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找
不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
package cn.itcsat.day1; import java.util.Collection; import java.util.HashSet; public class ReflectTest4 { /** * @param args */ public static void main(String[] args) { 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=7;//pt1.y改变之后,hashCode值改变,则无法移除(内存泄露) collections.remove(pt1); System.out.println(collections.size());//3,覆盖了hashcode和equals方法后,size为2 } }
package cn.itcsat.day1; public class ReflectPoint { private int x; public int y; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ReflectPoint other = (ReflectPoint) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } }
框架的概念及用反射技术开发框架的原理
框架要解决的核心问题:因为在写程序时,无法知道要被调用的类名,所以,在程序中无法直接
new某个类的实例对象,要使用反射方式来做。
getRealPath()://获得真实路径
一定要使用完整的路径,但完整的路径不是硬编码,而是运算出来的
package cn.itcast.day1; import java.io.InputStream; import java.util.Collection; import java.util.Properties; public class ReflectTest4 { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // 一定要使用完整的路径,但完整的路径不是硬编码,而是运算出来的 // InputStream ips = new FileInputStream("config.properties"); // 使用类加载器加载配置文件,此方法只读 // InputStream ips=ReflectTest4.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties"); //InputStream ips=ReflectTest4.class.getResourceAsStream("resources/config.properties"); InputStream ips=ReflectTest4.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties"); Properties props = new Properties(); props.load(ips); ips.close();// load后关闭,否则对象关联的系统资源不被释放(不是对象);对象由虚拟机回收 String className = props.getProperty("className");// 获取配置文件中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 = 7;// pt1.y改变之后,hashCode值改变,则无法移除(内存泄露) // collections.remove(pt1); System.out.println(collections.size());// 3,覆盖了hashcode和equals方法后,size为2 } }
内省-->JavaBean
IntroSpector-->JavaBean(符合某种特定规则的特殊的Java类)
JavaBean 是一种JAVA语言写成的可重用组件。为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性。众所周知,属性名称符合这种模式,其他Java 类可以通过自身机制发现和操作这些JavaBean 属性。
例如:
class Person{ private int x; public int getAge(){ return x; } public void setAge(int age){ this.x=age; } }
属性名的变化规则:
Age-->如果第二个字母是小写的,则把第一个字母变成小写的;否则保持大写-->age
gettime-->time
getTime-->time
getCPU-->CPU
JavaBean的简单内省操作
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。
BeanUtils工具包:
BeanUtils.getProperty(bean, name);//获取,字符串类型
BeanUtils.setProperty(bean, name, value);//设置,字符串类型
package cn.itcast.day1; 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 java.util.Map; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; public class IntroSpectorTest { public static void main(String[] args) throws Exception { ReflectPoint pt1 = new ReflectPoint(3, 5); String propertyName="x"; //"x"-->"X"-->"getX"-->MethodGetX //如下是使用内省方式操作 Object retVal = getProperty(pt1, propertyName); System.out.println(retVal); //set Object value=7; setProperty(pt1, propertyName, value); System.out.println(BeanUtils.getProperty(pt1, "x")); BeanUtils.setProperty(pt1,"x", "9");//使用BeanUtils获得的是字符串类型,所以传入字符串 System.out.println(pt1.getX()); /* Map map={name:"xxx",age:18};//java7的新特性 BeanUtils.setProperty(map, "name", "lhm"); */ //BeanUtils支持属性的级联操作 //以下方法自动进行类型转化 BeanUtils.setProperty(pt1, "birthday.time", "111");//birthday本身是一个对象,使用time方法 System.out.println(BeanUtils.getProperty(pt1, "birthday.time")); //以下方式不进行类型转换 PropertyUtils.setProperty(pt1,"x", 9);//传入的是整数 System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName());//获得的是java.lang.Integer } private static void setProperty(Object pt1, String propertyName, Object value) throws IntrospectionException, IllegalAccessException, InvocationTargetException { PropertyDescriptor pd2=new PropertyDescriptor(propertyName, pt1.getClass()); Method methodSetX=pd2.getWriteMethod();//获得get方法 methodSetX.invoke(pt1, value);//此处使用变量value,如果直接使用常量7,则抽取方法时,不会把它当做参数 } private static Object getProperty(Object pt1, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException { /*PropertyDescriptor pd=new PropertyDescriptor(propertyName, pt1.getClass()); Method methodGetX=pd.getReadMethod();//获得get方法 Object retVal = methodGetX.invoke(pt1);//调用对象的方法,不知道返回的值为什么类型,则使用Object */ //使用遍历BeanInfo的所有属性方式来查找和设置某个RefectPoint对象的x属性 BeanInfo beanInfo= Introspector.getBeanInfo(pt1.getClass()); PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors(); Object retVal = null; for(PropertyDescriptor pd:pds){ if(pd.getName().equals(propertyName)){ Method methodGetX=pd.getReadMethod(); retVal = methodGetX.invoke(pt1); break; } } return retVal; } }
package cn.itcast.day1; import java.util.Date; public class ReflectPoint { private Date birthday=new Date(); private int x; public int y; public String str1="ball"; public String str2="basketball"; public String str3="itcast"; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
注解(jdk1.5)
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记。以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
@SuppressWarnings("deprecation")//压制警告
@Deprecated//标记过时
@Override//提示覆盖
注解类:
@interface A
{
}
应用了“注解类”的类:
@A
Class B
{
}
对“应用了注解类的类"进行反射操作的类:
Class C {
B.class.isAnnotionPresent(A.class);
A a = B.class.getAnnotion(A.class);
}
@Retention(RetentionPolicy.xxx)
元注解,其三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。
默认的值在class文件阶段。
@Override source(源文件)阶段
@SuppressWarings Runtime(内存中的字节码)阶段
@Target({ElementType.METHOD,ElementType.TYPE})
Target的默认值为任何元素,设置Target等于ElementType.METHOD,原来加在类上的注解就报错了,改为用数组方式设置{ElementType.METHOD,ElementType.TYPE}就可以了。
package cn.itcast.day2; @ItcastAnnotation(annotationAttr=@MetaAnnotation("chen"),color = "red", value = "abc", arrayAttr = { 1, 2, 3, 4 }) // 为注解设置属性,如果只有一个value属性需要设置值,则不需要属性名和等号 public class AnnotationTest { /** * @param args */ @SuppressWarnings("deprecation") // 压制警告 public static void main(String[] args) throws Exception { System.runFinalizersOnExit(true); if (AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)) {// 判断注解是否存在 ItcastAnnotation annotation = (ItcastAnnotation) AnnotationTest.class .getAnnotation(ItcastAnnotation.class);// 得到注解 System.out.println(annotation);// @cn.itcast.day2.ItcastAnnotation() System.out.println(annotation.color());// red System.out.println(annotation.value()); System.out.println(annotation.arrayAttr().length); System.out.println(annotation.lamp().nextLamp().name());//GREEN System.out.println(annotation.annotationAttr().value()); } } @Deprecated // 过时 public static void sayHello() { System.out.println("Hello World!"); } }
package cn.itcast.day2; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import cn.itcast.day1.EnumTest; @Retention(RetentionPolicy.RUNTIME)//元注解(注解的注解)。此处保留到内存阶段 @Target({ElementType.METHOD,ElementType.TYPE})//注解既可以用在方法上,也可以用在type(类、接口、枚举等)上 public @interface ItcastAnnotation { // String color();//abstract,public。为注解添加属性。 String color() default "blue";//可以为属性设置缺省值,在调用时如果不设置的话则使用缺省值 String value(); int[] arrayAttr() default{3,4,5};//添加数组属性,使用时指定值示例:arrayAttr={1,2,3}; EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;//枚举类型的属性 MetaAnnotation annotationAttr() default @MetaAnnotation("cc");//添加注解属性 }
package cn.itcast.day2; public @interface MetaAnnotation { String value(); }
泛型(jdk1.5)
没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中。使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,这样更安全;并且当从集合获取一个对象时,编译器也可以知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。
泛型是用来在限定集合中的输入类型的,编译完以后,会去掉类型信息,不影响运行效率。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
称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];
Vector v1 = new Vector<String>(); //正确
Vector<Object> v = v1;//正确,此处编译器认为v1是原始类型
泛型中的?通配符
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
通配符的扩展
限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();//Number及其子类
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();//Integer及其父类
错误:Vector<? super Integer> x = new Vector<Byte>();
限定通配符总是包括自己。
package cn.itcast.day2; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; public class GenericTest { /** * @param args * @throws Exception * @throws NoSuchMethodException */ public static void main(String[] args) throws NoSuchMethodException, Exception { ArrayList collection1 = new ArrayList(); collection1.add(1); collection1.add(1L); collection1.add("abc"); // int i=(Integer)collection1.get(1); ArrayList<String> collection2 = new ArrayList<String>(); // collection2.add(1); // collection2.add(1L); collection2.add("abc"); String element = collection2.get(0); System.out.println(element); Constructor<String> constructor1 = String.class .getConstructor(StringBuffer.class); String str2 = constructor1.newInstance(new StringBuffer("abc")); System.out.println(str2.charAt(2)); ArrayList<Integer> collection3 = new ArrayList<Integer>();// 类型参数为Integer System.out.println(collection3.getClass() == collection2.getClass());// true,虽然类型参数不同,但是使用的是同一个字节码 // collection3.add("abc");//直接添加String类型的数据会报错 collection3.getClass().getMethod("add", Object.class) .invoke(collection3, "abc");// 使用反射方法添加String类型数据 System.out.println(collection3.get(0));// abc,成功向集合中添加了String类型的数据 printCollection(collection3); HashMap<String,Integer> maps=new HashMap<String,Integer>(); maps.put("xxx", 25); maps.put("ccc", 26); maps.put("aaa", 27); //HashMap没有实现iterator方法,先获得Set集合(因为Set实现了iterator方法),再使用Map.Entry获取key和value Set<Map.Entry<String,Integer>> entrySet=maps.entrySet(); for(Map.Entry<String,Integer > entry:entrySet){ System.out.println(entry.getKey()+":"+entry.getValue()); } } // 用于打印的方法,要求能够打印任意参数化的类型 public static void printCollection(Collection<?> collection) {// 通配符?可以匹配任意类型 // collection.add("abc");//与类型有关的调用不能使用 // System.out.println(collection.size());//可以调用size方法,因为size方法与类型无关 for(Object obj:collection){//任何类型都可以转为Object System.out.println(obj); } } }
由C++的模板函数引入自定义泛型
C++模板函数:
template<class t>
T add(T x,T y){
return (T)(x+y);
}
Java中的泛型类型(或者泛型)类似于 C++ 中的模板。但是这种相似性仅限于表面,Java 语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:
<T> T add(T x,T y) {//public static <T>T add(T x,T y) return (T) (x+y);//此行编译出错,不是所有类型都能进行加法运算 //return null; }
只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。
main(){ // swap(new int[]{{1,2,3},2,6}); //只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误,而swap(3,5)不报错的原因是进行了自动装箱 //而int[]数组本身就是对象,不会自动转换成Integer,传入的时候出错。 String[] str = new String[] { "abc", "xyz", "itcast" }; swap(str, 1, 2); for (String s : str) { System.out.println(s);// abc,itcast,xyz } } public static <T> void swap(T[] a, int i, int j) { T temp = a[i]; a[i] = a[j]; a[j] = temp; }
也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
private static <T extends Exception> sayHello() throws T { try{ }catch(Exception e){ throw (T)e; } }
在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key); }
类型参数的类型推断
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
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 =(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);
定义泛型类型
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T> { private T field1; public void save(T obj){} public T getById(int id){} }
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String> dao = null;
new genericDao<String>();
注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
静态方法不能访问类上定义的泛型
如果静态方法操作的数据类型不确定,可以将泛型定义在方法上。
例如:
public class GenericDao<E> { public static void update2(E obj){//静态方法不能使用泛型,错误 //update2方法是静态的,用类名可以直接调用,而E只有在类的对象创建时才会明确 } public static <E>void update2(E obj){//正确 } }
public static void main(String[] args) throws Exception { //Vector<Date> v1=new Vector<Date>();//单独通过变量v1是无法获得泛型的类型的 Method applyMethod=GenericTest.class.getMethod("applyVector", Vector.class); Type[] types=applyMethod.getGenericParameterTypes(); ParameterizedType pType=(ParameterizedType)types[0]; System.out.println(pType.getRawType());//class java.util.Vector,获得原始类型 System.out.println(pType.getActualTypeArguments()[0]);//class java.util.Date,获得实际参数类型 } public static void applyVector(Vector<Date> v1){}
类加载器
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader(父-->子 )
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap(C++语言编写的二进制代码)。
委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
自定义类加载器
自定义的类加载器的必须继承ClassLoader
loadClass方法与findClass方法
defineClass方法(已过时)
findClass
protected Class<?> findClass(String name) throws ClassNotFoundException
使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来加载类。在通过父类加载器检查所请求的类后,此方法将被 loadClass 方法调用。默认实现抛出一个 ClassNotFoundException。
模板方法设计模式:父类-->loaderClass 总体流程由父类定义
子类1-->细节的方法由子类自己实现
子类2-->细节的方法由子类自己实现
覆盖findClass,
得到class文件后转成字节码-->defineClass方法
对class文件进行加密
public static void cypher(InputStream istream,OutputStream ostream) throws Exception { //下面这段代码可能遇到255的字节,当成byte就成了-1 /*byte b = 0; while((b = (byte)istream.read()) != -1) { ostream.write(b ^ 0xff);//异或 }*/ int b = 0; while((b = istream.read()) != -1) { ostream.write(((byte)b) ^ 0xff); } }
完整示例:
package cn.itcast.day2; import java.util.Date; public class ClassLoaderTest { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { System.out.println(ClassLoaderTest.class.getClassLoader().getClass() .getName());// sun.misc.Launcher$AppClassLoader System.out.println(System.class.getClassLoader());// null,特殊的类加载器,不是java对象,没有字节码 ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while (loader != null) { System.out.println(loader.getClass().getName());//sun.misc.Launcher$AppClassLoader loader = loader.getParent();//sun.misc.Launcher$ExtClassLoader } System.out.println(loader);//null // System.out.println(new // ClassLoaderAttachment().toString());//无法使用已加密的字节码 // 创建MyClassLoader对象,传入目录"itcastlib",并加载字节码ClassLoaderAttachment.class Class clazz = new MyClassLoader("itcastlib") .loadClass("ClassLoaderAttachment"); // ClassLoaderAttachment d1= // (ClassLoaderAttachment)clazz.newInstance();//此时不能使用ClassLoaderAttachment,因为此时未解密 Date d1 = (Date) clazz.newInstance();// 使用加载的字节码实例化 System.out.println(d1);//Hello,itcast! } }
package cn.itcast.day2; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class MyClassLoader extends ClassLoader { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { String srcPath = args[0]; String destDir = args[1]; FileInputStream fis = new FileInputStream(srcPath); String destFileName = srcPath.substring(srcPath.lastIndexOf('\\') + 1); String destPath = destDir + "\\" + destFileName; FileOutputStream fos = new FileOutputStream(destPath); cypher(fis, fos); fis.close(); fos.close(); } private static void cypher(InputStream ips, OutputStream ops) throws Exception { int b = -1; while ((b = ips.read()) != -1) {// 加密,解密 ops.write(b ^ 0xff);// 异或 } } private String classDir; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String classFileName = classDir + "\\" + name + ".class"; try { FileInputStream fis = new FileInputStream(classFileName); ByteArrayOutputStream bos=new ByteArrayOutputStream(); cypher(fis,bos);//解密 fis.close(); byte[] bytes=bos.toByteArray();//将解密内容转为字节数组 return defineClass(bytes,0,bytes.length);//将字节数组生成字节码并返回 } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } public MyClassLoader() { } public MyClassLoader(String classDir) { this.classDir = classDir; } }
package cn.itcast.day2; import java.util.Date; public class ClassLoaderAttachment extends Date { public String toString(){ return "Hello,itcast!"; } }
代理
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService ------|----------|------------|-------------
安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
Class proxy{ void sayHello(){ ………. try{ target.sayHello(); }catch(Exception e){ ……….. } …………. } }
分析JVM动态生成的类
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
创建动态类的实例对象
用反射获得构造方法
编写一个最简单的InvocationHandler类
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。
将创建动态类的实例对象的代理改成匿名内部类的形式编写
让jvm创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
产生的类字节码必须有个一个关联的类加载器对象;
生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
用Proxy.newInstance方法直接一步就创建出代理对象。
分析动态生成的类内部的代码
$Proxy0 implements Collection { InvocationHandler handler; public $Proxy0(InvocationHandler handler) { this.handler = handler; } }
Client程序调用objProxy.add(“abc”)方法时,涉及三要素:objProxy对象、add方法、“abc”参数
Class Proxy$ { add(Object object) { return handler.invoke(Object proxy, Method method, Object[] args); } }
在代理实例上的 java.lang.Object 中声明的 hashCode、equals 或 toString 方法的调用将按照与编码和指派接口方法调用相同的方式进行编码,并被指派到调用处理程序的 invoke 方法,如上所述。传递到 invoke 的 Method 对象的声明类是 java.lang.Object。代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。
package cn.itcast.day3; import java.lang.reflect.Constructor; 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 ProxyTest { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // 返回代理类的 java.lang.Class 对象(获取字节码),并向其提供类加载器和接口数组 Class clazzProxy1 = Proxy.getProxyClass( Collection.class.getClassLoader(), Collection.class);// 通常使用与接口相同的类加载器 System.out.println(clazzProxy1.getName()); // 查看获得的字节码中有什么方法 System.out.println("-----------begin constructors list-----------"); Constructor[] constructors = clazzProxy1.getConstructors(); for (Constructor constructor : constructors) { String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); // 单线程StringBuilder效率高,多线程StringBuffer效率较高 sBuilder.append('('); Class[] clazzParams = constructor.getParameterTypes(); for (Class clazzParam : clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if (clazzParams != null && clazzParams.length != 0) { sBuilder.deleteCharAt(sBuilder.length() - 1);// 去掉最后一个逗号',' sBuilder.append(')'); System.out.println(sBuilder.toString()); } } System.out.println("-----------begin methods list-----------"); Method[] methods = clazzProxy1.getMethods(); for (Method method : methods) { String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); // 单线程StringBuilder效率高,多线程StringBuffer效率较高 sBuilder.append('('); Class[] clazzParams = method.getParameterTypes(); for (Class clazzParam : clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if (clazzParams != null && clazzParams.length != 0) { sBuilder.deleteCharAt(sBuilder.length() - 1);// 去掉最后一个逗号',' sBuilder.append(')'); System.out.println(sBuilder.toString()); } } System.out .println("-----------begin create instance object-----------"); // clazzProxy1.newInstance();//没有不带参数的构造方法 Constructor constructor = clazzProxy1 .getConstructor(InvocationHandler.class); class MyInvocationHander1 implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } // InvocationHandler是interface,只能实现它的实现类,这里是MyInvocationHandler Collection proxy1 = (Collection) constructor .newInstance(new MyInvocationHander1()); System.out.println(proxy1);// null,这里是toString方法返回了null,否则如果对象没有创建成功的话会出现异常 proxy1.clear();// 清除集合 // proxy1.size(); Collection proxy2 = (Collection) constructor .newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); final ArrayList target = new ArrayList(); Collection proxy3 = (Collection)getProxy(target, new MyAdvice()); proxy3.add("abc");// 每次调用add,都会调用invoke方法 proxy3.add("cba"); proxy3.add("ccc"); System.out.println(proxy3.size()); System.out.println(proxy3.getClass().getName());// $Proxy0,返回的不是目标 } private static Object getProxy(final Object target,final Advice advice) { Object proxy3 = (Collection) Proxy.newProxyInstance( target.getClass().getClassLoader(), /*new Class[] { Collection.class }*/ target.getClass().getInterfaces() , new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 指定目标 /*long beginTime = System.currentTimeMillis(); Object retVal = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println(method.getName() + " running time of " + (endTime - beginTime)); return retVal;*/ advice.beforeMethod(method); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } }); return proxy3; } }
package cn.itcast.day3; import java.lang.reflect.Method; public interface Advice { void beforeMethod(Method method); void afterMethod(Method method); }
package cn.itcast.day3; import java.lang.reflect.Method; public class MyAdvice implements Advice { long beginTime=0; @Override public void beforeMethod(Method method) { // TODO Auto-generated method stub System.out.println("开始"); beginTime = System.currentTimeMillis(); } @Override public void afterMethod(Method method) { // TODO Auto-generated method stub System.out.println("结束"); long endTime = System.currentTimeMillis(); System.out.println(method.getName() + " running time of " + (endTime - beginTime)); } }