Java 泛型通配符?解惑

Java|Java 中 JSONPath 的使用 JSONPath 是一种用于从 JSON 数据结构中提取特定元素的查询语言,类似于 XPath 用于 XML。它提供了强大的查询功能,允许用户通过简单的表达式获取 JSON 文档中的特定数据。本文将介绍 JSONPath 的基本语法,并提供在 Java 中如何使用 JSONPath 的示例代码。JSONPath 提供了一种简便且强大的方式来查询和处理 JSON 数据。它的语法简单但功能强大,适合用于各种场景下的 JSON 数据提取。 阅读详情

转自:http://www.linuxidc.com/Linux/2013-10/90928.htm

T  有类型

?  未知类型

一、通配符的上界

既然知道List<Cat>并不是List<Anilmal>的子类型,那就需要去寻找替他解决的办法, 是AnimalTrianer.act()方法变得更为通用(既可以接受List<Animal>类型,也可以接受List<Cat>等参数)。在Java里解决办法就是使用通配符“?”,具体到AnimalTrianer,就是将方法改为act(List<? extends Animal> list),当中“?”就是通配符,而“? extends Animal”则表示通配符“?”的上界为Animal,换句话说就是,“? extends Animal”可以代表Animal或其子类,可代表不了Animal的父类(如Object),因为通配符的上界是Animal。如下,为改进之后的AnimalTrianer

public class AnimalTrainer {
	public void act(List<? extends Animal> list) {
		for (Animal animal : list) {
			animal.eat();
		}
	}
}

再来测试一下,如下,发现Test 2 可以通过编译了:

public class TestAnimal {
	public static void main(String[] args) {
		AnimalTrainer animalTrainer = new AnimalTrainer();
		//Test 1
		List<Animal> animalList = new ArrayList<>();
		animalList.add(new Cat("cat1"));
		animalList.add(new Bird("bird1"));
		
		animalTrainer.act(animalList);	//可以通过编译
		
		//Test 2
		List<Cat> catList = new ArrayList<>();
		catList.add(new Cat("cat2"));
		catList.add(new Cat("cat3"));
		
		animalTrainer.act(catList);		//也可以通过编译
	}
}

经过上述分析,可以知道List<Animal>和List<Cat>都是List<? extends Animal>的子类型,类似有List<Bird>,List<Magpie>也是List<? extends Animal>的子类型。

现总结如下,对于通配符的上界,有以下几条基本规则:(假设给定的泛型类型为G,(如List<E>中的List),两个具体的泛型参数X、Y,当中Y是X的子类(如上的Animal和Cat))

  • G<? extends Y> 是 G<? extends X>的子类型(如List<? extends Cat> 是 List<? extends Animal>的子类型)。
  • G<X> 是 G<? extends X>的子类型(如List<Animal> 是 List<? extends Animal>的子类型)
  • G<?> 与 G<? extends Object>等同,如List<?> 与List<? extends Objext>等同。

 

学到这里,可能会遇到一些疑惑的地方,或者说事理解不透的地方,先观察如下两段代码片段,判断一下其是否可行??

public void testAdd(List<? extends Animal> list){
		//....其他逻辑
		list.add(new Animal("animal"));
		list.add(new Bird("bird"));
		list.add(new Cat("cat"));
	}
 
List<? extends Animal> list = new ArrayList<>();
list.add(new Animal("animal"));
list.add(new Bird("bird"));
list.add(new Cat("cat"));

先分析如下:因为“? extends Animal”可代表Animal或其子类(Bird,Cat),那上面的操作应该是可行的。事实上是”不行“,即无法通过编译。为什么呢??

在解释之前,再来重新强调一下已经知道的规则:在List<Aimal> list里只能添加Animal类对象及其子类对象(如Cat和Bird对象),在List<Bird>里只能添加Bird类和其子类对象(如Magpie),可不能添加Animal对象(不是Bird的子类),类似的在List<Cat>和List<Magpie>里只能添加Cat和Bird对象(或其子类对象,不过这没有列出)。现在再回头看一下testAdd()方法,我们知道List<Animal>、List<Cat等都是List<? extends Animal>的子类型。先假设传入的参数为为List<Animal>,则第一段代码的三个“add”操作都是可行的;可如果是List<Bird>呢??则只有第二个“add”可以执行;再假设传入的是List<Tiger>(Tiger是想象出来的,可认为是Cat的子类),则三个“add”操作都不能执行。

现在反过来说,给testAdd传入不同的参数,三个“add”操作都可能引发类型不兼容问题,而传入的参数是未知的,所以java为了保护其类型一致,禁止向List<? extends Animal>添加任意对象,不过却可以添加null,即list.add(null)是可行的。有了上面谈到的基础,再来理解第二段代码就不难了,因为List<? extends Animal>的类型“? extends Animal”无法确定,可以是Animal,Bird或者Cat等,所以为了保护其类型的一致性,也是不能往list添加任意对象的,不过却可以添加null。

先总结如下:不能往List<? extends Animal> 添加任意对象,除了null。

另外提醒大家注意的一点是,在List<? extends Animal> 可以是Animal类对象或Bird对象等(只是某一类对象),反过来说,在List<? extends Animal> list里的都是Animal对象,即Bird也是Animal对象,Cat也是Animal对象(用java的语言来说就是子类可以指向父类,父类却不能指向子类),那么在Animal里的所有方法都是可以调用的,如下:

for (Animal animal : list) { animal.eat(); }


二、通配符的下界

既然有了通配符的上界,自然有着通配符的下界。可以如此定义通配符的下界 List<? super Bird>,其中”Bird“就是通配符的下界。注意:不能同时声明泛型通配符申明上界和下界。

在谈注意细节之前,我们先看一下通配符的使用规则——对于通配符的上界,有以下几条基本规则:(假设给定的泛型类型为G,(如List<E>中的List),两个具体的泛型参数X、Y,当中Y是X的子类(如上的Animal和Cat))

  • G<? super X> 是 G<? super Y>的子类型(如List<? super Animal> 是 List<? super Bird>的子类型)。
  • G<X> 是 G<? super X>的子类型(如List<Animal> 是 List<? super Animal>的子类型)

现在再来看如下代码,判断其是否符合逻辑:

public void testAdd(List<? super Bird> list){
		list.add(new Bird("bird"));
		list.add(new Magpie("magpie"));
	}
List<? super Bird> list = new ArrayList<>();
list.add(new Bird("bird"));
list.add(new Magpie("magpie"));
list.add(new Animal("animal"));

看第一段代码,其分析如下,因为”? super Bird”代表了Bird或其父类,而Magpie是Bird的子类,所以上诉代码不可通过编译。而事实上是”行“,为什么呢?2?

在解疑之前,再来强调一个知识点,子类可以指向父类,即Bird也是Animal对象。现在考虑传入到testAdd()的所有可能的参数,可以是List<Bird>,List<Animal>,或者List<Objext>等等,发现这些参数的类型是Bird或其父类,那我们可以这样看,把bird、magpie看成Bird对象,也可以将bird、magpie看成Animal对象,类似的可看成Object对象,最后发现这些添加到List<? supe Bird> list里的对象都是同一类对象(如本文刚开篇提到的Test 1),因此testAdd方法是符合逻辑,可以通过编译的。:

现在再来看一下第二段代码对于,第二、三行代码的解释和上文一样,至于最后一行“list.add(new Animal("animal"))”是无法通过编译的,为什么的??为了保护类型的一致性,因为“? super Bird”可以是Animal,也可以是Object或其他Bird的父类,因无法确定其类型,也就不能往List<? super Bird>添加Bird的任意父类对象。

既然无法确定其父类对象,那该如何遍历List<? super Bird> ? 因为Object是所有类的根类,所以可以用Object来遍历。如下,不过貌似其意义不大。

for (Object object : list) {//...}

那“? super BoundingType”可以应用在什么地方呢??“? super BoundingType”应用相对广泛,只不过是混合着用。下面举个简单的例子。先假设有以下两个Student和CollegeStudent,当中CollegeStudent继承Student,如下:

public class Student implements Comparable<Student>{
	private int id;

	public Student(int id) {
		this.id = id;
	}

	@Override
	public int compareTo(Student o) {
		return (id > o.id) ? 1 : ((id < o.id) ? -1 : 0);
	}
}
 
public class CollegeStudent extends Student{
	public CollegeStudent(int id) {
		super(id);
	}
}


先需要根据他们的id对他们进行排序(注意此处是对数组对象进行排序),设计方法如下,(n指数组元素的个数):

public static <T extends Comparable<? super T>> 
			void selectionSort(T[] a,int n)

先理解此方法含义,首先<T extends Comparable<T>>规定了数组中对象必须实现Comparable接口,Comparable<? Super T>表示如果父类实现Comparable接口,其自身可不实现,如CollegeStudent。先假设有一个CollegeStudent的数组,如下:

CollegeStudent[] stu = new CollegeStudent[]{
   new CollegeStudent(3),new CollegeStudent(2),
   new CollegeStudent(5),new CollegeStudent(4)};


执行方法 selectionSort(stu,4)是完全可以通过的。可如果定义的selectionSort方法如下:

public static <T extends Comparable<T>> 
			void selectionSort(T[] a,int n)

则方法selectionSort(stu,4)不能执行,因为CollegeStudent没有实现Comparable<CollegeStudent>接口。换句话就是“? super T”使selectionSort方法变得更为通用了。selectionSort完整代码的实现可参考本文的末尾。

三、无界通配符

知道了通配符的上界和下界,其实也等同于知道了无界通配符,不加任何修饰即可,单独一个“?”。如List<?>,“?”可以代表任意类型,“任意”也就是未知类型。无界通配符通常会用在下面两种情况:

1、当方法是使用原始的Object类型作为参数时,如下:

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + "");
    System.out.println();
}

可以选择改为如下实现:

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + "");
    System.out.println();
}

这样就可以兼容更多的输出,而不单纯是List<Object>,如下:

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);

 

2、在定义的方法体的业务逻辑与泛型类型无关,如List.size,List.cleat。实际上,最常用的就是Class<?>,因为Class<T>并没有依赖于T。

最后提醒一下的就是,List<Object>与List<?>并不等同,List<Object>是List<?>的子类。还有不能往List<?> list里添加任意对象,除了null。

 

 

附录:selectionSort的代码实现:(如果需要实现比较好的输出,最好重写Student的toString方法)

public class SortArray {
	
	//对一组数组对象运用插入排序,n指数组元素的个数
	public static <T extends Comparable<? super T>> 
					void selectionSort(T[] a,int n) {
		for (int index = 0; index < n-1; index++) {
			int indexOfSmallest = getIndexOfSmallest(a,index,n-1);
			swap(a,index,indexOfSmallest);
		}
	}
	
	public static <T extends Comparable<? super T>> int getIndexOfSmallest(T[] a, int first, int last) {
		T minValue = a[first]; // 假设第一个为minValue
		int indexOfMin = first; // 取得minValue的下标
		for (int index = first + 1; index <= last; index++) {
			if (a[index].compareTo(minValue) < 0) {
				minValue = a[index];
				indexOfMin = index;
			}
		}

		return indexOfMin;
	}
	
	public static void swap(Object[] a,int first,int second) {
		Object temp = a[first];
		a[first] = a[second];
		a[second] = temp;
	}
	
	public static void main(String[] args) {
		CollegeStudent[] stu = new CollegeStudent[]{
				new CollegeStudent(3),
				new CollegeStudent(2),
				new CollegeStudent(5),
				new CollegeStudent(4)};
		selectionSort(stu, 4);
		for (Student student : stu) {
			System.out.println(student);
		}
	}
}
Java 泛型中的通配符详解 可以使用上界通配符来放宽对变量的限制。例如,你想编写一个方法,该方法适用于List, List 和 List;就可以通过使用上界通配符来实现这一点。声明一个上界通配符,需要使用通配符 ('?'),跟上 extends 关键字,然后再跟它的上界。比如,编写用于 Number 列表和 Number 子类型(如 Integer、Double 和 Float)的方法,可以指定 List 阅读详情

相关推荐

Java泛型&通配符

Java 泛型(Generics)是 JDK 5 引入的一个新特性,它提供了编译时类型安全检测机制,允许在编译时检测出非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。泛型擦除是指在编译过程中,Java 编译器会将泛型类型信息擦除,将泛型类型转换为原始类型(Raw Type),也就是去掉泛型参数的类型信息,使得在运行时无法获取泛型的实际类型参数。例如,编译器会在编译期间动态地将泛型 T 擦除为 Object 或将 T extends xxx 擦除为其限定的类型 xxx。

心有谦谦结 1052

【JAVA】JsonPath

1. 用途 JAVA用来解析JSON字符串 2.依赖 <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>2.4.0</version> ...

屎蛋的铲屎官 873

Java 泛型使用通配符

ArrayList<?泛型,JDK1.5新加入的,解决数据类型的安全性问题,其主要原理是在类声明时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。使用泛型时,泛型类型必须为引用数据类型,不能为基本数据类型,Java中的普通方法,构造方法,静态方法中都可以使用泛型,方法使用泛型之前必须先对泛型进行声明,可以使用任意字母,一般都要大写。在引入范型之前,要在类中的方法支持多个数据类型,就需要对方法进行重载,在引入范型后,可以解决此问题(多态),更进一步可以定义多个参数以及返回值之间的关系。

usertest1的博客 1066

Java对JSON路径解析JsonPath例子

Java json 路径解析, JSONPATH

yinggenan的博客 4134

三级等保网络拓扑图实战:从零搭建符合标准的医院安全架构

本文详细阐述了如何从零开始设计并搭建符合三级等保标准的医院网络安全架构。文章基于“一个中心,三重防护”的核心框架,深入解析了医院网络分区设计、安全域划分、关键安全设备选型与配置等实战要点,并提供了绘制专业网络拓扑图的技巧与等保测评准备指南,旨在帮助医院IT人员系统化地构建既合规又实用的纵深防御体系。

weixin_29326909的博客 503

STM32 HAL库下DHT22高精度温湿度采集与30秒定时实现

单总线传感器(如DHT22)是嵌入式环境监测系统的核心数据源,其通信依赖微秒级精确时序控制。在STM32平台中,需结合硬件定时器(如TIM2)构建稳定周期触发机制,并通过GPIO+精确延时(非HAL_Delay)完成协议解析。该方案突破整数读取局限,完整还原带一位小数的温度与湿度值,显著提升工业级数据精度。典型应用于物联网终端、智能农业节点及边缘侧环境监控系统,为MQTT/CoAP等协议上报提供结构化原始数据支撑。本实践基于STM32F103与HAL库,不依赖第三方驱动,具备强可移植性与工程鲁棒性。

weixin_42453228的博客 513

JAVA 泛型通配符extends

在java泛型中,? 表示通配符,代表未知类型,子类型限定< ? extends Object>表示上界通配符(Upper Bounds Wildcards),超类型限定< ? super Object>表示下界通配符。 通配符 与 T 的区别: T:作用于模板上,用于将数据类型进行参数化,不能用于实例化对象。 ?:在实例化对象的时候,不确定泛型参数的具体类型时,可以使用通配符进行对象定义。 < T > 等同于 < T extends Object>.

u012289399的博客 2408

ICS v9.5 OverbyteIcsWSocket.pas 整体架构分析

核心架构:OverbyteIcsWSocket.pas 是 Internet Component Suite (ICS) 的基础网络组件单元,提供跨平台 Winsock API 封装,支持TCP/UDP 客户端/服务器,DNS 解析和Raw Socket。在 v9.5 中,SSL/TLS 代码已分离至 OverbyteIcsSslBase.pas 等单元,使核心单元更精简,专注于非加密 socket 操作。

sonadorje的专栏 992

Java泛型 通配符详解

对于<? super 类型>,编译器将只允许写操作,不允许读操作。即只可以设值(比如set操作),不可以取值(比如get操作)。 对于<? extends 类型>,编译器将只允许读操作,不允许写操作。即只可以取值,不可以设值。 以上两点都是针对于源码里涉及到了类型参数的函数而言的。比如对于List而言,不允许的写操作有add函数,因为它的函数签名是boolean add(E e);,此时这个形参E就变成了一个涉及了通配符的类型;而不允许的读操作有get函数,因为它的函数签名是E get(int index)

anlian523的博客 1万+

用python画带有正负值的条形图

常见的条形图只能表现数据的大小,却不能表现值的正负关系。但是其实仅仅使用`pandas`和`matplotlib`两个包就可以实现y轴负轴的添加以及负值的显示,并且可以为正负值分别设置不同的颜色,让条形图看上去更加清晰,易于我们分析

weixin_44853840的博客 5382

NTN(三) Timing

这篇看下k_offset和k_mac,如38.300所述,k_offset是配置的调度偏移量,需要大于或等于service link RTT和Common TA之和;k_mac 是配置的偏移量,需要大于或等于 RP 和 gNB 之间的 RTT。 相关参数会通过RRC层NTN-config配置,其中的cellSpecificKoffset-r17和kmac-r17主要用于各种Timing场景的偏移。 kmac:如果DL和UL frame timing 在 gNB端未对齐,网络端

asd199086的博客 4826

Java泛型通配符深度解析:无限定、上界、下界通配符全面掌握

泛型通配符如同一个无形的手,操控着程序的行进方向,让程序的逻辑更加严密,更加安全。泛型通配符,顾名思义,它是泛型的一种特殊形式,用于表示各种类型的泛型。你可能会问,为什么我们需要它呢?其实,当我们在开发过程中,经常会遇到一种情况,就是我们无法确定一个集合的具体类型,或者我们需要表示任何类型的情况,这时候,泛型通配符就派上了用场。它的出现,不仅让我们的代码更加灵活,而且也让我们的程序更加健壮。在接下来的内容中,我们将会深入探讨泛型通配符的各种形式,包括无限定通配符、上界通配符和下界通配符。

万猫学社 3787

【Windows Server 2019】活动目录 (Active Directory) ——将计算机加入域和脱离域

博文介绍了如何将计算机加入AD域和脱离AD域,包括加入域,验证客户机加入到域后的信息,使用域账户用户在客户机上登录,使用本地账户用户在计算机上登录,脱离域。

VMware, Windows, Linux, Cisco, Container, Kubernetes, Kali 6169

Java泛型中通配符的使用详解

?extends T>确定了上界为T,那么获取值时类型一定是T或T的父类,但赋值时由于不能确定具体是T或者T的某个子类而赋值失败。

小鲁蛋的博客 8197

Allegro孔和线被做到器件里面如何解锁操作指导

在做PCB设计的时候,会遇到孔和线被做到器件的情况,当需要移动器件的时候,孔和线也会跟着移动。孔和线被做到器件里面如何解锁操作指导。

weixin_54787054的博客 1144

弹窗提示报错: F:\c\CFX5\bin\Debug\net9.0-windows10.0.26100.0\CFX5.dll 面向无法动态枚举工具箱项目的平台...如何解决?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 944

【Java基础】Java泛型和泛型通配符详解

Java泛型的详解,总结了泛型通配符所有的含义,以及用法,并且说明了各个用法之间的区别。

大脑不能为空 2744

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

在 Java 的泛型类型中使用通配符

Java 从版本5起开始引入泛型(generics)机制。我们知道,Java 的泛型类型如同java.lang.String,java.io.File 一样,属于普通的 Java 类型。比方说,下面两个变量的类型就是互不相同的: List listObj = new ArrayList(); List listStr = new ArrayList(); 虽然 String 是 Object

一个执着的程序员 1万+

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Java中泛型通配符的使用

一点睛 引入通配符可以在泛型实例化时更加灵活地控制,也可以在方法中控制方法的参数。 语法如下: 泛型类名<? extends T>或泛型类名<? super T>或泛型类名<?> ? extends T:表示T或T的子类 ? super T:表示T或T的父类 ?:表示可以是任意类型 二通配符在泛型类创建泛型对象中使用 1代码 ...

实践求真知 3439

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Java泛型(通配符)

Java泛型通配符 在了解通配符之前,如下知识点的理解对于为何设置通配符有着很好的解释作用。 协变: 不变: 逆变: 更加详细的解释可参考文章:《几个搞不太懂的术语:逆变、协变、不变》 ...

weixin_44298385的博客 4373

java泛型中通配符的使用详解

在java泛型中,通配符往往能提供给我们比不可变类型更多的灵活性。例如: public static void main(String[] args) { List&lt;Number&gt; list1 = new ArrayList&lt;Number&gt;(); List&lt;Long&gt; list2 = new ArrayList&lt;Long&gt;(); Lis...

zy丶秋水的博客 5037

Java泛型中的通配符详解

Java泛型系统通过通配符机制实现了类型约束与灵活性的平衡。无界通配符作为泛型系统的"未知类型"占位符,主要解决容器类的安全读取需求,其设计严格遵循"写入受限,读取为Object"的原则。上界通配符通过类型边界限定,在数学运算等场景中实现了子类兼容性,典型如数值计算时支持所有Number子类的混合运算。可变参数方法与泛型结合时,需特别注意类型擦除导致的堆污染问题。通过和注解可管理编译器警告,但核心在于确保方法内部不破坏类型一致性。

静水深流 1033

Java数据结构基础——泛型、通配符_java通配符和泛型

一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。只接受 Number 的子类型作为 E 的类型,实参没有指定类型边界 E,可以视为 E extends Object。实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值。使用Object数组的缺点有二,存放元素时可以存放任何类型的元素,再者,取出元素的时候需要手动强转。因此,Java的泛型机制是在编译级别实现的。注意:泛型时在编译的时候的一种机制,在运行时是没有泛型的概念的。

2401_89734329的博客 1405

Java泛型通配符

Java泛型是Java 5引入的一项功能,旨在提供强类型的集合处理。泛型通配符则是泛型中的一种特殊语法,允许在不具体指定类型的情况下进行更灵活的代码编写。

专业深耕,技术前沿 782

Java泛型通配符和泛型限定

类型通配符 类型通配符一般是使用?代替具体的类型实参(此处是类型实参,而不是类型形参)。当操作类型时不需要使用类型的具体功能时,只使用Object类中的功能,那么可以用 ? 通配符来表未知类型。例如 List&lt;?&gt; 在逻辑上是List&lt;String&gt;、List&lt;Integer&gt; 等所有List&lt;具体类型实参&gt;的父类。 public class ...

爪哇菌的博客 7710
上一篇: java Collection 个人记录和记忆
下一篇: java static import 静态导入
千雅爸爸
博客等级 码龄18年 292粉丝 639原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值