Java解惑

1.递归中的坑

之前在leetCode上刷题,问题传送门:Binary Tree Paths,按理说一个很简单的递归方法就能解决啊,为什么会出现问题呢?先po代码:

public class Solution {
public List<String> binaryTreePaths(TreeNode root) {
    List<String> answer = new ArrayList<String>();
    if (root != null) searchBT(root, "", answer);
    return answer;
}
private void searchBT(TreeNode root, String path, List<String> answer) {
     if (root.left == null && root.right == null) answer.add(path + root.val);
     if (root.left != null){
         path = path + root.val + "->";
         searchBT(root.left, path, answer);
     } 
     if (root.right != null){
         path = path + root.val + "->";
         searchBT(root.right, path, answer);
     } 
 }
}

上面的代码就是会出错,通过打log,发现path+="someString",虽然结果传递给searchBT方法没有问题,但是却产生了副作用:path的值也跟着改变了。这种错误为什么在平时的代码中没有遇到呢?我分析是因为,平常程序很少有递归调用,从而忽略了这个错误。

关于java的传值方式,我一直都没能搞清楚,请看第6部分内容!

改成这样就是想要的结果:

public class Solution {
 public List<String> binaryTreePaths(TreeNode root) {
    List<String> answer = new ArrayList<String>();
    if (root != null) searchBT(root, "", answer);
    return answer;
}
private void searchBT(TreeNode root, String path, List<String> answer) {
    if (root.left == null && root.right == null) answer.add(path + root.val);
    if (root.left != null) searchBT(root.left, path + root.val + "->", answer);
    if (root.right != null) searchBT(root.right, path + root.val + "->", answer);
}
}

问题解决!

2.Anonymous Classes

Anonymous classes enable you to make your code more concise. They enable you to declare and instantiate a class at the same time. They are like local classes except that they do not have a name. Use them if you need to use a local class only once.

Anonymous Classes的语法:an anonymous class is an expression.The syntax of an anonymous class expression is like the invocation(调用) of a constructor, except that there is a class definition contained in a block of code.

示例代码:

//其中HelloWorld是一个interface
HelloWorld frenchGreeting = new HelloWorld() {
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };

关于内部类和内部匿名类如何捕获变量,引用外部类的变量,一直以来感觉都不太好理解,一些注意和为什么请看权威R大的答案

3.Lambda Expressions

有了匿名内部类的概念,如果我们要实现的匿名类很简单,比如实现的接口只有一个方法,那么专门写一个匿名类就显得很笨重啊,这时候Lambda Expressions就闪亮登场了!

Lambda expressions let you express instances of single-method classes more compactly.匿名类一定程度简化了内部类,Lambda expressions一定程度上简化了匿名类。

一个例子:

//Use Aggregate Operations That Accept 
//Lambda Expressions as Parameters
roster
    .stream()
    .filter(
        p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25)
    .map(p -> p.getEmailAddress())
    .forEach(email -> System.out.println(email));

Aggregate operations process elements from a stream, not directly from a collection (which is the reason why the first method invoked in this example is stream).

Syntax of Lambda Expressions

组成部分:
1.A comma-separated list of formal parameters enclosed in parentheses.
2.The arrow token, ->
3.A body, which consists of a single expression or a statement block.

Note that a lambda expression looks a lot like a method declaration; you can consider lambda expressions as anonymous methods—methods without a name.

一个示例:

public class Calculator {

    interface IntegerMath {
        int operation(int a, int b);
    }

    public int operateBinary(int a, int b, IntegerMath op) {
        return op.operation(a, b);
    }

    public static void main(String... args) {

        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a - b;
        System.out.println("40 + 2 = " +
                myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " +
                myApp.operateBinary(20, 10, subtraction));
    }
}

The method operateBinary performs a mathematical operation on two integer operands. The operation itself is specified by an instance of IntegerMath. The example defines two operations with lambda expressions, addition and subtraction. The example prints the following:

40 + 2 = 42
20 - 10 = 10

4.Method References

Suppose that the members of your social networking application are contained in an array, and you want to sort the array by age. You could use the following code:

List<Person> roster=new ArrayList<>();
        Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

        class PersonAgeComparator implements Comparator<Person> {
            public int compare(Person a, Person b) {
                return a.getBirthday().compareTo(b.getBirthday());
            }
        }

        Arrays.sort(rosterAsArray, new PersonAgeComparator());

如何用Comparator来sort!

注意到sort方法:

static <T> void sort(T[] a, Comparator<? super T> c)

Notice that the interface Comparator is a functional interface. Therefore, you could use a lambda expression instead of defining and then creating a new instance of a class that implements Comparator:(对于该类interface可以简化之)

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

However, this method to compare the birth dates of two Person instances already exists as Person.compareByAge. You can invoke this method instead in the body of the lambda expression:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

Because this lambda expression invokes an existing method, you can use a method reference instead of a lambda expression:

Arrays.sort(rosterAsArray, Person::compareByAge);

这种写法跟上面代码完全等价。

Kinds of Method References

一共有四种:

1.Reference to a Static Method:
上面例子Person::compareByAge就是。
2.Reference to an Instance Method of a Particular Object:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }

    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

3.Reference to an Instance Method of an Arbitrary Object of a Particular Type:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

4.Reference to a Constructor:

5.Collections类知识汇总

官方资料相关:
The Collections Framework

1.Overview

2.Outline of the Collections Framework(API reference)

3.Tutorial

4.Design FAQ

一个一个来看:

1-overview

为什么要设计Collections framework呢?它主要有以下几种优点:

1.Reduces programming effort by providing data structures and algorithms so you don’t have to write them yourself.

2.Increases performance by providing high-performance implementations of data structures and algorithms. Because the various implementations of each interface are interchangeable(可相互交换的), programs can be tuned by switching implementations.

3.Provides interoperability(互用性) between unrelated APIs by establishing a common language to pass collections back and forth.

4.Reduces the effort required to learn APIs by requiring you to learn multiple ad hoc collection APIs.

5.Reduces the effort required to design and implement APIs by not requiring you to produce ad hoc collections APIs.

6.Fosters(培养) software reuse by providing a standard interface for collections and algorithms with which to manipulate them.

那么这个framework主要由哪些部件组成呢?组成部分:

1.Collection interfaces. Represent different types of collections, such as sets, lists, and maps. These interfaces form the basis of the framework.

2.General-purpose implementations. Primary implementations of the collection interfaces.通用

3.Legacy implementations. The collection classes from earlier releases, Vector and Hashtable, were retrofitted(翻新) to implement the collection interfaces.历史遗留实现,拿Hashtable为例,现在用的不多,更多的是使用HashTable。

4.Special-purpose implementations. Implementations designed for use in special situations. These implementations display nonstandard performance characteristics, usage restrictions, or behavior.专用

5.Concurrent implementations. Implementations designed for highly concurrent use.专门针对于多线程的实现

6.Wrapper implementations. Add functionality, such as synchronization, to other implementations.

7.Convenience implementations. High-performance “mini-implementations” of the collection interfaces.

8.Abstract implementations. Partial implementations of the collection interfaces to facilitate custom implementations.实现了Collection接口的一部分功能,Skeletal implementations of the collection interfaces to facilitate custom implementations.

9.Algorithms. Static methods that perform useful functions on collections, such as sorting a list.例如Collections.sort()

10.Infrastructure. Interfaces that provide essential support for the collection interfaces.

11.Array Utilities. Utility functions for arrays of primitive types and reference objects. Not, strictly speaking, a part of the collections framework, this feature was added to the Java platform at the same time as the collections framework and relies on some of the same infrastructure.

你是否想问,上述这些部分都是由java中哪些类或者接口组成的呢,有没有例子呢?答案在这里Outline of the Collections Framework 。这对我们理解Collections framework的构成很重要,一定要认真看该参考答案!

Adapter implementations
感觉很高级,Implementations that adapt one collections interface to another:

1.newSetFromMap(Map) - Creates a general-purpose Set implementation from a general-purpose Map implementation.
2.asLifoQueue(Deque) - Returns a view of a Deque as a Last In First Out (LIFO) Queue.

Collection Interfaces

分为两部分,第一部分(也是最基本的)是在java.util.Collection中,他有下列的继承者:

Set,SortedSet,NavigableSet,Queue,BlockingQueue,TranferQueue,Deque,BlockingDeque.

第二部分,是以java.util.Map为基础来实现的接口,并不是真正意义上的Collections。 但是,这些接口包含了collection-view的操作方式,也就是可以像操作Collections一样操作Map。Map包含下列继承者:

SortedMap,NavigableMap,ConcurrentMap,ConcurrentNavigableMap.

从源码中也能看出有这两部分:

//Collection
public interface Collection<E> extends Iterable<E> {}
//Set
public interface Set<E> extends Collection<E> {}
//List
public interface List<E> extends Collection<E> {}
//Map,单独的一个接口
public interface Map<K,V> {}

Collection接口大部分方法都表明了optional(可选择),意味着在具体实现的时候可以随意选择实现某些方法,如果调用没有实现的方法,将抛出运行时错误UnsupportedOperationException。

Collection Implementations

The general purpose implementations(具有通用目的的实现) are summarized in the following table:

官方总结,值得信赖!

关于general purpose implementations:1.他们都支持Collection interface中的方法(随意才叫通用嘛),2.对于存放在它们中的元素类型没有任何限制,3.它们都不是线程安全的,但是如果你想要它们线程安全的版本的话,Collections有一个静态工厂方法synchronizedCollection,可以改变它们为线程安全的,具体用法还有一些额外要求,请参考synchronization wrappers

The AbstractCollection, AbstractSet, AbstractList, AbstractSequentialList and AbstractMap classes provide basic implementations of the core collection interfaces, to minimize the effort required to implement them. The API documentation for these classes describes precisely how each method is implemented so the implementer knows which methods must be overridden, given the performance of the basic operations of a specific implementation.

我想这些Abstract类的作用是方便我们自定义Collection

Concurrent Collections

多线程interface:

BlockingQueue,TransferQueue,BlockingDeque,ConcurrentMap,ConcurrentNavigableMap.

实现了的多线程的类:
LinkedBlockingQueue,ArrayBlockingQueue,PriorityBlockingQueue,DelayQueue,SynchronousQueue,LinkedBlockingDeque,LinkedTransferQueue,CopyOnWriteArrayList,CopyOnWriteArraySet,ConcurrentSkipListSet,ConcurrentHashMap,ConcurrentSkipListMap.

Algorithms

The Collections class contains these useful static methods.Collections类包含了很多有用的静态方法。

1.sort(List) - Sorts a list using a merge sort algorithm, which provides average case performance comparable to a high quality quicksort, guaranteed O(n*log n) performance (unlike quicksort), and stability (unlike quicksort). A stable sort is one that does not reorder equal elements.

2.binarySearch(List, Object) - Searches for an element in an ordered list using the binary search algorithm.

3.reverse(List) - Reverses the order of the elements in a list.

4.shuffle(List) - Randomly changes the order of the elements in a list.

5.fill(List, Object) - Overwrites every element in a list with the specified value.

6.copy(List dest, List src) - Copies the source list into the destination list.

7.min(Collection) - Returns the minimum element in a collection.

8.max(Collection) - Returns the maximum element in a collection.

9.rotate(List list, int distance) - Rotates all of the elements in the list by the specified distance.

10.replaceAll(List list, Object oldVal, Object newVal) - Replaces all occurrences of one specified value with another.

11.indexOfSubList(List source, List target) - Returns the index of the first sublist of source that is equal to target.

12.lastIndexOfSubList(List source, List target) - Returns the index of the last sublist of source that is equal to target.

13.swap(List, int, int) - Swaps the elements at the specified positions in the specified list.

14.frequency(Collection, Object) - Counts the number of times the specified element occurs in the specified collection.

15.disjoint(Collection, Collection) - Determines whether two collections are disjoint, in other words, whether they contain no elements in common.

16.addAll(Collection< super T>, T…) - Adds all of the elements in the specified array to the specified collection.

关于Comparable和Comparator

1.Comparable - Imparts a natural ordering to classes that implement it. The natural ordering can be used to sort a list or maintain order in a sorted set or map. Many classes were retrofitted to implement this interface.

2.Comparator - Represents an order relation, which can be used to sort a list or maintain order in a sorted set or map. Can override a type’s natural ordering or order objects of a type that does not implement the Comparable interface.作用有二:1.可以改变一个type的自然属性2.对于没有实现Comparable接口的type,可以让他们可以被比较,从而有顺序。

两者比较

In short, there isn’t much difference. They are both ends to similar means. In general implement comparable for natural order, (natural order definition is obviously open to interpretation), and write a comparator for other sorting or comparison needs. 一个类实现了Comparable接口的话,那么定义的肯定是它的最常用默认的排序方式嘛,因为这样将顺序已经写死了。而Comparator定义的顺序并不影响该类的自然顺序!

参考:Java : Comparable vs Comparator

Runtime exceptions

1.UnsupportedOperationException - Thrown by collections if an unsupported optional operation is called.

2.ConcurrentModificationException - Thrown by iterators and list iterators if the backing collection is changed unexpectedly while the iteration is in progress. Also thrown by sublist views of lists if the backing list is changed unexpectedly.

3-Tutorial

1.Lesson: Introduction to Collections

2.Lesson: Interfaces
链接,针对每个interface写的都还行,推荐看看。interfaces总结

3.Lesson: Aggregate Operations

要明白本节的内容,首先需要了解Lambda ExpressionsMethod References。前面已经讲过。

For what do you use collections? You don’t simply store objects in a collection and leave them there. In most cases, you use collections to retrieve items stored in them.

常规写法:

for (Person p : roster) {
    System.out.println(p.getName());
}

用aggregate operation的写法:

roster
    .stream()
    .forEach(e -> System.out.println(e.getName());

Pipelines and Streams

A pipeline is a sequence of aggregate operations.

A pipeline contains the following components:

1.A source: This could be a collection, an array, a generator function, or an I/O channel. In this example, the source is the collection roster.

2.Zero or more intermediate operations(中间操作). An intermediate operation, such as filter, produces a new stream.

A stream is a sequence of elements.

3.A terminal operation(最终操作). A terminal operation, such as forEach, produces a non-stream result, such as a primitive value (like a double value), a collection, or in the case of forEach, no value at all. In this example, the parameter of the forEach operation is the lambda expression e -> System.out.println(e.getName()), which invokes the method getName on the object e. (The Java runtime and compiler infer that the type of the object e is Person.)

示例:

The following example calculates the average age of all male members contained in the collection roster with a pipeline that consists of the aggregate operations filter, mapToInt, and average:

double average = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();

Differences Between Aggregate Operations and Iterators

Aggregate operations, like forEach, appear to be like iterators. However, they have several fundamental differences:

1.They use internal iteration
2.They process elements from a stream
3.They support behavior as parameters

更多的关于Aggregate Operations的内容:ReductionParallelism 。这应该是java自身的并行计算框架啊,跟Spark,Hadoop在并行计算方面的思想很相同。

4.Implementations

Summary of Implementations实现总结):

The Java Collections Framework provides several general-purpose implementations of the core interfaces:

1.For the Set interface, HashSet is the most commonly used implementation.
2.For the List interface, ArrayList is the most commonly used implementation.
3.For the Map interface, HashMap is the most commonly used implementation.
4.For the Queue interface, LinkedList is the most commonly used implementation.
5.For the Deque interface, ArrayDeque is the most commonly used implementation.

5.Lesson: Algorithms

它们都在Collections类中作为静态方法存在,大多数算法用在List上,还有一些用于任意的Collection实例上。

1.Sorting

The sort operation uses a slightly optimized merge sort algorithm that is fast and stable:

Fast: It is guaranteed to run in n log(n) time and runs substantially faster on nearly sorted lists. Empirical tests showed it to be as fast as a highly optimized quicksort. A quicksort is generally considered to be faster than a merge sort but isn’t stable and doesn’t guarantee n log(n) performance.(经过改善的merge sort具有stable,还能保证速度,速度方面跟quicksort差不多)

Stable: It doesn’t reorder equal elements. This is important if you sort the same list repeatedly on different attributes. If a user of a mail program sorts the inbox by mailing date and then sorts it by sender, the user naturally expects that the now-contiguous list of messages from a given sender will (still) be sorted by mailing date. This is guaranteed only if the second sort was stable.

2.Shuffling

shuffle

//swap
public static void swap(List<E> a, int i, int j) {
    E tmp = a.get(i);
    a.set(i, a.get(j));
    a.set(j, tmp);
}
//shuffle
public static void shuffle(List<?> list, Random rnd) {
    for (int i = list.size(); i > 1; i--)
        swap(list, i - 1, rnd.nextInt(i));
}

3.Routine Data Manipulation

manipulation:reverse,fill,copy,swap,addAll。

4.Searching

The binarySearch algorithm searches for a specified element in a sorted List.两种方式调用之,一种是直接传List和search key,但这需要保证List按照自然顺序升序排列,另一种是再多传一个Comparator,在search之前,先按Comparator顺序排序。

5.Composition

composition:frequency,disjoint。

6.Lesson: Custom Collection Implementations

如果要自定义的话,这无疑是最好的官方指导

It is fairly easy to do this with the aid of the abstract implementations provided by the Java platform.

接下来我们来实现下Arrays.asList吧:

public static List<T> asList(T[] a) {
    return new MyArrayList<T>(a);
}

private static class MyArrayList<T> extends AbstractList<T> {

    private final T[] a;

    MyArrayList(T[] array) {
        a = array;
    }

    public T get(int index) {
        return a[index];
    }

    public T set(int index, T element) {
        T oldValue = a[index];
        a[index] = element;
        return oldValue;
    }

    public int size() {
        return a.length;
    }
}

4-Java Collections API Design FAQ

Q1:Won’t programmers have to surround any code that calls optional operations with a try-catch clause in case they throw an UnsupportedOperationException?

It was never our intention that programs should catch these exceptions: that’s why they’re unchecked (runtime) exceptions. They should only arise as a result of programming errors, in which case, your program will halt due to the uncaught exception.

Q2:Why don’t you provide an Iterator.add method?

The semantics are unclear, given that the contract for Iterator makes no guarantees about the order of iteration. Note, however, that ListIterator does provide an add operation, as it does guarantee the order of the iteration.

Q3:Why doesn’t Map extend Collection?

This was by design. We feel that mappings are not collections and collections are not mappings. Thus, it makes little sense for Map to extend the Collection interface (or vice versa).

If a Map is a Collection, what are the elements? The only reasonable answer is “Key-value pairs”, but this provides a very limited (and not particularly useful) Map abstraction. You can’t ask what value a given key maps to, nor can you delete the entry for a given key without knowing what value it maps to.

Collection could be made to extend Map, but this raises the question: what are the keys? There’s no really satisfactory answer, and forcing one leads to an unnatural interface.

Maps can be viewed as Collections (of keys, values, or pairs), and this fact is reflected in the three “Collection view operations” on Maps (keySet, entrySet, and values). While it is, in principle, possible to view a List as a Map mapping indices to elements, this has the nasty property that deleting an element from the List changes the Key associated with every element before the deleted element. That’s why we don’t have a map view operation on Lists.

6.java传值方式

参考:Is Java “pass-by-reference” or “pass-by-value”?

highest vote 1:

Java is always pass-by-value. Unfortunately, they decided to call pointers references, thus confusing newbies. Because those references are passed by value.(因为引用是通过值传递的)。

为了证明是值传递还是引用传递,请看下面的代码:

public static void main( String[] args ){
    Dog aDog = new Dog("Max");
    foo(aDog);

    if (aDog.getName().equals("Max")) { //true
        System.out.println( "Java passes by value." );

    } else if (aDog.getName().equals("Fifi")) {
        System.out.println( "Java passes by reference." );
    }
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true

    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

In this example aDog.getName() will still return “Max”. The value aDog within main is not overwritten in the function foo with the Dog “Fifi” as the object reference is passed by value. If it were passed by reference, then the aDog.getName() in main would return “Fifi” after the call to foo.(如果为引用传递,那么在call foo方法后,name会改变,但是并没有!可见为值传递)

再看下面代码:

Dog aDog = new Dog("Max");
foo(aDog);
aDog.getName().equals("Fifi"); // true
public void foo(Dog d) {
    d.getName().equals("Max"); // true
    d.setName("Fifi");
}

In the above example, FiFi is the dog’s name after call to foo(aDog). Any operations that foo performs on d are such that, for all practical purposes, they are performed on aDog itself (except d=new Dog(“Boxer”)).

highest vote 2:

你可以简单地认为java传值的方式是传递reference的内存地址(之所以这样不够准确是因为java传递的不是直接地址(direct address),但差不多)。比如:

Dog myDog = new Dog("Rover");
foo(myDog);

you’re essentially passing the address of the created Dog object to the foo method.

(I say essentially because Java pointers aren’t direct addresses, but it’s easiest to think of them that way)

Suppose the Dog object resides at memory address 42. This means we pass 42 to the method.

Java works exactly like C. You can assign a pointer, pass the pointer to a method, follow the pointer in the method and change the data that was pointed to. However, you cannot change where that pointer points(即改变pointer指向的地址).

In C++, Ada, Pascal and other languages that support pass-by-reference, you can actually change the variable that was passed.

highest vote 3:

我们通过图能更好地理解java的传值方式。先看下面代码:

public class Main{
     public static void main(String[] args){
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }
     public static void changeReference(Foo a){
          Foo b = new Foo("b");
          a = b;
     }
     public static void modifyReference(Foo c){
          c.setAttribute("c");
     }
}

下面按步骤来理解:

step1:
Declaring a reference named f of type Foo and assign it to a new object of type Foo with an attribute “f”.

Foo f = new Foo("f");

step2:
From the method side, a reference of type Foo with a name a is declared and it’s initially assigned to null.

public static void changeReference(Foo a)

step3:
As you call the method changeReference, the reference a will be assigned to the object which is passed as an argument.

changeReference(f);

step4:
Declaring a reference named b of type Foo and assign it to a new object of type Foo with an attribute “b”.

Foo b = new Foo("b");

step5:
a = b is re-assigning the reference a NOT f to the object whose its attribute is “b”.

step6:
As you call modifyReference(Foo c) method, a reference c is created and assigned to the object with attribute “f”.

step7:
c.setAttribute(“c”); will change the attribute of the object that reference c points to it, and it’s same object that reference f points to it.

hope you understand now how passing objects as arguments works in Java!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值