JDK8新特性学习总结一

public class DemoLambdaIntro {
    public static void main(String[] args) {
        //匿名内部类
        //1.定义了一个没有名字的类
        //2.这个类实现了runnable接口
        //3.创建了这个类的对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行");
            }
        }).start();
    }
}

匿名内部类的语法是很冗余的,其实我们关注的是run方法和里面要执行的代码,lambda表达式体现的是函数式变成思想,只需要将要执行的代码放到函数中(函数就是类中的方法),lambda就是一个匿名函数,我们只需要将要执行的代码放到lambda表达式中即可

public class DemoLambdaIntro {
    public static void main(String[] args) {
        //匿名内部类
        //1.定义了一个没有名字的类
        //2.这个类实现了runnable接口
        //3.创建了这个类的对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行");
            }
        }).start();

        //体验lambda表达式
        new Thread(()->{
            System.out.println("lambda表达式执行啦");
        }).start();
    }
}

lambda表达式的好处:可以简化匿名内部类,让代码更加精简

 

练习使用lambda表达式

public class DemoLambdaIntro {
    /*
        的标准格式:
            lambda表达式是一个匿名函数,而函数相当于JAVA中的方法
        (参数列表) -> {
        }
        (参数列表):参数列表
        {}方法体
        ->没有实际含义,起到连接的作用

        public static void main(String[] args) {

        }
    */
    public static void main(String[] args) {
        goSwimming(new Swimmable() {
            @Override
            public void swimming() {
                System.out.println("我是匿名内部类的游泳");
            }
        });

        //小结:以后我们看到方法的参数是接口就可以考虑使用lambda表达式
        //我们可以这么认为,Lambda表达式就是对接口中的抽象方法的重写
        goSwimming(() ->{
            System.out.println("我是lambda的游泳");
        });
        System.out.println("------------------");

        goSiking(new Smokeable() {
            @Override
            public int smoking(String name) {
                System.out.println("匿名内部类抽了"+ name +"的烟");
                return 5;
            }
        });
        goSiking((String name)->{
            System.out.println("lambda抽了"+ name +"的烟");
            return 6;
        });


    }
    //练习有参数有返回值的lambda
    private static void  goSiking(Smokeable s){
        int i = s.smoking("中华");
        System.out.println("返回值:"+i);
    }

    //练习无参数无返回值的lambda

    public static void goSwimming(Swimmable s){
        s.swimming();
    }
}

 

public interface Smokeable {
    public abstract  int smoking(String name);

}
public interface Swimmable {
    public abstract  void swimming();
}

增强练习案例  

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.stream.Collectors;

public class DemoLambdaIntro {
    public static void main(String[] args) {
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("刘德华",58,174));
        persons.add(new Person("张学友",58,176));
        persons.add(new Person("刘德华",54,171));
        persons.add(new Person("黎明",53,178));

       /* Collections.sort(persons, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o2.getAge()-o1.getAge();
            }
        });*/

       Collections.sort(persons,(Person o1,Person o2) ->{
           return o2.getAge()-o1.getAge();
       });

        for (Person person : persons) {
            System.out.println(person);
        }

    }
}
public class Person {
    String name;
    int age;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }

    public Person(String name, int age, int height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    int height;


}

小结:首先学习了lambda表达式的标准格式

(参数列表) -> {
        }

 

以后我们看到调用的方法他的参数是接口就可以考虑使用lambda表达式来替代匿名内部类,不是所有的匿名内部类都能使用lambda来替换,lambda表示式相当于接口的抽象方法重写

 小结
匿名内部类在编译的时候会一个class文件
Lambda在程序运行的时候形成一个类
1.在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
2.还会形成一个匿名内部类,实现接口,,重写抽象方法
3.在接口的重写方法中会调用新生成的方法

 

Lambda省略格式


目标
掌握Lambda省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
1.小括号内参数的类型可以省略
2.如果小括号内有且仅有一个参数,则小括号可以省略
3.如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号(同时省略)

(int a) -> {
return new Person( ) ;
}
a -> new Person()
import java.util.ArrayList;
import java.util.Collections;

public class DemoLambdaIntro {
    public static void main(String[] args) {
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("刘德华",58,174));
        persons.add(new Person("张学友",58,176));
        persons.add(new Person("刘德华",54,171));
        persons.add(new Person("黎明",53,178));

       Collections.sort(persons,(o1,o2) ->o2.getAge()-o1.getAge());

        persons.forEach(t -> System.out.println(t));


    }
}


 

public class DemoLambdaIntro {
    public static void main(String[] args) {
        //方法的参数或局部变量类型必须为接口才能使用Lambda
        test(() -> {
        });
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System. out. println("aa");
            }
        };

        Flyable f =() -> {
            System.out.println("我会飞啦");
        };


    }
    public static void test (Flyable a) {

    }

    //只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda
    @FunctionalInterface //检测这个接口是不是只有一个抽象方法
    interface Flyable {
        //接口中有且仅有一个抽象方法
        public abstract void eat() ;
//        public abstract void eat2() ;
    }
}

小结:

Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
1.方法的参数或局部变量类型必须为接口才能使用Lambda
2.接口中有且仅有一个抽象方法
 

 

Lambda和匿名内部类对比


目标
了解Lambda和匿名内部类在使用上的区别
1.所需的类型不一样
匿名内部类需要的类型可以是类、抽象类、接口
Lambda表达式需要的类型必须是接口
2.抽象方法的数是不一样
匿名内部类所需的接口中抽象方法的数量随意
Lambda表达式所需的接口只能有一个抽象方法
3.实现原理不同
置名内部类是在编译后会形成class
Lambda表达式是在程序运行的时候动态生成class


小结
当接口中只有一个抽象方法时,建议使用Lambda表达式其他其他情况还是需要使用匿名内部类


JDK 8接口增强介绍


JDK 8以前的接口:

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


JDK 8对接口的增强,接口还可以有默认方法和静态方法
JDK 8的接口:

interface 接口名{
    静态常量;
    抽象方法;
    默认方法:
    静态方法;
}
interface A {
    public abstract void test01() ;
    //此时如果需要在A接口新增一个方法,所有继承这些接口的实现类都要重新去实现它,非常麻烦
//    public abstract void test02() ;
}
class B implements A {
    @Override
    public void test01() {
        System.out.println("B test01");
    }
}
class C implements A {
    @Override
    public void test01() {
        System.out.println("C test01");
    }
}


接口中的默认方法实现类不必重写,可以直接使用,实现类也可以根据需要重写。这样就方便接口的扩展。
 

接口默认方法的定义格式

interface 接口名{
    修饰符 default 返回值类型 方法名() {
        代码;
    }
}

接口默认方法的使用
方式一:实现类直接调用接口默认方法
方式二:实现类重写接口默认方法
 

public class DemoLambdaIntro {
    public static void main(String[] args) {
        BB bb = new BB() ;
        bb. test01() ;
        CC cc = new CC() ;
        cc. test01() ;
    }

}

interface AA {

    public default void test01() {
        System.out.println("我是接口AA默认方法");
    }
}
//默认方法使用方式一:实现类可以直接使用
class BB implements AA{

}
//默认方法使用方式二:实现类可以重写默认方法
class CC implements AA {
    @Override
    public void test01(){
        System.out.println("我是CC类重写的默认方法");
    }
}

接口静态方法
为了方便按口扩展,JDK 8为法口新增了静态方法。
接口静态方法的定义格式

interface 接口名《
    修饰符 static 返回值 类型方法名() {
        代码;
    }
}


接口静态方法的使用
直接使用接口名调用即可:接口名.静态方法名():

public class DemoLambdaIntro {
    public static void main(String[] args) {
        BBB bbb = new BBB() ;
        // bbb.test01();
        //使用接口名.静态方法名(;
        AAA.test01();
    }
}
interface AAA {
    public static void test01() {
        System.out.println("我是接口静态方法");
    }
}

class BBB implements AAA{
   /* @Override 静态方法不能重写
    public static void test01() {
        System.out.println("我是接口静态方法");
    }*/
}


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

小结
接口中新增的两种方法:
默认方法和静态方法
如何选择呢?如果这个方法需要被实现类继承或重写,使用默认方法,如果接口中的方法不需要被继承就使用静态方法
 

常用内置函数式接口


目标


了解内置函数式接口由来
了解常用内置函数式接口


内置函数式接口来由来


我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名、抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。

常用内置函数式接口介绍


它们主要在java.util.function包中。下面是最常用的几个接口。


1. Supplier接口

java.util.function.supplier<T>接口,它意味看“供给”,对应的Lambda表达式需要“对外提供"一个符合泛型类型的对象数据。
 

@FunctionalInterface
public interface Supplier<T> {
    public abstract T get();
}


供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回的接口。


使用Lambda表达式返回数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer类。

import java.util.Arrays;
import java.util.function.Supplier;

public class DemoLambdaIntro {
    //使用Lambda表达式返回数组元素最大值

    public static void main(String[] args) {

        printMax(() -> {
            int[]arr = {11,99,88,77,22};
            Arrays.sort(arr); //升序排序
            return arr[arr. length - 1];
        });
    }
    public static void printMax (Supplier<Integer> supplier) {
        int max = supplier.get() ;
        System. out. println("max ="+ max) ;
    }

}


2. Consumer接口

java.util.function.Consumer<T>接口则正好相反,它不是生产一个数据, 而是消费一个数据, 其数据类型由泛型参数决定。

@FunctionalInterface
public interface Consumer<T> {
    public abstract T accept();
}

使用Lambda表达式将一个字符串转成大写和小写的字符串


Consumer消费型接口,可以拿到accept方法参数传递过来的数据进行处理,有参无返回的接口。基本使用如:

import java.util.function.Consumer;

public class DemoLambdaIntro {
    public static void main(String[] args) {
        System.out.println("开始啦");
        printHello((String str) -> {
            System. out. println(str. toUpperCase());
        });
    }
    public static void printHello (Consumer<String> consumer){
        System.out.println("aaa");
        consumer.accept( "Hello World");

    }
}
import java.util.function.Consumer;

public class DemoLambdaIntro {
    public static void main(String[] args) {
        System.out.println("开始啦");
        printHello((String str) -> {
            System. out. println(str. toLowerCase());
        },(String str) -> {
            System. out. println(str. toUpperCase());
        });
    }
    public static void printHello (Consumer<String> c1,Consumer<String> c2) {
        System.out.println("aaa");
        String str =  "Hello World";
        c1.accept(str);
        c2.accept(str);
    }
}


默认方法: andThen


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

import java.util.function.Consumer;

public class DemoLambdaIntro {
    public static void main(String[] args) {
        System.out.println("开始啦");
        printHello((String str) -> {
            System. out. println(str. toLowerCase());
        },(String str) -> {
            System. out. println(str. toUpperCase());
        });
    }
    public static void printHello (Consumer<String> c1,Consumer<String> c2) {
        System.out.println("aaa");
        String str =  "Hello World";
        /*c1.accept(str);
        c2.accept(str);*/
        c1.andThen(c2).accept(str);
    }
}


3. Function接口

java.util.function.Function<T, R>接口用来根据一个类型的数据得到另一个类型的数据, 前者称为前置条件,后者称为后置条件。有参数有返回值。

@FunctionalInterface
public interface Function<T, R> {
    public abstract R apply(T t);
}


使用Lambda表达式将字符串转成数字


Function转换型接口,对apply方法传入的T类型数据进行处理,返回R类型的结果,有参有返回的接口。使用的场景例如:将String类型转换为Integer类型。

import java.util.function.Function;

public class DemoLambdaIntro {
    //使用Lambda表达式将字符串转成数字
    public static void main(String[] args) {
        getNumber((String str) -> {
            int i = Integer. parseInt(str);
            return i;
        });
    }
    public static void getNumber (Function<String,Integer> function) {
        Integer numl = function.apply( "10" );
        System.out.println(" num1 ="+ numl) ;
    }
}


默认方法: andThen


Function接口中有一个默认的andThen方法,用来进行组合操作。

import java.util.function.Function;

public class DemoLambdaIntro {
    //使用Lambda表达式将字符串转成数字,第二个操作将这个数字乘以5
    public static void main(String[] args) {
        getNumber((String str)->{
            return Integer. parseInt(str) ;
        },(Integer i) -> {
            return i * 5;
        });
}
    public static void getNumber (Function<String,Integer> f1, Function<Integer, Integer> f2) {
        System. out. println("aa");
        Integer num = f1. apply("6");
        Integer num2 = f2. apply (num) ;
        System.out.println("num2 =" + num2) ;
    }
}
import java.util.function.Function;

public class DemoLambdaIntro {
    //使用Lambda表达式将字符串转成数字,第二个操作将这个数字乘以5
    public static void main(String[] args) {
        getNumber((String str)->{
            return Integer. parseInt(str) ;
        },(Integer i) -> {
            return i * 5;
        });
}
    public static void getNumber (Function<String,Integer> f1, Function<Integer, Integer> f2) {
        System. out. println("aa");
        /*Integer num = f1. apply("6");
        Integer num2 = f2. apply (num) ;*/
        Integer num2 = f1.andThen(f2).apply("6");
        System.out.println("num2 =" + num2) ;
    }
}


4. Predicate接口

有时候我们需要对某种类型的数据进行判断,似而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>接口。
 

@FunctionalInterface
public interface Predicate<T> {
    public abstract boolean test(T t);
}
Predicate接口用于做判断,返回boolean类型的值

使用Lambda判断一个人名如果超过3个字就认为是很长的名字
对test方法的参数T进行判断,返回boolean类型的结果。用于条件判断的场景:
 

import java.util.function.Predicate;

public class DemoLambdaIntro {
    //使用Lambda判断一个人名如果超过3个字就认为是很长的名字
    public static void main(String[] args) {
        System.out.println("开始啦");
        isLongName((String name) -> {
            return name.length() > 3;
        });
    }
    public static void isLongName (Predicate<String> predicate){
        System.out.println(" aa");
        boolean isLong = predicate. test("迪丽热巴");
        System.out.println("是否是长名字:"+ isLong) ;
    }
}

条件判断的标准是传入的Lambda表达式逻辑,只要名称长度大于3则认为很长。


默认方法: and


既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate条件使用”与”逻辑连接起来实现"并且”的效果时,可以使用default方法and.
 

使用Lambda表达式判断一个字符串中即包含W,也包含H
使用Lambda表达式判断一个字符串中包含W或者包含H
使用Lambda表达式判断一个字符串中即不包含W
如果要判断一个字符串既要包含大写"H",又要包含大写"W",那么: 

import java.util.function.Predicate;

public class DemoLambdaIntro {
    /*使用Lambda表达式判断-个字符串中即包含W,也包含H
    使用Lambda表达式判断-个字符串中包含W或者包含H
    使用Lambda表达式判断-个字符串中即不包含W*/
    public static void main(String[] args) {
        System.out.println("开始啦");
        test((String str) -> {
        //判断是否包含W
            return str. contains("W") ;
        },(String str) -> {
        //判断是否包含H
            return str. contains("H");
        });
    }
    public static void test (Predicate<String> p1, Predicate<String> p2){
        String str = "Hello World" ;
        boolean b1 = p1. test(str);
        boolean b2 = p2. test(str) ;
        if (b1 && b2){
            System.out.println("即包含W,也包含H");
        }
    }
}
import java.util.function.Predicate;

public class DemoLambdaIntro {
    /*使用Lambda表达式判断-个字符串中即包含W,也包含H
    使用Lambda表达式判断-个字符串中包含W或者包含H
    使用Lambda表达式判断-个字符串中即不包含W*/
    public static void main(String[] args) {
        System.out.println("开始啦");
        test((String str) -> {
        //判断是否包含W
            return str. contains("W") ;
        },(String str) -> {
        //判断是否包含H
            return str. contains("H");
        });
    }
    public static void test (Predicate<String> p1, Predicate<String> p2){
        String str = "Hello World" ;
       /* boolean b1 = p1. test(str);
        boolean b2 = p2. test(str) ;
        if (b1 && b2){
            System.out.println("即包含W,也包含H");
        }*/
//        使用Lambda表达式判断-个字符串中即包含W,也包含H
        boolean b = p1.and(p2).test(str);
        if(b){
            System.out.println("即包含W,也包含H");
        }

//        使用Lambda表达式判断-个字符串中包含W或者包含H
        boolean b1 = p1.or(p2).test(str);
        if(b1){
            System.out.println("包含W或者包含H");
        }
//        使用Lambda表达式判断-个字符串中即不包含W
        boolean b2 = p1.negate().test("Hello");
        //negate相当于取反 !boolean
        if(b2){
            System.out.println("不包含W");
        }
    }
}

介绍方法引用


目标


了解Lambda的冗余场景
掌握方法引用的格式
了解常见的方法引用方式


Lambda的冗余场景
使用Lambda表达式求一个数组的和
 

import java.util.function.Consumer;

public class DemoLambdaIntro {
    //求一个数组的和
    public static void getMax(int[] arr) {
        int sum = 0;
        for (int n : arr) {
            sum += n;
        }
        System.out.println(sum);
    }

    public static void main(String[] args) {
        //使用Lambda表达式求一个数组的和
        printMax((int[] arr) ->{
          /*  int sum = 0;
            for (int n : arr) {
                sum += n;
            }
            System.out.println(sum);*/
            getMax(arr);
        });
        //使用方法引用
        //让这个指定的方法去重写接口的抽象方法,到时候调用接口的抽象方法就是调用传递过去的这个方法
        printMax(DemoLambdaIntro::getMax);
    }

    public static void printMax(Consumer<int[]> consumer) {
        int[] arr = {11, 22, 33, 44, 55};
        consumer.accept(arr);
    }
}

请注意其中的双置号::写法,这被称为方法引用",是一种种新的语法。
方法引用的格式
符号表示: ::
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用
应用场景:如果Lambda所要实现的方案,已经有其他方法存在相同方案,那么则可以使用方法引用。


常见引用方式
方法引用在JDK 8中使用方式相当灵活,有以下几种形式: 
1. instanceName:: methodName 对象::方法法名
2. ClassName::stat icMethodName 类名::静态方法
3. ClassName::methodName 类名::普通方法
4. ClassName::new 类名::new调用的构造器
5. TypeName[]::new  String[]::new调用数组的构造器


小结
首先了解Lambda表达式的冗余情况体验了方法引用,了解常见的方法引用方式


对象名::引用成员方法
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法,
代码为:
 

    //对象::实例方法
    @Test
    public void test01(){
        Date now = new Date();
        Supplier<Long> supp = () -> {
            return now.getTime();
        };
        System.out.println(supp.get());

        //使用方法引用
        Supplier<Long> supp2 = now::getTime;
        System.out.println(supp2.get());
        //注意:方法引用有两个注意事项
        // 1.被引用的方法,参数要和接口中抽象方法的参数一样
        // 2.当接口抽象方法有返回值时,被引用的方法也必须有返回值
        /* Supplier<Long> su3 = now::setTime;
         su3. get();*/
        /*Supplier<Long> su4 = now::setDate;
        su4. get() ;*/
    }
}

方法引用的注意事项
1.被引用的方法,参数要和接口中抽象方法的参数一样
2.当接口抽象方法有返回值时,被引用的方法也必须有返回值


类名::引用静态方法
由于在java. lang. System类中已经存在了静态方法currentTimeMillis,所以当我们需要通过Lambda来调用该方法时,可以使用方法引用,写法是:
 

//类名::静态方法
    @Test
    public void test02() {
       /* Supplier<Long> su = () -> {
            return System.currentTimeMillis();
        };*/
        Supplier<Long> su = System::currentTimeMillis;
        Long time = su.get();
        System. out. println("time ="+ time);
    }

类名::引用实例方法
Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者。
 

//类名::实例方法
    @Test
    public void test03(){
       /* Function<String,Integer> f1 = (String str) -> {
            return str.length();
        };*/
        //类名::实例方法(注意: 类名::实例方法实际上会将第一个参数作为方法的调用者)
        Function<String,Integer> f1 = String::length;
        int length = f1.apply( "hello");
        System. out. println("length ="+ length) ;

        // BiFunction<String,Integer, String> f2 = String:: substring;
        //相当于这样的Lambda
        BiFunction<String,Integer,String> f2 = (String str, Integer index) -> {
            return str.substring (index) ;
        };
        String str2 = f2. apply( "helloworld", 3);
        System. out. println(" str2 ="+ str2); // loworld
    }

类名::new引用构造器
由于构造器的名称与类名完全一样。 所以构造器引用使用类名称: :new的格式表示。首先是一个简单的Person类:
 

public class Person {
    String name;
    int age;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public Person() {
        System.out.println("执行无参构造");
    }

    public Person(String name, int age) {
        System.out.println("执行有参构造");
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}
//类名::new 引用类的构造器
    @Test
    public void test04(){

       /* Supplier<Person> su1 = ()->{
            return  new Person();
        };*/

        Supplier<Person> su1 = Person::new;

        Person person = su1.get();
        System.out.println("person =" + person);

        /*BiFunction<String,Integer,Person> su2 = (String name,Integer age) ->{
            return  new Person(name,age);
        };*/

        BiFunction <String,Integer,Person> su2 = Person::new;
        Person person1 = su2.apply("迪丽热巴",18);
        System.out.println("person =" + person1);

    }

数组::new引用数组构造器
数组也是object的子类对象,所以山同样具有构造器,只是语法稍有不同。
 

 //类型[]::new
    @Test
    public void test05() {
       /* Function<Integer, int[]> f1 = (Integer length) -> {
            return new int [length];
        };*/
        Function<Integer, int[]> f1 = int[]::new;
        int[] arr1 = f1.apply(10);
        System.out.println(Arrays.toString(arr1));

    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值