包
包 (package) 是组织类的一种方式,使用包的主要目的是保证类的唯一性。
import java.util.Date;
//使用 import 语句导入包.
public class Test {
public static void main(String[] args) {
Date date = new Date();
//使用 java.util.Date 这种方式引入 java.util 这个包中的 Date 类.
//如果需要使用 java.util 中的其他类,
//可以使用 import java.util.*
System.out.println(date.getTime());
}
}
//静态导入
import static java.lang.System.*;
//使用 import static 可以导入包中的静态的方法和字段
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
注意事项: import 和 C++ 的 #include 差别很大。C++ 必须 #include 来引入其他文件内容,但是 Java 不需要,import 只是为了写代码的时候更方便,import 更类似于 C++ 的 namespace 和 using。
我们已经了解了类中的 public 和 private,private 中的成员只能被类的内部使用。如果某个成员不包含 public 和 private 关键字,此时这个成员可以在包内部的其他类使用,但是不能在包外部的类使用。
//Demo1.java
package com.demo;
public class Demo1 {
int value = 10;
}
//Demo2.java
package com.demo;
public class Demo2 {
public static void Main(String[] args) {
Demo1 demo = new Demo1();
System.out.println(demo.value);
}
}
// 执行结果, 能够访问到 value 变量
10
//Test.java
import com.demo.Demo1;
public class Test {
public static void main(String[] args) {
Demo1 demo = new Demo1();
System.out.println(demo.value);
}
}
// 编译出错
常见的的系统包:
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包。
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。
- 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。
此时我们就可以让 Cat 和 Bird 分别继承 Animal 类,来达到代码重用的效果。Animal 这样被继承的类,我们称为 父类,基类或超类,对于像 Cat 和 Bird 这样的类。我们称为子类或派生类。
对于上面的代码,可以使用继承进行改进。此时我们让 Cat 和 Bird 继承自 Animal 类,那么 Cat 在定义的时候就不必再写 name 字段和 eat 方法。
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
//extends 英文原意指 "扩展".
//而我们所写的类的继承, 也可以理解成基于父类进行代码上的 "扩展".
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();
}
}
组合
和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果。
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
组合并没有涉及到特殊的语法(诸如 extends 这样的关键字),仅仅是将一个类的实例作为另外一个类的字段。
多态
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。
public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
圈复杂度是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙,那么就比较简单容易理解,而如果有很多的条件分支或者循环语句,就认为理解起来更复杂。我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为 “圈复杂度”,如果一个方法的圈复杂度太高,就需要考虑重构。