面向对象
对象的内存复杂对象的内存对象数组的内存思考:方法存储在哪里?Java程序的内存划分this、super注解(Annotation)访问控制(Access Control)toString方法(类名 + @ + 哈希值的16进制)static、静态导入成员变量的初始化初始化块、静态初始化块单例模式(Singleton Pattern)final、常量(Constant)嵌套类(Nested Class)内部类(Inner Class)内部类内存分布静态嵌套类(Static Nested Class)什么情况使用嵌套类?局部类(Local Class)抽象类(Abstract Class)与接口(Interface)抽象类接口(Interface)抽象与接口的对比(如何选择)接口的升级问题(默认方法、静态方法)默认方法(Default Method)静态方法(Static Method)多态(Polymorphism)类方法调用的细节成员变量访问的细节instanceof对象数组的注意点Java笔记目录可以点这里:Java 强化笔记
毕竟是进阶学习,挑了一些个人认为的易错点,难点,还有些太基础的我觉得是在没有必要记录了。。。
对象的内存
Java 中所有对象都是new
出来的,所有对象的内存都是在堆空间,所有保存对象的变量都是引用类型。
public static void main(String[] args) {Dog dog = new Dog();dog.age = 20;dog.weight = 5.6;dog.run();dog.eat("appel");}
Java 运行时环境有个垃圾回收器(garbage collector,简称GC),会自动回收不再使用的内存
当一个对象没有任何引用指向时,会被 GC 回收掉内存
复杂对象的内存
public class Dog {public int price;}
public class Person {public int age;public Dog dog;}
public static void main(String[] args) {Dog dog = new Dog();dog.price = 100;Person person = new Person();person.age = 20;person.dog = dog;}
对象数组的内存
public static void main(String[] args) {Dog[] dogs = new Dog[7];for (int i = 0; i < dogs.length; i++) {dogs[i] = new Dog();}dogs[6] = null;}
思考:方法存储在哪里?
public class Dog {public int price;public void run() {System.out.println(price + "_run");}public void eat() {System.out.println(price + "_eat");}public static void main(String [] args) {Dog dog1 = new Dog();dog1.price = 100;dog1.run();dog1.eat();Dog dog2 = new Dog();dog2.price = 200;dog2.run();dog2.eat();}}
Java程序的内存划分
Java 虚拟机在执行 Java 程序时会将内存划分为若干个不同的数据区域,主要有:
PC 寄存器(Program Counter Register):存储 Java 虚拟机正在执行的字节码指令的地址Java 虚拟机栈(Java Virtual Machine Stack):存储栈帧堆(Heap):存储 GC 所管理的各种对象方法区(Method Area):存储每一个类的结构信息
(比如字段和方法信息、构造方法和普通方法的字节码等)本地方法栈(Native Method Stack):用来支持 native 方法的调用
(比如用 C 语言编写的方法)
this、super
this
是一个指向当前对象的引用,常见用途是:
访问当前类中定义的成员变量调用当前类中定义的方法(包括构造方法)
只能在构造方法中使用this
调用其他构造方法;
如果在构造方法中调用了其他构造方法:
构造方法调用语句必须是构造方法中的第一条语句
this
的本质是一个隐藏的、位置最靠前的方法参数;
public class Dog {public String name;public int age;public int price;public Dog(String name, int age, int price) {this.name = name;this.age = age;this.price = price;}// 只能在构造方法中使用 this 调用其他构造方法public Dog(String name) {// 如果在构造方法中调用了其他构造方法// 构造方法调用语句必须是构造方法中的第一条语句// int a = 1; //errorthis(name, 0, 0);}}
super
的常见用途是:
访问父类中定义的成员变量调用父类中定义的方法(包括构造方法)
子类的构造方法必须先调用父类的构造方法,再执行后面的代码;
如果子类的构造方法没有显式调用父类的构造方法:
编译器会自动调用父类无参的构造方法(若此时父类没有无参的构造方法,编译器将报错)
public class Person {public int age;public Person(int age) {this.age = age;}}
public class Student extends Person {public int no;public Student(int no) {super(0); // 不写会报错this.no = no;}}
注解(Annotation)
3 个常见的注解:
@Override
::告诉编译器这是一个重写后的方法@SuppressWarnings({"rawtypes", "ununsed"})
:让编译器不生成某个类别警告信息@Deprecated
::表示这个内容已经过期,不推荐使用
访问控制(Access Control)
Java 中有 4 个级别的访问权限,从高到低如下所示:
public
:在任何地方都是可见的protected
:仅在自己的包中、自己的子类中可见无修饰符(package-private):仅在自己的包中可见private
:仅在自己的类中可见
使用注意:
上述 4 个访问权限都可以修饰类的成员,比如成员变量、方法、嵌套类(Nested Class)等只有public
、无修饰符(package-private)可以修饰顶级类(Top-level Class)上述 4 个访问权限不可以修饰局部类(Local Class)、局部变量一个 Java 源文件中可以定义多个顶级类,public 顶级类的名字必须和文件名一样
toString方法(类名 + @ + 哈希值的16进制)
当打印一个对象时,会自动调用对象的toString
方法,并将返回的字符串打印出来
toString
方法来源于基类java.lang.Object
,默认实现如下所示:
默认打印:类名 + @ + 哈希值的16进制
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}
static、静态导入
static
常用来修饰类的成员:成员变量、方法、嵌套类
成员变量:(类变量,实例变量)
被static
修饰:类变量,静态变量,静态字段
在程序运行过程中只占用一份固定的内存(存储在方法区)
可以通过实例、类访问
没有被static
修饰:实例变量
在每个实例内部都有一份内存
只能通过实例访问,不可以通过类访问
不推荐使用实例访问类变量、类方法
方法:(类方法、实例方法)
被static
修饰:类方法、静态方法
可以通过实例、类访问
内部不可以使用 this
可以直接访问类变量、类方法
不可以直接访问实例变量、实例方法没有被static
修饰:实例方法
只能通过实例访问,不可以通过类访问
内部可以使用 this
可以直接访问实例变量、实例方法
可以直接访问类变量、类方法
在同一个类中:不能有同名的实例变量和类变量,不能有相同签名的实例方法和类方法。
static 有个用法:静态导入
使用了静态导入后,就可以省略类名来访问静态成员(成员变量、方法、嵌套类)
静态导入的经典使用场景:
import static java.lang.Math.PI;public class Main {public static void main(String[] args) {System.out.println(2 * PI * 10);System.out.println(2 * PI * 20);}}
正确使用静态导入,可以消除一些重复的类名,提高代码可读性;
过度使用静态导入,会让读者分不清静态成员是在哪个类中定义的;
建议:谨慎使用静态导入。
成员变量的初始化
编译器会自动为未初始化的成员变量设置初始值;
如何手动给实例变量提供初始值?
在声明中在构造方法中在初始化块中
编译器会将初始化块复制到每个构造方法的头部(每创建一个实例对象,就会执行一次初始化块)
如何手动给类变量提供初始值?
在声明中在静态初始化块中
当一个类被初始化的时候执行静态初始化块;
当一个类第一次被主动使用时,JVM 会对类进行初始化;
初始化块、静态初始化块
public class Person {static {// 静态初始化块System.out.println("static block");}{// 初始化块System.out.println("block");}public Person() {}public Person(int age) {}public static void main(String[] args) {new Person();// static block// blocknew Person(20);// block}}
一个更复杂的示例:
public class Person {static {System.out.println("Person static block");}{System.out.println("Person block");}public Person() {System.out.println("Person constructor");}
public class Student extends Person {static {System.out.println("Student static block");}{System.out.println("Student block");}public Student() {System.out.println("Student constructor");}}
执行顺序:父类静态块 -> 子类静态块 -> 父类代码块 -> 父类构造器 -> 子类代码块 -> 子类构造器
public static void main(String[] args) {new Student();// Person static block// Student static block// Person block// Person constructor// Student block// Student constructor}
单例模式(Singleton Pattern)
如果一个类设计成单例模式,那么在程序运行过程中,这个类只能创建一个实例。
饿汉式单例模式:像饿汉一样,上来就直接创建了唯一的那个实例。(线程安全)
/** 饿汉式单例模式*/public class Rocket {private static Rocket instance = new Rocket();private Rocket(){}public static Rocket getInstance() {return instance;}}
懒汉式单例模式:像懒汉一样,只有用到的时候采取创建实例。(线程不安全)
/** 懒汉式单例模式*/public class Rocket {private static Rocket instance = null;private Rocket(){}public static Rocket getInstance() {if (instance == null) {instance = new Rocket();} return instance;}}
final、常量(Constant)
被final
修饰的类:不能被子类化,不能被继承
被final
修饰的方法:不能被重写
被final
修饰的变量:只能进行1次赋值
常量的写法:
public static final double PI = 3.14159265358979323846;
private static final int NOT_FOUND = - 1;
如果将基本类型或字符串定义为常量,并且在编译时就能确定值:
编译器会使用常量值替代各处的常量名(类似于 C 语言的宏替换)称为编译时常量( compile-time constant)
例如下面的情况,编译时会被替换:
public class Main {static final int A = 123456;static final String B = "HELLO";public static void main(String[] args) {System.out.println(A); // 编译时直接被替换为下面// System.out.println(123456);System.out.println(B); // 编译时直接被替换为下面// System.out.println("HELLO");}}
下面这种情况,编译时无法确定值,不会被替换:
public class Main {static int NUMBER = getNum();static int getNum() {int a = 10;int b = 20;return a + b * 2 + 6;}public static void main(String[] args) {System.out.println(NUMBER); // 不会被替换}}
嵌套类(Nested Class)
嵌套类:定义在另一个类中的类;在嵌套类外层的类,称为:外部类(Outer Class)最外层的外部类,称为:顶级类(Top-level Class)public class OuterClass {// 顶级类// 静态嵌套类static class StaticNestedClass {}// 非静态嵌套类(内部类)class InnerClass {}}
内部类(Inner Class)
内部类:没有被static
修饰的嵌套类,非静态嵌套类
跟实例变量、实例方法一样,内部类与外部类的实例相关联:
必须先创建外部类实例,然后再用外部类实例创建内部类实例内部类不能定义除编译时常量以外的任何 static 成员
内部类与外部类:
内部类可以直接访问外部类中的所有成员(即使是private
)外部类可以直接访问内部类实例的成员变量、方法(即使是private
)
内部类示例:先有公司company
才能有员工employee
,将employee
设置为company
的内部类,而company
可以访问employee
的实例的成员变量、方法(包括private
),employee
可以访问company
的所有成员(包括private
)。
public class Company {private String name;public Company(String name) {this.name = name;}public void fire(Employee e) {// 外部类可以直接访问[内部类实例]的成员变量(包括 private)System.out.println(name + " fire " + e.no);}public class Employee {private int no;public Employee(int no) {this.no = no;}public void show() {// 内部类可以直接访问[外部类]中的所有成员(包括 private)System.out.println(name + " : " + no);}}public static void main(String[] args) {Company c = new Company("Google");Employee e = c.new Employee(17210224);e.show();c.fire(e);}}
内部类的细节:如果有同名变量,默认访问内部的,访问外部的需要特别指出。
public class OuterClass {private int x = 1; // 外部类变量xpublic class InnerClass {private int x = 2; // 内部类变量xpublic void show() {// 默认访问内部System.out.println(x); // 2System.out.println(this.x); // 2// 访问外部类的同名变量需要这么写System.out.println(OuterClass.this.x); // 1}}public static void main(String[] args) {new OuterClass().new InnerClass().show();}}
内部类内存分布
public class Person {private int age;public class Hand {private int weight;}public static void main(String[] args) {// 必须先有Person对象才能创建Hand对象Person p1 = new Person();Hand h1 = p1.new Hand();Person p2 = new Person();Hand h2 = p2.new Hand();}}
在Hand
类被释放前,Person
类不会被释放(被Hand
指向着)
静态嵌套类(Static Nested Class)
静态嵌套类:被static
修饰的嵌套类;
静态嵌套类在行为上就是一个顶级类,只是定义的代码写在了另一个类中;
对比一般的顶级类,静态嵌套类多了一些特殊权限
可以直接访问外部类中的成员(即使被声明为private
)
public class Person {private int age;private static int count = 1;private static void run() {System.out.println("Person - run");}public static class Car {// 静态嵌套类public void test() {Person person = new Person();// 静态嵌套类可以直接访问外部类中的成员(包括 private)System.out.println(person.age); // 0Person.count = 1;Person.run(); // Person - runSystem.out.println(count); // 1run(); // Person - run}}}
public static void main(String[] args) {Person p = new Person();// 静态嵌套类的使用Person.Car c = new Person.Car(); // 如果之前 import Person.Car; 可以直接使用;// Car c = new Car();c.test();}
什么情况使用嵌套类?
如果类 A 只用在类 C 内部,可以考虑将类 A 嵌套到类 C 中;
封装性更好程序包更加简化增强可读性、维护性
如果类 A 需要经常访问类 C 的非公共成员,可以考虑将类 A嵌套到类 C 中;
另外也可以根据需要将类 A 隐藏起来,不对外暴露
如果需要经常访问非公共的实例成员,设计成内部类(非静态嵌套类),否则设计成静态嵌套类;
如果必须先有 C 实例,才能创建 A 实例,那么可以将 A 设计为 C 的内部类
局部类(Local Class)
局部类:定义在代码块中的类(可以定义在方法中、for 循环中、if 语句中等)
局部类不能定义除编译时常量以外的任何static
成员;
局部类只能访问final
或者有效final
的局部变量;
从 Java 8 开始,如果局部变量没有被第二次赋值,就认定为是有效final
局部类可以直接访问外部类中的所有成员(即使被声明为private
)
局部类只有定义在实例相关的代码块中,才能直接访问外部类中的实例成员(实例变量、实例方法)
局部类示例:
public class TestLocalClass {private int a = 1;private static int b = 2;private static void test1() {}private void test2() {}public void test3() {int c = 2;class LocalClass {static final int d = 4;void test4() {System.out.println(a + b + c + d);test1();test2();}}new LocalClass().test4();}}
抽象类(Abstract Class)与接口(Interface)
抽象类
抽象方法:被abstract
修饰的实例方法
只有方法声明,没有方法实现(参数列表后面没有大括号,而是分号)不能是private
权限(因为定义抽象方法的目的让子类去实现)只能定义在抽象类、接口中
抽象类:被abstract
修饰的类
可以定义抽象方法不能实例化,但可以自定义构造方法子类必须实现抽象父类中的所有抽象方法(除非子类也是一个抽象类)可以像非抽象类一样定义成员变量、常量、嵌套类型、初始化块、非抽象方法等
也就说,抽象类也可以完全不定义抽象方法
常见使用场景:
抽取子类的公共实现到抽象父类中,要求子类必须要单独实现的定义成抽象方法
实例:
public abstract class Shape {protected double area;protected double girth;public double getArea() {return area;}public double getGirth() {return girth;}public void show() {calculate();System.out.println(area + "_" + girth);}protected abstract void calculate();}
public class Rectangle extends Shape {private double width;private double height;public Rectangle(double width, double height) {super();this.width = width;this.height = height;}@Overrideprotected void calculate() {area = width * height;girth = (width + height) * 2;}}
public class Circle extends Shape {private double radius;public Circle(double radius) {this.radius = radius;}@Overrideprotected void calculate() {double half = Math.PI * radius;area = half * radius;girth = half * 2;}}
public static void main(String[] args) {Rectangle rectangle = new Rectangle(10, 20);rectangle.show();Circle circle = new Circle(30);circle.show();}
接口(Interface)
API(Application Programming Interface)
应用编程接口,提供给开发者调用的一组功能(无须提供源码)
Java 中的接口:
一系列方法声明的集合用来定义规范、标准
接口中可以定义的内容:
抽象方法(可以省略 abstract)常量(可以省略 static、final)嵌套类型从 Java 8 开始可以定义:默认方法(default
)、静态方法
上述可以定义的内容都是隐式 public 的,因此可以省略 public 关键字从 Java 9 开始可以定义:private 方法不能自定义构造方法、不能定义(静态)初始化块、不能实例化
接口的细节:
一个类可以通过implements
关键字实现一个或多个接口
实现接口的类必须实现接口中定义的所有抽象方法,除非它是个抽象类如果一个类实现的多个接口中有相同的抽象方法,只需要实现此方法一次extends
和 implements
可以一起使用,implements
必须写在extends
的后面当父类、接口中的方法签名一样时,那么返回值类型也必须一样
一个接口可以通过extends
关键字继承一个或者多个接口
当多个父接口中的方法签名一样时,那么返回值类型也必须一样
抽象与接口的对比(如何选择)
抽象类和接口的用途还是有点类似,该如何选择?
何时选择抽象类?
在紧密相关的类之间共享代码需要除 public 之外的访问权限需要定义实例变量、非 final 的静态变量
何时选择接口?
不相关的类实现相同的方法只是定义行为,不关心具体是谁实现了行为想实现类型的多重继承
接口的升级问题(默认方法、静态方法)
如果接口需要升级,比如增加新的抽象方法:会导致大幅的代码改动,以前实现接口的类都得改动
若想在不改动以前实现类的前提下进行接口升级,从 Java 8 开始,有 2 种方案:
默认方法(Default Method)静态方法(Static Method)
默认方法(Default Method)
用default
修饰默认方法默认方法只能是实例方法默认方法的使用:
当一个类实现的接口中有默认方法时,这个类可以:
啥也不干,沿用接口的默认实现重新定义默认方法,覆盖默认方法的实现重新声明默认方法,将默认方法声明为抽象方法(此类必须是抽象类)
当一个接口继承的父接口中有默认方法时,这个接口可以:
啥也不干,沿用接口的默认实现重新定义默认方法,覆盖默认方法的实现重新声明默认方法,将默认方法声明为抽象方法
简单示例:Eatable
中有默认方法,Dog
啥也不干,Cat
覆盖默认方法。
public interface Eatable {// 默认方法default void eat(String name) {System.out.println("Eatable - eat - " + name);}}
// Eatable接口中新增了方法, 但是没有影响到Dog类public class Dog implements Eatable {}
// Eatable接口中新增了方法, Cat类中可以覆盖public class Cat implements Eatable {@Overridepublic void eat(String name) {Eatable.super.eat(name);System.out.println("Cat - eat - " + name);}}
public static void main(String[] args) {Dog dog = new Dog();dog.eat("bone"); // Eatable - eat - boneCat cat = new Cat();cat.eat("fish");// Eatable - eat - fish// Cat - eat - fish}
1、如果父类定义的非抽象方法与接口的默认方法相同时,最终将调用父类的方法(就近原则):
public class Animal {public void run() {System.out.println("Animal - run");}}
public interface Runnable {default void run() {System.out.println("Runnable - run");}}
public class Dog extends Animal implements Runnable {}
public static void main(String[] args) {Dog dog = new Dog();dog.run(); // 继承的父类、实现的接口中都有 run() 方法, 默认调用父类的// Animal - run}
2、如果父类定义的抽象方法与接口的默认方法相同时,要求子类实现此抽象方法
可以通过super
关键字调用接口的默认方法
public interface Runnable {default void run() {System.out.println("Runnable - run");}}
public abstract class Animal {public void run() {}}
public class Dog extends Animal implements Runnable {// 父类的抽象方法run方法与接口中的run方法相同, 要求实现父类的抽象方法@Overridepublic void run() {Runnable.super.run(); // 可以通过super调用接口的默认方法System.out.println("Dog - run");// Runnable - run// Dog - run}}
如果(父)接口定义的默认方法与其他(父)接口定义的方法相同时,要求子类型实现此默认方法:
例:Runnable
和Walkable
两个父接口中定义的默认方法都是run()
,Testable
继承了两个父类,则要求实现默认方法run()
,Dog
类同理。
public interface Runnable {default void run() {System.out.println("Runnable - run");}}
public interface Walkable {default void run() {System.out.println("Walkable - run");}}
// Testable 父接口继承了 Runnable 父接口和 Walkable 父接口// 他们都有默认方法 run, 要求 Testable 接口实现该默认方法public interface Testable extends Runnable, Walkable {@Overridedefault void run() {Runnable.super.run();Walkable.super.run();System.out.println("Testable - run");}}
// Dog 类实现了 Runnable、Walkable 两个接口// 他们都有默认方法 run, 要求 Dog 类实现该默认方法public class Dog implements Runnable, Walkable {@Overridepublic void run() {Runnable.super.run();Walkable.super.run();System.out.println("Dog - run");}}
public static void main(String[] args) {Dog dog = new Dog();dog.run();// Runnable - run// Walkable - run// Dog - run}
再看一个例子:
public interface Animal {default String myself() {return "I am an animal.";}}
public interface Fire extends Animal {}
public interface Fly extends Animal {@Overridedefault String myself() {return "I am able to fly.";}}
public class Dragon implements Fly, Fire {}
public static void main(String[] args) {Dragon dragon = new Dragon();System.out.println(dragon.myself());// I am able to fly.}
静态方法(Static Method)
接口中定义的静态方法只能通过接口名调用,不能被继承
public interface Eatable {static void eat(String name) {System.out.println("Eatable - eat - " + name);}}
public interface Sleepable {static void eat(String name) {System.out.println("Sleepable - eat - " + name);}}
public interface Dog extends Sleepable, Eatable {static void eat(String name) {System.out.println("Dog - eat - " + name);}}
public static void main(String[] args) {Dog.eat("1");Eatable.eat("1");Sleepable.eat("3");// Dog - eat - 1// Eatable - eat - 1// Sleepable - eat - 3}
多态(Polymorphism)
什么是多态?
具有多种形态同一操作作用于不同的对象,产生不同的执行结果
多态的体现:
父类(接口)类型指向子类对象调用子类重写的方法
JVM 会根据引用变量指向的具体对象来调用对应的方法:
这个行为叫做:虚方法调用(virtual method invocation)类似于 C++ 中的虚函数调用
多态示例:
public class Animal {public void speak() {System.out.println("Animal - speak");}}
public class Dog extends Animal {@Overridepublic void speak() {System.out.println("Dog - wangwang");}}
public class Cat extends Animal {@Overridepublic void speak() {System.out.println("Cat - miaomiao");}}
public static void main(String[] args) {speak(new Dog()); // Dog - wangwangspeak(new Cat()); // Cat - miaomiao}// 多态的体现: 父类(接口)类型指向子类对象, 调用子类重写的方法static void speak(Animal animal) {animal.speak();}
类方法调用的细节
调用类方法只看左边声明的类,与实例化的类无关:
public class Animal {public static void run() {System.out.println("Animal - run");}}
public class Dog extends Animal{public static void run() {System.out.println("Dog - run");}}
public static void main(String[] args) {Dog.run(); // Dog - runAnimal.run(); // Animal - runDog dog1 = new Dog();// 调用类方法只看左边声明的类,与实例化的类无关dog1.run(); // Dog - runAnimal dog2 = new Dog();// 调用类方法只看左边声明的类,与实例化的类无关dog2.run(); // Animal - run}
成员变量访问的细节
public class Person {public int age = 1;public int getPAge(){return age;}}
public class Student extends Person {public int age = 2;public int getSAge() {return age;}}
public static void main(String[] args) {Student stu1 = new Student();System.out.println(stu1.age); // 2System.out.println(stu1.getPAge()); // 1System.out.println(stu1.getSAge()); // 2Person stu2 = new Student();System.out.println(stu2.age); // 2System.out.println(stu2.getPAge()); // 1}
instanceof
可以通过instanceof
判断某个类型是否属于某种类型(及其子类)public class Animal {}
public interface Runnable {}
public class Dog extends Animal implements Runnable {}
public static void main(String[] args) {Object dog = new Dog();System.out.println(dog instanceof Dog); // trueSystem.out.println(dog instanceof Animal); // trueSystem.out.println(dog instanceof Runnable); // trueSystem.out.println(dog instanceof String); // false}
instanceof
使用场景:
public class Animal {}
public class Cat extends Animal {public void miao() {System.out.println("Cat - miao");}}
public class Dog extends Animal{public void wang() {System.out.println("Dog - wang");}}
public static void main(String[] args) {speak(new Dog()); // Dog - wangspeak(new Cat()); // Cat - miao}static void speak(Animal animal) {if (animal instanceof Dog) {((Dog) animal).wang();} else if (animal instanceof Cat) {((Cat) animal).miao();}}
对象数组的注意点
public static void main(String[] args) {Object obj1 = 11;Integer obj2 = (Integer) obj1; // 可以转换成功System.out.println(obj2);// 下面一行等价于这么写: Object[] objs1 = new Object[] {11, 22, 33};Object[] objs1 = {11, 22, 33 };// java.lang.ClassCastException: // java.lang.Integer cannot be cast to [Ljava.lang.IntegerInteger[] objs2 = (Integer[]) obj1; // 转换失败, 抛异常System.out.println(objs2);}
【Java 面向对象】基础 Java程序的内存划分 嵌套类(内部类 静态嵌套类) 局部类 接口的升级问题(默认方法 静态方法) instanceof