笔记3

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 常用方法

流模型的操作很丰富,这些方法可以被分为两种:

  1. 延迟方法: 返回值类型仍然是Stream接口自身类型的方法,因而支持链式调用。
  2. 终结方法: 返回值类型不再是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使用:

  1. 定义一个测试类(测试用例)
  2. 定义测试方法:可以独立运行
  3. 给方法加@Test
  4. 导入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对象的方法:

  1. Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象;
    多用于配置文件,将类名定义在配置文件中。读取文件,加载类
  2. 类名.class:通过类名的属性class获取;
    多用于参数的传递
  3. 对象.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对象功能

获取功能:

  1. 获取成员变量们

  2. 获取构造方法们

  3. 获取成员方法们

  4. 获取类名

  • String getName()
1. 获取Field

获取成员变量们:

  • Field[] getFields():获取所有public修饰(公共)的成员变量;
  • Field getField(String name):获取指定名称的public修饰的成员变量;
  • Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符;
  • Field getDeclaredFields(String name):获取指定名称的成员变量。

获取成员变量后的操作:

  1. 设置值:void set(Object obj, Object value)
  2. 获取值:get(Object obj)
  3. 忽略访问权限修饰符的安全检查: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不能运行,它只有成员变量,没有方法。

作用:

  1. 编写文档:通过代码里标识的注释生成文档(doc文档)
  2. 代码分析:通过代码里标识的注解对代码进行分析(使用反射)
  3. 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查(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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值