目录
1. 包
1.1 导入包中的类
1.2 静态导入
1.3 将类放到包中
1.4 包的访问权限控制
1.5 常见的系统包
2. 继承
2.1 语法规则
2.2 protected关键字
3. 组合
4. 多态
4.1 向上转型
4.2 动态绑定
4.3 方法重写
4.3 理解多态
包 (package) 是组织类的一种方式.
使用包的主要目的是保证类的唯一性.
可以使用 java.util.Date 这种方式引入 java.util 这个包中的 Date 类. 但是这种写法比较麻烦一些, 可以使用 import 语句导入包.
使用 import static 可以导入包中的静态的方法和字段。
基本规则
在文件的最上方加上一个 package 语句指定该代码在哪个包中.
包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存 储代码.
如果一个类没有 package 语句, 则该类被放到一个默认包中.
我们已经了解了类中的 public 和 private. private 中的成员只能被类的内部使用. 如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用.
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编程开发包。
代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法).
有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联.
// Animal.java public class Animal { public String name; public Animal(String name) { this.name = name; } public void eat(String food) { System.out.println(this.name + "正在吃" + food); } } // Cat.java class Cat { public String name; public Cat(String name) { this.name = name; } public void eat(String food) { System.out.println(this.name + "正在吃" + food); } } // Bird.java class Bird { public String name; public Bird(String name) { this.name = name; } public void eat(String food) { System.out.println(this.name + "正在吃" + food); } public void fly() { System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿"); } }这个代码我们发现其中存在了大量的冗余代码. 仔细分析, 我们发现 Animal 和 Cat 以及 Bird 这几个类中存在一定的关联关系: 这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的. 这三个类都具备一个相同的 name 属性, 而且意义是完全一样的. 从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a 语义). 此时我们就可以让 Cat 和 Bird 分别继承 Animal 类, 来达到代码重用的效果.
此时, Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类, 对于像 Cat 和 Bird 这样的类, 我们称为 子类, 派生类 和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果。
基本语法
class 子类 extends 父类 {
} 使用 extends 指定父类.
Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
子类会继承父类的所有 public 的字段和方法.
对于父类的 private 的字段和方法, 子类中是无法访问的.
子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.
如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 "封装" 的初衷.
两全其美的办法就是 protected 关键字. 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
小结: Java 中对于字段和方法共有四种访问权限
private: 类内部能访问, 类外部不能访问
默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
public : 类内部和类的调用者都能访问
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果. 组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段. 这是我们设计类的一种常用方式之一.组合表示 has - a 语义 在刚才的例子中, 我们可以理解成一个学校中 "包含" 若干学生和教师.
继承表示 is - a 语义 在上面的 "动物和猫" 的例子中, 我们可以理解成一只猫也 "是" 一种动物.
Bird bird = new Bird("圆圆"); 这个代码也可以写成这个样子 Bird bird = new Bird("圆圆"); Animal bird2 = bird; // 或者写成下面的方式 Animal bird2 = new Bird("圆圆"); 此时 bird2 是一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例. 这种写法称为 向上转型. 向上转型这样的写法可以结合 is - a 语义来理解.
为啥叫 "向上转型"?
在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表 示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 "向上转型" , 表示往父类的方向转.
当子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢? 对前面的代码稍加修改, 给 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("谷子"); } } // 执行结果 我是一只小动物 圆圆正在吃谷子 我是一只小鸟 扁扁正在吃谷子animal1 和 animal2 虽然都是 Animal 类型的引用, 但是 animal1 指向 Animal 类型的实例, animal2 指向 Bird 类型的实例.
针对 animal1 和 animal2 分别调用 eat 方法, 发现 animal1.eat() 实际调用了父类的方法, 而 animal2.eat() 实际调用了子类的方法.
因此, 在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引 用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.
针对刚才的 eat 方法来说:
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
关于重写的注意事项:
1. 重写和重载完全不一样. 不要混淆
2. 普通方法可以重写, static 修饰的静态方法不能重写.
3. 重写中子类的方法的访问权限不能低于父类的方法访问权限.
有了面的向上转型, 动态绑定, 方法重写之后, 我们就可以使用多态的形式来设计程序了.
当类的调用者在编写一个方法的时候, 参数类型为父类, 此时在该方法内部并不知道, 也不关注当前的引用指向的是哪个类型(哪个子类)的实例. 此时这个引用调用方法可能会有多种不同的表现, 这种行为就称为 多态。
1) 类调用者对类的使用成本进一步降低.
封装是让类的调用者不需要知道类的实现细节.
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
2) 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
3) 可扩展能力更强.
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.