第五章 第二节 面向对象(面向对象的三大特性、抽象类和接口)
1.面向对象的三大特性
面向对象的三大特性:封装、继承、多态
抽象就是指,将对象的特性抽象为属性,对象的行为抽象成方法。
1.1封装
编写类时,将功能相同的或相近的代码写到一个类中,这就是一个封装的过程。(比如之前我们写的Student类,将学生的姓名、学号、性别、年龄等写到一个类中,这就是封装)
在Java中为了更好地保护类不被外部干扰,还需要使用JavaBean的方式编写类。
1)类的属性使用private修饰
如上图所示,相当于就是把Student的属性全部暴露在了外面,外面能进行直接修改。
从外部能100%的控制内部。但是外部如果能修改容易出现输入错误—>使用private修饰,
则不能从外部更改使用了:如下图:已经定义成private的age只能在类的内部访问,
在类的外部访问就会报错。
既然private修饰的内容只能在内部访问,那么我们如何在内部进行更改?在外部如何访问?---->给外部一定的
权限
2)为属性添加公共的Get和Set方法
Get方法:获取属性的值;Set方法:设置属性的值;
在内部的Set方法里面可以添加限定条件,如下图所示:在外部只需要进行调用Set方法进行传入参数即可,并
且如下图所写的内部setAge方法会根据条件进行判断再确定是否进行修改设置值的操作。
如果想在外部输入age:如下图所示,调用getAge方法:
总结:
广义上的封装:编写类的过程就是一个封装的过程。
狭义的封装:隐藏类的实现细节,更好地保护类的内部。
封装的步骤:
1) 将所有的属性私有化,使用private关键字修饰。
2) 为所有的属性提供公共的get和set方法。
Eclispe提供了快速生成get和set方法的生成器。
还可以借助第三方的工具,lombok。
右键Source或者上面菜单Source,选择Generate Getters and Setters,在弹出的窗口右侧选择select all;
再点击ok即可。就会自动添加对应的get和set方法。
如下图封装的完整代码示例:
高内聚,低耦合
1.2继承
继承是指类与类之间的关系,也就是泛化。继承是类与类之间最密切的一种关系,是很强的一种关系。
在定义一个类时,可以使用extends关键字指定它的父类。
语法:
public class 子类名 extends 父类名 {
}
被继承的类就是父类,基类(base class),超类(super class)。
继承父类的类就是子类,派生类,扩展类。
1.2.1继承的特性
继承是为了代码复用。
1) 子类会继承父类的所有属性和方法,但是私有的不会继承。
比如下图中父类定义为:Person
在子类中我们没有定义name属性,但是可以用,是从父类Person里面继承过来的
2) 子类可以扩展只属于自己的属性和方法。
3) 子类还可以重写父类的属性和方法,覆盖父类的属性和方法。
注意:
1)子类会继承父类的私有属性和方法,只是不能直接访问(可以通过Set、Get方法间接访问)
2)子类不会继承父类的构造方法,但是调用子类构造方法时,会先调用父类的构造方法。
1.2.2隐藏属性
如果子类中声明与父类中名字相同的属性,父类的属性将被隐藏,子类对象只能访问子类中重新定义的属性。
隐藏属性类型可以不同。
1.2.3方法的重写
方法的重写也叫方法的复写,也叫方法的覆盖。
在继承关系下,子类中出现了和父类方法名以及参数列表都相同的方法,这种情况子类重写了父类的方法。
注意:
1) 子类重写方法的返回值类型不能大于父类方法的返回值类型。
2) 子类重写方法的权限修饰符不能小于父类方法的权限修饰符。
3) 私有的方法不算重写。
4) 子类重写方法时,可以使用@Override注解检查是不是重写的父类方法。
1.3多态
多态:多种形态
多态是指相同或近似的代码,在执行时表现出不同的形态,调用不同的方法。
在Java中,多态有两种体现形式,编译时多态和运行时多态。(方法的重载、方法的重写)
1.3.1编译时多态
在代码编译时,决定调用哪个方法。主要体现方式是方法的重载。
方法的重载:
在一个类中,出现了方法名相同,但是参数列表不同的方法。
重载方法在调用时,由实参的数据类型决定调用哪个方法。
方法的确定是代码编译时就可以确定的
因方法的重载带来的多态,称为编译时多态。
1.3.2运行时多态
代码在运行时,才能决定应该调用哪个方法。主要体现方式是方法的重写。
方法的重写:
在继承关系下,子类中出现了和父类方法名以及参数列表(参数的数据类型、顺序、数量)都一样的方法,子类重写了父类的方法。
注意:
1)子类方法的参数数量,类型和顺序必须与父类方法一样
2)子类方法的参数名可以与父类不一样
3)子类方法的返回值类型必须小于等于父类方法的返回值类型,如果是引用数据类型必须一样,引用数据类型子类方法的返回值类型可以是父类方法返回值的子类(小于),等于也行。
4)private的方法不能被子类重写
5)子类方法的权限修饰符必须大于等于父类方法的权限修饰符。
上转型对象: 子类创建的对象,保存在父类类型的变量中。
上转型对象的特点:
1) 上转型对象只能调用父类声明的属性和方法。(父类中定义的方法和父类继承来的方法)
2) 上转型对象在调用子类重写的方法,执行的是子类中的方法。
这种多态,只有在代码运行时才能确定调用哪个,称为运行时多态。
总结:我们在执行方法时,是站在对象类型的角度上执行的;在调用时,是站在变量类型的角度上进行调用的。
1.4练习
public class Demo02 {
public static void main(String[] args) {
A a = new A(); // a是A类型,值也是A类型
A b = new B(); // b是A类型,值是B类型,b是一个上转型对象
C c = new C(); // c是C类型,值也是C类型
// 重载方法在调用时,由实参的数据类型决定调用哪个方法。
// 上转型对象只能调用父类声明的属性和方法。
// 上转型对象在调用子类重写的方法,执行的是子类的方法。
// a 对象可以调用哪些方法
// A.show(A)
// A.show(B)
a.show(a); // 参数a的类型是A,A.a
a.show(b); // 参数b的数据类型是A, A.a
a.show(c); // 参数c的类型是C, B类更接近,A.b
// b是个上转型对象 b只能调用父类中声明的方法
// b对象可以调用的方法,
// A.show(A)
// A.show(B) => B.show(B)
b.show(a); // 参数a的类型是A, A.a
b.show(b); // 参数b的数据类型是A, A.a
b.show(c); // 参数c的类型是C, B类更接近, B.b
// c可以调用什么方法
// A.show(A)
// A.show(B) => B.show(B)
// B.show(C)
c.show(a); //参数a的类型是A, A.a
c.show(b); //参数b的数据类型是A, A.a
c.show(c); //参数c的类型是C, B.c
}
}
class A {
public void show(A a) {
System.out.println(“A.a”);
}
public void show(B b) {
System.out.println(“A.b”);
}
}
class B extends A {
public void show(B b) {
System.out.println(“B.b”);
}
public void show(C c) {
System.out.println(“B.c”);
}
}
class C extends B {
}
2.抽象类
2.1抽象类
什么是抽象类?
抽象类是一特殊的类,被abstract关键字修饰的类就是抽象类。
在Java中,有些类不适合创建对象,这时可以把类定义成抽象类。
抽象类不能创建对象,但是作为父类使用。如下图所示,抽象类创建对象会报错:
抽象类可以像普通类一样,有属性、方法、构造器、块和静态块、内部类,还可以继承父类,实现接口。
抽象类的使用必须依赖于它的子类。
public abstract class 类名 {
类体;
}
2.2抽象方法
被abstract关键字修饰的方法就是抽象方法 。抽象方法只有方法的定义,没有方法的实现(没有方法体)。抽象方法不能有方法体,结尾必须以;结束。如下图:
抽象方法必须由子类重写/实现。
修饰符 abstract 方法名(参数列表);
2.3抽象类与抽象方法的关系
1)抽象类可以拥有类所拥有的一切,包括:属性,方法,构造方法等;
抽象类可以有抽象方法,也可以没有。
2) 有抽象方法的类必须是抽象类。
抽象方法是一个不完整的方法,抽象类可以包含抽象方法,抽象类也就是一个不完整的类,由不完整的类创建对象,行为就是不确定的,Java中不允许出现这种对象。因此抽象类不能创建对象。
为什么还要有抽象类和抽象方法?
在抽象过程中,有些类的行为是不确定的,这些行为不能有具体的实现,因此必须依赖抽象方法进行描述。
有面向对象的过程中,有些类就不应该直接创建对象,这样的类就应该定义成抽象类。
比如:教学管理系统,在这个系统中包含两种角色学生和老师,学生和老师存在共性,都有姓名,性别,就需要有一个父类人管理这些共性,同时实现代码复用。人是不能直接创建对象的。
抽象类的子类有两个选择:
1) 实现抽象类中的所有抽象方法,子类就可以是一个普通类
2)如果没有实现所有的抽象方法(会继承父类方法),子类必须是一个抽象类。
接口的实现类类似。
3.接口
接口是一种比抽象类更抽象的数据类型。
接口的特点:
1) 接口中的属性,默认使用public static final修饰,也就是公共的静态常量。
2) 接口中的方法,默认使用public abstract修饰,也就是公共的抽象方法。
3) 接口也是不能创建对象的。
接口语法:
public interface 接口名{
静态常量;
抽象方法;
}
接口不能创建对象,因此接口的使用必须依赖于它的实现类。
修饰符 class 类名 implements 接口名1, 接口名2{
类体
}
注意:
Java中一个类最多只能有一个直接父类,单继承。
(理解:门 防火门 防火防盗门)
可以让一个类同时实现多个接口来解决单继承的问题,称为多实现。
一个接口也可以继承另外一个接口,使用extends
接口也可以继承父接口的所有方法,而且也是单继承。
jdk1.8中新特性:
1)函数式接口:接口中只有一个抽象方法
2)允许接口中有静态方法
3)允许接口中有默认方法
注意:
接口的实现类,要么实现接口中所有的抽象方法,要么实现类就只能是一个抽象类。
3.1为什么要使用接口
在Java中一个类最多只能有一个直接的父类,这叫类的单继承。
一个类可以同时实现多个接口,这叫多实现,多实现是为了弥补单继承的缺陷。
接口也可以继承,接口的继承是多继承的。
面向接口编程
3.2面试常见问题
- 抽象类与接口的异同??
相同点:抽象类和接口都不能创建对象,抽象类和接口都可以拥有抽象方法。
不同点:
1) 抽象类是类,接口是接口,它们是不同的数据类型。
2) 抽象类可以包含属性,方法,构造方法,接口只能拥有静态常量和抽象方法。
3) 抽象类的使用依赖于它的子类,子类是使用extends关键字继承,接口的使用依赖于它的实现类,使用implements关键实现接口。
4) 类的继承是单继承,接口的继承是多继承。
- JDK8的新特性
1) 函数式接口:在一个接口中,如果只包含一个抽象方法,这个接口就叫函数式接口。
@FunctionalInterface 检查函数式接口
2) 接口静态方法:在接口中可以定义静态方法,可以拥有方法体。
3) 接口默认方法:接口中可以使用default修饰方法,默认方法可以有方法体,实现类可以选择重写也可以不重写。