一: Java数据类型
Java中的数据类型分为基本数据类型和引用数据类型。
基本数据类型:
1)整性:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
2)浮点型:float(4字节)、double(8字节)
3)字符型:char(2字节)(唯有这个封装类特殊,为Character)
4)布尔型:boolean(1字节)
引用数据类型:(默认值为null)
1)类
2)接口
3)对象、数组
二:Java变量类型
java中有局部变量、成员变量、静态变量、参数变量
1)成员变量使用时,不需要初始化。成员变量在对象创建的时候创建,在对象被销毁的时候销毁。
2)局部变量在使用前必须被初始化。如果不进行初始化,编译器会报错,因为 Java 不会为局部变量提供默认值。
局部变量存储在 Java 虚拟机(JVM)的栈上,与存储在堆上的实例变量或对象不同。
3)静态变量(类变量):它与类相关而不是与实例相关,即无论创建多少个类实例,静态变量在内存中只有一份拷贝,被所有实例共享。
4)参数变量(分为值传递和引用传递)
三: Java修饰符(分为访问修饰符、非访问修饰符)
1.访问修饰符
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
-
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
-
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
-
public : 对所有类可见。使用对象:类、接口、变量、方法
-
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
protected 需要从以下两个点来分析说明:
- 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
- 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
注意:在不同的访问修饰符修饰的类中,子类继承父类有不同的规则。
-
父类中声明为 public 的方法在子类中也必须为 public。
-
父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
-
父类中声明为 private 的方法,不能够被子类继承。
总结:子类声明的方法修饰符不能低于父类。
2.非访问修饰符
1)static 修饰符
用来修饰类方法和类变量。
2)final 修饰符
用来修饰类、方法和变量,final 修饰的类不能够被继承(因为没有类能继承final类的任何属性),修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
3)abstract 修饰符
用来创建抽象类和抽象方法。
特点:抽象类可以没有抽象方法,但是有抽象方法的类一定是抽象类。
4)synchronized 修饰符
主要用于线程的编程,保证线程安全。
5)volatile修饰符(可用于实现可见性)
- volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
- volatile也可以避免指令重排序(这里就不对指令重排序进一步讲解)。
6)transient 修饰符
序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。
四:Java 各种常用类
1.Number类
常用方法:
1 | xxxValue() 将 Number 对象转换为xxx数据类型的值并返回。 |
---|---|
2 | compareTo() 将number对象与参数比较。 |
3 | equals() 判断number对象是否与参数相等。 |
4 | valueOf() 返回一个 Number 对象指定的内置数据类型 |
5 | toString() 以字符串形式返回值。 |
6 | parseInt() 将字符串解析为int类型。 |
2.Character类
序号 | 方法与描述 |
---|---|
1 | isLetter() 是否是一个字母 |
2 | isDigit() 是否是一个数字字符 |
3 | isWhitespace() 是否是一个空白字符 |
4 | isUpperCase() 是否是大写字母 |
5 | isLowerCase() 是否是小写字母 |
6 | toUpperCase() 指定字母的大写形式 |
7 | toLowerCase() 指定字母的小写形式 |
8 | toString() 返回字符的字符串形式,字符串的长度仅为1 |
3.String类
String 创建的字符串存储在公共池中,而 new 创建的字符串对象在堆上:
String s1 = "Runoob"; // String 直接创建
String s2 = "Runoob"; // String 直接创建
String s3 = s1; // 相同引用
String s4 = new String("Runoob"); // String 对象创建
String s5 = new String("Runoob"); // String 对象创建
4.StringBuffer 方法(线程安全的)
以下是 StringBuffer 类支持的主要方法:
序号 | 方法描述 |
---|---|
1 | public StringBuffer append(String s) 将指定的字符串追加到此字符序列。 |
2 | public StringBuffer reverse() 将此字符序列用其反转形式取代。 |
3 | public delete(int start, int end) 移除此序列的子字符串中的字符。 |
4 | public insert(int offset, int i) 将 int 参数的字符串表示形式插入此序列中。 |
5 | insert(int offset, String str) 将 str 参数的字符串插入此序列中。 |
6 | replace(int start, int end, String str) 使用给定 String 中的字符替换此序列的子字符串中的字符。 |
以下列表列出了 StringBuffer 类的其他常用方法:
序号 | 方法描述 |
---|---|
1 | int capacity() 返回当前容量。 |
2 | char charAt(int index) 返回此序列中指定索引处的 char 值。 |
3 | void ensureCapacity(int minimumCapacity) 确保容量至少等于指定的最小值。 |
4 | void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 将字符从此序列复制到目标字符数组 dst 。 |
5 | int indexOf(String str) 返回第一次出现的指定子字符串在该字符串中的索引。 |
6 | int indexOf(String str, int fromIndex) 从指定的索引处开始,返回第一次出现的指定子字符串在该字符串中的索引。 |
7 | int lastIndexOf(String str) 返回最右边出现的指定子字符串在此字符串中的索引。 |
8 | int lastIndexOf(String str, int fromIndex) 返回 String 对象中子字符串最后出现的位置。 |
9 | int length() 返回长度(字符数)。 |
10 | void setCharAt(int index, char ch) 将给定索引处的字符设置为 ch 。 |
11 | void setLength(int newLength) 设置字符序列的长度。 |
12 | CharSequence subSequence(int start, int end) 返回一个新的字符序列,该字符序列是此序列的子序列。 |
13 | String substring(int start) 返回一个新的 String ,它包含此字符序列当前所包含的字符子序列。 |
14 | String substring(int start, int end) 返回一个新的 String ,它包含此序列当前所包含的字符子序列。 |
15 | String toString() 返回此序列中数据的字符串表示形式。 |
5.Java 数组
数组创建方式:
1)int[] array = new int[10];
2)int[] array = {1,2,3,4,5};
Arrays 类
java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的,可以类名.方法直接调用。
具有以下功能:
-
给数组赋值:通过 fill 方法。
-
对数组排序:通过 sort 方法,按升序。
-
比较数组:通过 equals 方法比较数组中元素值是否相等。
-
查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。
具体说明请查看下表:
序号 | 方法和说明(范围查询都是包含左边不包含右边) |
---|---|
1 | public static int binarySearch(Object[] a, int fromIndex, int toIndex, Object key) 用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。 |
2 | public static boolean equals(long[] a, long[] a2) 如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
3 | public static void fill(int[] a , int fromIndex, int toIndex, int val) 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
4 | public static void sort(Object[] a,int fromIndex, int toIndex ) 对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
5 | public static <T> T[] copyOf(T[] original, int newLength) 方法用于复制数组的一部分。如果复制的长度比原数组长,则用默认值填充新的位置。 |
6 | public static <T> T[] copyOfRange(T[] original, int from, int to) Arrays.copyOfRange() 方法用于复制指定范围内的数组元素并返回一个新的数组。 |
7 | List<String> list = Arrays.asList(array); 将数组转换为 List 。注意,这个方法返回的 List 是固定大小的,不能添加或删除元素。 |
五:Java日期时间
1.java.util 包提供了 Date 类来封装当前的日期和时间。
Date 对象创建以后,可以调用下面的方法。
序号 | 方法和描述 |
---|---|
1 | boolean after(Date date) 若当调用此方法的Date对象在指定日期之后返回true,否则返回false。 |
2 | boolean before(Date date) 若当调用此方法的Date对象在指定日期之前返回true,否则返回false。 |
3 | Object clone( ) 返回此对象的副本。 |
4 | int compareTo(Date date) 比较当调用此方法的Date对象和指定日期。两者相等时候返回0。调用对象在指定日期之前则返回负数。调用对象在指定日期之后则返回正数。 |
5 | int compareTo(Object obj) 若obj是Date类型则操作等同于compareTo(Date) 。否则它抛出ClassCastException。 |
6 | boolean equals(Object date) 当调用此方法的Date对象和指定日期相等时候返回true,否则返回false。 |
7 | long getTime( ) 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。 |
8 | int hashCode( ) 返回此对象的哈希码值。 |
9 | void setTime(long time) 用自1970年1月1日00:00:00 GMT以后time毫秒数设置时间和日期。 |
10 | String toString( ) 把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。 |
2.SimpleDateFormat日期格式化
import java.util.*;
import java.text.*;
public class DateDemo {
public static void main(String[] args) {
Date dNow = new Date( );
//HH 是 24 小时制,而 hh 是 12 小时制。
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间为: " + ft.format(dNow));
}
}
3.Calendar类(抽象类)
创建一个代表系统当前日期的Calendar对象
Calendar c = Calendar.getInstance();//默认是当前日期
创建一个指定日期的Calendar对象
使用Calendar类代表特定的时间,需要首先创建一个Calendar的对象,然后再设定该对象中的年月日参数来完成。
//创建一个代表2009年6月12日的Calendar对象
Calendar c1 = Calendar.getInstance();
c1.set(2009, 6 - 1, 12);
c1.set(Calendar.YEAR,2008);
//把c1对象的日期加上10,也就是c1也就表示为10天后的日期,其它所有的数值会被重新计算
c1.add(Calendar.DATE,10);
Calendar类对象字段类型
Calendar类中用以下这些常量表示不同的意义,jdk内的很多类其实都是采用的这种思想
常量 | 描述 |
---|---|
Calendar.YEAR | 年份 |
Calendar.MONTH | 月份 |
Calendar.DATE | 日期 |
Calendar.DAY_OF_MONTH | 日期,和上面的字段意义完全相同 |
Calendar.HOUR | 12小时制的小时 |
Calendar.HOUR_OF_DAY | 24小时制的小时 |
Calendar.MINUTE | 分钟 |
Calendar.SECOND | 秒 |
Calendar.DAY_OF_WEEK | 星期几 |
六、Java异常处理
1.以下是三种类型的异常:
1)检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这些异常在编译时强制要求程序员处理。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
这类异常通常使用 try-catch 块来捕获并处理异常,或者在方法声明中使用 throws 子句声明方法可能抛出的异常。
2)运行时异常:这些异常在编译时不强制要求处理,通常是由程序中的错误引起的,例如 NullPointerException、ArrayIndexOutOfBoundsException 等,这类异常可以选择处理,但并非强制要求。
3)错误: 错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
异常类有两个主要的子类:IOException 类和 RuntimeException 类。
2.throws/throw 关键字
1)throw 关键字用于在当前方法中抛出一个异常。
通常情况下,当代码执行到某个条件下无法继续正常执行时,可以使用 throw 关键字抛出异常,以告知调用者当前代码的执行状态。
2)throws 关键字用于在方法声明中指定该方法可能抛出的异常。当方法内部抛出指定类型的异常时,该异常会被传递给调用该方法的代码,并在该代码中处理异常。
3.try-with-resources
Java 新增的 try-with-resource 语法结构,旨在自动管理资源,确保资源在使用后能够及时关闭,避免资源泄露 。
try-with-resources 是一种异常处理机制,它能够自动关闭在 try 块中声明的资源,无需显式地在 finally 块中关闭。
class RunoobTest {
//在 try 关键字后面声明资源,然后跟随一个代码块。
无论代码块中的操作是否成功,资源都会在 try 代码块执行完毕后自动关闭。
public static void main(String[] args) throws IOException{
try (Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
while (scanner.hasNext()) {
writer.print(scanner.nextLine());
}
}
}
}
七、Java面向对象
继承中成员变量的访问特点:就近原则。
先在局部位置找--->本类成员位置找--->父类成员位置找。
1.父类哪些内容可以被子类继承?
1)构造方法无法被继承但是可以通过super关键字调用。
2)非私有的成员变量可以被继承和调用,私有的可以被继承但是不能调用。
3)非私有的成员方法可以被继承和调用,私有的不可以被继承和调用。
2.super 与 this 关键字
super 关键字:我们可以通过 super 关键字来实现对父类成员的访问,用来引用当前对象的父类。
this 关键字:指向自己的引用,引用当前对象,即它所在的方法或构造函数所属的对象实例。
注意:子类中的所有构造方法,默认会调用父类的无参构造方法。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器,如果用super关键字调用父类有参构造器,则无参构造不会被隐式调用。
3.Java 重写(Override)与重载(Overload)
重写(Override)是指子类定义了一个与其父类中具有相同名称、参数列表和返回类型的方法,并且子类方法的实现覆盖了父类方法的实现。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
重写与重载的区别
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
八、多态
多态(Polymorphism)是面向对象编程中的一个核心概念,它允许同一个接口或方法在不同的对象上产生不同的行为。
多态分为编译时多态和运行时多态:
编译时多态:方法重载
运行时多态:方法重写
1.多态调用成员的特点:
多态存在的三个必要条件
-
继承
-
重写
-
父类引用指向子类对象:Parent p = new Child();
1)变量调用:编译看左边,运行也看左边。(子类会把父类的成员变量继承下来)
编译看左边:javac编译代码的时候,会看左边父类中有没有这个变量,如果有,编译成功,如果没有编译失败。
运行也看左边:java运行代码的时候,实际获取的是左边父类中成员变量的值。
2)方法调用:编译看左边,运行看右边。(子类中重写的方法,在虚方法表中会把父类的方法覆盖掉)
编译看左边:javac编译代码的时候,会看左边父类中有没有这个方法,如果有,编译成功,如果没有编译失败。
运行看右边:运行时,运行的是子类中的方法
abstract class Animal {
abstract void eat();
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void bark() {
System.out.println("狗叫");
}
}
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.eat();// 执行 Animal 类的方法
b.eat();//执行 Dog 类的方法
b.bark();
}
//该程序将抛出一个编译错误。
//原因:因为创建Dog对象的接收对象是父类对象Animal,而且于父类中没有bark方法,因此无法调用
2.抽象类和接口
在面向对象编程中,接口是一种抽象类型,它定义了一组方法的签名,但不包含方法的实现。接口只规定了类应该具有哪些方法,而不关心这些方法是如何实现的。
接口有以下特性:
-
接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
-
接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
-
接口中的方法都是公有的。
抽象类总结规定
-
抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
-
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
-
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
-
构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
-
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
接口和抽象类的区别
-
抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
-
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
-
接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
-
一个类只能继承一个抽象类,而一个类却可以实现多个接口。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
注:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。
注:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。
九、Java枚举(enum)
枚举值都是 public static final 的
1.enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。
values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中:
-
values() 返回枚举类中所有的值。(枚举类名.values())
-
ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样(每个枚举值.ordinal())。
-
valueOf()方法返回指定字符串值的枚举常量。
2.枚举类成员
枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。
枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。
enum Color {
RED, GREEN, BLUE;
// 构造函数
private Color() {
System.out.println("Constructor called for : " + this.toString());
}
public void colorInfo() {
System.out.println("Universal Color");
}
}
public class Test {
// 输出
public static void main(String[] args) {
Color c1 = Color.RED;
System.out.println(c1);
c1.colorInfo();
}
}
十、Java反射
Java 反射(Reflection)是一个强大的特性,它允许程序在运行时查询、访问和修改类、接口、字段和方法的信息。反射提供了一种动态地操作类的能力,这在很多框架和库中被广泛使用,例如Spring框架的依赖注入。
工作流程
-
获取 Class 对象:首先获取目标类的
Class
对象。
//通过类字面量获取class对象
Class<String> Clazz = String.class;
//通过对象实例获取class对象
String str = "s";
Class<? extends String> aClass = str.getClass();
//通过Class.forName方法
Class<?> aClass1 = Class.forName("java.lang.String")
//获取对象
Object o = aClass1.getDeclaredConstructor().newInstance();
-
获取成员信息:通过
Class
对象,可以获取类的字段、方法、构造函数等信息。 -
操作成员:通过反射 API 可以读取和修改字段的值、调用方法以及创建对象。
以下是 java.lang.reflect 包中的主要类和接口的详细介绍:
1.Class 类
功能:表示类的对象,提供了获取类信息的方法,如字段、方法、构造函数等。
主要方法:
- getFields():获取所有公共字段。
- getDeclaredFields():获取所有声明的字段,包括私有字段。
- getMethods():获取所有公共方法。
- getDeclaredMethods():获取所有声明的方法,包括私有方法。
- getConstructors():获取所有公共构造函数。
- getDeclaredConstructors():获取所有声明的构造函数,包括私有构造函数。
- getSuperclass():获取类的父类。
- getInterfaces():获取类实现的所有接口。
2.Field 类
功能:表示类的字段(属性),提供了访问和修改字段值的方法。
主要方法:
- get(Object obj):获取指定对象的字段值。
- set(Object obj, Object value):设置指定对象的字段值。
- getType():获取字段的数据类型。
- getModifiers():获取字段的修饰符(如 public、private)。
3.Method 类
功能:表示类的方法,提供了调用方法的能力。
主要方法:
- invoke(Object obj, Object... args):调用指定对象的方法。
- getReturnType():获取方法的返回类型。
- getParameterTypes():获取方法的参数类型。
- getModifiers():获取方法的修饰符(如 public、private)。
4.Constructor 类
功能:表示类的构造函数,提供了创建对象的能力。
主要方法:
- newInstance(Object... initargs):创建一个新实例,使用指定的构造函数参数。
- getParameterTypes():获取构造函数的参数类型。
- getModifiers():获取构造函数的修饰符(如 public、private)。
十一:Java中常见的关键字
1.class
:用于定义一个类,类是 Java 中面向对象编程的基本组织单元。
2.interface
:用于定义一个接口,接口定义了一组方法签名,实现该接口的类必须实现这些方法。
3.static
:修饰类变量、方法,属于类而非实例。
4.final
:修饰类(不可继承)、方法(不可重写)、变量(常量)。
5.synchronized
:保证线程同步。
6.volatile
:确保变量可见性,禁止指令重排。
7.try/catch/finally
:异常处理。
8.if
、else
:用于条件判断,根据条件的真假执行不同的代码块。
9.this
:表示当前对象的引用,通常用于区分成员变量和局部变量,或者在构造方法中调用其他构造方法。
10.super
:表示父类对象的引用,用于调用父类的构造方法、成员变量或成员方法。
借鉴自菜鸟教程