注释 1.5新特性: 枚举,注解,泛型。
1:静态导入
* 如:import static java.lang.*
2:重载重写
* overload:重载
* override:重写,用可变参数实现的重载更加好的方便使用。
3:自动装猜箱子
* Integer a1 = 1,Integer a2 = 1;这时候a1和a2相同。
* 当a1,a2的值不再-128-127之间就是不相同。因为在那个范围的时候它就会自动
到常量值中查询-128-127的值,不在该范围的时候就创建出来。
这种a1,a2在-128-127之间公用一个值的时候叫做享员模式。
* 享员模式:多个相同对象的数值,只存在一份。
* String类型就使用了享元模式.String对象是不变对象,一旦创建出来就不能改变,如果需要改变一个字符串的值,就只 好创建一个新的String对象,在JVM内部,String对象都是共 的。如果一个系统中有两个String对象所包含的字符串相 同的话,JVM实际上只创建一个String对象提供给两个引用,从而实现String对象的共享,String的inern()方法给出这 字符串在共享池中的唯一实例.
4:枚举
* 当有的时候我们想固定一些值,只能是这些值的时候用它控制:如星期几
* 枚举的值其实就是一个对象。枚举的构造方法必须是私有的,不准别人随意创建枚举类型对象。
* 枚举的值初始化默认是调用枚举的默认构造方法。无参数形式。要想调用有参数形似的构造方法
* 在值的后面如FRI(1) 这样调用参数构造函数。
枚举对每个值其实是枚举的一个对象,当枚举里面有抽象方法的时候,枚举就自认为是一个抽象类,所以那些对象是不可能被创建的,这时候每个值就要是一个子类,通过匿名类的方式
实现抽象方法,从而构造对象。
枚举类只有一个对象值的时候等于差不多是单列,因为构造方法私有。
5:消灭if else 如下:
消灭前代码:
package com.moom;
public class WeekDay {
private WeekDay(){};
public static final WeekDay MON = new WeekDay();
public static final WeekDay TUS = new WeekDay();
public static final WeekDay WED = new WeekDay();
public WeekDay getNextDay() {
if(this == MON) {
return TUS;
}else if(this == TUS) {
return WED;
}else {
return MON;
}
}
public String toString() {
if(this==MON) {
return "MON";
}else if(this==TUS) {
return "TUS";
}else {
return "WED";
}
}
}
消灭后代码
package com.moom;
public abstract class WeekDay2 {
private WeekDay2(){};
public static final WeekDay2 MON = new WeekDay2() {
@Override
public WeekDay2 getNextDay() {
return TUS;
}
@Override
public String toString() {
return "MON";
}
};
public static final WeekDay2 TUS = new WeekDay2() {
@Override
public WeekDay2 getNextDay() {
return WED;
}
@Override
public String toString() {
return "TUS";
}
};
public static final WeekDay2 WED = new WeekDay2() {
@Override
public WeekDay2 getNextDay() {
return MON;
}
@Override
public String toString() {
return "WED";
}
};
public abstract WeekDay2 getNextDay();
public abstract String toString();
}
6:class
* 所有类的一个总称。
如:所有的人是是人,所有的类是class
* class是字节码
* 获取类的class方式: eg:Person.class, person.getClass(),Class.forName("com.moom.Person")//有的自后从JVM中拿出来,没有的时候类加载器加载。;
9个预定义class,8个基本(int ,long。。。。。),1个void.
* 实验class的方法,包括数组的class.
* eg:
Class c1 = s1.getClass();
Class c2 = s2.getClass();
System.out.println(c1 == c2); // 表明只會生成一份字節碼文件
int a = 1;
System.out.println(int.class.getName()); //获取Int class的名字
System.out.println(int.class.isPrimitive()); //查看class是否是基本的class--int,double,boolean......
System.out.println(void.class.getName());
System.out.println(int[].class.isPrimitive()); // 表明数组不是基本类型CLASS
7:反射:反射就是把JAVA类中的各种成分映射成相应的JAVA类。
* 构造方法,根据class可以获取所有类型的构造方法,方法可以根据参数获取相应的构造方法,然后根据构造方法可以获取对应的对象。
Constructor可以获取对象的实例方法,根据相应的参数实例成对应的对象。
这样做有一个不好,就是每次我要通过反射的方式获取对象的时候,都要通过class获取constructor在用constructor构造对象。
JAVA给我们提供了一个方便点的方式,就是Class直接由一个newInstance()的方法,返回实例。
这个方法就是说调用默认的无参数的构造方法,里面的实现机制其实也同获取了无参数的constructor来构造对象。
* eg:
Constructor s = String.class.getConstructor(StringBuffer.class);
String s1 = (String)s.newInstance(new StringBuffer("a")); //根据构造方法,获取对象
System.out.println(s1); //构造方法是怎么样的newInstance参数也要传相应类型的参数
char[] c = {'c'};
Constructor s2 = String.class.getConstructor(char[].class);
String s3 = (String)s2.newInstance(c);
System.out.println(s3);
* Field:每个类的对应的字段。 通过它能获取具体对象的字段的值,要注意的是:字段是属于所有的类的,所以要获取具体对象的值,就是Field.get(对象名字);
* eg:
private static Object getUser(String string, Map map) throws Exception{
Object o = Class.forName(string).newInstance(); //通过反射创建传过来字符串的对象
Field[] fields = o.getClass().getDeclaredFields(); //访问对象类中所有的字段
for(Field field:fields) {
field.setAccessible(true); //把字段的可修改属性设置成可以
field.set(o, map.get(field.getName())); //给对应对象的对应字段设置值。
}
return o;
}
* 可以在做练习: 题目:在一个类中有成员变量字符串值。通过反射把他们的值改变。
* Method:反射方法。
可以通过这个方式获取类的对应的方法。
String str1 = "abcd";
Method methodCharAt = str1.getClass().getMethod("charAt", int.class);//获取对应的charAt方法,并且参数是一个int
System.out.println(methodCharAt.invoke(str1, 1)); //调用方法,方法的第一个参数是对象的名字,第二个参数是方法的实际参数值
* //思考,当第一个参数是Null的时候,显然这个调用不属于任何对象,它就是类方法。
Method m = A.class.getMethod("getString", int.class);
System.out.println(m.invoke(null, 1));
* 当我们要通过反射调用时数组参数形式的方法的时候,当数组的参数是基本类型的时候。
比如方法 pulic static String getString(int[] x) 这时候我们在调用的时候可以这样method.invoke(null,new int[]{1,2,3});
但是当是String 或者对象类型的时候就不可以这样传递了,我们需要打包如method.invoke(null,Object(new String[]{"1","2","3"}));
或者method.invoke(null,new Object[]{new String[]{"1","2","3"}});
对不是静态的方法也是同样的效果
解释:对于基本类型,它能自动拆解成数组形式,对于类类型数组,它把所有的当成一个包的整体等于是一个参数,从而会出现错误。
要先打下包,如上。
对数组的反射: 对于具有相同类型,相同维数长度的数组的class是相同的。
*eg: 数组的例子:
int[] a1 = new int[3];
int[] a3 = new int[4];
int[][] a2 = new int[2][3];
String[] s1 = new String[3];
String[][] s2 = new String[3][4];
String[] s3 = new String[]{"a","b","c"};
System.out.println(a1.getClass()==a3.getClass());
//System.out.println(a1.getClass()==s1.getClass());
Object object1 = a1;
Object object2 = a2;
//不行,因为它默认的是转化成数组之后就是Int了,int不是Object
//Object[] o1 = a1;
//可以是因为转成的是int[]数组,数组是对象
Object[] o2 = a2;
//这样String类型是Object就可以当成数组处理
System.out.println(Arrays.asList(s3));
//int非组数类型,当成泛型对待,从而打印出的只有一个元素
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a2));
问题:打印任意类型的对象,是数组的时候也要挨个打印print(object);
private static void print(Object obj) {
Class clazz = obj.getClass();
if(clazz.isArray()) {
int len = Array.getLength(obj);
for(int i=0; i<len; i++) {
System.out.print(Array.get(obj, i));
}
}else {
System.out.println(obj);
}
}
如何获取数组当中元素的类型?System.out.print(Array.get(obj, i).getClass().getName()); 张老师错了?
8:hashcode
* 首先只是在实现了hash算法的集合当中才一般用用到它,比如hashset. 比如arraylist不用。就是说在一个类里重写hashcode方法在有利用到
实现hash算法的时候才用到它,不然可以不重写它没任何问题。
*hashset和arraylist区别。 当有一个对像要放入到arraylist当中的时候(其实是放对象的引用),放多少它的长度就是多少。
当时hashset的时候,它会根据你放入的这个对象计算hash值,当计算出来之后存在就不帕耍辉诰头湃搿?
所以List的时候你重复放入一个对象集合的大小会增加,hash的时候它就不会增加。
例子:
public static void main(String[] args) throws Exception{
Collection c = new HashSet(); //是hash的时候c的size是3,list的时候size是4
Point p1 = new Point(3,3);
Point p2 = new Point(5,5);
Point p3 = new Point(3,3);
c.add(p1);
c.add(p2);
c.add(p3);
c.add(p1);
System.out.println(c.size());
}
*从上面的程序我们其实应该知道的是,p1和p3其实应该是相等的,所以当成一个就行了。所以这时候我们就应该hash的时候size是2.但是对象new来的时候都是根据内存的方式
计算它自己的hash值的,所以我们这时候就是不一样的了,从而size是3. 所以在Point对象里面我们重写hashcode方法就行了,这样就能保证对象计算出的hash值是一样的,从而
也不会说重复放入我们觉得相同的对象了,但是只是重写hashcode方法没用,因为它只是计算hash值的东西,我们还不能确定两个对象的数据是否相同。所以要同时重写equal方法,
保证两个对象的equals是真。
问题:如下:
Point p1 = new Point(3,3);
Point p2 = new Point(5,5);
Point p3 = new Point(3,3);
c.add(p1);
c.add(p2);
c.add(p3);
c.add(p1);
p1.setY(4); //这里我们改变p1的y值
c.remove(p1); //我们这里本来是想把p1移除的,抱歉因为你修改了y的值,我们移除的去算Hash值是和存进去的不一样的,无法移除,发生内存泄露。
System.out.println(c.size());
注释:所有参加hash值计算的字段的改变,使得我们无法再寻找到我们想移除的对象。
9:框架和工具类的异同。
* 框架是来调用你的东西,工具是你去调用的东西。
* 从配置文件中读取类名字,动态实例化对象。
* 配置文件的路径,
1:应该动态的获取。配置文件配置
2:放在classpath下获取。
* 用classloader加载,
* 用类自己加载(其实还是间接的用了)
* 配置文件一般通过classloader加载。一般的框架也都是如此。
* 框架中我们一般不用New,也不想固定说就是hashSet所以我们把它配置起来
对于文件的读取,我们现在是这样放的相对路径,现实中我们一般不可以这样
因为我们是要给别人用,每个人的路径都是不同的,有两种方式解决,就是
每个人用的时候配置一下className.properties文件的路径,然后我们读取
路径下的文件就可以了。
最常用的一种是把文件放入classpath下,放完之后就是用classloader把它给
加载出来就行了。
//InputStream in = new FileInputStream("className.properties");
//这样通过classlaoder我们就加载进去了,默认的路径是项目的下面跟路径。当放在某个包下的时候就加包名
//InputStream in = TestClassLoaderAndPath.class.getClassLoader().getResourceAsStream("className.properties");
//InputStream in = TestClassLoaderAndPath.class.getClassLoader().getResourceAsStream("source/className.properties");
//也可以这样,直接通过类读取,其实就是中间也是通过classloader只是方式不一样。更加方便
InputStream in = TestClassLoaderAndPath.class.getResourceAsStream("/source/className.properties");
Properties p = new Properties();
p.load(in);
in.close();
String className = p.getProperty("className");
Collection c = (Collection)Class.forName(className).newInstance();
10:javaBean(内省(introspector))
* introspector对JAVABEAN进行操作----特殊的JAVA类
* 他其实也是对JAVA类的操作,它的功能反射也能做到,但是它用来操作标准的JAVA类,更加方便。
*比如: 当我们通过反射来获取某个类的的某个字段的set方法,然后根据set方法设置值,这时候
我们就要获取这个字段值,然后再和set组合成方法名,在调用方法,比较麻烦。
然而我们用Intorspector的时候就方便多了。
比如我同样式得到某个类某个属性的set方法,其实就是对属性写的方法吧
我们只要:PropertyDescriptor p = new PropertyDescriptor(属性名字,对应的类对象);
p.getWriter()就得到了属性的set方法等于就是,就直接在调用设置值了,
所以这种方式比反射更加方便快捷。
两种方式的对字段的设置与读取。
public static void main(String[] args) throws Exception{
Point p1 = new Point(3, 5);
setPropert(p1, "x", 4);
System.out.println(getProperty(p1, "x"));
}
public static void setProperty(Object object,String s,Object value) throws Exception{
PropertyDescriptor pd = new PropertyDescriptor(s, object.getClass());
//拿到S属性的写方法就是setS(),在调用返回
Method methodWrite = pd.getWriteMethod();
//获取到了方法,并且调用设置值
methodWrite.invoke(object,value);
}
public static Object getProperty(Object object,String s) throws Exception{
PropertyDescriptor pd = new PropertyDescriptor(s, object.getClass());
//拿到S属性的读方法就是getS(),在调用返回
Method methodRead = pd.getReadMethod();
//获取到了方法,并且调用设置值
return methodRead.invoke(object);
}
//一种更加复杂的实现
public static void setPropert(Object object,String s,Object value) throws Exception {
PropertyDescriptor[] pds = Introspector.getBeanInfo(object.getClass()).getPropertyDescriptors();
for(PropertyDescriptor pd:pds) {
if(pd.getName().equals(s)) {
pd.getWriteMethod().invoke(object, value);
break;
}
}
}
11:既然各种对类的操作我们都用到,然后直接又写,我们就打包咯,随时能用,所以有个开源的包beanutils就对这些功能进行了 包装。
public static void main(String[] args) throws Exception{
Point p1 = new Point(3, 5);
//通过BeanUtils设置字段的值和读取字段的值
BeanUtils.setProperty(p1, "x", 4);
System.out.println(BeanUtils.getProperty(p1, "x"));
//写成字符串的形式也行,因为BeanUtils是用String的方式来设置字段的值
BeanUtils.setProperty(p1, "x", "47");
System.out.println(BeanUtils.getProperty(p1, "x"));
//输出是:java.lang.String
System.out.println(BeanUtils.getProperty(p1, "x").getClass().getName());
//PropertyUtils.setProperty(p1, "x", "66");错,因为PropertyUtils不会把字段当成字符串处理。
PropertyUtils.setProperty(p1, "x", 66);
System.out.println(PropertyUtils.getProperty(p1, "x"));
//输出是:java.lang.Integer
System.out.println(PropertyUtils.getProperty(p1, "x").getClass().getName());
}
12:JAVA注解
* 注解能使我们发现自己程序中可能存在的错误,比如当你写了override注解的时候,你覆盖父类的方法,如果覆盖不正确,编译就不可能会通过。
这样能更好的有助于我们发现错误,解决错误。
* 注解可以让我们告诉别人我们的类库中的某个方法已经废弃了,可以使用别的方式代替。可以用deprecated注解。
* 注解也同样可以让我们消除我们调用已经废弃的方法而产生的警告。 用@SuppressWarnings("deprecation")
* 注解的标记可以在各种地方,如包,类,方法,变量......
* 注解类的编写
public @interface moom {
String str(); //一个方法,方法的返回值是String,每个用到注解的地方相当于一个Moom注解实例
}
* 注解的生命周期
就是说注解是在什么情况下起作用:三种:在编写代码的时候(source),在编译的时候存在(class),在运行的时候存在(runtime)。
三种其实是一个枚举,枚举的名字是:RetentionPolicy
* 注解的获取与例子
moom mo = Test.class.getAnnotation(moom.class);
mo.str();//访问注解的方法,返回写注解时候的数值
* 注解retention
前面说到注解有三种类型,这个也是一个注解,它就是用来指定注解的生命周期。
eg:
@retention(RetentionPolicy.RUNTIME)
* 注解target
用来指定注解可以用在什么地方。
eg:
@Target(value={ElementType.METHOD}):指定名用方法上,还有type(类上),package,annotation......
* 可以给方法设定默认值,当我们没有默认值的时候,我们可以发现在我们使用这个注解的时候我们必须给注解所拥有的方法指定
相应的值。如下方式:String str() default "abc";这样我们就默认的设置了方法的默认返回值。在调用的时候,如果我们没有
给他设定相应的值,它就取这个默认的值。
* 注解里的方法可以返回好多类型,比如:string,int,数组,class,annotation,enum.....
eg:
public @interface moom {
String str() default "a";
int intt();
String[] stt();
Light getLight();
Retention getRoom();
Class getC();
}
用的时候相应的设置方法如下:
@moom(intt=1,stt={"a","b","c"},getLight=Light.GREEN,getRoom=@Retention(value = RetentionPolicy.RUNTIME),getC=String.class)
打印设置的相关信息:
moom mo = AnnotationTest.class.getAnnotation(moom.class);
System.out.println(mo.str());
System.out.println(mo.intt());
System.out.println(Arrays.asList(mo.stt()));
System.out.println(mo.getLight().getLigth().name());
System.out.println(mo.getRoom().value());
System.out.println(mo.getC().getName());
13:泛型
* 泛型几基本英雄,消除繁琐的类型转换。
有时候我们给一个集合或者在反射的时候要指定一些东西,拿出来的时候是一个Object,这时候我们就要用类型转换转成相应的类型。
如果用类型,我们在放之前就指定是什么类型的,就可以防止类型转换了,因为它知道你要拿的类型是什么类型的。
* 使用的泛型之后的比如所有集合,虽然它们的参数化类型不一样,但是它还是和JDK4一样,为的效率,每次我们拿它們的class拿到的是同一份
eg:
Collection<String> con = new ArrayList<String>();
Collection<Integer> con1 = new ArrayList<Integer>();
//true 说明不同类型参数的集合,其实拿到的是同一份class字节码
System.out.println(con.getClass()== con1.getClass());
* 使用了参数类型化的集合之后我们是不能放其它的东西 在里面了。
如:Collection<String> con = new ArrayList<String>();我们是不在在编辑的时候con.add(1).但是我们知道的是JDK4不用泛型的时候是能
放进去任何类型的。所以应该支持,这时候我们就应该想到用反射,在运行的时候它才知道我们放进去的是int型 的,从而也就不会在编译的时候
报错,不让我们放进去了。
eg:
Collection<String> con = new ArrayList<String>();
con.add("abc");
Method methodAdd = con.getClass().getMethod("add", Object.class);
methodAdd.invoke(con, 1);
System.out.println(con);
这样我们就是成功的把INT型的数值放进了在声明 的时候是放入String的集合中。
* 但是有的时候我们还是希望能够处理各种各样的参数类型化的集合。
eg:有个方法,我们只要给它一个集合,不管是什么样的参数类型的。都能打印出。
这时候 就用到了泛型的通配符?
public static void print(Collection<?> con) {
System.out.println(con);
}
这样的话,我们定了?的化,它就能接收不同类型的参数类型的集合了。
* Collection<? extends Object> 有的时候我们会看到这样的。 其实我们也应该可以发现也有?匹配,当是?的时候它是可以匹配任何参数化类型的数据的。
当用了extends的时候,它的意思就是说只能匹配是继承了Object的类型(包括OBject本身)。
eg:Collection<? extends Number> con = new ArrayList<Integer>();对的
Collection<? extends Number> con = new ArrayList<String>();错的
*小例子
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Set<Entry<String,Integer>> set = map.entrySet();
for(Map.Entry<String, Integer> m:set) {
System.out.println(m.getKey() + "," + m.getValue());
}
* 泛型方法 public T <T> getS(T a ,T b ) { return T;}
* 泛型类
public class DAO<T> {
public void add(T t) {
}
public T find(int id) {
return null;
}
}
* 获取方法上的参数里的泛型参数的类型
Method method = GenericTest.class.getMethod("pt", Vector.class); //获取Pt方法,参数是Vector
Type[] type = method.getGenericParameterTypes(); //获取方法的泛型化参数
System.out.println(type[0]);
ParameterizedType t = (ParameterizedType)type[0]; //拿到第一个泛型化的参数
System.out.println(t.getActualTypeArguments()[0] == String.class); //拿到第一个参数的泛型的实际类型,看看它的CLASS是否是String.class
System.out.println(t.getRawType()==VeLASS); //拿到第一个参数的类型,看它是的CLASS是否是Vector.class.
14:类加载器。
* 默认有三个:BootStrap,ExtClassLoader,AppClassLoader
* 类加载器也是类,但是类加载器又是谁加载的呢。是由BootStrap,它不是一个类,它是JVM开的时候就在内存当中存在,是由C++编写的。
* BootStrap----最上面的类加载器,主要用来加载jre/lib/rt.jar下的类,就是系统给我们提供的常用的类
ExtClassLoader----BootStrap的子加载器,主要是用来加载jre/lib/ext/*.jar.所以当我们想把加载我们自己的类的时候,我们就可以把这
些类放到该目录下就能加载访问了,从而不会出现classNotFoundException.
AppClassLoader---ExtClassLoader的子加载器,主要用来加载classpath下的类。所以我们只要设置classpath就能设访问到我们自己想要访问的类了。
* 类加载器的机制:我们应该知道当我们想要写一个类,比如自己的类java.lang.System来覆盖系统的这个类,这是不可能的,因为JVM是这样做的。当你
想要加载一个类的时候,先用这个类比如AppClassLoader加载,AppClassLoader然后叫它的爸爸ExtClassLoader加载,ExtClassLoader又叫BootStrap加载
它找到了JAVA.LANG.STRING这个类,然后加载返回去就行了。 这样做一是能避免别人这样做覆盖是吧。 二是,如果我们有多个加载器,每个加载器都加载
同一个类,这样的话每个都行成一份class,给内存造成压力。所以交给爸爸的话,就加载一份,以后你们要加,直接向爸爸要就行了。
* eg:
//这样的话会报空指针错误,因为它是用BootStrap来加载的,它是用c++编写的,根本不存在类的class,所以被是空
//System.out.println(System.class.getClassLoader().getClass().getName());
//这样打印我们就会发现打印出null
System.out.println(System.class.getClassLoader());
//我们发现时AppClassLoader加载的,因为ClassLoader也是一个类,所以也是.getClassLoader().getClass().getName()访问
System.out.println(DAO.class.getClassLoader().getClass().getName());
//我们发现打印的是ExtClassLoader,因为我刚才把AnnotationTest打包到了jre/lib/ext/下去了。
System.out.println(AnnotationTest.class.getClassLoader().getClass().getName());
ClassLoader cl = DAO.class.getClassLoader();
while(cl!=null) {
System.out.println(cl.getClass().getName());
cl = cl.getParent();
}
* 这样的话,我们不就是也可以写自己的类加载器,然后在程序运行的时候指定我们要加载的类,从而不用说每次都把我们想加载的类放到classpath下了。
不过应该我们也能在程序获取AppClassLoader---ExtClassLoader 这些加载器,然后让它们加载我们想加载的类。
刚才自己做了一下实验发现,ExtClassLoader意思就是说它只能加载jre/lib/ext下在类,我本来还想说通过程序运行的时候看看能不能让它加载别的类。
看来是不行的了,还是得自己写classloader,不能在现在的classloader下面做什么不好的想法。
同时让我回想起来了tomcat的类加载机制。
* 利用加密class的方式引出自定义的ClassLoader
首先写一个简单的加密:
public static void main(String[] args) throws Exception{
String s1 = args[0];
String s2 = args[1];
FileInputStream in = new FileInputStream(s1);
FileOutputStream out = new FileOutputStream(s2);
cpy(in,out);
}
private static void cpy(InputStream in,OutputStream out) throws Exception{
int b;
while((b=in.read())!=-1) {
out.write(b ^ 0xff);
}
in.close();
out.close();
}
加密方法是对In文件进行加密,然后输出是Out
从参数中接收源来目的类所在的地方,然后加密后就行了,生成一样的文件名的类。
这时候我们把加密后的class拷贝到没有加密的class上覆盖它。然后我们在运行之
前程序用到class的地方,我们发现就会出错哦。 因为那个class已经不是正常的了
是加密后的了,我们要解密类加载器才能进行加载。
这时候我们就自己定一个类加载器去解释这个类就行了。如下:
public static void main(String[] args) throws Exception{
String s1 = args[0]; //这是运行的时候给我们传过来要加密的class所在的路径
String s2 = args[1]; //把加密后的class放到哪个目录下
FileInputStream in = new FileInputStream(s1);
FileOutputStream out = new FileOutputStream(s2);
cpy(in,out); //加密方法
}
private static void cpy(InputStream in,OutputStream out) throws Exception{
int b;
while((b=in.read())!=-1) {
out.write(b ^ 0xff); //加密很假单,只是异或一下,所解和时候同样用这个方法就解开了
}
in.close();
out.close();
}
private String classDir; //加载的CLASS所在的目录
public MyClassLoader() {}
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = classDir + "/" + name + ".class"; //加载的类的名字
System.out.println(className);
try {
FileInputStream in = new FileInputStream(className);
ByteArrayOutputStream out = new ByteArrayOutputStream();
cpy2(in, out); //把加载的文件流返回byte[]
in.close();
out.close();
byte[] bytes = out.toByteArray();
return this.defineClass(name,bytes, 0, bytes.length) //真正的解析加载成class
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
private static void cpy2(InputStream in,OutputStream out) throws Exception{
int b;
while((b=in.read())!=-1) {
out.write(b); //这个是我用到加载对应文件夹下的class,不是用来加密的。
}
in.close();
out.close();
}
* 这样的话加载方式,我们以后就可以做自己想做的事,自己想加载的class,而不用说用系统给我们的。
* 类加载,当我们加载的时候一个类是用一个类加载器加载,那么它所在的类里的用到的类也是用它自己
类加载器加载上或者父类加载器加载。都不存在的话就出错。就算它的子加载器能加载这个CLASS也会
报错。
15:代理
StringBuilder(线程不安全) StringBuffer(线程安全)
原理:JDK的代理是利用Proxy来创建代理。Proxy.getProxyClass(所有代理类的classloader,所要代理类所要实现的接口);
获取代理类的class之后,我们顺利成章的本来应该是newInstance()出现一个对象形成代理吧,但是事实却不是这样的,我们
通过打印代理类的所有构造方法发现,它只存在一个构造方法,构造方法的参数是InvocationHandler,所以我们要传递一个
InvocationHandler才能创建出代理类。
Proxy.getProxyClass(Test.class,Test.class.getInterfaces()).getConstructor(InvocationHandler.class).newInstance(new InvocationHandlerChildern());
传递一个实现InvocationHandler的接口的类的对象创建实例。
我们发现实现InvocationHandler就要实现它的方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
代理类在调用任何实际的方法的时候都会调用这个方法。所以我们要调用真实方法的时候,应该在实现InvocationHandler接口类里面保存目标类
的一个引用,然后再创建实现InvocationHandler的类对象的时候,传递目标类,然后再Invoke方法里面用真实目标类调用真实方法。在返回结果。
JDK动态代理原理:
客户端(调用的时候传递目标类,而且传递实现InvocationHandler接口类的对象)----->代理类(接收目标类和InvactionHandler对象,保存到自己的变量当中)
------>代理类根据客户段调用的方法,代理类里面的实现也是这样的-------void test() {
invocationHandler.invoke()
}
---->就是说客户端调用test()方法,代理类它也有一个test()方法,然后进行调用这个方法,在用invocationHandler调用Invoke方法,这时候程序就进入到我们
写的invocationHandler的invoke方法里面进行执行,我们就可以在这个方法里面,做一些自己想做的事情,做完之后再用真实的目标对象调用我们确实想调用的
方法,然后结束。
* 代理,我们只是想说通过它能多做一些操作,所以说我们生成代理应该给它传递我们要人家做什么事情的对象,当然我们不肯给代理说你就做这个事情,不能写死
了,那天程序需要改动又要修改源程序,那么不利于扩展,所以我们可以用接口,用一个接口去引用我们想要用的对象,以后每次我想改变我们的代理所需要做的
事情,我们就用新的事情,继承接口,传递对象,就可以实现想要做的事情。 多态的 体现。
* 顺便学习了Spring的AOP实现。在实现的过程中,进一步加强我自己对SPRING的理解和对JDK动态代理的原理。