面向对象编程的三大特征是封装、继承和多态。
封装(Encapsulation):封装是面向对象编程的基本特征之一。它指的是将数据和操作数据的方法封装在一个单独的单位中,使其成为一个独立的、可复用的模块。通过封装,我们可以隐藏对象的细节,只暴露必要的接口给外部使用者。封装可以提高代码的安全性、可读性和可维护性。
继承(Inheritance):继承是指一个对象(称为子类)可以从另一个对象(称为父类)继承属性和方法。子类可以使用父类的属性和方法,并且可以在此基础上进行扩展或修改。继承使得代码的复用变得更加容易和高效。通过继承,可以创建一个层次结构的类,使得类与类之间形成关系,提高代码的可理解性和可维护性。
多态(Polymorphism):多态是指同一个方法或操作可以被多个不同类型的对象调用,并且可以根据对象的实际类型来执行不同的操作。多态性可以通过重载(方法名相同,参数列表不同)、重写(子类重写父类的方法)和接口实现来实现。多态可以提高代码的灵活性和可扩展性,使得代码更加通用和适应变化。
封装是面向对象编程中的一个重要概念,它指的是将数据和操作数据的方法封装在一个单独的单位中,形成一个独立的模块。封装的目的是隐藏实现的细节,只暴露必要的接口给外部使用者。
类是对象的抽象模板,它定义了一组属性和方法,描述了对象的特征和行为。
在类中,我们可以定义属性(成员变量)和方法(成员函数)来表示对象的状态和行为。属性表示对象的数据,而方法表示对象的操作。通过封装,我们可以将属性和方法组织在一起,形成一个独立的单元,使得相关的代码集中在一起。
封装的好处有以下几个方面:
隐藏实现细节:
封装使得对象的实现细节对于使用者来说是不可见的,使用者只需要关注对象的公共接口,无需了解对象内部的具体实现,这样可以保护数据的安全性和完整性。
提高代码的安全性:
通过封装,可以对数据进行访问控制,只暴露必要的接口给外部,从而避免了对数据的非法访问或篡改。可以通过定义访问修饰符(如私有、公有等)来控制属性和方法的访问权限。
提高代码的可读性和可维护性:
封装使得代码更加模块化,每个类只负责特定的功能,减少了代码的复杂性。这样可以使代码更易于阅读、理解和维护,并且有利于代码的重用。
代码的复用和扩展:
通过封装,我们可以将通用的功能封装在一个类中,然后在其他类中进行调用和复用。这样可以提高代码的复用性,减少冗余代码的编写。同时,封装也为代码的扩展提供了便利,可以通过继承和多态等方法进行功能的扩展和修改。
在封装的过程中,我们可以使用访问修饰符来控制属性和方法的访问权限:
私有访问修饰符(private):
私有属性和方法只能在类的内部访问,外部无法直接访问。私有属性通常用于存储对象的状态,而私有方法可以被公共方法或其他私有方法调用,用于实现对象的行为。
公有访问修饰符(public):
公有属性和方法可以被类的外部访问,外部可以通过对象来访问和修改这些属性,以及调用这些方法。
保护访问修饰符(protected):
保护属性和方法可以被类的内部和子类访问,但对于类的外部来说是不可见的。保护属性和方法通常用于实现类的继承。
private
关键字private关键字是一种访问修饰符,用于限制类的成员(属性和方法)的访问权限。
在使用private关键字修饰的成员中,只能在同一个类的内部进行访问,而在类的外部是不可见的。
具体来说,当将属性或方法声明为private时,表示它们只能在当前类的内部范围中被访问。这意味着其他类或对象无法直接访问私有成员,只有在当前类的方法中才能使用私有成员。
例如:
public class Person {
private String name; // 私有属性
private void sayHello() { // 私有方法
System.out.println("Hello, my name is " + name);
}
public void introduce() { // 公有方法
sayHello(); // 在类的内部调用私有方法
}
public void setName(String newName) { // 公有方法
name = newName; // 在类的内部访问私有属性
}
}
在上面的示例中,Person
类有一个私有属性name
和一个私有方法sayHello()
。这些私有成员只能在类的内部被访问和使用。然而,Person
类还有两个公有方法introduce()
和setName(String newName)
,它们是可以被外部类或对象调用的。
对于外部类或对象来说,它们无法直接访问私有属性name
或私有方法sayHello()
。但是,通过公有方法introduce()
或setName(String newName)
,外部类或对象可以间接地访问和修改私有成员。在introduce()
方法内部,通过调用私有方法sayHello()
来访问私有成员name
。
这种通过private关键字限制访问权限的机制,可以实现对数据和方法的封装,保证了数据的安全性和代码的可维护性。
只有需要的操作才能被外部使用,并且内部的实现细节对外部是不可见的。
public
关键字使用公有访问修饰符public
关键字,可以将属性、方法或类定义为公有的,表示它们可以在任何地方被访问。
具体来说,使用public
关键字修饰的成员或类,可以在任何地方被访问或调用,包括同一包内的其他类、不同包内的其他类和在其他JAR文件中的类。这使得公有成员和类成为Java程序中最常用的接口,以便让代码在多个模块或组件之间进行共享和交互。
例如:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
在这个示例中,HelloWorld
类被定义为公有类,因此在其他类中可以访问和调用它。类中的main
方法也是公有方法,并且因为它被定义为公有,所以其他类可以在不同的地方调用此方法,执行输出“Hello, world!”的操作。
【注意】在使用public
关键字时,需要遵循以下几点:
只有一个类可以被定义为公有类。在Java中,一个源文件只能包含一个公有类,且必须与文件名相同。
公有方法、属性和类必须使用关键字public
进行修饰,否则它们将默认为包级私有访问权限。
公有成员通常用于定义API或库,供其他用户或开发者使用。
对于公有类中的公有方法和属性,尽可能保持简单明了、易于使用,并且需要提供必要的文档和例子。
【示例】当我们使用public
访问修饰符时,表示该成员或类是公开的,可以在任何地方进行访问。
public class Person {
public String name; // 公有属性
public void introduce() {
System.out.println("Hello, my name is " + name);
}
}
在上面的示例中,Person
类有一个公有属性name
,它可以被其他类直接访问和修改。公有属性可以在类的外部通过对象来访问和修改属性的值。
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
在上面的示例中,Calculator
类有一个公有方法add()
,它可以被其他类直接调用。公有方法可以在类的外部通过对象来调用,并返回执行结果。
public class Student {
public String name;
public int age;
public void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
在上面的示例中,Student
类是一个公有类,它的属性name
和age
以及方法displayInfo()
都是公有的。这意味着其他类可以创建Student
对象,并访问和使用其公有属性和方法。
公有访问修饰符提供了最高级别的访问权限,因此在使用`public`关键字时,需要谨慎设计和使用公有成员。
公有成员的使用应遵循良好的编程实践和封装原则,确保公有成员的合理、安全和一致使用。
protected
关键字当我们使用protected
访问修饰符时,意味着该成员或类可以在其所属的类、子类和同一包中进行访问。
protected
访问修饰符有以下特点:
protected
成员。protected
成员。protected
成员。【示例】
protected
属性public class Vehicle {
protected String brand;
protected void honk() {
System.out.println("Honk honk!");
}
}
public class Car extends Vehicle {
private String modelName;
public Car(String modelName) {
this.modelName = modelName;
}
public void displayInfo() {
System.out.println("Brand: " + brand + ", Model: " + modelName);
}
}
在上面的示例中,Vehicle
类有一个protected
属性brand
和一个protected
方法honk()
。Car
类是Vehicle
类的子类,可以直接访问继承自父类的brand
属性。例如:
public class Main {
public static void main(String[] args) {
Car myCar = new Car("Toyota");
myCar.brand = "Toyota";
myCar.displayInfo(); // Brand: Toyota, Model: Toyota
myCar.honk(); // Honk honk!
}
}
protected
构造方法public class Animal {
protected String name;
protected Animal(String name) {
this.name = name;
}
protected void sleep() {
System.out.println(name + " is sleeping.");
}
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
public void play() {
System.out.println(name + " is playing.");
}
}
在上面的示例中,Animal
类有一个受保护的构造方法和一个受保护的sleep()
方法。Cat
类是Animal
类的子类,可以通过调用super(name)
来调用父类的受保护构造方法。例如:
public class Main {
public static void main(String[] args) {
Cat myCat = new Cat("Tom");
myCat.play(); // Tom is playing.
myCat.sleep(); // Tom is sleeping.
}
}
通过使用`protected`访问修饰符,我们可以在类的继承层次结构中限制对特定成员的访问。
它提供了一种受限的访问权限,仅允许在类层次结构中相关的类中访问。
这有助于实现封装和继承的概念,同时控制对类的成员的访问级别。
在进行封装时,有一些注意事项需要遵循,以确保代码安全性和可维护性。
以下是几个封装的注意事项:
封装私有字段:使用私有访问修饰符(private
)来限制对类的字段的直接访问。通过提供公共方法(getters和setters)来控制对这些字段的访问。这样可以防止外部直接修改字段的值,同时可以在方法中添加额外的逻辑来保证数据的完整性。
封装隐藏实现细节:尽量隐藏实现细节,只暴露必要的接口。这样可以避免其他部分代码对内部实现进行依赖,从而提高代码的灵活性和可维护性。可以使用private
、protected
和包级私有(默认)等访问修饰符来限制对类的可见性。
提供合适的访问级别:根据设计需求,为类的成员选择合适的访问级别(private
、protected
、public
等)。避免暴露不必要的细节,同时为其他类提供必要的访问接口,以平衡封装性和可用性。
使用文档注释:为类、方法和字段提供清晰的文档注释,以明确说明其用途、预期行为和允许的输入。这样可以帮助其他开发人员更好地理解并正确使用代码。
一个类可以继承另一个类的属性和方法。
被继承的类称为父类(或基类、超类),继承它的类称为子类(或派生类)。
通过继承,子类可以直接获得父类的属性和方法,而不需要从头开始编写这些代码。父类通常是一个抽象的通用概念,而子类则是对父类的特化和补充。
在继承关系中,子类可以通过以下方式使用继承的父类功能:
继承属性:子类继承了父类的字段和变量,这意味着子类可以直接访问和使用这些属性。
这样可以避免在子类中重复声明相同的属性,减少代码的冗余。
继承方法:子类也继承了父类的方法,这意味着子类可以直接调用自己的方法,也可以调用从父类继承而来的方法。
这使得子类可以扩展和重写父类的功能。
扩展功能:子类可以添加额外的属性和方法,以满足子类特定的需求。
这样,子类可以在父类基础上进行定制化的功能扩展,提供更具体和特殊的行为。
继承的一般格式如下:
class 父类 {
// 父类的属性和方法
}
class 子类 extends 父类 {
// 子类特有的属性和方法
}
其中,class 父类
定义了父类,而 class 子类 extends 父类
定义了子类,并通过关键字 extends
表示子类继承自父类。
例如:
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + "正在进食");
}
}
class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name);
this.breed = breed;
}
public void bark() {
System.out.println(name + "(品种:" + breed + ")正在叫");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog("小黑", "哈士奇");
// 父类的方法
myDog.eat();
// 子类的方法
myDog.bark();
}
}
在上面的例子中,Animal
类是一个父类,它有一个 name
属性和一个 eat()
方法。Dog
类是 Animal
类的子类,通过关键字 extends
继承了 Animal
类的属性和方法,并添加了一个名为 breed
的属性和一个 bark()
方法。
在 Main
类的 main
方法中,我们创建了一个 Dog
类的对象 myDog
,并使用 myDog.eat()
调用了父类的 eat()
方法,使用 myDog.bark()
调用了子类的 bark()
方法。
通过继承,子类 Dog
获得了父类 Animal
的属性和方法,可以进行代码的重用。在这个例子中,子类 Dog
扩展了父类 Animal
的功能,实现了特定类型的动物——狗的行为。这样,通过继承,可以实现代码的组织和封装,提高代码的可维护性和可扩展性。
继承具有以下几个特点:
继承可以实现代码重用
父类已经定义好了一些通用的属性和方法,子类可以通过继承父类,直接获得这些属性和方法。子类不需要重新编写这些通用的代码,可以在父类的基础上进行扩展和修改。
继承可以提高代码的可维护性
如果我们需要修改一些通用功能,我们只需要修改父类中对应的方法或属性,所有子类将自动继承这个更新后的功能。这种修改只需要在父类中进行一次即可,大大提高了代码的可维护性。
继承可以实现多态
子类可以重写父类的方法,从而改变方法的行为和实现。当我们使用父类的引用来调用子类重写的方法时,实际调用的是子类重写后的方法。这种灵活性称为多态,可以方便地实现程序的扩展和解耦。
继承可以实现分类和层次结构
继承可以将一组相关的类通过抽象出共性的属性和方法,形成一个类的层次结构。每个子类都可以继承父类的属性和方法,并可以扩展自己的特定属性和方法,从而实现分类和层次结构。
继承具有一些重要的原则:
单继承:
在大多数面向对象编程语言中,一个类只能直接继承一个父类,即单继承。这是为了避免继承体系的复杂性和不确定性。然而,通过使用接口、混合和多态等概念,可以实现多继承的一些效果。
继承链(多层继承):
继承关系可以形成一个继承链,其中一个子类可以成为另一个子类的父类。这样,继承关系可以逐级扩展和派生,形成更复杂的继承体系。
覆盖和重写:
如果一个子类对父类的方法进行了重新定义,就称为方法的覆盖或重写。这允许子类根据自己的需求重新实现父类的方法,修改其行为或添加额外的逻辑。通过使用关键字 super
,子类还可以调用父类的被重写的方法。
继承的可见性:
继承并不意味着子类能够直接访问父类的私有成员。子类只能继承和访问父类的可见成员,即那些被声明为 public
或者 protected
的成员。
在继承关系中,子类通常可以继承父类的属性和方法,但有一些东西是子类不能继承的:
私有成员:
子类无法继承父类的私有方法,可以继承私有变量,但是无法访问私有变量(既无法使用继承下来的私有变量),私有方法只能在父类内部访问,子类无法直接访问或继承。
构造方法:
子类不能继承父类的构造方法。子类需要自己定义构造方法,并在其中通过 super
关键字调用父类的构造方法来初始化父类的部分。
类的初始化块:
子类无法继承父类的静态初始化块和实例初始化块。这些初始化块只在对应类的对象和类加载过程中执行,子类无法直接访问或继承。
父类的静态成员和静态方法:
子类可以继承父类的静态成员和静态方法,但不能重写它们。子类可以通过父类名直接访问父类的静态成员和静态方法。
this
关键字this
关键字用于引用当前对象(即调用该关键字的对象)的成员变量和方法。它可以在构造函数、实例方法和实例初始化块中使用。
在子类中,如果需要访问子类自己的成员变量或调用子类自己的方法时,可以使用 this
关键字。通过 this
关键字可以解决父类和子类中成员变量或方法同名的问题。
例子:
class Parent {
int num = 10;
void display() {
System.out.println("Parent class display method");
}
}
class Child extends Parent {
int num = 20;
void display() {
int num = 30;
System.out.println("Child class display method");
System.out.println("Value of num in Child: " + num);
System.out.println("Value of num in Child: " + this.num);
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.display();
}
}
输出结果:
Child class display method
Value of num in Child: 30
Value of num in Child: 20
在上述示例中,子类 Child
中的 display
方法使用了 this
关键字来引用子类自己的成员变量 num
super
关键字super
关键字用于引用父类的成员变量、方法和构造函数。它可以在子类中使用来调用父类的成员或构造函数。当子类和父类有同名的成员变量或方法时,可以使用 super
关键字来访问父类的成员。
在子类中,如果需要访问父类的成员变量或调用父类的方法时,可以使用 super
关键字。
例子:
class Parent {
int num = 10;
void display() {
System.out.println("Parent class display method");
}
}
class Child extends Parent {
int num = 20;
void display() {
int num = 30;
System.out.println("Child class display method");
System.out.println("Value of num in Child: " + num);
System.out.println("Value of num in Parent: " + super.num);
super.display();
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.display();
}
}
输出结果:
Child class display method
Value of num in Child: 30
Value of num in Parent: 10
Parent class display method
在上述示例中,子类 Child
中的 display
方法使用 super
关键字来引用父类的成员变量 num
和父类的方法 display
。
在使用继承时,需要注意以下几个事项:
继承要符合“is-a”关系原则
子类应该是父类的一种特殊情况,但不是所有的情况都适合使用继承。子类和父类之间应该存在一种符合“is-a”关系的特殊关系,比如狗是一种动物,所以狗可以继承动物类。如果没有这种特殊关系,则不应该使用继承。
“is-a”关系原则是指在继承关系中,子类应该是父类的一种特殊情况或类型。
也就是说,当我们说一个类A继承自另一个类B时,类A应该符合类B的定义,并且能够表达是类B的一种特殊情况。
例如,如果有一个Animal类,其中定义了一些通用的属性和方法,然后有一个Dog类,我们可以说“Dog是一种动物”,这符合“is-a”关系原则。
Dog类继承Animal类,从而获得了Animal类的通用属性和方法,并可以通过重写方法来实现自己特定的行为。
然而,如果有一个Car类,我们不能说“Car是一种动物”,这不符合“is-a”关系原则。
Car类和Animal类之间并没有一种特殊关系,它们有不同的概念和属性,所以不适合使用继承来表示它们之间的关系。
子类不能访问父类中的私有成员
父类中的私有成员是被封装的,子类不能访问这些成员。如果需要访问父类中的私有成员,可以使用父类提供的公共方法或受保护方法,或者使用反射机制进行访问。但在大多数情况下,我们应该避免直接访问父类中的私有成员,保持封装性原则。
子类可以继承父类的非私有成员
子类可以自由地继承父类的非私有成员,包括公有成员、受保护成员和默认(包)成员。但子类不能直接访问父类中的静态私有成员和构造方法。
子类可以覆盖父类的方法
子类可以重新定义或覆盖父类的普通方法(即虚方法表中的方法),从而改变方法的行为和实现。当然,在重新定义方法时需要注意方法的返回类型、参数、修饰符和异常,不要破坏方法的调用约定和语义。
虚方法表(Virtual Method Table,VMT)是一种用于实现动态分派的机制。它是类的元数据结构之一,用于存储类中的虚方法的地址。
每个类都有一个与之对应的虚方法表,它包含了该类中声明的所有虚方法。虚方法表中的每一项都是一个函数指针,指向对应方法的具体实现。
当通过一个对象调用虚方法时,Java虚拟机(JVM)会根据该对象的实际类型在虚方法表中查找对应的方法地址,然后调用具体的实现。
虚方法表只适用于非私有、非静态和非 final 的方法。私有方法和静态方法在 Java 中无法被重写,而 final 方法由于是最终的,无法被子类覆盖。
父类的引用可以指向子类的对象
由于子类包含父类的所有成员,所以可以使用父类类型的引用来指向子类的实例对象,这种行为称为类的多态。在多态的情况下,只能访问引用的类型所定义的方法和成员,而不能访问子类自己定义的成员。如果要使用子类自己定义的成员,必须将引用转换为子类类型。
多态(Polymorphism)指的是同一类型的对象在不同的情况下表现出不同的行为。
在多态的情况下,通过父类引用变量可以引用子类对象,并且在调用方法时会根据对象的实际类型来确定具体执行哪个对象的方法。
简单来说,多态允许我们使用统一的接口来操作不同的对象,从而实现灵活和可扩展的代码。
使用多态需要满足以下两个前提条件:
继承关系:多态要求存在父类和子类之间的继承关系。
子类继承了父类的属性和方法,并且可以通过方法重写改变方法的实现。
方法重写:在子类中重写(覆盖)了父类中的方法。
子类重写的方法必须具有相同的方法签名(方法名和参数列表),这样才能保证两个方法在调用时能够互相替代。
父类引用变量:需要使用父类引用变量来引用子类的对象,即将子类对象赋值给父类引用变量。
运行时绑定:子类对象必须在运行时绑定到父类引用变量。
在运行时通过父类引用变量调用方法时,会根据对象的实际类型来确定调用哪个子类对象的方法,而不是在编译时就确定调用哪个方法。
调用成员变量时:编译看左边,运行看左边
成员变量:在多态情况下,通过父类引用变量访问成员变量时,编译器会根据引用变量的类型(左边)来确定访问的成员变量。无论实际引用的是不是子类对象,都会访问到父类中定义的成员变量。
举例:假设有一个父类Animal和一个子类Dog,其中Animal类有一个成员变量name。
当通过Animal类型的引用变量animal访问成员变量时,编译器会根据animal的类型(左边)来决定访问的是父类Animal的name成员变量。
调用成员方法时:编译看左边,运行看右边
成员方法:在多态情况下,通过父类引用变量调用成员方法时,编译器会根据引用变量的类型(左边)来确定要调用的方法。但是在运行时,方法的执行取决于实际引用对象的类型(右边)。即编译时根据左边确定方法的签名,运行时根据右边确定具体执行的方法实现。
举例:假设有一个父类Animal和一个子类Dog,其中Animal类有一个方法makeSound()。
当使用Animal类型的引用变量animal调用makeSound()方法时,编译器会根据animal的类型(左边)来决定调用的是父类Animal的makeSound()方法。
而实际运行时,如果animal引用的是Dog类的对象,那么将执行Dog类重写的makeSound()方法。
例如:
Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
在Java中,多态中的类型转换主要包括两种形式:向上转型(Upcasting)和向下转型(Downcasting)。
向上转型(Upcasting):
向上转型是指把一个子类类型的对象赋值给父类类型的引用变量。这种转型是隐式的,编译器会自动进行类型转换。
这种转型可以安全地进行,因为子类对象是可以当作父类对象来使用的。即父类引用类型的对象可以引用子类对象,但只能访问父类中定义的成员。
例子:
Animal animal = new Dog(); // 向上转型,将子类Dog对象赋值给父类Animal引用变量
animal.eat(); // 调用父类Animal的eat()方法
向下转型(Downcasting):
向下转型是指将一个已经向上转型的父类类型的引用变量,转换回原来子类类型的对象。这种转型是显式的,需要进行强制类型转换。
这种转型需要在运行时进行,因为编译器无法确定实际运行时对象的类型。
在进行向下转型之前,需要使用instanceof
运算符进行类型检查,以避免类型转换异常(ClassCastException)。
例子:
Animal animal = new Dog(); // 向上转型,将子类Dog对象赋值给父类Animal引用变量
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型,将父类Animal引用变量转换为子类Dog对象
dog.bark(); // 调用子类Dog的bark()方法
}
instanceof
运算符instanceof
是一个Java运算符,用于检查一个对象是否属于某个特定类或其子类的实例。
它的语法格式为:
object instanceof class
其中,object
是要进行判断的对象,class
是要判断的类名。
instanceof
运算符的返回结果是一个布尔值,如果对象是指定类或其子类的实例,则返回true
,否则返回false
。
使用instanceof
运算符可以在进行向下转型(Downcasting)时进行类型检查,以避免类型转换异常(ClassCastException)。通过在转型之前使用instanceof
,我们可以判断一个对象是否是某个特定类的实例,然后再决定是否进行类型转换。
例如:
Animal animal = new Dog(); // 向上转型,将子类Dog对象赋值给父类Animal引用变量
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型,将父类Animal引用变量转换为子类Dog对象
dog.bark(); // 调用子类Dog的bark()方法
} else {
System.out.println("animal 不是 Dog 的实例");
}
在使用多态时,我们需要注意以下几点:
父类引用指向子类对象时,只能调用父类和子类中重写父类方法的方法,不能调用子类独有的方法。
父类中的静态方法不能被子类重写,因此使用父类引用调用静态方法时,只会调用父类的静态方法,而不是子类的方法。
在向下转型时,需要进行类型检查(instanceof
运算符),避免出现类型转换异常。
如果父类和子类中有相同的成员变量名,父类引用访问该变量时会访问到父类的变量,子类引用访问该变量时会访问到子类的变量。