Java内部类

本文深入探讨Java内部类的特性和应用场景,包括成员内部类、局部内部类、匿名内部类及静态内部类的特点与区别,同时解析内部类如何访问外部类成员。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

title:Java内部类

date:2017年11月11日18:57:15


对内部类其实也接触了挺多了的,在学习回调方法的时候我们接触了匿名内部类,在创建线程的时候我们也习惯在new Thread的时候直接创建一个Runnable对象实例。

    Thread t1=new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                System.out.println(Thread.currentThread().getName()+"start");
            try {
                Thread.sleep(2000);

            } catch (Exception e) {
                // TODO: handle exception
            }
            System.out.println(Thread.currentThread().getName()+"finish");
            }
        },"t1");

还有在学习单例模式的时候,为了保证线程安全,我们通常会选用饿汉式单例,当然,我们要明白,最好的单例模式是使用枚举,因为枚举是天然的单例,具体的在之前的单例模式中有介绍。

public class Singleton3 {
    private Singleton3() {
    }

    private static class SingleHolder {
        public static Singleton3 holder = new Singleton3();
    }

    public static final Singleton3 getInstance() {
        return SingleHolder.holder;
    }
}

这些内部类的使用场景足以证明内部类的应用是十分广泛的,我们有必要深入了解一下内部类相关知识。

一.内部类的特性

在《Thinking in java》中,提到了内部类有如下特性:

  • 内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。

  • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。

这个在我们马上要讲的锁(Lock)的实现中就会出现,在独享锁ReentrantLock的实现中有两种实现,分别为公平锁和非公平锁,分别依赖于ReentrantLock中的两个内部类实现(这两个类都继承了Sync类,而Sync又继承了AbstractQueuedSynchronizer类(进行线程安全原子操作的类))。部分代码如下:

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
  static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
  • 创建内部类对象的时刻并不依赖于外围类对象的创建。
  • 内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
  • 内部类提供了更好的封装,除了该外围类,其他类都不能访问。

二.四种内部类介绍

  • 成员内部类

    顾名思义,他和普通的类成员变量一样,

    他是可以无限制的访问外围类的所有 成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。

    package com.wangcc.MyJavaSE.innerClass;
    
    public class OutClass {
    private String name;
    private int age;
    
    public OutClass(String name, int age) {
        this.name = name;
        this.age = age;
    
    }
    
    @Override
    public String toString() {
        return "OutClass [name=" + name + ", age=" + age + "]";
    }
    
    public class InnerClass {
        public void innerPrint() {
            age = 23;
            System.out.println(OutClass.this.toString());//(1)
        }
    }
    
    public InnerClass getInnerClass() {
        return new InnerClass();
    }
    
    public static void main(String[] args) {
        OutClass outClass = new OutClass("kobe", 39);
        InnerClass innerClass = outClass.getInnerClass();
        innerClass.innerPrint();
    }
    }

    我们运行程序,得到的输出:

    OutClass [name=kobe, age=23]

    注意,这个时候,如果我们将(1)改成: System.out.println(this.toString());

    就会输出:

    com.wangcc.MyJavaSE.innerClass.OutClass$InnerClass@15db9742

    所以证明成员内部类是一个独立的类,他有着自己的this指针。

    之前我们说到内部类可以访问到外部类的所有变量,那么为什么呢?下面就来探究一下

    我们知道一个包含内部类的类编译之后会生出两个class文件。这里则是:OutClassInnerClass.classOutClass.class,OutClassInnerClass.class进行反编译:

    javap -v OutClass$InnerClass.class >>C:\Coding\temp\inner.txt (输出cmd命令结果到文本中)

    得到的文本如下:

    Classfile /C:/Coding/WorkSpace/MyJavaSE/target/classes/com/wangcc/MyJavaSE/innerClass/OutClass$InnerClass.class
    Last modified 2017-11-11; size 886 bytes
    MD5 checksum 2abdd0575f95b91c87e476d303b5c05e
    Compiled from "OutClass.java"
    public class com.wangcc.MyJavaSE.innerClass.OutClass$InnerClass
    minor version: 0
    major version: 49
    flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
     #1 = Class              #2             // com/wangcc/MyJavaSE/innerClass/OutClass$InnerClass
     #2 = Utf8               com/wangcc/MyJavaSE/innerClass/OutClass$InnerClass
     #3 = Class              #4             // java/lang/Object
     #4 = Utf8               java/lang/Object
     #5 = Utf8               this$0
     #6 = Utf8               Lcom/wangcc/MyJavaSE/innerClass/OutClass;
     #7 = Utf8               <init>
     #8 = Utf8               (Lcom/wangcc/MyJavaSE/innerClass/OutClass;)V
     #9 = Utf8               Code
    
    #10 = Fieldref           #1.#11         // com/wangcc/MyJavaSE/innerClass/OutClass$InnerClass.this$0:Lcom/wangcc/MyJavaSE/innerClass/OutClass;
    
    
    #11 = NameAndType        #5:#6          // this$0:Lcom/wangcc/MyJavaSE/innerClass/OutClass;
    
    
    #12 = Methodref          #3.#13         // java/lang/Object."<init>":()V
    
    
    #13 = NameAndType        #7:#14         // "<init>":()V
    
    
    #14 = Utf8               ()V
    
    
    #15 = Utf8               LineNumberTable
    
    
    #16 = Utf8               LocalVariableTable
    
    
    #17 = Utf8               this
    
    
    #18 = Utf8               Lcom/wangcc/MyJavaSE/innerClass/OutClass$InnerClass;
    
    
    #19 = Utf8               innerPrint
    
    
    #20 = Methodref          #21.#23        // com/wangcc/MyJavaSE/innerClass/OutClass.access$0:(Lcom/wangcc/MyJavaSE/innerClass/OutClass;I)V
    
    
    #21 = Class              #22            // com/wangcc/MyJavaSE/innerClass/OutClass
    
    
    #22 = Utf8               com/wangcc/MyJavaSE/innerClass/OutClass
    
    
    #23 = NameAndType        #24:#25        // access$0:(Lcom/wangcc/MyJavaSE/innerClass/OutClass;I)V
    
    
    #24 = Utf8               access$0
    
    
    #25 = Utf8               (Lcom/wangcc/MyJavaSE/innerClass/OutClass;I)V
    
    
    #26 = Fieldref           #27.#29        // java/lang/System.out:Ljava/io/PrintStream;
    
    
    #27 = Class              #28            // java/lang/System
    
    
    #28 = Utf8               java/lang/System
    
    
    #29 = NameAndType        #30:#31        // out:Ljava/io/PrintStream;
    
    
    #30 = Utf8               out
    
    
    #31 = Utf8               Ljava/io/PrintStream;
    
    
    #32 = Methodref          #3.#33         // java/lang/Object.toString:()Ljava/lang/String;
    
    
    #33 = NameAndType        #34:#35        // toString:()Ljava/lang/String;
    
    
    #34 = Utf8               toString
    
    
    #35 = Utf8               ()Ljava/lang/String;
    
    
    #36 = Methodref          #37.#39        // java/io/PrintStream.println:(Ljava/lang/String;)V
    
    
    #37 = Class              #38            // java/io/PrintStream
    
    
    #38 = Utf8               java/io/PrintStream
    
    
    #39 = NameAndType        #40:#41        // println:(Ljava/lang/String;)V
    
    
    #40 = Utf8               println
    
    
    #41 = Utf8               (Ljava/lang/String;)V
    
    
    #42 = Utf8               SourceFile
    
    
    #43 = Utf8               OutClass.java
    
    
    #44 = Utf8               InnerClasses
    
    
    #45 = Utf8               InnerClass
    
    {
    final com.wangcc.MyJavaSE.innerClass.OutClass this$0;
      descriptor: Lcom/wangcc/MyJavaSE/innerClass/OutClass;
      flags: ACC_FINAL, ACC_SYNTHETIC
    
    public com.wangcc.MyJavaSE.innerClass.OutClass$InnerClass(com.wangcc.MyJavaSE.innerClass.OutClass);
      descriptor: (Lcom/wangcc/MyJavaSE/innerClass/OutClass;)V
      flags: ACC_PUBLIC
      Code:
        stack=2, locals=2, args_size=2
           0: aload_0
           1: aload_1
           2: putfield      #10                 // Field this$0:Lcom/wangcc/MyJavaSE/innerClass/OutClass;
           5: aload_0
           6: invokespecial #12                 // Method java/lang/Object."<init>":()V
           9: return
        LineNumberTable:
          line 18: 0
        LocalVariableTable:
          Start  Length  Slot  Name   Signature
              0      10     0  this   Lcom/wangcc/MyJavaSE/innerClass/OutClass$InnerClass;
    
    public void innerPrint();
      descriptor: ()V
      flags: ACC_PUBLIC
      Code:
        stack=2, locals=1, args_size=1
           0: aload_0
           1: getfield      #10                 // Field this$0:Lcom/wangcc/MyJavaSE/innerClass/OutClass;
           4: bipush        23
           6: invokestatic  #20                 // Method com/wangcc/MyJavaSE/innerClass/OutClass.access$0:(Lcom/wangcc/MyJavaSE/innerClass/OutClass;I)V
           9: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
          12: aload_0
          13: invokevirtual #32                 // Method java/lang/Object.toString:()Ljava/lang/String;
          16: invokevirtual #36                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          19: return
        LineNumberTable:
          line 20: 0
          line 21: 9
          line 22: 19
        LocalVariableTable:
          Start  Length  Slot  Name   Signature
              0      20     0  this   Lcom/wangcc/MyJavaSE/innerClass/OutClass$InnerClass;
    }
    SourceFile: "OutClass.java"
    InnerClasses:
       public #45= #1 of #21; //InnerClass=class com/wangcc/MyJavaSE/innerClass/OutClass$InnerClass of class com/wangcc/MyJavaSE/innerClass/OutClass
    

    我们注意看这一段:

    final com.wangcc.MyJavaSE.innerClass.OutClass this$0;
      descriptor: Lcom/wangcc/MyJavaSE/innerClass/OutClass;
      flags: ACC_FINAL, ACC_SYNTHETIC
    
    public com.wangcc.MyJavaSE.innerClass.OutClass$InnerClass(com.wangcc.MyJavaSE.innerClass.OutClass);

    我们看第一行:定义了指向外部对象的一个指针。

    编译器会默认为成员内部类添加了一个指向外部类对象的引用,然后再上面片段的最后一行进行赋值。我们观察Java类,发现我们并没显示的实现一个带有外部对象作为参数的构造方法,所以默认应该是只有一个无参的构造方法才对呀,但是编译器还是会默认会为构造方法添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的OutClass this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对OutClass this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。所以也就有:

    第一:成员内部类中不能存在任何static的变量和方法;第二:成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。

  • 局部内部类

     局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

    局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。我们看一个例子。

    package com.wangcc.MyJavaSE.innerClass;
    
    class People {
    private int age;
    
    public People(int age) {
        this.age = age;
    }
    
    public int getAge() {
        return age;
    }
    
    public People() {
    
    }
    }
    
    class Man {
    
    public People getWoman() {
        class Woman extends People { // 局部内部类
            public Woman(int age) {
                super(10);
            }
        }
        return new Woman(24);
    }
    }
    
    public class PartClass {
    public static void main(String[] args) {
        Man man = new Man();
        People woman = man.getWoman();
        System.out.println(woman.getAge());
    }
    }
  • 匿名内部类

​ 匿名类一般用来做监听器(回调方法)使用,这个在我们之前讲的MQ API中已经说过了,匿名类再实现回调方法的时候是经常使用,可以看看之前的MQ系列。

对于匿名内部类,有一些他独特的地方:

它是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。匿名内部类也是不能有访问修饰符和static修饰符的。

我们之前在使用匿名内部类的时候,对于内部类中的方法里的参数的修饰和局部变量的修饰都需要使用

final,大家有没有想过这到底是为什么呢。我们来通过一个例子来分析一下,依然是通过反编译:

我们之前说过了,我们在开启线程的时候经常就会使用内部类。

package com.wangcc.MyJavaSE.innerClass;

public class AnonymousInnerClassTest2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("Main Thread");
        AnonymousInnerClassTest2 test = new AnonymousInnerClassTest2();
        test.test(20);
        System.out.println("Main End");
    }

    public void test(final int b) {
        final int a = 10;
        new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                System.out.println(Thread.currentThread().getName());
                System.out.println(a);
                System.out.println(b);
            }
        }, "t1").start();
        ;

    }
}

我们运行这个类,发现编译后生成的class文件也有两个,AnonymousInnerClassTest21.classAnonymousInnerClassTest2.class,AnonymousInnerClassTest21.class我们反编译一下

Classfile /C:/Coding/WorkSpace/MyJavaSE/target/classes/com/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1.class
  Last modified 2017-11-11; size 1083 bytes
  MD5 checksum f380d6128102e0eaa6244a78a84d9dfc
  Compiled from "AnonymousInnerClassTest2.java"
class com.wangcc.MyJavaSE.innerClass.AnonymousInnerClassTest2$1 implements java.lang.Runnable
  minor version: 0
  major version: 49
  flags: ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1
   #2 = Utf8               com/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Class              #6             // java/lang/Runnable
   #6 = Utf8               java/lang/Runnable
   #7 = Utf8               this$0
   #8 = Utf8               Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2;
   #9 = Utf8               val$b
  #10 = Utf8               I
  #11 = Utf8               <init>
  #12 = Utf8               (Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2;I)V
  #13 = Utf8               Code
  #14 = Fieldref           #1.#15         // com/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1.this$0:Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2;
  #15 = NameAndType        #7:#8          // this$0:Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2;
  #16 = Fieldref           #1.#17         // com/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1.val$b:I
  #17 = NameAndType        #9:#10         // val$b:I
  #18 = Methodref          #3.#19         // java/lang/Object."<init>":()V
  #19 = NameAndType        #11:#20        // "<init>":()V
  #20 = Utf8               ()V
  #21 = Utf8               LineNumberTable
  #22 = Utf8               LocalVariableTable
  #23 = Utf8               this
  #24 = Utf8               Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1;
  #25 = Utf8               run
  #26 = Fieldref           #27.#29        // java/lang/System.out:Ljava/io/PrintStream;
  #27 = Class              #28            // java/lang/System
  #28 = Utf8               java/lang/System
  #29 = NameAndType        #30:#31        // out:Ljava/io/PrintStream;
  #30 = Utf8               out
  #31 = Utf8               Ljava/io/PrintStream;
  #32 = Methodref          #33.#35        // java/lang/Thread.currentThread:()Ljava/lang/Thread;
  #33 = Class              #34            // java/lang/Thread
  #34 = Utf8               java/lang/Thread
  #35 = NameAndType        #36:#37        // currentThread:()Ljava/lang/Thread;
  #36 = Utf8               currentThread
  #37 = Utf8               ()Ljava/lang/Thread;
  #38 = Methodref          #33.#39        // java/lang/Thread.getName:()Ljava/lang/String;
  #39 = NameAndType        #40:#41        // getName:()Ljava/lang/String;
  #40 = Utf8               getName
  #41 = Utf8               ()Ljava/lang/String;
  #42 = Methodref          #43.#45        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #43 = Class              #44            // java/io/PrintStream
  #44 = Utf8               java/io/PrintStream
  #45 = NameAndType        #46:#47        // println:(Ljava/lang/String;)V
  #46 = Utf8               println
  #47 = Utf8               (Ljava/lang/String;)V
  #48 = Methodref          #43.#49        // java/io/PrintStream.println:(I)V
  #49 = NameAndType        #46:#50        // println:(I)V
  #50 = Utf8               (I)V
  #51 = Utf8               SourceFile
  #52 = Utf8               AnonymousInnerClassTest2.java
  #53 = Utf8               EnclosingMethod
  #54 = Class              #55            // com/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2
  #55 = Utf8               com/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2
  #56 = NameAndType        #57:#50        // test:(I)V
  #57 = Utf8               test
  #58 = Utf8               InnerClasses
{
  final com.wangcc.MyJavaSE.innerClass.AnonymousInnerClassTest2 this$0;
    descriptor: Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2;
    flags: ACC_FINAL, ACC_SYNTHETIC

  com.wangcc.MyJavaSE.innerClass.AnonymousInnerClassTest2$1(com.wangcc.MyJavaSE.innerClass.AnonymousInnerClassTest2, int);
    descriptor: (Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2;I)V
    flags:
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: aload_1
         2: putfield      #14                 // Field this$0:Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2;
         5: aload_0
         6: iload_2
         7: putfield      #16                 // Field val$b:I
        10: aload_0
        11: invokespecial #18                 // Method java/lang/Object."<init>":()V
        14: return
      LineNumberTable:
        line 1: 0
        line 15: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1;

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: invokestatic  #32                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
         6: invokevirtual #38                 // Method java/lang/Thread.getName:()Ljava/lang/String;
         9: invokevirtual #42                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
        15: bipush        10
        17: invokevirtual #48                 // Method java/io/PrintStream.println:(I)V
        20: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
        23: aload_0
        24: getfield      #16                 // Field val$b:I
        27: invokevirtual #48                 // Method java/io/PrintStream.println:(I)V
        30: return
      LineNumberTable:
        line 19: 0
        line 20: 12
        line 21: 20
        line 22: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  this   Lcom/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1;
}
SourceFile: "AnonymousInnerClassTest2.java"
EnclosingMethod: #54.#56                // com.wangcc.MyJavaSE.innerClass.AnonymousInnerClassTest2.test
InnerClasses:
     #1; //class com/wangcc/MyJavaSE/innerClass/AnonymousInnerClassTest2$1

我们注意看run方法那一段:有一条指令

bipush 10

  这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

然后我们注意看构造方法:构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参a以参数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

  也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

  从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

  对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

  • 静态内部类

    静态内部类我们见的非常多了,在学习HashMap源码时,我们就有说到他的底层实现是散列数组加链表(JDK8在链表长度大于8之后会变成红黑树)其中的散列数组的元素Node就是用HashMap中的一个静态内部类实现的。

    静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值