---------------------------android培训、java培训、期待与您交流!---- -------------------------
Class类
Java程序中的各个Java类属于同一类事物,描叙这种事物的类就是Class类Class类 就是 运行时类型信息
得到Class类的三种方式
New Object().getClass();System.class;
Class.forName(“java.lang.String”); //主要用这种,配置文件配置
面试题:说明Class.forName()的作用?
得到类的字节码,得到类的字节码有两种情况,就是这个类的字节码已经存在于JVM的内存中,
直接调用; 或者JVM中没有,则用类加载器重新创建一份类的字节码,返回给方法。
9个预定义的Class对象
8种基本数据类型都有对应的Class类对象。boolean,byte,char,short,int,long,float,double
还有 一个void 类型,也有一个对应的Class类对象。
理解:
System.out.println(String.class == Class.forName(“java.lang.String”));
//输出为true,
//对字节码的比较都是用==,
//对数组的字节码进行比较,如果用==,则编译不通过,可以用equals方法
System.out.println(new String(“abc”).getClass().isPrimitive());
//查看一个字符串的字节码是否是基本数据类型,输出为false
System.out.println(int.class == Integer.TYPE “ , ” + int.class == Integer.class);
//Integer.TYPE 表示包装类包装的基本类型, 输出为 true,false
System.out.println(int[].class.isPrimitive() + “ , ” + int[].isArray());
//数组也是一种类型,有单独的判断方法
System.out.println(String.class == Class.forName(“java.lang.String”));
//输出为true,
//对字节码的比较都是用==,
//对数组的字节码进行比较,如果用==,则编译不通过,可以用equals方法
System.out.println(new String(“abc”).getClass().isPrimitive());
//查看一个字符串的字节码是否是基本数据类型,输出为false
System.out.println(int.class == Integer.TYPE “ , ” + int.class == Integer.class);
//Integer.TYPE 表示包装类包装的基本类型, 输出为 true,false
System.out.println(int[].class.isPrimitive() + “ , ” + int[].isArray());
//数组也是一种类型,有单独的判断方法
反射
反射就是把Java类中的“各种成分”映射成相应的java类。这些相应的java类有:
Package
Field
Method
Contructor
得到这些相应的类的实例对象可以做甚么呢?怎么用呢?这就是反射的要点。
1. Constructor类
描叙类的构造方法构造方法:
Constructor[] cs = Class.forName(“java.lang.String”).getConstructors();
Constructor c
= Class.forName(“java.lang.String”).getConstructor(StirngBuffer.class);
注意:得到一个类的构造方法类对象,
根据类的构造方法的参数的Class类和参数的个数
Constructor的一般方法
Public Class getDeclaringClass(); // 得到该构造方法类所属的Class类,返回的是Object Public Object newInstance(Object… initargs) //创建一个类的实例对象并返回 用Constructor来构造一个类的实例对象 //new String(new StringBuffer(“abc”)) Constructor c = String.class.getConstructor(StringBuffer.class); String s = (String)c.newInstance(new StringBuffer(“abc”)); //需要强转 2.Field类
描叙类的成员变量 理解: //ReflectPoint类中有两个成员变量x是类中的私有成员,y是公有的 ReflectPoint rp = new ReflectPoint(3,5); //fieldY不是对象上的变量,而是类上的,要得到对象上的成员变量的值,//需要调用方法 public Object get(Object obj); Field fieldY = rp.getClass().getField(“y”); System.out.println(fieldY.get(rp)); //需要得到类中的私有成员,要用不同的方法 FieldX fieldX = rp.getClass().getDeclaredField(x); //因为fieldX是私有成员,默认不能取值,需要设置: fieldX.setAccessible(true); System.out.println(fieldX.get(rp)); 练习: 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”。 //主要的方法
Private static void changeStringValue(Object obj) throws Exception{
Field[] fs = obj.getClass().getFields();
For(Field f : fs){
If(f.getType() == String.class){
String oldValue = (String)f.get(obj);
String newValue = oldValue.repalce(‘b’, ‘a’);
//将obj对象上的字段f,设置为newValue
f.set(obj, newValue);
}
}
}
3.Method类
描叙类对象的某个方法1. 得到某个类的某个方法
//通过某个类的Class得到该类的Method
public Method getMethod(Stirng name, Class<?>….parameterTypes);
Method m = String.class.getMethod(“charAt”, int.class);
2. 调用某个类的某个这个方法
Public Object invoke(Object obj, Object…args);
m.invoke(str,0);//调用字符串str的charAt方法,得到第一个索引处的值
//如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。
//如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。
调用某个类的这个静态方法,将对象参数设为null
m.invoke(null, Object…args);
用反射方式执行某个类中的main方法
目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
//因为方法调用的方法
Public Object invoke(Object obj, Object… args)
//第二个参数必须是Object的,而且是可变参数,
//所以main方法的参数传递只能进行封装
………….
Method m =
Class.forName("ReflactMain_Test").getMethod("main",String[].class);
m.invoke(null, new Object[]{new String[]{"a","b","c"}});
……………
数组的反射
反射每一个(具有相同的类型和相同纬度的)数组得到的Class都是同一个。代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
思考题:怎样才能得到数组中的元素类型?
没有办法得到数组中的元素类型,只能先得到数组中
独立的元素,然后得到元素的类型
private static void printObj(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));
}
}else{
System.out.println(obj);
}
}
类:java.lang.reflect.Array
public final class Array
方法:
public static Object get(Object array, int index)
//返回指定数组对象中索引元素的值
public static int getLength(Object array);
//返回数组对象的长度
public static void set(Object array,int index, Object value)
提示:
1,通常来说,一个类的两个实例对象用equals()方法比较的结果相等时,
它们的哈西码也必须相等。
2,当一个对象被存储进HashSet集合中之后,就不能修改这个对象中的
那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进
HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法
使用该对象的当前引用作为的参数去hashSet集合中检索对象,也将返回找
不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而
造成内存泄漏。
反射的作用 : 实现框架功能
java.util.Properties
//从一个properties的配置文件中读取配置信息
//在配置文件config.properties中配置:className=java.util.ArrayList
..........
InputStream is = new FileInputStream("config.properties");
Properties p = new Properties();
p.load(is);
is.close();
String className = p.getProperty("className");
Collection c = (Collection)Class.forName(className).newInstance();
..............
实际工程中的配置文件的路径问题:
getRealPath(); //获取工程目录的绝对路径,然后可以得到工程下的某个文件的具体路径
通过内加载器来加载资源:
InputStream is =
ReflectTest.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
//注意路径的格式: 如上的cn目录之前不能加'/'
直接通过Class字节码来获取 输入流,获取路径
InputStream is =
ReflectTest.class.getResourceAsStream("config.properties");
//配置文件相对于ReflectTest.class的相对路径
InputStream is =
ReflectTest.class.getResourceAsStream("resource/config.properties");
//配置文件在ReflectTest.class包里面的一个文件夹resource下放置
InputStream is =
ReflectTest.class.getResourceAsStream("/cn/itcast/day1/config.properties");
//用绝对路径方式来读取配置文件,
//路径字符串最开始的'/'代表 classpath所在的位置
内省 IntroSpector
(单词的意思:内窥镜,检查,视察)
JavaBean 特殊的类
方法具有特定的规则:|- int getAge()
|- void setAge(int age)
例子:
class Person{
private int x;
public int getAge(){return x;}
public void setAge(int age){this.x = age;}
}
1.JavaBean 的属性 是根据 set 和get方法 来确定的,而不是实际的类中的属性
2,去掉set和get方法名中的'set'和'get'字符,剩下来的字符串就是属性名,
剩下来的字符串: 如果第二个字符是小写的,则把第一个字母变成小写的
如果第二个字符是大写的,则第一个字母也保持大写
如果在连个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,
这种JavaBean的实例对象通常称之为值对象(Value-Object)。
类PropertyDescriptor
public class PropertyDescriptor//PropertyDescriptor 描述 Java Bean 通过一对存储器方法导出的一个属性。
构造方法:
PropertyDescriptor(String propertyName, Class<?> beanClass)
普通方法:
public Method getReadMethod(); //获得应该用于读取属性值的方法。
public Method getWriteMethod(); // 获得应该用于写入属性值的方法。
//参数obj,一个JavaBean实例对象,propertyName,该JavaBean的一个属性
//返回这个JavaBean的这个属性的值
public static Object getProperty(Object obj, String propertyName){
PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
Method m = pd.getReadMethod();
Object retVal = m.invoke(obj);
return retVal;
}
//参数:obj,一个JavaBean对象,propertyName,该JavaBean的一个属性,arg,用于设置这个属性的值
public static void setProperty(Object obj, String propertyName, Object arg){
PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
Method m = pd.getWriteMethod();
m.invoke(obj, arg);
}
类Introspector
java.beanspublic class Introspector
//Introspector 类为通过工具学习有关受目标 Java Bean 支持的
//属性、事件和方法的知识提供了一个标准方法。
静态方法:
public static BeanInfo getBeanInfo(Class<?> beanClass);
// 在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件。
接口 BeanInfo
public interface BeanInfo实现类: SimpleBeanInfo
方法:
public PropertyDescriptor[] getPropertyDescriptors()
//获得 beans PropertyDescriptor。该Bean的 所有PropertyDescriptor。
//麻烦一点的得到 Bean 属性 的方法
public static Object getProperty(Object obj, String propertyName){
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptior[] pds = beanInfo.getPropertyDescriptiors();
Object retVal = null;
for(PropertyDescriptor pd : pds){
if(pd.getName.equals(propertyName)){
Method m = pd.getReadMethod(propertyName, obj.getClass());
retVal = m.invoke(obj);
break;
}
}
return retVal;
}
开源 提供的 操作JavaBean 的工具:
Beanutils工具包
1.导入包: commons-beanutils.jarcommons-logging.jar
工具类org.apache.commons.beanutils.BeanUtils
方法:
public static String getProperty(Object bean, String name);
//得到Bean的属性
public static void setProperty(Object bean, String name, Object value);
//设置Bean的属性
public static java.util.Map describe(Object bean)
//返回一个bean的所有属性的map集合
工具类org.apache.commons.beanutils.PropertyUtils
方法:
public static Object getProperty(Object bean, String name);
//得到属性
public void setProperty(Object bean, String name, Object value)
//设置属性
注解 Annotation:
Object中的几个基本的注解:@SuppressWarnings("deprecation") //取消编译时 的 “方法过时” 的提示
@Deprecated //设置 编译时的“ 方法过时”提示
@Override //标识 方法 是 覆盖 父类的方法
注解的应用
|- 定义注解类
| @interface A {}
|- 应用了“注解类”的类
| @A
| class B{}
|- 对“应用了注解类的类”进行反射操作的类
|
Class中的几个方法用于判断和得到注解
AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class);//判断类AnnotationTest上是否有注解ItcastAnnotation
ItcastAnnotation annotation =
(ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
//得到AnnotationTest类上的注解ItcastAnnotation
为注解添加注解(源注解)
eg://该注解保留到运行时期
@Retention(RetentionPolicy.RUNTIME)
public @interface ItcastAnnotation{
}
源注解@Retention
三个取值:
|- RetentionPolicy.SOURCE : 被应用的注解应用在java源文件级别|- RetentionPolicy.CLASS : 被应用的注解应用在class文件级别,默认值
|- RetentionPolicy.RUNTIME : 被应用的注解应用在内存中的字节码
源注解@Target
的取值
|- ElementType.METHOD //表示被应用的注解可以用在|- ElementType.TYPE //表示被应用的注解可以用在类,接口,枚举,,,,
|- ElementType.FIELD //表示被应用的注解可以用在;类的属性上
|- ElementType.CONSTRUCTOR //表示被应用的注解可以用在类的构造器
..........
为注解添加基本属性
注解的属性其实是方法例子:
//定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD, ElementType.TYPE)
public @interface ItcastAnnotation{
String color(); //添加注解的属性
}
//将一个注解应用到一个类上
@ItcastAnnotation(color="red")
public class AnnotationTest{
public static void main(String[] args){
Class clazz = AnnotationTest.class;
if(clazz.isAnnotationPresent(ItcastAnnotation.class)){
//得到注解类
ItcastAnnotation a = (ItcastAnnotation)clazz.getAnnotation(ItcastAnnotation.class);
//将注解的属性打印到控制台上
System.out.println(a.color());
}
}
}
注解的一个特殊属性:
例子://如果只有一个属性,且这个属性是value,
public @interface ItcastAnnotation{
String value(); //添加注解的属性
}
//那么将注解应用到其他类上的时候,可以不使用"="
.....................
@ItcastAnnotation("abc")
public static void main(String[] args){}
//或者注解类 中有多个属性,但是其他属性都有默认值的情况下,也可以不
//使用“=”
public @interface ItcastAnnotation{
String value();
String color() defalut "blue";
}
为注解增加更高级的属性
| 数组类型的属性| 枚举类型的属性
| 注解类型的属性
在注解中添加数组类型的属性
例子:
//定义一个注解
public @interface ItcastAnnotaion{
int[] arrAttr();
}
//将注解应用到一个类上
@ItcastAnnotation(arrAttr={1,2,3})
public class AnnotationTest{............}
//如果传递给数组的只有一个值,可以省略大括号
@ItcastAnnotation(arrAttr=3)
public class AnnotationTest{........}
在注解中添加枚举类型的属性
//定义一个枚举类
public enum Color{RED,BLUE,YELLOW;}
//定义一个注解,该注解具有一个枚举类型的属性
public @interface ItcastAnnotation{
Color color() default default Color.RED;
}
//将注解应用到一个类上
import java.lang.annotation.*;
@ItcastAnnotation(color=Color.BLUE)
public class AnnotationTest{
public static void main(String[] args){
Class clazz = AnnotationTest.class;
if(clazz.isAnnotationPresent(ItcastAnnotation.class)){
ItcastAnnotation a = (ItcastAnnotation)clazz.getAnnotation(ItcastAnnotation.class);
Color c =a.color();
System.out.println(c.toString());
}}}
在注解中添加枚举类型的属性
//定义一个注解,包含一个注解属性
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ItcastAnnotation{
MetaAnnotation annotationAttr() default @MetaAnnotation("zhangxiaoxiang");
}
//定义一个注解,用于作为上面注解的属性
@interface MetaAnnotation{
String value();
}
//定义一个类,将注解应用在这个类上
@ItcastAnnotation
public class AnnotationTest{
public static void main(String[] args){
Class clazz = AnnotationTest.class;
if(clazz.isAnnotationPresent()){
ItcastAnnotation ia =
(ItcastAnnotation)clazz.getAnnotation(ItcastAnnotation.class);
System.out.println(ia.annotationAttr().value());
}}}
注解的返回值的类型 可以是 :
primitive types,
String,
Class,
any invocation of Class,
enum type,
annotation type,
array of one of the preceding types 前面类型的数组
泛型:
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会
除掉“类型”信息,使得程序运行效率不受影响,对于参数化的泛型类型,
getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码去掉
泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据。
例如:用反射得到集合,再调用其add方法就可以。
泛型相关术语:
整个 ArrayList<E>称为泛型类型
E称为类型参数
ArrayList<Integer> 称为参数化的类型
这里的 Integer 称为实际类型参数
尖括号<> 念 typeof
ArrayList 称为原始类型
注意:
1.参数化类型与原始类型可以兼容 ,只是编译器会有警告,2.参数化类型不考虑类型参数的继承关系:
Vector<String> vs= new Vector<Object>(); //错
Vector<Object> vo = new Vector<String>(); //错
3.数组的元素不能使用参数化的类型
Vector<Integer>[] vectorList = new Vector<Integer>[10]; //错
思考题://下面2行代码 会报错么?
Vector v1 = new Vector<String>();
Vector<Object> v2 = v1;
泛型的综合应用:
以 Map的 遍历 为例子
自定义泛型
Java中的泛型类型(或者泛型)类似于 C++ 中的模板。但是这种相似性仅限于表面,Java 语言中的泛型基本上完全是在编译器中实现,
用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,
这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。
这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。
所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
在方法上定义泛型
private static <T> T add(T x, T y){
}
//1.首先声明一个泛型类型,在返回值之前并紧挨着返回值,用<T>来定义
泛型类型推断:
应用上面定义的泛型方法: add(3,5); add(3.123, 4); add(3, "abc");
// 在这3个例子中的泛型类型到底是什么类型?
去可能的类型的交集,
第一个是int,第二个是Number,第三个是Object
例子分析:
//问题:交换数组中的两个元素的位置,用泛型实现这样一个方法
public static <E> void swap(E[] arr, int i , int j){
E tmp = E[i]; E[i] = E[j]; E[j] = tmp;
}
//注意:泛型的实际类型只能是引用类型,不能是基本类型;
//应用上面的泛型方法
swap(new int[]{1,2,3,4,5}, 2,3); //编译不通过
定义泛型时可以使用 extends限定符
例如: public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
//表示 A必须是Annotation的子类
定义泛型时还可以用&表示多个边界
例如: <V extends Serializable & Cloneable>
编译器不允许创建类型变量的数组;在创建数组实例时,数组的元素不能使用参数化的类型。
例如: Vector<Integer>[] vector = new Vector<Integer>[10];//编译报错
T[] arr = new T[10];//编译报错
可以用类型变量表示异常,成为参数化的异常,可以用于方法的throws列表中,
但是不能用于catch子句中。
例如:
private static <T extends Exception> sayHello() throws T{
....
try{
....
}catch(Exception e){ //这里不能将Exception换成T来表示
throw (T)e; //将e包装成T类型的异常抛出
}
}
在泛型中可以同时有多个类型参数
练习:
// 1.编写一个泛型方法,自动将Object类型的对象转换成其他类型。
public static <T> autoConvert(Object obj){
return (T)obj;
}
// 2.定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象。
public static <T> fillArray(T[] arr, T t){
for(int i=0; i<arr.length; i++){
arr[i] = t;
}}
// 3.采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容。
// 在这种情况下,前面的通配符方案要比范型方法更有效,// 当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,
// 即同一个类型变量在方法签名的两处被使用,
// 或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需要使用范型方法。
public static <T> void printCol(Collection<T> col){
for(T t : col){
System.out.print(t+ "\t");
}
}
// 4.定义一个方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中。
public static <T> void copyCol2Arr(List<T> col, T[] arr){
col.toArray(arr);
}
//5.定义一个方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中。
类型参数的类型推断
根据调用泛型方法时实际传递的参数类型或者返回值的类型来推断; 具体规则:1.当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,
2.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定
3.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型
4.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型
5.参数类型的类型推断具有传递性,
下面第一种情况推断实际参数类型为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);
定义泛型类型:(在类上定义泛型的就是泛型类型)
//dao : data access object -> crud: create, read,update,delete
示例:
public classGenericDao<T>{
public void add(T t){}
public T findById(int id){return null;}
public Set<T> findByConditions(String where){return null;}
public void delete(T t){}
public void delete(int id){}
public void update(T t){}
}
注意:当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),
而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,
所以静态成员不应该有类级别的类型参数。
通过反射获得泛型的参数化类型
例子:获得集合中的元素的类型 /*
通过反射获得泛型的参数化类型
例子:获得集合中的元素的类型
*/
import java.lang.reflect.*;
import java.util.*;
class GenericTypeTest {
public static void main(String[] args) {
Method applyMethod = null;
try{
//反射得到类中的某个方法的字节码
applyMethod =
GenericTypeTest.class.getMethod("applyVector", Vector.class);
}catch(NoSuchMethodException ex){
ex.printStackTrace();
}
//得到该方法上的所有参数的类型信息
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){}
}
类加载器
1.Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
BootStrap :不是java类,JVM内核中
ExtClassLoader
AppClassLoader
2.类加载器也是Java类,因为其他“是java类的类加载器”本身也要被类加载器加载,
显然必须有第一个类加载器不是java类,就是 BootStrap 。
3.Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,
在实例化每个类加载器对象时,
需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载。
类加载器类ClassLoader:
public abstract class ClassLoader
构造方法(只能继承用):protected ClassLoader();
protected ClassLoader(ClassLoader parent);
//需要指定父级加载器
方法:
public ClassLoader getParent();
//返回委托的父类加载器
public URL getResource(String name);
//查找具有给定名称的资源
public Class<?> loadClass(String name);
//加载指定类名的类,返回该类的Class
//加载该类时,首先调用父级加载器加载该类,没找到,则回到本加载器中来
//再调用本类的findClass(String name)方法
protected Class<?> findClass(String name);
//查找指定类名的类,返回该类的字节码,自定义类加载器需要重载这个方法
protected Class<?> defineClass(byte[] b, int off, int len);//过时
protected final Class<?> defineClass(String name, byte[] b, int off, int len);
//参数数组时Class的二进制数据,返回一个Class的字节码, 自定义类加载器时使用
1.每个Class对象都包含一个对定义它的ClassLoader的引用。
2.数组类的Class对象不是由类加载器创建的,而是由Java运行时根据需要自动创建。
数组类的类加载器由Class.getClassLoader()返回,该加载器与其元素类型的类加载器
是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。
3.应用程序需要实现ClassLoader的子类,以扩展Java虚拟机动态加载类的方式。
4.类加载器通常由安全管理器使用,用于指示安全域。
5.通常情况下,Java虚拟机以与平台有关的方式,以本地文件系统中加载类。
示例:
public class ClassLoaderTest{
public static void main(String[] args){
//得到加载本类的加载器的名称
String name = ClassLoaderTest.class.getClassLoader().getClass().getName();
//得到加载System的加载器;会返回null,就是 BootStrap.
ClassLoader loader = System.class.getClassLoader();
}}
类加载器的树状结构和委托机制
~------------------~----------------------------------------------------------------------------~|根加载器: | BootStrap 加载: JRE/lib/rt.jar |
|------------------|------------------------------------------------------------------------------ |
| | ExtClassLoader 加载: JRE/lib/ext/*.jar |
|------------------|------------------------------------------------------------------------------- |
| | AppClassLoader 加载: ClassPath指定的所有jar或者目录 |
|------------------|------------------------------------------------------------------------------- |
|自定义类加载器 | 加载自己指定的特殊目录 |
~--------------------------------------------------------------------------------------------------~
类加载器的委托机制
当Java虚拟机要加载一个类时
1.首先当前线程的类加载器去加载线程中的第一个类.
2.如果类A中引用类类B,Java虚拟机将使用加载类A的类加载器来
加载类B
3.还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去
加载某个类.
每个类加载器加载类时,又先委托给其上级类加载器.
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,
则抛出ClassNotFoundException.
编写自己的类加载器
MyClassLoader 指定其父级类加载器AppClassLoader,加载一个特定目录中的类,
并且特定目录中的类需要加密,
只能使用自定义的类加载器来加载解密
1.必须继承抽象类 ClassLoader
2.覆盖方法protected Class findClass(String name){}
3.覆盖方法protected Class<?> defineClass(String name, byte[] b, int off, int len){}
4,自定义一个方法从.class文件中读取内容
示例:
class NetworkClassLoader extends ClassLoader{
public Class findClass(String name){
byte[] b = loadClassData(name); //自定义方法得到类的二进制数据
return defineClass(name, b, 0, b.length); //将二进制数据转换成字节码返回
}
public byte[] loadClassData(String name){} //从输入流中得到.class文件的二进制数据
}
模板方法设计模式:
模板方法模式是通过把不变的的行为搬移到超类,去除子类中重复的代码来体现它的优势;
当不变的和可变的行为在子类实现中混合在一起的时候,不变的行为就会在子类中重复实现,
我们通过模板方法模式把这些行为搬移到单一的地方,这样就可以帮助子类摆脱重复不变行为的纠缠。
例如ClassLoader抽象类, 封装了loaderClass(),封装了defineClass(),这些是加载类中不变的行为,
只有findClass()方法中的行为是不同加载类需要实现的不同的行为
编写自己的类加载器
/
知识讲解:
自定义的类加载器的必须继承ClassLoader
loadClass方法与findClass方法
defineClass方法
编程步骤:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
实验步骤:
对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast
运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
///
//加密解密的方法//:
public static void cypher(InputStream in, OutputStream out) throws Exception{
//下面的这段代码可能遇到255的字节,当成byte就成了 -1
int b = 0;
while( (b=is.read()) != -1 ){
out.write( ((byte)b) ^ 0xff );
}
}
一个类加载器的高级问题分析
///
编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,
正常发布后,看到打印结果为WebAppClassloader。
把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。
把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。
父级类加载器加载的类无法引用只能被子级类加载器加载的类.
//
代理
概念和作用
1.编写一个与目标类具有相同功能的代理类,代理类的每个方法调用目标类的相同方法,并且在调用方法时加上系统功能的代码.
2.如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在
配置文件中配置是使用目标类,还是代理类,这样以后很容易切换,譬如,想要日志功能时
就配置代理类,否则配置目标类.
AOP :
Aspect oriented program:面向方面的编程
AOP的目标就是要使交叉业务模块化,可以采用将切面代码移动到原始方法的周围,
这与直接在方法中编写切面代码的运行效果是一样的.
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术
动态代理技术
1.JVM可以再运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类.
2.JVM生成的动态类必须实现一个或者多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标
类的代理.
3. CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接
口的类生成动态代理类,那么可以使用CGLIB库.
4.代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还
可以在代理方法中的如下四个位置加上系统功能代码:
a,在调用目标方法之前
b,在调用目标方法之后
c,在调用目标方法前后
d,在处理目标方法异常的catch块中
分析JVM动态生成的类
创建实现类Collection的接口的动态类和查看其名称,分析Proxy
接口InvocationHandler
只有一个方法:
public Object invoke(Object proxy, Method method, Object[] args)
代理类
java.lang.reflect.Proxy
构造方法:
protected Proxy(InvocationHandler h);
//实现代理类的构造方法,没有无参的构造方法,
//所以不能直接用Class.newInstance()来构造Proxy
方法:
public static InvocationHandler getInvocationHandler(Object proxy)
//得到指定的代理实例对象的InvocationHandler
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces);
//动态生成的类,因为只在内存中存在,所以需要手动指定类加载器,
public static boolean isProxyClass(Class<?> cl)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
//得到代理类的实现对象的简洁的方法
Client程序调用objProxy.add("abc")方法时,涉及三个要素:objProxy对象,add方法,"abc"参数
代理类会将从Object中继承的hashCode(), equals(),toString()方法传递给InvocationHandler 处理,
从Object中继承的其他类代理类会自己处理.
让动态生成的类成为目标类的代理
怎样将目标类传过去?
让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量
怎样封装系统功能传给InvocationHandler?
将系统功能代码模块化,即将切面代码也改为通过参数形式提供,
把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数
传递,接受者只要调用这个对象的方法,即等于执行类外界提供的代码.
为bind方法增加一个Advice参数.(Advice接口)
import java.lang.reflect.*;
import java.util.*;
public class ProxyTest {
public static void main(String[] args) throws Exception{
Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//show(clazz);
//createInstance(clazz);
//createInstance2(clazz);
//createInstance3(clazz);
final Collection target = new ArrayList();
final Advice advice = new MyAdvice();
Collection c = (Collection)getProxy(target, advice);
c.clear();
c.size();
c.add(1);
c.add(new ProxyTest());
}
//将目标对象提取出来,将需要的额外功能封装到接口Adice中,使得方法更为通用
private static Object getProxy(final Object target, final Advice advice){
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args){
advice.beforeMethod(method);
Object retVal = null;
try{
retVal = method.invoke(target, args);
}catch(IllegalAccessException ex){
ex.printStackTrace();
}catch(InvocationTargetException ex){
ex.printStackTrace();
}
advice.afterMethod(method);
return retVal;
}
}
);
return proxy;
}
public static void createInstance3(Class clazz) throws Exception{
Collection proxy = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args){
return null;
}
}
);
}
/**
根据代理类的字节码Class得到构造器,
创建InvocationHandler的匿名内部类作为参数传递给Proxy的字节码的构造器,得到Proxy的对象实例
*/
public static void createInstance2(Class clazz) throws Exception{
Constructor constructor = clazz.getConstructors()[0];
Collection collection = (Collection)constructor.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args){
return null;
}
});
}
/**
创建一个InvacationHandler的子类,
根据代理类的字节码Class得到构造器,
将InvocationHandler的子类作为参数传递给Proxy的构造器,得到代理类的对象实例
*/
public static void createInstance(Class clazz) throws Exception{
//需要创建一个InvocationHandler接口的实例对象传递给代理类的构造方法
class MyInvocationHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args){
return null;
}
}
//代理类没有无参的构造方法,所以需要为newInstance()传递参数.
Collection proxy = (Collection)clazz.getConstructors()[0].newInstance(new MyInvocationHandler());
System.out.println(proxy);
}
//查看代理类的字节码的所有构造器和所有方法
public static void show(Class clazz){
System.out.println(".......show Constructors............");
Constructor[] cs = clazz.getConstructors();
for(Constructor c : cs){
StringBuilder sb = new StringBuilder();
sb.append(c.getName());
sb.append("(");
Class[] clazzType = c.getParameterTypes();
for(Class cl : clazzType){
sb.append(cl.getName() + ",");
}
if(clazzType != null && clazzType.length >0){
sb.deleteCharAt(sb.length() - 1);
}
sb.append(")");
System.out.println(sb);
}
System.out.println(".......show Methods............");
Method[] ms = clazz.getMethods();
for(Method m : ms){
StringBuilder sb = new StringBuilder();
sb.append(m.getName());
sb.append("(");
Class[] clazzType = m.getParameterTypes();
for(Class cl : clazzType){
sb.append(cl.getName() + ",");
}
if(clazzType != null && clazzType.length >0){
sb.deleteCharAt(sb.length() - 1);
}
sb.append(")");
System.out.println(sb);
}
}
}
//切面接口,切入到InvocationHandler中的invoke方法中
interface Advice{
void beforeMethod(Method method);
void afterMethod(Method method);
}
//实现类切面接口的类实例
class MyAdvice implements Advice{
private long beginTime = 0;
public void beforeMethod(Method method){
beginTime = System.currentTimeMillis();
}
public void afterMethod(Method method){
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "消耗的时间 : " + (endTime - beginTime));
}
}
---------------------------android培训、java培训、期待与您交流!---- -------------------------