Java编程思想读书笔记——泛型(四)

15.15 混型

混型:混合多个类的能力,以产生一个可以表示混型中所有类型的类。

15.15.1 C++中的混型
15.15.2 与接口混合

使用接口产生混型效果:

package com.mzm.chapter15;

import java.util.Date;

/**
 * 
 */
public class Mixins {

    public static void main(String[] args) {
        Mixin mixin1 = new Mixin(), mixin2 = new Mixin();
        mixin1.set("test string 1");
        mixin2.set("test string 2");
        System.out.println(mixin1.get() + " " + mixin1.getStamp() + " " + mixin1.getSerialNumber());
        System.out.println(mixin2.get() + " " + mixin2.getStamp() + " " + mixin2.getSerialNumber());
    }
}

interface TimeStamped {
    long getStamp();
}

class TimeStampedImp implements TimeStamped {

    private final long timeStamp;

    public TimeStampedImp() {
        timeStamp = new Date().getTime();
    }

    @Override
    public long getStamp() {
        return timeStamp;
    }
}

interface SerialNumbered {
    long getSerialNumber();
}

class SerialNumberImp implements SerialNumbered {

    private static long counter = 1;

    private final long serialNumber = counter++;

    @Override
    public long getSerialNumber() {
        return serialNumber;
    }
}

interface Basic {
    void set(String val);
    String get();
}

class BasicImp implements Basic {

    private String value;

    @Override
    public void set(String val) {
        value = val;
    }

    @Override
    public String get() {
        return value;
    }
}

/**
 * 实现TimeStamped, SerialNumbered两个接口
 * 分别定义TimeStamped, SerialNumbered两个接口类型的成员变量
 * 接口的实现方法则直接调用相应成员变量的方法
 */
class Mixin extends BasicImp implements TimeStamped, SerialNumbered{

    private TimeStamped timeStamped = new TimeStampedImp();
    private SerialNumbered serialNumber = new SerialNumberImp();

    @Override
    public long getStamp() {
        return timeStamped.getStamp();
    }

    @Override
    public long getSerialNumber() {
        return serialNumber.getSerialNumber();
    }
}

本质是使用代理模式,每一个混入类型都必须在目标类中有相应的成员变量作为代理。这种方式在混入类型较多的情况下,代码量会急速增加。

15.15.3 使用装饰器模式

装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的——无论对象是否被装饰,都拥有一个可以向对象发送的公共消息集。装饰类虽然也可以添加新方法,但是这是受限的。
装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。因此可以将基于参数化类型的混型当做是一种泛型装饰器机制,该机制不需要装饰器设计模式的继承结构。

package com.mzm.chapter15;

import java.util.Date;

/**
 * 装饰器模式
 * 子类继承父类,并拥有父类类型的成员对象,在构造器中从外部传入该成员对象
 * Created by 蒙卓明 on 2018/1/14.
 */
public class Decoration {

    public static void main(String[] args) {
        TimeStamped2 t = new TimeStamped2(new Basic2());
        TimeStamped2 t2 = new TimeStamped2(new SerialNumbered2(new Basic2()));
        //t2是TimeStamped2类型,不具有getSerialNumber()方法
        //t2.getSerialNumber();
        SerialNumbered2 s = new SerialNumbered2(new Basic2());
        SerialNumbered2 s2 = new SerialNumbered2(new TimeStamped2(new Basic2()));
        //s2是SerialNumbered2类型,不具有getStamp()方法
        //s2.getStamp();
    }
}

/**
 * 第一层
 */
class Basic2 {

    private String value;

    public void set(String val) {
        value = val;
    }

    public String get() {
        return value;
    }
}

/**
 * 第二层
 */
class Decorator extends Basic2 {

    protected Basic2 basic2;

    public Decorator(Basic2 basic2) {
        this.basic2 = basic2;
    }

    public void set(String val) {
        basic2.set(val);
    }

    public String get() {
        return basic2.get();
    }
}

/**
 * 第三层
 */
class TimeStamped2 extends Decorator {

    private final long timeStamp;

    public TimeStamped2(Basic2 basic2) {
        super(basic2);
        timeStamp = new Date().getTime();
    }

    public long getStamp() {
        return timeStamp;
    }
}

/**
 * 第三层
 */
class SerialNumbered2 extends Decorator {

    private static long counter = 1;
    private final long serialNumber = counter++;

    public SerialNumbered2(Basic2 basic2) {
        super(basic2);
    }

    public long getSerialNumber() {
        return serialNumber;
    }
}
15.15.4 与动态代理混合

可以使用动态代理来创建一种比装饰器更贴近混型模型的机制。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。

package com.mzm.chapter15;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


/**
 * 
 */
public class DynamicProxyMixin {

    public static void main(String[] args) {
        Object mixin = MixinProxy.newInstance(
                new TwoTuple(new BasicImp(), Basic.class),
                new TwoTuple(new TimeStampedImp(), TimeStamped.class),
                new TwoTuple(new SerialNumberImp(), SerialNumbered.class)
        );
        Basic b = (Basic) mixin;
        TimeStamped t = (TimeStamped) mixin;
        SerialNumbered s = (SerialNumbered) mixin;
        b.set("Hello");
        System.out.println(b.get());
        System.out.println(t.getStamp());
        System.out.println(s.getSerialNumber());
    }
}

class MixinProxy implements InvocationHandler {

    Map<String, Object> delegateByMethod;

    @SuppressWarnings("unchecked")
    public MixinProxy(TwoTuple<Object, Class<?>>... pairs) {
        delegateByMethod = new HashMap<>();
        for(TwoTuple<Object, Class<?>> pair : pairs) {
            for(Method method : pair.second.getMethods()) {
                String methodName = method.getName();
                if(!delegateByMethod.containsKey(methodName)) {
                    delegateByMethod.put(methodName, pair.first);
                }
            }
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        String methodName = method.getName();
        Object delegate = delegateByMethod.get(methodName);
        return method.invoke(delegate, args);

    }

    public static Object newInstance(TwoTuple... pairs) {
        Class[] interfaces = new Class[pairs.length];
        for(int i = 0; i < pairs.length; i++) {
            interfaces[i] = (Class) pairs[i].second;
        }
        ClassLoader cl = pairs[0].first.getClass().getClassLoader();
        return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs));
    }
}
15.16 潜在类型机制

潜在类型机制(结构化类型机制/鸭子类型机制),具有这种类型机制的语言只要求实现某个方法子集,而不是某个特定的类或接口。具有这种类型机制的语言典型的有Python和C++
例子:
Python:

class Dog(Object):
    def speak(self):
        print("Arf!")
    def sit(self):
        print("Sitting")
    def reproduce(self):
        pass

class Robot(Object):
    def speak(self):
        print("Click!")
    def sit(self):
        print("Clank!")
    def oilChange(self):
        pass

def perform(anything):
    anything.speak()
    anything.sit()

if __name__ == '__main__':
    a = Dog()
    b = Robot()
    perform(a)
    perform(b)

perform()函数就隐含着一个潜在接口,但无需显式声明。显然只要对象实现了speak()和sit()两个方法就可以传入perform()函数。
java不支持潜在类型机制,因此将上面的代码转换成java版本,则如下:

package com.mzm.chapter15;

/**
 * 
 */
public interface Performs {

    void speak();
    void sit();

}
package com.mzm.chapter15;

import com.mzm.chapter14.Dog;

/**
 * 
 */
public class DogsAndRobots {

    public static void main(String[] args) {
        PerformingDog d = new PerformingDog();
        Robot r = new Robot();
        Communicate.perform(d);
        Communicate.perform(r);
    }
}


class PerformingDog extends Dog implements Performs {

    @Override
    public void speak() {
        System.out.println("Woof!");
    }

    @Override
    public void sit() {
        System.out.println("Sitting");
    }

    public void reproduce() {

    }
}

class Robot implements Performs {

    @Override
    public void speak() {
        System.out.println("Click!");
    }

    @Override
    public void sit() {
        System.out.println("Clank!");
    }

    public void oilChange() {

    }
}

class Communicate {

    public static <T extends Performs> void perform(T performer) {
        performer.speak();
        performer.sit();
    }

}
15.17 对潜在类型机制的补偿
15.17.1

通过反射可以在java中实现潜在类型机制

package com.mzm.chapter15;

import java.lang.reflect.Method;

/**
 * 
 */
public class LatentReflection {

    public static void main(String[] args) {
        CommunicateReflectively.perform(new SmartDog());
        CommunicateReflectively.perform(new Robot());
        CommunicateReflectively.perform(new Mime());
    }
}

class Mime {

    public void walkAgainstTheWind() {

    }

    public void sit() {
        System.out.println("Pretending to sit");
    }

    public void pushInvisibleWalls() {

    }

    public String toString() {
        return "Mime";
    }

}

class SmartDog {

    public void speak() {
        System.out.println("Woof!");
    }

    public void sit() {
        System.out.println("Sitting");
    }

    public void reproduce() {

    }
}

class CommunicateReflectively {

    public static void perform(Object speaker) {

        Class<?> spkr = speaker.getClass();

        try {
            try {
                Method speak = spkr.getMethod("speak");
                speak.invoke(speaker);
            } catch (NoSuchMethodException e) {
                System.out.println(speaker + " cannot speak");
            }

            try {
                Method sit = spkr.getMethod("sit");
                sit.invoke(speaker);
            } catch (NoSuchMethodException e) {
                System.out.println(speaker + " cannot sit");
            }
        } catch (Exception e) {
            throw new RuntimeException(speaker.toString(), e);
        }
    }
}
15.17.2 将一个方法应用于序列

前面利用反射的方式实现了潜在类型机制,但它将所有类型检查转移到了运行期。我们希望能实现同时实现编译期类型检查和潜在类型机制:

package com.mzm.chapter15;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 
 */
public class Apply {

    /**
     * 实现编译期类型检查和潜在类型机制
     * @param seq 实现Iterable接口的序列容器,序列中存放的类型为T类型或者T类型的某个子类
     * @param f 指定方法
     * @param args 方法相关参数
     * @param <T> 序列容器存放类型的界限
     * @param <S> 序列容器类型
     */
    public static <T, S extends Iterable<? extends T>> void apply(S seq, Method f, Object... args) {
        try {
            for(T t : seq) {
                f.invoke(t, args);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

class Shape {

    public void rotate() {
        System.out.println(this + " rotate");
    }

    public void resize(int newSize) {
        System.out.println(this + " resize " + newSize);
    }

}

class Square extends Shape {

}

class FilledList<T> extends ArrayList<T> {

    public FilledList(Class<? extends T> type, int size) {
        try {
            for(int i = 0; i < size; i++) {
                add(type.newInstance());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

class ApplyTest {

    public static void main(String[] args) throws Exception {
        List<Shape> shapes = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            shapes.add(new Shape());
        }
        Apply.apply(shapes, Shape.class.getMethod("rotate"));
        Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 5);

        List<Square> squares = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            squares.add(new Square());
        }
        Apply.apply(squares, Shape.class.getMethod("rotate"));
        Apply.apply(squares, Shape.class.getMethod("resize", int.class), 5);

        Apply.apply(new FilledList<>(Shape.class, 10), Shape.class.getMethod("rotate"));
        Apply.apply(new FilledList<>(Square.class, 10), Square.class.getMethod("rotate"));

        SimpleQueue<Shape> shapeQ = new SimpleQueue<>();
        for(int i = 0; i < 5; i++) {
            shapeQ.add(new Shape());
            shapeQ.add(new Square());
        }
        Apply.apply(shapeQ, Shape.class.getMethod("rotate"));
    }
}
15.17.3 当你并未碰巧拥有正确的接口时
package com.mzm.chapter15;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 
 */
public class Fill {

    /**
     * 向泛型容器中填充对象
     * 本方法只能用于实现Collection接口的容器对象
     * 对于没有实现Collection对象的容器对象,如SimpleQueue,无法调用该方法
     * @param collection 容器对象,持有类型为T
     * @param classToken T类或其子类的Class对象
     * @param size 容器大小
     * @param <T> 泛型
     */
    public static <T> void fill(Collection<T> collection, Class<? extends T> classToken, int size) {

        for(int i = 0; i < size; i++) {
            try {
                collection.add(classToken.newInstance());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class Contract {

    private static long counter = 0;
    private final long id = counter++;

    public String toString() {
        return getClass().getName() + " " + id;
    }
}

class TitleTransfer extends Contract {

    public static void main(String[] args) {
        List<Contract> contracts = new ArrayList<>();
        Fill.fill(contracts, Contract.class, 3);
        Fill.fill(contracts, TitleTransfer.class, 2);

        for(Contract c : contracts) {
            System.out.println(c);
        }

        SimpleQueue<Contract> contractsQ = new SimpleQueue<>();
        //SimpleQueue这个容器类并没有实现Collection接口
        //Fill.fill(contractsQ, Contract.class, 3);
    }
}

Fill类的fill()方法只适用于实现了Collection接口的容器对象,对于没有实现该接口的容器对象,即使拥有add()方法,也无法使用fill()方法。这个方法并不是那么泛化,因为它将使用范围限定在了Collection的继承范围内。
而使用潜在类型机制的参数化类型机制,才能真正实现泛化。

15.17.4 用适配器仿真潜在类型机制

潜在类型机制实际上创建了一个包含所需方法的隐式接口,如果手工编写该接口,就可以解决问题。
从已有的接口中编写代码来产生需要的接口,这是适配器设计模式的一个典型示例。

package com.mzm.chapter15;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 用适配器仿真潜在类型机制
 * 
 */
public class Fill2 {

    /**
     * Class对象版本的fill()方法
     * @param addable 实现Addable接口的容器对象,其持有对象类型为T
     * @param classToken 持有对象类型或其子类的Class对象
     * @param size 容器大小
     * @param <T> 持有类型
     */
    public static <T> void fill(Addable<T> addable, Class<? extends T> classToken, int size) {
        for(int i = 0; i < size; i++) {
            try {
                addable.add(classToken.newInstance());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 生成器版本的fill()方法
     * @param addable 实现Addable接口的容器对象,其持有对象类型为T
     * @param generator 持有对象类型或其子类的Class对象
     * @param size 容器大小
     * @param <T> 持有类型
     */
    public static <T> void fill(Addable<T> addable, Generator<T> generator, int size) {
        for(int i = 0; i < size; i++) {
            addable.add(generator.next());
        }
    }
}

/**
 * 潜在类型机制实际上创建的隐式接口,这里手工创建
 * @param <T>
 */
interface Addable<T> {
    void add(T t);
}

/**
 * 使用组合,将Collection对象作为代理
 * @param <T>
 */
class AddableCollectionAdapter<T> implements Addable<T> {

    private Collection<T> c;

    public AddableCollectionAdapter(Collection<T> c) {
        this.c = c;
    }

    @Override
    public void add(T item) {
        c.add(item);
    }
}

/**
 * 自动捕获适配器类型
 */
class Adapter {

    public static <T> Addable<T> collectionAdapter(Collection<T> c) {
        return new AddableCollectionAdapter<>(c);
    }
}

/**
 * 继承SimpleQueue类,同时实现Addable接口
 * @param <T>
 */
class AddableSimpleQueue<T> extends SimpleQueue<T> implements Addable<T> {

    public void add(T item) {
        super.add(item);
    }
}

class Fill2Test {

    public static void main(String[] args) {

        //Collection类型适配
        List<Coffee> carrier = new ArrayList<>();
        Fill2.fill(new AddableCollectionAdapter<>(carrier), Coffee.class, 3);

        Fill2.fill(Adapter.collectionAdapter(carrier), Latte.class, 2);

        for(Coffee c : carrier) {
            System.out.println(c);
        }

        System.out.println("----------------------------------------------");

        //使用适配器类AddableSimpleQueue
        AddableSimpleQueue<Coffee> coffeeQ = new AddableSimpleQueue<>();
        Fill2.fill(coffeeQ, Mocha.class, 4);
        Fill2.fill(coffeeQ, Latte.class, 1);
        for(Coffee c : coffeeQ) {
            System.out.println(c);
        }
    }
}

但是使用适配器是一个额外的步骤。

15.18 将函数对象用作策略
package com.mzm.chapter15;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;


/**
 * 函数式编程的实现
 * 
 */
public class Functional {

    /**
     * reduce算子
     * @param seq 可迭代的序列
     * @param combiner 具有聚合逻辑的函数
     * @param <T> 序列中的类型
     * @return 将序列中的所有元素按照combiner的聚合逻辑进行聚合
     */
    public static <T> T reduce(Iterable<T> seq, Combiner<T> combiner) {
        Iterator<T> it = seq.iterator();
        if(it.hasNext()) {
            T result = it.next();
            while(it.hasNext()) {
                result = combiner.combine(result, it.next());
            }
            return result;
        }
        return null;
    }

    /**
     * foreach算子
     * @param seq 可迭代序列
     * @param func 收集序列中的元素的函数对象
     * @param <T> 序列持有类型
     * @return 函数对象
     */
    public static <T> Collector<T> forEach(Iterable<T> seq, Collector<T> func) {
        for(T t : seq) {
            func.function(t);
        }
        return func;
    }

    /**
     * transform算子
     * @param seq 可迭代的序列
     * @param func 转换函数,将T类型对象转换成R类型对象
     * @param <R> 转换后类型
     * @param <T> 转换前类型
     * @return 将持有类型为T的序列转换成持有类型为R的序列
     */
    public static <R, T> List<R> transform(Iterable<T> seq, UnaryFunction<R, T> func) {
        List<R> result = new ArrayList<>();
        for(T t : seq) {
            result.add(func.function(t));
        }
        return result;
    }

    /**
     * filter算子
     * @param seq 可迭代的序列
     * @param pred 条件判断函数,根据传入的参数返回true或false
     * @param <T> 序列的只有类型
     * @return 从序列中筛选满足某种条件的元素,并组成新的序列返回
     */
    public static <T> List<T> filter(Iterable<T> seq, UnaryPredicate<T> pred) {
        List<T> result = new ArrayList<>();
        for(T t : seq) {
            if(pred.test(t)) {
                result.add(t);
            }
        }
        return result;
    }

    /**
     * 整数加法函数类
     */
    static class IntegerAdder implements Combiner<Integer> {

        @Override
        public Integer combine(Integer x, Integer y) {
            return x + y;
        }

    }

    /**
     * 整数减法函数类
     */
    static class IntegerSubtracter implements Combiner<Integer> {

        @Override
        public Integer combine(Integer x, Integer y) {
            return x - y;
        }

    }

    /**
     * 大浮点数加法函数类
     */
    static class BigDecimalAdder implements Combiner<BigDecimal> {

        @Override
        public BigDecimal combine(BigDecimal x, BigDecimal y) {
            return x.add(y);
        }

    }

    /**
     * 大整数加法函数类
     */
    static class BigIntegerAdder implements Combiner<BigInteger> {

        @Override
        public BigInteger combine(BigInteger x, BigInteger y) {
            return x.add(y);
        }
    }

    /**
     * 长整数加法函数类
     */
    static class AtomicLongAdder implements Combiner<AtomicLong> {

        @Override
        public AtomicLong combine(AtomicLong x, AtomicLong y) {
            return new AtomicLong(x.addAndGet(y.get()));
        }

    }

    /**
     * 将一个浮点数转换成其最后一位的单位(ulp)
     */
    static class BigDecimalUlp implements UnaryFunction<BigDecimal, BigDecimal> {

        @Override
        public BigDecimal function(BigDecimal x) {
            return x.ulp();
        }

    }

    /**
     * 大于函数
     * @param <T> 实现Comparable接口的类型
     */
    static class GreaterThan<T extends Comparable<T>> implements UnaryPredicate<T>{

        private T bound;

        public GreaterThan(T bound) {
            this.bound = bound;
        }

        @Override
        public boolean test(T x) {
            return x.compareTo(bound) > 0;
        }

    }

    /**
     * 获取序列中的每个元素并相乘,最后返回最终结果
     */
    static class MultiplyingIntegerCollector implements Collector<Integer> {

        private Integer val = 1;

        @Override
        public Integer function(Integer x) {
            val *= x;
            return val;
        }

        @Override
        public Integer result() {
            return val;
        }
    }

    public static void main(String[] args) {
        List<Integer> li = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
        Integer result = reduce(li, new IntegerAdder());
        System.out.println(result);

        result = reduce(li, new IntegerSubtracter());
        System.out.println(result);

        System.out.println(filter(li, new GreaterThan<>(4)));

        System.out.println(forEach(li, new MultiplyingIntegerCollector()).result());

        System.out.println(forEach(filter(li, new GreaterThan<>(4)), new MultiplyingIntegerCollector()).result());

        MathContext mc = new MathContext(7);
        List<BigDecimal> lbd = Arrays.asList(
                new BigDecimal(1.1, mc),
                new BigDecimal(2.2, mc),
                new BigDecimal(3.3, mc),
                new BigDecimal(4.4, mc)
        );
        BigDecimal rbd = reduce(lbd, new BigDecimalAdder());
        System.out.println(rbd);

        System.out.println(filter(lbd, new GreaterThan<>(new BigDecimal(3))));

        List<BigInteger> lbi = new ArrayList<>();
        BigInteger bi = BigInteger.valueOf(11);
        for(int i = 0; i < 11; i++) {
            lbi.add(bi);
            bi = bi.nextProbablePrime();
        }
        System.out.println(lbi);

        BigInteger rbi = reduce(lbi, new BigIntegerAdder());
        System.out.println(rbi);
        System.out.println(rbi.isProbablePrime(5));

        List<AtomicLong> lal = Arrays.asList(
                new AtomicLong(11),
                new AtomicLong(47),
                new AtomicLong(74),
                new AtomicLong(133)
        );
        AtomicLong ral = reduce(lal, new AtomicLongAdder());
        System.out.println(ral);

        System.out.println(transform(lbd, new BigDecimalUlp()));
    }
}

/**
 * 含聚合逻辑方法的接口,实现该接口,即可创建对应的聚合函数对象
 * @param <T> 聚合逻辑的操作对象
 */
interface Combiner<T> {

    T combine(T x, T y);

}

/**
 * 含转换逻辑方法的接口,实现该接口,即可创建对应的转换函数对象
 * @param <R> 转换后的类型
 * @param <T> 转换前的类型
 */
interface UnaryFunction<R, T> {

    R function(T x);

}

/**
 * 含搜集序列中的元素的逻辑方法的接口
 * @param <T> 序列持有的类型
 */
interface Collector<T> extends UnaryFunction<T, T> {

    T result();

}

/**
 * 含条件判断逻辑方法的接口,实现该接口,即可创建对应的条件判断函数对象
 * @param <T>
 */
interface UnaryPredicate<T> {

    boolean test(T x);

}

这里使用Java来实现函数式编程,其中Combiner、UnaryFunction、Collector和UnaryPredicate在其他语言中即可使用所谓的潜在类型机制实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值