A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 Yilin 于 2018-8-13 22:52 编辑

Stream流、方法引用、JUnit、反射、注解笔记

一、 Stream流
1.1 流式思想概述
1.1.1 Stream流式思想处理数据的方式

让代码像流水线一样, 一步一步的对数据进行处理, 得到最终的结果
流也可以看作是一个容器, 里面可以装很多元素, 形成元素流
1.1.2 流相比于集合的2个优点:
1. Pipelining(管道特性)
Stream流对象的一些方法调用完毕后, 会返回新的Stream流对象, 类型相同, 可链式调用, 类似于"管道"
2. 内部迭代特性:
集合遍历通过 Iterator 或者 增强for, 显式的在集合外部进行迭代, 这叫做外部迭代
Stream提供了内部迭代的方法 forEach(), 可以直接调用遍历方法
使用Stream流的3个步骤:
1. 获取数据源 (集合, 数组)
2. 数据转换
3. 获得结果



1.1.3 2种获取Stream流的方式
获取Stream流对象的2种方式:
1. 利用Collection接口中的默认方法 default Stream<E> stream() 方法: 集合转Stream对象
2. 利用Stream接口中的静态方法 static<T> Stream<T> of(T... values): 数组转Stream对象
java.util.Collection<E>接口:
// 默认方法
[AppleScript] 纯文本查看 复制代码
default Stream<E> stream(): 将"集合"转换为Stream对象
java.util.stream.Stream<T>接口: 管道接口, 泛型为流中元素的类型
// 静态方法
static<T> Stream<T> of(T... values): 将"数组"转换为Stream对象


// 集合转换为Stream流对象
[AppleScript] 纯文本查看 复制代码
List<String> list = new ArrayList<>();
Stream<String> listStream = list.stream(); // List集合的Stream
Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream(); // Set集合的Stream
Map<String,String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream(); // 键的集合的Stream
Stream<String> valueStream = map.values().stream(); // 值的集合的Stream
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream(); // 键值对的集合的Stream
// 数组转换为Stream流对象
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
String[] array = {"a", "b", "c"};
Stream<String> arrayStream = Stream.of(array);

1.2 Stream API: 方法分类, forEach()遍历
1.2.1延迟方法: (具有延迟执行的特性)
返回值类型 是 Stream接口自身类型的方法, 支持链式调用
filter(): 过滤
map(): 映射/转换
limit(): 截取
skip(): 跳过
1.2.2 终结方法:
返回值类型 不是 Stream接口自身类型的方法, 不支持链式调用
forEach(): 遍历
count(): 统计
注意:
除了终结方法外, 其余方法均为延迟方法
------------------------------------------------------
创建数据源 -> filter()过滤 | map()映射 | limit()截取 | skip()跳过 -> forEach() | count()结果
------------------------------------------------------
获取流 转换 聚合
延迟方法 终结方法
[AppleScript] 纯文本查看 复制代码
new ArrayList().filter(...).map(...).limit(...).skip(...).forEach(...);
Stream.of(1,2,3).filter(...).map(...).limit(...).skip(...).count();

java.util.stream.Stream<T>接口: 管道接口
// 抽象方法
void forEach(Consumer<? super T> action): 遍历流中的元素进行逐一消费. 并不保证元素的逐一消费动作
在流中是被有序执行的
1.2.3 使用forEach()遍历集合
[AppleScript] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) {
String[] arr = {"张三", "李四", "王五", "赵六", "田七"};
// 将数组转换为流对象
Stream<String> stream = Stream.of(arr);
// 遍历流
stream.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
// 使用链式调用+Lambda表达式的标准格式
Stream.of("张三", "李四", "王五", "赵六", "田七")
.forEach((String name)-> {
System.out.println(name);
});
// Lambda的省略格式
Stream.of("张三", "李四", "王五", "赵六", "田七")
.forEach(name-> System.out.println(name));
}
}

1.3 Stream API: filter()过滤
1.3.1 filter()过滤介绍

java.lang.String类:
boolean startsWith(String prefix): 判断当前字符串是否以参数字符串开头
java.util.stream.Stream<T>接口: 管道接口
// 抽象方法
Stream<T> filter(Predicate<? super T> predicate): 过滤符合条件的结果. 返回过滤后的流
boolean test(String name)
1.3.2 使用filter()过滤数组
需求:
定义字符串数组: {"张三丰", "张翠山", "赵敏", "周芷若", "张无忌"}
将数组转换为Stream对象, 并过滤出姓张的, 并将元素打印出来

[AppleScript] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) {
// 普通方式
String[] arr = {"张三丰", "张翠山", "赵敏", "周芷若", "张无忌"};
Stream<String> stream1 = Stream.of(arr);
Stream<String> stream2 = stream1.filter(new Predicate<String>() {
@Override
public boolean test(String name) {
return name.startsWith("张");
}
});
stream2.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
System.out.println(name);
}
});

// 链式调用+Lambda表达式
[AppleScript] 纯文本查看 复制代码
Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌")
.filter(name->name.startsWith("张"))
.forEach(name-> System.out.println(name));
}
}

Stream流的特点: 只能使用一次
每次调用延迟方法返回的Stream流对象, 都是经过处理后返回的新的Stream流对象
之前的Stream流在调用方法后, 已经使用过并关闭了, 不能再次使用, 否则会抛出异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
1.4 Stream API: map()映射转换
1.4.1 map()映射转换介绍

java.util.stream.Stream<T>接口: 管道接口
// 抽象方法
<R> Stream<R> map(Function<T, R> mapper): 将当前流中的T类型的元素, 转换R类型元素, 放入新流并返回
R apply(T t)
1.4.2 使用map()转换数组元素
需求:
定义字符串数组: {"1", "2", "3", "4"}
将数组转换为Stream流, 使用map()方法将流中的字符串都转换为int, 遍历输出转换后的结果

[AppleScript] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) {
String[] arr = {"1", "2", "3", "4"};
Stream<String> stream1 = Stream.of(arr);
Stream<Integer> stream2 = stream1.map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
});
stream2.forEach(i-> System.out.println(i));
// 链式调用+Lambda表达式
Stream.of("1", "2", "3", "4")
.map(s -> Integer.parseInt(s))
.forEach(i-> System.out.println(i));
}
}

1.5 Stream API: count()统计流中元素个数
1.5.1 count()统计流介绍

java.util.stream.Stream<T>接口: 管道接口
// 抽象方法
long count(): 获取流中的元素个数 (终结方法)
1.5.2 使用count()统计个数
需求:
创建一个ArrayList集合, 存储整数: 1,2,3,4,5,6,7
将集合转换为Stream流对象, 调用count()获取流中元素的个数, 打印到控制台

[AppleScript] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
long count = list.stream().count();
System.out.println(count);
// 利用JDK9提供的快速创建集合方法
long count2 = List.of(1, 2, 3, 4, 5, 6, 7)
.stream()
.count();
System.out.println(count2);
}
}

1.6 Stream API: limit()获取前n个(只要前n个)
1.6.1  limit()介绍

java.util.stream.Stream<T>接口: 管道接口
// 抽象方法
Stream<T> limit(long maxSize): 从流中获取前maxSize个. 如果maxSize大于等于元素个数, 则返回所有元
素的流
1.6.2 使用limit()获取数组中前3个元素
需求:
创建字符串数组: {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"}
将数组转换为Stream流, 从流中获取前3个元素, 将截取后的流遍历打印输出到控制台

[AppleScript] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) {
String[] arr = {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"};
// Stream.of(arr)
// .limit(3)
// .forEach(name-> System.out.println(name));
Stream.of("美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼")
.limit(300) // 超出实际大小, 有多少返回多少
.forEach(name-> System.out.println(name));
}
}


1.7 Stream API: skip()跳过前n个(不要前n个)
1.7.1 skip()接口介绍

java.util.stream.Stream<T>接口: 管道接口
// 抽象方法
Stream<T> skip(long n): 从流中跳过n个元素, 获取后面的元素. 如果n大于等于元素个数, 则全都跳过
1.7.2 使用skip()跳过数组中的元素
需求:
创建字符串数组: {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"}
将数组转换为Stream流, 从流中跳过前3个元素, 遍历打印输出到控制台
[AppleScript] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) {
String[] arr = {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"};
Stream.of(arr)
.skip(3)
.forEach(name-> System.out.println(name));
System.out.println("-----------");
Stream.of("美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼")
.skip(3)
.forEach(name-> System.out.println(name));
}
}

1.8 Stream API: 静态方法concat()合并两个流
1.8.1  concat()介绍

java.util.stream.Stream<T>接口: 管道接口
// 静态方法
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): 合并两个流的元素,
变成一个新的流. 两个流中的元素类型必须相同, 或有共同的父类
1.8.2 使用concat()方法合并两个流, 并遍历输出
需求:
创建2个字符串数组:
{"张三丰", "张翠山", "赵敏", "周芷若", "张无忌"}
{"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"}
将2个数组分别转换为Stream流对象, 并使用Stream的静态方法concat()将2个流拼接为新的流, 遍历打印新流的元素
[AppleScript] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) {
String[] arr1 = {"张三丰", "张翠山", "赵敏", "周芷若", "张无忌"};
String[] arr2 = {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"};
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(name-> System.out.println(name));
System.out.println("---------");
// 简化
Stream.concat(
Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌"),
Stream.of("美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼")
).forEach(name-> System.out.println(name));
}
}

二、 方法引用
2.1 方法引用基本介绍

方法引用: Method Reference
如果Lambda表达式仅仅是调用一个已经存在的方法, 那就可以通过方法引用来替代Lambda表达式
作用: 简化Lambda表达式
:: 方法引用运算符, 它所在的表达式被称为方法引用
Lambda表达式写法:
(String s) -> System.out.println(s)
参数传递给System.out.println()方法去打印
方法引用写法:
System.out::println
用System.out.println()方法中代码, 来作为Lambda中重写方法的实现方式
注意:
Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常
方法引用能简化以下场景: (方法名后不要写小括号)
1. 通过对象名引用成员方法 对象名::成员方法名 System.out::println
2. 通过类名引用静态方法 类名::静态方法名 i -> Math.abs(i) Math::abs
3. 通过super引用父类成员方法 super::父类方法名 ()->super.eat(); super::eat
4. 通过this引用本类成员方法 this::本类方法名 ()->this.eat(); this::eat
5. 引用某个类的构造方法 类名::new name->new Person(name) Person::new
6. 引用创建数组的方法 数据类型[]::new length->new int[length]; int[]::new

[AppleScript] 纯文本查看 复制代码
@FunctionalInterface
public interface Printable {
//定义字符串的抽象方法
void print(String s);
}
public class Demo01Printable {
//定义一个方法,参数传递Printable接口,对字符串进行打印
public static void printString(Printable p){
p.print("HelloWorld");
}
public static void main(String[] args) {
// 匿名内部类方式
printString(new Printable(){
@Override
public void print(String s){
System.out.println(s);
}
});
//Lambda方式
printString((s)->{
System.out.println(s); // 只是调用了一个已经存在的System.out.println()
});
// 方法引用
printString(System.out::println);
}
}

2.2 方法引用: 通过对象名引用成员方法
2.2.1 通过对象名引用成员方法

对象名::成员方法名
适用场景:
当Lambda表达式中, 仅仅是"通过某个对象调用已有的方法"时, 就可以用这种方式简化
// 定义函数式接口: 模拟Java中已经提供的函数式接口

[AppleScript] 纯文本查看 复制代码
@FunctionalInterface
public interface Printable {
// 定义功能: 打印s. 功能怎么实现先不管
void print(String s);
}
// 定义一个类: 模拟Java中已经定义好的类和方法
public class MethodRefObject {
// 将字符串变大写再打印出来
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
public class Test {
// 定义方法用于打印Hello, 参数为 打印字符串的方式
public static void printString(Printable lambda){
lambda.print("Hello");
}
public static void main(String[] args) {
// 普通Lambda方式
printString((s)->{
// 创建对象, 调用已经存在方法printUpperCase
MethodRefObject obj = new MethodRefObject();
obj.printUpperCaseString(s);
});
// 使用方法引用简化
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCaseString);
// 直接引用obj的printUpperCaseString方法作为抽象方法print的实现
}
}

2.2.2 简化对象调用方法
[AppleScript] 纯文本查看 复制代码
public class Test {
// 定义方法用于打印Hello, 参数为 打印字符串的方式
public static void printString(Printable lambda){
lambda.print("Hello");
}
public static void main(String[] args) {
// 匿名内部类方式
printString(new Printable() {
@Override
public void print(String s) {
MethodRefObject obj = new MethodRefObject();
obj.printUpperCaseString(s);
}
});
// 普通Lambda方式调用printString方法: 创建MethodRefObject对象, 调用已经存在方法
printUpperCase
printString(s->{
// 利用MethodRefObject中的printUpperCaseString转换为大写打印
MethodRefObject obj = new MethodRefObject();
obj.printUpperCaseString(s);
});
// 方法引用方式调用printString方法: 创建MethodRefObject对象, 直接引用obj的
printUpperCaseString方法
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCaseString);
// 一行代码实现
printString(new MethodRefObject()::printUpperCaseString);
}
}

2.3 方法引用: 通过类名引用静态方法
2.3.1 通过类名引用静态方法

类名::静态方法名 Math.abs(1) Math::abs
适用场景:
当Lambda表达式中, 仅仅是"通过某个类名调用已有的静态方法"时, 就可以用这种方式简化
// 定义函数式接口: 模拟Java中已经提供的函数式接口
[AppleScript] 纯文本查看 复制代码
@FunctionalInterface
public interface Calcable {
// 计算整数的绝对值并返回
int calcAbs(int number);
}
public class Test {
//定义方法用来计算一个数的结果. 参数: 被计算的整数 和 计算的方式
public static int method(int number, Calcable c){
return c.calcAbs(number);
}
public static void main(String[] args) {
// Lambda方式
int number = method(
-10,
(n)->{
return Math.abs(n); //对参数进行绝对值得计算并返回结果
});
System.out.println(number);
// 方法引用
int number2 = method(-10, Math::abs); //直接引用Math类中的abs方法作为calcAbs抽象方法的实现
System.out.println(number2);
}
}


引用静态方法
[AppleScript] 纯文本查看 复制代码
public class Test {
//定义方法用来计算一个数的结果. 参数: 被计算的整数 和 计算的方式
public static int method(int number, Calcable c){
return c.calcAbs(number);
}
public static void main(String[] args) {
// Lambda方式调用method方法: 传递-10和Lambda, 调用Math.abs()方法计算出结果, 并返回
int result1 = method(-10, (number)->{return Math.abs(number);});
System.out.println(result1); // 10
// 方法引用方式调用method方法: 传递-10, 直接引用Math类中的abs方法
int result2 = method(-10, Math::abs);
System.out.println(result2); // 10
}
}

2.4 方法引用: 通过super引用父类成员方法
2.4.1 通过super引用父类成员方法

super::父类方法名
适用场景
当Lambda表达式中, 仅仅是"调用父类某个已有的方法"时, 就可以用这种方式简化
// 定义见面打招呼的函数式接口: 模拟Java中已经提供的函数式接口
[AppleScript] 纯文本查看 复制代码
@FunctionalInterface
public interface Greetable {
// 见面打招呼的方法
void greet();
}
// 定义父类: 人类
public class Human {
//定义一个sayHello的方法
public void sayHello(){
System.out.println("Hello 我是Human!");
}
}
// 定义子类: 男人类, 继承人类
public class Man extends Human {
// 子类重写了父类sayHello的方法
@Override
public void sayHello() {
System.out.println("Hello 我是Man!");
}
//定义方法用来见面打招呼. 参数是见面打招呼的方式
public void method(Greetable g){
g.greet();
}

三、 JUnit单元测试
3.1 使用步骤
3.1.1 定义一个测试类(也叫测试用例)

包名:xxx.xxx.xx.test
被测试的类名: Calculator
对应的测试类名: CalculatorTest
3.1.2定义测试方法:可以独立运行
被测试的方法名: add()
对应的测试方法名: testAdd()
建议测试方法的返回值是void, 参数列表是空参
3.1.3 在方法上加 @Test 注解
3.1.4 在@Test注解上按 Alt+Enter

选择 Add 'JUnit4' to Classpath 导入junit依赖环境
3.1.5 在方法名上右键, 选择 Run '方法名()'
判定结果:
红色:失败
绿色:成功.(测试通过)
断言: Assert
使用断言操作来判断结果是否符合预期:
Assert.assertEquals(期望的结果, 运算的结果);
如果 期望的结果 和 运算的结果 相等, 则认为测试通过, 否则测试失败
测试失败的原因提示:
java.lang.AssertionError:
Expected :1 (表示我期望得到的是1)
Actual :-1 (但是实际得到的是-1)
3.2 JUnit: @Before, @After
@Before: 修饰的方法会"在每个测试方法执行之前"被执行
@After: 修饰的方法会"在每个测试方法执行之后"被执行
注意:
@Before, @After 修饰的方法可以有多个, 但是谁先执行是由JUnit内部来决定的, 没有明显的规律
所以不要写多个@Before, @After 修饰的方法
3.2.1 练习: 使用@Before, @After
需求:
在CalculatorTest测试类中, 再定义两个方法:
[AppleScript] 纯文本查看 复制代码
@Before
public void start() {
System.out.println("开始测试方法");
}
@After
public void end() {
System.out.println("测试方法结束");
}
重新测试, 查看是否每个方法测试前后都有输出
代码:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
}
public class CalculatorTest {
@Before
public void start() {
System.out.println("开始测试方法");
}
@After
public void end() {
System.out.println("测试方法结束");
}
// 测试add方法的测试方法
@Test
public void testAdd() {
System.out.println("测试testAdd");
Calculator calculator = new Calculator();
int result = calculator.add(1, 2);
Assert.assertEquals(3, result);
}
// 测试sub方法的测试方法
@Test
public void testSub() {
System.out.println("测试testSub");
Calculator calculator = new Calculator();
int result = calculator.sub(1, 2);
Assert.assertEquals(-1, result);
}
}

四、 反射
4.1  框架
半成品软件. 可以在框架的基础上进行软件开发, 简化编码
比如JUnit就是一个单元测试框架, 它不是一个独立的软件, 而是和我们开发的软件结合, 简化代码测试
反射: 将类的各个组成部分, 封装为其他对象, 这就是反射机制
成员变量(字段): Field类的对象
构造方法: Constructor类的对象
成员方法: Method类的对象
好处:
1. 可以在程序运行过程中, 操作这些对象
2. 可以解耦, 提高程序的可扩展性
Java代码在计算机中的3个阶段:
SOURCE: 源代码阶段
CLASS: 类对象阶段
RUNTIME: 运行时阶段

4.2 反射: 获取字节码对象的3种方式
获取一个类的字节码对象的3种方式:
4.2.1 Class.forName("全类名")
将字节码文件加载进内存,返回Class对象
适用场景: 多用于配置文件,将类名定义在配置文件中. 读取文件, 加载类
4.2.2 类名.class .
通过类名的属性class获取
适用场景: 多用于参数的传递 getConstructor(String.class, int .class)
4.2.3 对象.getClass()
getClass()方法在Object类中定义
适用场景: 多用于对象的获取字节码的方式 p.getClass()
同一个类的字节码对象, 只有唯一的一个
Class clz
4.3反射: Class的方法概述
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 获取功能
4.3.1 获取成员变量们
Field[] getFields(): 获取所有 public 的成员变量
Field getField(String name): 获取指定名称的 public 的成员变量
Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符
Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符
4.3.2 获取构造方法们
Constructor<?>[] getConstructors(): 获取所有 public 的构造方法
Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 的构造方法
Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取指定的构造方法, 不
考虑权限修饰符
4.3.3获取成员方法们:
Method[] getMethods(): 获取所有 public 的成员方法
Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 的成员方法
Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符
Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不
考虑权限修饰符
4.3.4获取Class对象代表的类的全类名
String getName(): 获取当前Class对象代表的类的全类名
4.3.5 创建对象
T newInstance(): 使用当前类的空参构造创建一个对象
4.4 反射: 获取成员变量Field
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 获取功能
1. 获取成员变量们
Field[] getFields(): 获取所有 public 的成员变量
Field getField(String name): 获取指定名称的 public 的成员变量
Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符
Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符
java.lang.reflect.Field: 表示一个成员变量
// 成员方法 p1.age p2.age Field age p1.age =10; p2.age = 20; ageField.set(p2, 20)
void set(Object obj, Object value): 设置指定对象的成员变量的值
Object get(Object obj): 获取指定对象的成员变量的值
void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
4.5 使用反射获取类中成员变量, 并赋值和获取值
4.5.1需求:
定义Person类:
成员变量(注意权限):
public String name;
private int age;
生成无参/有参构造, set/get方法, toString方法
定义测试类
在main()方法中:
通过Person类名先获取Person类的Class对象
创建Person对象p
通过反射获取p对象的name属性的Field对象, 将姓名修改为"张三"
通过反射获取p对象的age属性的Field对象, 将年龄修改为18
打印p对象, 查看是否修改
代码:
[AppleScript] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException,
IllegalAccessException {
// 1. 获取Person类的字节码对象
Class personClass = Class.forName("com.itheima.test02.Person");
// Class personClass = Person.class;
// Class personClass = new Person().getClass()
Person p = new Person();
// 通过Person类的字节码对象获取name属性, 将姓名修改为"张三"
Field nameField = personClass.getDeclaredField("name");
nameField.set(p, "张三");
// 通过Person类的字节码对象获取age属性, 将年龄修改为18
Field ageField = personClass.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(p, 18);
System.out.println(p);
}
}

4.6反射: 获取构造方法Constructor
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 获取构造方法们
Constructor<?>[] getConstructors(): 获取所有 public 的构造方法
Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 的构造方法
Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符
Constructor<T> getDeclaredConstructor(Class... parameterTypes): 获取指定的构造方法, 不考虑权限
修饰符
T newInstance(): 使用当前类的空参构造创建一个对象
java.lang.reflect.Constructor<T>: 表示一个构造方法
// 成员方法
T newInstance(Object... initargs): 使用当前构造方法传入参数, 创建对象
void setAccessible(boolean flag): 注意: 构造方法不能利用此方法忽略权限, 会抛异常
4.7 反射: 获取成员方法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类: 表示一个成员方法
// 成员方法
Object invoke(Object obj, Object... args): 使用指定对象和指定参数值调用此方法
String getName(): 获取方法名
void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
4.8 练习: 利用反射实现一个框架
需求:
写一个"框架", 框架写好后不需改动框架任何代码, 只需修改配置文件中的类名和方法名, 就能帮我们创建任意类的对
象, 并且执行其中任意方法
代码:
[AppleScript] 纯文本查看 复制代码
配置文件pro.properties:
className=com.itheima.test03.Student
methodName=sleep
public class Person {
public void eat() {
System.out.println("吃东西");
}
}
public class Student {
public void sleep() {
System.out.println("睡觉");
}
}
public class Test {
public static void main(String[] args) throws Exception {
// 获取类加载器
ClassLoader cl = Test.class.getClassLoader();
InputStream is = cl.getResourceAsStream("pro.properties");
// 创建Properties对象, 加载流
Properties properties = new Properties();
properties.load(is);
// 通过键获取值, 得到类名和方法名
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
// 利用反射获取该类字节码对象
Class clazz = Class.forName(className);
// 创建该类的一个对象
Object obj = clazz.newInstance();
// 通过字节码对象获取指定的方法
Method method = clazz.getDeclaredMethod(methodName);
// 调用方法
method.invoke(obj);
}
}

五、 注解
5.1 概念
注解: Annotation
JDK 1.5 引入. 也叫元数据, 是一种代码级别的说明
它可以声明在包, 类, 字段(成员变量), 方法, 局部变量, 方法参数等的前面, 用来对这些元素进行说明
注解: 说明程序的。给计算机看的
注释: 用文字描述程序的。给程序员看的
使用注解: @注解名称
作用分类:
1. 编写文档: 通过代码里标识的注解生成文档 (生成API文档 @author @version @since @param @return)
2. 代码分析: 通过代码里标识的注解对代码进行分析 (使用反射)
3. 编译检查: 通过代码里标识的注解让编译器能够实现基本的编译检查 (@Override)
5.2 JDK内置注解
JDK中预定义的一些注解:
@Override: 检测被该注解标注的方法是否是"重写"父类(接口)的
@Deprecated: 该注解标注的内容,表示已过时
@SuppressWarnings: 压制警告. 一般传递参数all @SuppressWarnings("all")
5.3 自定义注解
5.3.1 自定义注解格式:

元注解
public @interface 注解名称 {
属性列表; (接口中的抽象方法)
}
5.3.2 注解的本质:
注解本质上就是一个接口,该接口默认继承Annotation接口
public interface MyAnno extends java.lang.annotation.Annotation {}
枚举:
enum, enumeration. JDK 1.5 引入
主要是用于定义一些相关的常量, 比如星期, 颜色, 用来进行区分
枚举定义方式:
[AppleScript] 纯文本查看 复制代码
public enum 枚举名 {
枚举常量1, 枚举常量2, 枚举常量3
}
// 示例
public enum Week { // 星期
SUNDAY, MONDAY, TUESDAY, WEDSDAY, THURSDAY, FRIDAY, SATURDAY;
}
setWeek(Week.SUNDAY);
public enum Color { // 颜色
RED, BLUE, YELLOW;
}
setColor(Color.RED);
System.out.pritnln(Color.RED); // RED
switch(color) {
case Color.RED:
// ...
break;
}
// 类似于类中静态常量
public class Color {
public static final int RED = 0;
public static final int BLUE = 1;
public static final int YELLOW = 2;
}
setColor(Color.RED);
switch(color) {
case Color.RED:
// 修改颜色为红色
break;
}
public enum Person { // 一些人
P1, P2, P3;
}
kill(Person.P2);

5.3.3 自定义注解: 属性定义
属性:接口中的抽象方法
属性的要求:
1. 属性的返回值类型可以是以下类型:
基本数据类型(8种)
String
枚举
注解
以上类型的数组
2. 定义了属性,在使用注解时需要"给属性赋值"
1. 属性使用 default 关键字指定默认值, 则可以不赋值
2. 如果只有一个名为"value"的属性需要赋值, 则 value 可以省略, 直接定义值即可
3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
[AppleScript] 纯文本查看 复制代码
public @interface MyAnno {
String name() default "张三"; // 有默认值
int value(); // 属性名为value
String[] strs(); // 字符串数组
}
@MyAnno(name = "李四", value = 10, strs = {"a", "b"})
public class Worker {
}

5.3.4 自定义注解: 元注解
元注解:
用于描述注解的注解
常用元注解:
@Target: 描述注解能够作用的位置
ElementType枚举的常用取值:
TYPE:可以作用于类上
METHOD:可以作用于方法上
FIELD:可以作用于成员变量上
示例: @Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention: 描述注解被保留的阶段
RetentionPolicy枚举的取值:
SOURCE: 保留到源代码阶段
CLASS: 保留到类对象阶段
RUNTIME: 保留到运行时阶段
示例: @Retention(RetentionPolicy.RUNTIME):当前被描述的注解会保留到class字节码文件中并被JVM
读取到
[AppleScript] 纯文本查看 复制代码
@Documented: 加上后, 当前注解会被抽取到api文档中
@Inherited: 加上后, 当前注解会被子类继承
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno {
int value();
String name() default "张三";
}

5.3.5 自定义注解: 解析注解
获取注解属性值的步骤:
1. 获取注解定义位置的对象 (Class对象, Field对象, Method对象)
2. 调用 getAnnotation(注解名.class) 方法获取注解对象
3. 通过注解对象调用抽象方法获取属性值
注解类型 注解变量名 = 被注解的类.class.getAnnotation(注解名.class);
数据类型 变量名 = 注解变量名.抽象方法();
[AppleScript] 纯文本查看 复制代码
ProAnno proAnno = Test.class.getAnnotation(ProAnno.class);
String className = proAnno.className();
String methodName = proAnno.methodName();

5.4 练习: 自定义测试框架
需求:
已知 Check注解 和 小明写的计算器类:
[AppleScript] 纯文本查看 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
/* 小明定义的计算器类 */
public class Calculator {
//加法
@Check
public void add(){
String str = null;
str.toString();
System.out.println("1 + 0 =" + (1 + 0));
}
//减法
@Check
public void sub(){
System.out.println("1 - 0 =" + (1 - 0));
}
//乘法
@Check
public void mul(){
System.out.println("1 * 0 =" + (1 * 0));
}
//除法
@Check
public void div(){
System.out.println("1 / 0 =" + (1 / 0));
}
public void show(){
System.out.println("永无bug...");
}
}

请定义测试类Test, 在main方法中实现测试框架:
1. 创建Calculator类的对象, 通过Calculator对象获取该类的Class对象
2. 通过Class对象的getMethods()方法, 获取包含所有public权限的Method对象的数组
3. 定义异常计数器变量 int number = 0;
4. 创建字节缓冲输出流对象指向bug.txt文件:
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
5. 遍历Method对象数组, 取出每个Method对象
6. if判断当前Method方法上是否使用了@Check注解: if(method.isAnnotationPresent(Check.class)){}
7. 如果有, 则调用该方法: method.invoke(计算器对象); 并使用try...catch捕获Exception
8. 在catch中, 一旦发生异常, 则异常计数器number++, 并利用BufferedWriter将异常信息写入文件:
[AppleScript] 纯文本查看 复制代码
bw.write(method.getName()+ " 方法出异常了");
bw.newLine();
bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因:"+e.getCause().getMessage());
bw.newLine();
bw.write("--------------------------");
bw.newLine();

代码:
[AppleScript] 纯文本查看 复制代码
public class MyJUnit {
public static void main(String[] args) throws IOException {
Calculator calculator = new Calculator();
Class clazz = calculator.getClass();
// 定义异常计数器
int count = 0;
// 创建流
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
// 获取所有public方法
Method[] methods = clazz.getMethods();
// 遍历
for (Method method : methods) {
try {
// 判断当前方法是否加了@Check注解
if (method.isAnnotationPresent(Check.class)) {
// 调用
method.invoke(calculator);
}
} catch (Exception e) {
// 异常信息计数器增加
count++;
// 获取异常信息
bw.write(method.getName()+ " 方法出异常了");
bw.newLine();
bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因:"+ e.getCause().getMessage());
bw.newLine();
bw.write("--------------------------");
bw.newLine();
}
}
// 写入汇总异常信息
bw.write("共发生了" + count + "个异常");
// 释放资源
bw.close();
}
}

六、 反射及注解常用API
获取一个类的字节码对象的3种方式:
1. Class.forName("全类名")
2. 类名.class .
3. 对象.getClass()
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 获取功能
1. 获取成员变量们
[AppleScript] 纯文本查看 复制代码
Field[] getFields(): 获取所有 public 的成员变量
Field getField(String name): 获取指定名称的 public 的成员变量
Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符
Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符

2. 获取构造方法们
[AppleScript] 纯文本查看 复制代码
Constructor<?>[] getConstructors(): 获取所有 public 的构造方法
Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 的构造方法
Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取指定的构造方法, 不

考虑权限修饰符
3. 获取成员方法们:
[AppleScript] 纯文本查看 复制代码
Method[] getMethods(): 获取所有 public 的成员方法
Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 的成员方法
Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符
Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不
考虑权限修饰符

4. 获取Class对象代表的类的全类名
[AppleScript] 纯文本查看 复制代码
String getName(): 获取当前Class对象代表的类的全类名
// 其他
T newInstance(): 使用当前类的空参构造创建一个对象
A getAnnotation(Class<A> annotationClass): 获取当前类的注解对象
ClassLoader getClassLoader(): 返回该类的类加载器
java.lang.reflect.Field: 表示一个成员变量

// 成员方法
[AppleScript] 纯文本查看 复制代码
void set(Object obj, Object value): 设置指定对象的成员变量的值
Object get(Object obj): 获取指定对象的成员变量的值
void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
java.lang.reflect.Constructor<T>: 表示一个构造方法
// 成员方法
T newInstance(Object... initargs): 使用当前构造方法传入参数, 创建对象
void setAccessible(boolean flag): 注意: 构造方法不能利用此方法忽略权限, 会抛异常
java.lang.reflect.Method类: 表示一个成员方法
// 成员方法
Object invoke(Object obj, Object... args): 使用指定对象和指定参数值调用此方法
String getName(): 获取方法名
void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
java.lang.ClassLoader: 类加载器
// 成员方法
InputStream getResourceAsStream(String name): 读取相对于bin目录中的文件, 返回一个字节流

常用元注解:
[AppleScript] 纯文本查看 复制代码
@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 个回复

倒序浏览
写的很好 不错 加油
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马