Java面试基础


这阵子在准备找寒假实习(java后台开发),因为自己是转专业的,基础不是很牢固,所以打算通过复习java常用的面试题来巩固。 下面是关于Java基础的基本面试题目,参考链接是 [ Java基础知识面试题(2020最新版)](https://thinkwon.blog.csdn.net/article/details/104390612) ,感谢这位博主的贡献。 全文25383字,全部都是自己一字一句打出来的,历时两天。

文章目录

1. Java概述

1.1 何为编程

让计算机掘金某个问题,使用某种程序设计语言编写程序代码,并最终得到结果的过程。

1.1.1 什么是Java

  • Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++难以理解的多继承、指针等概念
  • 作为静态面向对象编程语言的代表,极好得实现了面向对象理论

1.1.2 jdk1.5之后的三大版本

  • Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
  • Java EE(Enterprise,企业版)
  • Java ME(Micro,微型版)

1.1.3 JVM、JRE和JDK的关系

  • JVM:Java虚拟机,Java程序运行在虚拟机上,不同平台有自己的处理机,因此Java可以跨平台
  • JRE:Java运行环境,包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包,包含运行java程序必不可少的系统类,如基本数据类型,基本数学函数,字符串处理,线程,异常类处理等,系统默认加载这个包
    如果只需要运行一个开发好的Java程序,只需要安装JRE
  • JDK:Java开发工具,提供给开发人员使用,包括Java开发工具(编译工具javac.exe;打包工具jar.exe)与JRE。
  • 关系图:
    在这里插入图片描述

1.1.4 什么是跨平台性?原理是什么

一次编译,可以在多个系统平台运行。
实现原理:Java程序通过Java虚拟机在系统平台运行,只要该系统可以安装想要java虚拟机,该系统就可以运行java程序。

1.1.5 Java语言有哪些特点

  • 简单易学
  • 面向对象(继承,封装,多态)
  • 平台无关性
  • 支持网络编程
  • 支持多线程
  • 健壮性
  • 安全性

1.1.6 什么是字节码?采用字节码的最大好处是什么

  • 定义:Java源程序经过虚拟机编译器编译后产生的文件(.class),只面向虚拟机,不面向特定处理器
  • 好处:相比于传统解释型语言,执行效率更高;跨平台性;
  • 概念:机器与编程程序之间存在虚拟机,编程程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。
  • 过程:Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。

1.1.7 什么是Java程序的主类?应用程序和小程序的主类有何不同?

  • 主类是指包含main()方法的类,主类是程序运行的入口点
  • 在小程序中,主类是一个继承自系统类的JApplet或Applet的子类。
  • 应用程序的主类不一定是public,但小程序的主类要求必须是public类

1.1.8 Java应用程序与小程序之间有那些差别?

程序是从主线程启动(main()),小程序没有main方法,主要嵌在浏览器页面上运行(调用init()线程或者run()方法启动)

1.1.9 Java和C++的区别

  • 都是面向对象的语言,都支持封装、继承、多态
  • Java不需要指针访问内存,程序内存更加安全
  • Java是类是单继承,接口可以多继承
  • Java有自动内存管理机制,不需要手动释放无用内存

1.1.10 Oracle JDK 和 OpenJDK 的对比(感觉有点偏)

2.基础语法

2.1 数据类型Java有哪些数据类型

  • 定义:Java语言是强语言类型,对于每一种数据都定义了明确具体的数据类型,在内存中分配了不同大小的内存空间
  • 分类:
  1. 基本数据类型
    1.1 数值型
    1.1.1 整数类型(byte,short,int,long)
    1.1.2 浮点类型(float,double)
    1.2 字符型(char)
    1.3 布尔型 (boolean)
  2. 引用数据类型
    2.1 类(class)
    2.2 接口(interface)
    2.3 数组([])
    在这里插入图片描述

2.1.1 switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上

  • Java5以前,只能是byte,short,int,char
  • Java5开始,加入枚举类型(enum)
  • Java7开始,加入String,但long是所有版本都不可以的

2.1.2 用最有效率的方法计算 2 乘以 8

左移2<<3

2.1.3 Math.round(11.5) 等于多少?Math.round(-11.5)等于多少

  • Math.round(11.5) = 12
  • Math.round(-11.5) = -11
  • 四舍五入的原理是在参数上加 0.5 然后进行下取整

2.1.4 float f=3.4;是否正确

不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;

2.1.5 short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗

  • 前者有错,1是int型,s+1是int型,因此需要强制转换类型:s1 = (short(s1 + 1)
  • 后者无错,含有隐含的强制转换

2.2 编码:Java语言采用何种编码方案?有何特点?

Java语言采用Unicode编码标准,Unicode(标准码),它为每一个字符制订看一个唯一的数值,因此在任何平台,程序都可以安心使用

2.3 注释:什么Java注释

  • 定义:用于解释说明程序的文字
  • 分类:
  1. 单行注释://
  2. 多行注释:/* */
  3. 文档注释:/** */

2.4 访问修饰符:访问修饰符 public,private,protected,以及不写(默认)时的区别

在这里插入图片描述 - 定义:可以被用于保护对类,变量,方法和构造方法的访问。

  • 分类:
  1. private:在同一类可见,使用对象:变量,方法,注意不能修饰类
  2. default(缺省,什么都不写):在同个包内可见,不使用任何修饰符。使用对象:类,接口,变量,方法
  3. protected:对同一包内的所有子类可见。使用对象:变量,方法,注意不能修饰类(外部类)
  4. public:对所有类可见,使用对象:类,接口,变量,方法

2.5 运算符:&和&&的区别

  • &:按位与或者逻辑与
  • &&:短路与运算,&&之所以称为短路运算,是因为如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算
  • 逻辑或|和短路||的区别也类似

2.6 关键字

2.6.1 Java 有没有 goto

goto是Java的保留字,但目前没有使用

2.6.2 final 有什么用?

  • 可以用于修饰类(不可被继承),方法(不可被重写),属性(不可被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,因此引用指向的内容可以被改变)

2.6.3 final finally finalize区别

  • final:可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值
  • finally:一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码
  • finalize:是Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,但我们调用System.gc()时,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断

2.6.4 this关键字的用法

  • this是自身的一个对象,代表对象本身,可以理解为指向对象本身的一个指针
  • 用法:
  1. 普通的直接引用,相当于当前对象本身
  2. 参与成员名字重名,用this来区分
  3. 引用本类的构造函数
class Person{
    private String name;
    private int age;
    
    public Person() {
    }
 
    public Person(String name) {
        this.name = name;
    }
    public Person(String name, int age) {
        this(name);
        this.age = age;
    }
}

2.6.5 super关键字的用法

  • 定义:可以理解为指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类
  • 用法:
  1. 普通的直接引用:与this类似,相当于当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员
  2. 子类中成员变量或者方法与父类的成员变量或方法同名,用super区分
class Person{
    protected String name;
 
    public Person(String name) {
        this.name = name;
    }
 
}
 
class Student extends Person{
    private String name;
 
    public Student(String name, String name1) {
        super(name);
        this.name = name1;
    }
 
    public void getInfo(){
        System.out.println(this.name);      //Child
        System.out.println(super.name);     //Father
    }
 
}

public class Test {
    public static void main(String[] args) {
       Student s1 = new Student("Father","Child");
       s1.getInfo();
 
    }
}
  1. 引用父类的构造函数
  • super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)
  • this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)

2.6.6 this与super的区别

  • super()在子类中调用父类构造方法,this()在本类内调用本类的其他构造方法
  • this和super不能出现在一个构造器内,因为this会调用其他构造函数,其他构造函数也会有super语句的存在,因为有相同的语句,编译器不会通过
  • this是一个指针,super是一个Java关键字.
  • 均需要放在构造方法内的第一行
  • 指的都是对象,均不可以在static环境中使用:方法,变量,语句块

2.6.7 static存在的主要意义

  • 意义:
  1. 创建独立于具体对象的域变量或者方法,以至于即使没有创建对象,也能使用属性和调用方法
  2. 形成静态代码以优化程序性能,类初次加载时,会按照static的顺序来执行每个static块,并且只会执行一次。因此将只需要进行一次的初始化操作都放在static代码块中进行

2.6.8 static的独特之处

  1. static修饰的变量或者方法独立于该类的任何对象,被类的实例对象共享
  2. 类第一次加载时,会加载static修饰的部分,并且只会在类第一次加载时进行初始化
  3. static变量在类加载时分配空间,以后创建类对象不会重新分配,赋值的话可以任意赋值
  4. 被static修饰的变量或者方法优于对象存在,即使没有对象,也可以去访问

2.6.9 static应用场景

  • 如果某个成员变量被所有对象所共享,那么这个成员变量就应该定义为静态变量
  1. 修饰成员变量
  2. 修饰成员方法
  3. 静态代码块
  4. 修饰类
  5. 静态导包

2.6.10 static注意事项

  1. 静态只能访问静态
  2. 非静态即可以访问非静态,也可以访问静态

2.7 流程控制语句

2.7.1 break ,continue ,return 的区别及作用

  • break:跳出上一层循环,结束当前循环
  • continue:跳出本次循环,继续执行下次循环
  • return:程序返回,不在执行下面代码

2.7.2 在 Java 中,如何跳出当前的多重嵌套循环

在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。

public static void main(String[] args) {
    ok:
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            System.out.println("i=" + i + ",j=" + j);
            if (j == 5) {
                break ok;
            }

        }
    }
}

3. 面向对象

3.1 面向对象概述:面向对象和面向过程的区别

  1. 面向对象:模型化的,只需要抽象出一个类,但其实面向对象的底层也是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了
  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活
  • 缺点:性能比面向过程低,因为类调用需要实例化,开销比较大,比较消耗资源
  1. 面向过程:具体化的,流程化的,一步一步分析,一步一步实现
  • 优点:性能比面向对象好,适用于单片机、嵌入式开发、Linux/Unix
  • 缺点:没有面向对象易维护、易复用、易扩展

3.2 面向对象三大特性

3.2.1 面向对象的特征有哪些方面

  • 抽象:将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,不关心行为的细节是什么。
  • 封装:把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被访问就不提供方法。隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性
  • 继承:使用已经存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或者方法,也可以只用父类的更,但不能选择性继承父类。提高复用性。
  1. 子类拥有父类非 private 的属性和方法
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展
  3. 子类可以用自己的方式实现父类的方法
  • 多态:指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在程序时不确定,而是在程序运行期间才能确定,即一个引用变量到底是哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。
  1. 两种形式实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口同一方法)
  2. 方法重载实现的是编译的多态性(前绑定),方法重写实现的是运行时的多态性(后绑定)
  3. 一个引用变量到底指向哪个类的实例对象,该引用变量发出的方法调用的到底是哪个类中实现的方法,必须程序运行期间才能决定。
  4. 实现多态必须做两件事:方法重写(子类基础父类并重写父类已有的或抽象的方法);对象造型(用父类应用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)

3.2.2 什么是多态机制?Java语言是如何实现多态的?

  • 定义:程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
  • 分类:编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
  • 实现:继承+重写+向上转型
  1. 继承:在多态中必须存在有继承关系的子类和父类
  2. 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法
  3. 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备调用父类的方法和子类的方法

3.2.3 面向对象五大基本原则是什么(可选)

  1. 单一职责原则SRP:类的功能单一,不能包罗万象。
  2. 开放封闭原则OCP:对于扩展开放,对于修改封闭。
  3. 里氏替换原则LSP:子类可以替换父类出现在父类出现的任何地方。
  4. 依赖倒置原则DIP:高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象;抽象不应该依赖于具体实现,具体实现应该依赖于抽象(就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。)
  5. 接口分离原则ISP:设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好

3.3 类与接口

3.3.1 抽象类和接口的对比

  • 定义:前者是捕捉子类的通用特性,后者是抽象方法的集合。从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
  • 相同点:
  1. 接口和抽象类都不能实例化
  2. 都位于继承的顶端,用于被其他实现或者继承
  3. 都包含抽象方法,其子类都必须覆写这些抽象方法
  • 不同点:
参数抽象类接口
声明使用abstract关键字声明使用interface关键字声明
实现使用extends继承抽象类,如果子类不是抽象类的话,需要提高抽象类中所有声明方法的实现使用implements实现接口,需要提供接口中所有声明的方法的实现
构造器可以有不能有
访问修饰符任意访问修饰符默认是public,不允许为private或者protected
多继承一个类最多继承一个抽象类一个类可以实现多个接口
字段声明任意默认是static和final
  • 备注:
    Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。
    所以现在可以为接口提供默认实现的方法,而不用强制子类来实现它。
  • 接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:
  1. 行为模型应该总是接口而不是抽象类定义,所以通常优先使用接口,尽量少用抽象类
  2. 抽象类的使用情况:需要定义子类的行为,又要为子类提供通用的功能

3.3.2 普通类和抽象类有哪些区别?

  • 普通类不能包含抽象方法,抽象类可以包含抽象方法
  • 抽象类不能被直接实例化,普通类可以直接实例化

3.3.3 抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承,使用final就不能被继承

3.3.4 创建一个对象用什么关键字?对象实例与对象引用有何不同?

  • new关键字
  • new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存在栈内存中)。
  • 一个对象引用可以指向0或者1个对象,一个对象有n个引用指向它

3.4 变量与方法

3.4.1 成员变量与局部变量的区别有哪些

  • 变量:程序运行过程中,在每个范围其值可以发生改变的量,从本质上来讲,变量其实就是内存中的一小块区域
  • 成员变量:方法外部,类内部定义的变量
  • 局部变量:类的方法中的变量
  • 区别:
  1. 作用域:前者针对整个类有效,后者只在方法或者语句内有效
  2. 存储位置:前者随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中;后者在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
  3. 生命周期:前者随着对象的创建而存在,随着对象消失而消失;后者当方法调用完,或者语句结束,就自动释放。
  4. 初始值:前者有默认初始值,后者没有默认初始值,使用前必须赋值
  5. 使用原则:就近原则,使用变量首先在局部范围内找,有就使用,接着在成员变量中查找

3.4.2 在Java中定义一个不做事且没有参数的构造方法的作用

  • 作用:防止子类因为没有构造方法,并且没有用super()调用父类的特定构造方法,而造成的编译错误
  • 解释:Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

3.4.3 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

帮助子类做初始化工作。

3.4.4 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?

  • 作用:完成对类对象的初始化工作
  • 没有类的构造方法也可以执行,因为一个类及时没有声明构造方法也会有默认的不带参数的构造方法

3.4.5 构造方法有哪些特性?

  • 名字与类名相同
  • 没有返回值,但不能用void声明构造函数
  • 生成类的对象是自动执行,无需调用

3.4.6 静态变量和实例变量区别

  • 静态变量:静态变量由于不属于任何实例对象,属于类的,所以在内存中只有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间
  • 实例变量:每次创建对象,都会为每个对象分配成员变量的内存空间,实例变量属于实例对象的,在内存中,创建几次对象就有几份成员变量

3.4.7 静态变量与普通变量区别

  • 静态对象:能被所有对象所共享,在内存中只有一个副本,当且仅当类初次加载时会被初始化,初始化顺序按照定义的顺序进行
  • 普通变量:对象拥有,创建对象时初始化,存在多个副本,各个对象的副本互不影响

3.4.8 静态方法和实例方法有何不同?

  • 调用方式:外部调用静态方法,可以使用类名.方法名的方式,也可以使用对象名.方法名;而实例方法只有对象名.方法名这个方式,静态方法调用不需要创建对象
  • 访问范围:静态方法在访问类的成员时,只允许访问静态成员(静态成员变量与方法),不允许访问实例成员变量与方法,实例方法无限制

3.4.9 在一个静态方法内调用一个非静态成员为什么是非法的?

由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员

3.4.10 什么是方法的返回值?返回值的作用是什么?

  • 定义:获取某个方法体的代码执行后的产生的结果
  • 作用:接收到结果,使得它可以用于其他操作

3.5 内部类

3.5.1 什么是内部类?

  • 定义:可以在一个类的定义中定义另一个类。内部类本身就是类的一个属性,与其他属性定义方式一致

3.5.2 内部类的分类有哪些

  • 四种:成员内部类,局部内部类,匿名内部类,静态内部类

3.5.3 静态内部类

  • 定义:定义在类内部的静态类
  • 代码:
public class Outer {

    private static int radius = 1;

    static class StaticInner {
        public void visit() {
            System.out.println("visit outer static  variable:" + radius);
        }
    }
}
  • 特点:静态内部类可以访问外部类的所有静态变量,而不可以访问外部类的非静态变量;
  • 静态内部类的创建方式(new 外部类.静态内部类()):
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();

3.5.4 成员内部类

  • 定义:定义在类内部,成员位置上的非静态类
  • 代码:
public class Outer {

    private static  int radius = 1;
    private int count =2;
    
     class Inner {
        public void visit() {
            System.out.println("visit outer static  variable:" + radius);
            System.out.println("visit outer   variable:" + count);
        }
    }
}
  • 特点:成员内部类可以访问外部类的所有变量和方法,包括静态和非静态,私有和公有
  • 调用方式,依赖于外部类的实例(外部类实例.new 内部类()):
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();

3.5.5 局部内部类

  • 定义:定义在方法中的内部类
  • 代码:
public class Outer {

    private  int out_a = 1;
    private static int STATIC_b = 2;

    public void testFunctionClass(){
        int inner_c =3;
        class Inner {
            private void fun(){
                System.out.println(out_a);
                System.out.println(STATIC_b);
                System.out.println(inner_c);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
    public static void testStaticFunctionClass(){
        int d =3;
        class Inner {
            private void fun(){
                // System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
                System.out.println(STATIC_b);
                System.out.println(d);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
}
  • 特点:定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类智能访问外部类的静态变量和方法
  • 调用方式:new 内部类(),只能在对应方法内定义
 public static void testStaticFunctionClass(){
    class Inner {
    }
    Inner  inner = new Inner();
 }

3.5.6 匿名内部类

  • 定义:没有名字的内部类
  • 代码:
public class Outer {

    private void test(final int i) {
        new Service() {
            public void method() {
                for (int j = 0; j < i; j++) {
                    System.out.println("匿名内部类" );
                }
            }
        }.method();
    }
 }
 //匿名内部类必须继承或实现一个已有的接口 
 interface Service{
    void method();
}
  • 特点:
  1. 匿名内部类必须继承一个抽象类或者实现一个接口
  2. 匿名内部类不能定义任何静态成员和静态方法
  3. 当所有方法的形参需要被匿名内部类使用时,必须声明为final
  4. 匿名内部类不能是抽象的,他必须要实现继承的类或者实现的接口的所有抽象方法
  • 创建方式:
new/接口{ 
  //匿名内部类实现部分
}

3.5.7 内部类的优点

  • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据
  • 内部类不为同一包的其他类所见,有更好的封装性
  • 内部类有效实现了多重继承,优化java单继承的缺陷
  • 匿名内部类可以很方便的定义回调

3.5.8 内部类有哪些应用场景

  • 一些多算法场合
  • 解决一些非面向对象的语句块
  • 适当使用内部类,使得代码更加灵活和富有扩展性
  • 当某个类除了它的外部类,不被其他类使用时

3.5.9 局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?

  • 因为生命周期不一致:局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量,就会出错。
  • 加了final,可以确保局部内部类使用的变量与外层的局部变量区分开

3.5.10 内部类相关,看程序说出运行结果

public class Outer {
    private int age = 12;

    class Inner {
        private int age = 13;
        public void print() {
            int age = 14;
            System.out.println("局部变量:" + age);
            System.out.println("内部类变量:" + this.age);
            System.out.println("外部类变量:" + Outer.this.age);
        }
    }

    public static void main(String[] args) {
        Outer.Inner in = new Outer().new Inner();
        in.print();
    }

}

运行结果为:14-13-12

3.6 重写与重载

3.6.1 构造器(constructor)是否可被重写(override)

构造器不能被继承,因此不能被重写,但可以被重载

3.6.2 重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

  • 方法的重载和重写是实现多态的方式,区别在于前者实现的是编译时的多态,后者是运行的多态性
  • 重载:发生在同一个类中,方法名相同但参数列表不同(个数、类型、顺序),与方法返回值和修饰符无关,即重载的方法不能根据返回类型进行区分。
  • 重写:发生在父子类,方法名、参数列表相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏替换原则);如果父类方法访问修饰符为private则子类中就不是重写。

3.7 对象相等判断

3.7.1 == 和 equals 的区别是什么

  • ==:判断两个对象的地址相不相同,即判断是不是同个对象(基础数据类型,比较的是值;引用数据类型比较的是对象的内存地址)
  • equals:也是判断这两个对象是否相同(情况1:类没有覆盖equals方法,则等价于用==比较这两个对象;情况2:类覆盖了equals方法,一般用equals来判断两个对象的内容是否相等)
  • 举例:
public class test1 {
    public static void main(String[] args) {
        String a = new String("ab"); // a 为一个引用
        String b = new String("ab"); // b为另一个引用,对象的内容一样
        String aa = "ab"; // 放在常量池中
        String bb = "ab"; // 从常量池中查找
        if (aa == bb) // true
            System.out.println("aa==bb");
        if (a == b) // false,非同一对象
            System.out.println("a==b");
        if (a.equals(b)) // true
            System.out.println("aEQb");
        if (42 == 42.0) { // true
            System.out.println("true");
        }
    }
}

说明:

  • String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值
  • 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象

3.7.2 hashCode 与 equals (重要)

  • hashCode()介绍
  1. 作用:哈希码的作用是确定该对象在哈希表中的索引位置
  2. 定义:获取哈希码,也称为散列码;它实际上是返回一个int整数;hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数
  3. 散列表存储特点:散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
  • 为什么要有hashCode
    当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度
  • hashCode()与equals()的相关规定
  1. 如果对象相同,hashCode一定相同
  2. 如果对象相同,调用equals()返回true
  3. 两个对象有相同的hashCode,不一定相同
  • equals方法被覆盖,则hashCode方法也会被覆盖:hashCode()默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使两个对象指向相同数据)

3.7.3 对象的相等与指向他们的引用相等,两者有什么不同?

  • 对象相等:比较的是内存中的存放内容是否相等
  • 引用相等:比较的是他们指向的内存地址是否相等

3.8 值传递

3.8.1 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递

  • 是值传递
  • Java语言的方法只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中,参数的值就是对该对象的引用。对象的属性可以再调用过程中改变,但对对象的引用的改变是不会影响到调用者的

3.8.2 为什么 Java 中只有值传递

3.8.3 例子

  • 例子1:方法修改基本数值类型参数
public static void main(String[] args) {
    int num1 = 10;
    int num2 = 20;

    swap(num1, num2);

    System.out.println("num1 = " + num1);
    System.out.println("num2 = " + num2);
}

public static void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;

    System.out.println("a = " + a);
    System.out.println("b = " + b);
}

结果:

a = 20
b = 10
num1 = 10
num2 = 20

分析:
在这里插入图片描述
在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身

  • 例子2:方法修改对象参数的状态
    public static void main(String[] args) {
        int[] arr = { 1, 2, 3, 4, 5 };
        System.out.println(arr[0]);
        change(arr);
        System.out.println(arr[0]);
    }

    public static void change(int[] array) {
        // 将数组的第一个元素变为0
        array[0] = 0;
    }

结果:

1
0

分析:
在这里插入图片描述array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。

  • 例子3:方法修改对象引用
public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Student s1 = new Student("小张");
        Student s2 = new Student("小李");
        Test.swap(s1, s2);
        System.out.println("s1:" + s1.getName());
        System.out.println("s2:" + s2.getName());
    }

    public static void swap(Student x, Student y) {
        Student temp = x;
        x = y;
        y = temp;
        System.out.println("x:" + x.getName());
        System.out.println("y:" + y.getName());
    }
}

结果:

x:小李
y:小张
s1:小张
s2:小李

分析:
交换前:
在这里插入图片描述

交换后:
在这里插入图片描述通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝

3.8.4 总结

  • Java程序对对象才用的不是引用调用,实际上,对象引用是按值传递的
  • 一个方法不能修改一个基本数据类型的参数(数值型或者布尔型)
  • 一个方法可以改变一个对象参数的状态
  • 一个方法不能让对象参数引用一个新的对象

3.8.5 值传递和引用传递有什么区别

  • 值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递之后就互不相关了
  • 引用传递:指的是方法调用时,传递的参数是按引用进行传递,传递的是引用的地址,也就是传递的是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前后都指向一个引用(也就是一个内存空间)

3.9 Java包

3.9.1 JDK 中常用的包有哪些

  • java.lang:这个是系统的基础类;
  • java.io:这里面是所有输入输出有关的类,比如文件操作等;
  • java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
  • java.net:这里面是与网络有关的类;
  • java.util:这个是系统辅助类,特别是集合类;
  • java.sql:这个是数据库操作的类。

3.9.2 import java和javax有什么区别

  • 没什么区别,都是一个名字
  • 刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分

4. IO流

4.1 java 中 IO 流分为几种?

  • 按照流的流向:输入流与输出流
  • 按照操作单元:字节流与字符流
  • 按照流的角色:节点流与处理流
  • Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的:
  1. InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流
  2. OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流
  • 按操作方式:
    在这里插入图片描述

  • 按操作对象
    在这里插入图片描述

4.2 BIO,NIO,AIO 有什么区别?

  • 简单回答:
  1. BIO:Block IO 同步阻塞IO,模式简单,并发处理能力低
  2. NIO:Non IO 同步非阻塞IO,升级版,客户端和服务端通过Channel通讯,实现多路复用
  3. AIO:Asynchronous IO 异步非阻塞IO,NIO2,异步IO的操作基于事件和回调机制
  • 详细回答:
  1. BIO:同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
  2. NIO:NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
  3. AIO:AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了

4.3 Files的常用方法都有哪些?

  • 后面的方法前缀都是Files.
  • exists():检测文件路径是否存在
  • createFile():创建文件
  • createDirectory():创建文件夹
  • delete():删除一个文件或目录
  • copy():复制文件
  • move():移动文件
  • size():查看文件个数
  • read():读取文件
  • write():写入文件

5. 反射

5.1 什么是反射机制?

  • 定义:JAVA反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
  • 静态编译和动态编译:
  • 静态编译:在编译时确定类型,绑定对象
  • 动态编译:运行时确定类型,绑定对象

5.2 反射机制优缺点

  • 优点:运行期类型的判断,动态加载类,提高代码灵活度
  • 缺点:性能瓶颈,反射相当于一系列解释操作,通知JVM要做的事情,性能比直接的java代码要慢很多

5.3 反射机制的应用场景有哪些?

  • 反射是框架设计的灵魂。
  • 在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
  • 举例:
  1. 我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;
  2. Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性

5.4 Java获取反射的三种方法

  1. 通过new对象实现反射机制
  2. 通过路径实现反射机制
  3. 通过类名实现反射机制
public class Student {
    private int id;
    String name;
    protected boolean sex;
    public float score;
}
public class Get {
    //获取反射机制三种方式
    public static void main(String[] args) throws ClassNotFoundException {
        //方式一(通过建立对象)
        Student stu = new Student();
        Class classobj1 = stu.getClass();
        System.out.println(classobj1.getName());
        //方式二(所在通过路径-相对路径)
        Class classobj2 = Class.forName("fanshe.Student");
        System.out.println(classobj2.getName());
        //方式三(通过类名)
        Class classobj3 = Student.class;
        System.out.println(classobj3.getName());
    }
}

6. 网络编程

7. 常用API

7.1 String相关

7.1.1 字符型常量和字符串常量的区别

  1. 形式上:前者单引号,后者双引号
  2. 含义上:字符常量相当于一个整型值(ASCII值),可以参加表达式运算;字符串代表一个地址值(该字符串在内存中存放位置)
  3. 占内存上:字符常量只占一个字节,字符串常量占若干个字节(至少一个字符结束标志)

7.1.2 什么是字符串常量池?

  • 定义:位于堆内存,专门用于存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用

7.1.3 String 是最基本的数据类型吗

  • 不是,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符
  • String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用
  • Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean
  • 除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型

7.1.4 String有哪些特性

  • 不变性:String是只读字符串,是一个典型的immutable对象,对它进行任何操作,其实都是在创建一个新的对象,再把引用指向新对象。
  • 不变模式的作用:当对象被多线程共享并频繁访问时,可以保证数据一致性
  • 常量池优化:String对象创建后,会在字符串常量池中间缓存,如果下次创建相同的对象,直接返回缓存的引用
  • final:使用final定义String类,表示String类不能被继承,提高系统安全性

7.1.5 String为什么是不可变的吗?

简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:

/** The value is used for character storage. */
private final char value[];

7.1.6 String真的是不可变的吗?

  1. 不可变,但引用可变
  • 例子:
String str = "Hello";
str = str + " World";
System.out.println("str=" + str);
  • 结果:
str=Hello World
  • 分析:
    实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串
  1. 通过反射可以修改所谓的“不可变”对象
  • 例子:
// 创建字符串"Hello World", 并赋给引用s
String s = "Hello World";

System.out.println("s = " + s); // Hello World

// 获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");

// 改变value属性的访问权限
valueFieldOfString.setAccessible(true);

// 获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);

// 改变value所引用的数组中的第5个字符
value[5] = '_';

System.out.println("s = " + s); // Hello_World
  • 结果:
s = Hello World
s = Hello_World
  • 分析:
    用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。

7.1.7 是否可以继承 String 类

String 类是 final 类,不可以被继承

7.1.8 String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中

7.1.9 String s = new String(“xyz”);创建了几个字符串对象

  • 两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象
  • 例子:
String str1 = "hello"; //str1指向静态区
String str2 = new String("hello");  //str2指向堆上的对象
String str3 = "hello";
String str4 = new String("hello");
System.out.println(str1.equals(str2)); //true
System.out.println(str2.equals(str4)); //true
System.out.println(str1 == str3); //true
System.out.println(str1 == str2); //false
System.out.println(str2 == str4); //false
System.out.println(str2 == "hello"); //false
str2 = str1;
System.out.println(str2 == "hello"); //true

7.1.10 如何将字符串反转?

  • 使用 StringBuilder 或者 stringBuffer 的 reverse() 方法
  • 例子:
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba

7.1.11 数组有没有 length()方法?String 有没有 length()方法

  • 数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆

7.1.12 String 类的常用方法都有那些?

  • length()
  • charAt()
  • indexOf():返回指定字符的索引
  • replace():字符串替换
  • trim():去除字符串两端空白
  • split():分割字符串,返回一个分割后的字符串数组
  • getBytes():返回字符串的 byte 类型数组
  • toLowerCase()
  • toUpperCase()
  • substring()
  • equals()

7.1.13 在使用 HashMap 的时候,用 String 做 key 有什么好处?

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快

7.1.14 String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的

  • 可变性:String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
  • 线程安全性:String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
  • 性能:每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
  • 对于三者的总结:
  1. 如果要操作少量的数据用 = String
  2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer

7.2 Date相关

7.3 包装类相关

7.3.1 自动装箱与拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来
  • 拆箱:将包装类型转换为基本数据类型
  • 原始类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

7.3.2 int 和 Integer 有什么区别

Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

7.3.3 Integer a= 127 与 Integer b = 127相等吗

  • 对于对象引用类型:== 比较的是对象的内存地址
  • 对于基本数据类型:==比较的是值
  • 如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false
  • 例子
public static void main(String[] args) {
    Integer a = new Integer(3);
    Integer b = 3;  // 将3自动装箱成Integer类型
    int c = 3;
    System.out.println(a == b); // false 两个引用没有引用同一对象
    System.out.println(a == c); // true a自动拆箱成int类型再和c比较
    System.out.println(b == c); // true

    Integer a1 = 128;
    Integer b1 = 128;
    System.out.println(a1 == b1); // false

    Integer a2 = 127;
    Integer b2 = 127;
    System.out.println(a2 == b2); // true
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值