Fork me on GitHub

十六.抽象类和抽象方法、接口、内部类

一.抽象类和抽象方法

1.抽象方法

使用abstract修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。

2.抽象类

包含抽象方法的类就是抽象类。通过abstract方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。

3.看看具体实现

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* @author RickYinPeng
* @ClassName Animal
* @Description 抽象类和抽象方法
* @date 2019/1/19/11:18
*/
public abstract class Animal {

/**
* 抽象类也可以定义普通的字段属性
*/
private String name;

/**
* 抽象类可以包含构造方法,但是我们不能调用这个构造方法,会报错
*/
public Animal() {
}

/**
* 如果一个类中有抽象方法,那么这个类一定是抽象类
* 抽象方法的意义在于:将方法的设计和方法的实现分离了
*/
public abstract void run();

/**
* 抽象类中可以有普通方法,就跟我们平常的类一样
*/
public void breath(){
System.out.println("普通方法");
/**
* 我们在这个抽象类的普通方法中调用了它的一个抽象方法,有人就疑惑了,其实这里会调用子类的run方法
* 这个我们之前说过,在多态提高篇中说过
*/
run();
}
}

/**
* 子类继承一个抽象类:
* 1:要么实现抽象类的所有抽象方法
* 2:要么将其本身定义为抽象类,就像这样:class abstract Cat extends Animal{} 这样就可以不用重写抽象方法了
*/
class Cat extends Animal{

/**
* 子类继承抽象类,那么必须实现抽象类中的抽象方法
*/
@Override
public void run() {

}

}

4. 抽象类的注意事项:


1. 抽象类也可以定义普通的字段属性

2. 抽象类可以包含构造方法,但是我们不能调用这个构造方法,会报错

3. 如果一个类中有抽象方法,那么这个类一定是抽象类

4. 抽象类中可以有普通方法,就跟我们平常的类一样

二.接口

1.为什么需要接口?接口和抽象类的区别?

接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。

抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。接口是完全面向规范的,规定了一批类具有的公共方法规范。

接口和实现类不是父子关系,是实现规则的关系。比如:我定义一个接口Runnable,Car实现它就能在地上跑,Train实现它也能在地上跑,飞机实现它也能在地上跑。就是说,如果它是交通工具,就一定能跑,但是一定要实现Runnable接口。

接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。

### 2.如何定义和使用接口?

#### 2.1 声明格式

1
2
3
4
[访问修饰符]  interface 接口名   [extends  父接口1,父接口2…]  {
常量定义;
方法定义;
}


#### 2.2 定义接口的详细说明
1. 访问修饰符:只能是public或默认。
2. 接口名:和类名采用相同命名机制。
3. extends:接口可以多继承。
4. 常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
5. 方法:接口中的方法只能是:public abstract。 省略的话,也是public abstract。

#### 2.3 注意
1. 子类通过implements来实现接口中的规范。
2. 接口不能创建实例,但是可用于声明引用变量类型。
3. 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
4. JDK1.7之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
5. JDK1.8后,接口中包含普通的静态方法。

#### 2.4 具体使用

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* @author RickYinPeng
* @ClassName Test_05
* @Description 接口
* @date 2019/1/19/11:36
*/
public class Test_05 {
}

interface MyInterface{
/**
* 接口中只有:常量和抽象方法
* 注意:
* 接口中的常量前面的修饰是 public static final 你写都不写他都有
*/
/*public static final */String MAX_GREAD="BOSS";
int MAX_SPEED=120;

/**
* 注意:
* 接口中的方法不能是私有的,也即不能是private修饰的
*/
/*private void test03();*/

/**
* 注意:
* 接口中的方法默认修饰就是 public abstract 修饰的,你写与不写都是一样的
*/
/*public abstract */void test01();
public int test02(int a,int b);

/**
* JDK1.8中接口中可以有方法了,但是必须是default修饰的
*/
default void test(){
System.out.println();
}
}

/**
* 实现接口,必须实现接口中的方法,默认方法可以不用实现
*/
class MyClass implements MyInterface{

@Override
public void test01() {
/**
* 所以接口中这个常量是 public static final 修饰的(加于不加都是这样)
*/
String a = MyInterface.MAX_GREAD;
System.out.println("test01");
}

@Override
public int test02(int a, int b) {
System.out.println(a+b);
return a+b;
}
}


#### 2.5 接口的多继承


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
33
34
35
36
37
38
39
/**
* @author RickYinPeng
* @ClassName Test_06
* @Description 接口支持多继承
* @date 2019/1/19/11:59
*/
interface InterfaceA{
void aaa();
}
interface InterfaceB{
void bbb();
}

/**
* 接口支持多继承,那么现在接口InterfaceC就拥有接口InterfaceA和InterfaceB的方法了,即拥有aaa()和bbb();
*/
interface InterfaceC extends InterfaceA,InterfaceB{
void ccc();
}

/**
* 所以当一个类实现InterfaceC的时候需要去重写这三个方法
*/
class TestClass implements InterfaceC{
@Override
public void aaa() {

}

@Override
public void bbb() {

}

@Override
public void ccc() {

}
}


## 三.内部类

一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类(innerclasses)。

内部类可以使用public、default、protected 、private以及static修饰。而外部顶级类(我们以前接触的类)只能使用public和default修饰。

内部类只是一个编译时概念,一旦我们编译成功,就会成为完全不同的两个类。对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后会出现Outer.class和Outer$Inner.class两个类的字节码文件。所以内部类是相对独立的一种存在,其成员变量/方法名可以和外部类的相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**外部类Outer*/
class Outer {
private int age = 10;
public void show(){
System.out.println(age);//10
}
/**内部类Inner*/
public class Inner {
//内部类中可以声明与外部类同名的属性与方法
private int age = 20;
public void show(){
System.out.println(age);//20
}
}
}

1.内部类分类

在Java中内部类主要分为成员内部类(非静态内部类、静态内部类)、匿名内部类、局部内部类。

2.成员内部类

成员内部类(可以使用private、default、protected、public任意进行修饰。 类文件:外部类$内部类.class)

2.1 非静态内部类(外部类里使用非静态内部类和平时使用其他类没什么不同)

  1. 非静态内部类必须寄存在一个外部类对象里。因此,如果有一个非静态内部类对象那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对象。
  2. 非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类成员。
  3. 非静态内部类不能有静态方法、静态属性和静态初始化块。
  4. 外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例。
成员变量访问要点:
  1. 在内部类中访问内部类里方法的局部变量:直接写变量名即可。
  2. 在内部类中访问内部类属性:要写this.变量名。
  3. 在内部类中访问外部类属性:要写外部类名.this.变量名。
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* @author RickYinPeng
* @ClassName Test_InnerClass_06
* @Description 测试非静态内部类
* @date 2019/1/19/15:04
*/
public class Test_06_InnerClass {
public static void main(String[] args) {

//创建内部类对象
Outer.Inner inner = new Outer().new Inner();

inner.show();
}
}
class Outer{
private int age = 10;

public void testOuter(){
System.out.println("Outer.testOuter()");
}

/**
* 虽然Inner是Outer的内部类,但是在编译生成的时候还是会生成两个class文件
*/
/**
* 非静态内部类:class Inner{}
* 1:非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类成员
* 2:静态内部类不能有静态方法、静态属性和静态初始化块
* 静态内部类: static class Inner{}
* 1:当一个静态内部类存在时,并不一定存在对应的外部类对象。所以静态内部类的实例方法不能直接访问外部类的实例方法
* 2:静态内部类看作外部类的一个静态成员,因此,外部类的方法中可以通过:"静态内部类.名字"的方式访问静态内部类的静态成员
* 3:通过 new 静态内部类()访问静态内部类的实例
*/
class Inner{

/*static {
会报错
}*/

int age = 20;

public void show(){
int age = 30;
System.out.println("外部类的成员变量age:"+Outer.this.age);
System.out.println("内部类的成员变量age:"+this.age);
System.out.println("局部变量age:"+age);
}
}
}

2.2 静态内部类

定义方式:

1
2
3
static  class   ClassName {
//类体
}

注意:

  1. 当一个静态内部类对象存在,并不一定存在对应的外部类对象。 因此,静态内部类的实例方法不能直接访问外部类的实例方法。
  2. 静态内部类看做外部类的一个静态成员。 因此,外部类的方法中可以通过:“静态内部类.名字”的方式访问静态内部类的静态成员,通过 new 静态内部类()访问静态内部类的实例。
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package yp.JavaSE.Review_05;

/**
* @author RickYinPeng
* @ClassName Test_07_staticClass
* @Description
* @date 2019/1/19/15:22
*/
public class Test_07_staticClass {
public static void main(String[] args) {
/**
* 创建静态内部类
*/
Outer2.Inner2 inner2 = new Outer2.Inner2();
}
}
class Outer2{

private int age = 10;

/**
* 外部类可以访问静态内部类的静态成员变量,但是不能访问非静态成员变量
*/
public void testOuter(){

System.out.println(Inner2.age);
}

/**
* 静态内部类
*/
static class Inner2{
/**
* 静态内部类可以创建静态成员
*/
static int age;

public String name;

public void show(){
/**
* 静态内部类不能直接访问外部类的成员变量
*/
/*System.out.println(Outer2.this.age);*/
}
}

}

3.匿名内部类

适合那种只需要使用一次的类。比如:键盘监听操作等等。

语法:

1
2
3
new  父类构造器(实参类表) \实现接口 () {
//匿名内部类类体!
}

匿名内部类的使用

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
/**
* @author RickYinPeng
* @ClassName Test_08_anonymousInnerClass
* @Description 测试匿名内部类
* @date 2019/1/19/15:51
*/
public class Test_08_anonymousInnerClass {
public static void main(String[] args) {
Test_08_anonymousInnerClass.test01(new A() {
@Override
public void aa() {
System.out.println("匿名内部类");
}
});

}

public static void test01(A a){
a.aa();
}

}

interface A{
void aa();
}

注意:

  1. 匿名内部类没有访问修饰符。
  2. 匿名内部类没有构造方法。因为它连名字都没有那又何来构造方法呢。

4.局部内部类

还有一种内部类,它是定义在方法内部的,作用域只限于本方法,称为局部内部类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test2 {
public void show() {
//作用域仅限于该方法
class Inner {
public void fun() {
System.out.println("helloworld");
}
}
new Inner().fun();
}
public static void main(String[] args) {
new Test2().show();
}
}