黑马程序员-Java高新技术(二)

------- android培训java培训、期待与您交流! ----------

类加载器:
1.类加载器是将.class的文件加载经内存,也可将普通文件中的信息加载进内存。
2.文件的加载问题:
  (1)eclipse会将源程序中的所有.java文件加载成.class文件,以确保编译,然后放到classPath指定的目录中去。并且会将非.java文件原封不动的复制到.class指定的目录中去。在真正编译的时候,使用classPath目录中的文件,即放置.class文件的目录。
  (2)写完程序是要讲配置文件放到.class文件目录中一同打包,这些都是类加载器加载的,资源文件(配置文件)也同样加载了配置文件。
  (3)框架中的配置文件都要放到classPath指定的文件夹中,原因是它的内部就是用类加载器加载的文件。
3.资源文件的加载:是使用类加载器。
  (1)由类加载器ClassLoader的一个对象加载经内存,即用getClassLoader()方法加载。若要加载普通文件,可用getResourseAsStream(String name)在classPath的文件中逐一查找要加载的文件。
  (2)在.class身上也提供了方法来加载资源文件,其实它内部就是先调用了Loader方法,再加载的资源文件。
如:Reflect.class.getResourseAsStream(String name)
4.配置文件的路径问题:
  (1)用绝对路径,通过getRealPath()方法运算出来具体的目录,而不是内部编码出来的。
一般先得到用户自定义的总目录,在加上自己内部的路径。可以通过getRealPath()方法获取文件路径。对配置文件修改是需要要储存到配置文件中,那么就要得到它的绝对路径才行,因此,配置文件要放到程序的内部。
  (2)name的路径问题:
如果配置文件和classPath目录没关系,就必须写上绝对路径,
如果配置文件和classPath目录有关系,即在classPath目录中或在其子目录中(一般是资源文件夹resource),那么就得写相对路径,因为它自己了解自己属于哪个包,是相对于当前包而言的。

代码演示:
配置文件内容:
className=java.util.ArrayList
public class ReflectTest2 {    
    public static void main(String [] args)throws Exception{    
        //读取系统文件到读取流中    
        //方式一:    
        //InputStream ips = new FileInputStream("config.propert");    
        /*getRealPath()--得到完整的路径//如:金山词霸/内部  
         * 一定要用完整的路径,但完整的路径不是硬编码出来的,而是运算出来的。*/    
        //方式二:    
        //InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/text1/config.propert");    
        //方式三:    
            //第一种:配置文件(资源文件)在当前包中    
        InputStream ips = ReflectTest2.class.getResourceAsStream("resourse/config.propert");    
            //第二种:配置文件(资源文件)不在当前包中,和此包没太大关系    
        //InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/test2/resourse/config.properties");    
            
        //加载文件中的键值对    
        Properties props = new Properties();    
        props.load(ips);    
        //关闭资源,即ips调用的那个系统资源    
        //注意:关闭的是ips操作的流,加载进内存后,就不再需要流资源了,需要关闭    
        ips.close();    
        //定义变量,将文件中的类名赋值给变量    
        String className = props.getProperty("className");    
        //通过变量,创建给定类的对象    
        Collection cons =     
                (Collection)Class.forName(className).newInstance();    
            
        //将元素添加到集合中    
        /*Collection cons = new HashSet();*/    
        ReflectPoint pt1 = new ReflectPoint(3,3);    
        ReflectPoint pt2 = new ReflectPoint(5,5);    
        ReflectPoint pt3 = new ReflectPoint(3,3);    
        cons.add(pt1);    
        cons.add(pt2);    
        cons.add(pt3);    
        cons.add(pt1);    
        //移除元素    
        cons.remove(pt1);    
        System.out.println(cons.size());    
    }    
}    


由内省引出JavaBean
1.内省,是对内部进行检查,了解更多的底层细节。
2.内省的作用:主要针对JavaBean进行操作。
3.JavaBean简述:
  (1)JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法都符合某种特殊的命名规则。
  (2)它是一种特殊的Java类,其中的方法名称等,都符合特殊的规则。只要一个类中含有get和set打头的方法,就可以将其当做JavaBean使用。
  (3)字段和属性:
字段就是我们定义的一些成员变量,如private String name;等
属性是具有某些功能,Bean属性,是含有get或set方法的那些属性的字段,即这个变量的get属性,set属性等。
4.JavaBean的作用:
  如果要在两个模板之间传递多个信息,可将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO),这些信息在类中用私有字段来储存,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。
5.JavaBean的命名方式
  JavaBean的属性是根据其中的setter和getter方法来确定的,而不是依据其中的变量,如方法名为setId,则中文意思是设置Id,getId也是如此;去掉前缀,剩余部分就是属性名称,如果剩余部分的第二个字母小写,则把剩余部分改为小写。如:getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。
6.JavaBean的好处:
  (1)在JavaEE开发中,经常要使用JavaBean。很多环境就要求按JavaBean的方式进行操作,别人都这么用,那么就必须要求这么做。
  (2)JDK中提供了对JavaBean进行操作的API,这套API称为内省,若要自己通过getX的方式来访问私有x,可用内省这套API,操作JavaBean要比使用普通的方式更方便。

代码演示:
public class IntroSpectorTest {    
    /**  
     * @param args  
     */    
    /*  
     * public static void main(String[] args) throws Exception {  
      
        // TODO Auto-generated method stub  
        ReflectPoint pt1 = new ReflectPoint(3,5);  
        String propertyName = "x";  
        //"x"-->"X"-->"getX"-->MethodGetX-->  
        //内省的方式:  
        //属性描述符:PropertyDescriptor  
        //get属性信息  
        PropertyDescriptor pd =  
                new PropertyDescriptor(propertyName,pt1.getClass());  
        Method methodGetX = pd.getReadMethod();  
        Object retVal = methodGetX.invoke(pt1);  
        System.out.println(retVal);  
        //set属性信息  
        Object value = 7;  
        PropertyDescriptor pd2 =  
                new PropertyDescriptor(propertyName,pt1.getClass());  
        Method methodSetX = pd2.getWriteMethod();  
        methodSetX.invoke(pt1,value);  
          
        System.out.println(pt1.getX());  
     }  
     */    
    //上面的get或set代码分别通过选中要重构的代码,通过右击选重构获得get和set方法:    
    public static void main(String[] args) throws Exception {    
        // TODO Auto-generated method stub    
        ReflectPoint pt1 = new ReflectPoint(3,5);    
        String propertyName = "x";    
        //一般方式:"x"-->"X"-->"getX"-->MethodGetX-->    
        //内省方式:    
        //通过get和set方法获取属性值    
        Object retVal = getProperty(pt1, propertyName);    
        System.out.println(retVal);    
            
        Object value = 7;    
        setProperty(pt1, propertyName, value);    
        System.out.println(pt1.getX());    
    }    
        
        //设置属性值的方法             //此处的类型为Object,通用,下同    
    private static void setProperty(Object rf, String propertyName,    
            Object value) throws IntrospectionException,    
            IllegalAccessException, InvocationTargetException {    
        //创建属性描述符对象,将属性名称和加载文件等信息写入其中    
        PropertyDescriptor pd =    
                new PropertyDescriptor(propertyName,rf.getClass());    
        //通过反射的方法类Method,获取属性所对应的set方法    
        Method methodSetX = pd.getWriteMethod();    
        methodSetX.invoke(rf, value);    
    }    
    //获取属性值的方法    
    private static Object getProperty(Object rf, String propertyName)    
            throws IntrospectionException, IllegalAccessException,    
            InvocationTargetException {    
        //创建属性描述符对象,获取属性所对应的名称和加载文件等信息    
        PropertyDescriptor pd =    
                new PropertyDescriptor(propertyName,rf.getClass());    
        //通过反射的方法类Method,获取属性所对应的get方法    
        Method methodGetX = pd.getReadMethod();    
        Object retVal = methodGetX.invoke(rf);    
        return retVal;    
    }    
}  

7.对JavaBean的复杂内省操作:
  (1)在IntroSpector类中有getBeanInfo(Class cls)的方法。
  (2)获取Class对象的Bean信息,返回的是BeanInfo类型。
  (3)BeanInfo类中有getPropertyDescriptors()的方法,可获取所有的BeanInfo的属性信息,返回一个PropertyDescriptor[]。
  (4)在通过遍历的形式,找出与自己想要的那个属性信息。


BeanUtils工具包
1.BeanUtils等工具包都是由阿帕奇提供的,为了便于开发。
2.BeanUtils可以将8种基本数据类型进行自动的转换,因此对于非基本数据类型,就需要注册转换器Converter,这就需要ConverUtils包,
3.BeanUtils工具包好处:
  (1)提供的set或get方法中,传入的是字符串,返回的还是字符串,因为在浏览器中,用户输入到文本框的都是以字符串的形式发送至服务器上的,所以操作的都是字符串。也就是说这个工具包的内部有自动将整数转换为字符串的操作。
  (2)支持属性的级联操作,即支持属性链。如可以设置:人的脑袋上的眼镜的眼珠的颜色。这种级联属性的属性连如果自己用反射,那就很困难了,通过这个工具包就可以轻松调用。
  (3)可以和Map集合进行相互转换:可将属性信息通过键值对的形式作为Map集合存储(通过staticjava.util.Map describe(java.lang.Object bean)的方法),也可以将Map集合转换为JavaBean中的属性信息(通过static voidpopulate(java.lang.Object bean, java.util.Map properties)的方法)。

代码演示:
1.设置和获取属性值:
public class BeanUtilDemo {    
    /**  
     * BeanUtils使用  
     */    
    
    @Test     
    public void test1() throws Exception{    
        //创建对象,设置属性值    
        Person p = new Person();    
        BeanUtils.setProperty(p, "name", "zzz");    
        String name = BeanUtils.getProperty(p, "name");    
        System.out.println(name);    
    }    
        
    @Test     
    public void test2() throws Exception{    
        //创建对象,传入属性值    
        Person p = new Person();    
        String name = "wangwu";    
        String age = "23";    
        String hight = "173.5";    
        //设置属性值    
        BeanUtils.setProperty(p, "name", name);    
        BeanUtils.setProperty(p, "age", age);    
        BeanUtils.setProperty(p, "hight", hight);    
        //获取属性值    
        System.out.println(BeanUtils.getProperty(p, "name"));    
        System.out.println(BeanUtils.getProperty(p, "age"));    
        System.out.println(BeanUtils.getProperty(p, "hight"));    
    }    

2.未注册的属性值的获取和设置
//获取未注册的属性,即非八种基本数据类型的引用类型    
//private Date birthday    
@Test     
public void test3() throws Exception{    
    Person p = new Person();    
    String name = "wangwu";    
    String age = "23";    
    String hight = "173.5";    
    String birthday = "1990-09-09";    
    ConvertUtils.register(new Converter() {    
        //注册器Converter接口中方法的重写    
        @Override    
        public Object convert(Class type, Object value) {    
            if(value == null)    
                return null;    
            if(!(value instanceof String))    
                throw new ConversionException("只支持String类型的转换");    
            String str = (String) value;    
            if(value.equals(""))    
                return null;    
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");    
            try{    
                return sdf.parse(str);    
            }catch(ParseException e){    
                throw new RuntimeException(e);//异常链不能掉,这里必须写上e    
            }    
        }},    
        Date.class);    
        
    //测试    
    BeanUtils.setProperty(p, "name", name);    
    BeanUtils.setProperty(p, "age", age);    
    BeanUtils.setProperty(p, "hight", hight);    
    BeanUtils.setProperty(p, "birthday", birthday);    
    System.out.println(BeanUtils.getProperty(p, "name"));    
    System.out.println(BeanUtils.getProperty(p, "age"));    
    System.out.println(BeanUtils.getProperty(p, "hight"));    
    System.out.println(BeanUtils.getProperty(p, "birthday"));    
}    
    
//使用已经写好的注册器DateLocaleConverter    
@Test     
public void test4() throws Exception{    
    Person p = new Person();    
    String name = "wangwu";    
    String age = "23";    
    String hight = "173.5";    
    String birthday = "1990-09-09";    
    //将日期注册到BeanUtils上    
    ConvertUtils.register(new DateLocaleConverter(), Date.class);//提供的注册器不健壮,因为传入空字符串,就会报错    
                                                                //所以,当没有提供注册器或需要加强注册器的时候,可以自己写    
    //测试    
    BeanUtils.setProperty(p, "name", name);    
    BeanUtils.setProperty(p, "age", age);    
    BeanUtils.setProperty(p, "hight", hight);    
    BeanUtils.setProperty(p, "birthday", birthday);    
    System.out.println(BeanUtils.getProperty(p, "name"));    
    System.out.println(BeanUtils.getProperty(p, "age"));    
    System.out.println(BeanUtils.getProperty(p, "hight"));    
    System.out.println(BeanUtils.getProperty(p, "birthday"));    
    Date date = p.getBirthday();    
    System.out.println(date.toLocaleString());    
}  

3.Map集合在BeanUtils中的应用
@Test    
public void test5() throws Exception {    
    /*  
     * JDK 7.0新特性:   
     * Map map = {"name" : "zs", "age" : 22, "hight" : 176.5};  
     */    
    //将数据存入集合    
    Map map = new TreeMap();    
    map.put("name", "zhangsan");    
    map.put("age", "20");    
    map.put("hight", "172.5");    
    map.put("birthday", "1999-10-02");    
        
    //注册器    
    ConvertUtils.register(new DateLocaleConverter(), Date.class);    
    //获取属性    
    Person p = new Person();    
    BeanUtils.populate(p, map);    
        
    System.out.println(BeanUtils.getProperty(p, "name"));    
    System.out.println(BeanUtils.getProperty(p, "age"));    
    System.out.println(BeanUtils.getProperty(p, "hight"));    
    System.out.println(BeanUtils.getProperty(p, "birthday"));    
        
}    
    
//属性链    
@Test    
public void test6() throws Exception {    
    Person p = new Person();    
    BeanUtils.setProperty(p, "birthday.time", "111212");    
    System.out.println(BeanUtils.getProperty(p, "birthday.time"));    
}    
补充:
1.BeanUtils是以字符串的形式进行操作的
2.PropertyUtils是以传入值本身的类型进行操作的。
//PropertyUtils可直接解析为指定类型,而BeanUtils只能指定字符串的类型    
@Test    
public void test7() throws Exception {    
    Person p = new Person();    
    System.out.println("-----BeanUtiles-------");    
    BeanUtils.setProperty(p, "age", "22");//字符串形式    
    System.out.println(BeanUtils.getProperty(p, "age"));    
    System.out.println(BeanUtils.getProperty(p, "age").getClass().getName());    
        
    System.out.println("-----PropertyUtiles-------");    
    PropertyUtils.setProperty(p, "age", 22);//Integer形式    
    System.out.println(PropertyUtils.getProperty(p, "age"));    
    System.out.println(PropertyUtils.getProperty(p, "age").getClass().getName());    
}  


注解
1.注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则没有某种标记。
2.java编译器、开发工具和其他应用程序就可以用反射来了解自己的类及各种元素上有无何种标记,有什么标记,就会做出相应的处理。
3.标记可以加在包、类、字段、方法、方法参数,以及局部变量上等等。
4.在java.lang包中提供了最基本的annotation,即注解。
5.格式:@注解类名()。如果有属性,则在括号中加上属性名(可省略)和属性值。

java中三种最基本的注解:
1.@SuppressWarning(”deprecation”)-压制警告
SupressWarning是告知编译器或开发工具等提示指定的编译器警告;
”deprecation”是告知具体的信息即方法已过时。
2.@Deprecated-提示成员等已经过时,不再推荐使用。
  源代码标记@Deprecated是在JDK1.5中作为内置的annotation引入的,用于表明类(class)、方法(method)、字段(field)已经不再推荐使用,并且在以后的JDK版本中可能将其删除,编译器在默认情况下检测到有此标记的时候会提示警告信息。
例如:假定之前的某个类升级了,其中的某个方法已经过时了,不能够将过时的方法删除,因为可能会影响到调用此类的这个方法的某些程序,这是就可以通过在方法上加这个注解。
3.@Override-提示覆盖(父类方法)
加上此注解,,可对自己类中的方法判断是否是要覆盖的父类的方法,典型的例子即在集合中覆盖equals(Object obj)方法,其中的参数类型必须是Object,才能被覆盖,若不是,加上此注解就会提示警告。


注解类
1.定义格式:@interface 名称{statement}
2.元注解(注解的注解)
一个注解有其生命周期(Retetion)和存放的位置(Taget),这就可以通过元注解说明。
  (1)Retetion:用于说明注解保留在哪个时期,加载定义的注解之上。
    一个注解的声明周期包含:
java源程序--(javac)-->class文件--(类加载器)-->内存中的字节码
第一、当再源程序上加了注解,javac将java源程序编译为class文件,可能会把源程序中的一些注解去掉,进行相应的处理操作,当我们拿到源程序的时候,就看不到这些注解了。
第二、假设javac把这些注解留在了源程序中(或者说留在了class文件中),当运行此class文件的时候,用类加载器将class文件调入内存中,此时有转换的过程,即把class文件中的注解是否保留下来也不一定。
注意:class文件中不是字节码,只有把class文件中的内部加载进内存,用类加载器加载处理后(进行完整的检查等处理),最终得到的二进制内容才是字节码。
Reteton(枚举类)取值:
Retetion.Policy.SOURSE:java源文件时期,如@Overried和@SuppressWarning
Retetion.Policy.CLASS: class文件时期(默认阶段)
Retetion.Policy.RUNTIME:运行时期,如@Deprecated
  (2)Taget:用于说明注解存放在哪些成分上,默认值是任何元素
其值可设置为枚举类ElementType类中的任何一个,包括:包、字段、方法、方法参数、构造器、类等值。取值为:
PACKAGE(包声明)
FIELD(字段声明)
ANNOTATION_TYPE(注释类型声明)
CONSIRUCTOR(构造器声明)
METHOD(方法声明)
PARAMETER(参数声明)
TYPE(类、接口(包含注释类型)或枚举声明)
LOCAL_VARIABLE(局部变量声明)
注意:其中代表类的值是TYPE。因为class、enum、interface和@
interface等都是属于Type的。不可用CLASS表示.
3.通过反射查看其它类中的注释:
过程:
第一、注解类:@interfaceA{}
第二、应用了“注释类”的类:@Aclass B{}
第三、对“应用注释类的类”进行反射操作的类:class{...},操作如下:
B.class.isAnnotionPresent(A.class);//判断是否存在此注解类
A a = B.class.getAnnotation(a.class);//存在的话则得到这个注释类的对象

代码演示:
@Retention(RetentionPolicy.RUNTIME)    
@Target({ElementType.TYPE,ElementType.METHOD})    
public @interface ItcastAnnotation {}    
    
@ItcastAnnotation()    
public class AnnotionTest {    
    @SuppressWarnings("deprecation")//表示压制警告的注解    
    @ItcastAnnotation()    
    public static void main(String[] args) {    
        System.runFinalizersOnExit(true);    
        //反射方式查看注解    
        //检查类上是否有注解    
        if(AnnotionTest.class.isAnnotationPresent(ItcastAnnotation.class)){    
            //通过反射获取到注解    
            ItcastAnnotation annotation = AnnotionTest.class.getAnnotation(ItcastAnnotation.class);    
            System.out.println(annotation);    
        }    
    }   


为注解增加基本属性
1.属性:一个注解相当于一个胸牌,但仅通过胸牌还不足以区别带胸牌的两个人,这时就需要给胸牌增加一个属性来区分,如颜色等。
2.定义格式:同接口中的方法一样:String color();定义缺省格式:Stringvalue() default ”ignal”;
3.应用:直接在注解的括号中添加自身的属性,如:
@ItcastAnnotation(color=”red”)
这个和上面的@SuppressWarnings("deprecation")是一样的,其中的"deprecation"就是属性值
1)当只有一个属性时,可直接传入属性值。如”red”
2)当含有其他属性值的时候,如果那个属性值是缺省的(default),也可以直接传入这个属性值。


为注解增加高级属性
1.可以为注解增加的高级属性的返回值类型有:
1)八种基本数据类型   2)String类型  3)Class类型
4)枚举类型   5)注解类型   6)前五种类型的数组
2.数组类型的属性:
定义:int[]arrayArr() default {1,2,3};     -->可不定义默认值
应用:@MyAnnotation(arrayArr={2,3,4})  --> 可重新赋值
注:若数组属性中只有一个元素(或重新赋值为一个元素),这时属性值部分可省略大括号。
3.枚举类型的属性:
  假设定义了一个枚举类TraffLamp,它是EnumTest的内部类,其值是交通灯的三色。
定义:EnumTest.TrafficLamplamp();
应用:@MyAnnotation(lamp=EnumTestTrafficLamp.GREEN)
4.注解类型的属性:
  假定有个注解类:MetaAnnotation,其中定义了一个属性:String value()
定义:MetaAnnotationannotation() default @MetaAnnotation(”xxx”);
应用:@MyAnnotation(annotation=@MetaAnnotation(”yyy”))  --> 可重新赋值
可认为上面的@MetaAnnotation是MyAnnotation类的一个实例对象,同样可以认为上面的@MetaAnnotation是MetaAnnotation类的一个实例对象,调用:
MetaAnnotation ma =MyAnnotation.annotation();
System.out.println(ma.value());
5.Class类型的属性:
定义:Class cls();
应用:@MyAnnotation(cls=ItcastAnnotion.class)
注:这里的.class必须是已定义的类,或是已有的字节码对象
6.基本数据类型的属性(以int为例):
定义:int val()default 3;     -->可不定义默认值
应用:@MyAnnotation(val=7)  --> 可重新赋值

代码演示:
//将定义的注解的生命周期设置在运行时期    
@Retention(RetentionPolicy.RUNTIME)    
//定义注解的放置位置    
@Target({ElementType.TYPE,ElementType.METHOD})    
//自定义注解    
public @interface ItcastAnnotation {    
    //定义属性    
    String str();    
    int val() default 1;    
    int[] arr() default {2,3,4};    
    Class cls() default AnnotionTest.class;    
    EnumText.TrafficLamp lamp() default EnumText.TrafficLamp.YELLOW;    
    MetaAnnotation annotation() default @MetaAnnotation("sss");    
}    
    
//测试注解类,用反射查看其属性    
package cn.itcast.text2;    
import cn.itcast.text1.EnumText;    
@ItcastAnnotation(annotation=@MetaAnnotation("anntation"),    
                Lamp=EnumText.TrafficLamp.RED,    
                arr=7,val=5,str="String",    
                cls=ItcastAnnotation.class)    
public class AnnotionTest {    
    @SuppressWarnings("deprecation")//表示压制警告的注解    
    @ItcastAnnotation(str = "yyy")//有缺省值可不用写缺省部分    
    public static void main(String[] args) {    
        //反射方式查看注解    
        //检查类上是否有注解    
        if(AnnotionTest.class.isAnnotationPresent(ItcastAnnotation.class)){    
            //通过反射获取到注解    
            ItcastAnnotation annotation = AnnotionTest.class.getAnnotation(ItcastAnnotation.class);    
            //打印查看属性值    
            System.out.println(annotation);    
            System.out.println(annotation.str());    
            System.out.println(annotation.val());    
            System.out.println(annotation.arr().length);    
            System.out.println(annotation.cls().getName());    
            System.out.println(annotation.lamp().nextLamp());    
            System.out.println(annotation.annotation().value());    
        }    
    }    
}    
    
//定义枚举类,交通灯    
package cn.itcast.text1;    
public class EnumText {    
    public static void main(String[] args) {}    
    //定义交通灯    
    public enum TrafficLamp{    
        //定义3个元素,即此类的子类,覆写抽象方法    
        RED(30){    
            @Override    
            public TrafficLamp nextLamp() {return GREEN;}},    
        GREEN(45){    
            @Override    
            public TrafficLamp nextLamp() {return YELLOW;}},    
        YELLOW(5) {    
            @Override    
            public TrafficLamp nextLamp() {return RED;}};    
        private int time;    
        //构造方法    
        private TrafficLamp(int time){this.time = time;}    
        //抽象方法,转为下个灯    
        public abstract TrafficLamp nextLamp();    
    }    
}   


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值