前言

本节内容参考

面向对象(OOP)概述

面向过程和面向对象是两种思想

面向过程

面向过程重点强调过程,举个例子:倒杯水。

  1. 放下杯子
  2. 拿起暖瓶
  3. 将水导入杯子中

面向过程强调的整个事件的过程,事无巨细

面向对象

强调的是这个整体,而不是其中的细节,比如倒杯水,强调的是能倒杯水的这个人。

面向对象注重于谁能实现,而不在乎怎么实现

面向对象和面向过程的适用场景

不能说面向对象比面向过程要好,这其实是两种不同的思想,针对于不同的环境。

打个比方,在公司:

  • 老板的思想是:找个能干找个活的人。
  • 程序员的思想是:怎么把这个活干好。

两种思想具体在代码中,适用场景是不同的。 比如要考虑这个项目的某个功能的整体怎么实现,就要用到面向对象。 如果要考虑这个项目的某个功能的具体怎么实现,就要深入细节,要用到面向过程。


类与对象

类与对象的关系

简单来说就是模板和实体的关系。

  • 一个手机图纸,这叫类。
  • 根据这个图纸造出来的手机叫做对象。

如何创建对象和初始化对象

使用关键字new创建对应的对象

  1. package com.howling;
  2. public class OOP {
  3. public static void main(String[] args) {
  4. Studnet studnet = new Studnet();
  5. }
  6. }
  7. class Studnet {
  8. private String name;
  9. private Integer age;
  10. }

在这,Student是类,而student为对象

扩展:创建对象的方式一共有5种: 1、new出来 2、clone()方法浅拷贝 3、反射 4、反序列化 5、工厂模式


构造器

什么是构造器

构造器也叫做构造方法,是从模板-->实体的必须条件。

有了构造方法之后,才可以创建对象。

构造器的基本实现

默认会给我们创建一个无参数的构造器

  1. class Studnet {
  2. private String name;
  3. private Integer age;
  4. }
  5. class Studnet {
  6. public Studnet() {
  7. }
  8. private String name;
  9. private Integer age;
  10. }

构造器默认创建一个无参数的,也就是说,上面这两个在意义上是相同的

注意一点,只要我们写了构造方法,那么系统不会再给我们提供默认的构造方法

我们可以看出构造器的基本特点:

  1. 和类名字相同
  2. 无返回值
  3. 可重载

我们马上说到重载

构造器的重载

  1. class Studnet {
  2. public Studnet() {
  3. }
  4. public Studnet(String name) {
  5. this.name = name;
  6. }
  7. public Studnet(String name, Integer age) {
  8. this.name = name;
  9. this.age = age;
  10. }
  11. private String name;
  12. private Integer age;
  13. }

以上是三个构造器的重载,这就意味着,我们可以通过三种构造来创建对象

  1. package com.howling;
  2. public class OOP {
  3. public static void main(String[] args) {
  4. Studnet studnet = new Studnet();
  5. Studnet twoStudnet = new Studnet("howling");
  6. Studnet threeStudent = new Studnet("howling", 21);
  7. }
  8. }
  9. class Studnet {
  10. public Studnet() {
  11. }
  12. public Studnet(String name) {
  13. this.name = name;
  14. }
  15. public Studnet(String name, Integer age) {
  16. this.name = name;
  17. this.age = age;
  18. }
  19. private String name;
  20. private Integer age;
  21. }

类中的方法

我们可以使用对象去调用类中的方法。

  1. package com.howling;
  2. public class OOP {
  3. public static void main(String[] args) {
  4. Studnet threeStudent = new Studnet("howling", 21);
  5. threeStudent.fun();//howling
  6. new Studnet("bean",21).fun();//bean
  7. }
  8. }
  9. class Studnet {
  10. public Studnet(String name, Integer age) {
  11. this.name = name;
  12. this.age = age;
  13. }
  14. private String name;
  15. private Integer age;
  16. public void fun(){
  17. System.out.println(name);
  18. }
  19. }

可以看到,每个对象调用的方法结果都是不同的

内存分析

面向对象 - 图1

  • Stack

    1. 每个线程私有,不能实现线程之间的共享
    2. 局部变量放置在栈中
    3. 栈是由系统自动分配,速度快
    4. 在一个连续的空间中
  • Heap

    1. new出来的对象都在堆中
    2. 是一个不连续的内存空间,分配灵活,速度慢
    • 方法区
      1. 被所有的线程共享
      2. 存放程序中永远是一成不变的或者是惟一的内容:(类代码信息,静态变量,字符串常量)

面向对象的特性

抽象

面向对象现在说的是三大特性,其实在早些年还说了一种特性叫做抽象,现在貌似不说了,但是这里还是要提一嘴。

抽象的概念

抽象的意思在汉语里是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征的过程

比如,你要对猫这一物种进行抽象,那么你其实可以抽出它的姓名,性别,颜色,等等。

抽象也是具有层级性的抽象

比如,你要在猫里面进行分类,可以分出家养猫和野生猫。

其中家养猫可以抽象出来主人的住址和居住地。而野生猫只能抽象出来居住地。

从这个观点上来看,不同层级的分类中,抽象出来的东西是不同的。

越往上走,生命甚至抽象不出性别。但是归根到了细节上面,你甚至可以抽象出来这只猫叫什么名字。

所以抽象的层级关系也可以大致理解为不断细分的过程。

抽象的意义

抽象只有在研究特定的问题的时候才会有意义。

日常生活中,人们不会在乎猫是不是有脊椎动物,因为这个没有意义。

但是在动物的研究中心这就有意义了

这样抽象确实是可以,但是没有什么意义。

但是如果你在研究各种动物的时候,猫属于有脊椎动物,这个时候就有意义了。

所以在研究不同问题的时候,我们也要有不同的考虑方向


封装

封装的概念

在Java中,把数据隐藏起来,把操作数据的方式暴露给外面,这就叫做封装。

比如一般人会看电视,但是不会去了解电视机到底是怎么工作的。

只需要一个遥控器就可以实现切换频道,调整声音等功能。但是不需要去关心到底是怎么做到的。

封装的步骤

  1. private关键字
  2. 操作数据的方法
  1. class Studnet {
  2. private String name;
  3. private Integer age;
  4. public Studnet() {
  5. }
  6. public Studnet(String name, Integer age) {
  7. this.name = name;
  8. this.age = age;
  9. }
  10. public String getName() {
  11. return name;
  12. }
  13. public void setName(String name) {
  14. this.name = name;
  15. }
  16. public Integer getAge() {
  17. return age;
  18. }
  19. public void setAge(Integer age) {
  20. this.age = age;
  21. }
  22. }

上面是一个标准的类,我们叫做JavaBean,带有无参构造和全参构造,还有操作数据的方法。 一般在类中,我们使用gettersetter对数据进行操作

封装的意义

这样写好处十分明显:

  1. 隐藏了代码的细节,提升了数据的安全性
  2. 方便调用
  3. 形成了操作数据的规范,提高了系统的可维护性

方法重载

之前我们讲过类的构造器重载,现在我们来讲一下方法的重载。

其实构造器的重载也属于方法的重载,只不过构造器对于方法来说比较特殊。

方法的重载其实就是相同名字的方法不同的调用。

方法的重载和返回值,访问修饰符等无关,只和参数列表有关系

  1. private void overLoad(){}
  2. public int overLoad(int i){ return i; }
  3. int overLoad(int i,int j){ return j; }

上面是方法的重载,可以看到和访问修饰符,返回值无关,只和参数列表有关系

参数列表具体又可以分为三个条件:

  1. 参数数量
  2. 参数类型
  3. 参数顺序

继承

继承的概念

就像孩子继承父亲一样,面向对象中也存在着继承的关系。

继承的实现步骤

  1. 首先要有一个父类(基类/超类),用于被继承
  2. 子类使用extends关键字声明为父类的子类s
  1. package com.howling;
  2. public class OOP {
  3. public static void main(String[] args) {
  4. Child child = new Child();
  5. Integer property = child.property;
  6. child.company();
  7. }
  8. }
  9. class Father{
  10. private String name;
  11. private String sex;
  12. Integer property;
  13. void company(){
  14. System.out.println("投资");
  15. System.out.println("收入");
  16. System.out.println("支出");
  17. }
  18. }
  19. class Child extends Father{
  20. }

继承的特性

我们就以孩子和父亲举例子。

孩子继承父亲,那么就说明儿子具备着父亲基因上的的很多特性。 Java子类继承父类(基类/超类),那么子类就具备着父类上的很多特性。 父亲自身也有一些特性是不可以被继承的,属于父亲自身私有的,比如姓名。 父类自身也有一些特性,是私有的private,所以不可以被子类继承。 一个孩子只能有一个父亲。 一个子类只能有一个父类。 孩子的父亲还有父亲,说明是一种传承关系。 子类的父类还有父类,说明是一种传承关系,这是多级继承。

到此为止,我们的特性就出来了

  1. 子类继承着父类大部分的属性
  2. 私有属性不能被继承
  3. 继承关系是单继承关系
  4. 继承可以多级继承

现在再转过头来,看上面的代码,就会发现这些特性

Obejct

要特别提一嘴Object类,因为Java中的所有类都是直接或者间接地继承Object

如果对应到地球,那么Obejct就是第一个出现的生命,地球上的所有生命都继承自这个生命。

方法重写

假如父类有一个方法叫做A,子类中也写了一个方法叫A,这个时候冲突就出现了。

  1. package com.howling;
  2. public class OOP {
  3. public static void main(String[] args) {
  4. new Father().func();//父类
  5. new Child().func();//子类
  6. }
  7. }
  8. class Father{
  9. void func(){
  10. System.out.println("父类");
  11. }
  12. }
  13. class Child extends Father{
  14. @Override
  15. void func(){
  16. System.out.println("子类");
  17. }
  18. }

我们可以看到,方法的重写相当于覆盖的效果,同时可以使用注解@Override来检测是否进行了正确的重写

重写的注意事项

  1. 重写只存在于父类和子类之间
  2. 私有方法不能被子类读取到,所以不能重写
  3. 只要涉及到静态方法,就不能重写
    1. 静态方法不可以被重写为非静态方法
    2. 非静态方法不可以被重写为静态方法
    3. 子类可以定义一个和父类静态方法相同名字,相同参数的静态方法,但这其实不是重写,是创建一个新的
  4. 重写的语法
    1. 方法名,参数列表必须相同
    2. 返回值必须为父类的返回值或父类返回值的子类
    3. 访问修饰符可以更加公开但是不能私有
    4. 抛出异常的范围可以被缩小,但是不能扩大

以上的条件都可以被@Override检测到,会直接爆红

为什么要进行方法的重写

子类继承了父类的方法,但是不一定适合子类

父类中的代码很适合父类,但是不一定适合子类。

比如toString()方法,它对于引用类型的原本的意思是返回地址值,但是现在我们一般都返回它的值


多态

多态概述

按照字面意思来讲,就是多种状态。在面向对象过程中确实也是这样的。

同样的一个事物多种有不同的状态,而具体要执行什么状态由实际情况来决定。

在Java中,在一定的程度上可以理解为:封装和继承是基础,而多态才是面向对象优势爆发的核心。

在Java中,具体的表现形式就是父类的引用指向子类的对象

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Object object = new Object();
        Object father = new Father();//Object的引用指向Father
        Father child = new Child();//Father的引用指向Child


        System.out.println(object.toString());//java.lang.Object@1b6d3586
        System.out.println(father.toString());//父类的重写
        System.out.println(child.toString());//子类的重写
    }
}


class Father{
    @Override
    public String toString() {
        return "父类的重写";
    }
}

class Child extends Father{
    @Override
    public String toString() {
        return "子类的重写";
    }
}

在这里的层级关系明显是:Object-->Father-->Child 但是执行方法的关系树是:Child-->Father-->Object 子类如果重写了,那么优先选择子类;没有去找父类,还没有找父类的父类……一直到Object 说明多态在方法执行上是倒序执行的,和JVM的双亲委派机制表现形式是相同的

重写,重载,多态

在谈论三者的关系时,首先明确一个概念:虚拟方法调用

在子类中对父类的方法进行了重载,这时,父类就叫做虚拟方法。

我们在调用的时候,父类会根据不同的子类对象进行不同的方法调用,这就是一个由虚到实的过程。

多态的调用就是这样的一个过程。所以多态的显著特点之一就是在编译期无法确定,在调用的时候才可以确定

重写和多态的关系:重写是多态的前提之一

重载和多态的关系:多态在编译期无法确定,调用的时候才可以确定,但是重载在编译期就确定了,所以二者没什么关系

现在还有一种说法,叫做重载叫做编译时多态,而重写的叫做运行时多态。但是这种说法个人感觉站不住脚

重载和重写的关系:唯一相像的地方就是:都是改变方法。除此之外没什么关系。

多态的注意事项

  1. 属性没有多态,只有方法有多态
  2. 多态的三条件

    1. 有继承
    2. 方法可以被子类重写,并且可以被外部引用

      1. staticfinalprivate修饰的方法不可以被重写,所以不符合定义,不是多态
      2. protected可以被重写,但是不可以被外部引用,所以不符合定义,不是多态
    3. 父类的引用指向子类的对象

面向对象中的关键子类字

this

this关键字用于指向调用者本身。比如在类中,this就代表着这个类本身,或者是这个类的对象。

我们一般可以使用this区分局部变量和成员变量

package com.howling;

public class OOP {
    public static void main(String[] args) {
        //局部变量:猫,成员变量:狗
        new Animal("狗").call("猫");
    }
}

class Animal{
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void call(String name){
        System.out.println("局部变量:"+name+",成员变量:"+this.name);
    }
}

还可以使用this表示要创建的对象

package com.howling;

public class OOP {
    public static void main(String[] args) {
        //com.howling.Animal@1b6d3586
        new Animal();
        //com.howling.Animal@4554617c
        new Animal();
    }
}

class Animal{
    public Animal() {
        System.out.println(this);
    }
}

可以看到地址值并不是一个,这可以证明表示的并不是类,而是对象


super

super关键字在子类中使用,用于指向调用者父类对象的引用。利用super关键字可以访问父类的非私有成员变量和方法

package com.howling;

public class OOP {
    public static void main(String[] args) {
        //父类的创建 
        // 0
        // super:com.howling.Dog@1b6d3586
        new Dog();

        //父类的创建 
        // 0
        // super:com.howling.Dog@4554617c
        new Dog();
    }
}

class Animal{

    public Animal() {
        System.out.println("父类的创建");
    }

    private String name;

    public int age;

    void call(){
        System.out.println("super:"+this);
    }
}



class Dog extends Animal{
    public Dog() {
        System.out.println(super.age);
        super.call();
    }
}

可以很清晰的看到地址值并不是一个,因为super指向父类的时候,会首先创建一个父类的对象,然后根据这个对象指向对应的方法

我们知道有对应的构造器才可以创建对象,那么我们故意替换掉原有的构造器,但是不给构造器对应的值,看看是个什么情况

package com.howling;

public class OOP {
    public static void main(String[] args) {

        // 2
        // super:com.howling.Dog@1b6d3586
        new Dog();

        // 2
        // super:com.howling.Dog@4554617c
        new Dog();
    }
}

class Animal{

    private String name;
    public int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void call(){
        System.out.println("super:"+this);
    }
}



class Dog extends Animal{
    public Dog() {
        //不加这一行会出错,而且这一行必须在第一行
        super("1",2);
        System.out.println(super.age);
        super.call();
    }
}

可以看到结果,不使用构造器的话编译器会直接报错 而且super(x,x)必须加在第一行,这意味着我们之前说过的:首先创建父类,然后创建子类的说法是正确的


instanceof和类型转换

简单来说,instanceof 判断这个对象是不是这个类型的

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Dog dog = new Dog();

        Animal animal = new Animal();

        System.out.println("" instanceof String);//true

        System.out.println(dog instanceof Dog);//true

        System.out.println(dog instanceof Animal);//true

        System.out.println(animal instanceof Dog);//false

    }
}

class Animal{ }

class Dog extends Animal{ }

类型转换

类型转换就只有一句话:范围小的可以自动转换为范围大的,而范围大的要强制转换为范围小的

举一个现实生活中的例子:特仑苏可以叫做牛奶,但不是所有牛奶都叫特仑苏

在Java中,具体的表现为:父类的引用可以指向子类的对象,但是子类的引用不能指向父类的对象

short i = 1;
int j = i + 1;
short k = (short)(i + 1);

static

static可以用于变量,方法,代码块

static变量

static变量称为静态变量,静态变量和非静态变量有区别:

  1. 静态变量属于类,可以直接使用类名访问
  2. 非静态变量属于对象,必须创建出对象来访问

static方法称为静态方法,静态方法和成员方法有区别:

  1. 静态方法属于类,可以直接使用类名来访问
  2. 成员方法属于对象,必须使用对象来访问

static代码块称为静态代码块,其他代码块称为匿名代码块,静态代码块和匿名代码块有区别:

1、静态代码块在类加载完成的时候加载,并且只加载一次

2、匿名代码块在创建对象的时候加载,并且每次创建都加载

package com.howling;

public class OOP {
    public static void main(String[] args) {

        //静态代码块加载
        //匿名代码块加载
        new Animal();

        //匿名代码块加载
        new Animal();

        //匿名代码块加载
        new Animal();

        //匿名代码块加载
        new Animal();

    }
}

class Animal{
    static {
        System.out.println("静态代码块加载");
    }

    {
        System.out.println("匿名代码块加载");
    }
}

执行顺序中Java中类及方法的加载顺序的位置

  1. 父类静态变量
  2. 父类静态代码块
  3. 子类静态变量
  4. 子类静态代码块
  5. 父类成员变量
  6. 父类匿名代码块
  7. 父类构造器
  8. 子类成员变量
  9. 子类匿名代码块
  10. 子类构造器

加载顺序时分为两部分加载:首先加载静态部分,然后加载非静态部分 第一部分:静态部分:静态部分首先是父类的静态变量,然后是父类的静态代码块。然后是子类的静态变量,然后是子类的静态代码块 第二部分:非静态部分:非静态部分首先是父类的成员变量,然后是父类的匿名代码块,然后是父类的构造方法。然后是子类的成员变量,子类的静态代码块,子类的构造方法

package com.howling;

public class OOP {
    public static void main(String[] args) {
        new Dog();
        //父类静态变量构造:0
        //父类静态代码块加载
        //子类静态变量构造:0
        //子类静态代码块加载
        //父类成员变量构造:0
        //父类匿名代码块加载
        //父类构造器加载
        //子类成员变量构造:0
        //子类匿名代码块加载
        //子类构造器加载
    }
}
class Animal{

    static int fatherStaticInt;

    private int fatherInt;

    static {
        System.out.println("父类静态变量构造:"+fatherStaticInt);
        System.out.println("父类静态代码块加载");
    }

    {
        System.out.println("父类成员变量构造:"+fatherInt);
        System.out.println("父类匿名代码块加载");
    }

    public Animal() {
        System.out.println("父类构造器加载");
    }
}
class Dog extends Animal{

    static int sonStaticInt;

    private int sonInt;

    static {
        System.out.println("子类静态变量构造:"+sonStaticInt);
        System.out.println("子类静态代码块加载");
    }

    {
        System.out.println("子类成员变量构造:"+sonInt);
        System.out.println("子类匿名代码块加载");
    }

    public Dog() {
        System.out.println("子类构造器加载");
    }
}

所以出现了这么一个现象: 1、静态方法中只能调用静态变量,不能调用其他变量和方法,因为还没有加载 2、静态方法中可以调用静态变量,是因为静态变量在一开始的


final

final作为最终的意思,所以:

  • 对类而言:final类不可以被继承(比如String)
  • 对方法而言:final方法不可以被重写
  • 对变量而言:final修饰的变量叫做常量,一旦确定了数据就不能被更改。(当然你也可以先定义,不赋予数据)

abstract

abstract,抽象。

抽象的意思就是可以没有实体,这个东西先占个位置,等我想好了再将它实现出来。

所以根据这一点来看abstract可以修饰在方法上和类上,不能修饰在变量上

因为加载类上,类可以被继承;加载方法上,方法可以被重写。而变量什么也没有。

package com.howling;

public class OOP {
    public static void main(String[] args) {}
}

abstract class Animal{
    public abstract void call();

    abstract void call(String name);

    public void call(String age,String name){
        System.out.println(name+":"+age);
    }
}

class Dog extends Animal{

    @Override
    public void call() {}

    @Override
    void call(String name) {}
}

abstract class Cat extends Animal{}

1、抽象方法没有代码块,但是抽象方法必须在抽象类中 2、抽象类中可以没有抽象方法,也可以有; 3、如果子类不是抽象类,必须重写抽象父类的抽象方法;如果子类是抽象类,那么可以不重写抽象父类的抽象方法

package com.howling;

public class OOP {
    public static void main(String[] args) {


        Animal animal = new Animal() {
            @Override
            public void call() {

            }

            @Override
            void call(String name) {

            }
        };

        Dog dog = new Dog();
    }
}

abstract class Animal{
    public abstract void call();

    abstract void call(String name);

    public void call(String age,String name){
        System.out.println(name+":"+age);
    }
}

class Dog extends Animal{

    @Override
    public void call() {}

    @Override
    void call(String name) {}
}

abstract class Cat extends Animal{

}

抽象方法必须要实现,不管是怎么实现的

接口

接口和抽象类大有不同

我们在上面讲过抽象类,抽象类在级别上其实也是类的一种衍生。但是接口在级别上是和类一个级别的。

抽象类也是类,所以抽象类中可以不写方法体,也可以写方法体。接口中的方法在JDK8之前不可以有方法体,只能其他类实现,但是在JDK8和之后接口中可以有方法体

抽象类是类,所以抽象类是可以被继承的,而且一个类只能单继承一个类,这些类中当然也包含抽象类。接口不是类,他的专有名词叫做实现,而且可以让一个类实现多个接口

抽象类也是类,只是在普通类的基础上加了一个abstract。接口不是类,所以他的专有声明叫做interface。

抽象类的变量和方法默认使用default来修饰。接口的变量默认使用public static final来修饰,接口的方法默认使用public abstract来修饰

接口和抽象类有些相同

1、可以在方法中不写方法体,并让其他类能够继承/实现了。

2、只有抽象类才能够 继承/实现 抽象类/接口,但是不重写/实现 抽象方法

总结

抽象类 接口
变量默认使用default修饰 变量默认使用public static final修饰
方法默认使用default修饰 方法默认使用 public abstract
可以使用private关键字 关键字最低也得是protected
在类中可以定义抽象方法,也可以定义普通方法 在JDK8之后可以定义非抽象方法
是类的一种,使用abstract来声明 不属于类的下属部分,使用interface来声明
其他类要继承抽象类,当然使用extends来继承 其他类要实现抽象类,使用implements来实现
一个普通类只能继承一个类,这其中当然也包含抽象类 一个类可以实现多个接口
除了抽象类,其他的任何一中类继承抽象之后,必须要重写抽象方法 除了抽象类,其他的任何一种类实现接口之后,必须要重写抽象方法
枚举类不能继承抽象类,和其他的任何一种类 枚举类可以实现接口

友情扩展: 1、枚举类之所以不能够继承其他的类,是因为enum已经继承了Enum类 2、Enum类实现了Serializable接口,也就是说每一个枚举类都自带序列化

内部类

成员内部类

概念

成员内部类,顾名思义,一个类中的类

定义

class Out{
    class Inner{
    }
}

内部类的实例化

首先要实例化外部类,然后通过外部类的对象实例化内部类

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Out out = new Out();

        Out.Inner inner = out.new Inner();
    }
}

class Out{
    class Inner{
    }
}

内部类和外部类之间的互相访问

内部类访问外部类

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Out out = new Out();

        Out.Inner inner = out.new Inner();

        inner.outId();
    }
}

class Out{

    private int id;

    private void call(){
        System.out.println("外部类方法调用");
    }


    class Inner{
        public void outId(){
            //直接访问外部类的变量,包括私有变量
            System.out.println(id);
            //直接访问外部类的方法,包括私有方法
            call();
        }
    }
}

外部类访问内部类

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Out out = new Out();

        out.inCall();
    }
}

class Out{

    public void inCall(){
        //先创建对象
        Inner inner = new Inner();


        //可以根据对象获取对应的变量和方法,包括私有的
        inner.call();
        System.out.println(inner.id);
    }


    class Inner{
        private int id;

        private void call(){
            System.out.println("内部类方法调用");
        }
    }
}

外部类和内部类的冲突解决

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Out out = new Out();
        Out.Inner inner = out.new Inner();
        inner.call();
    }
}

class Out{

    private int id;

    public void call(){
        System.out.println("内部类方法调用");
    }

    class Inner{
        private int id = 7;

        void call(){
            //外部类访问内部类就,先创建对象,没什么冲突
            //内部类访问外部类的冲突

            //直接调用优先自己
            System.out.println(id);

            //调用外部
            System.out.println(Out.this.id);

            Out.this.call();

        }
    }
}

成员内部类中不能写静态属性和静态方法

接下来的是我的个人想法 我们之前讲过优先级的问题,我们当时说的是首先加载静态内容的部分,然后加载其他的非静态的部分。 假如成员内部类中定义了一个变量或者是静态方法,那么按照规则来说它肯定是首先要进行加载的。 但是现在有一个问题:按照级别来说,成员内部类是和成员一个级别的,按理说他不应该首先加载。 但是静态的需要先加载 那么这样加载又不加载,就产生了逻辑错误

静态内部类

概念

可以认为静态内部类在层级上是和成员变量一个级别的,所以可以修饰类,但是一般情况下我们不可以修饰类

实现

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Out.Inner inner = new Out.Inner();
    }
}

class Out{

    private int id;

    public void call(){
        System.out.println(Inner.id);
        Inner.call();
    }

    static class Inner{
        static int id = 7;

        String xx = "xxx";

        static void call(){
            System.out.println("静态内部方法");
        }
    }
}

特性

1、外部类可以直接访问静态内部类内部静态属性 2、外部类不可以直接访问静态内部类非静态属性,仍然要创建对象,因为成员变量和成员方法属于对象 3、静态内部类不可以访问外部类的非静态属性 4、静态内部类可以直接调用外部类实例化,不需要外部类的对象,比如:

public class Person {    
 public static class Student{
     private String name;

     public Student() {
     }

     public Student(String name) {
         this.name = name;
     }
 }
}
public class Test {    
 public static void main(String[] args){
     Person.Student student = new Person.Student();
 }
}

局部内部类

概念

局部内部类,顾名思义,在方法中定义的。

实现

class Out{

    public void call(){
        class Inner{
        }
    }
}

特性

和成员内部类差不多,记住一条:如果局部内部类访问局部变量,那么这个局部变量必须是final修饰的

因为当方法出栈时,类也许不会出栈,那么假如这个类使用着局部变量,那么就尴尬了。 但是使用final修饰时,方法出栈变量不会出栈,还在常量池中待着。 但是注意,在JDK8之后,已经不需要你手动增加final了,JVM会在底层自动增加 但是我们的原理不能忘,不然你会以为这样就是正常的。

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Out out = new Out();

        out.call();
    }
}

class Out{

    public void call(){

        int id = 0;

        class Inner{

            void innerMethod(){
                System.out.println(id);
            }
        }

        Inner inner = new Inner();
        inner.innerMethod();
    }
}

匿名内部类

概念

一个对象只用一次

实现

package com.howling;

public class OOP {
    public static void main(String[] args) {

        Out out = new Out();

        out.call();
    }
}

class Out{

    public void call(){
        new Inner() {
            @Override
            public void method() {
                System.out.println("匿名内部类");
            }
        }.method();
    }

}

interface Inner{
    public void method();
}

安卓中貌似经常使用这种方法