Java中包-继承-组合-多态-抽象类知识点总结

1.认识包

包 (package)是组织类的一种方式.使用包的主要目的是保证类的唯一性.
在编写 Java 程序时,随着程序架构越来越大,类的个数也越来越多,这时就会发现管理程序中维护类名称也是一件很麻烦的事,尤其是一些同名问题的发生。有时,开发人员还可能需要将处理同一方面的问题的类放在同一个目录下,以便于管理。
为了解决上述问题,Java 引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题
包主要有三个功能:

  1. 区分相同名称的类。
  2. 能够较好地管理大量的类。
  3. 控制访问范围。

1.1将类放到包中

Java 中使用 package 语句定义包

package 包名;

基本规则

  1. 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
  2. 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
  3. 包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
  4. 如果一个类没有 package 语句, 则该类被放到一个默认包中

操作步骤

  1. 在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包
  2. 在弹出的对话框中输入包名, 例如 com.bit.demo1
  3. 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可
  4. 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了
  5. 同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句
    例如(com.pack.c):
    在这里插入图片描述

1.2包的使用

想使用其他包中的其他类的方法如下:Java 引入了 import 关键字,import 可以向某个 Java 文件中导入指定包层次下的某个类或全部类。import 语句位于 package 语句之后,类定义之前。一个 Java 源文件只能包含一个 package 语句,但可以包含多个 import 语句。
例如(指定包中的指定类):

import 包名+类名;

例如(指定包(example)中的全部类):

import example.*;

给大家举例几种系统包

  1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
  2. java.lang.reflect:java 反射编程包;
  3. java.net:进行网络编程开发包。
  4. java.sql:进行数据库开发的支持包。
  5. java.util:是java提供的工具程序包。(集合类等) 非常重要
  6. java.io:I/O编程开发包

2.继承

2.1背景

代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法).
有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联.
下面我斗胆来解释一下我所理解的继承:所谓继承,在我们实际的敲代码中,我们可能会遇到一些这样的问题,比如说你写了三个描述动物的类,一个是描述狗狗,一个是描述小猫咪,一个是描述小鸟。在没学习继承前,比如狗狗的特征有名字和吃这个行为,小猫咪的特征有名字和吃这个行为以及跳的行为,小鸟的特征有名字以及吃的行为和飞的行为,显然我们写每一个类的时候都得重复得敲名字和吃这个行为的代码。这样的话严重的拖拉了我们的进度。这个时候继承就被人创造出来了,只要写一个动物的类其中包涵名字和吃这个行为。我们在别的地方再次用到的时候就可以直接使用继承关键词将动物这个类传递下去
此时, Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类, 对于像 CatBird 这样的类, 我们称为 子类, 派生类和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果

2.2继承的语法是怎样的

基本语法:

class 子类 extends 父类 { 
}
  1. 使用 extends 指定父类.
  2. Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
  3. 子类会继承父类的所有 public 的字段和方法.
  4. 对于父类的 private 的字段和方法, 子类中是无法访问的.
  5. 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用

3.组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.
例如表示一个学校

public class Student { 
 ... 
} 
public class Teacher { 
 ... 
} 
public class School { 
 public Student[] students; 
 public Teacher[] teachers; 
}

总体而言,继承是一种is a的关系,组合类似于一种has a的关系,例如,鸵鸟是一种鸟,那么鸵鸟类就应该继承鸟类。鸟有翅膀,那么鸟类和翅膀类就应该是一种组合关系。

3多态

多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承重写向上转型

3.1向上转型

背景
为啥叫 “向上转型”?
在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 “向上转型” , 表示往父类的方向转

向上转型其实就是:由子类转型成基类,在继承图上是向上移动的。同时由于是从较专用类型向较通用
类型转换,所以是安全的,因为专用类型肯定包含通用类型。
并且有下面几种特点:

        1.把子类对象直接赋给基类类引用,不用进行强制转换

        2.引用可以直接使用基类中的方法,但是不能使用导出类的方法

        3.从子类向上转型,永远不需要知道正在处理的对象的确切类型

        4.在向上转型过程中可能发生类接口中方法丢失

下面我们来看一个例子:

Bird bird = new Bird("圆圆"); 
Animal bird2 = bird; 
// 或者写成下面的方式
Animal bird2 = new Bird("圆圆");

此时 bird2 是一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例. 这种写法称为 向上转型.
向上转型这样的写法可以结合 is - a 语义来理解.
例如, 我让我媳妇去喂圆圆, 我就可以说, “媳妇你喂小鸟了没?”, 或者 “媳妇你喂鹦鹉了没?”

3.2动态绑定

当子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢?
对前面的代码稍加修改, 给 Bird 类也加上同名的 eat 方法, 并且在两个 eat 中分别加上不同的日志

// Animal.java 
public class Animal { 
 protected String name; 
 public Animal(String name) { 
 this.name = name; 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小动物"); 
 System.out.println(this.name + "正在吃" + food); 
 }
} 
// Bird.java 
public class Bird extends Animal { 
 public Bird(String name) { 
 super(name); 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小鸟"); 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Animal animal1 = new Animal("圆圆"); 
 animal1.eat("谷子"); 
 Animal animal2 = new Bird("扁扁"); 
 animal2.eat("谷子"); 
 } 
} 
// 执行结果
我是一只小动物
圆圆正在吃谷子
我是一只小鸟
扁扁正在吃谷子

此时, 我们发现:
animal1animal2 虽然都是 Animal 类型的引用, 但是 animal1 指向 Animal 类型的实例, animal2 指向
Bird 类型的实例.
针对 animal1animal2 分别调用 eat 方法, 发现 animal1.eat() 实际调用了父类的方法, 而
animal2.eat() 实际调用了子类的方法.
因此, 在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引
用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.

3.3理解多态

代码示例: 打印多种形状

class Shape { 
 public void draw() { 
 // 啥都不用干
 } 
} 
class Cycle extends Shape { 
 @Override 
 public void draw() { 

 System.out.println("○"); 
 } 
} 
class Rect extends Shape { 
 @Override 
 public void draw() { 
 System.out.println("□"); 
 } 
} 
class Flower extends Shape { 
 @Override 
 public void draw() { 
 System.out.println("♣"); 
 } 
} 
/我是分割线// 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Shape shape1 = new Flower(); 
 Shape shape2 = new Cycle(); 
 Shape shape3 = new Rect(); 
 drawMap(shape1); 
 drawMap(shape2); 
 drawMap(shape3); 
 } 
 // 打印单个图形
 public static void drawShape(Shape shape) { 
 shape.draw(); 
 } 
}

在这个代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的.
当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关), 这种行为就称为 多态

使用多态的好处是什么?

  1. 类调用者对类的使用成本进一步降低.
    封装是让类的调用者不需要知道类的实现细节.
    多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
    因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低.
    这也贴合了 <<代码大全>> 中关于 “管理代码复杂程度” 的初衷.
  2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else

4.抽象类

在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class)

abstract class Shape { 
 abstract public void draw(); 
}

draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码).对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类
注意事项

  1. 抽象类不能直接实例化.
Shape shape = new Shape(); 
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
  1. 抽象方法不能是 private
abstract class Shape { 
 abstract private void draw(); 
} 
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstractprivate
  1. 抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用
abstract class Shape { 
 abstract public void draw(); 
 void func() { 
 System.out.println("func"); 
 } 
} 
class Rect extends Shape { 
 ... 
} 
public class Test { 
 public static void main(String[] args) { 
 Shape shape = new Rect(); 
 shape.func(); 
 } 
} 
// 执行结果
func

抽象类的作用
抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验.
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.
充分利用编译器的校验, 在实际开发中是非常有意义的.

5.接口

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.
语法规则
在刚才的打印图形的示例中, 我们的父类 Shape 并没有包含别的非抽象方法, 也可以设计成一个接口

interface IShape { 
 void draw(); 
} 
class Cycle implements IShape { 
 @Override 
 public void draw() { 
 System.out.println("○"); 
 } 
} 
public class Test { 
 public static void main(String[] args) { 
 IShape shape = new Rect(); 
 shape.draw(); 
 } 
}

使用 interface 定义一个接口
接口中的方法一定是抽象方法, 因此可以省略 abstract
接口中的方法一定是 public, 因此可以省略 public
Cycle 使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”
在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
接口不能单独被实例化
接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值