封装、继承、多态,认识一下!
作为学习java必须了解的Java特性,跟随本文,认识一下它们吧
封装
最常见的操作,比如我们要定义一个Book类,会将其下定义的变量私有化,然后提供公开的get、set方法。那为什么要这样做呢?原因呢也很简单,我们不希望类以外的程序可以随意的修改我们已经定义好的变量,只能通过我们提供的公有方法来修改或获取;甚至有些字段我们可以只提供get方法,不提供set方法,那么外部就无法修改我们的变量。而这就是封装的基本表现了。
除此之外,有时我们也会对方法进行封装,比如我们有一段逻辑,要获取某作者最新出版的书籍的相关信息,那么就可以将这部分逻辑封装起来,成为一个独立的功能块。当外部想要获取这个信息时,只需要调用我们已经封装好的方法即可。比如下面这一段代码:
public class RecommendService {
BookService bookService;
// 伪代码
public Recommend getRecommendations(String authorName){
Recommend recommend = new Recommend();
// 1.对传参的非空判断等处理
// 2.根据authorName查询其最新出版的书籍
// [重点:不需要关心具体的实现,只要了解使用后的结果]
Book book = bookService.getLatestBookByAuthor(authorName);
// 3.组装要返回的推荐信息
return recommend;
}
}
这段代码中,特别要注意的第二点逻辑说明,这里,我们只想获取一条书籍的推荐信息,我们并不想关心具体的实现,可能这一段逻辑在项目多处使用了,那么封装起来,一是避免了重复代码的使用,二是当这段逻辑内部的实现有改动,只需要更改一处即可,极大的简化了代码的维护。这也是封装的意义所在——降低耦合。
最后简单总结一下封装的特性(意义):
- 私有化成员属性,避免类外部的程序对其随意的修改,只能通过提供的公有化方法操作属性。
- 隐藏方法的具体实现,使得外部的调用更自由,逻辑清晰。
- 将功能模块化,以降低耦合。
继承
继承,简单来说,就是为了实现代码的重用。继承的关键字是extends,extend意为扩展,“public class A extends B(){}”,从这段使用继承的基本结构中,可简单理解为就是A对B的扩展。而这里,引申出两个概念——子类(派生类),父类(基类,超类)。上述中,B的角色是父类,它就好比制作PPT时选用的模板,而A的角色是子类,它在模板(B)上去绘制,可以对模板里可修改的文字、图片进行修改。
以下面两段代码说明一下:
public class Sport {
// 名称
private String name;
public Sport(String name) {
this.name = name;
}
public void print(){
System.out.println("Sport");
}
}
public class Football extends Sport {
// 规则说明
private String rule;
public Football(String name, String rule) {
super(name); // 调用父类构造器
this.rule = rule;
}
public void print(){
System.out.println("我是足球");
}
}
public class Football extends Sport {
// 规则说明
private String rule;
public Football(String name, String rule) {
super(name);// 调用父类构造器
this.rule = rule;
}
// public void print(){
// System.out.println("我是足球");
// }
public static void main(String[] args) {
Football football = new Football("足球","...");
football.print();
}
}
这里,FootBall类继承了Sport类,有这样几点说明:
- FootBall类继承了Sport类,所以可以获取Sport类定义的name变量。而既然name属性是属于父类Sport的,那么就应该在Sport的构造器中为name属性赋值,而在Football类中的构造器,只需调用父类对应赋值的构造器即可。
- 额外说明的是Football类拥有了name属性,不代表在构造器中可以使用this.name=name来赋值,因为Sport类中name属性是私有化的,如果两个类是在同一个包下,则修改Sport类的那么属性为protected,会发现this.name=name来赋值不再报错,但是实际开发中,往往父类、子类不在同一个包下,所以不推荐这样使用。
- FootBall类继承了Sport类,就获取了父类里定义的print方法,所以,虽然Football类里没有定义print方法,依旧可以使用。注意:构造方法不能被继承。
- main方法中实例化Football时,其执行顺序是先进入Football类的构造器,然后执行Sport类初始化(构造),进而再开始Football类自己的定义初始化,最后再回到自己的构造器做剩下的事情。
- 关于构造器额外补充一点,Sport类有一个有参的构造器,那无参的构造器就没有了,当有第三个类想要继承Sport时,自动生成的构造器会有super()这一段,提示异常,因为其默认要走父类的无参构造器,解决方法就是在Sport类中增加无参的构造器。
继承虽然作为Java的基本特性之一,但需要慎重使用。不正确的使用继承容易造成逻辑混乱,难以理解和维护。一般来说,当两者是从属关系(比如猫和动物的关系)就可以用继承,而像电脑与显示器的关系,就不适合使用继承关系
多态
多态是最重要的表现特征,算得上是一个综合应用。
首先说说多态的必要条件:
- 存在继承关系
- 子类重写父类的方法
- 父类的引用指向子类对象
用更容易的代码来理解一下:
public class Sport {
// 名称
private String name;
public Sport(String name) {
this.name = name;
}
public void print(){
System.out.println("Sport");
}
}
public class Football extends Sport {
// 规则说明
private String rule;
public Football(String name, String rule) {
super(name);// 调用父类构造器
this.rule = rule;
}
@Override
public void print(){
System.out.println("我是足球");
}
}
public class Test {
public static void main(String[] args) {
Sport sport = new Football("football","rule"); // 向上造型
sport.print();
}
}
输出结果:我是足球
Football类继承了Sport类,在main方法中,首先完成了一个向上造型。所谓造型,就是一个类型的对象赋值给另外一个类型的变量。这个时候sport变量所管理的是Football类中的东西。在Football类中重写了print方法,那么此时调用sport.print()执行的就是Football类中重写的方法。
Java的对象变量都是多态的,有两种表现形式,一种叫声明式类型(静态类型),一种是动态类型。声明式类型就是直观看的到的类型,main方法中定义的sport变量,其声明式类型就是Sport,而其动态类型是Football。
这里明确一下:Java是不存在对象给对象的赋值,而是让两个对象的管理者管理同一个对象。
子类的对象可以赋值给父类的变量,但是反过来,父类的对象赋值给子类的对象是不成立的。要想实现,可以利用造型,但前提是sport实际管理的是Football类型才行。如图所示:
还有一种多态的表现形式是接口的实现。接口相当于一个对外提供一系列的规范化方法的控制者,它没有具体的实现,而多态的表现就在于接口的实现。在不同的场景下实现接口,就可以满足不同的需要。下面给出一个简单的例子:
public interface SportDao {
// 获取一项运动
public void getSport();
}
public class FootballDaoImpl implements SportDao {
@Override
public void getSport() {
System.out.println("我是足球");
}
}
public class BasketballDaoImpl implements SportDao {
@Override
public void getSport() {
System.out.println("我是篮球");
}
}
在不同的实现类中对接口里定义的方法进行重写,以完成不同的业务需要。
第三种形式就是继承抽象类,重写里面的抽象方法。抽象类或抽象方法的关键字是abstract。抽象类归根结底还是一个类,而他对于普通类来说特殊的地方只在于它不能实例化对象,而因此,他必须被继承才能使用。抽象类常常和接口拿来比较,这里给出一些总结:
- 一个类只能继承一个抽象类,但可以实现多个接口。
- 接口中的方法都是隐式抽象的,无方法体,不需要声明abstract关键字;而抽象类中的方法可以有方法体。
- 接口中不能含有静态代码块以及静态方法,而抽象类可以有。
- 接口中没有构造方法,而抽象类中可以有。
- 接口中新加入方法,java8之前实现该接口的类都需要实现新方法(java8中允许使用default方法来克服这个问题),而抽象类里新加方法,可以给一个默认实现。
- 接口中的成员变量只能是 public static final 类型的,接口中的类可使用的修饰符只有public,也可以是无修饰符的;而抽象类中的成员变量可以是各种类型的,抽象类中的方法也可以是各种类型的。
通过抽象类实现的多态表现在对抽象方法的重写上,代码示例如下:
public abstract class Sport {
private String a;
public void getSport(){
System.out.println("获取运动");
}
public abstract void print();
}
public class FootballService extends Sport {
@Override
public void print() {
System.out.println("我是足球");
}
}
public class BasketballService extends Sport {
@Override
public void print() {
System.out.println("我是篮球");
}
}
以上。