文章标题:
Java中包、抽象类与接口的深入剖析
文章内容:
目录
- 包
- 导入包
- 静态导入
- 将类放入包
- 常见的系统包
- 抽象类
- 语法规则
- 注意事项
- 抽象类的作用
- 接口
- 实现多个接口
- 接口间的继承
- 接口使用实例
- (法一)实现Comparable接口的compareTo()方法
- (法二)实现Comparator比较器的compare()方法
- Clonable接口和深拷贝
- 抽象类和接口的区别
包
包(package)是组织类的一种方式,其核心作用是保证类的唯一性。举个例子,假如你在代码中定义了一个名为Test的类,而你的同事也可能定义一个同名的Test类,若出现两个同名类,就会引发冲突,导致代码无法成功编译。
导入包
Java已经预置了大量可供使用的现成类。例如:
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
上述代码通过java.util.Date的方式引入了java.util包中的Date类。不过这种写法相对繁琐,我们可以借助import语句来导入包。若需要使用java.util中的其他类,可采用import java.util.*的方式,但更建议明确指定要导入的类名,以此避免冲突情况的发生。需要留意的是,import和C++的#include存在显著差异,C++必须通过#include引入其他文件内容,而Java无需如此,import主要是为了让编写代码的过程更为便捷,其更类似于C++的namespace和using。
静态导入
利用import static能够导入包中的静态方法和字段。例如:
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
借助这种方式能够更便捷地编写代码,比如:
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
double result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
}
}
将类放入包
基本规则如下:在文件的最上方添加package语句来指定该代码所属的包。包名通常建议采用公司域名的颠倒形式,例如com.bit.demo1。并且包名需要与代码路径相匹配,比如创建com.bit.demo1包,就会对应存在一个com/bit/demo1的路径来存储代码。若一个类没有package语句,那么该类会被放置在默认包中。
常见的系统包
java.lang:包含系统常用基础类,像String、Object等,此包从JDK1.1后会自动导入。java.lang.reflect:用于Java反射编程的包。java.net:用于进行网络编程开发的包。java.sql:为数据库开发提供支持的包。java.util:是Java提供的工具程序包,包含集合类等。java.io:用于I/O编程开发的包。
抽象类
语法规则
对于没有实际功能的方法,我们可以把它设计成抽象方法(abstract method),包含抽象方法的类被称作抽象类(abstract class)。例如:
abstract class Shape {
abstract public void draw();
}
在draw方法前添加abstract关键字,就表示这是一个抽象方法,抽象方法没有方法体(没有{},无法执行具体代码)。对于包含抽象方法的类,必须使用abstract关键字来表明它是一个抽象类。
总结如下:
1. 抽象类由abstract修饰。
2. 被abstract修饰的方法是抽象方法,该方法可以没有具体实现。
3. 当类中存在抽象方法时,该类必须用abstract修饰。
4. 抽象类中可以有与普通类一样的成员变量和成员方法。
5. 抽象类不能直接实例化。
6. 抽象类存在的意义在于被继承。
7. 普通类继承抽象类时,必须重写抽象类中的所有抽象方法。
8. final和abstract不能同时存在,抽象方法不能用private或static修饰。
9. 若抽象类A不想被普通类B继承,可将B设为抽象类,当普通类C继承抽象类B后,C需要重写B和A中的所有抽象方法。
注意事项
- 抽象类不能直接实例化,例如:
Shape shape = new Shape();
// 编译出错:Error:(30, 23) java: Shape是抽象的; 无法实例化
- 抽象方法不能是
private的,例如:
abstract class Shape {
abstract private void draw();
}
// 编译出错:Error:(4, 27) java: 非法的修饰符组合: abstract和private
- 抽象类中可以包含非抽象方法和字段,非抽象方法的规则与普通方法一致,可被重写,也能被子类直接调用,例如:
abstract class Shape {
abstract public void draw();
void func() {
System.out.println("func");
}
}
class Rect extends Shape {
// 实现draw方法
}
public class Test {
public static void main(String[] args) {
Shape shape = new Rect();
shape.func();
}
}
// 执行结果:func
- 抽象类不一定包含抽象方法,但包含抽象方法的类必定是抽象类。
- 抽象类可以拥有构造方法,用于子类创建对象时初始化父类的成员变量。
抽象类的作用
抽象类存在的最主要意义是被继承。由于抽象类本身不能实例化,所以要使用它,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法。
接口
接口是更为进阶的抽象类。抽象类中能够包含非抽象方法和字段,而接口中包含的方法全是抽象方法,字段只能是静态常量。
语法规则如下:
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继承接口,这里表达的含义并非“扩展”,而是“实现”。- 调用时同样可以创建接口的引用,指向子类的实例。
- 接口不能单独实例化。
接口中只能包含抽象方法,对于字段而言,接口中只能是静态常量(final static)。例如:
interface IShape {
void draw();
public static final int num = 10;
}
其中public、static、final关键字都可以省略,省略后的num依然表示public的静态常量。
总结:
1. 使用interface定义接口。
2. 接口中的成员变量默认是public static final的,一般情况下可不显式写出。
3. 接口中的成员方法默认是public abstract的,通常也可不显式写出。
4. 接口中不存在普通方法。
5. Java8开始允许在接口中定义default方法,该方法可以有具体实现。
6. 接口中如果是static修饰的方法,可以有具体实现。
7. 接口不能通过new关键字实例化。
8. 类和接口之间通过implements关键字实现接口。
9. 接口也能发生向上转型和动态绑定。
10. 类实现接口中的方法时,方法不能没有public修饰。
11. 接口中没有构造方法和代码块。
12. 接口也会生成独立的字节码文件。
实现多个接口
有时需要让一个类同时继承多个父类,在有些编程语言中通过多继承的方式实现,但Java只支持单继承,一个类只能extends一个父类,但可以同时实现多个接口,达到类似多继承的效果。下面通过类来表示一组动物:
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
再提供一组接口,分别表示“会飞的”“会跑的”“会游泳的”:
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
接着创建几个具体的动物:
– 猫,是会跑的:
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用四条腿跑");
}
}
- 鱼,是会游的:
class Fish extends Animal implements ISwimming {
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + "正在用尾巴游泳");
}
}
- 青蛙,既能跑,又能游(两栖动物):
class Frog extends Animal implements IRunning, ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}
- 鸭子,水陆空三栖:
class Duck extends Animal implements IRunning, ISwimming, IFlying {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飞");
}
@Override
public void run() {
System.out.println(this.name + "正在用两条腿跑");
}
@Override
public void swim() {
System.out.println(this.name + "正在漂在水上");
}
}
上述代码展示了Java面向对象编程中常见的用法:一个类继承一个父类,同时实现多种接口。继承体现的是is – a语义,而接口体现的是具有xxx特性。比如猫是一种动物,具有会跑的特性;青蛙也是一种动物,既能跑又能游泳;鸭子也是一种动物,既能跑、游还能飞。
接口间的继承
接口可以继承另一个接口,以实现复用效果,使用extends关键字。例如:
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖的动物,既能跑,也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
// 需实现run和swim方法
}
通过接口继承创建新接口IAmphibious表示“两栖的”,此时实现该接口的Frog类需要实现run方法和swim方法。
接口使用实例
给对象数组排序
class Student{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.score + "]";
}
}
public class test {
public static void main(String[] args) {
Student[] students = new Student[] {
new Student("张三", 95),
new Student("李四", 96),
new Student("王五", 97),
new Student("赵六", 92),
};
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
运行时会抛出异常,原因是对学生对象进行排序不像对整数那样能直接比大小,所以需要实现Comparable接口并实现其compareTo方法。
(法一)实现Comparable接口的compareTo()方法
class Student implements Comparable<Student>{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.score + "]";
}
@Override
public int compareTo(Student o) {
return this.score - o.score;
}
}
public class test {
public static void main(String[] args) {
Student[] students = new Student[] {
new Student("张三", 95),
new Student("李四", 96),
new Student("王五", 97),
new Student("赵六", 92),
};
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
运行结果会按分数排序输出。
(法二)实现Comparator比较器的compare()方法
class Student{
public String name;
public int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.score + "]";
}
}
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.score - o2.score;
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class test {
public static void main(String[] args) {
Student student1 = new Student("zhangsan",10);
Student student2 = new Student("lisi",15);
AgeComparator ageComparator = new AgeComparator();
System.out.println(ageComparator.compare(student1, student2));
NameComparator nameComparator = new NameComparator();
System.out.println(nameComparator.compare(student1,student2));
}
}
运行会根据不同的比较器输出相应的比较结果。
Clonable接口和深拷贝
Java中内置了一些有用的接口,Clonable就是其中之一。Object类中有一个clone方法,调用该方法可以创建一个对象的拷贝,但要合法调用clone方法,必须先实现Clonable接口,否则会抛出CloneNotSupportedException异常。
class Money{
public double money = 19.9;
}
class Person implements Cloneable{
public int age;
public Money m;
public Person(int age) {
this.age = age;
this.m = new Money();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
" age=" + age +
'}';
}
}
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person(10);
Person person2 = (Person)person1.clone();
System.out.println(person1.m.money);
System.out.println(person2.m.money);
System.out.println("==========================");
person2.m.money = 99.99;
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
}
运行结果与预期不符,这是因为这是浅拷贝,修改person2的m会影响person1的m,所以需要对m实现深拷贝。
实现深拷贝后:
“`java
class Money implements Cloneable{
public double money = 19.9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public int age;
public Money m;
public Person(int
