java 三大特性之封装、继承、多态
一、封装
- 什么是封装?
- 为什么要使用封装?这里介绍下它的优点
- 怎么封装?举一个简单的例子
1. 什么是封装?
将类中属性和方法的实现逻辑进行隐藏,而对外只提供接口
2. 为什么要使用封装?这里介绍下它的优点
- 良好的封装能够减少耦合。
- 类内部的结构可以自由修改。
- 可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节。
下面说下我的理解。
- 其他程序只调用提供的接口,只要接口形式不变,功能OK,则接口的属性及方法内部实现可以不用关心,便于降低程序之间的耦合性,提高系统的扩展性和可维护性。
- 类中的属性及方法内部实现可以根据需求变化进行修改,只要对外提供的接口不变,则外部便不需要修改。
- 外部只能通过提供的接口对类中的属性进行操作,避免了外部直接操作类属性可能会出现的意外。
3. 怎么封装?举一个简单的例子
public class PackageTest {
private String userName;
private int age;
public String getName() {
return userName;
}
public void setName(String name) {
this.userName = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getUserInfo() {
return "userName=[" + this.userName + "], age=[" + this.age + "]";
}
}
从上面代码可以看出:
- 根据代码可以看出PackageTest类中的userName和age这两个属性是该类私有的,不对外公开。
- PackageTest类对外提供了5个方法(2个属性的get和set方法,1个获取用户信息的方法),外界可以通过2个属性的get和set方法来对PackageTest类的属性进行操作。
- PackageTest类的getUserInfo()方法,可以根据需求进行显示格式的修改或其他操作,而外部不会对调用的接口进行改动。
二、继承
- 什么是继承?
- 为什么要继承?
- 怎么使用继承?
- 继承的特点
- 示例
1. 什么是继承?
继承就是子类继承父类中访问权限为public,protected,default的属性和方法,使得子类具有父类相同的属性和方法,并且子类具有父类所不具有的一些属性、方法。
2. 为什么要继承?
- 提高代码的复用率
- 让类与类之间产生了关系,是多态的前提。
3. 怎么使用继承?
- 继承的关键字
extends
- 继承的格式
public class 子类名 extends 父类名{...}
4. 继承的特点
- java支持单继承,支持多重继承,但是不支持多继承。
1.1 支持单继承的意思就是:子类只能有一个父类。
1.2 支持多重继承的意思是:类A继承类B,类B继承类C,即类C是类B的父类,类B是类A的父类。
1.3 不支持多继承的意思是:类A有且只能继承一个父类。 - 子类拥有父类中访问权限为public,protected,default的属性和方法。若需要对父类中的属性或方法进行修改,则需要使用super关键字进行引用,如
super.父类变量名
,或者super.父类方法名(参数1...)
- 子类拥有父类所没有的本身特有的属性和方法。
- 提高了类之间的耦合性
- 子类不能继承父类的构造器
5. 示例
class A{
int a = 0;
public A(){
System.out.println("执行了A的构造函数");
}
public void showInfo(int b){
System.out.println("父类的showInfo方法被执行了......."+b);
}
}
public class B extends A {
int a = 1;
public B(){
System.out.println("执行了B的构造函数");
}
public void showInfo(int b){
System.out.println("子类中的showInfo方法被执行了......");
System.out.println("a:"+a);
System.out.println("super.a:"+super.a);
super.a = 88;
System.out.println("super.a:"+super.a);
}
public static void main(String args[]){
A a = new B();
a.showInfo(24);
System.out.println("**");
A a1 = new A();
a1.showInfo(24);
System.out.println("**");
B b = new B();
b.showInfo(24);
}
}
执行结果为:
执行了A的构造函数
执行了B的构造函数
子类中的showInfo方法被执行了…
a:1
super.a:0
super.a:88
**
执行了A的构造函数
父类的showInfo方法被执行了…24
**
执行了A的构造函数
执行了B的构造函数
子类中的showInfo方法被执行了…
a:1
super.a:0
super.a:88
注:上面代码时子类重写了父类的showInfo方法,在调用时调用的子类的方法。如果要调用父类的方法,在可以在子类重写的方法中使用
super.showInfo(参数)
来进行调用,抑或子类不重写父类方法,前提是父类的showInfo方法是public,protected,default修饰的。
##三、多态 ##
- 什么是多态?
- 向上转型
- 怎么使用多态?
- 经典实例
1. 什么是多态?
程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。
即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变。即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
简单来说就是:同一方法的调用作用于不同对象,可以有不同的解释,导致不同的执行结果。
2. 向上转型
实现多态有三个必要条件(继承、重写、向上转型),向上转型是其中之一。
向上转型指:子类引用的对象转换为父类类型。
如:Flower f = new Rose();// 子类Rose继承父类Flower,此父类对象也可以是接口
举个例子:
public class Animal {
public void eat(){
System.out.println("animal eatting...");
}
}
public class Cat extends Animal{
public void eat(){
System.out.println("我吃鱼");
}
}
public class Dog extends Animal{
public void eat(){
System.out.println("我吃骨头");
}
public void run(){
System.out.println("我会跑");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Cat(); //向上转型
animal.eat();
animal = new Dog();
animal.eat();
}
}
//结果:
//我吃鱼
//我吃骨头
上图父子类的关系如下图:
这就是向上转型,Animal animal = new Cat();
将子类对象 Cat 转化为父类对象 Animal。这个时候 animal 这个引用调用的方法名是父类的eat(),但是方法实现是子类的eat()方法。
转型过程中需要注意的问题
- 向上转型时,子类单独定义的方法会丢失。比如上面Dog类中定义的run方法,当animal引用指向Dog类实例时是访问不到run方法的,
animal.run();
会报错。 - 子类引用不能指向父类对象。
Cat c = (Cat)new Animal();
这样是不行的。
向上转型的好处
- 减少重复代码,使代码变得简洁。
- 提高系统扩展性。
3. 怎么使用多态?
实现多态的三个必要条件:继承、重写、向上转型
需要了解的知识点:当父类对象的引用变量(f)引用子类对象(Rose)时,被引用对象的类型(Rose)决定了调用谁的成员方法,引用变量类型(Flower)决定可调用的方法。如果子类中没有覆盖该方法,那么会去父类中寻找。
也就是:可以调用的方法名由父类决定,调用谁的成员方法实现由子类决定。
但是这也不是唯一的,主要还是遵从继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
4. 经典实例
class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
class C extends B{
}
class D extends B{
}
public class Demo {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
//结果:
//1--A and A
//2--A and A
//3--A and D
//4--B and A
//5--B and A
//6--A and D
//7--B and B
//8--B and B
//9--A and D
//能看懂这个结果么?先自己分析一下。
前三个,强行分析,还能看得懂。但是第四个,大概你就傻了吧。为什么不是b and b呢?
这里就要学点新东西了。
当父类对象引用变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。如果子类中没有覆盖该方法,那么会去父类中寻找。
可能读起来比较拗口,我们先来看一个简单的例子
class X {
public void show(Y y){
System.out.println("x and y");
}
public void show(){
System.out.println("only x");
}
}
class Y extends X {
public void show(Y y){
System.out.println("y and y");
}
public void show(int i){
}
}
class main{
public static void main(String[] args) {
X x = new Y();
x.show(new Y());
x.show();
}
}
//结果
//y and y
//only x
- Y 继承了 X,覆盖了 X 中的
show(Y y)
方法,但是没有覆盖show()
方法。 - 这个时候,引用类型为X的 x 指向的对象为 Y,这个时候,调用的方法由 Y 决定,会先从 Y 中寻找。执行
x.show(new Y());
,该方法在 Y 中定义了,所以执行的是 Y 里面的方法; - 但是执行
x.show();
的时候,有的人会说,Y 中没有这个方法啊?它好像是去父类中找该方法了,因为调用了 X 中的方法。 - 事实上,Y 类中是有
show()
方法的,这个方法继承自 X,只不过没有覆盖该方法,所以没有在 Y 中明确写出来而已,看起来像是调用了 X 中的方法,实际上调用的还是 Y 中的。
这个时候再看上面那句难理解的话就不难理解了吧。X是引用变量类型,它决定哪些方法可以调用;show()和 show(Y y) 可以调用,而 show(int i)不可以调用。Y 是被引用对象的类型,它决定了调用谁的方法:调用 y 的方法。
上面的是一个简单的知识,它还不足以让我们理解那个复杂的例子。我们再来看这样一个知识:
继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
如果你能理解这个调用关系,那么多态你就掌握了。我们回到那个复杂的例子:
abcd 的关系是这样的:
C/D —> B —> A
我们先来分析4 : a2.show(b)
- 首先,a2是类型为A的引用类型,它指向类型为B的对象。A确定可调用的方法:show(D obj)和show(A obj)。
- a2.show(b) ==> this.show(b),这里this指的是B。
- 然后.在B类中找show(B obj),找到了,可惜没用,因为show(B obj)方法不在可调用范围内,this.show(O)失败,进入下一级别:super.show(O),super指的是A。
- 在A 中寻找show(B obj),失败,因为没用定义这个方法。进入第三级别:this.show((super)O),this指的是B。
- 在B中找show((A)O),找到了:show(A obj),选择调用该方法。
- 输出:B and A
如果你能看懂这个过程,并且能分析出其他的情况,那你就真的掌握了。
我们再来看一下9:b.show(d)
- 首先,b为类型为B的引用对象,指向类型为B的对象。没有涉及向上转型,只会调用本类中的方法。
- 在B中寻找show(D obj),方法。现在你不会说没找到了吧?找到了,直接调用该方法。
- 输出 A and D。
现在再去理解这句话:当父类对象引用变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。如果子类中没有覆盖该方法,那么会去父类中寻找。继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。