面向对象的基本概念
1.面向对象
Java是一种面向对象的语言,比较符合人类认识现实世界的思维方式。
它的基本思想是把问题看成是由若干个对象组成,对象间相互独立,但又可以相互配合、连接和协调。
具有系统结构较稳定、子系统相对独立、软件可重用性、可维护性和可扩展性强的特点。
面向对象的三大特征:封装、继承和多态。
2.对象
对象是用来描述客观事物的一个实体
对象的属性:属性可以用来描述该对象,每个对象的每个属性都拥有特定值。
对象的方法:该对象可执行的操作。
3.类
类是具有相同属性和方法的一组对象的集合。
类定义了其对象将会有的特征(属性)和行为(方法)。
类与对象的关系:
I 类是对象的抽象,对象是类的实现
II 类是由属性和方法构成,它的每一个对象都有对应的属性和方法
定义类
所有Java程序都以类class为组织单元
关键字class定义自定义的数据类型
[访问修饰符] class 类名{
//省略类的内部具体代码
}
实例:一个类的基本结构
public class Person { //定义“人”类
public String name; //姓名
public String gender; //性别
public int age; //年龄
public void work(){ //行为:工作
System.out.println(this.name+"正在工作")
}
创建和使用对象
1.创建对象
类名 对象名 = new 类名();
new是关键字
左边的类名为对象的数据类型
右边的类名()称为类的构造方法
实例:接上,创建Person类的对象
Person laowang = new Person();
2.使用对象
在Java中,要引用对象的属性和方法,需要使用“.”
对象名.属性 //引用对象的属性
对象名.方法名() //引用对象的方法
实例:接上,调用对象laowang的属性和方法
public static void main(String[] args) {
Person laowang = new Person();
laowang.name = "老王"; //给laowang的属性赋值
laowang.gender = "男";
laowang.age = 40;
laowang.work; //调用laowang的work方法
}
对象数组
实例:继续用Person类做例子
public class TestPerson{
public static void main(String[] args){
Person laowang = new Person();
Person xiaoliu = new Person();
Person liming = new Person();
Person[] person = {laowang, xiaoliu, liming};
//定义包含了3个元素的对象数组,数据类型为Person
}
}
方法
方法的定义:
public 返回值类型 方法名(){
//这里编写方法的主体
}
方法的返回值
方法的返回值有两种情况:
1.如果方法具有返回值,方法中必须使用关键字return返回该值,返回值类型为该值的类型。返回值只能有一个。
public String getName(){
return name;
}
2.如果没有返回值,返回值类型为void,如果想查看结果则在方法内通过打印的方式来显示。
方法的调用
方法之间允许相互调用,不需要知道方法的具体实现,实现重用,提高效率。以下是两种调用情况的实例:
1.Student类的方法a()调用Student类的方法b(),直接调用
public class Student{
public void a(){
}
public void b(){
a();
}
}
2.Student类的方法a()调用Teacher类的方法b(),先创建类对象,然后使用“.”调用
public class Student{
public void a(){
Teacher t = new Teacher();
t.b();
}
}
带参数的方法
定义带参数的方法:
<访问修饰符> 返回类型 <方法名>(<形式参数列表>) {
//方法的主体
}
调用带参方法:
对象名.方法名(参数1,参数2,……,参数n)
//调用带参方法时,必须让实际参数的类型、顺序与形式参数列表匹配
注意:对象数组也可以作为参数,但是要记得匹配。
方法传参
基本数据类型和引用数据类型数据在传参时是有区别的!
- 基本数据类型,操作传递的是变量的值,改变一个变量的值不会影响另一个变量的值。
- 引用数据类型(类、数组和接口),赋值是把原对象的引用(可理解为内存地址)传递给另一个引用。
实例:
public class Student(){
int age;
}
public class Test {
public void calc1(int num){
num=num+1;
}
public void calc2(Student student){
student.age=student.age+1;
}
public static void main(String[] args){
Test test=new Test();
int n=8;
test.calc1(n);
Student stu=new Student();
stu.age=18;
test.calc2(stu);
System.out.println(n+"---"+stu.age);
}
}
输出的结果是:8---19
//n是int类型,为基本数据类型,方法使用过以后并不会改变它的值
//stu.age是类的属性,为引用数据类型,在方法中改变值后传给同样的地址,所以值改变了
方法重载
方法重载的定义:
方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数类型或参数个数
方法重载的目的:
针对同一个行为的多种表现,对应相同方法名的多个方法
方法重载的特点:
- 在同一个类中
- 方法名相同
- 参数的个数或类型不同
- 方法的返回值不能作为判断方法之间是否构成重载的依据
方法重载的优点:
方法重载其实是对原有方法的一种升级,可以根据参数的不同,采用不同的实现方法,而且不需要编写多个名称,简化了类调用方法的代码
public class Person{
public String name;
public String gender;
public int age;
public void work(){ //无参数的工作方法
System.out.println(this.name+"的工作理念:撸起袖子加油干!")
}
public void work(String content){ //有参数的工作方法
System.out.println(this.name+"的工作理念:"+content)
}
public static void main(String[] args){
Person p = new Person();
p.work();
p.work("干活挣钱有饭吃"); //调用重载的有参方法
}
}
构造方法
Java中,当类创建一个对象时会自动调用该类的构造方法,构造方法分为默认构造方法和带参数的构造方法。构造方法的主要作用是进行一些数据的初始化。
<访问修饰符> 构造方法名(){ //无返回值,方法名与类名相同,可以指定参数
//初始化代码
}
Student s1=new Student(); //new 类名();即是默认的无参构造
有参构造实例:
public class Student(){
String name;
int age;
public Student(String name, int age){
this.name = name;
this.age = age;
}
public static void main(String[] args){
Student s1 = new Student("李磊",14);
Student s2 = new Student("韩梅梅",13)
}
}
构造方法重载
自定义构造方法:方法名相同、参数项不同,与返回值、访问修饰符无关
此时系统不再提供默认无参构造方法,入股有需要则自己手动写一个
public Student(){}
public Student(String name,int age){
this.name=name;
this.age=age;}
this关键字
不难发现,this在上面的程序语句中已经出现过很多次了。那么this是什么含义呢?又有什么用法?
this关键字是对一个对象的默认引用。每个实例方法内部都有一个this引用变量,指向调用这个方法的对象。
- 使用this调用成员变量,解决成员变量和局部变量的同名冲突(后文将详细阐述)
public void setName(String name){
this.name = name; //成员变量和局部变量同名,必须使用this
}
- 使用this调用成员方法
public void play(int n){
health = health - n;
this.print(); //this可以省略,直接调用print()方法
}
- 使用this调用重载的构造方法,只能在构造方法中使用,且必须是构造方法的第一条语句
public Penguin(String name, String sex){
this.name = name;
this.sex = sex;
}
public Penguin(String name, int health, int love, String sex){
this(name, sex); //调用重载的构造方法
this.health = health;
this.love = love;
}
成员变量与局部变量
变量声明的位置决定变量作用域
变量作用域确定可在程序中按变量名访问该变量的区域
实例:
public class AutoLion{
变量1类型 变量1; //成员变量
变量2类型 变量2;
变量3类型 变量3;
public 返回类型 方法1(){
变量4类型 变量4; //局部变量
}
public 返回类型 方法2(){
变量5类型 变量5; //局部变量超出其作用域之后不可使用
}
}
成员变量和局部变量的区别:
作用域不同
- 局部变量的作用域仅限于定义它的方法
- 成员变量的作用域在整个类内部都是可见的
初始值不同
- Java会给成员变量一个初始值
- Java不会给局部变量赋予初始值
注意:
1.在同一个方法中,不允许有同名局部变量;在不同的方法中,可以有同名局部变量
2.在同一个类中,成员变量和局部变量同名时,局部变量具有更高的优先级
封装
封装的概念是将类的某些信息隐藏在类内部,不允许外部程序直接访问;而是通过该类提供的方法来实现对隐藏信息的操作和访问
属性隐藏
1.修改属性的可见性
public class Person{ //将Person类属性私有化
private String name;
private String gender;
private int age;
}
2.设置getter/setter()方法
可以用 右键 --> Generate --> getter&setter 由系统添加
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
3.设置属性的存取限制(对属性合法值进行判断)
public void setAge(int age){
if (age<0 || age>150) {
System.out.println("输入年龄不合法,将重置!")
return;
}
this.age=age;
}
封装的好处
1.便于使用者正确使用系统,防止错误修改属性
2.有助于系统之间的松耦合,提高系统独立性
3.提高软件的可重用性
4.降低了构建大型系统的风险
包机制
包的作用:
1.允许类组成较小的单元(类似文件夹),易于找到和使用相应的文件
2.有助于实施访问权限控制
3.防止命名冲突,区分名字相同的类
如何创建包?
package cn.jbit.inherit; //声明包
public class School {
//代码块
}
如何导入包?
为了使用不在同一包中的类,需要在Java程序中使用import关键字导入这个类
import 包名.类名;
import java.util.*; //导入java.util包中所有类 *之所有类
import cn.jtest.classandobject.School; //导入指定包中指定类 (School)
使用包的注意事项:
- 一个类同时引用了两个来自不同包的同名类
必须通过完整类名来区分
import oop.Dog;
import pet.Dog;
...
oop.Dog dog1 = new oop.Dog();
pet.Dog dog2 = new pet.Dog();
...
-
每个包都是独立的,顶层包不会包含子包的类
-
package和 import的顺序是固定的
package必须位于第一行(忽略注释行)
只允许有一个package语句
其次是import
接着是类的声明
访问权限控制
类的访问控制
类的访问修饰符:
public修饰符:公有访问级别
默认修饰符:包级私有访问级别(本包的可以访问,不同包不可)
类成员的访问控制
static修饰符
static可以用来修饰成员变量,成员方法,代码块
static变量
- static变量又称类变量,或者静态变量,在内存中只有一个拷贝
- 类内部,可在任何方法内直接访问静态变量
- 其他类中,可以直接通过类名访问
static变量的作用:
(1)能被类的所有实例共享,可作为实例之间进行交流的共享数据
(2)如果类的所有实例都包含一个相同的常量属性,可把这个属性定义为静态常量类型,从而节省内存空间
类的成员变量包括了类变量和实例变量(没有被static修饰的变量),每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响
static方法
- static方法,又称静态方法,可直接通过类名访问,静态方法中不能使用this和super
- 不能直接访问所属类的实例变量和实例方法,可直接访问类的静态变量和静态方法
另一种是实例方法,通过实例访问,可直接访问所属类的静态变量、静态方法、实例变量和实例方法 (上文方法小节里提到的)
注意:静态方法必须被实现,main()就是最常见的静态方法
static代码块
- JVM加载类时,加载静态代码块,是在第一次访问该类时执行,且只执行一次
- 如果有多个静态块,按顺序加载;每个静态代码块只会被执行一次
public class StaticTest {
static int num=100;
static{
num+=100;
System.out.println(num);
}
static{
num+=100;
System.out.println(num);
}
}
继承
先使用类图来理一下Pet 父类 和 Dog、Penguin 两个 子类 的关系
可以看到Dog和Penguin一些共有的东西被放进了 Pet类中,而他们各自仍保存了自己独有的属性和方法!这些共有的东西,就可以被看作是Dog类和Penguin类从他们的父类Pet中 继承 的。
使用继承
- 编写父类
[访问修饰符] class Pet {
//公共的属性和方法
}
- 编写子类,继承父类
[访问修饰符] class Dog extends Pet {
//子类特有的属性和方法
}
extends 是继承的关键字
知识要点:继承是Java中实现代码重用的重要手段之一;Java中只支持单根继承,即一个类只能有一个直接父类
super的用法
使用super关键字,super代表父类对象
访问父类构造方法:
super();
super(name, gender, ...);
// 在子类构造方法中调用且必须是第一句
访问父类属性:
super.name;
访问父类方法:
super.print();
注意:不可以访问父类中定义为private的属性和方法
继承条件下构造方法的调用规则
在Java中,一个类的构造方法在如下两种情况总是会被执行:
1.创建该类的对象(实例化)
2.创建该类的子类的对象(子类的实例化)
- 子类构造方法没有通过super显式调用父类的有参构造方法,也没通过this显式调用自身其他构造方法,系统默认调用父类的无参构造方法
- 子类构造方法通过super显式调用父类的有参构造方法,执行父类相应构造方法,而不执行父类无参构造方法
- 子类构造方法通过this显式调用自身的其他构造方法,在相应构造方法中应用以上两条规则
Object类
Object类是所有类的父类。在Java中,所有Java类都直接或间接地继承了java.lang.Object类。在定义一个类时,如果没有使用extends关键字(即无显式继承),则这个类直接继承Object类。
常见的Object类方法
toString(); //返回当前对象本身的有关信息,按字符串对象返回
equals(); //比较两个对象是否是同一个对象,是则返回true
clone(); //生成当前对象的一个副本,并返回
hashCode(); //返回该对象的哈希代码值
getClass(); //获取当前对象所属的类信息,返回Class对象
方法重写
在子类中可以根据需求对从父类继承的方法进行重新编写,这称为方法的重写或方法的覆盖(Overriding);重写时,可以用super.方法的方式来保留父类的方法;构造方法不能被重写。
方法重写的规则
- 方法名相同
- 参数列表相同
- 返回值类型相同或者是其子类
- 访问权限不能严于父类
- 父类的静态方法不能被子类覆盖为非静态方法,父类的非静态方法不能被子类覆盖为静态方法
- 子类可以定义与父类同名的静态方法,以便在子类中隐藏父类的静态方法(注:静态方法中无法使用super)
- 父类的私有方法不能被子类覆盖
- 不能抛出比父类方法更多的异常
方法重写实例
给Object类的equals方法重写
@Override
public boolean equals(Object book){
if (book instanceof Book){
Book book1 = (Book) book;
if (this.getBookId()==book1.getBookId() &&
this.getBookName().equals(book1.getBookName())){
return true;
}
}
return false;
}
//根据需求,只有当两本图书的编号Id和书名Name都相同时,
//两本书才会被判定为重复的书籍,因此改写.equals()方法
方法重写和重载
多态
多态一词在程序中,意味着一个特定类型的变量可以引用不同类型的对象,并且能自动地调用引用的对象的方法,也就是根据作用到的不同对象类型,响应不同的操作。方法重写是实现多态的基础
多态:同一个引用类型,使用不同的实例而执行不同操作
应用多态实例一
//Dog类
public class Dog extends Pet {
public void toHospital() {
this.setHealth(60);
System.out.println("狗狗打针、吃药");
}
}
//Penguin类
public class Penguin extends Pet {
public void toHospital() {
this.setHealth(70);
System.out.println("企鹅吃药、疗养");
}
}
//主人类
public class Master {
public void cure(Pet pet) {
if (pet.getHealth() < 50)
pet.toHospital();
}
}
//测试方法
Pet dog = new Dog();
Pet penguin = new Penguin();
Master master = new Master();
master.cure(dog); //体现了多态
master.cure(penguin);
//输出结果
狗狗打针、吃药
企鹅吃药、疗养
向上转型
上面代码里有展示Pet dog = new Dog();
这其实就是子类向父类的转换,称为向上转型:
<父类型><引用变量名> = new <子类型>();
向上转型的规则:
- 父类的引用指向子类对象,系统将自动进行类型转换
- 此时通过父类引用变量调用的方法是子类覆盖或继承父类的方法,不是父类的方法
注意: 此时通过父类引用的变量,无法调用子类特有的方法
向下转型
上文提到,通过父类引用的变量无法调用子类特有的方法;但如果此时需要调用子类特有的方法,可以通过把父类转换为子类来实现。
将一个指向子类对象的父类引用赋给一个子类的引用,即将父类类型转换为子类类型,称为向下转型,此时必须进行强制转换。
向下转型的语法:
<子类型><引用变量名> = (<子类型>)<父类型的引用变量>
Pet pet = new Dog();
pet.dogRun(); //dogRun是Dog类特有方法,故这里无法调用
Dog dog = (Dog) pet;
dog.dogRun(); //可执行Dog的特有方法
instanceof 运算符
在向下转型过程中,如果不是转换为真实子类类型,会出现类型转换异常
Pet pet = new Dog();
pet.toHospital();
Bird bird = (Bird) pet; //出错
于是我们可以用instanceof来判断
使用instanceof时,对象的类型必须和instanceof后面的参数所指定的类在继承上有上下级关系,否则会出现编译错误。
Pet pet = new Dog();
//Pet pet = new Bird();
pet.toHospital();
if (pet instanceof Dog) {
Dog dog = (Dog) pet;
dog.dogRun();
}else if (pet instanceof Bird) {
Bird bird = (Bird) pet;
bird.birdFly();
}
应用多态实例二
使用父类作为方法的形参
class Host{
public void letCry(Animal animal){
animal.cry();
}
}
public class Test {
public static void main(String[] args) {
Host host = new Host();
Animal animal;
animal = new Dog();
host.letCry(animal);
animal = new Cat();
host.letCry(animal);
animal = new Bird();
host.letCry(animal);
}
}
应用多态实例三
使用父类作为方法的返回值
class Host{
public Animal donate(String type){
Animal animal;
if (type.equals("dog"){
animal = new Dog();
}
if (type.equals("cat"){
animal = new Cat();
}
if (type.equals("bird"){
animal = new Bird();
}
return animal;
}
}
public class Test{
public static void main(String[] args) {
Host host = new Host();
Animal animal;
animal = host.donate("dog");
animal.cry();
animal = host.donate("cat");
animal.cry();
animal = host.donate("bird");
animal.cry();
}
}
抽象
抽象方法
在Java中,当一个类的方法被abstract关键字修饰时,该方法称为抽象类方法。抽象方法所在的类必须被定义为抽象类。
[访问修饰符] abstract<返回类型><方法名>([参数列表])
//abstract关键字表示该方法被定义为抽象方法
- 抽象方法需要用修饰符abstract修饰,普通方法不需要
- 普通方法有方法体,抽象方法没有方法体
抽象类
在Java中,当一个类被abstract关键字修饰时,该类被称为抽象类
abstract class <类名>
//abstract关键字表示该类被定义为抽象类
- 抽象类需要用修饰符abstract修饰,普通类不需要
- 普通类可以实例化,抽象类不能被实例化
定义一个抽象类:
public abstract class 类名称{
修饰符 abstract 返回类型 方法名();
修饰符 返回类型 方法名(){
方法体
}
}
如何使用
抽象类与抽象方法的使用:
- 抽象类中可以没有抽象方法,但包含了抽象方法的类必须被定义为抽象类
- 如果子类没有实现父类的所有抽象方法,子类必须被定义为抽象类
- 没有抽象构造方法,也没有抽象静态方法
- 抽象类中可以有非抽象的构造方法,创建子类的实例时可能调用
抽象的利弊
以手机游戏“愤怒的小鸟”举例,小鸟都有飞行、叫声、攻击等这些方法。虽然每只鸟都有攻击行为,但是每种小鸟的攻击方式各不相同。在Java程序中即是没有方法体,这里就可以用到抽象类与抽象方法,让每个对象(鸟)各自去实现自己的攻击行为。
然而,抽象类也有局限性。依然是“愤怒的小鸟”这款游戏,刚才还提到了鸟的叫声。不同的鸟的叫声可能不一样,也可能一样。比如小一点的鸟叫声一样,体型大一点的又不一样了,甚至有的鸟干脆不叫。此时,叫声的行为不再通用,如果使用抽象方法去解决这一问题,会造成代码冗余的情况。如何解决?请看下一个知识点,接口。
接口
Java中接口的作用和生活中的接口类似,它提供一种约定,使得实现接口的类(或结构)在形式上保持一致。
定义和实现
定义:
public interface 接口名 {
//接口成员
}
- 和抽象类不同,定义接口使用interface修饰符,访问修饰符只能是public,且可选
- 接口成员可以是全局常量和公共的抽象方法
与抽象类一样,使用接口也必须通过子类,子类通过implements关键字实现接口
实现:
public 类名 implements 接口名{
实现方法
普通方法
}
- 实现接口使用implements关键字
- 实现接口的类必须实现接口中定义的所有抽象方法
接口的特性
- 接口不可以被实例化
- 实现类必须实现接口的所有方法
- 接口中的变量默认都是public static final
- 接口中的方法默认都是public abstract
实例
public interface UsbInterface{
void service();
}
public class UDisk implements UsbInterface{
public void service(){
System.out.println("连接USB接口,开始传输数据!")
}
}
更复杂的接口
接口本身也可以继承接口
[修饰符] interface 接口名 extends 父接口1,父接口2...{
常量定义
方法定义
}
一个普通类只能继承一个父类,但能同时实现多个接口
class 类名 extends 父类名 implements 接口1,接口2...{
类的成员
}
异常
异常是指在程序的运行过程中所发生的的不正常事件。异常会中断正在运行的程序。
如果使用if-else语句进行异常处理,会有以下弊端:
1、代码臃肿
2、程序员要花很大精力“堵漏洞”
3、程序员很难堵住所有“漏洞”
这时候就需要用到异常处理机制。
Java异常体系结构
所有异常类型都是Throwable类的子类,它派生了两个子类:Error类和Exception类。
1、Error类:仅靠程序本身无法恢复的严重错误,如内存溢出、虚拟机错误等。
2、Exception类:由Java应用程序抛出和处理的非严重错误,如所需文件找不到、算术运算错位、数组下标越界等。它又可以分为两大类异常:
- 运行时异常:包括RuntimeException及其所有子类。不要求程序必须对它们进行处理。
- Checked异常:除了运行时异常的其他从Exception类继承来的异常类。
常见的异常类
异常处理的关键字
Java的异常处理是通过5个关键字来实现的:try、catch、 finally、throw、throws
try-catch块
第一种情况:正常
public void method(){
try {
// 代码段(此处不会产生异常)
} catch (异常类型 ex) {
// 对异常进行处理的代码段
}
// 代码段
}
第二种情况:出现异常
如果try语句在执行过程中发生异常,并且这个异常与catach语句块中声明的异常类型匹配,那么try语句块剩下的代码都将被忽略,而相应的catch语句块将会被执行
public void method(){
try {
// 代码段 1
// 产生异常的代码段 2
// 代码段 3,此时代码段3不执行
} catch (异常类型 ex) {
// 对异常进行处理的代码段4
}
// 代码段5
}
第三种情况:异常类型不匹配
public void method(){
try {
// 代码段 1
// 产生异常的代码段 2
// 代码段 3
} catch (异常类型 ex) {
// 对异常进行处理的代码段4
}
// 代码段5
}
处理异常信息
可以在catch块中处理异常,加入用户自定义处理信息
例如:
System.err.println("出现错误:被除数和除数必须是整数 ");
或者调用方法输出异常信息:
e.printStackTrace();
异常对象常用的方法:
try-catch-finally
无论是否发生异常,finally语句块中的代码总能被执行
存在return的try-catch-finally块
public void method(){
try {
// 代码段 1
// 产生异常的代码段 2
} catch (异常类型 ex) {
// 对异常进行处理的代码段3
return;
}finally{
// 代码段 4
}
}
即使在catch语句块中存在return语句,finally语句块中的语句也会被执行。发生异常时的执行顺序是,先执行catch语句块中return之前的语句,再执行finally语句块中的语句,最后执行catch语句块中的return语句退出。
多重catch块
一段代码可能会引发多种类型的异常,可以在一个try语句块后面跟多个catch语句块分别处理不同的异常
排列catch语句的顺序:先子类后父类
发生异常时按顺序逐个匹配
只执行第一个与异常类型匹配的catch语句
throws和throw
throws
如果在一个方法体中抛出了异常,并希望调用者能够及时地捕获异常,Java语言中通过关键字throws声明某个方法可能抛出的各种异常以通知调用者。
throws声明某个方法可能抛出的各种异常,多个异常用逗号隔开
public static void divide() throws Exception{
//代码块
}
throw
除了系统自动抛出的异常外,在编程过程中,有些问题是系统无法自动发现并解决的。
if (sex.equals("男") || sex.equals("女")) this.sex=sex;
else {
throw new Exception("性别必须是男或女!");
}
throws和throw比较
自定义异常
当JDK 中的异常类型不能满足程序的需要时,可以自定义异常类
使用自定义异常的步骤:
//构造方法1
public MyException() {
super();
}
//构造方法2
public MyException(String message) {
super(message);
}
//构造方法3
public MyException(String message, Throwable cause) {
super(message, cause);
}
//构造方法4
public MyException(Throwable cause) {
super(cause);
}
异常链
A方法调用B方法时,B方法却抛出了异常。那A方法继续抛出原有的异常还是抛出一个新异常呢?
异常链创建了新的异常但却保留了原有异常的信息。
写在最后
这是个人整理的面向对象知识点,包括了各种实际使用的代码示例和需要注意的细节。面向对象的知识点多且复杂,需要配合大量的上机练习来巩固。如果你发现我这篇博文有哪里写的不对或者还有我没提到的知识点,欢迎捉虫!