目录
包
文件夹,包(package) 是组织类的一种方式,使用包的主要目的是保证类的唯一性.
导入包中的类
导入一个类:import 包名.类名
导入包所有的类:import 包名.*
静态导入
使用 import static 可以导入包中的静态的方法和字段
例如:
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
// 静态导入的方式写起来更方便一些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
double result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
}
}
导入类中的静态域,使用这种方式可以更方便的写一些代码。
常见的系统包
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入;
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包;
- java.sql:进行数据库开发的支持包;
- java.util:是java提供的工具程序包;(非常重要)
- java.io:I/O编程开发包。
包访问权限: default(不需要写出来),当前包下的所有类中可见。
继承
语法规则
class 子类 extends 父类 {
}
- 使用 extends 指定父类.
- Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
- 子类会继承父类的所有 public 的字段和方法.
- 对于父类的 private 的字段和方法, 子类中是无法访问的.
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.
举个栗子:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Cat extends Animal {
public Cat(String name) {
// 使用 super 调用父类的构造方法.
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
cat.eat("猫粮");
Bird bird = new Bird("圆圆");
bird.fly();
}
}
Cat 和 Bird 继承自 Animal 类,Cat和Bird就是Animal的子类,Animal是Cat和Brid的父类。
继承访问权限:不同包下具有继承关系的类之间可见。
protected 关键字
如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 "封装" 的初衷.两全其美的办法就是 protected 关键字.
- 对于类的调用者来说, protected 修饰的字段和方法是不能访问的.
- 对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的.
Java 中对于字段和方法共有四种访问权限:
- private: 类内部能访问, 类外部不能访问.
- 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
- protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
- public : 类内部和类的调用者都能访问.
final 关键字
修饰一个变量或者字段的时候, 表示 常量 (不能修改).final 关键字也能修饰类, 此时表示被修饰的类就不能被继承(即该类不会有子类).
final public class Animal {
}
public class Bird extends Animal {
}
// 编译出错
Error:java: 无法从最终Animal进行继承
多态
向上转型
举个栗子:
Bird bird = new Bird("圆圆");
这行代码也可以这样写:
Bird bird = new Bird("圆圆");
Animal bird2 = bird;
// 或者写成下面的方式
Animal bird2 = new Bird("圆圆");
此时 bird2 是一个父类 (Animal) 的引用, 指向一个子类 (Bird) 的实例. 这种写法称为 向上转型.
语法:
父类名称 父类引用 = new 子类()
向上转型发生的时机:
- 直接赋值
- 方法传参
- 方法返回
方法重写
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
注意事项:
- 子类中覆写的权限 >= 父类的权限.
- 子类的方法返回值必须与父类保持一致(向上转型除外).
- 普通方法可以重写, static 修饰的静态方法不能重写.
- 针对重写的方法, 可以使用 @Override 注解来显式指定.
动态绑定
当子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢?
// 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()实际调用了子类的方法。
因此,只看new的是谁,就调用的是谁的方法。
理解多态
多态:当一个引用在不同场景下调用相同方法表现出不同行为。
举个栗子:
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 对应的实例相关), 这种行为就称为 多态。
多态顾名思义, 就是 "一个引用, 能表现出多种不同形态",就是方法重写 + 向上转型,
使用多态的好处:
- 类调用者对类的使用成本进一步降低.
- 能够降低代码的 "圈复杂度", 避免使用大量的 if - else.
- 可扩展能力更强(当有一个新子类产生时,由于多态性,调用者只需创建一个新类的实例就可以了).
向下转型
向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象.
语法:
子类 子类引用 = (子类)父类引用
注意事项:
向下转型可能会出错,两种方法避免出错:
- 使用Java提供的instanceof关键字(instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全)
- 要使用向下转型,先得发生向上转型(核心就在于使用向下转型的对象恰好是当前向上转型new出来的)
例如:Animal a1 = new Bird();
Bird bird = (Bird) a1;
抽象类
语法规则:
在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class).
abstract class Shape {
abstract public void draw();
}
- 在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码).
- 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类.
注意事项:
- 抽象类使用abstract定义,并且抽象类是普通类的超集(普通类有的东西,抽象类都有).
- 抽象类只是比普通类多了一个方法而已.
- 抽象方法使用abstract定义,只有方法的声明,没有方法体.
抽象类的作用
抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
接口
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.
语法规则
-
使用 interface 定义一个接口.
-
接口中的方法一定是抽象方法, 因此可以省略 abstract.
-
接口中的方法一定是 public, 因此可以省略 public.
-
使用 implements 继承接口. 此时表达的含义不再是 "扩展", 而是 "实现".
-
在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
-
接口不能单独被实例化.
注意事项:
- 我们创建接口的时候, 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 "形容词" 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
- 子类命名:接口名称 + impl.
- 多个接口之间可以用extends继承父接口.
- 一个类如果同时继承抽象类,实现接口,先extends一个类,然后implements多个接口.
- JDK8之后,接口中也允许出现普通方法.
Comparable和Clonable 接口
Java 中内置了一些很有用的接口, 例如Comparable和Clonable 。
Comparable
Comparable 接口中的CompareTo方法:返回值int
- 如果当前对象应排在参数对象之前, 返回小于 0 的数字;
- 如果当前对象应排在参数对象之后, 返回大于 0 的数字;
- 如果当前对象和参数对象不分先后, 返回 0;
Clonable
表示克隆的能力。
浅拷贝:
当一个对象是通过另一个对象clone出来的.此时这两个对象是独立的两个对象,但是这两个对象的内部包含的其他一弄是相同的,这种拷贝成为浅拷贝。
Cloneable 拷贝出的对象是一份 "浅拷贝"
看以下代码:
public class Test {
static class A implements Cloneable {
public int num = 0;
@Override
public A clone() throws CloneNotSupportedException {
return (A)super.clone();
}
}
static class B implements Cloneable {
public A a = new A();
@Override
public B clone() throws CloneNotSupportedException {
return (B)super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
B b = new B();
B b2 = b.clone();
b.a.num = 10;
System.out.println(b2.a.num);
}
}
// 执行结果
10
通过 clone 拷贝出的 b 对象只是拷贝了 b 自身, 而没有拷贝内部包含的 a 对象. 此时 b 和 b2 中包含的 a 引用仍然是指向同一个对象. 此时修改一边, 另一边也会发生改变.
深拷贝:
A是通过B拷贝来的,此时A和B包含的所有其他引用也是独立的,这种拷贝成为深拷贝。
深拷贝可以通过序列化或者嵌套实现clone方法来实现。