第五章 ---- 面向对象(二)

2、三大特征

2.1、继承性

继承的好处:

  • 减少了代码的冗余,提高了代码的复用性
  • 便于功能的扩展
  • 多态的前提

继承格式:class A extends B{}

  • A继承B:A获取了B中声明的所有属性和方法
  • B类中的private属性方法,子类任然获取到了,又因封装性影响,不能自接调用
  • A类可以声明自己特有的属性方法:功能拓展

继承性规定

  • 单继承:一个类只能有一个父类,但可多层继承
  • 继承后:获取直接,间接父类中声明的属性和方法
  • 没有显式声明父类,则继承于java.lang.Object 类

2.1.1、方法重写

重写:子类继承父类后,可以对父类中同名同参数的方法进行覆盖重写(方法名和形参列表需要一样)
应用:重写后子类对象调用同名同参方法,实际调用的是子类中的方法

重载和重写的区别

重写的规定

  • 权限修饰符:子类重写的不小于父类的(不能降低可见性)
    - 子类不能重写父类中声明为private的方法
  • 返回值类型:
    - 父类 void 子类只能是 void
    - 父类 A型 子类只能是 A类以及子类型
    - 父类 基本类型 子类需要是相同的基本类型
  • 异常:子类 不大于 父类抛出的异常类型
  • @Override:校验该方法是否是重写
  • static:
    - 子父类同名同参方法 为非static(考虑重写)
    - 子父类同名同参方法 为static(不是重写)
    - 静态方法不能被重写

2.1.2、子类实例化过程

从结果上:

  • 子继承父,就获取了父类中声明的属性和方法
  • 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。

从过程来看

  • 创建子类对象,一定会直接或间接调用父类构造器,进而调用父类的父类构造器,直到调用了Object类中的空参构造为止。
  • 正因为加载过所有的父类结构,所以才可以看到内存中父类中的结构,子类才可以考虑进行调用

明确:虽然创建子类对象时,调用了父类的构造器,但没有new所以只创建了一个子类对象

静态代码块 —> 构造代码块 —> 构造函数代码块
父类静态代码块—>子类静态代码块 ==>
父类构造代码块 —> 子类构造代码块 ==>
父类构造函数代码块 —> 子类构造函数代码块

销毁顺序,是先销毁子类

2.2、多态性

多态是什么:

多态:一个事物的多种形态
父类引用子类对象(Person p = new Man)
多态的使用,虚拟方法调用

  • 编译看左边,运行看右边
    - 调用子类特有方法时编译不通过
    - 调用子父类同名方法时,运行的是子类方法
    - 调用父类特有方法时,子类继承的父类方法
    - 只适用于方法,不适用于属性,属性编译和运行都看左边

为什么要有多态:

如果没有多态,会出现很多重载方法。
便于制定规范,各自不同实现由子类完成

调用子类特有方法(instanceof)

使用强制类型转换
强转时可能出现类型转换异常,使用instanceof检查

3、关键字

3.1、super

this:当前对象
super:父类的…

super 可以用来调用:属性、方法、构造器

  • 子类方法/构造器:可以使用super.属性 或 super.方法 的方式,显式调用父类中声明的属性或方法。
  • 子父类同名的属性,方法:使用this先找本类再找父类,使用super直接找父类
  • super调用构造器
    - super(形参列表),调用父类中声明的指定的构造器
    - super需要出现在首行,和this只能二选一
    - 在构造器中没有显示的调用,默认调用的是父类的空参构造
    - 在类的多个构造器中至少有一个构造器调用了父类的构造器

4、常用类说明

String

String概述

String是一个final类,代表不可变的字符序列,是常量
String对象的字符内容是存储在一个字符数组value[]中的。
String实现了Serializable接口:表示字符串是支持序列化的。
String实现了Comparable接口:表示String可以比较大小
String虽然是引用类型,但也具备值传递的特性

字面量方式

(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中

String s1 = "abc";//在常量池中产生一个字符数组,返回字符数据地址,在常量池中的位置
String s2 = "abc";//常量池中已经存在,返回存在的地址
System.out.println(s1 == s2); //true

String s2 = "bcd";

在这里插入图片描述

new String()

在这里插入图片描述

字符串拼接方式赋值对比

String s1 = "1abc";
String s2 = "ab";
String s3 = 1 +  "ab" + "c";
String s4 = 1 + s2 + "c";
System.out.println(s1 == s3);//true
System.out.println(s1 == s4); //false
  1. JVM优化机制String s3 = "ab" + "c"; 会被处理成String s3 = "abc";
  2. 只要有一个变量参与结果就在堆中
  3. 可以调用intern()返回常量池中的值

在这里插入图片描述

常用方法

  • length()
  • charAt(int index)
  • equals(Object obj)
  • equalsIgnoreCase()
  • substring()
  • indexOf(String str)
  • contains()
  • replace()
  • split()
  • toLowerCase()
  • toUpperCase()
  • trim()
  • toCharArray()
  • getBytes()
  • isEmpty()
  • matches()

String转换

1、与基本数据类型、包装类之间的转换
	String --> 基本数据类型、包装类:
		调用包装类的静态方法:parseXxx(str)
	基本数据类型、包装类 --> String:
		调用String重载的valueOf(xxx)
		使用字符串的 + 操作
2、与字符数组之间的转换
	String --> char[]:
		调用String的toCharArray()
	char[] --> String:
		调用String的构造器
3、与字节数组之间的转换
	编码:String --> byte[]:调用String的getBytes()
	解码:byte[] --> String:调用String的构造器

说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。

StringBuffer & StringBuilder

String:不可变的字符序列;底层使用char[]存储
StringBuffer: 可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

StringBuffer内存解析

String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};

StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';

StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];

//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
         默认情况下,扩容为原来容量的2+ 2,同时将原数组中的元素复制到新的数组中。

        指导意义:开发中建议大家使用:StringBuffer(int capacity)StringBuilder(int capacity)

常用方法

  • append(xxx)
  • delete(int start,int end)
  • setCharAt(int n ,char ch) / replace(int start, int end, String str)
  • charAt(int n )
  • insert(int offset, xxx)
  • length();
  • reverse();
  • for() + charAt() / toString()

StringBuffer作用

提高String的性能

Date

两个构造

Date()//创建一个对应当前时间的Date对象

Date(lone time)//指定毫秒值的时间

两个方法

toString()//转字符串
getTime() //获取毫秒值

SimpleDateFormat

Object

Object类是所有Java类的根父类

Object 类中方法

  • clone()
  • equals()
  • finalize()垃圾回收之前调用
  • getClass()
  • hashCode()
  • notify()
  • toString()
  • wait()
  • notifyAll()

hashCode()

Object提供给我们了一个Native的方法“public native int hashCode();

Hash

在这里插入图片描述
Hash是散列的意思,就是把任意长度的输入,通过散列算法变换成固定长度的输出,该输出就是散列值。关于散列值,有以下几个关键结论:

  • 如果散列表中存在和散列原始输入K相等的记录,那么K必定在f(K)的存储位置上
  • 不同关键字经过散列算法变换后可能得到同一个散列地址,这种现象称为碰撞
  • 如果两个Hash值不同(前提是同一Hash算法),那么这两个Hash值对应的原始输入必定不同
HashCode

1、HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的

2、如果两个对象equals相等,那么这两个对象的HashCode一定也相同

3、如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写

4、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置

HashCode 的作用

1、查找快捷:假设内存中有0-9的位置,现将 某字段 放入位置中,如不经过hash运算则任意存放,查找时需要遍历整个内存区域查找。当经过hash运算后存入的地址,在查找时可以直接到指定区域查找,从而提高查找效率。
2、产生hash碰撞后:ThreadLocal中的做法就是从算出来的位置向后查找第一个为空的位置,放置数据,HashMap的做法就是通过链式结构连起来

为什么重写Object的equals(Object obj)方法尽量要重写Object的hashCode()方法
public class HashCodeClass
{
    private String str0;
    private double dou0;
    private int       int0;

    public boolean equals(Object obj)
    {
        if (obj instanceof HashCodeClass)
        {
            HashCodeClass hcc = (HashCodeClass)obj;
            if (hcc.str0.equals(this.str0) &&
                hcc.dou0 == this.dou0 &&
                hcc.int0 == this.int0)
            {
                return true;
            }
            return false;
        }
        return false;
    }
}



public class TestMain
{
    public static void main(String[] args)
    {
        System.out.println(new HashCodeClass().hashCode());
        System.out.println(new HashCodeClass().hashCode());
        System.out.println(new HashCodeClass().hashCode());
        System.out.println(new HashCodeClass().hashCode());
        System.out.println(new HashCodeClass().hashCode());
        System.out.println(new HashCodeClass().hashCode());
    }
}



1901116749
1807500377
355165777
1414159026
1569228633
778966024

我们希望两个HashCodeClass类equals的前提是两个HashCodeClass的str0、dou0、int0分别相等。那么这个类不重写hashCode()方法是有问题的。

如果不重写,采用的是Object中的hashCode,根据对象地址值数学计算出得hashCode,每个对象是不同的,但对于equals相同的对象而言,也需要该对象的hashCode相同,这就是需要重写hashCode的原因


//Integer
public int hashCode() {
    return value;
    }

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
    }



//String
public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
        char v1[] = value;
        char v2[] = anotherString.value;
        int i = offset;
        int j = anotherString.offset;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
            return false;
        }
        return true;
        }
    }
    return false;
    }

//HashMap
public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

clone()

https://www.cnblogs.com/fnlingnzb-learner/p/10649509.html

https://www.jianshu.com/p/0235759d8103

为什么要克隆

基本数据类型,字符串,包装类都有对于的副本机制

当试图保存对象的某时刻状态时,发现变量是浅拷贝。

class Student {  
    private int number;  
  
    public int getNumber() {  
        return number;  
    }  
  
    public void setNumber(int number) {  
        this.number = number;  
    }  
      
}  
public class Test {  
      
    public static void main(String args[]) {  
        Student stu1 = new Student();  
        stu1.setNumber(12345);  
        Student stu2 = stu1;  
          
        System.out.println("学生1:" + stu1.getNumber());  
        System.out.println("学生2:" + stu2.getNumber());  

		stu2.setNumber(54321);  
		  
		System.out.println("学生1:" + stu1.getNumber());  
		System.out.println("学生2:" + stu2.getNumber());  
    }  
}  

在这里插入图片描述

如何实现克隆

一般步骤是(浅克隆):

  1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

  2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)

下面对上面那个方法进行改造:

class Student implements Cloneable{  
    private int number;  
  
    public int getNumber() {  
        return number;  
    }  
  
    public void setNumber(int number) {  
        this.number = number;  
    }  
      
    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return stu;  
    }  
}  
public class Test {  
    public static void main(String args[]) {  
        Student stu1 = new Student();  
        stu1.setNumber(12345);  
        Student stu2 = (Student)stu1.clone();  
          
        System.out.println("学生1:" + stu1.getNumber());  
        System.out.println("学生2:" + stu2.getNumber());  
          
        stu2.setNumber(54321);  
      
        System.out.println("学生1:" + stu1.getNumber());  
        System.out.println("学生2:" + stu2.getNumber());  
    }  
}  
浅克隆和深克隆

浅克隆
在这里插入图片描述
深克隆
在这里插入图片描述

解决多层克隆问题

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

public class Outer implements Serializable{
  private static final long serialVersionUID = 369285298572941L;  //最好是显式声明ID
  public Inner inner;
 //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]
  public Outer myclone() {
      Outer outer = null;
      try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ObjectOutputStream oos = new ObjectOutputStream(baos);
          oos.writeObject(this);
      // 将流序列化成对象
          ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
          ObjectInputStream ois = new ObjectInputStream(bais);
          outer = (Outer) ois.readObject();
      } catch (IOException e) {
          e.printStackTrace();
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
      return outer;
  }
}



public class Inner implements Serializable{
  private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
  public String name = "";

  public Inner(String name) {
      this.name = name;
  }

  @Override
  public String toString() {
      return "Inner的name值为:" + name;
  }
}
总结

实现对象克隆有两种方式:

1). 实现Cloneable接口并重写Object类中的clone()方法;

2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

equals()的使用

== 和 equals

  • ==:运算符
    - 1. 可以使用在基本数据类型变量和引用数据类型变量中
    - 2. 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型相同)
    - 2. 如果比较的是引用数据类型变量:必将两个对象的地址值是否相同。

  • equals():是方法,非运算符
    - 只适用有引用数据类型
    - Object 类中 equals()的定义:
    public boolean equals(Object obj){ return (this == obj); }
    - 通常情况下,我们自定义的类如果使用equals(),通常会重写父类的equals
    在这里插入图片描述

toString()

object 中的toString:输出的是对象的地址值
需要输出指定格式内容,需要重写toString

Objects工具类 - 简化我们的操作

在JDK7版本的时候,Java引入了java.util.Objects工具类,用于封装一些平时使用频度很高或容易出错的操作,这些操作形成了Objects的各个方法,下来我们来看看这些方法。

1. Objects()构造方法

private Objects() {
    throw new AssertionError("No java.util.Objects instances for you!");
}

这个方法没有特别的地方,但有两点需要特别注意:

  1. 该方法为私有方法
  2. 其实现为抛出一个异常(或错误)
    其主要目的是为了避免对象的构造,即使通过反射机制也做不到!

2. equals()

有别于Object.equals(),这个方法可以避免空指针异常。

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

3. deepEquals()

Object.equals()用于比较两个对象的引用是否相同,而deepEquals()却扩展成了可以支持数组。

 public static boolean deepEquals(Object a, Object b) {
    if (a == b)
        return true;
    else if (a == null || b == null)
        return false;
    else
        return Arrays.deepEquals0(a, b);
}

4. hashCode(Object o)

Object.hashCode()类似,只是在对象为null时返回的散列值为0而已。

public static int hashCode(Object o) {
    return o != null ? o.hashCode() : 0;
}

5. hash()

生成对象的散列值,包括数组。

public static int hash(Object... values) {
    return Arrays.hashCode(values);
}

6. toString()

归根结底,其内部最终调用了对象的toString()方法。只是额外多了空指针判断而已。

public static String toString(Object o) {
    return String.valueOf(o);
}
public static String toString(Object o, String nullDefault) {
    return (o != null) ? o.toString() : nullDefault;
}

7. compare()

用于比较两个对象。

public static <T> int compare(T a, T b, Comparator<? super T> c) {
    return (a == b) ? 0 :  c.compare(a, b);
}

8. requireNonNull()

在对象为空指针时,抛出特定message的空指针异常。

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
public static <T> T requireNonNull(T obj, String message) {
    if (obj == null)
        throw new NullPointerException(message);
    return obj;
}
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
    if (obj == null)
        throw new NullPointerException(messageSupplier.get());
    return obj;
}

9. isNull() 和 nonNull()

这两个方法用于判断对象为null和对象不为null。通常情况下,我们不会直接使用这两个方法,而是使用比较操作符==和!=。这两个方法主要用在jdk8开始支持的流计算里面。

public static boolean isNull(Object obj) {
    return obj == null;
}
public static boolean nonNull(Object obj) {
    return obj != null;
}

Comparable

是什么,为什么

Comparable 接口对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。

实现Comparable的类必须实现compareTo(Object obj)方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。

  1. 如果当前对象this大于形参对象obj,则返回正整数
  2. 如果当前对象this小于形参对象obj,则返回负整数
  3. 如果当前对象this等于形参对象obj,则返回零

实现Comparable 接口的对象列表(数组)可通过Collections.sort 或Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

如何实现

实现comparable接口

对于自定义类如果需要排序,让自定义类实现Comparable接口,重写compareTo()方法,在compareTo()方法中指定如何排序
Goods类

package com.zs.尹成.常用类.Object.comparable;

public class Goods implements Comparable{
    private String name;
    private double price;

    public Goods(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

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

    @Override
    public int compareTo(Object o) {
        if (o instanceof Goods){
            Goods goods = (Goods) o;

            //方式一:
            if(this.price  >goods.price){
                return 1;
            } else if (this.price < goods.price) {
                return -1;
            }else{
                return 0;
            }
        }
        throw new RuntimeException("传入的数据类型不一致");
    }
}

测试类

public class ComparableTest {
    @Test
    public void test1(){
        Goods[] arr = new Goods[4];
        arr[0] = new Goods("z1",42);
        arr[1] = new Goods("a1",22);
        arr[2] = new Goods("z2",32);
        arr[3] = new Goods("a2",2);

        Arrays.sort(arr);

        System.out.println(Arrays.toString(arr));
    }
}
使用Comparator函数式接口,临时实现
    @Test
    public void test2(){
        String[] arr = new String[]{"BB","AA","CC","EE","DD"};

        Arrays.sort(arr, (o1, o2) -> -o1.compareTo(o2));

        System.out.println(Arrays.toString(arr));
    }
    @Test
    public void test3(){
        Goods2[] arr = new Goods2[]{
                new Goods2("z1",42),
                new Goods2("a1",22),
                new Goods2("z2",32),
                new Goods2("a2",2)
        };


        Arrays.sort(arr, (o1, o2) -> {
                if (o1.getPrice() > o2.getPrice()){
                    return -1;
                }else if (o1.getPrice() < o2.getPrice()){
                    return 1;
                }else{
                    return 0;
                }
            }
        );

        System.out.println(Arrays.toString(arr));
    }

包装类

包装类实现软件的可拓展性
包装类:当作参数使用时,不会改变原来的数据(数据与地址是放在一起的)
同字符串做参数时也有相同的副本机制

 Integer i1 = 10; //在高速缓冲区
 Integer i3 = 10;
 Integer i4 = 1000;
 Integer i5 = 1000;//在堆上
 Integer i2 = new Integer(10);//在堆上
 System.out.println(i1 == i2);//false
 System.out.println(i1 == i3);//true
 System.out.println(i4 == i5);//false

//比较两个数是否相同
 System.out.println(i4.intValue() == i5.intValue());
 System.out.println(i4.equals(i5));

在这里插入图片描述

  • 基本 --> 包装类

    1. 调用包装类构造器
      Integer i = new Integer(1);
      Integer i = new Integer("1");
      Boolean b = new Boolean("true");
    2. 自动装箱
      Integer i = 1;
  • 包装类 --> 基本

    1. 调用xxxValue()
      int i = in1.intValue();
    2. 自动拆箱
      int i = in1
  • 基本、包装 —> String

    1. 连接 +
      String s = 1 + "";
    2. String的valueOf()
      String s = String.valueOf(1);
      String s = String.valueOf(new Bouble(12.4f));
  • String —> 基本、包装

    1. 包装类的parseXxx()
      int i = Integer.parseInt("1");

面试题

//三元运算符,需要类型统一,会有自动类型提升
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);//1.0
/**
包装类:将基本类型作为属性包装类的属性
如 private final int value;

Integer内部有一个内部类 IntegerCache
定义了一个缓存数组,Integer cache[],该数组中存储了高频对象-128~127
所以在范围内的值会在缓存中找,不在缓存中的则会创建新的对象
*/
public void method1() {
	Integer i = new Integer(1);
	Integer j = new Integer(1);
	System.out.println(i == j);//false
	Integer m = 1;
	Integer n = 1;
	System.out.println(m == n);//true
	Integer x = 128;
	Integer y = 128;
	System.out.println(x == y);//false
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悠闲的线程池

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值