反射、注解、Junit
反射反射: 概述 *反射:框架设计的灵魂
* 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
* 反射:将类的各个组成部分封装为其他对象,这就是反射机制
* 好处: 1. 可以在程序运行过程中,操作这些对象。 2. 可以解耦,提高程序的可扩展性。
Java代码在计算机中的3个阶段:
SOURCE:源代码阶段
CLASS:类对象阶段
RUNTIME:运行时阶段
获取字节码对象的3种方式
获取一个类的字节码对象的3种方式: 1. Class.forName("全类名") 将字节码文件加载进内存,返回Class对象 适用场景: 多用于配置文件,将类名定义在配置文件中. 读取文件, 加载类 2. 类名.class . 通过类名的属性class获取 适用场景: 多用于参数的传递 getConstructor(String.class, int.class) 3. 对象.getClass() getClass()方法在Object类中定义 适用场景: 多用于对象的获取字节码的方式 p.getClass() 同一个类的字节码对象, 只有"唯一的一个"
Class的方法概述
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容// 成员方法// 1. 获取成员变量们 Field[] getFields(): 获取所有 public 的成员变量 Field getField(String name): 获取指定名称的 public 的成员变量 Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符 Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符// 2. 获取构造方法们 Constructor<?>[] getConstructors(): 获取所有 public 的构造方法 Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 构造方法 Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符 Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取指定的构造方法, 不考虑权限修饰符// 3. 获取成员方法们: Method[] getMethods(): 获取所有 public 的成员方法 Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 成员方法 Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符 Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不考虑权限修饰符// 4. 获取Class对象代表的类的全类名 String getName(): 获取当前Class对象代表的类的全类名// 5. 创建对象 T newInstance(): 使用当前类的空参构造, 创建一个对象
反射: 获取成员变量Field
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容 // 获取功能 1. 获取成员变量们 Field[] getFields(): 获取所有 public 的成员变量 Field getField(String name): 获取指定名称的 public 的成员变量 Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符 Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符java.lang.reflect.Field: 表示一个成员变量 // 成员方法 String name; Person p = new Person(); p2.name = "abc"; void set(Object obj, Object value): 设置指定对象的成员变量的值 field.set(p1, "abc") Object get(Object obj): 获取指定对象的成员变量的值 field.get(p1) void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射 field.set
反射: 获取构造方法Constructor
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容 // 获取构造方法们 Constructor<?>[] getConstructors(): 获取所有 public 的构造方法 Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 构造方法 Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符 Constructor<T> getDeclaredConstructor(Class... parameterTypes): 获取指定的构造方法, 不考虑权限修饰符 T newInstance(): 使用当前类的空参构造创建一个对象 Constructor con = c.getConstructor(String.class, int.class); con.newInstance("zhangsan", 18); newPerson("zhangsan", 18); java.lang.reflect.Constructor<T>: 表示一个构造方法 // 成员方法 T newInstance(Object... initargs): 使用当前构造方法传入参数, 创建对象 void setAccessible(boolean flag): 注意: 构造方法不能利用此方法忽略权限, 会抛异常
反射: 获取成员方法Method
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容 // 获取成员方法们: Method[] getMethods(): 获取所有 public 的成员方法 Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 成员方法 Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符 Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不考虑权限修饰符 java.lang.reflect.Method类: 表示一个成员方法 // 成员方法 Person p = new Person(); p.eat("adf", 123); Object invoke(Object obj, Object... args): 使用指定对象和指定参数值调用此方法 String getName(): 获取方法名 void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
反射: 利用反射实现可以运行任意类的任意方法的框架案例
java.lang.Class<T> // 成员方法 ClassLoader getClassLoader(): 返回该类的类加载器 java.lang.ClassLoader: 类加载器 加载.class文件到内存的方法区中, 其他类型文件.properties // 成员方法 InputStream getResourceAsStream(String name): 读取相对于 out/production/模块名 目录中的文件, 返回一个字节流 使用类加载器加载配置文件 // 随便获取一个类的字节码对象 Class clazz = 类名.class; // 用字节码对象获取类加载器, 可加载bin目录中编译的文件 ClassLoader classLoader = clazz.getClassLoader(); // 使用类加载器加载一个文件, 返回一个字节流 InputStream is = classLoader.getResourceAsStream("相对于src目录的相对路径"); // 有了字节流, 就可以使用Properties的load(InputStream in)方法读取配置 Properties p = new Properties(); p.load(is); String value = p.getProperty("key");
注解注解: 概念
注解: Annotation JDK 1.5 引入. 也叫元数据, 是一种代码级别的说明 它可以声明在包, 类, 字段(成员变量), 方法, 局部变量, 方法参数等的前面, 用来对这些元素进行说明注解: 说明程序的。给计算机看的注释: 用文字描述程序的。给程序员看的使用注解: @注解名称作用分类: 1. 编写文档: 通过代码里标识的注解生成文档 (生成API文档 @author @version @since @param @return) 2. 代码分析: 通过代码里标识的注解对代码进行分析(使用反射) (JUnit提供的 @Test @Before @After) 3. 编译检查: 通过代码里标识的注解让编译器能够实现基本的编译检查 (@Override @FunctionalInterface)
注解: JDK内置注解
JDK中预定义的一些注解: @Override: 检测被该注解标注的方法是否是"重写"父类(接口)的 @Deprecated: 该注解标注的内容,表示已过时 @SuppressWarnings: 压制警告. 一般传递参数all @SuppressWarnings("all")
自定义注解: 格式和本质
public interface 接口名 {}自定义注解格式:关键字 @interface 元注解 public @interface 注解名称 { 属性; (接口中的抽象方法) 属性; 属性; ... }@注解名称 注解的本质: 注解本质上就是一个接口,该接口默认继承Annotation接口 public interface MyAnno extends java.lang.annotation.Annotation {}
自定义注解: 属性定义
属性: 接口中的"抽象方法" 属性的要求: 1. 属性的"返回值类型"可以是以下类型: 基本数据类型(8种) String 枚举 注解 以上类型的数组 2. 定义了属性,在使用注解时, 需要"给属性赋值" (其实是抽象方法的返回值) 1. 属性使用 default 关键字指定默认值, 则可以不赋值 2. 如果只有一个名为"value"的属性需要赋值, 则 value 可以省略, 直接写值即可 3. 给数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
自定义注解: 元注解
元注解: 用于描述注解的注解 常用元注解: @Target: 描述注解能够作用的位置 ElementType枚举的常用取值: TYPE:可以作用于类上 METHOD:可以作用于方法上 FIELD:可以作用于成员变量上 示例: @Target(value = {ElementType.TYPE, ElementType.METHOD}) @Retention: 描述注解被保留的阶段 RetentionPolicy枚举的取值: SOURCE: 保留到源代码阶段 CLASS: 保留到类对象阶段 RUNTIME: 保留到运行时阶段 示例: @Retention(RetentionPolicy.RUNTIME):保留注解到class字节码文件中并被JVM读取到 @Documented: 加上后, 当前注解会被抽取到api文档中 @Inherited: 加上后, 当前注解会被子类继承
自定义注解: 解析注解
获取注解属性值的步骤: 1. 获取注解定义位置的对象 (Class对象(类注解), Field对象(成员变量注解), Method对象(方法注解)) 2. 调用 ProAnno a = cls.getAnnotation(ProAnno.class) 方法获取注解对象 3. 通过注解对象调用抽象方法获取属性值 // 比如获取一个类上的注解 注解类型 注解变量名 = 被注解的类.class.getAnnotation(注解名.class); 数据类型 变量名 = 注解变量名.抽象方法(); ProAnno proAnno = Test.class.getAnnotation(ProAnno.class); String className = proAnno.className(); String methodName = proAnno.methodName();
注解案例: 自己实现测试框架
java.lang.reflect.Method类: // 继承的方法 boolean isAnnotationPresent(注解名.class): 判断当前方法上是否出现了某个注解java.lang.Class类: String getName(): 获取类的全名 com.itheima.Person String getSimpleName(): 获取类名不含包名 Personjava.lang.Throwable String getMessage(): 获取异常的详细原因 // 成员方法 Throwable getCause(): 获取真正导致异常的根本异常 try { // 调用方法 method.invoke(calculator); } catch (Exception e) { // 计数器增加 number++; // 获取方法名 method.getName(); // 获取异常的类型 e.getCause().getClass().getSimpleName();// 可以获取最根本异常NullPointerException e.getClass().getSimpleName(); // 不能获取最根本异常 InvocationTargetException // 获取异常的详细原因 e.getMessage(); }
JUnit单元测试JUnit: 使用步骤
JUnit使用步骤: 1. 定义一个测试类(也叫测试用例) 包名:xxx.xxx.xx.test 被测试的类名: Calculator 对应的测试类名: CalculatorTest 2. 定义测试方法:可以独立运行 被测试的方法名: add() 对应的测试方法名: testAdd() 建议测试方法的返回值是void, 参数列表是空参 3. 在方法上加 @Test 注解 4. 在 @Test 注解上按 Alt+Enter, 选择 "Add 'JUnit4' to Classpath" 导入JUnit依赖环境 5. 在方法名上右键, 选择 "Run '方法名()'" 判定结果: 红色:失败 绿色:成功.(测试通过) 断言: Assert 使用断言操作来判断结果是否符合预期: Assert.assertEquals(期望的结果, 运算的结果); 如果 期望的结果 和 运算的结果 相等, 则认为测试通过, 否则测试失败 测试失败的原因提示: java.lang.AssertionError: Expected :1 (表示我期望得到的是1) Actual :-1 (但是实际得到的是-1)
JUnit: @Before, @After
@Before: 修饰的方法会"在每个测试方法执行 之前"被执行@After: 修饰的方法会"在每个测试方法执行 之后"被执行注意: @Before, @After 修饰的方法可以有多个, 但是谁先执行是由JUnit内部来决定的, 没有明显的规律 所以不要写多个@Before, @After 修饰的方法
Stream流、方法引用
Stream流流式思想概述
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组 等。
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
获取流
- 所有的Collection集合都可以通过stream默认方法获取流;
- Stream接口的静态方法of可以获取数组对应的流。
根据Collection获取流
import java.util.*;import java.util.stream.Stream;public class Demo04GetStream { public static void main(String[] args) { List<String> list = new ArrayList<>(); // ... Stream<String> stream1 = list.stream(); Set<String> set = new HashSet<>(); // ... Stream<String> stream2 = set.stream(); Vector<String> vector = new Vector<>(); // ... Stream<String> stream3 = vector.stream(); }}根据Map获取流
import java.util.HashMap;import java.util.Map;import java.util.stream.Stream;public class Demo05GetStream { public static void main(String[] args) { Map<String, String> map = newHashMap<>(); // ... Stream<String> keyStream = map.keySet().stream(); Stream<String> valueStream = map.values().stream(); Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream(); }}根据数组获取流
import java.util.stream.Stream;public class Demo06GetStream { public static void main(String[] args) { String[] array = { "张无忌", "张翠山", "张三丰", "张一元" }; Stream<String> stream = Stream.of(array); }}
备注:of方法的参数其实是一个可变参数,所以支持数组。
1.4 常用方法
-
延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
-
终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。本小节中,终结方法包括count和forEach方法。
备注:本小节之外的更多方法,请自行参考API文档。
逐一处理:forEach
void forEach(Consumer<? super T> action);
复习Consumer接口java.util.function.Consumer<T>接口是一个消费型接口。Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。基本使用:import java.util.stream.Stream;public class Demo12StreamForEach { public static void main(String[] args) { Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若"); stream.forEach(name-> System.out.println(name)); }}过滤:filter
Stream<T> filter(Predicate<? super T> predicate);
复习Predicate接口
boolean test(T t);
基本使用
import java.util.stream.Stream;public class Demo07StreamFilter { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.filter(s -> s.startsWith("张")); }}
映射:map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
复习Function接口
R apply(T t);
基本使用
import java.util.stream.Stream;public class Demo08StreamMap { public static void main(String[] args) { Stream<String> original = Stream.of("10", "12", "18"); Stream<Integer> result = original.map(str->Integer.parseInt(str)); }}
统计个数:count
long count();
import java.util.stream.Stream;public class Demo09StreamCount { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.filter(s -> s.startsWith("张")); System.out.println(result.count()); // 2 }}取用前几个:limit
Stream<T> limit(long maxSize);
import java.util.stream.Stream;public class Demo10StreamLimit { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.limit(2); System.out.println(result.count()); // 2 }}跳过前几个:skip
Stream<T> skip(long n);
import java.util.stream.Stream;public class Demo11StreamSkip { public static void main(String[] args) { Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); Stream<String> result = original.skip(2); System.out.println(result.count()); // 1 }}组合:concat
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
备注:这是一个静态方法,与java.lang.String当中的concat方法是不同的。
import java.util.stream.Stream;public class Demo12StreamConcat { public static void main(String[] args) { Stream<String> streamA = Stream.of("张无忌"); Stream<String> streamB = Stream.of("张翠山"); Stream<String> result = Stream.concat(streamA, streamB); }}集合元素处理(Stream方式)题目
解答
import java.util.ArrayList;import java.util.List;import java.util.stream.Stream;public class DemoStreamNames { public static void main(String[] args) { List<String> one = new ArrayList<>(); // ... List<String> two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名; // 第一个队伍筛选之后只要前3个人; Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3); // 第二个队伍只要姓张的成员姓名; // 第二个队伍筛选之后不要前2个人; Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2); // 将两个队伍合并为一个队伍; // 根据姓名创建Person对象; // 打印整个队伍的Person对象信息。 Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println); }}
Person{name='宋远桥'}Person{name='苏星河'}Person{name='石破天'}Person{name='张天爱'}Person{name='张二狗'}方法引用
2.1 冗余的Lambda场景
@FunctionalInterfacepublic interface Printable { void print(String str);}
public class Demo01PrintSimple { private static void printString(Printable data) { data.print("Hello, World!"); } public static void main(String[] args) { printString(s -> System.out.println(s)); }}
2.2 问题分析
2.3 用方法引用改进代码
public class Demo02PrintRef { private static void printString(Printable data) { data.print("Hello, World!"); } public static void main(String[] args) { printString(System.out::println); }}
2.4 方法引用符
语义分析
- Lambda表达式写法:s -> System.out.println(s);
- 方法引用写法:System.out::println
推导与省略
@FunctionalInterfacepublic interface PrintableInteger { void print(int str);}
public class Demo03PrintOverload { private static void printInteger(PrintableInteger data) { data.print(1024); } public static void main(String[] args) { printInteger(System.out::println); }}
2.5 通过对象名引用成员方法
public class MethodRefObject { public void printUpperCase(String str) { System.out.println(str.toUpperCase()); }}
@FunctionalInterfacepublic interface Printable { void print(String str);}
public class Demo04MethodRef { private static void printString(Printable lambda) { lambda.print("Hello"); } public static void main(String[] args) { MethodRefObject obj = newMethodRefObject(); printString(obj::printUpperCase); }}2.6 通过类名称引用静态方法
@FunctionalInterfacepublic interface Calcable { int calc(int num);}
public class Demo05Lambda { private static void method(int num, Calcable lambda) { System.out.println(lambda.calc(num)); } public static void main(String[] args) { method(-10, n -> Math.abs(n)); }}
public class Demo06MethodRef { private static void method(int num, Calcable lambda) { System.out.println(lambda.calc(num)); } public static void main(String[] args) { method(-10, Math::abs); }}
- Lambda表达式:n -> Math.abs(n)
- 方法引用:Math::abs
2.7 通过super引用成员方法
@FunctionalInterfacepublic interface Greetable { void greet();}
public class Human { public void sayHello() { System.out.println("Hello!"); }}
public class Man extends Human { @Override public void sayHello() { System.out.println("大家好,我是Man!"); } //定义方法method,参数传递Greetable接口 public void method(Greetable g){ g.greet(); } public void show(){ //调用method方法,使用Lambda表达式 method(()->{ //创建Human对象,调用sayHello方法 new Human().sayHello(); }); //简化Lambda method(()->newHuman().sayHello()); //使用super关键字代替父类对象 method(()->super.sayHello()); }}
public class Man extends Human { @Override public void sayHello() { System.out.println("大家好,我是Man!"); } //定义方法method,参数传递Greetable接口 public void method(Greetable g){ g.greet(); } public void show(){ method(super::sayHello); }}
- Lambda表达式:() -> super.sayHello()
- 方法引用:super::sayHello