包
包 (package) 是组织类的一种方式.
使用包的主要目的是保证类的唯一性.
导入包中的类
Java 中已经提供了很多现成的类供我们使用. 例如
java.util.Date date = new java.util.Date();
可以使用 java.util.Date 这种方式引入 java.util 这个包中的 Date 类.但是这种写法比较麻烦一些, 可以使用 import 语句导入包.
import java.util.Date;
如果需要使用 java.util 中的其他类, 可以使用 import java.util.*
import java.util.*;
容易出现冲突的情况,不建议
使用 import static 可以导入包中的静态的方法和字段.
常见的系统包
- java.lang:系统常用基础类(String、Object)
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包。
继承
目的:达到代码重用的效果.
被继承的类, 我们称为 父类 , 基类 或 超类, 继承父类的类, 我们称为 子类, 派生类
和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果.
基本语法:
class 子类 extends 父类 {
}
- 使用 extends 指定父类.
- Java 中一个子类只能继承一个父类
- 子类会继承父类的所有 public 的字段和方法.
- 对于父类的 private 的字段和方法, 子类中是无法访问的.
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.
Java 中对于字段和方法共有四种访问权限
private: 类内部能访问, 类外部不能访问
默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
public : 类内部和类的调用者都能访问
什么时候下用哪一种呢?
我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public.
final 关键字
1.修饰一个变量或者字段的时候, 表示 常量 (不能修改).
final int a = 10;
a = 20; // 编译出错
2.final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.
final public class Animal {
}
public class Bird extends Animal {
}
// 编译出错
final 关键字的功能是 限制 类被继承
组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.这是我们设计类的一种常用方式之一.
组合表示 has - a 语义
继承表示 is - a 语义
多态
向上转型
一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例. 这种写法称为 向上转型.(UML 图)
Bird bird = new Bird("圆圆");
Animal bird2 = bird;
// 或者写成下面的方式
Animal bird2 = new Bird("圆圆");
向上转型发生的时机:
- 直接赋值(如上)
- 方法传参
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 注解来显式指定.有了这个注解能帮我们进行一些合法性校验.
重载和重写的区别.
多态顾名思义, 就是 “一个引用, 能表现出多种不同形态”
使用多态的好处是什么?
- 类调用者对类的使用成本进一步降低.(封装是让类的调用者不需要知道类的实现细节.多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.因此, 多态可以理解成是封装的更进一步)
- 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
- 可扩展能力更强.
向下转型
向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象.
对于 Animal animal = new Bird(“圆圆”) 这样的代码,
编译器检查有哪些方法存在, 看的是 Animal 这个类型.
执行时究竟执行父类的方法还是子类的方法, 看的是 Bird 这个类型.
示例:
Bird bird = (Bird)animal;
bird.fly();
但是这样的向下转型有时是不太可靠的.所以, 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换.instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了.
Animal animal = new Cat("小猫");
if (animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly();
}
super 关键字
如果需要在子类内部调用父类方法怎么办? 可以使用 super 关键字.
super 表示获取到父类实例的引用. 涉及到两种常见用法.
- 使用了 super 来调用父类的构造器
public Bird(String name) {
super(name);
}
- 使用 super 来调用父类的普通方法
super.eat(food);
如果在子类的 eat 方法中直接调用 eat (不加super), 那么此时就认为是调用子类自己的 eat (也就是递归了). 而加上 super 关键字, 才是调用父类的方法.
super 和 this
尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.
多态的核心是让调用者不必关注对象的具体类型. 这是降低用户使用成本的一种重要方式.
抽象类
像没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).
abstract class Shape {
abstract public void draw();
}
- 抽象方法没有方法体(没有 { }, 不能执行具体代码)
- 对于包含抽象方法的类, 必须加上 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 Cycle();
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 关键字.