第一 注解
一、概述:
1、注解是JDK1.5出现的新特性
2、注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则没有某种标记。
3、加了注解以后,java编译器、开发工具和其他应用程序就可以用反射来了解自己的类及各种元素上有无何种标记,有什么标记,就会做出相应的处理。
4、注解可以加在包、类、字段、方法、方法参数,以及局部变量上等等。
5、在java.lang包下的annotation包中提供了最基本的注解。
6、格式:@注解类名()。如果有属性,则在括号中加上属性名(可省略)和属性值。
二、java中三种最基本的注解:
1、@SuppressWarning(”deprecation”)--->压制警告
SupressWarning是告知编译器或开发工具等提示指定的编译器警告;
”deprecation”是告知具体的信息即方法已过时。
2、@Deprecated--->提示成员等已经过时,不再推荐使用。
源代码标记@Deprecated是在JDK1.5中作为内置的annotation引入的,用于表明类(class)、方法(method)、字段(field)已经不再推荐使用。
3、@Override--->提示覆盖(父类方法)
加上此注解,,可对自己类中的方法判断是否是要覆盖的父类的方法,典型的例子即在集合中覆盖equals(Object obj)方法,其中的参数类型必须是Object,才能被覆盖,若不是,加上此注解就会提示警告。
三、注解类:
1、定义格式:@interface 名称{statement}
2、元注解(注解的注解)
一个注解有其生命周期(Retetion)和存放的位置(Taget),这就可以通过元注解说明。
1)Retetion:用于说明注解保留在哪个时期,加载定义的注解之上。
①一个注解的声明周期包含:
java源程序--(javac)-->class文件--(类加载器)-->内存中的字节码
第一、当再源程序上加了注解,javac将java源程序编译为class文件,可能会把源程序中的一些注解去掉,进行相应的处理操作,当我们拿到源程序的时候,就看不到这些注解了。
第二、假设javac把这些注解留在了源程序中(或者说留在了class文件中),当运行此class文件的时候,用类加载器将class文件调入内存中,此时有转换的过程,即把class文件中的注解是否保留下来也不一定。
注意:class文件中不是字节码,只有把class文件中的内部加载进内存,用类加载器加载处理后(进行完整的检查等处理),最终得到的二进制内容才是字节码。
②Reteton(枚举类)取值:
Retetion.Policy.SOURSE:java源文件时期,如@Overried和@SuppressWarning
Retetion.Policy.CLASS: class文件时期(默认阶段)
Retetion.Policy.RUNTIME:运行时期,如@Deprecated
2)Taget:用于说明注解存放在哪些成分上,默认值是任何元素
其值可设置为枚举类ElementType类中的任何一个,包括:包、字段、方法、方法参数、构造器、类等值。取值为:
PACKAGE(包声明)
FIELD(字段声明)
ANNOTATION_TYPE(注释类型声明)
CONSIRUCTOR(构造器声明)
METHOD(方法声明)
PARAMETER(参数声明)
TYPE(类、接口(包含注释类型)或枚举声明)
LOCAL_VARIABLE(局部变量声明)
注意:其中代表类的值是TYPE。因为class、enum、interface和@interface等都是属于Type的。不可用CLASS表示。
四、为注解增加基本属性
1、属性:
一个注解相当于一个胸牌,但仅通过胸牌还不足以区别带胸牌的两个人,这时就需要给胸牌增加一个属性来区分,如颜色等。
2、定义格式:public abstract String color();//为注解添加属性,也可定义为String color();默认补齐public abstract
定义缺省格式:String value() default ”ignal”;也就是给其属性一个默认值
3、应用:直接在注解的括号中添加自身的属性,如:
@MyAnnotation(color=”red”)
当属性只有一个value这时可以不用写value=“..”,可以直接写成“..”。如:@SuppressWarnings("deprecation")
五、为注解增加高级属性
1、可以为注解增加的高级属性的返回值类型有:
1)八种基本数据类型
2)String类型
3)Class类型
4)枚举类型
5)注解类型
6)前五种类型的数组
2、数组类型的属性:
定义:int[]arrayArr()
应用:@MyAnnotation(arrayArr={2,3,4})
注:若数组属性中只有一个元素(或重新赋值为一个元素),这时属性值部分可省略大括号。
3、枚举类型的属性:
假设定义了一个枚举类TraffLamp,它是EnumTest的内部类,其值是交通灯的三色。
定义:EnumTest.TrafficLamplamp();
应用:@MyAnnotation(lamp=EnumTestTrafficLamp.GREEN)
4、注解类型的属性:
假定有个注解类:MetaAnnotation,其中定义了一个属性:String value()
定义:MetaAnnotationannotation() default @MetaAnnotation(”xxx”);
应用:@MyAnnotation(annotation=@MetaAnnotation(”yyy”)) --> 可重新赋值
可认为上面的@MetaAnnotation是MyAnnotation类的一个实例对象,同样可以认为上面的@MetaAnnotation是MetaAnnotation类的一个实例对象,调用:
MetaAnnotation ma =MyAnnotation.annotation();
System.out.println(ma.value());
5、Class类型的属性:
定义:Class cls();
应用:@MyAnnotation(cls=ItcastAnnotion.class)
注:这里的.class必须是已定义的类,或是已有的字节码对象
示例:
/*
* 注解相当于一种标记,在程序上加了注解就等于给程序打上了某种标记,然后编译器或开发工具就会采取相应的动作
* 注解可以加在包、字段、方法、方法的参数以及局部变量上
* 注解相当于一个你源程序中要调用的类,要在远程中调用某个类,得先准备好注解类
* 注解的返回值类型可以是基本数据类型(byte,short,int,long,float,double,char,boolean),String,Class,注解类型,还可以是以上类型的数组类型
*/
@MyAnnotation(annotationAttr=@MetaAnnotation("hah"),color="red",value="abc",arrayAttr={1,2,3},lamp=TrafficLamp.GREEN)// 为注解设置属性值
public class AnnotationTest {
@MyAnnotation("abc")//只有一个value需要设定时可以省略value=
public static void main(String[] args) {
if (AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = AnnotationTest.class
.getAnnotation(MyAnnotation.class);
System.out.println(annotation.color());
System.out.println(annotation.value());
System.out.println(annotation.arrayAttr());
System.out.println(annotation.lamp().nextLamp().name());
System.out.println(annotation.annotationAttr().value());
}
}
@SuppressWarnings("deprecation")
// 注解,告诉编译器,知道使用的方法已过时,不用提醒
public void test() {
System.runFinalizersOnExit(true);
}
@Deprecated
// 注解方法已过时
public void method() {
System.out.println("hahah");
}
@Override
public String toString() {
return super.toString();
}
}
@Retention(RetentionPolicy.RUNTIME)//元注解,保存在整个运行时期。@Retention表示注解的生命周期
@Target({ElementType.TYPE,ElementType.METHOD})//指定此注解被用的地方,type是指类上
public @interface MyAnnotation {
//设置缺省属性,也就是在调用注解时,可以不对其进行设置
public abstract String color() default "blue";//为注解添加属性,也可定义为String color();默认补齐public abstract
String value();
int[] arrayAttr() default 1;//当数组中只有一个元素时,可以省略大括号
EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.YELLOW;
MetaAnnotation annotationAttr() default @MetaAnnotation("metaannotation");
}
public @interface MetaAnnotation {
String value();
}
public class EnumTest {
public enum TrafficLamp {
// RED,GREEN,YELLOW;
// 子类实现父类的抽象方法
RED(30) {
@Override
public TrafficLamp nextLamp() {
return GREEN;
}
},
GREEN(40) {
@Override
public TrafficLamp nextLamp() {
return YELLOW;
}
},
YELLOW(5) {
@Override
public TrafficLamp nextLamp() {
return RED;
}
};
private int time;
//构造方法
private TrafficLamp(int time){
this.time = time;
}
public abstract TrafficLamp nextLamp();
}
}
第二 类加载器
一、概述:1、简单的说,类加载器就是加载类的工具。当用到一个类是,java虚拟机就需要类字节码加载进内存。
2、类加载器的作用:将.class文件中的内容加载进内存进行处理,处理完后的结果就是字节码。
3、默认的类加载器:
①、Java虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader
类加载器本身也是Java类,因为它是Java类,本身也需要加载器加载,显然必须有第一个类加载器而不是java类的,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字,获得的只能是null。
4、Java虚拟机中的所有类加载器采用子父关系的树形结构进行组织。类加载是从上往下的这么一种结构,也就是加载类时首先从父类开始查找,父类没有找到再交给子类,如果到了加载器的发起者位置都还没有找到需要被加载的指定文件,就会抛出异常。类加载器树状图:
实例:
public static void main(String[] args) {
System.out.println(Demo.class.getClassLoader().getClass().getName());
// 类加载器为null,说明它是被BootStrap加载,BootStrap加载器内嵌在虚拟机内,不需要被别的加载器加载
System.out.println(System.class.getClassLoader());
// System.out.println(new ClassLoaderAttachment().toString());
ClassLoader loader = Demo.class.getClassLoader();
while (loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
}
二、类加载器的委托机制:
1、加载类的方式
当Java虚拟机要加载一个类时,到底要用哪个类加载器加载呢?
1)首先,当前线程的类加载器去加载线程中的第一个类。
2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。
3)还可直接调用ClassLoader的LoaderClass()方法,来指定某个类加载器去加载某个类。
2、加载器的委托机制:每个类加载器加载类时,又先委托给上级类加载器。
每个ClassLoader本身只能分别加载特定位置和目录中的类,但他们可以委托其他类的加载器去加载,这就是类加载器的委托模式,类加载器一级级委托到BootStrap类加载器,当BootStrap在指定目录中没有找到要加载的类时,无法加载当前所要加载的类,就会一级级返回子孙类加载器,进行真正的加载,每级都会先到自己相应指定的目录中去找,有没有当前的类;直到退回到最初的类装载器的发起者时,如果它自身还未找到,未完成类的加载,那就报告ClassNoFoundException的异常。
简单说,就是先由发起者将类一级级委托给BootStrap,从父级开始找,找到了直接返回,没找到再返回给其子级找,直到发起者,再没找到就报异常。
3、委托机制的优点:可以集中管理,不会产生多字节码重复的现象。
二、类加载器的委托机制:
1、加载类的方式
当Java虚拟机要加载一个类时,到底要用哪个类加载器加载呢?
1)首先,当前线程的类加载器去加载线程中的第一个类。
2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。
3)还可直接调用ClassLoader的LoaderClass()方法,来制定某个类加载器去加载某个类。
2、加载器的委托机制:每个类加载器加载类时,又先委托给上级类加载器。
每个ClassLoader本身只能分别加载特定位置和目录中的类,但他们可以委托其他类的加载器去加载,这就是类加载器的委托模式,类加载器一级级委托到BootStrap类加载器,当BootStrap在指定目录中没有找到要加载的类时,无法加载当前所要加载的类,就会一级级返回子孙类加载器,进行真正的加载,每级都会先到自己相应指定的目录中去找,有没有当前的类;直到退回到最初的类装载器的发起者时,如果它自身还未找到,未完成类的加载,那就报告ClassNoFoundException的异常。
简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再返回给其子级找,直到发起者,再没找到就报异常。
3、委托机制的优点:可以集中管理,不会产生多字节码重复的现象。
三、自定义类加载器
1、自定义的类加载器必须继承抽象类ClassLoader,要覆写其中的findClass(String name)方法,而不用覆写loadClass()方法。
2、覆写findClass(String name)方法的原因:
1)是要保留loadClass()方法中的流程,因为loadClass()中调用了findClass(String name)这个方法,此方法返回的就是去寻找父级的类加载器。
2)在loadClass()内部是会先委托给父级,当父级找到后就会调用findClass(String name)方法,而找不到时就会用子级的类加载器,再找不到就报异常了,所以只需要覆写findClass方法,那么就具有了实现用自定义的类加载器加载类的目的。
流程:
父级-->loadClass-->findClass-->得到Class文件后转化成字节码-->defind()。
看实例:
public class Demo {
public static void main(String[] args) {
System.out.println(Demo.class.getClassLoader().getClass().getName());
// 类加载器为null,说明它是被BootStrap加载,BootStrap加载器内嵌在虚拟机内,不需要被别的加载器加载
System.out.println(System.class.getClassLoader());
// System.out.println(new ClassLoaderAttachment().toString());
ClassLoader loader = Demo.class.getClassLoader();
while (loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
loadClass();
}
//调用自定义的加载器进行加载文件
public static void loadClass() {
try {
//如果父类有就有父类加载,如果父类没有才交给子类,子类没有就抛异常
Class clazz = new MyClassLoder("mycypher")
.loadClass("enhance.test.classloader.ClassLoaderAttachment");
Date d = (Date) clazz.newInstance();// 不能用本类,要是用本来也就被编译器编译,造成父类加载,不能被自定义的加载器加载
System.out.println(d);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//自定义加载器
public class MyClassLoder extends ClassLoader {
private String classDir;
public MyClassLoder() {
}
public MyClassLoder(String classDir) {
this.classDir = classDir;
}
/*
* 对文件进行加密
*/
public static void main(String[] args) throws Exception {
// 通过运行时把参数传递给住函数,在eclipse中Run as-->Run Configurations
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);
}
/*
* 加密,解密文件
*/
private static void cypher(InputStream ins, OutputStream ops)
throws IOException {
int b = -1;
while ((b = ins.read()) != -1) {
ops.write(b ^ 0xff);// 0变1,1变0
}
}
/*
* 重写ClassLoader的findClass方法,定义自己加载类的方式
* 其实这里只是定义查找文件和得到字节码数组,具体的加载细节还是有父类去实现
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.') + 1) + ".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis, bos);
fis.close();
byte[] bytes = bos.toByteArray();// 得到字节码数组
return defineClass(null, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
public class ClassLoaderAttachment extends Date {//被加载的类
@Override
public String toString() {
return "hi";
}
}
补充:面试题
可不可以自己写个类加载java.lang.System呢?
回答:第一、通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System。
第二、但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。