跳到主要内容Java 包、抽象类与接口详解 | 极客日志Javajava算法
Java 包、抽象类与接口详解
Java 包用于组织类并保证唯一性,通过 import 语句引入。抽象类包含抽象方法,不可实例化,用于被继承并提供部分实现。接口定义行为规范,仅含抽象方法和常量,支持多实现。文章详细讲解包的使用规则、抽象类语法与限制、接口定义及多继承模拟,并通过对象排序(Comparable/Comparator)和对象克隆(浅拷贝/深拷贝)实例演示接口应用,最后对比抽象类与接口的核心区别。
狂少3 浏览 包
包 (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 只是为了写代码的时候更方便。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 {
{
;
;
sqrt(pow(x, ) + pow(y, ));
System.out.println(result);
}
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Test
public
static
void
main
(String[] args)
double
x
=
30
double
y
=
40
double
result
=
2
2
将类放入包
在文件的最上方加上一个 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 关键字表示这是一个抽象类。
- 抽象类是被
abstract 修饰的。
- 被
abstract 修饰的方法称为抽象方法,该方法可以没有具体的实现。
- 当一个类中含有抽象方法的时候,该类必须使用
abstract 修饰。
- 抽象类当中可以有和普通类一样的成员变量和一样的成员方法。
- 抽象类是不可以被实例化的。
- 抽象类既然不能实例化对象,那么要抽象类干什么?就是为了被继承。
- 当一个普通的类继承了这个抽象类之后,这个普通类一定要重写这个抽象类当中所有的抽象方法。
final 和 abstract 是不能同时存在的,抽象方法不能被 private 和 static 修饰!
- 当一个抽象类 A 不想被一个普通类 B 继承,此时可以把 B 这个类变成抽象类,那么再当一个普通类 C 继承这个抽象类 B 之后,C 要重写 B 和 A 里面所有的抽象方法。
注意事项
Shape shape = new Shape();
abstract class Shape { abstract private void draw(); }
- 抽象类中可以包含其他的非抽象方法,也可以包含字段。这个非抽象方法和普通方法的规则都是一样的,可以被重写,也可以被子类直接调用
abstract class Shape {
abstract public void draw();
void func() { System.out.println("func"); }
}
class Rect extends Shape { ... }
public class Test {
public static void main(String[] args) {
Shape shape = new Rect();
shape.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 Rect();
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 的静态常量。
- 使用
interface 来定义一个接口。
- 接口当中的成员变量默认是
public static final 的,一般情况下我们不写。
- 接口当中的成员方法默认是
public abstract,一般情况下我们不写。
- 接口当中不可以有普通的方法。
- Java 8 开始允许在接口当中定义一个
default 方法,可以有具体的实现的。
- 接口当中的方法如果是
static 修饰的方法那么是可以有具体的实现的。
- 接口不能通过
new 关键字进行实例化。
- 类和接口之间可以通过关键字
implements 来实现接口。
- 接口也可以发生向上转型和动态绑定的。
- 当一个类实现接口当中的方法之后,当前类当中的方法不能不加
public。
- 接口当中不能有构造方法和代码块。
- 一个接口也会产生独立的字节码文件。
实现多个接口
有的时候我们需要让一个类同时继承自多个父类。这件事情在有些编程语言通过多继承的方式来实现的。然而 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 { ... }
通过接口继承创建一个新的接口 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);
}
}
与我们预期的 19.9 99.99 不符,显然是因为这里是浅拷贝,因此我们需要对 m 实现深拷贝。
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 age) {
this.age = age;
this.m = new Money();
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.m = (Money) this.m.clone();
return tmp;
}
@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);
}
}
抽象类和接口的区别
核心区别:抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用(不必重写),而接口中不能包含普通方法,子类必须重写所有的抽象方法。