RxJava - lambda初体验

java 8 Lambda表达式

介绍

本篇文章主要是RxJava官方文档的辅料,里面太多的Lambda表达式,没有一些了解的确难以看懂,因为是主要的重心还是落在RxJava上,所以本文概述一下如何在Android Studio中使用以及各个表达式的意思。官方文档如何描述Lambda表达式:

Lambda expressions are a new and important feature included in Java SE 8. They provide a clear and concise way to represent one method interface using an expression. Lambda expressions also improve the Collection libraries making it easier to iterate through, filter, and extract data from a Collection. In addition, new concurrency features improve performance in multicore environments.

这段话指出了在java8中lambda表达式三个主要的作用:简洁的方式呈现接口;提高集合迭代,过滤和扩展的性能;提高多核环境下并发的性能。具体的翻译还是大家自行去理解,中英转换肯定会出现一定的偏差。

软硬件环境

  • JDK 8
  • NetBeans 7.4 (这个只是IDE 在Android Studio中同样是可以使用的)

Lambda应用在那些场景呢?因为不知道怎么翻译合适就直接上英语了.

Anonymous Inner Class

匿名内部类,用过java的人这个应该都知道就不再赘述

// 匿名内部类
JButton jb1 = new JButton();
jb1.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("xxx");
    }
});

// lambda表达式的方式
JButton jb = new JButton();
jb.addActionListener((e) -> System.out.println());
Functional Interface

在java中,一个interface里面只有一个需要实现的方法,这种形式的接口就被称作为Functional Interface,也被叫做SAM.(Single Abstract Method)

public interface ActionListener extends EventListener {

    /**
     * Invoked when an action occurs.
     */
    public void actionPerformed(ActionEvent e);

}
Lambda Expression Syntax

λ表达式的语法,通常λ表达式可以将5行代码,转换成一行代码来表示,上面的匿名内部类便是例子,λ表达式的语法由如下三个部分组成:

参数列表箭头方法体
(int x,int y)->x + y

lambda表达式实例

Listener Lambda
private static void listenerLambda() {
    //        Listener Lambda
    JButton jb1 = new JButton();
    jb1.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("xxx");
        }
    });

    // lambda表达式的方式
    JButton jb = new JButton();
    jb.addActionListener((e) -> System.out.println());
}
Comparator Lambda
private static void compatorLambda() {
    // sort with inner class
    List<Person> personList = new ArrayList<>();
    personList.add(new Person("zk"));
    personList.add(new Person("ak"));


    System.out.println("sort with inner class");
    Collections.sort(personList, new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            // 按照字典序排列 person
            return o1.getSurName().compareTo(o2.getSurName());
        }
    });

    for (Person p:
            personList) {
        System.out.println(p.getSurName());
    }


    System.out.println("sort with lambda");
    // sort with lambda
    Collections.sort(personList, (p1,p2)->p1.getSurName().compareTo(p2.getSurName()));
    for (Person p:
            personList) {
        System.out.println(p.getSurName());
    }
}
Runnable Lambda
private static void runnableLambda() {
    // 传统模式
    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Tran  A");
            System.out.println("Tran  B");
        }
    };

    // lambda
    Runnable r2 = () -> System.out.print("Lambda  A");
    System.out.print("Lambda  B");

    r1.run();
    r2.run();
}

Improving Code with Lambda Expression

使用Lambda优化代码。代码优化这件事可轻可重,像我写代码的主要目的就是奔着功能去,一往无前,有bug回头改,往往都是一去不返。代码优化有很多方面,像6大设计原则,23种设计模式等等,今天得例子要验证得Principle叫做 — ‘Don’t Repeat Yourself ’ 简称 (DRY),不得不说所有得原则全称都不如简称有格调。

那下面我们就来验证一下lambda相对于原有得做法到底 DRY 在哪里:

1.场景

有一群人来面试,只招聘司机、应征入伍、飞行员,面试得条件如下:

司机 : 年纪超过16
入伍 : 男性年龄在18-25之间
飞行员:年龄在23 到 65之间

Person 字段

public class Person {
private String givenName;
private String surName;
private int age;
private Gender gender;
private String eMail;
private String phone;
private String address; 

2.创建person List

怎么创建比较随意,我这里就按照官网得方式来创建(在上一篇RxJava中有关于设计模式得附录)

public static List<Person> createShortList() {
    List<Person> people = new ArrayList<>();
    people.add(
            new Person.Builder().
                    givenName("Bob")
                    .surName("Baker")
                    .age(21)
                    .gender(Gender.MALE)
                    .email("bob.baker@example.com")
                    .phoneNumber("201-121-4678")
                    .address("44 4th St, Smallville, KS 12333")
                    .build()
    );
    people.add(
            new Person.Builder()
                    .givenName("Jane")
                    .surName("Doe")
                    .age(25)
                    .gender(Gender.FEMALE)
                    .email("jane.doe@example.com")
                    .phoneNumber("202-123-4678")
                    .address("33 3rd St, Smallville, KS 12333")
                    .build()
    );
    people.add(
            new Person.Builder()
                    .givenName("John")
                    .surName("Doe")
                    .age(25)
                    .gender(Gender.MALE)
                    .email("john.doe@example.com")
                    .phoneNumber("202-123-4678")
                    .address("33 3rd St, Smallville, KS 12333")
                    .build()
    );

    return people;
}

3.选出你所要挑选的满足条件的人

public class RoboContactMethods {

    public void callDrivers(List<Person> pl) {
        for (Person p : pl) {
            if (p.getAge() >= 16) {
                roboCall(p);
            }
        }
    }

    public void emailDraftees(List<Person> pl) {
        for (Person p : pl) {
            if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE) {
                roboEmail(p);
            }
        }
    }

    public void mailPilots(List<Person> pl) {
        for (Person p : pl) {
            if (p.getAge() >= 23 && p.getAge() <= 65) {
                roboMail(p);
            }
        }
    }

    public void roboCall(Person p) {
        System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
    }

    public void roboEmail(Person p) {
        System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.geteMail());
    }

    public void roboMail(Person p) {
        System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
    }

}

我看这段代码是没问题的,for循环迭代选出满足条件的人,完全没毛病;那我们来看看官方的犊子是怎么演的。

  • 没有遵循DRY原则
    • 每个方法的选择条件必须重写
    • 每个方法都重复循环
  • 需要大量方法来实现每个用例
  • 上述代码不灵活,如果招聘条件发生变化,那么将引起大量的更改,可维护性不强

4.第一次重构

这次我们将上述的几个问题列入考虑,其实,我感觉我的翻译貌似出了点小差错,或者是我没明白他是什么意思,除了最后一项可以理解之外,其他的情况没弄懂是什么意思,再或者是本身语言的限制只能优化最后一项,那么下面看看,该如何重构。

public class RoboContactMethods2 {


public void callDrivers(List<Person> pl) {
    for (Person p : pl) {
        if (isDriver(p)) {
            roboCall(p);
        }
    }
}

public void emailDraftees(List<Person> pl) {
    for (Person p : pl) {
        if (isDraftee(p)) {
            roboEmail(p);
        }
    }
}

public void mailPilots(List<Person> pl) {
    for (Person p : pl) {
        if (isPilot(p)) {
            roboMail(p);
        }
    }
}

public boolean isDriver(Person p) {
    return p.getAge() >= 16;
}

public boolean isDraftee(Person p) {
    return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
}

public boolean isPilot(Person p) {
    return p.getAge() >= 23 && p.getAge() <= 65;
}

public void roboCall(Person p) {
    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
}

public void roboEmail(Person p) {
    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.geteMail());
}

public void roboMail(Person p) {
    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
}

}

搜索条件被封装进方法中,较之前的代码而言确实有一定的提升,条件可以被反复利用并且可以更方便的更改。但是,还是有很多重复的地方,是不是没有更好的办法呢?(······肯定是有的,要不我说个球)

在使用lambda之前先看看还有什么能改进的或者是为lambda做一些铺垫。上述的代码,其实并不符合开闭原则(OCP – 原则上对修改关闭,对扩展开放),这段程序可能发生改变是招聘条件,那么我们让调用程序自己去维护,就更进一步的优化了。

将条件抽象成接口,让调用者自己去维护,接口如下:

 public interface MyTest<T> {
   public boolean test(T t);
 }

RoboContactsAnon.java

package com.example.lambda;
import java.util.List;
import java.util.function.Predicate;

/**
 * @author MikeW
 */
public class RoboContactsAnon {
    public void phoneContacts(List<Person> pl, MyTest<Person> pred) {
        for (Person p : pl) {
            if (pred.test(p)) {
                roboCall(p);
            }
        }
    }

    public void emailContacts(List<Person> pl, MyTest<Person> pred) {
        for (Person p : pl) {
            if (pred.test(p)) {
                roboEmail(p);
            }
        }
    }

    public void mailContacts(List<Person> pl, MyTest<Person> pred) {
        for (Person p : pl) {
            if (pred.test(p)) {
                roboMail(p);
            }
        }
    }

    public void roboCall(Person p) {
        System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
    }

    public void roboEmail(Person p) {
        System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
    }

    public void roboMail(Person p) {
        System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
    }
}

RoboCallTest03.java 调用程序

public class RoboCallTest03 {

    public static void main(String[] args) {

      List<Person> pl = Person.createShortList();
      RoboContactsAnon robo = new RoboContactsAnon();

      System.out.println("\n==== Test 03 ====");
      System.out.println("\n=== Calling all Drivers ===");
      robo.phoneContacts(pl,
          new MyTest<Person>(){
            @Override
            public boolean test(Person p){
              return p.getAge() >=16;
            }
          }
      );

      System.out.println("\n=== Emailing all Draftees ===");
      robo.emailContacts(pl,
          new MyTest<Person>(){
            @Override
            public boolean test(Person p){
              return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
            }
          }
      );


      System.out.println("\n=== Mail all Pilots ===");
      robo.mailContacts(pl,
          new MyTest<Person>(){
            @Override
            public boolean test(Person p){
              return p.getAge() >= 23 && p.getAge() <= 65;
            }
          }
      );
  }
}

仅通过java8之前得方式,程序看起来已经符合OCP的原则;在前面提到过SAM这种形式是可以用,lambda来表示的。那么看看如何去用lambda,来进一步的优化上面的程序

lambda 优化

在java.util.function包中提供了一种形式类似的MyTest的接口,Predicate;

 public interface Predicate<T> {
   public boolean test(T t);
 }

RoboContactsLambda.java

package com.example.lambda;
import java.util.List;
import java.util.function.Predicate;

/**
 * @authorzhoukan
 */
public class RoboContactLambda {
    public void phoneContacts(List<Person> pl, Predicate<Person> pred) {
        for (Person p : pl) {
            if (pred.test(p)) {
                roboCall(p);
            }
        }
    }

    public void emailContacts(List<Person> pl, Predicate<Person> pred) {
        for (Person p : pl) {
            if (pred.test(p)) {
                roboEmail(p);
            }
        }
    }

    public void mailContacts(List<Person> pl, Predicate<Person> pred) {
        for (Person p : pl) {
            if (pred.test(p)) {
                roboMail(p);
            }
        }
    }

    public void roboCall(Person p) {
        System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
    }

    public void roboEmail(Person p) {
        System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
    }

    public void roboMail(Person p) {
        System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
    }
}

Mytest接口被更换成了Predicate,调用程序的代码片段如下

public static void main(String[] args){ 

     List<Person> pl = Person.createShortList();
     RoboContactLambda robo = new RoboContactLambda();

     Predicate<Person> allDrivers = p -> p.getAge() >= 16;
     robo.phoneContacts(pl, allDrivers);
}

当然即使不适用util.function提供的接口,也是可以实现了,只要满足lambda表达式的场景就好:

    robo.mailContacts(pl,
      /*new MyTest<Person>(){
        @Override
        public boolean test(Person p){
          return p.getAge() >= 23 && p.getAge() <= 65;
        }
      }*/
            (p) -> p.getAge() > 23 && p.getAge() <= 65
    );

The java.util.function 包

function包提供了几种方便去使用lambda的interface,当然不仅仅是给lambda表达式使用,所有诸如上述的优化都是可以考虑用这些方法的,确实我看到这里就有点抑制不住内心的小狂喜,lambda的这些function不就是Rxjava中常用的操作符嘛,这么麻烦的工作算是没白做,就下面的需求,我们来体验一下她的魅力。

  • Predicate: A property of the object passed as argument
  • Consumer: An action to be performed with the object passed as argument
  • Function: Transform a T to a U
  • Supplier: Provide an instance of a T (such as a factory)
  • UnaryOperator: A unary operator from T -> T
  • BinaryOperator: A binary operator from (T, T) -> T

输出东方人和西方人的信息

上面的personList(求职者),要将他们的信息分别发给,老板和老板娘,他们一个是中国人,一个是美国人;由于习惯不同,西方习惯姓在后名在前(分别对应Given name 和 surname),中国的习惯我就不提了。

传统的方式

person.java

public void printWesternName() {

    System.out.println("\nName: " + this.getGivenName() + " " + this.getSurName() + "\n" +
            "Age: " + this.getAge() + "  " + "Gender: " + this.getGender() + "\n" +
            "EMail: " + this.geteMail() + "\n" +
            "Phone: " + this.getPhone() + "\n" +
            "Address: " + this.getAddress());
}


public void printEasternName() {

    System.out.println("\nName: " + this.getSurName() + " " + this.getGivenName() + "\n" +
            "Age: " + this.getAge() + "  " + "Gender: " + this.getGender() + "\n" +
            "EMail: " + this.geteMail() + "\n" +
            "Phone: " + this.getPhone() + "\n" +
            "Address: " + this.getAddress());
}  
使用Function Interface的方式
public interface Function<R,T>{
    public R apply(T t);
}

指定两个泛型以T作为输入,R作为输出,那么对于上述场景而言 -> Function

输出
==== NameTestNew02 ===
===Custom List===
Name: Bob EMail: bob.baker@example.com
Name: Jane EMail: jane.doe@example.com
Name: John EMail: john.doe@example.com
Name: James EMail: james.johnson@example.com
Name: Joe EMail: joebob.bailey@example.com
Name: Phil EMail: phil.smith@examp;e.com
Name: Betty EMail: betty.jones@example.com

===Western List===

Name: Bob Baker
Age: 21  Gender: MALE
EMail: bob.baker@example.com
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333

Name: Jane Doe
Age: 25  Gender: FEMALE
EMail: jane.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

Name: John Doe
Age: 25  Gender: MALE
EMail: john.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

Name: James Johnson
Age: 45  Gender: MALE
EMail: james.johnson@example.com
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111

Name: Joe Bailey
Age: 67  Gender: MALE
EMail: joebob.bailey@example.com
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111

Name: Phil Smith
Age: 55  Gender: MALE
EMail: phil.smith@examp;e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333

Name: Betty Jones
Age: 85  Gender: FEMALE
EMail: betty.jones@example.com
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333

===Eastern List===

Name: Baker Bob
Age: 21  Gender: MALE
EMail: bob.baker@example.com
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333

Name: Doe Jane
Age: 25  Gender: FEMALE
EMail: jane.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

Name: Doe John
Age: 25  Gender: MALE
EMail: john.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

Name: Johnson James
Age: 45  Gender: MALE
EMail: james.johnson@example.com
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111

Name: Bailey Joe
Age: 67  Gender: MALE
EMail: joebob.bailey@example.com
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111

Name: Smith Phil
Age: 55  Gender: MALE
EMail: phil.smith@examp;e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333

Name: Jones Betty
Age: 85  Gender: FEMALE
EMail: betty.jones@example.com
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333

lambda表达式和集合

体验过Function接口的优点还没完,java 8给我们的惊喜还不止这些。(不这些都不够,我必须是你近旁的一只木棉,根紧握地里,也相触云间 … 神特么的乱入),lambda基础部分结束后,来看看在集合方面它又有哪些优势。

先看看有哪些不一样的操作符:

  • forEach
  • stream
  • filter
  • collection
  • map

1.添加新的类,将查询条件全部封装进HashMap中

public class SearchCriteria {

    private final Map<String, MyTest<Person>> searchMap = new HashMap<>();

    private SearchCriteria() {
        super();
        initSearchMap();
    }

    private void initSearchMap() {
        MyTest<Person> allDrivers = p -> p.getAge() >= 16;
        MyTest<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
        MyTest<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;

        searchMap.put("allDrivers", allDrivers);
        searchMap.put("allDraftees", allDraftees);
        searchMap.put("allPilots", allPilots);

    }

    public MyTest<Person> getCriteria(String PredicateName) {
        MyTest<Person> target;

        target = searchMap.get(PredicateName);

        if (target == null) {

            System.out.println("Search Criteria not found... ");
            System.exit(1);

        }

        return target;

    }

    public static SearchCriteria getInstance() {
        return new SearchCriteria();
    }

}

2.Looping

Test01ForEach.java

public static void main(String[] args) {

    List<Person> pl = Person.createShortList();

    System.out.println("\n=== Western Phone List ===");
    pl.forEach(p -> p.printWesternName());

    System.out.println("\n=== Eastern Phone List ===");
    pl.forEach(Person::printEasternName);

    System.out.println("\n=== Custom Phone List ===");
    pl.forEach(p -> {
        System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail()));
    });

}

3.链式调用和过滤器

Test02Filter.java
public class Test02Filter {

    public static void main(String[] args) {

        List<Person> pl = Person.createShortList();

        SearchCriteria search = SearchCriteria.getInstance();

        System.out.println("\n=== Western Pilot Phone List ===");

        pl.stream().filter(search.getCriteria("allPilots"))
                .forEach(Person::printWesternName);


        System.out.println("\n=== Eastern Draftee Phone List ===");

        pl.stream().filter(search.getCriteria("allDraftees"))
                .forEach(Person::printEasternName);

    }

}

4.getting Lazy

由于篇幅过长,写的有点疲惫,细枝末节的地方,可能没描述清楚,留言里面见;当然这个getting lazy说的并不是我懒

  • Laziness:跟java-web的懒加载类似的概念,在前面程序中,循环就采取了了‘lazy’的方式,在filter之后再进行输出,这是一种更为高效的方式
  • Eagerness:Code that performs operations on every object in a list is considered “eager”. For example, an enhanced for loop that iterates through an entire list to process two objects, is considered a more “eager” approach.(ps : 直接给原文了)
stream

在上面的代码中,filter之前都会先使用stream,stream方法会将Collection作为输入,返回java.util.stream.Stream接口作为输出。Stream就是集合中元素的序列;默认情况下,一旦元素被调用在stream中将不再有该元素,类似于java中的queuen,因此,每个stream只能被使用一次;另外,stream能变成一个序列在该方法调用之后,后文的例子将印证这一点。

转化和结果

当stream被使用之后被处理,所以在stream中的元素,是无法发生更改;那么如果你还是想在链式操作之后保留stream对应的collection:可以将他们保留在一个新的集合中SHOW CODE :

Test03toList.java

public class Test03toList {

public static void main(String[] args) {

         List<Person> pl = Person.createShortList();

         SearchCriteria search = SearchCriteria.getInstance();

         // Make a new list after filtering.
         List<Person> pilotList = pl
                 .stream()
                 .filter(search.getCriteria("allPilots"))
                 .collect(Collectors.toList());

         System.out.println("\n=== Western Pilot Phone List ===");
         pilotList.forEach(Person::printWesternName);

       }

}

.collect(Collectors.toList()); 返回一个新的collection

集合中的计算

map()通常和filter成对出现,map会从对象里抽出某个属性,来执行一些操作,下面的例子以age作为主要的操作字段,来呈现集合中计算的用法

Test04Map.java

public class Test04Map {

public static void main(String[] args) {
     List<Person> pl = Person.createShortList();

     SearchCriteria search = SearchCriteria.getInstance();

     // Calc average age of pilots old style
     System.out.println("== Calc Old Style ==");
     int sum = 0;
     int count = 0;

     for (Person p:pl){
       if (p.getAge() >= 23 && p.getAge() <= 65 ){
         sum = sum + p.getAge();
         count++;
       }
     }

     long average = sum / count;
     System.out.println("Total Ages: " + sum);
     System.out.println("Average Age: " + average);


     // Get sum of ages
     System.out.println("\n== Calc New Style ==");
     long totalAge = pl
             .stream()
             .filter(search.getCriteria("allPilots"))
             .mapToInt(p -> p.getAge())
             .sum();

     // Get average of ages
     OptionalDouble averageAge = pl
             .parallelStream()
             .filter(search.getCriteria("allPilots"))
             .mapToDouble(p -> p.getAge())
             .average();

     System.out.println("Total Ages: " + totalAge);
     System.out.println("Average Age: " + averageAge.getAsDouble());

   }

}

结语

总算是写完了,博客后面写得不是很走心,但是还是比较开心,又一次通过官方文档,完成了一个知识点:另外由于篇幅问题,Android Studio中如何引入java 8将在另外一篇博客中说明,lambda就到这里了,欢迎勘误。

文档 :

oracle 官方教程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值