前言
本节内容参考
- 百度百科
- BiliBili:黑马程序员
- BiliBili:尚硅谷
- BiliBili:遇见狂神说:https://space.bilibili.com/95256449/
- CSDN:qq_44903056:https://blog.csdn.net/qq_44903056/article/details/106434208
面向对象(OOP)概述
面向过程和面向对象是两种思想
面向过程
面向过程重点强调过程,举个例子:倒杯水。
- 放下杯子
- 拿起暖瓶
- 将水导入杯子中
面向过程强调的整个事件的过程,事无巨细
面向对象
强调的是这个整体,而不是其中的细节,比如倒杯水,强调的是能倒杯水的这个人。
面向对象注重于谁能实现,而不在乎怎么实现
面向对象和面向过程的适用场景
不能说面向对象比面向过程要好,这其实是两种不同的思想,针对于不同的环境。
打个比方,在公司:
- 老板的思想是:找个能干找个活的人。
- 程序员的思想是:怎么把这个活干好。
两种思想具体在代码中,适用场景是不同的。 比如要考虑这个项目的某个功能的整体怎么实现,就要用到面向对象。 如果要考虑这个项目的某个功能的具体怎么实现,就要深入细节,要用到面向过程。
类与对象
类与对象的关系
简单来说就是模板和实体的关系。
- 一个手机图纸,这叫类。
- 根据这个图纸造出来的手机叫做对象。
如何创建对象和初始化对象
使用关键字new
创建对应的对象
package com.howling;
public class OOP {
public static void main(String[] args) {
Studnet studnet = new Studnet();
}
}
class Studnet {
private String name;
private Integer age;
}
在这,Student是类,而student为对象
扩展:创建对象的方式一共有5种: 1、new出来 2、clone()方法浅拷贝 3、反射 4、反序列化 5、工厂模式
构造器
什么是构造器
构造器也叫做构造方法,是从模板-->实体
的必须条件。
有了构造方法之后,才可以创建对象。
构造器的基本实现
默认会给我们创建一个无参数的构造器
class Studnet {
private String name;
private Integer age;
}
class Studnet {
public Studnet() {
}
private String name;
private Integer age;
}
构造器默认创建一个无参数的,也就是说,上面这两个在意义上是相同的
注意一点,只要我们写了构造方法,那么系统不会再给我们提供默认的构造方法
我们可以看出构造器的基本特点:
- 和类名字相同
- 无返回值
- 可重载
我们马上说到重载
构造器的重载
class Studnet {
public Studnet() {
}
public Studnet(String name) {
this.name = name;
}
public Studnet(String name, Integer age) {
this.name = name;
this.age = age;
}
private String name;
private Integer age;
}
以上是三个构造器的重载,这就意味着,
我们可以通过三种构造来创建对象
package com.howling;
public class OOP {
public static void main(String[] args) {
Studnet studnet = new Studnet();
Studnet twoStudnet = new Studnet("howling");
Studnet threeStudent = new Studnet("howling", 21);
}
}
class Studnet {
public Studnet() {
}
public Studnet(String name) {
this.name = name;
}
public Studnet(String name, Integer age) {
this.name = name;
this.age = age;
}
private String name;
private Integer age;
}
类中的方法
我们可以使用对象去调用类中的方法。
package com.howling;
public class OOP {
public static void main(String[] args) {
Studnet threeStudent = new Studnet("howling", 21);
threeStudent.fun();//howling
new Studnet("bean",21).fun();//bean
}
}
class Studnet {
public Studnet(String name, Integer age) {
this.name = name;
this.age = age;
}
private String name;
private Integer age;
public void fun(){
System.out.println(name);
}
}
可以看到,每个对象调用的方法结果都是不同的
内存分析
栈
Stack
- 每个线程私有,不能实现线程之间的共享
- 局部变量放置在栈中
- 栈是由系统自动分配,速度快
- 在一个连续的空间中
堆
Heap
- new出来的对象都在堆中
- 是一个不连续的内存空间,分配灵活,速度慢
- 方法区
- 被所有的线程共享
- 存放程序中永远是一成不变的或者是惟一的内容:(类代码信息,静态变量,字符串常量)
面向对象的特性
抽象
面向对象现在说的是三大特性,其实在早些年还说了一种特性叫做抽象
,现在貌似不说了,但是这里还是要提一嘴。
抽象的概念
抽象的意思在汉语里是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征的过程
比如,你要对猫这一物种进行抽象,那么你其实可以抽出它的姓名,性别,颜色,等等。
抽象也是具有层级性的抽象
比如,你要在猫里面进行分类,可以分出家养猫和野生猫。
其中家养猫可以抽象出来主人的住址和居住地。而野生猫只能抽象出来居住地。
从这个观点上来看,不同层级的分类中,抽象出来的东西是不同的。
越往上走,生命甚至抽象不出性别。但是归根到了细节上面,你甚至可以抽象出来这只猫叫什么名字。
所以抽象的层级关系也可以大致理解为不断细分的过程。
抽象的意义
抽象只有在研究特定的问题的时候才会有意义。
日常生活中,人们不会在乎猫是不是有脊椎动物,因为这个没有意义。
但是在动物的研究中心这就有意义了
这样抽象确实是可以,但是没有什么意义。
但是如果你在研究各种动物的时候,猫属于有脊椎动物,这个时候就有意义了。
所以在研究不同问题的时候,我们也要有不同的考虑方向
封装
封装的概念
在Java中,把数据隐藏起来,把操作数据的方式暴露给外面,这就叫做封装。
比如一般人会看电视,但是不会去了解电视机到底是怎么工作的。
只需要一个遥控器就可以实现切换频道,调整声音等功能。但是不需要去关心到底是怎么做到的。
封装的步骤
private
关键字- 操作数据的方法
class Studnet {
private String name;
private Integer age;
public Studnet() {
}
public Studnet(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
上面是一个标准的类,我们叫做
JavaBean
,带有无参构造和全参构造,还有操作数据的方法。 一般在类中,我们使用getter
和setter
对数据进行操作
封装的意义
这样写好处十分明显:
- 隐藏了代码的细节,提升了数据的安全性
- 方便调用
- 形成了操作数据的规范,提高了系统的可维护性
方法重载
之前我们讲过类的构造器重载,现在我们来讲一下方法的重载。
其实构造器的重载也属于方法的重载,只不过构造器对于方法来说比较特殊。
方法的重载其实就是相同名字的方法不同的调用。
方法的重载和返回值,访问修饰符等无关,只和参数列表有关系
private void overLoad(){}
public int overLoad(int i){ return i; }
int overLoad(int i,int j){ return j; }
上面是方法的重载,可以看到和访问修饰符,返回值无关,只和参数列表有关系
参数列表具体又可以分为三个条件:
- 参数数量
- 参数类型
- 参数顺序
继承
继承的概念
就像孩子继承父亲一样,面向对象中也存在着继承的关系。
继承的实现步骤
- 首先要有一个父类(基类/超类),用于被继承
- 子类使用
extends
关键字声明为父类的子类s
package com.howling;
public class OOP {
public static void main(String[] args) {
Child child = new Child();
Integer property = child.property;
child.company();
}
}
class Father{
private String name;
private String sex;
Integer property;
void company(){
System.out.println("投资");
System.out.println("收入");
System.out.println("支出");
}
}
class Child extends Father{
}
继承的特性
我们就以孩子和父亲举例子。
孩子继承父亲,那么就说明儿子具备着父亲基因上的的很多特性。 Java子类继承父类(基类/超类),那么子类就具备着父类上的很多特性。 父亲自身也有一些特性是不可以被继承的,属于父亲自身私有的,比如姓名。 父类自身也有一些特性,是私有的
private
,所以不可以被子类继承。 一个孩子只能有一个父亲。 一个子类只能有一个父类。 孩子的父亲还有父亲,说明是一种传承关系。 子类的父类还有父类,说明是一种传承关系,这是多级继承。
到此为止,我们的特性就出来了
- 子类继承着父类大部分的属性
- 私有属性不能被继承
- 继承关系是单继承关系
- 继承可以多级继承
现在再转过头来,看上面的代码,就会发现这些特性
Obejct
要特别提一嘴Object
类,因为Java中的所有类都是直接或者间接地继承Object
如果对应到地球,那么Obejct就是第一个出现的生命,地球上的所有生命都继承自这个生命。
方法重写
假如父类有一个方法叫做A
,子类中也写了一个方法叫A
,这个时候冲突就出现了。
package com.howling;
public class OOP {
public static void main(String[] args) {
new Father().func();//父类
new Child().func();//子类
}
}
class Father{
void func(){
System.out.println("父类");
}
}
class Child extends Father{
@Override
void func(){
System.out.println("子类");
}
}
我们可以看到,方法的重写相当于覆盖的效果,同时可以使用注解
@Override
来检测是否进行了正确的重写
重写的注意事项
- 重写只存在于父类和子类之间
- 私有方法不能被子类读取到,所以不能重写
- 只要涉及到静态方法,就不能重写
- 静态方法不可以被重写为非静态方法
- 非静态方法不可以被重写为静态方法
- 子类可以定义一个和父类静态方法相同名字,相同参数的静态方法,但这其实不是重写,是创建一个新的
- 重写的语法
- 方法名,参数列表必须相同
- 返回值必须为
父类的返回值或父类返回值的子类
- 访问修饰符可以更加公开但是不能私有
抛出异常的范围可以被缩小,但是不能扩大
以上的条件都可以被
@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的双亲委派机制表现形式是相同的
重写,重载,多态
在谈论三者的关系时,首先明确一个概念:虚拟方法调用
在子类中对父类的方法进行了重载,这时,父类就叫做虚拟方法。
我们在调用的时候,父类会根据不同的子类对象进行不同的方法调用,这就是一个由虚到实的过程。
多态的调用就是这样的一个过程。所以多态的显著特点之一就是在编译期无法确定,在调用的时候才可以确定
重写和多态的关系:重写是多态的前提之一
重载和多态的关系:多态在编译期无法确定,调用的时候才可以确定,但是重载在编译期就确定了,所以二者没什么关系
现在还有一种说法,叫做重载叫做编译时多态,而重写的叫做运行时多态。但是这种说法个人感觉站不住脚
重载和重写的关系:唯一相像的地方就是:都是改变方法。除此之外没什么关系。
多态的注意事项
- 属性没有多态,只有方法有多态
多态的三条件
- 有继承
方法可以被子类重写,并且可以被外部引用
static
,final
,private
修饰的方法不可以被重写,所以不符合定义,不是多态protected
可以被重写,但是不可以被外部引用,所以不符合定义,不是多态
父类的引用指向子类的对象
面向对象中的关键子类字
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变量称为静态变量,静态变量和非静态变量有区别:
- 静态变量属于类,可以直接使用类名访问
- 非静态变量属于对象,必须创建出对象来访问
static方法称为静态方法,静态方法和成员方法有区别:
- 静态方法属于类,可以直接使用类名来访问
- 成员方法属于对象,必须使用对象来访问
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中类及方法的加载顺序的位置
- 父类静态变量
- 父类静态代码块
- 子类静态变量
- 子类静态代码块
- 父类成员变量
- 父类匿名代码块
- 父类构造器
- 子类成员变量
- 子类匿名代码块
- 子类构造器
加载顺序时分为两部分加载:首先加载静态部分,然后加载非静态部分 第一部分:静态部分:静态部分首先是父类的静态变量,然后是父类的静态代码块。然后是子类的静态变量,然后是子类的静态代码块 第二部分:非静态部分:非静态部分首先是父类的成员变量,然后是父类的匿名代码块,然后是父类的构造方法。然后是子类的成员变量,子类的静态代码块,子类的构造方法
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();
}
安卓中貌似经常使用这种方法