1. OOP的三大特性有什么
- 封装
- 继承
- 多态
2. 封装
2.1 什么是封装
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
2.2 为什么要封装
- 封装可以被认为是一个?;て琳希乐垢美嗟拇牒褪荼煌獠坷喽ㄒ宓拇胨婊梦?。
- 封装是为了保护数据,这里的数据是指对象的属性(不是方法的局部变量)。
保护数据中的?;な鞘裁匆馑???;な侵付允莸亩列慈ㄏ蕖?/p>
?;な侨绾问迪值模炕蛘咚捣庾笆侨绾问迪值??是通过访问修饰符实现的。
2.3 访问修饰符
访问修饰符共有四个
- public:公共的
- protected:受保护的
- 默认|default(什么都不写)
- private:私有的
2.3.1 封装之Public
public修饰的属性没有访问限制,是最宽松的封装。
public class Animal{
public String dog;
}
dog被public修饰,可以在任何地方被访问以及被读写。
public修饰的类、属性和方法在任何地方都能被访问。
2.3.2 封装之protected
protected是为继承而设计的访问修饰符
protected修饰的属性和方法在子类中一定可以访问,同一个包可以访问,子类在不同包也可以访问。
package com.sunmer.tmp;
public class Father {
protected int age;
int weight;
}
package com.sunmer.tmp;//同包
public class Sub1 extends Father{
public static void main(String[] args) {
Sub1 sub1 = new Sub1();
sub1.age= 1;//可以访问
sub1.weight = 1;//可以访问
}
}
package com.sunmer.oop;//不同包
import com.sunmer.tmp.Father;
public class Sub2 extends Father {
public static void main(String[] args) {
Sub2 sub2 = new Sub2();
sub2.age = 0;//可以访问
}
}
package com.sunmer.oop;//不同包
import com.sunmer.tmp.Father;
public class Sub3 extends Sub2 {
public static void main(String[] args) {
Sub2 sub2 =new Sub2();
sub2.age = 0;//不可访问
Sub3 sub3 =new Sub3();
sub3.age = 0;//可以访问
}
}
2.3.3 封装之默认(default)
默认访问修饰符是指什么都不写
默认访问修饰符修饰的类、属性和方法只能在同一个包里被访问
default可以修饰类、属性和方法
package packageone;
public class Father {
int num1=1;
void print(){
System.out.println("哒哒哒!");
}
}
package packageone;//同一个包
public class Test {
public static void main(String[] args) {
Father father = new Father();
father.num1=1;//可以访问
father.print();//可以访问
}
}
package packagetwo;//不同包
import packageone.Father;
public class Test {
public static void main(String[] args) {
Father father = new Father();
father.num1 = 1;//不可访问
father.print();//不可访问
}
}
在不同包里,会提示:
'print()' is not public in 'packageone.Father'. Cannot be accessed from outside package
2.3.4封装之private
private修饰的属性只能在同一个类里被访问,是最严格的封装
public class Father {
private int num1=1;
private void print(){
System.out.println("哒哒哒!");
}
}
public class Test {
public static void main(String[] args) {
Father father = new Father();
father.num1=1;//不可访问
//'num1' has private access in 'packageone.Father'
father.print();//不可访问
//'print()' has private access in 'packageone.Father'
}
}
private不能修饰类
private class Father{//报错,private不能修饰类
}
如果我们非要访问private封装的属性怎么办呢?
可以使用getter和setter方法访问
最典型封装是:共有方法封装私有属性
getter和setter方法
public class Father {
private int num1=1;
public int getNum1() {//给私有成员可读权限
return num1;
}
public void setNum1(int num1) {//给私有成员可写权限
this.num1 = num1;
}
}
public class Test {
public static void main(String[] args) {
Father father = new Father();
father.getNum1();//可读取变量,但不能改
father.setNum1(1);//可以改写变量,给变量赋值
}
}
2.4 表格展示
位置 | public | protected | 默认修饰符 | private |
---|---|---|---|---|
同类访问 | √ | √ | √ | √ |
同包其他类访问 | √ | √ | √ | × |
同包子类访问 | √ | √ | √ | × |
不同包子类访问 | √ | √ | × | × |
不同包非子类访问 | √ | × | × | × |
3. 继承
3.1 生活中的继承
在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物,同理,狸花猫和波斯猫继承自猫,而牧羊犬和秋田犬继承自狗。这些动物之间会形成一个继承体系,具体如下图所示。
3.2 Java中的继承
类和类之间有三种关系
is a :继承关系,例如:公共汽车 is a 汽车
use a:使用关系,例如:人 use a 钳子
has a:包含关系,例如:人has a 胳膊
3.2.1 继承的基本概念
继承可以解决编程中代码的冗余问题,是实现代码重用的重要手段之一。
继承是软件可重用性的一种表现,新类可以在不增加自身代码的情况下,通过从现有的类中继承其属性和方法,来实现充实自身内容,这种现象或行为就称之为继承。此时新类称之为子类,现有类称为父类。
继承最基本的作用是使得代码可以重用,即一个类只能有一个直接父类。
3.2.2 为什么要继承
- 继承的出现提高了代码的复用性,提高软件开发效率。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
3.2.3 继承的语法规则
在程序中,如果想要声明一个类继承另一个类,需要使用extends关键字。
class 子类 extends 父类{
}
3.2.4 继承的使用
定义鸟类bird
public class Bird {
private String name;
public Bird(String name) {
this.name = name;
}
public Bird() {
}
public void sound(){
System.out.println("吱吱吱。。。");
}
public void fly(){
System.out.println("时速300km/h中。。。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
定义猫类cat
public class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
public Cat() {
}
public void sound(){
System.out.println("喵喵喵");
}
public void climbTrees(){
System.out.println("上树中......");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在这两个类中我们看到有很多相同的属性和方法,既然有重复的地方我们可以使用继承了
定义动物类Animal
将鸟类和猫类相同的属性和方法写在动物类中
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public Animal() {
}
public void sound(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后将两个类作为子类继承动物类,并且将之前的相同代码删除,留下不同属性方法。
public class Bird extends Animal{
public Bird(String name) {
super(name);
}
public Bird() {
}
public void fly(){
System.out.println("时速300km/h中。。。");
}
}
public class Cat extends Animal{
public Cat() {
}
public Cat(String name) {
super(name);
}
public void climbTrees(){
System.out.println("上树中......");
}
}
注意:
- 继承中的super(调用父类成员)
- super调用父类构造函数,必须是子类构造函数的第一行
- super只能出现在子类(子类的普通方法或构造方法)中,而不是其他位置
- 具有访问权限的限制,如无法通过super访问父类private成员
- 无法继承父类的构造方法
验证子类是否继承到了父类的数据
public class Test {
public static void main(String[] args) {
Bird bird = new Bird();//实例化子类对象
Cat cat = new Cat();//实例化子类对象
bird.setName("鸟爷");//通过父类的属性方法为子类赋值
cat.setName("猫哥");//通过父类的属性方法为子类赋值
System.out.println(bird.getName());
System.out.println(cat.getName());
}
}
结果为:
鸟爷
猫哥
结果说明子类是继承了父类的数据的。
对象的属性可以通过set方法赋值
3.2.5 方法重写
如果子类从父类继承的方法不能满足子类的需要,或者不适合子类的需要。
此时子类可以将从父类继承的方法重写定义成满足自己需要的方法。
重新定义称为重写。
在上边的例子中,鸟和猫都是有一个相同发出叫声的sound方法的,但是鸟叫和猫叫显然是不一样的,不能满足子类的需要,这时可以使用方法重写
public class Bird extends Animal{
public Bird(String name) {
super(name);
}
public Bird() {
}
@Override//方法重写
public void sound() {
System.out.println("吱吱吱!");
}
public void fly(){
System.out.println("时速300km/h中。。。");
}
}
public class Cat extends Animal{
public Cat() {
}
public Cat(String name) {
super(name);
}
@Override//方法重写
public void sound() {
System.out.println("喵喵喵");
}
public void climbTrees(){
System.out.println("上树中......");
}
}
调用一下两个子类sound方法
public class Test {
public static void main(String[] args) {
Bird bird = new Bird();
Cat cat = new Cat();
bird.sound();
cat.sound();
}
}
结果为:
吱吱吱!
喵喵喵
实现了具体叫声的不同
方法重写的注意事项
- 方法重写时,方法的返回值类型 方法名 参数列表都要与父类一样。(同名,同参,同返回)
- 子类方法覆盖父类方法,必须要保证权限大于等于父类权限
class Fu{
void show(){}
public void method(){}
}
class Zi extends Fu{
public void show(){} //扩大show的访问权限,编译运行没问题
void method(){} //缩小method的访问权限,编译错误
}
- 方法重写时,子类不能缩小父类抛出的异常
Bird bird = new Bird("鸟爷");//构造方法直接赋值
Cat cat = new Cat("猫哥");//构造方法直接赋值
3.2.6 创建子类对象时的运行规则
- 第一个方面:构造函数的调用顺序问题
- new子类时,首先调用子类构造函数,但是不执行子类构造函数
- 子类构造函数被调用后立即调用父类的构造函数。
- 第二个方面:属性初始化顺序的问题
- 从Object类开始初始化
- 然后依次从父到子的顺序初始化(哪个类定义的属性,就由哪个类负责初始化)
代码编写步骤:
第一步:重构父类构造
- 首先为父类添加构造,父类的构造为父类中定义的属性初始化
- 子类定义的特有属性不是父类负责初始化的。
第二步:重构子类构造
- 子类构造应该获得所有属性的初始值,本例中共6个属性,其中5个是父类的,1个是自己的
- 子类构造的第一行必须是调用父类构造,
- 如果不写super(),那么默认再子类第一行添加调用父类无参的构造super()
- 子类再调用父类构造代码super()的下面负责给自己的属性赋值
第三步:重构实例化子类对象
- 再测试类中new子类时,要为子类传入属性的参数
第四步:测试
3.2.7 继承的注意事项
- 类只支持单继承,不允许多继承
- 多个类子可以继承一个父类
- 允许多层继承
class A{}
class B extends A{} // 类B继承类A,类B是类A的子类
class C extends B{} // 类C继承类B,类C是类B的子类,同时也是类A的子类
- 子类和父类是一种相对的概念
- 所有类都有一个父类object,如果一个类没有显示定义父类,那么默认继承Object类
4. 多态
4.1 认识多态
多态一词的通常含义是指能够呈现出多种不同的形式或形态。而在程序设计术语中,它意味着一个特定类型的变量,可以引用不同类型的对象,并且能自动的调用引用的对象的方法。也就是根据用到的不同对象类型,响应不同的操作。
方法重写是实现多态的基础。
多态的实现方法有两种
- 方法重写(公认的多态)
- 方法重载(非公认的多态)
4.2 多态的语法格式
多态的格式:父类引用指向子类实例(即为里氏替换原则)
父类类型 变量名 = new 子类类型();
变量名.方法名();
Pet pet = new Cat();
pet.toString();
4.3 多态的实现
public class Bird extends Animal{
public Bird(String name) {
super(name);
}
public Bird() {
}
@Override//方法重写
public void sound() {
System.out.println("吱吱吱!");
}
public void fly(){
System.out.println("时速300km/h中。。。");
}
}
public class Cat extends Animal{
public Cat() {
}
public Cat(String name) {
super(name);
}
@Override//方法重写
public void sound() {
System.out.println("喵喵喵");
}
public void climbTrees(){
System.out.println("上树中......");
}
}
多态表现为:
public class Test {
public static void main(String[] args) {
Animal animal = new Bird("鸟爷");//父类引用指向子类实例
animal.sound();
animal = new Cat("猫哥");//父类引用指向子类实例
animal.sound();
}
}
结果为:
吱吱吱!
喵喵喵
4.4 多态的转型
多态的转型分为向上转型与向下转型两种:
- 向上转型就是父类引用指向子类对象
- 向下转型就是子类引用指向父类对象
向上转型
向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
使用格式:
父类类型 变量名 = new 子类类型();
向下转型
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。
如果是直接创建父类对象,是无法向下转型的!
使用格式:
子类类型 变量名 = (子类类型) 父类类型的变量;
4.5 instanceOf运算符解决子类扩展方法的调用
多态有优势,但是也有劣势,向上转型后,只能使用父类共性的内容,而无法使用子类特有的功能。
可以通过instanceOf运算符来解决这个问题
public class Bird extends Animal{
public Bird(String name) {
super(name);
}
public Bird() {
}
@Override//方法重写
public void sound() {
System.out.println("吱吱吱!");
}
public void fly(){
System.out.println("时速300km/h中。。。");
}
}
public class Cat extends Animal{
public Cat() {
}
public Cat(String name) {
super(name);
}
@Override//方法重写
public void sound() {
System.out.println("喵喵喵");
}
public void climbTrees(){
System.out.println("上树中......");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Bird("鸟爷");//构造方法直接赋值
animal.sound();
if ( !(animal instanceof Bird) ){
System.out.println("类型不匹配,不能转换");
return;
}else {
Bird bird = (Bird) animal;//向下转型,实现子类特有的功能
bird.fly();
}
}
}
结果为:
吱吱吱!
时速300km/h中。。。