之前说完了类、对象、方法以及面向对象的三大特性封装、继承和多态,现在来了解一下接口、代码块和一些常见的类如抽象类、包装类等。

一、接口

1、概念

接口(Interface),是一种抽象类型,是抽象方法的集合,是对功能的抽象。接口本身不包含任何实现细节,只是定义了一种规范,规定了实现接口的类应该做什么,而不是怎么做。在前面讲 java数据类型时也提到接口,是一种引用类型。

注:interface关键字声明接口,implements关键字实现接口。

接口定义一种规范,规定某一批类里必须提供某些方法,要求这些类必须完全实现接口里所定义的全部抽象方法,从而实现接口中定义的功能。

要访问接口方法,接口必须由另一个具有implements关键字(而不是 extends)的类”实现”(类似于继承)。接口方法的主体由”implement”类提供。

2、语法

定义一个接口的语法如下。

1
2
3
4
5
6
[修饰符] interface 接口名 extends 父接口1, 父接口2, ... {
0-N 常量
0-N 抽象方法
0-N 内部类、接口、枚举
0-N 默认方法或静态方法 // Java 8 以上才允许
}

3、几点注意

1)接口无法被实例化,但是可以被实现,可以多实现。一个类实现了一个或多个接口之后,这个类必须完全实现(重写)这些接口里所定义的全部抽象方法,否则该类必须定义成抽象类。
2)接口没有构造方法,不能包含成员变量,除static 和 final 变量。
3)==接口支持多继承==。(Java类是单继承)
4)实现接口语法如下

1
2
[修饰符] class 实现类名 extends 父类 implements 接口1, 接口2, ... {
}

接口与接口之间有继承关系extends,支持多继承

类与接口之间有实现关系implements,可以多实现

实现接口方法时,必须使用 public 修饰

接口的方法默认就是public abstract,故可省略不写

4、接口和抽象类

接口和类是两个不同的概念,其中,类描述对象的属性和方法,而接口则包含类要实现的方法。==接口体现的是一种规范,抽象类体现的是一种模板式设计==。

5、总结

1)Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;
2)接口也是数据类型,属于引用类型,适用于向上转型(upcasting)和向下转型(downcasting);
3)接口的所有方法都是抽象方法,接口不能定义实例字段;
4)接口可以定义default方法(JDK>=1.8)

6、面向接口编程(扩展)

面向接口编程是一种编程范式,强调的是在设计软件应用时,应该先定义接口,再实现接口。这种方式有很多优点,如可以提高代码的可读性、可维护性和可扩展性,以及降低代码之间的耦合度。

1)接口多态声明方式

1
2
3
// 接口名 变量名 = new 该接口的实现类名();
// 面向接口编程
Animal myDog=new Dog();

2)简单案例
首先定义一个Animal接口,并在接口内定义eat和sleep方法。

1
2
3
4
5
public interface Animal {
// 定义一个接口
void eat(); // 接口的方法默认就是public abstract,故可省略
void sleep();
}

再定义一个Dog类,用该类实现Animal接口,并在main方法中使用接口类型的引用myDog来调用eat和sleep方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Dog implements Animal {
// 重写Animal接口内所定义的全部抽象方法
@Override
public void eat() {
System.out.println("Dog is eating");
}
@Override
public void sleep() {
System.out.println("Dog is sleeping");
}
// 在主函数中使用接口类型的引用来调用方法
public static void main(String[] args) {
// 接口名 变量名 = new 该接口的实现类名();
// 面向接口编程
Animal myDog=new Dog();
myDog.eat();
myDog.sleep();
}
}

运行结果

1
2
Dog is eating
Dog is sleeping

分析

使用面向接口编程的好处就是,可以==轻松地更换实现接口的类,而不需要修改使用接口的代码==。例如,我们可以定义一个新的类Cat,这个类也实现了Animal接口,然后我们可以在不修改主函数的情况下,将myDog的引用更换为Cat的实例。这就是面向接口编程的基本思想:==编程针对接口,而不是针对实现==。这样可以使我们的代码更加灵活和可扩展,也更容易进行单元测试和重构。

二、代码块

代码块里的变量属于局部变量,只在自己所在区域(即前后的 {})内有效。

1、局部代码块

直接定义在方法内部的代码块,如条件执行体、循环体。

2、普通初始化块

即构造代码块:直接定义在类中(一般不这么用)。

3、静态代码块

即类初始化块:初始化块的修饰符只能是 static。使用 static 修饰的初始化块,通常用来对类变量做初始化操作、加载资源、加载配置文件等。

  • 随着所在类的加载而执行(只执行一次)。
  • 在同类中优先于 main 方法执行。

4、案例

1)编写一个程序示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo {
private static Demo demo=new Demo(); // 单例模式
static {
System.out.println("静态初始化"); //静态代码块=>static修饰
}
Demo(){
// 编译后,“还原”到这里 System.out.println("普通初始化块");
System.out.println("构造器...");
}
{
System.out.println("普通初始化块"); //普通初始化块=>直接定义在类中
}
public static void main(String[] args) { //局部代码块=>直接定义在方法内部
System.out.println("进入 main 方法");
new Demo();
new Demo();
}
}

2)程序执行结果为

1
2
3
4
5
6
7
8
普通初始化块
构造器...
静态初始化
进入 main 方法
普通初始化块
构造器...
普通初始化块
构造器...

3)结果分析
代码执行顺序:父类静态代码块 -> 子类静态代码块 -> 父类非静态代码块 -> 父类构造方法 -> 子类非静态代码块 -> 子类构造方法

  • 类变量的初始化 及 静态初始化块(执行顺序与它们在源代码中的排列顺序相同)
  • 实例变量的初始化 及 普通初始化块(执行顺序与它们在源代码中的排列顺序相同)
  • 构造器(先加载父类的字节码文件并调用父类的构造器)
  • main 方法

三、一些类

1、内部类(Inner Classes)

说到类与类之间的关系,很多人首先想到的可能是并列关系,其实还有嵌套关系,比如内部类。顾名思义,内部类就是一个类定义在另一个类的内部。内部类可以分为以下几种。

注:包含内部类的最外面那个类称为外部类(outer class)

1.1 成员内部类

1)成员内部类是最普通的内部类,位于另一个类的内部。

1
2
3
4
5
6
7
8
9
10
11
class Circle { //外部类
double radius=0; //成员变量
public Circle(double radius){ //成员方法
this.radius=radius;
}
class Draw{ //成员内部类
public void drawShape(){
System.out.println("drawshape");
}
}
}

==2)成员内部类可以无条件访问外部类的所有成员属性和成员方法==(包括private成员和静态成员)。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Circle { //外部类
private double radius = 0; //成员变量
public static int count =1; //静态成员
public Circle(double radius) { //成员方法
this.radius = radius;
}
class Draw { //成员内部类
public void drawShape() {
System.out.println(radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}

3)当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问。

外部类.this.成员变量
外部类.this.成员方法

4)外部类如果要访问成员内部类的成员,必须==先创建一个成员内部类的对象,再通过指向这个对象的引用来访问==。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Circle { // 外部类
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawShape(); //必须先创建成员内部类的对象,再进行访问
} //Draw d=new Draw(),上面代码相当于d.drawShape(),因为getDrawInstance()方法返回了Draw对象
private Draw getDrawInstance() {
return new Draw(); //返回成员内部类的对象
}
class Draw { //成员内部类
public void drawShape() {
System.out.println(radius); //外部类的private成员
}
}
public static void main(String[] args) {
Circle c=new Circle(1);
}
}
// 输出结果:1.0

5)成员内部类是依附外部类而存在的,即如果==要创建成员内部类的对象,必须先存在一个外部类的对象==。创建成员内部类对象的两种方式如下。

  • 通过外部类对象创建
  • 通过getInstance方法创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) {
Outter outter = new Outter(); //第一种方式
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
Outter.Inner inner1 = outter.getInnerInstance(); //第二种方式,getInstance方法
}
}
class Outter { // 外部类
private Inner inner = null;
public Outter() { //无参构造
}
public Inner getInnerInstance() { //getInnerInstance()方法
if(inner == null)
inner = new Inner();
return inner;
}
class Inner { // 成员内部类
public Inner() { //无参构造
}
}
}

注:getInstance在单例模式 (保证一个类仅有一个实例,并提供一个访问它的全局访问点) 的类中常见,用来生成唯一的实例,getInstance往往是static的。

1.2 局部内部类

概念:定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于==局部内部类的访问仅限于方法内或者该作用域内==。

作用域:即一个变量或方法在程序中可以被访问的范围,可以是一个类、方法或一个代码块。

局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class People{
public People() { // 无参构造
}
}
class Man{
public Man(){ // 无参构造
}
public People getWoman(){
class Woman extends People{ //局部内部类,定义在getWoman这个方法内
int age =0;
}
return new Woman();
}
}

1.3 匿名内部类

匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。

注:在编写事件监听代码时使用匿名内部类不但方便,而且使代码更加容易维护。

1.语法

1
2
3
new <类或接口>() {
// 类的主体
};

优点:这种形式的 new 语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高。

2.匿名类的两种实现方式
1)继承一个类,重写其方法。
2)实现一个接口(可以是多个),实现其方法。

注:匿名内部类实现一个接口的方式与实现一个类的方式相同

3.案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Out {
void show(){
System.out.println("调用 Out 类的 show() 方法");
}
}
class testAnonyInter { // 测试
private void show() {
// 在这个方法中构造一个匿名内部类
Out anonyInter = new Out() {
// 获取匿名内部类的实例
void show() {
System.out.println("调用匿名类中的 show() 方法");
}
};
anonyInter.show();
}
public static void main(String[] args) {
testAnonyInter tA = new testAnonyInter();
tA.show();
}
}

运行结果

1
调用匿名类中的 show() 方法

4.匿名类的特点
1)匿名类和局部内部类一样,可以访问外部类的所有成员。eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Out {
void show(){
System.out.println("调用 Out 类的 show() 方法");
}
}
class testAnonyInter {
public static void main(String[] args) {
int a = 10;
final int b = 10;
Out anonyInter = new Out() { // 构造一个匿名类
void show() {
System.out.println("调用了匿名类的 show() 方法"+a); // 编译出错
System.out.println("调用了匿名类的 show() 方法"+b); // 编译通过
}
};
anonyInter.show();
}
}

运行结果

1
2
调用了匿名类的 show() 方法10
调用了匿名类的 show() 方法10

2)匿名类中允许使用非静态代码块进行成员初始化操作。eg:

1
2
3
4
5
6
7
8
Out anonyInter = new Out() {
int i; { // 非静态代码块
i = 10; //成员初始化
}
public void show() {
System.out.println("调用了匿名类的 show() 方法"+i);
}
}

3)匿名类的非静态代码块会在父类的构造方法之后被执行。

5.补充

匿名内部类不能有访问修饰符和static修饰符。

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,==大部分匿名内部类用于接口回调==。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

1.4 静态内部类

1)概念
定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
2)说明

静态内部类不需要依赖于外部类,这点和类的静态成员属性类似,并且它不能使用外部类的非static成员变量或者方法,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

3)eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test { // 测试类
public static void main(String[] args){
Outter.Inner inner=new Outter.Inner();
}
}
class Outter{
int a=10;
static int b=5;
public Outter(){
}
static class Inner{ //静态内部类
public Inner(){
//System.out.println(a); 编译出错,java: 无法从静态上下文中引用非静态变量 a
System.out.println(b); //输出结果:5
}
}
}

2、抽象类

2.1 抽象方法

1)概念
一个方法使==用 abstract 来修饰,且只有声明没有实现==。
2)特征

  • 使用 abstract 修饰且没有方法体(没有方法体与空方法体不同)。
  • 抽象方法只能定义在抽象类或接口中。
  • 不能使用 private、final 或 static 修饰。

    注:在使用 abstract 关键字修饰抽象方法时不能使用 private 修饰,因为抽象方法必须被子类重写,而如果使用了 private 声明,则子类是无法重写的。

2.2 抽象类

与抽象方法一样,抽象类也要使用 abstract 关键字声明。几点注意如下:

==一个方法被声明为抽象的,那么这个类也必须声明为抽象的==。一个抽象类中,可以有 0~n 个抽象方法和具体方法。

有构造方法,但不能直接用来创建对象,只留给子类创建对象时调用。

==子类重写父类时,必须重写父类所有的抽象方法==。即抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

==抽象类不能实例化,即不能使用 new 关键字创建对象==。

==要访问抽象类,它必须从另一个类继承==。

3)代码示例
创建一个抽象类 Shape

1
2
3
4
5
6
7
8
9
public abstract class Shape {
public int width; // 几何图形的长
public int height; // 几何图形的宽
public Shape(int width, int height) { //带全部参数的构造方法
this.width = width;
this.height = height;
}
public abstract double area(); // 定义抽象方法,计算面积
}

定义一个正方形类Square,继承形状类 Shape,并重写了 area( ) 抽象方法。

1
2
3
4
5
6
7
8
9
10
public class Square extends Shape {
public Square(int width, int height) { //带全部参数的构造方法
super(width, height);
}
// 重写父类中的抽象方法,实现计算正方形面积的功能
@Override
public double area() {
return width * height;
}
}

再定义一个三角形类Triangle,继承形状类 Shape,并重写父类中的抽象方法 area()。

1
2
3
4
5
6
7
8
9
10
public class Triangle extends Shape {
public Triangle(int width, int height) { //带全部参数的构造方法
super(width, height);
}
// 重写父类中的抽象方法,实现计算三角形面积的功能
@Override
public double area() {
return 0.5 * width * height;
}
}

最后创建一个测试类。

1
2
3
4
5
6
7
8
public class ShapeTest {
public static void main(String[] args) {
Square s = new Square(5, 4); // 创建正方形类对象
System.out.println("正方形的面积为:" + s.area());
Triangle t = new Triangle(2, 5); // 创建三角形类对象
System.out.println("三角形的面积为:" + t.area());
}
}

运行结果

1
2
正方形的面积为:20.0
三角形的面积为:5.0

总结:Shape 类只是定义了计算图形面积的方法,而对于如何计算并没有任何限制,需要子类==重写==父类抽象方法来具体实现。即==抽象类 Shape 仅定义了子类的一般形式,所以是抽象的==。

3、基本类型包装类

1)装箱
基本类型数据转成对应的包装类对象
2)拆箱
包装类对象转成对应的基本类型数据
3)自动装箱
把一个基本类型变量直接赋给对应的包装类变量或 Object 变量

  • 在底层依然是手动装箱,使用的是 Xxx.valueOf() 方法

4)自动拆箱
包装类对象直接赋给对应的基本类型变量,或者包装类对象与基本数据类型变量使用“==”比较

  • 在底层依然是手动拆箱,使用的是 xxxValue() 方法

注:八大基本数据类型的包装类都是最终类、不可变类(对应的储存数值的成员变量 value 值使用 private final 修饰)

5)对应

Byte、Short、Integer、Long、Float、Double、BigDecimal、BigInteger 类都是 Number 抽象类的子类,都是 Comparable 接口的实现类

6)案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) {
// 装箱
Integer intObj1 = new Integer(123);
Integer intObj2 = Integer.valueOf(123); // 推荐,带有缓存
// 拆箱
int num3 = intObj1.intValue(); // int num3 = new Integer(123);
Integer intObj4 = 123; // 底层 Integer intObj4 = Integer.valueOf(123)
int num5 = intObj4; // 底层 int num5 = intObj4.intValue()
boolean b = intObj4 == 123;// true,底层 intObj4.intValue() == 123
Object boolObj = true;
if (boolObj instanceof Boolean) {
boolean b1 = (Boolean)boolObj; // 大转小(Object->boolean),强转
System.out.println(b1);
}
}
}
// 结果:true