Java接口、包(4.7-4.8)

 
 

接口 & 包

 

一、 接口

1.1 接口的概念

1. 📒使程序设计和实现相互分离:
在单继承的继承树中,设计和实现不可避免地要纠缠在一起。在设计的时候,人们也许只想提供一个类的抽象的接口,而不希望去具体实现它,因为那是实现阶段的事情。使用接口类型可以很完美地解决这个问题。

2.📒弥补Java只支持单重继承的不足:
Java的类型层次结构具有一定的局限——它只支持单继承,一个类只能有一个父类,而不能交叉继承树的其它分支中有用的部分。这样就给面向对象的程序设计带来了一些困难。但是多继承的引入使得继承层次结构变得混乱,更加容易出错和产生二义性。

3.📒约束实现接口的类:
接口只是约束实现了该接口的类必须满足接口的要求。
当定义一个接口时,实际上是在编写一个契约,用来描述实现其的类能够做什么,能够充当什么角色,具体怎么做和接口无关。

所以,Java抛弃了多继承,引入一种新的层次结构——接口来达到同样的功能。

📒**接口和类的区别:** (1) 类只能单继承,而接口可以多继承。 (2) 类中的方法可以是具体的,也可以抽象的。 接口中的方法都是抽象的。 (3) 接口中的方法要用类来实现,一个类可以实现多个接口。 📒**注意:** (1) 接口可以继承:一个接口可以继承多个其它的接口, (2) 接口中的方法要用类来实现 。 (3) 一个类可以实现多个接口 。 (4) Java接口反映了对象较高层次的抽象,为描述相互似乎没有关系的对象的共性提供了一种有效的手段。

 
 

1.2 接口的说明

1
2
3
4
[修饰符] interface 接口名[extends] [接口列表]
{
接口体
}

 
1. 📒首部:
修饰符:或者不使用修饰符(同包访问),或者只能使用public(任意访问)。归结为只能使用public或者缺省.
extends:定义父接口,支持多重继承(多个父接口用逗号分隔)
    public interface Cookable extends Foodable,Printable

2.📒接口体:定义常量和抽象方法:
 接口的成员:成员变量和方法
(1) 接口中的成员变量:都是隐含public、static、final的——静态最终变量(常量),例如在接口中有下面这行代码:
int STEP=5;
等同于:
public static final int STEP=5;
(2) 接口中的方法:接口中说明的方法都是抽象方法,所有方法隐含public和abstract的 ,例如 int increment(int x);
注意, 接口中的方法不能使用下面的修饰符:
static
native或synchronized
final

 
 

1.3 接口的使用

 接口自己不能提供方法的实现,接口中的方法必须由类实现。Java语言用关键字implements声明类中将实现的接口。声明接口的形式:
  [类修饰符] class类名 [extends子句] [ implements 子句]
注意:
在implements子句中可以包含多个接口类型,各个接口类型之间用逗号隔开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[例4-19]  TestInterface.java 接口的使用示例
interface Runner{ public void run();}
interface Swimmer{ public void swim();}
abstract class Animal {abstract public void eat();}

class Person extends Animal implements Runner,Swimmer {
//Person是能跑和游泳的动物,所以继承了Animal,同时实现了Runner和Swimmer两个接口
public void run() {System.out.println("run");}
public void swim(){System.out.println("swim");}
public void eat(){System.out.println("eat");}
}
public class TestInterface{
public static void main(String args[]){
TestInterface t=new TestInterface();
Person p=new Person();
t.m1(p);
t.m2(p);
t.m3(p);
}
public void m1(Runner f){f.run();}
public void m2(Swimmer s){s.swim();}
public void m3(Animal a){a.eat();}
}

 
 

1.4 对接口的引用

 (1) 接口可以作为一种引用类型来使用。任何实现该接口的类的实例都可以存储在该接口类型的变量中,通过这些变量可以访问类所实现的接口中的方法。Java运行时系统动态地确定该使用哪个类中的方法。

 (2) 把接口作为一种数据类型可以不需要了解对象所对应的具体的类,而着重于它的交互界面。例如,[例4-18]中Person类实现了Runner接口,可以如下赋值,和子类对象赋给父类对象是类似的:
Runner r=new Person();

 
 

1.5 使用接口实现多重继承

 程序设计有时需要表达这样的意思:
   “x从属于a,也从属于b,也从属于c”
C++:多重继承,每个类都有具体的实施细节
Java:接口,只有一个类有具体的实施细节

图片1.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[例4-20]  Swan.java 接口实现多重继承
/* Swan表示天鹅类,它继承了animal基础类,并实现了接口canWalk、canSwim、canFly,即天鹅具有
animal的功能,同时也具有行走、游泳和飞行的功能。这就实现了多重继承。
*/
interface canWalk{ void walk();}
interface canSwim{ void swim();}
interface canFly{void fly();}
abstract class animal{abstract void eat();}
public class Swan extends animial implements canWalk,canSwim,canFly
{ public void walk()
{ System.out.println("swan walking!");
}
public void swim()
{ System.out.println("swan swimming!");}
public void fly()
{ System.out.println("swan flying!");}
void eat(){ System.out.println("swan eating!");}

public static void main(String[] args)
{
Swan swan=new Swan();
swan.walk();
swan.swim();
swan.fly();
swan.eat();
}
}

 
 

1.6 抽象类和接口的选择

问题:接口和抽象类都可以使设计和实现分离,那么继承结构中的基础类应该定义成一个普通类、抽象类还是接口 ??
(1) 在语法上都是可以的,但是把基础类定义成抽象类或接口往往会增加程序的灵活性和容错性;
(2) 如果基础类只是定义一些接口,根本不需要具体的实现, 那么首先优先选择使用接口,接口的抽象程度比抽象类更高;如果基础类必须实现方法或者定义成员变量的时候,才考虑采用抽象类。

 
 

1.7 多态

📒多个类中的多态:在具有继承关系的多个类中,子类对父类方法的覆盖(不能是重载父类的方法),即子类和父类可以有相同首部的方法,运行的时间决定每个对象到底执行哪个特定的版本。
📒判断程序中是否存在多态:继承(覆盖)、向上转型、动态绑定。

 1. 覆盖:前面已经讲过,如下:
20-1.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[例4-21] Shapes.java 多态性示例1
class Shape {
void draw() {}
void erase() {}
}
class Circle extends Shape {
void draw() {
System.out.println("Circle.draw()"); }
void erase() {
System.out.println("Circle.erase()");
}}
class Square extends Shape {
void draw() {
System.out.println("Square.draw()"); }
void erase() {
System.out.println("Square.erase()");
}}
public class Shapes {
static void doShape(Shape s){
s.draw();
s.erase(); }
public static void main(String[] args) {
Circle c=new Circle();
Square s=new Square();
doShape(c);
doShape(s);
}}
运行结果:
Circle.draw()
Circle.erase()
Square.draw()
Square.erase()

 
 2. 向上转型:子类对象可以直接转换成父类对象,即一个对象除了可以当作自己的类型,还可以作为它的基础类型对待,这种把子类型当作它的基本类型处理的过程,就叫作向上转型(即“Upcasting”)
例如:
Shape s=new Circle();
Circle c=new Circle();
Square s=new Square();
doShape(c);
doShape(s);

 3. 动态绑定:
  绑定:将一个方法调用同一个方法体连接起来
  早期绑定:程序运行以前执行绑定;
  动态绑定(或后期绑定):在运行期间执行绑定。
Java支持动态绑定:能在运行期间判断参数的实际类型,并分别调用适当的方法体,从而实现了多态性。在Java中所有非final和非static的方法都会自动地进行动态绑定。

如下代码块:
21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Shape { 
void draw() {}
void erase() {}
}
class Triangle extends Shape {
void draw() {
System.out.println("Triangle.draw()"); }
void erase() {
System.out.println("Triangle.erase()"); }
}
public class Shapes
{
static void doShape(Shape s)
{
s.draw();
s.erase();
}
public static void main(String[] args)
{
Circle c=new Circle();
Square s=new Square();
Triangle t=new Triangle();
doShape(c);
doShape(s);
doShape(t) ;
}
}

 
 

二、 包

2.1 包的说明和作用

1. 📒包的说明:
• 包是用于组织类的一种方式,可以对类进行分组,一个包中可以包含任意数量的类和接s口,本身是一种命名机制,具体的表现就是一个文件夹
• 包是一种松散的类的集合:一般不要求处于同一个包中的类有明确的相互关系,如包含、继承等,但是由于同一个包中的类在默认情况下可以相互访问,所以为了方便编程和管理,通常把需要在一起工作的类放在一个包里。

2. 📒包的作用:
(1)包能够让程序员将类组织成单元,通过文件夹或目录来组织文件和应用程序;
(2)包减少了名称冲突带来的问题,可以防止同名的类发生冲突;
(3)包能够更大面积的保护类、变量和方法,而不是分别对每个类进行保护;
(4)包可以用于标示类。

 
 

2.2 包的创建

1. 📒无名包:
  系统为每个没有明确指明所属包的.java文件默认创建的包
    无名包中的类无法被引用和复用

2. 📒有名包:
(1)说明格式 package 包名;
   package SubClass;
   package MyClass.SubClass;
(2)说明语句必须放在整个.java文件的第一行

3. 📒创建包的含义:
 创建包就是在当前文件夹下创建一个子文件夹,以便存放这个包中包含的所有类的.class文件.

4. 📒可以在不同的文件中使用相同的包说明语句,这样就可以将不同文件中的类都包含到相同的程序包中.

1
2
3
4
package SubClass;         package MyClass.SubClass;
class s1{ class s2{
…… ……
} }

 
 

2.3 包的引用

1. 📒使用全名引用:
 (1) 同包的类相互引用时:
    在使用的属性或方法名前加上类名作为前缀即可.
 (2) 不同包中的类相互引用时:
    在类名的前面再加上包名——类的全名.
    例如:Class myDate extends java.util.Date{
    java.util.Date d=new java.util.Date();
    ……
    }

2. 📒使用import:
  import可以加载整个包中的文件或包中的某一个文件。import语句的格式为:
    import package1[.package2…].(classname|*);

1
2
3
4
5
6
7
8
9
10
11
import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import java.util.Date;
class myDate extends Date{
Date d=new Date();
……
}

注意:java编译器会为所有程序自动引入包java.lang,因此不必用import语句引入它包含的所有的类,但是若需要使用其他包中的类,必须用import语句引入。

 
 

2.4 编译和运行包中的类

当程序中用package语句指明一个包,在编译时产生的字节码文件(.class文件)需要放到相应的以包名为名称的文件夹目录下:
(1)手工建立子目录,以包名命名该目录,再将.class文件复制到相应目录下。
(2)在编译时,使用“javac –d”命令

 
 

2.5 Java包

  Java自带了一些包。这些包以“java.”开头,作为java的一个标准部分发布。学习Java,必须学习Java常用包中的类。从第六章开始,主要内容是介绍Java常用包的使用。

包的使用参考。
输入流的使用参考。