# 接口与继承

# 接口

  1. 引用数据类型,interface定义,只能包含常量和抽象方法不能被实例化(new对象) ,默认public static finalpublic abstract

  2. 需要被实现/继承,实现类:必须重写所有抽象方法(重写时必须加public关键字)

  3. 接口中成员的访问权限只能是public的,不写也默认是public.

  4. 一个类可以实现多个接口,用逗号分隔。若又继承又实现,应先继承后实现

    修饰符 interface A extends 接口1,接口2{
    
    }
    
    修饰符 class A implements 接口1,接口2{
    
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
  5. 接口可以继承接口

    关系;

    类与类-----------------继承

    类与接口-------------- 实现implements

    接口与接口------------继承

接口用于描述类所具有的功能,而不提供功能的实现,功能的实现需要写在实现接口的类中,并且该类必须实现接口中所有的未实现方法。

接口的声明语法格式如下:

修饰符 interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

1
2
3
4
5

如声明一个 Animal 接口:

// Animal.java
interface Animal {
        //int x;
        //编译错误,x需要初始化,因为是 static final 类型
        int y = 5;
        public void eat();
        public void travel();
}

1
2
3
4
5
6
7
8
9

注意点

在 Java9 中,接口可以拥有私有方法和私有静态方法,但是只能被该接口中的 default 方法和静态方法使用。

实现上面的接口:

// Cat.java
public class Cat implements Animal{

     public void eat(){
         System.out.println("Cat eats");
     }

     public void travel(){
         System.out.println("Cat travels");
     }
     public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.travel();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 设计规则

  1. 将所有派生类所共有的属性和行为,抽到超类中------------抽共性

  2. 派生类的行为都一样,设计为普通方法

    派生类的行为不一样,设计为抽象方法

  3. 将部分派生类所共有的属性和行为,抽到接口中

    接口是对继承的单根性的扩展-----------实现多继承

# 封装

封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别

这样做有什么好处?

  1. 只能通过规定的方法访问数据。
  2. 隐藏类的实例细节,方便修改和实现。

我们在开汽车的时候,只用去关注如何开车,我们并不在意车子是如何实现的,这就是封装。

如何去实现类的封装呢?

  1. 修改属性的可见性,在属性的前面添加修饰符 (private)
  2. 对每个值属性提供对外的公共方法访问,如创建 getter/setter(取值和赋值)方法,用于对私有属性的访问
  3. 在 getter/setter 方法里加入属性的控制语句,例如我们可以加一个判断语句,对于非法输入给予否定。

img

# getter/setter

class Student{

    private String name;
    private int age;


    public String getName(){ //getter获取
        return name;
    }
    public void setName(String name){ //setter设置
        this.name = name;
    }
    public int getAge(){ //getter获取
        return age;
    }
    public void setAge(int age){ //setter设置

        this.age = age;
    }

}





//getter和setter的演示
public class GetterSetterDemo {

    public static void main(String[] args) {

        Student zs = new Student();
        zs.setName("zhangsan");
        zs.setAge(25);
        System.out.println(zs.getName());
        System.out.println(zs.getAge());


        Student ls = new Student();
        ls.setName("lisi");
        ls.setAge(24);
        System.out.println(ls.getName());
        System.out.println(ls.getAge());

    }

}

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

如果我们没有在属性前面添加任何修饰符,我们通过对象就可以直接对属性值进行修改,没有体现封装的特性。这在许多程序设计中都是不安全的,所以我们需要利用封装,来改进我们的代码。

首先在类里要将属性前添加 private 修饰符。然后定义 gettersetter 方法。修改 People.javaNewObject.java 的内容如下。

public class People {
    //属性(成员变量)有什么,前面添加了访问修饰符private
    //变成了私有属性,必须通过方法调用
    private double height;     //身高

    //属性已经封装好了,如果用户需要调用属性
    //必须用getter和setter方法进行调用
    //getter和setter方法需要程序员自己定义
    public double getHeight(){
    //getter 方法命名是get关键字加属性名(属性名首字母大写)
    //getter 方法一般是为了得到属性值
      return height;
    }

    //同理设置我们的setter方法
    //setter 方法命名是set关键字加属性名(首字母大写)
    //setter 方法一般是给属性值赋值,所以有一个参数
    public void setHeight(double newHeight){
      height = newHeight;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

现在 main 函数里的对象,不能再直接调用属性了,只能通过 gettersetter 方法进行调用。

public class NewObject {

    public static void main(String[] args) {
        People LiLei = new People();    //创建了一个People对象LiLei

        //利用setter方法为属性赋值
        LiLei.setHeight(170.0);

        //利用getter方法取属性值
        System.out.println("LiLei的身高是"+LiLei.getHeight());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 继承

继承可以看成是类与类之间的衍生关系。比如狗类是动物类,牧羊犬类又是狗类。于是我们可以说狗类继承了动物类,而牧羊犬类就继承了狗类。于是狗类就是动物类的子类(或派生类),动物类就是狗类的父类(或基类)。

所以继承需要符合的关系是:is a(是的关系),父类更通用,子类更具体。

语法:

class 子类 extends 父类

1
2

例如我们定义了一个 Animal 类,再创建一个 Dog 类,我们需要它继承 Animal 类。

class Dog extends Animal {
    ...
}

1
2
3
4

超类(super class)/父类(parent class)/基类(base class):派生类所共有的属性和行为

派生类(extended class)/子类(subclass):派生类所特有的属性和行为

  1. 派生类可以访问:派生类的+超类的,而超类只能访问超类的,不能访问派生类

  2. 一个超类可以有多个派生类,一个派生类只能有一个超类---------单一继承

    注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段!

接下来我们就来练习一下吧!

我们先创建一个父类 Animal.java

public class Animal {
    public int legNum;     //动物四肢的数量

    //类方法
    public void bark() {
        System.out.println("动物叫!");
    }
}

1
2
3
4
5
6
7
8
9

接下来创建一个子类Dog.java

public class Dog extends Animal {
}

1
2
3

Dog 类继承了父类 Animal,我们 Dog 类里什么都没有写,其实它继承了父类 Animal,所以 Dog 类拥有 Animal 类的全部方法和属性(除开 private 方法和属性)。我们创建一个测试类测试一下。

public class Test{
    public static void main(String[] args) {
        Dog a = new Dog();
        a.legNum = 4;
        a.bark();
    }
}
1
2
3
4
5
6
7

为什么需要继承?

如果有两个类相似,那么它们会有许多重复的代码,导致后果就是代码量大且臃肿,后期的维护性不高。通过继承就可以解决这个问题,将两段代码中相同的部分提取出来组成一个父类,实现代码的复用

继承的特点

  • 子类拥有父类除 private 以外的所有属性和方法。

  • 子类可以拥有自己的属性和方法。

  • 子类可以重写实现父类的方法。

  • Java 中的继承是单继承,一个类只有一个父类。

  • 继承具有传递性

  • 构造派生类之前必须先构造超类

    1)在派生类的构造方法中若自己没有调用超类构造方法,

    ----则默认super()来调用超类的无参构造方法

    注意:super()调用超类构造方法必须位于派生类构造方法的第一行

    2)在派生类的构造方法中若自己调用了超类的构造方法,

    ----则不再默认提供

注:Java 实现多继承的一个办法是 implements(实现)接口,但接口不能有非静态的属性,这一点请注意。

# 继承树

注意到我们在定义Person的时候,没有写extends。在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类。下图是PersonStudent的继承树:

┌───────────┐
│  Object   │
└───────────┘
      ▲
      │
┌───────────┐
│  Person   │
└───────────┘
      ▲
      │
┌───────────┐
│  Student  │
└───────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13

Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。

类似的,如果我们定义一个继承自PersonTeacher,它们的继承树关系如下:

       ┌───────────┐
       │  Object   │
       └───────────┘
             ▲
             │
       ┌───────────┐
       │  Person   │
       └───────────┘
          ▲     ▲
          │     │
          │     │
┌───────────┐ ┌───────────┐
│  Student  │ │  Teacher  │
└───────────┘ └───────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# protected

继承有个特点,就是子类无法访问父类的private字段或者private方法。例如,Student类就无法访问Person类的nameage字段:

class Person {
    private String name;
    private int age;
}

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // 编译错误:无法访问name字段
    }
}
1
2
3
4
5
6
7
8
9
10

这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问:

class Person {
    protected String name;
    protected int age;
}

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // OK!
    }
}
1
2
3
4
5
6
7
8
9
10

因此,protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问,后面我们还会详细讲解。

# super

super 关键字在子类内部使用,代表父类对象。

  1. 访问超类/父类的属性(成员变量) super.属性名,没有同名现象,一般省略.

  2. 访问超类/父类的方法 super.bark(),用于发生方法重写的时候.

  3. 子类构造方法需要调用超类/父类的构造方法时,在子类的构造方法体里最前面的位置:super(),否则会执行默认的super().

  4. 构造派生类之前必须先构造超类.

    • 在派生类构造方法中若没有调用超类的构造方法,则默认super()调用超类无参构造方法
    • 在派生类的构造方法中若自己调用了超类的构造方法,则不再默认提供
    • 成员变量在哪个类中,构造方法就在哪个类中.
    public class SuperDemo {
        public static void main(String[] args) {
            Boo o = new Boo();
        }
    }
    
    class Coo{
        Coo(int a){
        }
    }
    class Doo extends Coo{
        Doo(){
            super(5);//调用超类的有参构造方法,不加这句话如默认
        }
        /*
        //如下代码为默认的,编译错误
        Doo(){
            super();
        }
        */
    }
    
    class Aoo{
        Aoo(){
            System.out.println("超类构造");
        }
    }
    class Boo extends Aoo{
        Boo(){
            super(); //默认的,调用超类的无参构造方法
            System.out.println("派生类构造");
        }
    }
    
    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

# 多态

多态是指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。多态也称作动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

通俗地讲,只通过父类就能够引用不同的子类,这就是多态,我们只有在运行的时候才会知道引用变量所指向的具体实例对象。

# 多态的实现条件

Java 实现多态有三个必要条件:继承、重写和向上转型(即父类引用指向子类对象)。

只有满足上述三个条件,才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

# 多态的意义

  1. 同一类型的引用在指向不同的对象时,有不同的实现(所有抽象方法都是多态的)

    ----行为的多态:cut()step()getImage()......

  2. 同一个对象被造型为不同的类型时,有不同的功能(所有对象都是多态的) ----对象的多态:我、水......

# 多态的实现方式

Java 中多态的实现方式:1.继承父类进行方法重写,2.抽象类和抽象方法,3.接口实现。

# 向上转型/向上造型 ---------------自动类型转换

  1. 超类型的引用指向派生类的对象
  2. 能造型成为的数据类型: 超类+所实现的接口
  3. 能点出来什么,看引用的类型--------这是规定,记住就OK
  4. **何时向上转型(造型)😗*当多种角色能干的事是一样的,可以将那多种角色造型到超类数组中,统一访问----------------实现代码复用

要理解多态必须要明白什么是"向上转型",比如,一段代码如下,Dog 类是 Animal 类的子类:

Animal a = new Animal();  //a是父类的引用指向的是本类的对象

Animal b = new Dog(); //b是父类的引用指向的是子类的对象

1
2
3
4

在这里,可以认为由于 Dog 继承于 Animal,所以 Dog 可以自动向上转型为 Animal,所以 b 是可以指向 Dog 实例对象的。

注:不能使用一个子类的引用去指向父类的对象,因为子类对象中可能会含有父类对象中所没有的属性和方法。

如果定义了一个指向子类对象的父类引用类型,那么它除了能够引用父类中定义的所有属性和方法外,还可以使用子类强大的功能。但是对于只存在于子类的方法和属性就不能获取。

新建一个 Test.java,例如:

class Animal {
    //父类方法
    public void bark() {
        System.out.println("动物叫!");
    }
}

class Dog extends Animal {

    //子类重写父类的bark方法
    public void bark() {
        System.out.println("汪、汪、汪!");
    }
    //子类自己的方法
    public void dogType() {
        System.out.println("这是什么品种的狗?");
    }
}


public class Test {

    public static void main(String[] args) {
        Animal a = new Animal();
        Animal b = new Dog();
        Dog d = new Dog();

        a.bark();
        b.bark();
        //b.bark();
        //b.dogType()编译不通过
        d.bark();
        d.dogType();
    }

}
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

在这里,由于 b 是父类的引用,指向子类的对象,因此不能获取子类的方法(dogType() 方法), 同时当调用 bark() 方法时,由于子类重写了父类的 bark() 方法,所以调用子类中的 bark() 方法。

因此,向上转型,在运行时,会遗忘子类对象中与父类对象中不同的方法,也会覆盖与父类中相同的方法——重写(方法名,参数都相同)。

# 强制类型转换/向下造型

成功的条件只有如下两种:

  1. 引用所指向的对象,就是该类型

    interface Inter()
    class Aoo{ }
    class Boo extends Aoo implements Inter{}
    class Coo extends Aoo{}
    
    
    Aoo o = new Boo();
    Boo o1 = (Boo)o;//符合条件1
    Inter o2 = (Inter)o;//符合条件2
    Coo o3 = (Coo)o;//发生ClassCastException类型转换异常  
    if(o instanceof Coo){
        Coo o3 = (Coo)o;
    }else{
        System.out.println("o不是Coo类型")
    }
    
    System.out.println(o inatanceof Boo);//返回true,可以强制转换
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  2. 引用所指向的对象,继承了该类或实现了该接口

强转时若不符合如上两个条件,则发生ClassCastException类型转换异常

建议在强转之前应先通过instanceof来判断引用指向的对象是否是该类型

# final

final 关键字可以修饰类、方法、属性和变量,应用率极低

  1. final 修饰类,则该类不允许被继承,为最终类

  2. final 修饰方法,则该方法不允许被覆盖(重写)

  3. final 修饰属性:则该类的属性不会进行隐式的初始化(类的初始化属性必须有值)或在构造方法中赋值(但只能选其一)

  4. final 修饰变量,则该变量的值只能赋一次值,即常量

如:

//静态常量
public final static String SHI_YAN_LOU="shiyanlou";
1
2

# static final常量(常用)

  1. 必须声明同时初始化
  2. 通过类名点来访问,不能被改变
  3. 建议:常量名所有字母都大写,多个单词用_分隔 ,eg: EXIT_ON_CLOSE
  4. 编译器在编译时会将常量直接替换为具体的值,效率高
  5. 何时用:数据永远不变,并且经常使用
public class StaticFinalDemo {
    public static void main(String[] args) {
        System.out.println(Aoo.PI); //通过类名点来访问
        //Aoo.PI = 3.1415926; //编译错误,常量不能被改变


        //1)加载Boo.class到方法区中
        //2)静态变量num一并存储到方法区中
        //3)到方法区中获取num的值并输出
        System.out.println(Boo.num);

        //编译器在编译时会将常量直接替换为具体的值,效率高
        //相当于System.out.println(5);
        System.out.println(Boo.COUNT);

    }
}

class Boo{
    public static int num = 5; //静态变量
    public static final int COUNT = 5; //常量
}

class Aoo{
    public static final double PI = 3.14159;
    //public static final int NUM; //编译错误,常量必须声明同时初始化
}
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

# 抽象方法

  1. abstract修饰
  2. 只有方法的定义,没有具体的实现(连{}都没有)
  3. 包含抽象方法的类必须是抽象类

抽象方法声明语法如下:

abstract void f();  //f()方法是抽象方法

1
2

# 抽象类

  1. abstract修饰

  2. 包含抽象方法的类必须是抽象类,不包含抽象方法的类也可以声明为抽象类.

  3. 抽象类不能被实例化(new对象) ,抽象类的对象不能直接创建,通常是定义引用变量指向子类对象

    SeaObject o = new SeaObject();//编译错误,创建了SeaObject对象
    SeaObject o;
    SeaObject[] os = new SeaObject[3];//创建SeaObject数组d
    
    1
    2
    3
  4. 抽象类是需要被继承的,派生类:

    1. 重写所有抽象方法---------变不完整为完整
    2. 也声明为抽象类-----------一般不这么做
  5. 抽象类的意义:

    1. 封装共有的属性和行为-----------代码复用
    2. 给所有派生类提供统一的类型-----向上造型
    3. 可以包含抽象方法,为所有派生类提供统一的入口(造型后能点出来)
    4. 派生类的具体实现不同,但入口是一致的

那抽象类如何用代码实现呢,它的规则如下:

  1. abstract 修饰符定义抽象类。
  2. abstract 修饰符定义抽象方法,只用声明,不需要实现。

我们来写一写代码吧!

1、在 home/project/ 目录下创建一个抽象类 TelePhone.java

2、填写需要子类实现的抽象方法。

//抽象方法
public abstract class TelePhone {
    public abstract void call();  //抽象方法,打电话
    public abstract void message(); //抽象方法,发短信
}

1
2
3
4
5
6

3、构建子类,并实现抽象方法。新建一个 CallPhone.java

public class CallPhone extends TelePhone {

    @Override
    public void call() {
        System.out.println("我可以打电话!");
    }

    @Override
    public void message() {
        System.out.println("我可以发短信!");
    }

    public static void main(String[] args) {
        CallPhone cp = new CallPhone();
        cp.call();
        cp.message();
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

CallPhone 类添加 main 方法测试运行结果。

普通方法意味着可以重写也可以不重写,但抽象方法是必须被重写的,设计为抽象方法可以达到强制派生类必须重写的目的

# 内部类

将一个类的定义放在另一个类的定义内部,这就是内部类。而包含内部类的类被称为外部类。

内部类的主要作用如下:

  1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类
  2. 内部类的方法可以直接访问外部类的所有数据,包括私有的数据
  3. 内部类所实现的功能使用外部类同样可以实现,只是有时使用内部类更方便
  4. 内部类允许继承多个非接口类型

注:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为 outer 的外部类和其内部定义的名为 inner 的内部类。编译完成后出现 outer.classouter$inner.class 两类。所以内部类的成员变量 / 方法名可以和外部类的相同。

# 成员内部类(应用率低)

  1. 类中套类,外面的称为外部类,里面的称为内部类

  2. 内部类通常只服务于外部类,对外不具备可见性

  3. 内部类对象只能在外部类中创建

  4. 内部类中可以直接访问外部类的成员(包括私有的)

  5. 内部类中有个隐式的引用指向了创建它的外部类对象

    语法: 外部类名.this
    
    1
    class Aoo{//外部类
        private int a;//私有也可以访问
        Boo o1 = new Boo();
        void create(){
            Baby b = new Baby();//正确,内部类对象通常在外部类中创建
        }
        class Boo{//内部类
            void show(){
                System.out.println(a);//两个等价,省略写法
                System.out.println(Aoo.this.a);//完整写法
                System.out.println(this.a);//编译错误
            }
        }
        class Baby{
            
        }
        
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
// People.java
//外部类People
public class People {
    private String name = "LiLei";         //外部类的私有属性
    //内部类Student
    public class Student {
        String ID = "20151234";               //内部类的成员属性
        //内部类的方法
        public void stuInfo(){
            System.out.println("访问外部类中的name:" + name);
            System.out.println("访问内部类中的ID:" + ID);
        }
    }

    //测试成员内部类
    public static void main(String[] args) {
        People a = new People();     //创建外部类对象,对象名为a
        //成员内部类的实例化语法:
        Student b = a.new Student(); //使用外部类对象创建内部类对象,对象名为b
        // 或者为 People.Student b = a.new Student();
        b.stuInfo();   //调用内部对象的stuInfo方法
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

成员内部类的使用方法:

  1. Student 类相当于 People 类的一个成员变量,所以 Student 类可以使用任意访问修饰符。
  2. Student 类在 People 类里,所以访问范围在类里的所有方法均可以访问 People 的属性(即内部类里可以直接访问外部类的方法和属性,反之不行)。
  3. 定义成员内部类后,必须使用外部类对象来创建内部类对象,即 内部类 对象名 = 外部类对象.new 内部类();
  4. 如果外部类和内部类具有相同的成员变量或方法,内部类默认访问自己的成员变量或方法,如果要访问外部类的成员变量,可以使用 this 关键字。如上述代码中:a.this

注:成员内部类不能含有 static 的变量和方法,因为成员内部类需要先创建了外部类,才能创建它自己的。

# 静态内部类

静态内部类通常被称为嵌套类。

// People.java
//外部类People
public class People {
    private String name = "LiLei";         //外部类的私有属性

/*外部类的静态变量。
Java 中被 static 修饰的成员称为静态成员或类成员。它属于整个类所有,而不是某个对象所有,即被类的所有对象所共享。静态成员可以使用类名直接访问,也可以使用对象名进行访问。
*/
    static String ID = "510xxx199X0724XXXX";

    //静态内部类Student
    public static class Student {
        String ID = "20151234";               //内部类的成员属性
        //内部类的方法
        public void stuInfo(){
            System.out.println("访问外部类中的name:" + (new People().name));
            System.out.println("访问外部类中的ID:" + People.ID);
            System.out.println("访问内部类中的ID:" + ID);
        }
    }

    //测试成员内部类
    public static void main(String[] args) {
        Student b = new Student();   //直接创建内部类对象,对象名为b
        b.stuInfo();                 //调用内部对象的suInfo方法
    }
}
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

静态内部类是 static 修饰的内部类,这种内部类的特点是:

  1. 静态内部类不能直接访问外部类的非静态成员,但可以通过 new 外部类().成员 的方式访问。
  2. 如果外部类的静态成员与内部类的成员名称相同,可通过 类名.静态成员 访问外部类的静态成员;如果外部类的静态成员与内部类的成员名称不相同,则可通过 成员名 直接调用外部类的静态成员。
  3. 创建静态内部类的对象时,不需要外部类的对象,可以直接创建 内部类 对象名 = new 内部类();

# 局部内部类

局部内部类,是指内部类定义在方法和作用域内。

例如:

// People.java
//外部类People
public class People {
    //定义在外部类中的方法内:
    public void peopleInfo() {
        final String sex = "man";  //外部类方法中的常量
        class Student {
            String ID = "20151234"; //内部类中的常量
            public void print() {
                System.out.println("访问外部类的方法中的常量sex:" + sex);
                System.out.println("访问内部类中的变量ID:" + ID);
            }
        }
        Student a = new Student();  //创建方法内部类的对象
        a.print();//调用内部类的方法
    }
    //定义在外部类中的作用域内
    public void peopleInfo2(boolean b) {
        if(b){
            final String sex = "man";  //外部类方法中的常量
            class Student {
                String ID = "20151234"; //内部类中的常量
                public void print() {
                    System.out.println("访问外部类的方法中的常量sex:" + sex);
                    System.out.println("访问内部类中的变量ID:" + ID);
                }
            }
            Student a = new Student();  //创建方法内部类的对象
            a.print();//调用内部类的方法
        }
    }
    //测试方法内部类
    public static void main(String[] args) {
        People b = new People(); //创建外部类的对象
        System.out.println("定义在方法内:===========");
        b.peopleInfo();  //调用外部类的方法
        System.out.println("定义在作用域内:===========");
        b.peopleInfo2(true);
    }
}
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

局部内部类也像别的类一样进行编译,但只是作用域不同而已,只在该方法或条件的作用域内才能使用,退出这些作用域后无法引用的。

# 匿名内部类(应用率高---------------大大简化代码的操作)

  1. 若想创建一个类(派生类)的对象,并且对象只被创建一次,

    此时该类不必命名,称为匿名内部类

  2. 匿名内部类中不能修改外面变量的值,因为在匿名内部类中默认变量为final的

匿名内部类,顾名思义,就是没有名字的内部类。正因为没有名字,所以**匿名内部类只能使用一次,它通常用来简化代码编写。**但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口。

例如:

// Outer.java
public class Outer {

    public Inner getInner(final String name, String city) {
        //1,创建了一个Inner的一个派生类,但是没有名字
        //2,为该派生类创建了一个对象,每次创建的都不是同一个类的对象
        //3,大括号内为派生类的类体
        return new Inner() {//匿名内部类
            private String nameStr = name;
            public String getName() {//方法重写
                return nameStr;
            }
        };
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        Inner inner = outer.getInner("Inner", "NewYork");
        System.out.println(inner.getName());
    }
}
interface Inner {
    String getName();
}

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

运行结果:Inner。

匿名内部类是不能加访问修饰符的。要注意的是,new 匿名类,这个类是要先定义的, 如果不先定义,编译时会报错该类找不到。

同时,在上面的例子中,当所在的方法的形参需要在内部类里面使用时,该形参必须为 final。这里可以看到形参 name 已经定义为 final 了,而形参 city 没有被使用则不用定义为 final

然而,因为匿名内部类没名字,是用默认的构造方法的,无参数的,如果需要该类有带参数的构造方法,示例如下:

public Inner getInner(final String name, String city) {
  return new Inner(name, city) {
    private String nameStr = name;

    public String getName() {
      return nameStr;
    }
  };
}

1
2
3
4
5
6
7
8
9
10

注意这里的形参 city,由于它没有被匿名内部类直接使用,而是被抽象类 Inner 的构造方法所使用,所以不必定义为 final