1. Stream流
在Java 8中,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
java.util.stream.Stream< T>是Java 8新加入的常用的流接口(这并不是一个函数式接口)。
Stream流属于管道流,只能被消费(使用)一次。
1.1 获取Stream流
获取一个流的常用方法:
- 所以的Collection集合都可以通过stream的默认方法获取流;
default Stream< E> stream () - Stream接口的静态方法of可以获取数组对应的流。
static < T> Stream< T> of (T… values>,参数是一个可变参数
public class DemoGetStream {
public static void main(String[] args) {
//把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream1 = set.stream();
Map<String, String> map = new HashMap<>();
//获取键,存储到一个Set集合中
Set<String> keySet = map.keySet();
Stream<String> stream2 = keySet.stream();
//获取值,存储到一个Collection集合中
Collection<String> collection = map.values();
Stream<String> stream3 = collection.stream();
//获取键值对
Set<Map.Entry<String, String>> entries = new HashSet<>();
Stream<Map.Entry<String, String>> stream4 = entries.stream();
//把数组转换为Stream流
Stream<Integer> stream5 = Stream.of(1, 2, 3);
//传递数组
Integer[] in = {1, 2, 3};
Stream<Integer> stream6 = Stream.of(in);
}
}
1.2 常用方法
流模型的操作很丰富,这些方法可以被分为两种:
- 延迟方法: 返回值类型仍然是Stream接口自身类型的方法,因而支持链式调用。
- 终结方法: 返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。
1. 逐一处理:forEach
void forEach(Consumer<? super T> action): 该方法接收一个Consumer函数式接口函数,会将每一个流元素交给函数进行处理。
public class Stream_getEach {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c");
//使用forEach对数据进行遍历
stream.forEach((String s) -> {
System.out.println(s);
});
}
}
2. 过滤:filter
Stream< T> filter(Predicate<? super T> predicate): 该接口接收一个Predicate函数式接口参数作为筛选条件。
public class Stream_filter {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c", "abc");
//对stream流中的元素进行过滤,只要a开头的元素
Stream<String> stream1 = stream.filter((String s) -> {
return s.startsWith("a");
});
stream1.forEach(s -> System.out.println(s));
}
}
3. 映射:map
< R> Stream< R> map(Function<? super T, ? extends R> mapper): 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
public class Stream_map {
public static void main(String[] args) {
Stream<String> stream = Stream.of("1", "2", "3");
Stream<Integer> stream1 = stream.map((String s) -> {
return Integer.parseInt(s);
});
stream1.forEach(i -> System.out.println(i));
}
}
4. 统计个数:count
**long count():**用于统计Stream流中元素的个数,该方法是个终结方法,返回值是一个long类型的整数,所以不能再继续调用Stream流中的其他方法了。
public class Stream_count {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3);
long count = stream.count();
System.out.println(count);
}
}
5. 截取前n个元素:limit
Stream< T> limit(long maxSize): 如果集合当前长度大于参数则进行截取,否则不进行操作。该方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流。
public class Stream_limit {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
Stream<Integer> stream1 = stream.limit(2);
stream1.forEach(i -> System.out.println(i));
}
}
6. 跳过前n个元素:skip
Stream< T> skip(long n): 如果流的当前长度大于n,则跳过前n个元素,否则会得到一个长度为0的空流。
public class Stream_skip {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
Stream<Integer> stream1 = stream.skip(2);
stream1.forEach(i -> System.out.println(i));
}
}
7. 组合:concat
static < T> Stream< T> concat(String<? extends T> a, Stream<? extends T> b): 该方法可以将两个流合并成为一个流。
public class Stream_concat {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("你");
Stream<String> stream2 = Stream.of("好");
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(s -> System.out.print(s));
}
}
2. 方法引用
方法引用是java8的新特性之一, 可以直接引用已有Java类或对象的方法或构造器。方法引用与lambda表达式结合使用,可以进一步简化代码。
方法引用符:
双冒号::为引用运算符,它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么就可以通过::来引用该方法作为Lambda的替代者。
注:Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常。
2.1 通过对象名引用成员方法
使用前提: 对象名已经存在,成员方法也已经存在,就可以使用对象名引用成员方法。
练习: 将传入的字符串大写输出
public class MethodObject {
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
//定义一个打印的函数式接口
@FunctionalInterface
public interface Printable {
void print(String s);
}
//通过对象名引用成员方法
public class Demo01ObjectMethodReference {
public static void printString(Printable p){
p.print("Hello");
}
public static void main(String[] args) {
printString((s) -> {
//创建MethodObject对象
MethodObject mo = new MethodObject();
//调用printUpperCaseString,把字符串大写输出
mo.printUpperCaseString(s);
});
//使用方法引用优化Lambda
MethodObject mo = new MethodObject();
printString(mo::printUpperCaseString);
}
}
2.2 通过类名引用静态成员方法
使用前提: 类已经存在,静态成员方法也已经存在。
练习: 计算整数的绝对值
@FunctionalInterface
public interface Calcable {
int calsAbs(int num);
}
public class DemoStaticMethodReference {
public static int method(int num, Calcable c) {
return c.calsAbs(num);
}
public static void main(String[] args) {
int num = method(-10, (n) -> {
return Math.abs(n);
});
System.out.println(num);
//使用方法引用优化
int i = method(-10, Math::abs);
}
}
2.3 通过super引用父类的成员方法
格式:super::成员方法
@FunctionalInterface
public interface Greetable {
void greet();
}
//父类
public class Human {
public void sayHello(){
System.out.println("Hello, i am human");
}
}
public class Man extends Human {
//子类重写父类sayHello方法
@Override
public void sayHello(){
System.out.println("Hello, i am man");
}
//定义一个参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(() -> {
Human h = new Human();
h.sayHello();
});
//因为存在子父类关系,所以我们可以直接通过super调用父类的成员方法
method(() -> {
super.sayHello();
});
//使用方法引用优化
method(super::sayHello);
}
public static void main(String[] args) {
new Man().show();
}
}
2.4 通过this引用本类的成员方法
格式:this::成员方法
@FunctionalInterface
public interface Food {
void food();
}
public class Eat {
public void eatFood(){
System.out.println("真好吃");
}
public void madeFood(Food f){
f.food();
}
public void sayFood(){
madeFood(()->{
this.eatFood();
});
//使用方法引用优化
madeFood(this::eatFood);
}
public static void main(String[] args) {
new Eat().sayFood();
}
}
2.5 类的构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示。
@FunctionalInterface
public interface PersonBuilder {
//根据传递的名字,构造Person对象返回
Person builder(String name);
}
public class DemoPersonBuilder {
public static void printName(String name, PersonBuilder pb){
System.out.println(pb.builder(name).getName());
}
public static void main(String[] args) {
printName("张三", (name) -> {
return new Person(name);
});
//使用方法引用优化
printName("张三", Person::new);
}
}
2.6 数组的构造器引用
数组也是Object的子类对象,所以同样具有构造器,只是语法稍有不同。
格式:数据类型[]::new
@FunctionalInterface
public interface ArrayBuilder {
int[] builder(int len);
}
public class DemoArrayBuilder {
public static int[] createArray(int len, ArrayBuilder ab){
return ab.builder(len);
}
public static void main(String[] args) {
int[] array = createArray(10, (len) -> {
return new int[len];
});
//优化
int[] array1 = createArray(10, int[]::new);
}
}
3. Junit单元测试
测试分类:
- 黑盒测试:在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测试,只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息。
- 白盒测试:通过检查软件内部的逻辑结构,对软件中的逻辑路径进行覆盖测试。在程序不同地方设立检查点,检查程序的状态,以确定实际运行状态与预期状态是否一致。
Junit使用:
- 定义一个测试类(测试用例)
- 定义测试方法:可以独立运行
- 给方法加@Test
- 导入junit依赖环境
JUnit 断言
Junit所有的断言都包含在 Assert 类中。这个类提供了很多有用的断言方法来编写测试用例。只有失败的断言才会被记录。Assert 类中的一些有用的方法列式如下:
- assertEquals(expected, actual):检查两个变量或者等式是否平衡
- assertTrue(condition):检查条件为真
- assertFalse(condition):检查条件为假
- assertNotNull(object):检查对象不为空
- assertNull(object):检查对象为空
- assertSame(A, B):检查两个相关对象是否指向同一个对象
- assertNotSame(A, B):检查两个相关对象是否不指向同一个对象
- assertArrayEquals(expectedArray, resultArray):方法检查两个数组是否相等
JUnit 注解
- @Test:这个注释说明依附在 JUnit 的 public void 方法可以作为一个测试案例。
- @Before:有些测试在运行前需要创造几个相似的对象。在 public void 方法加该注释是因为该方法需要在 test 方法前运行。
- @After:如果你将外部资源在 Before 方法中分配,那么你需要在测试运行后释放他们。在 public void 方法加该注释是因为该方法需要在 test 方法后运行。
- @BeforeClass:在 public void 方法加该注释是因为该方法需要在类中所有方法前运行。
- @AfterClass:它将会使方法在所有测试结束后执行。这个可以用来进行清理活动。
- @Ignore:这个注释是用来忽略有关不需要执行的测试的。
4. 反射
概述: Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
反射就是把java类中的各种成分映射成一个个的Java对象
反射是框架的灵魂,它可以有效地降低类之间的耦合,很多框架都运用了反射原理。
- 通过new关键字创建对象操作对象,在编译时就已经确定;
- 通过反射可以在程序运行过程中动态的操作对象,可以获得编译期无法获得的信息,动态操作最大限度发挥了java扩展性。
4.1 获取字节码Class对象的三种方法
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
获取Class对象的方法:
- Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象;
多用于配置文件,将类名定义在配置文件中。读取文件,加载类 - 类名.class:通过类名的属性class获取;
多用于参数的传递 - 对象.getClass():getClass()方法在Object类中定义着。
多用于对象获取字节码的方式
public class DemoReflect {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class.forName("全类名")
Class<?> cla1 = Class.forName("holiday3.Person");
//2. 类名.class
Class cla2 = Person.class;
//3. 对象.getClass()
Person p = new Person();
Class cla3 = p.getClass();
System.out.println(cla1 == cla2); //true
System.out.println(cla1 == cla3); //true
//同一个字节码文件在一次程序运行过程中,只会加载一次,无论通过哪种方式获取的Class对象都是同一个
}
}
4.2 Class对象功能
获取功能:
-
获取成员变量们
-
获取构造方法们
-
获取成员方法们
-
获取类名
- String getName()
1. 获取Field
获取成员变量们:
- Field[] getFields():获取所有public修饰(公共)的成员变量;
- Field getField(String name):获取指定名称的public修饰的成员变量;
- Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符;
- Field getDeclaredFields(String name):获取指定名称的成员变量。
获取成员变量后的操作:
- 设置值:void set(Object obj, Object value)
- 获取值:get(Object obj)
- 忽略访问权限修饰符的安全检查:setAccessible(true)
public class Demo01Reflect {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//获取Person的Class对象
Class<Person> cla = Person.class;
//1. Field[] getFields()
Field[] fields = cla.getFields();
for (Field field: fields) {
System.out.println(field);
}
//2. Field getField(String name)
Field a = cla.getField("a");
//获取成员变量a的值
Person p = new Person();
Object value1 = a.get(p);
System.out.println(value1);
//设置a的值
a.set(p, "张三");
System.out.println(p);
//3. Field[] getDeclaredFields()
Field[] declaredFields = cla.getDeclaredFields();
for (Field declaredField: declaredFields) {
System.out.println(declaredField);
}
//4. Field getDeclaredFields(String name)
Field d = cla.getDeclaredField("d");
//访问私有的成员变量前,需要忽略访问权限修饰符的安全检查
d.setAccessible(true); //暴力反射
Object value2 = d.get(p);
System.out.println(value2);
}
}
2. 获取Constructor
获取构造方法们:
- Constructor<?>[] getConstructors():获取所有的公共(public修饰)构造方法。
- Constructor< T> getConstructors(类<?>… parameterTypes):获取指定的公共构造方法。
- Constructor< T> getDeclaredConstructors(类<?>… parameterTypes):获取所有的构造方法。
- Constructor<?>[] getDeclaredConstructors():获取指定的构造方法,不管修饰符。
创建对象: T newInstance(Object… initargs)
如果使用空参数构造方法创建对象,操作可以简化成使用Class对象的newInstance方法
public class Demo2Reflect {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Person> cla = Person.class;
//Constructor<T> getConstructors(类<?>... parameterTypes)
Constructor<Person> constructor = cla.getConstructor(String.class, int.class);
System.out.println(constructor);
//创建对象
Object p = constructor.newInstance("猪猪侠", 15);
System.out.println(p);
//使用空参数构造方法创建对象
Constructor<Person> constructor1 = cla.getConstructor();
Object o1 = constructor1.newInstance();
//简化
Object o2 = cla.newInstance();
}
}
3. 获取Method
获取成员方法们
- Method[] getMethods()
- Method getMethods(String name, 类<?>… parameterTypes)
- Method[] getDeclaredMethods()
- Method getDeclaredMethods(String name, 类<?>… parameterTypes)
执行方法: Object invoke(Object obj, Object… args)
获取方法的名称: String getName
public class Demo3Reflect {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class cla = Person.class;
//获取指定的方法
Method m1 = cla.getMethod("eat");
Person p = new Person();
//执行方法
m1.invoke(p);
Method m2 = cla.getMethod("eat", String.class);
m2.invoke(p, "饭");
//获取所有public修饰的方法
Method[] methods = cla.getMethods();
//不仅仅是“能看到的”,还有一些Object类中的方法
for(Method method: methods){
System.out.println(method);
System.out.println(method.getName());
}
}
}
5. 注解
从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。
Annotation提供了一种为程序元素(包、类、构造器、方法、成员变量、参数、局域变量)设置元数据的方法。Annotation不能运行,它只有成员变量,没有方法。
作用:
- 编写文档:通过代码里标识的注释生成文档(doc文档)
- 代码分析:通过代码里标识的注解对代码进行分析(使用反射)
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查(Override)
JDK中预定义的一些注解:
- @Override:检测被该注解标注的方法是否继承自父类(接口)的;
- @Deprecated:表示被注解标注的内容已过时;
- @SuppressWarnings:压制警告。需要传参,一般传递all。
5.1 自定义注解
格式:(元注解)
public @interface 注解名称{
属性列表;
}
本质: 注解本质上就是一个接口,该接口默认继承Annotation接口
属性: 接口中可以定义的成员方法
要求:
1. 属性的返回值类型:基本数据类型、String、枚举、注解、以上类型的数组。
2. 定义了属性,在使用时需要赋值;
如果定义属性时使用default关键字给属性默认初始化值,那么使用注解时,可以不进行属性的赋值;
如果只有一个属性需要赋值,并且属性的名称是value,那么value可以省略,直接定义值即可;
数组赋值时,值使用{}包裹,如果只有一个值,{}可以省略。
元注解: 用于描述注解的注解
- @Target:描述注解能够作用的位置;
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
- @Retention:描述注解被保留的时间范围;
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}
- @Documented:描述注解是否被抽取到api文档中;
- @Inherited:描述注解是否被子类继承。
在程序中使用注解 获取注解中定义的属性值
//描述需要执行的类名和方法名
@Target(ElementType.TYPE) //表示该MyAnno注解只能作用于类上
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
String className();
String methodName();
}
@MyAnno(className = "holiday3.DemoShow1", methodName = "holiday3.DemoShow2")
public class ReflectTest {
public static void main(String[] args) {
//1. 解析注解,获取该类的字节码文件对象
Class<ReflectTest> reflectTestClass = ReflectTest.class;
//2. 获取上面的解析注解(其实就是在内存中生成了一个该注解接口的子类实现对象)
MyAnno annotation = reflectTestClass.getAnnotation(MyAnno.class);
//3. 调用注解对象中定义的抽象方法,获取返回值
String className = annotation.className();
String methodName = annotation.methodName();
System.out.println(className);
System.out.println(methodName);
}
}