注解
注解不同于注释,注解可以加在类的上面,以@注解名
实现。这样的注解可以被编译器读到,从而对这个类进行一些操作。也可以在反射的时候,被其它的类的方法所读到,通过反射从而创建对象或者改变这个类模板的一些状态。
常见的4个元注解:
元注解的作用就是负责注解其他注解, Java定义了4个标准的meta annotation类型,他们被用来提供对其他annotation类型作说明.
这些类型和它们所支持的类在java.lang.annotation包中可以找到
@Target
, @Retention
,@Documented
, @Inherited
@Target
:用于描述**注解的使用范围(**即:被描述的注解可以用在什么地方)
@Retention
: 表示需要在什么级别保存该注释信息,用于描述注解的生命周期,(SOURCE < CLASS < RUNTIME) 越高级范围越大
@Document
:说明该注解将被包含在javadoc中
@Inherited
: 说明子类可以继承父类中的该注解
注意:所有注解都有一个共同父类Annotation!!!
import java.lang.annotation.*;
@MyAnnotation
public class Demo02 {
void test(){
}
}
//定义一个注解
//Target 表示我们的注解可以用在哪些地方.
@Target(value = {ElementType.METHOD, ElementType.TYPE})
//Retention表示我们的注解在什么地方还有效。
// runtime>class>sources
@Retention(value = RetentionPolicy.RUNTIME)
//Documented表示是否将我们的注解生成在Javadoc中
@Documented
//Inherited子类可以继承父类的注解
@Inherited
@interface MyAnnotation{
String value() default ""; //这里注解的定义参考下面的说明
}
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
分析:
- @ interface用来声明一个注解,格式: public @ interface注解名{定义内容}
其中的每一个方法实际 上是声明了一个配置参数. - 方法的名称就是参数的名称.
- 返回值类型就是参数的类型(返回值只能是基本类型,Class , String , enum ).
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员, 一般参数名为value 只有一个参数时 运用注解时参数名可省
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值.
内置的一些注解:
反射
Reflection (反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
例如:获得String对象的类模板
Class C= Class.forName("java.lang String");
为什么叫反射?
看图
正常new类的方式和反射完全相反。因此才叫反射。
反射的功能
运行时判断任意对象所属类
运行时构造任意一个类的对象
运行时判断任意一个类所具有的成员变量以及方法
运行时获取泛型信息
运行时调用任意一个对象变量成员以及方法
运行时处理注解
生成动态代理
主要的类
java.lang.Class
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect.Construct
Class类
public static ClassforName(String name); //返回指定类名的Class对象
public Object newInstance(); //调用无参构造函数,返回该Class对象的一个实例
public String getName(); //返回此Class对象所表示的实体(类,接口,数组,或者void)的名称
public Class getSuperClass(); //返回当前Class对象的父类Class对象
public Class[] getInterfaces(); //获取当前Class对象的接口
public ClassLoader getClassLoader(); //返回该类的类加载器
public Constructor[] getConstructors(); //返回包含某些Constructor对象的数组
public Method getMethod(String name, Class... T);//返回一个Method对象,此对象形参类型为paramType
Field[] getDeclaredFields(); //返回Field对象的一个数组
获取Class实例
- 已知具体的类,通过类的Class属性获取
Class clazz = Person.class;
- 已知某个类的实例,调用getClass()方法
Class clazz = person.getClass();
- 已知类全名,则可以通过forName()方法调用。但是可能抛出ClassNotFoundException异常。
Class clazz = Class.forName("xxx");
- 内置基本数据类型可以直接用类名.Type
- 利用ClassLoader
注意:在需要获得接口的class对象时,需要接口名.Class,不能对象.Class。因为后者是获取到的是实现类的Class对象。
.class和getClass()区别
类名.class叫做“类字面量”,因class是关键字, 所以类名.class编译时确定。
getclass()运行时根据实际实例确定,getClass()是动态而且是final的。
String.class 是能对类名的引用 取得在内存中该类型class对象的引用,
new String().getClass() 是通过实例对象取得在内存中该实际类型
class对象的引用。
可以有Class对象的类型:
- class: 外部类成员(成员内部类,静态内部类),
- 局部内部类,匿名内部类。
- interface: 接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
- Class
类加载的过程
举例:
public class Demo04 {
public static void main(String[] args) {
A a=new A();
System.out.println("初始化完打印的值"+A.m);//100
/*
1.加载到内存,产生一个类对应Class对象
2.链接,链接结束后m=0
3.初始化
<clinit>(){
按顺序收集所有静态语句
System. out . println( "A类静态代码块初始化") ;
m = 300;
m=100;
}
m=100
*/
}
}
class A{
static int m ;
static{
System.out.println("静态代码块初始化");
System.out.println("类变量分配的初始值"+m);
m=1000;
System.out.println(m);
}
public A(){
System.out.println("无参构造初始化");
}
}
什么时候发生类初始化?
1.类主动引用
1.1 当虚拟机启动时,会初始化main方法所在的类
1.2 new一个类的对象
1.3 调用类的静态成员
1.4 使用java.lang.reflect包方法对类进行反射调用
1.5当初始化一个类,如果父类未被初始化,则先初始化父类。
不会发生类的初始化
2.类的被动引用
2.1 当访问一个静态域时候,只有真正声明这个域的类才会被初始化。比如通过子类引用父类的静态变量,只会让父类初始化。
2.2 通过数组定义类引用,不会触发此类的初始化。因为还没有真正构造对象,只是声明。
2.3 引用类变量常量不会触发此类的初始化 (常量在准备阶段被赋值了,而不是因为在常量池)
注意:
如果自己定义和jdk同名的类,运行时虚拟机会在系统的类加载器中寻找,再去扩展类加载器中寻找,再去根加载器中寻找,如果存在同名的类,会使用根加载器中的类,而不使用自己定义的类。
创建运行时类的对象
这些功能包括:
- 通过反射获取运行时类的完整结构Field、Method, Constructor、 Superclass、 Interface、 Annotation
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的Field
- 注解
示例代码:
Class c1 = Class.forName("com.reflection.User");
//获得包名+类名
User user = new User();
Class c2 = user.getClass();
//获得类的信息
System.out.println(c1.getName());//获得包名+类名
System.out.println(c1.getSimpleName());//获得类名
//获得类的属性
System.out.println("=======================");
Field[] fields = c1.getFields();//获取类的公开属性和父类的公开属性
fields = c1.getDeclaredFields();//获取类的任何属性只能本类的无父类
for (Field field : fields) {
System.out.println(field);
}
//获得指定属性 只能未本类的无父类
Field name = c1.getDeclaredField("name");
System.out.println(name);
//获得类的方法
System.out.println("=========================");
Method[] methods = c1.getMethods();//获得本类和父类的所有public方法 无构造方法
for (Method method : methods) {
System.out.println("methods " + method);
}
System.out.println("=========================");
Method[] decmethods = c1.getDeclaredMethods();//获得本类的所有方法包括私有
for (Method method : decmethods) {
System.out.println("decmethods " + method);
}
//获得指定方法(不包含private)
//需要传参数的原因:存在重载,参数可找到指定的方法
System.out.println("=========================");
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
//获得本类构造器
System.out.println("=========================");
//public
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("getConstructors " + constructor);
}
System.out.println("=========================");
//包含private
Constructor[] constructors1 = c1.getDeclaredConstructors();
for (Constructor constructor : constructors1) {
System.out.println("getDeclaredConstructors " + constructors1);
}
//获得指定的构造器
Constructor getDeclaredConstructor = c1.getDeclaredConstructor(String.class,int.class);
System.out.println("指定构造器" + getDeclaredConstructor);
通过类对象创建对应类的实例
有了对应的Class对象,我们也可以创建所对应的类实例了。
- 如果类有无参构造,我们则可以通过
newInstance()
方法调用创建出一个实例 - 如果有有参构造,我们则需要获得其有参构造的Construct对象,通过Construct对象的
newInstance()
方法来进行new获取。
调用指定的方法
我们可以通过反射来调用指定类实例中指定的方法。其步骤为:
①通过Class类的getMethod(String name, Class… parameterTypes)
方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
②之后使用Object invoke(Object obj, Object[] args)
进行调用,并向方法中传递要设置的obj对象的参数信息。
- Object对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法若为静态方法,此时形参0bject obj可为null
- 若原方法形参列表为空,则Object[] args为null
- 若原方法声明为private,则需要在调用此invoke()方法前**(getDeclaredXxx只是可以获取对应私有的方法、属性、构造方法,要使用需setAccessible为true),显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
如果该方法为private、protected方法,则调用时需要取消语法访问检查。Method和Field、Constructor对象都有setAccessible()
方法。设置为true之后,该方法、类成员则可随意调用。
public void setAccessible(boolean flag)
例子:狂神说Java—动态创建对象,通过反射。
//动态创建对象,通过反射
public class Demo08 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得class对象
Class c1 = Class.forName("com.reflection.User");
//创建一个对象
System.out.println("============================");
User user = (User)c1.newInstance();//本质是调用了类的无参构造器
System.out.println(user);
//通过构造器创建对象
System.out.println("============================");
Constructor constructor = c1.getDeclaredConstructor(String.class,int.class);
User user2 = (User)constructor.newInstance("打爆",22);
System.out.println(user2);
//通过反射调用普通方法
//通过反射获取一个方法
System.out.println("============================");
Method setName = c1.getDeclaredMethod("setName",String.class);
//invoke:激活的意思
//参数:对象,方法参数的值
setName.invoke(user,"立良");
System.out.println(user.getName());
System.out.println("============================");
//通过反射操作属性
User user3 = (User)c1.newInstance();
//通过Class对象获取对应属性信息
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,我们需要关闭程序的安全监测,属性或方法的setAccessible(true)
name.setAccessible(true);
name.set(user3,"小宝");//找到实例变量属性并设置值 静态属性时第一个参数Object为null
System.out.println(user3.getName());
}
}
public class Demo09 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test1();//5ms
test2();//4114ms
test3();//1483ms
}
public static void test1(){
User user = new User();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long end = System.currentTimeMillis();
System.out.println(end-start+"ms");
}
public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName",null);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long end = System.currentTimeMillis();
System.out.println(end-start+"ms");
}
public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName",null);
getName.setAccessible(true);//使用了setAccessible(true)后速度会快很多
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long end = System.currentTimeMillis();
System.out.println(end-start+"ms");
}
}
反射来操作泛型
Java中针对反射中的形参的反射设置了四个类。这四个类ParameterizedType , GenericArrayType ,TypeVariable和WildcardType几种类型不能被归一到Class类中。但是又和原始类型齐名。
- ParameterizedType :表示一种参数化类型,比如Collection ,具有<>符号的变量是参数化类型即泛型
- GenericArrayType :表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable :是各种类型变量的公共父接口
- WildcardType :代表一种通配符类型表达式
- java.lang.reflect.Type:java语言中所有类型的公共父接口
- Type所有类型指代的有:原始类型 (raw types)【对应Class】,参数化类型 (parameterizedtypes)【对应ParameterizedType】, 数组类型 (array types)【对应GenericArrayType】,类型变量 (type variables)【对应TypeVariable】,基本数据类型(primitivetypes)【仍然对应Class】
狂神说Java反射操作泛型的例子:
public class Demo10 {
public void test01 (Map<String,User> map, List<User> list){
System.out.println("test01");
}
public Map<String, User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Demo10.class.getMethod("test01",Map.class,List.class);
Type[] genericParameterTypes= method.getGenericParameterTypes();//获得方法中形参类型 返回Type类型的数组 Type[].
for (Type genericParameterType : genericParameterTypes) {
System.out.println("参数范型"+genericParameterType);
if (genericParameterType instanceof ParameterizedType //如果是泛型){
Type[] actualTypeAnguments=(ParameterizedType)genericParameterType.getActualTypeArguments();//getActualTypeArguments() 获取参数化类型<>中的实际类型
for (Type actualTypeAngument : actualTypeAnguments) {
System.out.println("实际参数范型"+actualTypeAngument);
}
}
}
Method method1 = Demo10.class.getMethod("test02",null);
Type getGenericReturnType= method1.getGenericReturnType();//获取返回值泛型
if (getGenericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = (ParameterizedType) getGenericReturnType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("返回值范型" + actualTypeArgument);
}
}
}
}
getGenericParameterTypes()
: 返回Type类型的数组 Type[]
.
getParameterTypes()
: 返回Class类型的数组: Class<?>[]
.
①如果方法参数不是参数化类型(泛型),那么getParameterTypes和getGenericParameterTypes返回的结果是一样的。
②如果方法参数是泛型,这时就有区别了,getGenericParameterTypes会返回完整的信息(List),而getParameterTypes只会返回参数类型(List),参数化类型无法得到。
反射获得注解
具体用法看下面的例子:
import java.lang.annotation.*;
public class Test10 {
public static void main(String[] args) throws NoSuchFieldException {
Class<Student1> student1Class = Student1.class;
Annotation[] annotations = student1Class.getAnnotations();//Annotation为所有注解的父类 获取类上所有注解
for (Annotation annotation : annotations) {
System.out.println(annotation);//@reflect.Table(value=db_student)
}
Table annotation = student1Class.getAnnotation(Table.class);//获取class对象标注在类上指定的注解
System.out.println(annotation);//@reflect.Table(value=db_student)
String value = annotation.value();//获取class对象中指定的注解的值 即注解的值
System.out.println(value);
java.lang.reflect.Field name = student1Class.getDeclaredField("name");//获取class对象中的属性
Field annotation1 = name.getAnnotation(Field.class);//获取class对象中的标注在属性上的注解
System.out.println(annotation1.columnName());//获取class对象中的属性上注解的值
System.out.println(annotation1.length());
System.out.println(annotation1.type());
}
}
//类上注解
@Table("db_student")
class Student1{
//属性上注解
@Field(columnName = "db_id",type = "int",length = 10)
private int id;
@Field(columnName = "db_age",type = "int",length = 10)
private int age;
@Field(columnName = "db_name",type = "varchar",length = 10)
private String name;
public Student1() {
}
public Student1(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field{
String columnName();
String type();
int length();
}