反射

反射是如何获取类的信息、操作它们

1、反射第一步:加载类,获取类的字节码:class对象

2、获取类的构造器:Constructor

3、获取类的成员变量:Field对象

4、获取类的成员变量:Method对象

反射第一步:加载类,获取类的字节码:class对象

Student.java————>Student.class————>字节码文件——(加载到内存中)——>内存中(Student.class)

1
2
3
4
5
6
7
8
9
10
11
//获取Class对象的三种方式
//第一种方式
// 通过class.forName()传入类的全路径获取:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
//通过类加载器:
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
//第二种方式,调用Class提供方法
Class c1 = TargetObject.class;
//第三种方法,Object提供的方法:
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();

举例:

定义cat类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//首先自定义一个Cat类
package com.li.d2_reflect;
public class Cat {
public static int a;
public static final String COUNTRY = "中国"
private String name;
private int age;
public Cat(){
System.out.println("无参数构造器执行了~~");
}
public Cat(String name, int age) {
System.out.println("无有参数构造器执行了~~");
this.name = name;
this.age = age;
}
private void run(){
System.out.println("猫跑的贼快~");
}
private String eat(){
System.out.println("猫爱吃猫粮~");
}
private String eat(String name){
return "猫最爱吃:" + name;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
public String getAge() {
return age;
}
public void setName(int age) {
this.age = age;
}

@Override

public String tostring() {return "Cat{" + "name='" + name + '\'' + " , age=" + age +';' ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test1Class{ 
public static void main(String[] args) throws Exception {
Class c1 = Student.class;
System.out.println(c1.getName()); // 全类
System.out.printn(c1.getSimpleName()); // 简名: Student

Class c2 = Class.forName("com.itheima.d2_reflect.Student");System.out.println(c1 == c2);

Student s = new Student();
Class c3 = s.getClass();
System.out.println(c3 == c2);
}
}

反射第二步:获取类的构造器、并对其进行操作

Class提供了从类中获取构造器的方法

1
2
3
4
5
6
7
8
9
10
11
/*
Class提供了从类中获取构造器的方法
*/
//获取全部构造器(只能获取public修饰的)
Constructor<?>[] getConstructors();
//获取全部构造器,推荐使用这个(只要存在就能拿到)
Constructor<?>[] getDeclaredConstructors();
//获取某个构造器 (只能获取public修饰的)
Constructor<T> getConstructor(Class<?>... parameterTypes);
//获取某个构造器,推荐使用这个(只要存在就能拿到)
Constructor<T> getDeclaredConstructor(Class<?>..,parameterTypes);

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Test2Constructor {
@Test
public void testGetConstructors(){
// 1、反射第一步: 必须先得到这个类的CLass对象
Class c = Cat.class;
// 2、获取类的全部构造器
Constructor[] constructorsPublicType = c.getConstructors();
Constructor[] constructorsAllType = c.getDeclaredConstructors();
// 3、遍历数组中的每个构造器对象,getName()全类名,getParameterCount()参数个数
for(Constructor constructor : constructorsPublicType) {
System.out.println(constructor.getName() + "--->"+ constructor.getParameterCount());
}
}
@Test
public void testGetConstructor() throws Exception {
// 1、反射第一步: 必须先得到这个类的CLass对象
Class c = Cat.class
// 2、获取某个构造器: 无参数造器
Constructor constructorsPublicType = c.getConstructor();
Constructor constructorsAllType = c.getDeclaredConstructor();
System.out.println(constructor.getName() + "--->"+ constructor.getParameterCount());

// 3、获取有参数构造器,注意类型是String.class和int.class
Constructor constructor1 = c.getDeclaredConstructor(String.class, int.class);
System.out.printIn(constructor1.getName() +"--->"+ constructor1.getParameterCount());
}



//代码段1执行结果
com.li.d2_reflect.Cat--->0
com.li.d2_reflect.Cat--->2

//代码段2执行结果
com.li.da_reflect.Cat--->0
com.li.d2_reflect.Cat--->2

获取类构造器的作用: 依然是初始化对象返回

1
2
3
4
//调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回
T newInstance(Object... initargs)
//设置为true,表示禁止检查访问控制 (暴力反射)
public void setAccessible(boolean flag)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test2Constructor {
@Test
// //测试 获取类的单个构造器
public void testGetConstructor() throws Exception {
// 1、反射第一步: 必须先得到这个类的CLass对象
Class c = Cat.class
// 2、获取某个构造器: 无参数造器
Constructor constructorsPublicType = c.getConstructor();
Constructor constructorsAllType = c.getDeclaredConstructor();
System.out.println(constructor.getName() + "--->"+ constructor.getParameterCount());
constructorsPublicType.setAccessible(true); // 禁止检查访问权限
Cat cat = (Cat) constructorsPublicType.newInstance();
System.out.println(cat);

// 3、获取有参数构造器,注意类型是String.class和int.class
Constructor constructor1 = c.getDeclaredConstructor(String.class, int.class);
System.out.printIn(constructor1.getName() +"--->"+ constructor1.getParameterCount());
constructor1.setAccessible(true); // 禁止检查访问权限
Cat cat2 = (Cat) constructor2.newInstance( ...initargs:"叮当猫"3);
System.out.printn(cat2);
}

//代码执行结果
com.li.d2_reflect.Cat--->0
无参数构造器执行了~~
Cat{name='null',age=0}
com.li.d2_reflect.Cat--->2
有参数构造器执行了~~
Cat{name='叮当猫工 age=3}

反射第三步:获取类的成员变量

Class提供了从类中获取成员变量的方法

1
2
3
4
5
6
7
8
//获取类的全部成员变量(只能获取public修饰的)
public Field[] getFields()
//获取类的全部成员变量(只要存在就能拿到)
public Field[] getDeclaredFields()
//获取类的某个成员变量(只能获取public修饰的)
public Field getField(String name)
//获取类的某个成员变量(只要存在就能拿到)
public Field getDeclaredField(String name)

定义的cat类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Test3Field {

@Test
//获取类的变量
public void testGetFields(){

// 1、反射第一步: 必须是先得到类的CLass对象
Class c = Cat.class;
// 2、获取类的全部成员变量
Field[] fields = c.getDeclaredFields();
// 3、遍历这个成员变量数组
for (Field field : fields) {
System.out.println(field.getName()+"---> "+ field.getType());
}
// 4、定位某个成员变量
Field fName = c.getDeclaredField( name: "name");
System.out.println(fName.getName() + "--->" + fName.getType());
Field fAge = c.getDeclaredField( name: "age");
System.out.println(fAge.getName() + "--->" + fAge.getType());

// 赋值
Cat cat = new Cat();
fName.setAccessible(true); // 禁止访问控制权限
fName.set(cat,"卡菲猎");
System.out.printn(cat):

// 取值
String name = (String) fName.get(cat);
System.out.println(name);
}


//代码执行结果
a---> int
COUNTRY---> class java.lang.string
name---> class java.lang.String
age---> int

// 4、定位某个成员变量
name---> class java.lang.String
age---> int

// 赋值
无参数构造器执行了~~
Cat{name='卡菲猫',age=0}

// 取值
卡菲猫

获取到成员变量的作用: 依然是赋值、取值

1
2
3
4
5
6
//赋值
void set(Object obj,object value);
//取值
Object get(Object obj);
//设置为true,表示禁止检查访问控制(暴力反射)
public void setAccessible(boolean flag);

反射第四步:获取类的成员方法

1
2
3
4
5
6
7
8
//获取类的全部成员方法(只能获取public修饰的)
Method[] getMethods()
//获取类的全部成员方法(只要存在就能拿到)
Method[] getDeclaredMethods()
//获取类的某个成员方法(只能获取public修饰的)
Method[] getMethod(String name,Class<?>... parameterTypes)
//获取类的某个成员方法(只要存在就能拿到)
Method[] getDeclaredMtthod(String name,Class<?>...parameterTypes)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Test3Method {

@Test

public void testGetMethods(){

// 1、反射第一步: 先得到CLass对象
Class c = Cat.class;

// 2、获取类的全部成员方法。

Method[] methods = c.getDeclaredMethods();
// 3、遍历这个数组中的每个方法对象

for (Method method : methods){

System.out.println(method.getName() + "--->"+ method.getParameterCount() + "--->" + method.getReturnType());
}
// 4、获取某个方法对象
// 拿run方法,无参数的
Method run = c.getDeclaredMethod( name:"run"); // 拿run方法,无参数的
System.out.printn(run.getName() + "--->"+ run.getParameterCount() + "--->"+ run .getReturnType());
// 拿eat方法,有参数的
Method eat = c.getDeclaredMethod( name: "eat", String.class);
System.out.println(eat.getName() + "--->"+ eat.getParameterCount() + "--->"+ eat.getReturnType());

// 5、执行
Cat cat = new Cat();
run.setAccessible(true); // 禁止检查访问权限
object .rs = run.invoke(cat); // 调用无参数的run方法,用cat对象触发调用的
System.out.printin(rs);

eat.setAccessible(true); // 禁止检查访问权限
String rs2 = (String) eat.invoke(cat,...args:"鱼儿");
System.out.printin(rs2);
}
}


//代码结果
//遍历这个数组中的每个方法对象
getName--->0--->class java.lang.String
run--->0---->void
toString--->0---->class java.lang.String
setName--->1---->voideat--->1---->class java.lang.String
eat--->0---->void
getAge--->0---->int
setAge--->1---->void
// 4、获取某个方法对象
run--->0---->void
eat--->0---->void
// 5、执行

猫跑的贼快~
null
猫最爱吃:鱼

成员方法的作用:依然是执行

1
2
3
4
//触发某个对象的该方法执行。
public object invoke(Object obj,Object... args)
//设置为true,表示禁止检查访问控制(暴力反射)
public void setAccessible(boolean flag)

注解

1
2
3
4
//自定义注解
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}

例:

1
2
3
4
5
public @interface MyTest{
String aaa();
boolean bbb()default true;
String[] ccc();
}
1
2
3
4
public @interface MyTest{
String value(); //特殊属性
int age() default 23;
}
1
2
3
4
5
6
7
@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})
@MyTest2(value = "孙悟空",age=1000)
public class AnnotationTest1 {
@MyTest1(aaa="铁扇公主",bbb=false,ccc={"Python","前端","Java"})
public void test1(){
}
}

经过编译和反编译后,可以看出注解的源码

1
2
3
4
5
6
7
8
9
//注解本质上是个借口
public interface MyTest1 extends Annotation{
public abstract String aaa();
public abstract boolean bbb():
public abstract String[] ccc();
}

//调用@MyTest1本质上是注解的实现类对象,实现了该注解以及Annotation接口。封装注解的属性信息
@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})

元注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD))
public @interface Test {
}

@Target //作用:声明被修饰的注解只能在哪些位置使用
@Target(ElementType.TYPE) //可以设置多个,中间用逗号隔开
1. TYPE,类,接口
2. FIELD,成员变量
3. METHOD,成员方法
4. PARAMETER,方法参数
5. CONSTRUCTOR,构造器
6. LOCAL VARIABLE,局部变量

@Retention // 作用: 声明注解的保留周期
@Retention(RetentionPolicy.RUNTIME)
1. SOURCE //只作用在源码阶段,字节码文件中不存在。
2. CLASS(默认值) //保留到字节码文件阶段,运行阶段不存在
3. RUNTIME (开发常用) //一直保留到运行阶段。

解析注解例子:

1
2
3
4
AnnotatedElement接口提供了解折注解的方法:
public Annotation[] getDeclaredAnnotations() //获取当前对象上面的注解
public T getDeclaredAnnotation(class<T> annotationClass) //获取指定的注解对象
public boolean isAnnotationPresent(Class<Annotation> annotationClass) //判断当前对象上是否存在某个注解
1
2
3
4
5
6
7
8
//注解类
@Target({ElementType.TYPE,ELementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
String value();
double aaa();
default 10:String[] bbb();
}
1
2
3
4
5
6
7
//demo类
@MyTest4(value ="蜘蛛精",aaa=99.5,bbb = {"至尊宝”,"黑马"})
public class Demo {
@MyTest4(value ="孙悟空",aaa=199.9,bbb = {"紫霞","牛夫人"})
public void test1(){
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//解析注解 类
public class AnnotationTest3 {
@Test
public void parseclass(){
// 1、先得到CLass对象
Class c = Demo .class;
// 2、解析类上的注解
// 判断类上是否包含了某个注解
if(c.isAnnotationPresent(MyTest4.class)){
MyTest4 myTest4 =(MyTest4) c.getDeclaredAnnotation(MyTest4.class);
System.out.printIn(myTest4.value());
System.out.printn(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
}

//输出结果
蜘蛛精
99.5
[至尊宝,黑马]

注解的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class AnnotationTest{
@MyTest
public void test1(){
System.out.println("===test1===");
}
//@MyTest
public void test2(){
System.out.println("===test2===");
}
@MyTest
public void test3(){
System.out.println("===test3===");
}
//@MyTest
public void test4(){
System.out.println("===test4===");
}
public static void main(String[] args){
AnnotationTest t = new AnnotationTest;
//启动程序
//1.得到Class对象
Class c = AnnotationTest.class;
//2.提取这个类中的全部成员方法
Method[] methods = c.getDeclaredMethods();
//3.遍历这个数组中的每个方法,看方法是否粗壮乃@MyTest注解
for(Method method : methods){
if(method.isAnnotationPresent(MyTest.class)){
//说明当前方法上存在注解
method.invoke(t);//因为测试方法无参,所以不写
}
}
}

}

动态代理

异常

认识异常

接下来,我们学习一下异常,学习异常有利于我们处理程序中可能出现的问题。我先带着同学们认识一下,什么是异常?

我们阅读下面的代码,通过这段代码来认识异常。 我们调用一个方法时,经常一部小心就出异常了,然后在控制台打印一些异常信息。其实打印的这些异常信息,就叫做异常。

那肯定有同学就纳闷了,我写代码天天出异常,我知道这是异常啊!我们这里学习异常,其实是为了告诉你异常是怎么产生的?只有你知道异常是如何产生的,才能避免出现异常。以及产生异常之后如何处理。

因为写代码时经常会出现问题,Java的设计者们早就为我们写好了很多个异常类,来描述不同场景下的问题。而有些类是有共性的所以就有了异常的继承体系

先来演示一个运行时异常产生

1
2
3
int[] arr = {11,22,33};
//5是一个不存在的索引,所以此时产生ArrayIndexOutOfBoundsExcpetion
System.out.println(arr[5]);

下图是API中对ArrayIndexOutOfBoundsExcpetion类的继承体系,以及告诉我们它在什么情况下产生。

再来演示一个编译时异常

我们在调用SimpleDateFormat对象的parse方法时,要求传递的参数必须和指定的日期格式一致,否则就会出现异常。 Java比较贴心,它为了更加强烈的提醒方法的调用者,设计了编译时异常,它把异常的提醒提前了,你调用方法是否真的有问题,只要可能有问题就给你报出异常提示(红色波浪线)。

编译时异常的目的:意思就是告诉你,你小子注意了!!,这里小心点容易出错,仔细检查一下

有人说,我检查过了,我确认我的代码没问题,为了让它不报错,继续将代码写下去。我们这里有两种解决方案。

  • 第一种:使用throws在方法上声明,意思就是告诉下一个调用者,这里面可能有异常啊,你调用时注意一下。
1
2
3
4
5
6
7
8
9
10
/**
* 目标:认识异常。
*/
public class ExceptionTest1 {
public static void main(String[] args) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse("2028-11-11 10:24");
System.out.println(d);
}
}
  • 第二种:使用try…catch语句块异常进行处理。
1
2
3
4
5
6
7
8
9
10
11
public class ExceptionTest1 {
public static void main(String[] args) throws ParseException{
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse("2028-11-11 10:24");
System.out.println(d);
} catch (ParseException e) {
e.printStackTrace();
}
}
}

好了,关于什么是异常,我们就先认识到这里。

1.2 自定义异常

同学们经过刚才的学习已经认识了什么是异常了,但是无法为这个世界上的全部问题都提供异常类,如果企业自己的某种问题,想通过异常来表示,那就需要自己来定义异常类了。

我们通过一个实际场景,来给大家演示自定义异常。

需求:写一个saveAge(int age)方法,在方法中对参数age进行判断,如果age<0或者>=150就认为年龄不合法,如果年龄不合法,就给调用者抛出一个年龄非法异常。

分析:Java的API中是没有年龄非常这个异常的,所以我们可以自定义一个异常类,用来表示年龄非法异常,然后再方法中抛出自定义异常即可。

  • 先写一个异常类AgeIllegalException(这是自己取的名字,名字取得很奈斯),继承
1
2
3
4
5
6
7
8
9
// 1、必须让这个类继承自Exception,才能成为一个编译时异常类。
public class AgeIllegalException extends Exception{
public AgeIllegalException() {
}

public AgeIllegalException(String message) {
super(message);
}
}
  • 再写一个测试类,在测试类中定义一个saveAge(int age)方法,对age判断如果年龄不在0~150之间,就抛出一个AgeIllegalException异常对象给调用者。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ExceptionTest2 {
public static void main(String[] args) {
// 需求:保存一个合法的年
try {
saveAge2(225);
System.out.println("saveAge2底层执行是成功的!");
} catch (AgeIllegalException e) {
e.printStackTrace();
System.out.println("saveAge2底层执行是出现bug的!");
}
}

//2、在方法中对age进行判断,不合法则抛出AgeIllegalException
public static void saveAge(int age){
if(age > 0 && age < 150){
System.out.println("年龄被成功保存: " + age);
}else {
// 用一个异常对象封装这个问题
// throw 抛出去这个异常对象
throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age);
}
}
}
  • 注意咯,自定义异常可能是编译时异常,也可以是运行时异常
1
2
3
4
5
1.如果自定义异常类继承Excpetion,则是编译时异常。
特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。

2.如果自定义异常类继承RuntimeException,则运行时异常。
特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。

1.3 异常处理

同学们,通过前面两小节的学习,我们已经认识了什么是异常,以及异常的产生过程。接下来就需要告诉同学们,出现异常该如何处理了。

比如有如下的场景:A调用用B,B调用C;C中有异常产生抛给B,B中有异常产生又抛给A;异常到了A这里就不建议再抛出了,因为最终抛出被JVM处理程序就会异常终止,并且给用户看异常信息,用户也看不懂,体验很不好。

此时比较好的做法就是:1.将异常捕获,将比较友好的信息显示给用户看;2.尝试重新执行,看是是否能修复这个问题。

我们看一个代码,main方法调用test1方法,test1方法调用test2方法,test1和test2方法中多有扔异常。

  • 第一种处理方式是,在main方法中对异常进行try…catch捕获处理了,给出友好提示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ExceptionTest3 {
public static void main(String[] args) {
try {
test1();
} catch (FileNotFoundException e) {
System.out.println("您要找的文件不存在!!");
e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。
} catch (ParseException e) {
System.out.println("您要解析的时间有问题了!");
e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。
}
}

public static void test1() throws FileNotFoundException, ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse("2028-11-11 10:24:11");
System.out.println(d);
test2();
}

public static void test2() throws FileNotFoundException {
// 读取文件的。
InputStream is = new FileInputStream("D:/meinv.png");
}
}
  • 第二种处理方式是:在main方法中对异常进行捕获,并尝试修复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 目标:掌握异常的处理方式:捕获异常,尝试修复。
*/
public class ExceptionTest4 {
public static void main(String[] args) {
// 需求:调用一个方法,让用户输入一个合适的价格返回为止。
// 尝试修复
while (true) {
try {
System.out.println(getMoney());
break;
} catch (Exception e) {
System.out.println("请您输入合法的数字!!");
}
}
}

public static double getMoney(){
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请您输入合适的价格:");
double money = sc.nextDouble();
if(money >= 0){
return money;
}else {
System.out.println("您输入的价格是不合适的!");
}
}
}
}