包
包 (package) 是组织类的一种方式. 使用包的主要目的是保证类的唯一性.
导入包
import 和 C++ 的 #include 差别很大. C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要. import 只是为了写代码的时候更方便. import 更类似于 C++ 的 namespace 和 using
使用 import static 可以导入包中的静态的方法和字段
将类放到包中
基本规则
在文件的最上方加上一个 package 语句指定该代码在哪个包中.
包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.xx.demo ).
要和代码路径相匹配. 例如创建 com.xx.demo 的包, 那么会存在一个对应的路径 com/xx/demo来存 储代码. 如果一个类没有 package 语句, 则该类被放到一个默认包中.
包的访问权限控制
类中的 public 和 private. private 中的成员只能被类的内部使用.
如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用.
常见的系统包
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包。
继承
class 子类 extends 父类 {
//...
}
使用 extends 指定父类.
Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
子类会继承父类的所有 public 的字段和方法.
对于父类的 private 的字段和方法, 子类中是无法访问的.
子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.
protected 关键字
如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.
两全其美的办法就是 protected 关键字. 对于类的调用者来说, protected 修饰的字段和方法是不能访问的,对于类的子类和同一个包的其他类来说, protected 修饰的字段和方法是可以访问的
Java 中对于字段和方法共有四种访问权限
private: 类内部能访问, 类外部不能访问
默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
public : 类内部和类的调用者都能访
final 关键字, 修饰一个变量或者字段的时候, 表示 常量 (不能修改).
final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.
final 关键字的功能是 限制 类被继承
平时使用的 String 字符串类, 就是用 final 修饰的, 不能被继承.
组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.
组合表示 has - a 语义
继承表示 is - a 语义
多态
向上转型
是一个父类的引用, 指向一个子类的实例. 这种写法称为 向上转型.
Bird bird = new Bird("啾咪");
Animal bird2 = bird;
// 或者写成下面的方式
Animal bird2 = new Bird("啾啾")
为啥叫 “向上转型”?
在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表示类之间的关系. 此时父类通常画在子类的上方. 所以我们就称为 “向上转型” , 表示往父类的方向转.
向上转型发生的时机:
直接赋值(示例代码)
方法传参
public class Test {
public static void main(String[] args) {
Bird bird = new Bird("圆圆");
feed(bird);
}
public static void feed(Animal animal) {
animal.eat("谷子");
}
}
方法返回
public class Test {
public static void main(String[] args) {
Animal animal = findMyAnimal();
}
public static Animal findMyAnimal() {
Bird bird = new Bird("圆圆");
return bird;
}
}
动态绑定
在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引 用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.
方法重写
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
重写和重载完全不一样. 不要混淆
普通方法可以重写, static 修饰的静态方法不能重写.
重写中子类的方法的访问权限不能低于父类的方法访问权限.
针对重写的方法, 可以使用 @Override 注解来显式指定
多态理解
多态顾名思义, 就是 “一个引用, 能表现出多种不同形态”
一个引用到底是指向父类对象, 还是某个子类对象(可能有多个), 也是要根据上下文的代 码来确定
使用多态的好处是什么?
1.类调用者对类的使用成本进一步降低.
封装是让类的调用者不需要知道类的实现细节. 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
2.能够降低代码的 “圈复杂度”, 避免使用大量的 if - else .
3. 可扩展能力更强.
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.
什么叫 “圈复杂度” ? 圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很 多的条件分支或者循环语句, 就认为理解起来更复杂. 因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”. 如果一 个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10 .
向下转型
向上转型是子类对象转成父类对象,
向下转型就是父类对象转成子类对象.
相比于向上转型来说, 向下转型没那么常见, 但是也有一定的用途.
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了.
super关键字
super 表示获取到父类实例的引用.
- 使用了 super 来调用父类的构造器
- 使用 super 来调用父类的普通方法
super/this 异同
区别 | this | super |
---|---|---|
概念 | 访问本类中的属性和方法 | 由子类访问父类的属性和方法 |
查找范围 | 先查找本类,本类没有再查找父类 | 不查找本类而查找父类 |
特殊 | 表示当前对象 | 无 |
抽象类
像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).
abstract class Shape {
abstract public void draw();
}
在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体 代码). 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类.
- 抽象类不能直接实例化.
- 抽象方法不能是 private 的
- 抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写, 也可以被子类直接调用
抽象类存在的最大意义就是为了被继承. 抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
使用抽象类相当于多了一重编译器的校验.
接口
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含 静态常量.
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 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”
在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
接口不能单独被实例化
扩展(extends) vs 实现(implements)
扩展指的是当前已经有一定的功能了, 进一步扩充功能. 实现指的是当前啥都没有, 需要从头构造出来.
接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).
interface IShape {
void draw();
public static final int num = 10;
}
其中的 public, static, final 的关键字都可以省略. 省略后的 num 仍然表示 public 的静态常量
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.
抽象类和接口都是 Java 中多态的常见使用方式.
核心区别:
抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写),
而接口中不能包含普通方法, 子类必须重写所有的抽象方法
区别 | 抽象类(abstract) | 接口(interface) |
---|---|---|
结构组成 | 普通类+抽象方法 | 抽象方法+全局变量 |
权限 | 各种权限 | public |
子类使用 | 使用extends继承抽象类 | 使用implements实现接口 |
关系 | 一个抽象类可以实现若干接口 | 接口不能继承抽象类,但可以使用extends关键字继承多个父接口 |
子类限制 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |