反射、注解、动态代理、JDK8新特性
day14【反射、注解、动态代理、JDK8新特性】
今日内容:
"反射:以后我们学的所有框架底层都是由反射
"注解:今天只学基本语法
设计模式: 动态代理设计模式(基本格式)
JDK8的新特性
第一章.反射
1.类的加载
源文件--通过javac编译-->字节码文件---通过Java命令(通过ClassLoader)--->JVM运行
字节码文件什么时候会被加载?
当该类被使用到时就会被加载
字节码文件需要加载几次?
只需要加载一次,当前第一次使用该类时加载,以后使用到该类不需要加载
"字节码文件被加载JVM方法区内存之后,JVM会干什么事?"
JVM会在堆中为该字节码文件创建一个Class对象(字节码文件对象),对象中保存了字节码文件中所有的信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v7rCBuSz-1578312743688)(img/image-20200105091214510.png)]
2.什么是反射
反射是一种运行时技术,运行时获取某个类的Class对象,从而解剖它,从中获取成员变量,成员方法,构造方法等,进而可以使用他们(无论私有与否)
3.反射在实际开发中的应用
a.编写IDE(集成开发环境),比如IDEA,Eclipse
b.以后我们底层学习框架和设计框架都必须使用反射技术
4.反射中万物皆对象的概念
反射中万物皆对象:
字节码文件 ---> Class对象
成员变量 ---> Field对象
成员方法 ---> Method对象
构造方法 ---> Constructor对象
newInstance ---> 创建对象
invoke ---> 调用/执行
体验一下反射中语法:
正常语法 反射语法
new 构造方法(参数); 构造方法对象.newInstance(参数);
对象名.成员方法名(参数); 成员方法对象.invoke(对象名,参数);
sout(对象名.成员变量名); sout(成员变量对象.get(对象名));
对象名.成员变量名 = 值 成员变量对象.set(对象名,值);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTKj6BJM-1578312743689)(img/image-20200105092106340.png)]
5.反射的第一步获取字节码文件对象(Class对象)
Java提供三种方式,可以获取到Class对象
/**
* 获取Class对象
*/
public class TestDemo {
public static void main(String[] args) throws ClassNotFoundException {
//1.通过类的静态成员来获取
Class clazz1 = Cat.class;
System.out.println(clazz1);
//2.通过该类的某个对象,来获取Class对象
Cat cc = new Cat();
Class clazz2 = cc.getClass();
System.out.println(clazz2);
//3.通过Class类的静态方法
Class clazz3 = Class.forName("com.itheima.demo02_GetClassObject.Cat");
System.out.println(clazz3);
//注意:以上三种是Java提供的三种方式,而不是获取三个Class对象
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
System.out.println(clazz2 == clazz3);
//以上三个Class对象,实际上是同一个Class对象,只是获取的方式不同而已
}
}
6.Class对象中的三个常见方法
public String getName(); 获取全限定类名(包名.类名)
public String getSimpleName(); 获取类名(不带包名)
public Object newInstance(); 创建该Class对象所代表类的对象
/**
* Class对象的三个方法
*/
public class TestDemo {
public static void main(String[] args) throws Exception {
//1.通过类的静态成员来获取
Class clazz1 = Cat.class;
//4.获取全限定类名
System.out.println("---------------");
System.out.println(clazz1.getName());
//5.获取类名
System.out.println(clazz1.getSimpleName());
//6.创建对象(Class所代表类Cat的对象)
Object obj = clazz1.newInstance();
System.out.println(obj);
}
}
7.通过反射获取构造方法&&使用构造方法创建对象
获取构造:
public Constructor getConstructor(构造方法的参数类型.class...);只能获取"public"构造
public Constructor getDeclaredConstructor(构造方法的参数类型.class...);获取"任意"构造
使用构造:
public Object newInstace(构造方法的实际参数);
如果是私有构造怎么使用?
a.先设置该构造方法具有暴力访问权限
构造方法对象.setAccessible(true);
b.然后正常使用
public Object newInstace(构造方法的实际参数);
/**
* 通过反射获取其中的构造方法,并使用构造方法
*/
public class TestDemo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1.获取Class对象
Class cc = Cat.class;
//2.获取构造方法对象
Constructor con1 = cc.getConstructor();
System.out.println(con1);
Constructor con2 = cc.getConstructor(int.class, String.class);
System.out.println(con2);
//如果是非公有构造,必须调用getDeclaredConstructor来获取
Constructor con3 = cc.getDeclaredConstructor(String.class);
System.out.println(con3);
//3.通过构造方法对象就可以创建对象
System.out.println("---------");
Object obj1 = con1.newInstance();
System.out.println(obj1);
Object obj2 = con2.newInstance(10, "肥猫");
System.out.println(obj2);
//如果是私有构造方法,那么必须先设置暴力访问权限
con3.setAccessible(true);
//然后才能正常使用
Object obj3 = con3.newInstance("加肥猫");
System.out.println(obj3);
}
}
8.通过反射获取成员方法&&调用成员方法
获取成员方法:
public Method getMethod("方法名",方法参数类型.class...); 获取"public"修饰成员方法
public Method getDeclaredMethod("方法名",方法参数类型.class...); 获取"任意"修饰成员方法
使用成员方法:
public Object invoke(对象名,执行方法需要的实际参数); -- 执行该成员方法对象
如果是私有的成员方法怎么调用呢?
a.先设置暴力访问权限
成员方法对象.setAccessible(true);
b.正常调用该方法即可
public Object invoke(对象名,执行方法需要的实际参数); -- 执行该成员方法对象
/**
* 通过反射获取成员变量,并使用成员方法
*/
public class TestDemo {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class cc = Class.forName("com.itheima.demo04_GetMethod.Cat");
//2.获取成员方法
Method m1 = cc.getMethod("eat", String.class);
System.out.println(m1);
Method m2 = cc.getMethod("eat", String.class, String.class);
System.out.println(m2);
//如果成员方法是非公有的,必须通过getDeclaredMethod方法获取
Method m3 = cc.getDeclaredMethod("eat");
System.out.println(m3);
//3.使用成员方法对象
Cat cat = new Cat(10,"加菲猫");
m1.invoke(cat,"鱼"); // 相当于 cat.eat("鱼")
m2.invoke(cat,"鱼","啤酒");// 相当于cat.eat(鱼","啤酒");
//如果是私有方法,必须先设置暴力访问权限
m3.setAccessible(true);
//然后调用正常调用方法
m3.invoke(cat); //相当于cat.eat();
}
}
9.通过反射获取成员属性(了解即可)
因为成员变量都是私有的,并且有get/set方法,我们通过反射获取get/set方法就可以了!!
获取成员变量:
public Field getField(String 成员变量名); -- 获取"public"成员变量
public Field getDeclaredField(String 成员变量名); -- 获取"任意"成员变量
使用成员变量:
"取出成员变量的值
成员变量对象.get(对象名); // 相当于 对象名.成员变量名
需要先设置暴力权限
成员变量对象.setAccessible(true);
成员变量对象.get(对象名);
"修改成员变量的值
成员变量对象.set(对象名,新的值);
需要先设置暴力权限
成员变量对象.setAccessible(true);
成员变量对象.set(对象名,新的值);
10.反射中的其他方法(了解即可)
获取构造:
public Constructor[] getConstructors(); -- 获取所有"public"构造
public Constructor[] getDeclaredConstructors(); -- 获取所有"任意"构造
获取方法:
public Method[] getMethods(); -- 获取所有"public"成员方法,包括父类继承的
public Method[] getDeclaredMethods(); -- 获取所有"任意"成员方法(不包括父类继承的)
获取成员变量:
public Filed[] getFields(); -- 获取所有"public"的成员变量
public Filed[] getDeclaredFields(); -- 获取所有"任意"的成员变量
第二章 注解(基本语法)
1.JDK1.5新特性–注解
什么是注释:
对代码进行解释说明的文字(给程序员看)
什么是注解:
对代码进行解释说明的语法(给JVM看的,程序员也可以看)
2.注解的两个作用
a.给程序带入参数(框架的时候使用)
b.编译检查(在编译时期,检查我们的语法是否符合程序的要求)
@Override 方法重写注解
@FunctionalInterface 函数式接口注解
@Deprecated 方法过期注解
c.作为框架的配置文件(框架的时候使用)
3.常用两个注解介绍
@author 给Java文件,标记作者
@version 给Java文件,标记版本号
@Override 方法重写注解
@FunctionalInterface 函数式接口注解
@Deprecated 方法过期注解
@Test 单元测试Junit的注解
4.自定义注解
自定义类: public class 类名
自定义接口: public interface 接口名
自定义注解: public @interface 注解名
格式:
public @interface 注解名{
}
5.给自定义注解添加属性
格式:
public @interface 注解名{
//注解中只能写属性,格式: 数据类型 属性名();
数据类型 属性名() [default 默认值];
数据类型 属性名() [default 默认值];
数据类型 属性名() [default 默认值];
}
注解中属性的数据类型也是有限制:
a.八大基本类型
b.引用类型中只能写:String,枚举,其他注解,Class
c.以上12种类型的一维数组
6.自定义注解练习
/**
* 自定义注解
*/
public @interface MyAnnotation {
//注解的属性
int age(); // default 18;
String name();// default "张三";
String[] hobbies();// default {"抽烟", "喝酒", "跳楼"};
}
/**
* 使用注解格式:
* @注解名(属性名=属性值,属性名=属性值,..)
* 如果注解中某个属性没有默认值,那么使用该注解时必须给属性赋值,如果有默认值,可以赋值也可以不赋值
*/
@MyAnnotation(age = 18,name = "旺财",hobbies = {
"抽烟", "喝酒", "跳楼"})
public class Demo {
@MyAnnotation(age = 18,name = "旺财",hobbies = {
"抽烟", "喝酒", "跳楼"})
private String job;
@MyAnnotation(age = 18,name = "旺财",hobbies = {
"抽烟", "喝酒", "跳楼"})
public Demo(String job) {
this.job = job;
}
@MyAnnotation(age = 18,name = "旺财",hobbies = {
"抽烟", "喝酒", "跳楼"})
public void show(@MyAnnotation(age = 18,name = "旺财",hobbies = {
"抽烟", "喝酒", "跳楼"}) int age) {
@MyAnnotation(age = 18,name = "旺财",hobbies = {
"抽烟", "喝酒", "跳楼"})
String name = "";
}
}
7.使用注解时的注意事项
a.如果注解中某个属性没有默认值,那么使用该注解时必须给属性赋值,如果有默认值,可以赋值也可以不赋值
b.我们现在定义的注解,可以修饰基本上任意的地方(类上,方法上,局部变量上等...)
c.我们的注解没有实际含义,注解想要有实际含义必须有注解解析器的支持
8.自定义注解中的特殊属性名value
a.如果注解中只有一个属性,且属性名为value,那么使用该注解时,可以省略value直接给value赋值即可
/**
* 特殊的属性value
*/
public @interface MyAnno {
String value();
}
@MyAnno("abc") -- 此处省略value的名,直接写value的值
public class Demo {
}
b.如果注解中有多个属性,但是有一个叫value,其他属性都有默认值,使用时可以可以省略value直接给value赋值即可
/**
* 特殊的属性value
*/
public @interface MyAnno {
String value();
int age() default 20;
}
@MyAnno("abc") -- 此处省略value的名,直接写value的值,age不需要赋值
public class Demo {
}
9.注解的注解–元注解
元注解: 修饰注解的注解,称为元注解
两个元注解
@Target 元注解,修饰普通的注解
其作用是,规定普通的注解使用的目标:具体的使用目标,可以是以下几种
ElementType.TYPE 表示作用目标必须是类上或者接口上
ElementType.FIELD 表示作用目标必须是成员变量上
ElementType.METHOD 表示作用目标必须是成员方法上
ElementType.PARAMETER 表示作用目标必须是方法参数上
ElementType.CONSTRUCTOR 表示作用目标必须是构造方法上
ElementType.LOCAL_VARIABLE 表示作用目标必须是局部变量上
/**
* 使用@Target元注解,修饰普通的注解
* 作用:规定普通注解使用的目标
*/
//@Target(ElementType.TYPE) 表示作用目标必须是类上或者接口上
//@Target(ElementType.FIELD) 表示作用目标必须是成员变量上
//@Target(ElementType.METHOD) 表示作用目标必须是成员方法上
//@Target(ElementType.CONSTRUCTOR) 表示作用目标必须是构造方法上
//@Target(ElementType.PARAMETER) 表示作用目标必须是方法参数上
//@Target(ElementType.LOCAL_VARIABLE) 表示作用目标必须是局部变量上
@Target({
ElementType.FIELD,ElementType.METHOD}) 表示作用目标必须是成员变量和成员方法上
public @interface Anno1 {
}
@Retension 元注解,修饰普通的注解
其作用是,规定普通注解的生命周期,具体的使用生命周期,可以是以下几种
RetentionPolicy.SOURCE 表示我们的注解只在源码阶段存在,编译成字节码后删除
RetentionPolicy.CLASS 表示我们的注解在源码和字节码阶段都存在,运行时删除
RetentionPolicy.RUNTIME 表示我们的注解一直存在
/**
* 使用@Retension元注解,修饰普通的注解
* 作用:规定普通注解的生命周期
*/
//@Retention(RetentionPolicy.SOURCE) 表示我们的注解只在源码阶段存在,编译成字节码后删除
//@Retention(RetentionPolicy.CLASS) 表示我们的注解在源码和字节码阶段都存在,运行时删除
@Retention(RetentionPolicy.RUNTIME) 表示我们的注解一直存在
public @interface Anno2 {
}
10.注解的解析
就是通过代码,读取某个位置上注解的属性值
注解解析的固定步骤:
a.获取注解所在的Class对象
b.获取注解所在的具体目标(Field,Method,Constructor)
c.判断目标是否真的有此注解
d.从目标上获取我们要的注解
e.从注解上获取其各个属性值
/**
* 自定义注解:书注解
*/
@Retention(RetentionPolicy.RUNTIME) -- 设置Book注解的生命周期是一直存在
public @interface Book {
String name();
double price();
int pages();
String[] authors();
}
public class Pig {
private int age;
public Pig(int age) {
this.age = age;
}
@Book(name = "三国演义",price = 199.99,pages = 1001,authors = {
"曹雪芹","罗贯中","吴承恩","施耐庵"})
public void eat() {
}
}
public class TestDemo {
public static void main(String[] args) throws NoSuchMethodException {
// 注解解析的固定步骤:
// a.获取注解所在的Class对象
Class pigClass = Pig.class;
// b.获取注解所在的具体目标(Field,Method,Constructor)
Method eatMethod = pigClass.getMethod("eat");
// c.判断目标是否真的有此注解
boolean b = eatMethod.isAnnotationPresent(Book.class);
if (b) {
// d.从目标上获取我们要的注解
Book book = eatMethod.getAnnotation(Book.class);
// e.从注解上获取其各个属性值
System.out.println("书名:" + book.name());
System.out.println("价格:" + book.price());
System.out.println("页数:" + book.pages());
System.out.println("作者们:" + Arrays.toString(book.authors()));
} else {
System.out.println("该方法上没有此注解");
}
}
}
11.综合练习_模拟Junit的@Test注解
需求: 编写一个注解,模拟Junit的@Test注解功能(使用main方法来代替右键执行)
/**
* 自定义注解,模拟Junit的@Test注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
public class Demo {
@MyTest
public void test01() {
System.out.println(11111);
}
@MyTest
public void test02() {
System.out.println(22222);
}
@MyTest
public void test03() {
System.out.println(33333);
}
@MyTest
public void test04() {
System.out.println(44444);
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
//1.获取目标类的字节码文件对象
Class clazz = Demo.class;
//2.获取目标类上所有的成员方法
Method[] methods = clazz.getMethods();
//3.遍历判断
for (Method method : methods) {
//4.判断该方法上是否有注解
if (method.isAnnotationPresent(MyTest.class)) {
//5.执行该方法
method.invoke(new Demo());
}
}
}
}
第三章 动态代理
1.代理模式介绍
代理设计模式:
被代理对象,没有能力或者不愿意完成某件事情,找有一个代理对象,由代理对象去完成
作用:就是对被代理对象的功能进行增强
2.动态代理概述
底层通过反射技术,动态地(在运行时,需要时)生成被代理对象的代理对象
3.案例引出
/**
* 学校提供的教师服务
*/
public interface SchoolService {
String login(String loginName, String passWord);
String getAllClazzs();
}
/**
* 学校提供的教师服务的实现类
*/
public class SchoolServiceImpl implements SchoolService {
/**
* 登录服务
*/
@Override
public String login(String loginName, String passWord) {
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "恭喜您,登录成功...";
}
/**
* 查询班级服务
*/
@Override
public String getAllClazzs() {
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "1班,2班,3班";
}
}
public class TestDemo {
public static void main(String[] args) {
//1.创建一个实现类对象
SchoolServiceImpl service = new SchoolServiceImpl();
//2.登录
String result = service.login("laowang123", "gebi123");
System.out.println(result);
//3.查询班级
String allClazzs = service.getAllClazzs();
System.out.println(allClazzs);
}
}
4.使用动态代理优化代码
public class TestDemo {
public static void main(String[] args) {
//1.为学校的服务实现类,创建一个动态代理
/**
* Java提供创建代理对象的方法
* Proxy.newProxyInstance(
* 参数1:当前类的类加载器 基本上固定: 当前类.class.getClassLoader()
* 参数2:被代理对象所有实现的接口字节码对象数组 new Class[]{接口1.class,接口2.class..}
* 参数3:处理接口的实现类对象(处理类对象)
* );
*/
SchoolService serviceProxy = (SchoolService)Proxy.newProxyInstance(TestDemo.class.getClassLoader(), new Class[]{
SchoolService.class}, new InvocationHandler() {
/**
* 当我们调用动态代理对象的方法时,动态代理对象,会通过反射,把调用的方法,以及方法的参数封装起来
* @param proxy 其实就是代理对象
* @param method 通过代理对象调用的方法
* @param args 通过带俩对象调用方法时传入的参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object result = method.invoke(new SchoolServiceImpl(),args);
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)+"毫秒");
return result;
}
});
//2.调用动态代理对象的方法
// String result = serviceProxy.login("1111", "2222");
// System.out.println(result);
//
String allClazzs = serviceProxy.getAllClazzs();
System.out.println(allClazzs);
}
}
第四章 JDK8新特性
Lambda
Stream
1. 方法引用
方法引用介绍
允许开发者可以直接引用现有的方法,来代理函数式接口的匿名内部类(Lambda)
方法引用四种方式
a.通过对象名,引用对象中成员方法
对象名::成员方法名
b.通过类名,直接引用类中的静态方法
类名::静态方法名
c.引用类的构造器
类名::new
d.引用数组的构造器
数据类型[]::new
基于静态方法引用的代码演示
public class TestDemo {
public static void main(String[] args) {
//调用方法
// method(new 接口的实现类对象());
// method(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
// method(s -> System.out.println(s));
//使用方法引用
method(System.out::println);
//回顾:
Stream<String> stream = Stream.of("jack", "rose", "tom", "jerry");
// stream.forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
// stream.forEach(s -> System.out.println(s));
stream.forEach(System.out::println);
}
public static void method(Consumer<String> con) {
con.accept("java");
}
}
2. Base64
Base64介绍
一种编码技术
Base64内嵌类和方法描述
Base64.EnCoder和Base64.DeCoder
Base64.MimeEnCoder和Base64.MimeDeCoder
Base64.URLEnCoder和Base64.URLDeCoder
Base64代码演示
public class Base64Demo {
public static void main(String[] args) {
//1.EnCoder和DeCoder
Base64.Encoder encoder = Base64.getEncoder();
String encodeToString = encoder.encodeToString("中国HelloWorld我爱你".getBytes());
System.out.println("编码之后的字符串:"+encodeToString);
Base64.Decoder decoder = Base64.getDecoder();
byte[] bs = decoder.decode(encodeToString);
System.out.println("解码之后的字符串:" + new String(bs));
System.out.println("--------------");
//2.MIME类型编码和解码
String encodeToString1 = Base64.getMimeEncoder(4,"-".getBytes()).encodeToString("中国HelloWorld我爱你".getBytes());
System.out.println("编码之后的字符串:"+encodeToString1);
byte[] bs1 = Base64.getMimeDecoder().decode(encodeToString1);
System.out.println("解码之后的字符串:" + new String(bs1));
//3.URLEnCoder和URLDeCoder
System.out.println("--------------");
byte[] bs2 = Base64.getUrlEncoder().encode("http://www.itheima.com".getBytes());
System.out.println("编码之后的字符串:"+new String(bs2));
byte[] bytes = Base64.getUrlDecoder().decode(bs2);
System.out.println("解码之后的字符串:"+new String(bytes));
//UUID 生成一个32个长度的全球唯一字符串
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
}
}
总结
"能够通过反射技术获取Class字节码对象
"能够通过反射技术获取构造方法对象,并创建对象。
"能够通过反射获取成员方法对象,并且调用方法。
能够通过反射获取属性对象,并且能够给对象的属性赋值和取值。
"能够说出注解的作用
编译检查
"能够自定义注解和使用注解
public @interface 注解名{
数据类型 属性名();
数据类型只能是三大类: 基本类型,四个引用类型(String),以上12个一维数组
}
@注解名(属性名="具体的值",....)
特殊value
能够说出常用的元注解及其作用
@Target
@Rentetion
"能够解析注解并获取注解中的数据
a.获取Class
b.获取具体对象(Method,Constructor,Field)
c.判断 isAnnotaionPresent(注解名.class);
d.取出 getAnnotaion(注解名.class);
e.获取注解中的属性值 注解对象.属性名();
能够完成注解的MyTest案例
能够说出动态代理模式的作用
能够使用Proxy的方法生成代理对象
能够使用四种方法的引用(建议观看课后扩展视频_方法引用完整版)
能够使用Base64对基本数据、URL和MIME类型进行编解码
还没有评论,来说两句吧...