封装、继承和多态是面向对象结构的三大特性。
一、封装
1.什么是封装
封装顾名思义就是把一些细节给装起来,只展示需要的部分。
比如:我们用户使用电脑只需要用到鼠标,键盘。主机,显示屏等等,对于计算机核心部件像是电脑的主板,电脑的CPU,鼠标键盘的内部路线等等的一些细节就用外壳封装到计算机、鼠标、键盘等的内部。简单来说就是封装就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
2.访问限定符
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。
Java中提供了四种访问限定符:
二、继承
1.什么是继承
继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
2.为什么要用到继承
我们在定义类的时候,有可能会出现定义的几个类中,出现重复的成员变量和成员方法,那可不可以将这些重复的成员变量和成员方法抽取出来,定义新的类的时候,如果需要用到这些成员变量和成员方法的时候,直接调用,就不用自己再去定义重复的成员变量和成员方法,减少不必要的代码,完成共性的抽取,实现代码的复用。
eg:定义狗和猫两个类
class Dog { public String name; public int age; public String colour; public void sleep() { System.out.println(this.name+"正在睡觉!"); } public void dogeat() { System.out.println(this.name+"正在吃狗粮!"); } } class Cat { public String name; public int age; public String colour; public void sleep() { System.out.println(this.name+"正在睡觉!"); } public void cateat() { System.out.println(this.name+"正在吃猫粮!"); }
可以看出在狗和猫这两个类中出现了重复的成员变量和成员方法,如果我们接着定义像猪,马,猴,兔等等类,这些类中也包含了相同的成员变量和成员方法。这时我们就可以定义一个动物类,这个类包含了这些重复的成员变量和成员方法,这时狗这个类就可以继承动物这个类,那些重复的成员变量和成员方法在狗这个类中就不用再出现,但是狗这个类也是包含这些重复的成员变量和成员方法,因为狗继承了动物这个类。同理其他动物也可以这样,这样就实现共性的抽取,代码复用。
3.在Java中如何实现继承
在Java中如果要表示类之间的继承关系,需要借助extends关键字
eg:
class Animal { public String name; public int age; public String colour; public void sleep() { System.out.println(this.name+"正在睡觉!"); } } class Dog extends Animal{ public void eat() { System.out.println(this.name+"正在吃狗粮!"); } } class Cat extends Animal{ public void eat() { System.out.println(this.name+"正在吃猫粮!"); }
在上述代码中定义了一个Animal类、Dog类和Cat类,可以看出在Dog和Cat中抽取了重复的成员变量和成员方法定义在Animal类中,然后在定义Dog和Cat时使用extends,让Dog和Cat继承Animal。
这时我们在main方法中实例化一个狗和猫来看结果
public class Test { public static void main(String[] args) { Dog dog = new Dog(); Cat cat = new Cat(); dog.name = "旺财"; cat.name = "来福"; dog.sleep(); cat.sleep(); } } 运行结果:
在Dog类和Cat类中没有关于name和sleep(),但是却可以赋值和调用,这就是因为Dog和Cat继承了Animal。
注意:1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了。
eg:
class A { public int a = 1; } class B extends A { public int a =2; public void show() { System.out.println(a); } } public class Test { public static void main(String[] args) { B b = new B(); b.show(); } }
在上述代码中B类继承了A类但是在A类和B类中都存在成员变量a,那么b.a的值是多少?
代码结果:
从代码的结果可以看出,b.a的值是B类中的值。
可以得出结论,在子类方法中或者通过子类对象访问成员时:
1.如果访问的成员变量子类中有,优先访问自己的成员变量。
2.如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
3.如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员方法和成员变量类似:
1.通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
2.通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
那么如要在以上子类和父类成员变量同名的情况下,我们就是要访问父类的成员变量要怎么做呢?-->使用关键字super
4.super关键字
在某种场景下,可能会遇到子类和父类成员变量或成员方法同名的情况下,需要访问父类的成员变量或成员方法,这时就需使用super关键字,该关键字主要作用:在子类方法中访问父类的成员。
eg:
class A { public int a = 1; } class B extends A { public int a =2; public void show() { System.out.println(super.a); } } public class Test { public static void main(String[] args) { B b = new B(); b.show(); } } 代码结果:
super关键字的注意事项:
1. 只能在非静态方法中使用
2. 在子类方法中,访问父类的成员变量和方法。
5.子类的构造方法
eg:
class Dad { public Dad() { System.out.println("父类的构造方法"); } } class Son extends Dad { public Son() { System.out.println("子类的构造方法"); } } public class Test1 { public static void main(String[] args) { Son son = new Son(); } } 代码结果:
在上述代码中分别在构造方法中打印,当实例化对象的时候就会调用构造方法,从打印结果可以看出在调用构造方法时先调用父类的构造方法再调用子类的构造方法。
在上述的代码中,在Son类中并没有调用Dad类的方法,那为什么在调用子类的构造方法时会调用父类的构造方法?
其实在子类的构造方法中存在super(),当用户没有写时,编译器会默认调用基类的无参构造方法:super()。
注意点:
1.super()必须是子类构造方法中第一条语句。
2.super()在构造方法中只能出现一次,并且不能和this同时出现。
3.如果自己定义了带参数的super(),那么操作系统就不再提供不带参数的super(),如果要使用,需要自己再重载一个不带参数构造方法。
6.继承方式
在Java中的几种继承方式:
7.继承与组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(如:extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
比如:一辆汽车是由轮胎、发动机、方向盘、车载系统等组合成的。
eg:
// 轮胎类 class Tire{ // ... } // 发动机类 class Engine{ // ... } // 车载系统类 class VehicleSystem{ // ... } class Car{ private Tire tire; // 可以复用轮胎中的属性和方法 private Engine engine; // 可以复用发动机中的属性和方法 private VehicleSystem vs; // 可以复用车载系统中的属性和方法 // ... } // 奔驰是汽车 class Benz extends Car{ // 将汽车中包含的:轮胎、发送机、车载系统全部继承下来 }
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择。
三、多态
1.什么是多态
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
举个例子:在约一个女孩去吃饭的这件事情上,如果对象是男神,女孩就会答应,如果对象是舔狗,女孩就会拒绝。都是约女孩吃饭这同一件事情上,由于对象的不同,产生了不同的结果,这就是多态!(该例子仅仅是为了理解多态,无其他意思!)
2.向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。向上转型有三种使用场景:1. 直接赋值 2. 方法传参 3. 方法返回
eg:
class Dad { public void print() { System.out.println("父类!!!"); } } class Son extends Dad { public void show() { System.out.println("子类!!!"); } } class fun { public void fun1(Dad d) { System.out.println("向上转型!"); } //3. 作返回值:返回任意子类对象 public Dad fun2() { return new Son(); } } public class Test { public static void main(String[] args) { Dad d = new Son();//1.直接赋值:子类对象赋值给父类对象 // d.show() //err!!!不能调用子类的特有的方法 Fun f = new Fun(); //2. 方法传参:形参为父类型引用,可以接收任意子类的对象 f.fun1(new Son()); } }
在上述的main方法中就是创建一个子类对象,将其当成父类对象来使用。Dad是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。此时这个对象不能调用子类特有的方法。
3.方法重写
eg:
class Dad { public void print() { System.out.println("父类的print()"); } } class Son extends Dad { public void show() { System.out.println("子类的show()"); } } public class Test { public static void main(String[] args) { Dad d = new Son();//向上转型 d.print();//调用父类方法 } } 代码结果:
再看下面这个代码:
此时当在父类写一个和子类方法名一样的show()方法时,d对象调用的show()方法是父类的还是子类的?
eg:
class Dad { public void print() { System.out.println("父类的print()"); } public void show() { System.out.println("父类的show()"); } } class Son extends Dad { public void show() { System.out.println("子类的show()"); } } public class Test { public static void main(String[] args) { Dad d = new Son(); d.show(); } } 代码结果:
上面不是说向上转型后不能调用子类特有的方法吗?这里为什么又调用了子类的show()方法? 原因就是方法的重写!
重写也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写的规则:
1.子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型、方法名、参数列表、要完全一致
2.被重写的方法返回值类型可以不同,但是必须是具有父子关系的。
class Graph { public void draw() { System.out.println(" "); } } class Triangle extends Graph { public void draw() { //方法重写 System.out.println("△"); } } class Square extends Graph { public void draw() { //方法重写 System.out.println("□"); } } class Pentagram extends Graph { public void draw() { //方法重写 System.out.println("☆"); } } public class Test { public static void fun(Graph graph) { graph.draw(); } public static void main(String[] args) { Graph triangle = new Triangle();//向上转型 Graph square = new Square();//向上转型 Graph pentagram = new Pentagram();//向上转型 fun(triangle); fun(square); fun(pentagram); } } 代码结果:
上面代码中,实例化了3个不同的子类对象分别赋值给父类引用,这里发生了向上转型,接着通过父类引用调用父类的draw()方法,但由于每个子类都重写了draw()方法,所以调用draw()时发生动态绑定,根据对象调用自己的draw()方法,在使用者看来,都是调用draw()这个方法,但由于对象的不同,所产生的结果也不同,这就是多态。