JDK8 新特性Lambda & Stream

Lambda 表达式

为什么会有Lambda ?

假如说现在我们有如下的代码

package com.demo.fjj;

public class LambdaDemo {
    public static void main(String[] args) {
        // 开启一个新的线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程中执行的代码" + Thread.currentThread().getName());
            }
        }).start();
        System.out.println("主线程中执行的代码" + Thread.currentThread().getName());
    }
}

1,Thread 类需要一个Runnable 接口作为参数,其中的抽象方法 run 方法是用来指定线程任务的内容的核心
2,为了指定run 方法,不得不需要Runnable 的实现类
3,为了省去定义一个Runnable 的实现类,不得不使用匿名内部类
4,必须覆盖重写的抽象的 run 方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错
5,实际上,我们一般只关注方法体的代码。

简单体验一下 lambda

   new Thread(() -> { System.out.println("我的lambda 写的" + Thread.currentThread().getName()); }).start();

这样我们一行代码就可以写完了!!其实也可以说是一个匿名函数 可以传递的代码

lambda 优点

1,lambda 表达式简化了匿名内部类的使用 语法更加简单。
2,匿名内部类语法冗余 体验了lambda 表达式后 发现lambda 表达式是简化匿名内部类的一种方式。

什么时候可以用这个lambda

我刚看了一下她实现的这个 Runnable 发现注解上有一句话

在这里插入图片描述
点击了一个那个注解看到上面的解释
在这里插入图片描述
也就是说当我们看到是函数式接口的时候是可以用lambda 来写的。只能是接口类型

lambda 的语法规则

省去了面向对象的条条框框 lambda 的标准格式化3个部分组成:

(参数类型 参数名称)-> {
代码体
}

lambda 练习一

编写一个接口

public interface UserService {
    // 写一个没有返回值和 参数的方法
    void show();
}

编写传统的调用 如果一个方法的参数是一个接口的话,我们在调用的时候只能用new 的形式 写一个内部类

package com.demo.demo;

public class UserServiceimpl {
    public static void main(String[] args) {
        // 调用 getShow 的方法
        getShow(new UserService() {
            @Override
            public void show() {
                System.out.println("普通的方法 只能重写 一个内部类 ");
            }
        });
    }
    public static void getShow(UserService userService) {
        userService.show();
    }
}

使用lambda 的方式

package com.demo.demo;

public class UserServiceimpl {
    public static void main(String[] args) {
        // 调用 getShow 的方法
        getShow(() -> {
            System.out.println("我是lambda 的写法!!!");
        });
    }

    public static void getShow(UserService userService) {
        userService.show();
    }
}

但是第一种方式的话如果我们的接口里有两个抽象的方法的话就会报错比如:
在这里插入图片描述
在这里插入图片描述
他说有两个抽象的方法我只写了一个的实现大概。。。
修改一下
在这里插入图片描述

lambda 练习二

先创建一个简单的实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person  {
    private String name;
    private Integer age;
}

假设现在有list 里面是数据需要排序 我们用平常的方法的话只能是写内部类的形式

public class Demo {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("冯娇娇",18));
        list.add(new Person("zyh",19));
        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        for (Person person : list) {
            System.out.println(person);
        }
    }
}

lambda 的 写法

public class Demo {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("冯娇娇", 18));
        list.add(new Person("zyh", 19));
        Collections.sort(list, (Person o1, Person o2) -> {
            return o1.getAge() - o2.getAge();
        });
        list.stream().forEach(person -> {
            System.out.println(person);
        });
    }
}

Lambda 表达式的省略写法

在lambda 表达式的标准写法基础上 可以使用省略写法规则
1,小括号内的参数类型可以省略
2,如果小括号 内有只有一个参数 小括号可以省略
3,如果大括号内有一个语句 可以同时省略大括号 return 关键字以及语句分号。

Lambda 表达式的使用前提

Lambda 表达式的语法是非常简洁的 但是不能随便的使用
1,方法的参数或者局部变量类型必须为接口才能使用Lambda
2,接口中有切有一个抽象方法 (@FunctionInterface)

lambda 跟匿名内部类的区别

联系:
lambda 表达式创建的对象与匿名内部类生成的对象一样 可以直接调用接口中承认的默认的方法。
区别:
1,匿名内部类可以为任意接口创建实例,不管接口中包含多少个抽象方法,只要在匿名内部类中实现所有抽象方法就可以。
但是在lambda 表达式中只能为函数式接口创建实例。
2,匿名内部类可以为抽象类甚至普通类创建实例 但Lambda 表达式只能为函数式接口创建实例
3,匿名内部类实现的抽象方法可以允许调用接口中定义默认方法 但是lambda 表达式的代码块不允许调用接口中定义的默认的方法。

接口中新增的方法

JDK 8 中接口的新增

在Jdk 8 中接口有增强 在Jdk 8 之前

interface 接口名 {
静态常量;
抽象方法
}

Jdk 8 之后对接口做了增加 接口中可以有默认方法和静态方法

interface 接口名 {
静态常量;
抽象方法;
默认方法;
静态方法
}

默认方法

在原来的接口中

package com.demo.inter;

public class Demo {
    // 定义一个接口
    interface A {
        // 一个接口的普通方法
        void test01 ();
        // test2
        void test2 ();
    }
    // 一个实现类

}
class C implements Demo.A {

    @Override
    public void test01() {

    }
}

在这里插入图片描述
这里会很不方便 如果这个接口有很多的实现类的话 我们在写一个方法的话就会很麻烦要所有的类都实现,所以在JDK 8 的时候有了一个默认的方法 加上关键字就可以!

package com.demo.inter;

public class Demo {
    // 定义一个接口
    interface A {
        // 一个接口的普通方法
        void test01 ();

        // test2
        default void test2() {
            System.out.println("我是默认的方法");
        }
    }
    // 一个实现类

}
class C implements Demo.A {

    @Override
    public void test01() {

    }
}

当然实现类也可以去重写默认的方法

package com.demo.inter;

public class Demo {
    public static void main(String[] args) {
        A c = new C();
        c.test2();
    }
    // 定义一个接口
    interface A {
        // 一个接口的普通方法
        void test01 ();

        // test2
        default void test2() {
            System.out.println("我是默认的方法");
        }
    }
    // 一个实现类

}
class C implements Demo.A {
    @Override
    public void test2() {
        System.out.println("我是重写的!!!");
    }

    @Override
    public void test01() {

    }
}

接口中默认方法的使用

1,实现类直接调用接口的默认方法
2,实现类重写接口的默认方法

静态方法

在这里插入图片描述
接口中的静态方法在实现类是不能被重写的,调用的话只能通过接口类型:接口名.静态方法名()

两者的区别

1,默认方法通过实例调用 静态方法通过接口名调用
2,默认方法可以被继承 实现类可以直接调用接口默认方法,也可以重写接口默认方法
3,静态方法不能被继承 实现类不能重写接口的静态方法 只能使用接口名调用

函数式接口

在JDK 中帮我们提供的有函数式接口 主要在java.util.function 包中

Supplier

对应的lambda 表达式需要提供一个返回数据的类型
在这里插入图片描述

// 无参有返回值得
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

使用;这样就没有必要自己去写一个匿名内部类了。这样就可以直接调用JDK 写好的函数式接口用lambda 来写了。

public class Demo2 {
    private static int arr[] = {1, 2, 3, 4};
    private static int sum = 0;

    public static void main(String[] args) {

        for (int i : arr) {
            sum += i;
        }
        fun1(() -> sum);
    }

    private static void fun1(Supplier<Integer> supplier) {
        Integer sum = supplier.get();// 他这个get ()方法是无参有返回值的抽象方法
        System.out.println("总和是" + sum);
    }
}

Consumer

// 有参无返回值的
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

简单使用

public class Demo3 {
    public static void main(String[] args) {
        test(msg-> {
            System.out.println("转换成小写"+msg.toLowerCase());
        });
    }
    private static void test(Consumer<String> t1) {
        t1.accept("Hello world!");
    }
}

点进回去发现Consumer 有一个默认的
在这里插入图片描述

默认的andThen如果一个方法的参数和返回值全部都是Consumer类型,那么就可以实现效果,消费一个数据的时候,首先做一个操作然后在做一个操作,实现组合 而这个方法就是Consumer 接口中默认的andThen方法
简单的使用

public class anthenDemo {
    public static void main(String[] args) {
        test1(msg->{
            System.out.println(msg.toLowerCase());
        },msg2->{
            System.out.println(msg2.toUpperCase());
        });
    }
    private static void  test1(Consumer<String> t1,Consumer<String> t2) {
        t1.andThen(t2).accept("fjj");
    }
}

Function

@FunctionalInterface
// 有参有返回值的 Function 接口是根据一个类型的数据得到另一个类型的数据 前者称之为前置条件 后者是后置条件 有参数有返回值。
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

简单使用


public class Demo6 {
    public static void main(String[] args) {
        test(t1->{
            return Integer.parseInt(t1);
        });
    }
    private static void test(Function<String,Integer> t) {
        Integer apply = t.apply("555");
        System.out.println(apply);
    }
}

同样的这个也有默认的方法
在这里插入图片描述
那个andThen 使用规则和那个是一样的
但是默认的compose 方法的作用顺序和andThen 方法刚好相反而静态方法identity 则是,输入什么参数就返回什么参数。

Predicate

// 有参返回值是 boolean 
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

简单的使用

public class Demo5 {
    public static void main(String[] args) {
        demo(ll->{
            return ll.length()>3;
        },"hahahha");
    }
    private static void demo(Predicate<String> p1,String msg) {
        boolean b = p1.test(msg);
        System.out.println("b = " + b);

    }
}

在Predicate 中的默认方法提供了逻辑关系操作and or negate isEquals 方法

方法引用

在下面的例子中我们求和的代码中出现了 冗余的情况 就是如果我们lambda 去求和 和我们单独的方法是冗余的 我们可以用方法引用 的形式直接调用方法中求和的方法
传统的写法
在这里插入图片描述
简化写法
在这里插入图片描述

public class Demo {
    public static void main(String[] args) {
        test(Demo::sum);
    }
    // 求和的方法
    private static void sum(int []a) {
        int sum = 0;
        for (int i : a) {
            sum+=i;
        }
        System.out.println(sum);
    }
    private static void test (Consumer<int[]> aa) {
        int [] a = {10,55,99,77,66,88};
        aa.accept(a);
    }
}

方法引用常用的方式

符号表示 ::
符号说明:双冒号为方法引用运算符 而它所在的表达式被称为方法引用
应用场景:如果lambda 表达式所要实现的方案 已经有其他方法存在相同的方案 那么则可以使用方法引用
常见的引用方式:

instanceName:: methodName 对象:: 方法名

这个是一个常见的一种用法 如果一个类中已经存在了一个成员的方法 就可以通过对象名引用成员方法

public class Demo {
    public static void main(String[] args) {
        Date date = new Date();
        Supplier<Long> supplier = ()->date.getTime();
        System.out.println("supplier.get() = " + supplier.get());
        // 通过方法引用的形式
        Supplier<Long> supplier1 = date::getTime;
        System.out.println(supplier1.get());
    }
}

类名::静态方法名

public class Demo {
    public static void main(String[] args) {
        Supplier<Long> supplier = () -> {
            return System.currentTimeMillis();
        };
        System.out.println("supplier.get() = " + supplier.get());
        // 通过方法引用的形式
        Supplier<Long> supplier1 = System::currentTimeMillis;
        System.out.println(supplier1.get());
    }
}

类名:: 引用实例方法

public class Demo {
    public static void main(String[] args) {
        Function<String,Integer> function = (s) ->{
            return s.length();
        };
        System.out.println(function.apply("hello"));
        // 通过方法引用来实现
        Function<String,Integer> function1 = String::length;
        System.out.println(function1.apply("sss"));
        BiFunction<String,Integer,String> function2 = String::substring;
        String hello = function2.apply("Hello", 3);
        System.out.println("hello = " + hello);
    }
}

类名:: 构造器

由于构造器的名称和类名完全一致 所以构造器引用使用::new 的格式使用

数组:: 构造器

public class Demo {
    public static void main(String[] args) {
        Function<Integer,String[]> function =(len) ->{
            return new String[len];
        };
        String[] a1 = function.apply(2);
        System.out.println(a1.length);
        // 方法引用的写法
        Function<Integer,String[]> function1 = String[]::new;
        String[] apply = function1.apply(2);
        System.out.println("apply = " + apply.length);

    }
}

小结:方法引用是对Lambda 表达式符合特定情况下的一种缩写对策方式,是的lambda表达式更加精简 也可以理解为lambda表达式缩写形式 不过只能引用已经存在的方法。就是为了解决冗余的行为一个。

Stream ApI

为什么要有Stream?

假设我们现在有一个集合,我们需要找到里面姓冯而且名字长度是3 的并且输出出来,传统的做法如下

public class Demo {
    public static void main(String[] args) {
        // 创建一个数组
        List<String> list = Arrays.asList("冯娇娇", "娇Amy", "KIKO");
        // 拿到姓冯的 和带娇 的
        ArrayList<String> list1 = new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("冯") && s.length()==3) {
                list1.add(s);
            }
        }
        for (String s : list1) {
            System.out.println("s = " + s);
        }
    }
}

但是要是用Stream 流的话就比较简单了。只需要一句代码就可以解决这个问题

    public static void main(String[] args) {
        // 创建一个数组
        List<String> list = Arrays.asList("冯娇娇", "娇Amy", "KIKO");
        // 拿到姓冯的 和带娇 的
        list.stream().filter(s -> s.startsWith("冯")).filter(s -> s.length()==3).forEach(System.out::println);
    }

Steam 流式思想概述

Stream 和 Io 流 没有任何的关系 Stream 流式思想类似于工厂车间的“生产流水线” Stream 流不是一种数据结构 不保存数据 而是对数据进行加工处理 stream 可以看做流水线上的一个工序 在流水线上 通过多个工序让一个原材料加工成一个商品。
Stream Api 能让我们快速完成许多复杂的操作 如筛选 切片 映射 查找 去除重复 统计 匹配 和归约

Steam 流的获取方式

根据Collection 获取

首先 java.util.Colletion 接口中加入了 default 方法 stream 也就是说Collection 接口下的所有实现都可以通过 steam 方法来获取 Stream

但是Map 接口没有实现 我们可以通过 key 或者value 或者转换成entyset

通过stream 的of 方法

我们在实际操作中遇到的数组中的数据 由于数组对象不可能添加默认的方法 所有stream 接口中提供了静态的方法 of

Stream 常用方法介绍

Stream流模型的操作很丰富 这里介绍一些常用的Api 这些方法可以被分为两种:

在这里插入图片描述

终结方法:
返回值类型不再是stream 类型的方法 不再支持链式调用 终结方法包括count 和 foreach 方法
非终结方法
返回值类型仍然是stream 类型的方法 支持链式调用 (除了终结方法之外 其余方法均为非终结方法)

stream注意事项
1,stream 只能操作一次
2,stream 方法返回的是新的流
3,stream 不调用终结方法 中间的操作就不会执行。

forEach

作用:
用来遍历流里面的数据
在这里插入图片描述
使用

    public static void main(String[] args) {
        // 创建一个数组
        List<String> list = Arrays.asList("冯娇娇", "娇Amy", "KIKO");
        // 拿到姓冯的 和带娇 的
        list.stream().filter(s -> s.startsWith("冯")).filter(s -> s.length()==3).forEach(System.out::println);
    }

Count

就是统计一下Stream 流中元素的个数 比较简单

        // 创建一个数组
        List<String> list = Arrays.asList("冯娇娇", "娇Amy", "KIKO");
        System.out.println(list.stream().count());

filter

filter 方法的作用是用来过滤数据的 返回符合条件的数据
在这里插入图片描述
使用

package com.demo.Stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        // 创建一个数组
        List<String> list = Arrays.asList("冯娇娇", "娇Amy", "KIKO");
        // 拿到姓冯的 和带娇 的
        list.stream().filter(s -> s.startsWith("冯")).filter(s -> s.length()==3).forEach(System.out::println);
    }
}

limit

就是截取前几个数据
在这里插入图片描述
使用

        // 创建一个数组
        List<String> list = Arrays.asList("冯娇娇", "娇Amy", "KIKO");
        // 拿到姓冯的 和带娇 的
        list.stream().limit(2).forEach(System.out::println);

Skip

使用之后截取之后的数据跳过之前的数据

    // 创建一个数组
        List<String> list = Arrays.asList("冯娇娇", "娇Amy", "KIKO");
        // 拿到姓冯的 和带娇 的
        list.stream().skip(2).forEach(System.out::println);

map

如果我们需要将流中的元素映射到另一个流中 可以使用Map 方法
在这里插入图片描述
该接口需要一个Function 函数式接口参数 可以将当前流
使用

    public static void main(String[] args) {
        // 创建一个数组
        List<String> list = Arrays.asList("1", "2", "3");

        list.stream().map(Integer::parseInt).forEach(System.out::println);
    }

Sorted 的使用

这个就是根据数据自然排序
在这里插入图片描述

    public static void main(String[] args) {
        // 创建一个数组
        List<String> list = Arrays.asList("1", "22", "3");

        list.stream().map(Integer::parseInt).sorted().forEach(System.out::println);
    }

distinct

去除重复的一个
在这里插入图片描述

    public static void main(String[] args) {
    // 可以有效的去除我们集合中重复的元素
        // 创建一个数组
        List<String> list = Arrays.asList("1", "22", "66","3","3");

        list.stream().map(Integer::parseInt).distinct().forEach(System.out::println);
    }

Stream 流中的distinct 方法对于基本数据类型是可以直接去除重复的 但是对于自定义类型 我们需要重写hashCode 和 equals 方法来移除掉重复元素的。

match

这个一共有三个
在这里插入图片描述
使用

public class Demo {
    public static void main(String[] args) {
        // 创建一个数组
        List<String> list = Arrays.asList("1", "22", "66", "3", "3");

        boolean b = list.stream().map(Integer::parseInt)
//                .allMatch(s -> s > 0);
//                .anyMatch(s-> s>0);
                  .noneMatch(s -> s < 0);
        System.out.println("b = " + b);
    }
}

find

如果我们需要找到某些数据 可以使用find 方法来实现


public class Demo {
    public static void main(String[] args) {
        // 创建一个数组
        List<String> list = Arrays.asList("1", "22", "66", "3", "3");

        Optional<Integer> first = list.stream().map(Integer::parseInt)
                .findFirst();
        System.out.println("first = " + first.get());
        Optional<Integer> first1 = list.stream().map(Integer::parseInt)
                .findAny();
        System.out.println("first1 = " + first1.get());
    }
}

在这里插入图片描述

max min

找到最大的跟最小的
在这里插入图片描述

        // 创建一个数组
        List<Integer> list = Arrays.asList(1, 22, 66, 3,3);

        System.out.println("list.stream().max((s1,s2) ->s1-s2) = " + list.stream().max((s1, s2) -> s1 - s2));
        System.out.println("list.stream().max((s1,s2) ->s1-s2) = " + list.stream().min((s1, s2) -> s1 - s2));
    }

reduce 方法

将所有的数据归纳成一个数据
在这里插入图片描述

    public static void main(String[] args) {
        // 创建一个数组
        List<Integer> list = Arrays.asList(1, 22, 66, 3,3);
        list.stream().reduce(0,(x,y) ->{
            System.out.println("x = " + x);
            System.out.println("y = " + y);
           return x+y;
        });
    }

在这里插入图片描述

mapTolnt

可以把String 类型转换成Intger
使用

    public static void main(String[] args) {
        // Integer 占用的内存比int 多很多 在Stream 流操作中 会自动 装箱和拆箱
        Integer arr[] ={1,5,9,6};
        Stream.of(arr).filter(i->i>0).forEach(System.out::println);
        System.out.println("我是分割符号!!!!!!!!!!!!!!!!!!!!!");
        // 为了 提高代码的效率 可以先将流中Intget 数据装换为 int 数据 然后在操作
        IntStream intStream = Stream.of(arr).mapToInt(Integer::intValue);
        intStream.filter(i->i>0).forEach(System.out::println);
    }
}

concat

如果有两个流 希望合并成为一个流 那么可以使用Stream 接口的静态方法
使用

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("10", "22");
        Stream<String> stream1 = Stream.of("10", "22");
        Stream.concat(stream,stream1).forEach(System.out::println);

    }

Stream 综合案例

定义两个集合 然后在集合中存储多个用户名称 然后完成如下的操作
1,第一个队伍只保留姓名长度为3 的成员
2,第二个队伍筛选之后只要前3个人
3,第二个队伍只要姓张的成员
4,第二个队伍筛选之后不要前两个人
5,将两个队伍合并成一个队伍
5,根据姓名创建Person 对象
6,打印整个队伍的Person 信息

    public static void main(String[] args) {
//        定义两个集合 然后在集合中存储多个用户名称 然后完成如下的操作
      List<String> list = new ArrayList<>();
        list.add("冯娇娇");
        list.add("冯娇娇1");
        list.add("冯1");
        list.add("冯2");
        // -----------------------------
        List<String> list1 = new ArrayList<>();
        list1.add("冯娇娇3333");
        list1.add("张娇娇1");
        list1.add("冯3");
        list1.add("冯4");
//        1,第一个队伍只保留姓名长度为3 的成员
        System.out.println("1,第一个队伍只保留姓名长度为3 的成员");
        list.stream().filter(s-> s.length()>3).forEach(System.out::println);
//        2,第个队伍筛选之后只要前3个人
        System.out.println(" 2,第二个队伍筛选之后只要前3个人");
        list1.stream().limit(3).forEach(System.out::println);
//        3,第二个队伍只要姓张的成员//        4,第二个队伍筛选之后不要前两个人
        System.out.println(" 3,第二个队伍只要姓张的成员  4,第二个队伍筛选之后不要前两个人");
        list1.stream().filter(s -> s.startsWith("张")).skip(2).forEach(System.out::println);

//        5,将两个队伍合并成一个队伍 //        5,根据姓名创建Person 对象 //        6,打印整个队伍的Person 信息
        System.out.println(" 5,将两个队伍合并成一个队伍");
        Stream.concat(list,list1).map(Person::new).forEach(System.out::println);



    }

Stream 结果收集

结果收集到集合中

    public static void main(String[] args) {
        // 转换成 list 集合
        System.out.println(Stream.of("aa", "bb", "cc").collect(Collectors.toList()));
        // 转换成 Set 集合
        System.out.println(Stream.of("bb", "gg", "oo").collect(Collectors.toSet()));
        // 转换成 ArrayList 或者 HashSet
        Stream.of("aa","bb","cc").collect(Collectors.toCollection(ArrayList::new));
        Stream.of("aa","bb","cc").collect(Collectors.toCollection(HashSet::new));
    }

将结果收集到数组里面

    public static void main(String[] args) {
        Object[] objects = Stream.of("aa", "bb", "ddd").toArray();
        System.out.println(Arrays.toString(objects));
        // 需要指定返回数组中的元素类型
        System.out.println(Stream.of("aa", "bb").toArray(String[]::new));
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值