------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------
1、静态导入:
语法:import static 包名.类名。静态属性|静态方法静态导入就是在调用静态方法时不用再写类名了,直接调用。
导入一个类或包中的所有类:import java.net*;
导入某个静态方法:import static java.lang.Math.max;
导入所有的方法:import static java.lang.Math.*;
2、可变参数:
(1)可变参数的特点:
1.只能出现在参数列表的最后。
2.位于变量类型和变量名之间,前后有无空格都可以。
3.调用可变参数的方法时,编译器为该可变参数隐含创建一
个数组,在方法体中以数组的形式访问可变参数。
代码例子:
Class Test
{
Public static void main(String[] args)
{
System.out.println(add(2,3));
}
//接收一个整数,接收若干个整数,省略号表示接收的参数不定。
public static int add(int x,int... args)
{
int sum=x;
for(int i=0;i<args.length;i++)
{
sum+=args[i];
}
}
}
3.for循环增强:
语法:for(type 变量名:集合变量名或数组名或集合名){...}
注意事项:
迭代变量必须在()中定义,集合变量可以是数组或实现了Iterable接口的集合类!增强for循环中,在变量名前可以加修饰符,比如final修饰符,可以被局部内部类访问。 这个可迭代的集合必须要实现Iterable接口!才可以实现增强for迭代。
4.基本数据类型的装箱和拆箱:
JDK 5.0以后创建一个Interger对象,可以简写为:Integer iObj=3;也可以进行直接加法运算:即 (iObj+12);
在Integer i1=13;
Integer i2=13;
则这两个是同一个对象是在-128~127之中时是,超出范围就不是了, 和String不同。
这是一种设计模式:是享元模式,在很多的小对象经常被用到时,就将他们封装成一个对象。好处就是节省了内存。
还有:英语是:flyweight。有很对小的对象,他们有很多的属性都相同,把它们变成同一个对象,哪些不同的属性把
它们变成方法的参数,称之为外部状态,他们相同的属性叫做内部属性。
在一个字节范围内,就是在-128~127之间,Integer i1=Integer.valueOf(4);
Integeri2=Integer.valueOf(4);是同一个对象。
5.枚举:
(1)为什么要有枚举:
因为枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则编译器会报错,枚举可以让编译器在编译的时候就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
(2)用普通类如何实现枚举功能呢,定义一个Weekday的类来模拟枚举的功能。
1、私有的构造方法
2、每个元素分别用一个公有的静态成员变量表示
3、可以有若干公有方法或抽象方法,例如,要提供nextDay方法必须是抽象的。
例如:采用抽象方法定义nextDay就将大量的if else语句转移成了一个个独立的类。如果想在一个类中编写完成各个枚举类和测试调用类,那么可以将枚举类定义成调用类的内部类。
(3)枚举的基本应用:
1、举例:定义一个Weekday的枚举
2、扩展:枚举类的values,valueOf,name,toString,ordinal:排行第几,等方法
3、总结:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象,
例如:可以调用WeekDay.SUN.getClass().getName和WeekDay.class.getName()。
(4)枚举的基本应用举例:
代码例子:
public enum Weekday
{
/*这就是一个枚举定义好了,这些成员都是静态的,后面的封号此时可以写,也可以省略不写。
枚举相当于一个类,里面的每一个元素都是一个对象。*/
SUN,MON,TUE,WED,THI,FRI,SAT;
}
public class EnumTest
{
public static void main(String[] args)
{
/*那么我们在用这个枚举定义变量的时候,就只能定义枚举里面有的元素,比如SUN。*/
Weekday weekday1 = Weekday.SUN;
//打印的结果是SUN,发现枚举制动的实现了toString()方法。
System.out.println(weekday1);
//获取自己的名字,结果是SUN。
System.out.println(weekday1.name());
//这个方法就是获取SUN在枚举对象中的排行位置。
System.out.println(weekday1.ordinal());
//获取SUN的所属类的名称,结果为Weekday。
System.out.println(weekday1.getClass());
/*枚举的静态方法,把一个字符串变成它对应的枚举里边的元素
打印的结果是SUN,之所以打印出SUN是因为把SUN变成了
Weekday中的对象并且调用了Weekday中的toStrig()方法。*/
System.out.println(Weekday.valueOf("SUN"));
/*返回的是数组,也就是说吧枚举里面的元素逐一的添加到这个数组
里面,然后获取它的长度。结果为7。那么什么时候用这个方法呢?
比如说我们想迭代这个枚举里面的元素,用到高级for循环,但是
不能直接遍历这个枚举类,那么就用这个方法得到类里边所有元素
的一个数组,然后再对这个数组进行遍历。*/
System.out.println(Weekday.values().length);
}
}
(5)实现带有构造方法的枚举:
枚举是一个类,但是它没有构造方法,那么我们现在想为这个枚举定义有参的构造方法。代码如下:
代码例子:
public enum Weekday
{
//元素列表。
SUN,MON,TUE,WED,THI,FRI,SAT;
//空参数的构造函数。
private Weekday()
{
System.out.println("first");
}
//有参数的构造函数。
private Weekday(int day)
{
System.out.println("scond");
}
}
注意事项:
1、元素列表必须位于所有的成分之前,而且如果元素列表下面有成员了,那么元素列表的后面必须加上分号。
2、定义的构造函数必须是私有的。
3、现在有两个构造函数,一个有参数,一个没有参数,元素列表里面的的对象在创建的时候调用哪一个构造函数
呢?在对象的后面加上括号,括号里面有参数,在创建对象对象时就调用有参数的构造函数,括号里面没有参
数,在创建对象对象时就调用没有参数的构造函数。如果没有指定括号,那么就是默认调用无参数的构造函数。
(6)实现带有抽象方法的枚举:
枚举就相当于一个类,其中也可以定义构造方法,成员变量,普通方法和
抽象方法。
实现步骤:
1、定义枚举TrafficLamp。
2、实现的普通的next方法。
3、实现抽象的next方法,每个元素分别是由枚举类的子类来生成的实例对象,这些子类采用类似内部类的方式进行定义。
4、加上表示时间的构造方法。
总结:如果枚举只有一个成员时,就可以作为一种单例的实现方式。
代码例子:
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;
}
}
(7)枚举总结:
1、在程序中可以使用一个枚举类来指定对象的取值范围。
2、在Java中使用enum关键字定义一个枚举类,每一个枚举类都是继承Enum 类。
3、在枚举中可以通过values()方法取得枚举中的全部内容。
4、在枚举类中可以定义构造方法,但在设置枚举范围时必须显式地调用构造 方法。
5、所有的枚举类都可以直接使用Comparable进行排序,因为Enum类实现了Comparable接口。
6、Java类集中提供枚举的支持类是EnumMap、EnumSet。
7、一个枚举类可以实现一个接口或者直接定义一个抽象方法,但是每个枚举
8、对象都必须分别实现全部的抽象方法。
9、枚举实际是一个类,它中的元素其实是一个对象实例。
10、枚举中直接打印就是指定的字符串对象,这是因为枚举中复写了toString()方法。
6.分析反射的基础Class:
Java中的反射: 在JDK1.2就有了,用于:各种开发工具中。
任意一个类型都有对应的静态class属性。即类型名.class就是该类型的Class对象。但是:getClass和.class都要使用即先加载类型对象。
Class类:是反射的基石,Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个java类,他们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意小写class关键字的区别。Class来描述类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表等等。学习反射首先要明白Class这个类。
代码例子:
Person p1 = new Person();
Student s1 = new Student();
Class cls1 = Person.class;
Class cls2 = Student.class;
//获取Person的字节码文件。
P1.getClass();
P1这个对象是通过Person.class这个文件创建出来的。
(1)如何得到各个字节码对应的实例对象:
有3种方式:
1、类的名字.class。
2、对象.class,如new String().getClass()。
3、Class.forName(“类名”), 如:Class.forName(“java.lang.String);
Class.forName(“java.lang.String”);
这是一个静态方法,里面可以指定一个完整的名称。
(2)用forName()这个方法得到这个类的字节码有两种情况:
1、这个类的字节码已经加载到内存当中,就不用加载了,直接找到这个字节码返回就行。
2、比如说想得到这个类的字节码,但是虚拟机里面还没有这个类的字节码,于是就由类加载器加载,加载完以后,就
吧这份字节码缓冲起来,同时通过forName方法把这个字节码返回。
(3)九个预定义的Class实例对象:
有八个基本数据类型,他们对应的都有Class对象。void也有对应的Class对象。
代码例子:
public class ReflectTest
{
public static void main(String[] args)throws Exception
{
//创建一个字符串对象。
String str1 ="abc";
//用对象调用获取字节码文件。
Class c1s1= str1.getClass();
//用类名调用获取字节码文件。
Class c1s2 =String.class;
//用Class类调用获取字节码文件。
Class c1s3 =Class.forName("java.lang.String");
/*判断3个字节码是否相同,打印结果为true,true。说明是同一个字节码文件。*/
System.out.println(c1s1==c1s2);
System.out.println(c1s1==c1s3);
//判断String是否是原始类型的字节码,结果为false。
System.out.println(c1s1.isPrimitive());
/*判断int是否是原始类型的字节码,int是基本数据类型,是原始
类型的字节码,打印结果为true。*/
System.out.println(int.class.isPrimitive());
//比较int和Integer的字节码是否相同。结果为false,各有各的字节码。
System.out.println(int.class==Integer.class);
//比较int和Integer包装的基本类型的字节码是否相同,打印结果为 true。
System.out.println(int.class==Integer.TYPE);
//判断int型数组是否是原始类型,结果为false,因为数组也是一个类型。
System.out.println(int[].class.isPrimitive());
//判断这个字节码文件是否是数组,用Class中的isArray()方法,结果为 true。
System.out.println(int[].class.isArray());
}
}
7.反射:
(1)概念:
1、反射就是把JAVA类的各种成分映射成相应的java类。
2、反射中的另一个类:Constructor类代表某个类中的一个构造方法:
代码例子:
//得到某个类的所有的构造方法:
Constructor[]constructors=Class.forName("java.lang.String").getConstructors();
//得到某一个构造方法:
Constructorconstructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
(2)创建实例对象:
1、通常方式:
String str=new String(newStringBuffer("abc"));
2、反射方式:
String str=(String)constructor.newInstance(newStringBuffer("abc"));
例子:
//获取构造函数,获取构造函数的时候需要类型,如StringBuffer表示获取StringBuffer对应的构造函数。
Constructor constructor1 =String.class.getConstructor(StringBuffer.class);
/*调用获取的构造函数创建对象时,需要传一个获取构造函数时,相同
类型的实例对象。在编译的时候不知道是String类上的构造函数,所以
前面加上类型强转,(String)。
String str =(String)constructor1.newInstance(new StringBuffer("abc"));
//求字符串里面的角标位为2处对应的字符。
System.out.println(str.charAt(2));
扩展:
Class这个类里面也有一个newInstance()方法。
例子:
String s = Class.forName(“java.lang.String”).newInstance();
1、newInstance()内部先得到默认的构造方法,然后用该构造方法创建实例对象。
2、newInstance()内部用到了缓冲机制类保存默认构造方法的实例对象。
总结:我们要创建一个实例步骤:
首先是由Class得到Constructor,再由Constructor创建出new Object。
(3)成员变量的反射:
File类表示类的成员变量:
访问方式:
1、对于公有的成员直接用对象名.getClass().getField("成员变量名");然后用获
取的对象的变量.get(类型的对象名);来获取对象上对应的值。
2、对于私有的成员变量,那么是对象.class.getDeclaredField();同时还要将其
设置为可见的setAccessible(true);这就是暴力反射,强制使用。
字节码的比较方式:
在反射中如果字节码是一份就用==来比较,不使用equals,用==比较准确,因为是一份字节码。
(4)成员方法的反射:
Method类代某一个类中的一个成员方法。
例子:
//得到类中的某一个方法:
Method charAt=Class.forName("java.lang.String").getMethod("charAt",int.class);
调用方式:
1、通常方式:System.out.println(str.charAt(1));
2、反射方式:System.out.println(charAt.invoke(str,1));
如果传递给Method对象的invoke()方法的一个参数为null,这说明该Method对象对应的是一个静态方法。
jdk1.4和1.5的区别是1.5有了可变参数;1.4是用newObject[]{2};
(5)用反射方式执行某个类中的main方法:
在传递参数时:
1.将参数数组在打包成Object数组,在调用时他会拆包将数组作为参数传递。
2.将数组转化为对象,(Object)数组这样调用,是一个对象,就不会再拆包了。
(6)数组的反射:Arrays中有个aslist方法:
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不可以当作Object[]类型使用;非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异。
Array工具类用于完成对数组的反射操作。
怎么得到数组中的元素类型?没有办法得到数组中的类型;但是可以得到某个元素的类型。
(7)反射的作用-实现框架功能:
ArrayList_HashSet的比较及Hashcode分析:
框架和工具类的区别:工具类是被用户的类调用,而框架则是调用用户提供的类。
当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进的HashSet集合时的哈希值就不同了,即使使用contains方法是用该对象的当前引用作为的参数去HashSet集合中搜索对象,也将返回找不到的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存溢出。
这个例子可以说明很多问题:
1、内存为什么溢出,java中有内存溢出。
2、hashCode的作用,
3、集合中ArrayList和HashSet的应用。
8.内省
内省英文单词为:IntroSpector:----- JavaBean
1、如果要在两个模块之间传递多个信息,可以将这些信息封装到一个类中,这个类的实例对象统称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段类存储,如果读取或设置这些字段的值,这需要通过一些相
应的方法来访问。
2、JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则,JavaBean的属性是根据其中的setter和getter方法来确定的。而不是根据其中的成员变量。如果方法名为setld,中文意思极为设置id,
getRealPath()得到绝对路径;
一定要记住要用完整的路径,完整的路径不是硬编码,而是运算出来的。
框架中的配置文件都放在classpath路径下。
getClassLoader()类加载器,getResourceAsStream();
9.注解(Annotaion)
(1)什么是注解:
@SuppressWarnings(“deprecation”)
这就是一个注解,里面传了一个过时的单词,它的作用就是知道要用过时的方法了,在DOS命令行里面就不在提示过时的信息了。
例子:
@Deprecation:如果有过时的方法在编译的时候就会提示。
@Orerride:如果覆盖的方法有错误编译的时候就会提示。
理解:注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,如果没有加注解的话,就没有某种标记。
Javac编译器,开发工具和其它程序程序可以用反射类了解类及各种元素上有无某种标记,有什么标记,就去做相应的动作,标记可以加在包,类,字段,方法,方法的参数,以及局部变量上边。
(2)注解的定义与反射的调用:
1、@interfaceA{} :定义 注解类。
2、@A
class B{} : 应用了注解类的类。
3、class C //对应用了注解类的类进行反射操作的类。
{
B.class.isAnnotionPresent(A.class);
A a = B.class.getAnnotion(A.class);
}
代码例子:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//元注解。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
//定义一个注解。
public @interface ItcastAnnotation
{
}
//将注解加在一个类的上面。
@ItcastAnnotation
public class AnnotationTest
{
public static void main(String[] args)
{
//验证这个注解类是否存在,将需要验证的字节码文件传入到方法中,
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class));
//如果条件满足,说明注解存在,那么就获取注解,在编译的时候不知道获取的是哪一个注解,所以需要强转。
ItcastAnnotation annotation =(ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
//如果在某一个位置加上了一个注解,就等于有了这个注解的一个实例对象。
//那么打印一下这个注解,打印结果为ItcastAnnotation。
System.out.println(annotation);
}
}
元注解的作用:指定注解在字节码运行阶段。如果不加元注解,那么注解在Class文件阶段。
@Target({ElementType.METHOD,ElementType.TYPE})这个注解的作用:
ElementType.METHOD表示只能加到方法的上边,ElementType.TYPE表示加在类型的上边。也就是可以设置注解使用在什么成分上边。
理解:一个注解的生命周期有3的阶段:
java源文件-->class文件-->内存中的字节码。
Javac在把源文件翻译成class的时候,有可能去掉注解,类加载器在把class
文件调到内存里面的时候,也有可能去掉注解。那么如果想把注解保留到运行时期或是想设置在别的时期,那么就在注解类上加上@Retention,说明的意思,可以设置成:
1、源文件阶段:RetentionPolicy.SOURCE
2、class文件阶段:RetentionPolicy.CLASS
3、运行阶段:RetentionPolicy.RUNTIME
注意:默认的是class文件阶段。
(3)为注解添加各种属性:
举例:一个注解相当于一个胸牌,如果有了胸牌,就是黑马的学员,否则就不
是。如果还想区分是黑马哪个班的学员,这时候就需要在胸牌上增加一个
属性进行区分。
加了属性的标记效果为:@MyAnnotion(color=”red”)
定义基本类型的属性和应用属性:
1、在注解类中增加String color();
2、@MyAnnotion(color=”red”)
用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
1、
MyAnnotion a=(MyAnnotion)AnnotionTest.class.getAnnotion(MyAnnotion.class);
2、
System.out.println(a.color());
3、
可以认为上面的这个注解是@MyAnnotion是MyAnnotion类的一个实例对象。
为属性指定缺省值:
String color() default”yellow”;
Value属性:
1、String calue() default”zxx”;
2、如果注解中有一个名称为value的属性,而且你只想设置value属性( 及其他属性丢采用默认值或者)
代码例子:
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;
import cn.itcast.day1.TrafficLamp;
//元注解。
@Retention(RetentionPolicy.RUNTIME)
//目标。
@Target({ElementType.METHOD,ElementType.TYPE})
//定义一个注解。
public @interface ItcastAnnotation
{
//定义属性。
String color() default "blue";//添加默认值。
String value();
int[] arrayAttr() default {1,2,3};}//添加默认值。
//定义一个枚举类型的属性。
EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;
//用的时候把属性加在注解的后边并且赋值。
@ItcastAnnotation(color="red",value="abc",arrayAttr={1,2,3})
//当arrayAttr中的元素只有一个的时候,可以省略掉大括号。
public class AnnotationTest
{
public static void main(String[] args)
{
//验证这个注解类是否存在,将需要验证的字节码文件传入到方法中,
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class));
//如果条件满足,说明注解存在,那么就获取注解,在编译的时候不知道获取的是哪一个注解,所以需要强转。
ItcastAnnotationannotation= (ItcastAnnotation) AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
//那么打印属性。
System.out.println(annotation.color());//结果为red
System.out.println(annotation.value());//结果为abc
System.out.println(annotation.arrayAttr().length);//结果为3
//打印结果为GREEN
System.out.println(annotation.lamp().nextLamp().name())
}
}
10.类加载器与委托机制分析
什么交类加载器:就是加载类的工具。
比如说我们在java程序里面用到一个类,出现了这个类的名字,java虚拟机首先要把类的字节码加载到内存里面来。通常这个字节码的原始信息放在硬盘的classpath指定目录下,我们把.class里面的内容加载进硬盘里面来,再对它进行一些处理,处理完的结果就是字节码,就是把.class问价从硬盘上加载进内存,然后再对它进行一些处理,这些工作都是类加载器在做。
(1)Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类
负责加载特定位置的类。
1、BootStrap 根装载器(启动类装载器)
2、ExtClassLoader
3、AppclassLoader
(2)类加载器也是JAVA类,因为其它是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这个类加载器就是BoosStrap。
(3)Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统加载器为其父级类加载。
(4)类加载器的委托机制:
当java虚拟机要加载一个类时,到底派哪个类加载器去加载呢?
1、首先当前线程的类加载器去加载线程中的第一个类。
2、如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。
3、还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
1、当所有父级类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的子类,因为没有getChild方法,即使有,那么多子类,该找哪一个呢?
2、对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
(5)加载器的原理:
自定义一个类加载器,继承ClassLoader,复写findClass方法,将自定义加载类里面的数据存到字节数组中,再把字节数组传入到defineClass方法中,然后这个方法就返回穿进去二进制数据对应的字节码文件。
代码例子:
public class MyClassLoader extends ClassLoader
{
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;
//重写findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
String classFileName = classDir + "\\" + name + ".class";
FileInputStream fis = null;
try
{
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;
}
}
11.代理:
概念:
交叉业务的编程问题即为面向方面的编程(Aspect oriented program 简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动和原始方法的周围。这与直接在方法中编写切面代码的运行效果是一样的。
JVM可以再运行期动态生成类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类的生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以再代理方法中的如下四个位置加上系统功能代码。
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
分析代理类的作用与原理及AOP概念
(1)创建动态类及查看其方法列表信息
用JVM创建动态类及创建实例对象,那么需要给它提供那些信息?
三方面:
1、这个动态类实现那些接口
2、产生的动态类字节码必须有一个关联(通常是与传入接口相对应的类加载
器)的类加载器对象。
3、在创建对象的时候需要往构造函数里面传一个InvocationHandler的实现类
的对象。
解释:
用到Proxy类中的static Class<?> getProxyClass(ClassLoader loader,
Class<?> ...interfaces)
需要传入类加载器,和需要指明这个字节码实现了那些接口。这个方法可以在内存里面造出一份字节码,也就是造出一个类。
每个字节码都是由一个类加载器加载进来的,这个在java虚拟机内存里面操作了一个字节码,因此没有类加载器(ClassLoader),没有通过类加载器加载,就是内存里面出来的,所以也必须给它指定一个类加载器,通常这个类加载器就是与接口对应的类加载器。不指定的话,它就没有类加载器了。通过获取生成的这个动态类的构造函数,发现是由一个有参的构造函数,参数是InvocationHandler对象,还有实现的接口里面的方法,还有就是Object里面的方法。
代码例子:
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
{
public static void main(String[] args) throws Exception
{
/*指定接口,也就是接口的字节码文件,同时指定类加载器,通常指定
与指定接口相同的类加载器。返回字节码。*/
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//既然是一个字节码,那么我们获取这个字节码对应的类的名称。打印结果 是$proxy0,是一个类。
System.out.println(clazzProxy1.getName());
}
}
(2)创建动态类的实例对象及调用其方法
1、用反射获得构造方法。
2、编写一个最简单的nvocationHandler类。
3、调用构造方法创建动态类的实例对象,并将编写的nvocationHandler类的 实例对象传进去。
4、打印创建的对象和调用对象的没有返回值的方法和getClass方法。
5、将创建动态类的实例对象的代理改成匿名内部类的形式编写。
解释:
在创建动态类实例对象的时候,构造方法要接收一个InvocationHandler对象。所以要提前创建一InvocationHandler的一个子类,并且实现InvocationHandler。
创建完动态类的实例对象后,打印的结果为null,调用没有返回值的方法能够成功,调用有返回值的方法就会出现异常。为什么会出现异常?因为我们调用size方法的时候,它就会去调用invoke方法,invoke方法返回的是null,但是size需要返回的是整数,所以就会出现异常。
在调用getClass方法的时候,打印的结果是动态类的名字,因为Object只将3个方法交给了InvocationHandler,分别是:hashCode, equals, toString。所以在调用别的方法的时候就不调用InvocationHandler中的invoke方法了。
Proxy里面还提供了一个方法,是一个静态方法:newProxyInstance。这个方法里面传3个参数,类加载器,接口,实现这个接口的子类对象。这个方法可以用一步就完成动态对象的创建。
代码例子:
class MyInvocationHander1 implements InvocationHandler
{
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
{
return null;
}
}
Collectionproxy1 = (Collection)constructor.newInstance(new MyInvocationHander1());
System.out.println(proxy1);
proxy1.clear();
//proxy1.size();
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler()
{
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("zxx");
proxy3.add("lhm");
proxy3.add("bxd");
System.out.println(proxy3.size());
System.out.println(proxy3.getClass().getName());
}
------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------