一、抽象类
必须要重写的方法所在的类—>抽象类
1.抽象方法
(1)抽象方法:将共性的行为(方法)抽取到父类之后,由于每一个子类执行的内容不一样,所以,再父类中不能确定具体的方法体,该方法就可以定义为抽象方法。
(2)抽象类:包含抽象方法的类。
2.定义格式
抽象方法:
修饰符 abstract 返回值类型 方法名 (参数列表);
public abstract void work();
抽象类
abstract class 类名字 { }
3.细节
(1)抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
(2)抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
(3)抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
(4)抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。
(5)抽象类存在的意义是为了被子类继承。
二、接口
接口是一种规则,更多的侧重为一种行为
1.定义接口
//接口的定义格式:
interface 接口名称{
// 抽象方法
}
// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”
2.接口的使用
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
/**接口的实现:
在Java中接口是被实现的,实现接口的类称为实现类。
实现类的格式:*/
class 类名 implements 接口1,接口2,接口3...{
}
实现类还可以在继承一个类的同时实现多个接口
public class 类名 extends 父类 implements 接口名1,接口名2 { }
3.成员特点
(1)成员变量
只能是常量,默认修饰符:public static final
(2)构造方法
没有
(3)成员方法
默认修饰符
JDK7以前:只能定义抽象方法,默认修饰符:public abstract(不写,但java会自动加上)
JDK8新特性:接口中可以定义有方法体的方法
JDK9新特性:可以定义私有方法
4.接口与类的关系
(1)类和类
继承关系,单继承,多层继承
(2)类和接口
1)实现关系,单实现,多实现,继承一个类的同时实现多个接口
2)一个类实现一个接口,要重写所有的方法(因为接口里是抽象方法)
3)一个类实现多个接口,且接口中有重名的方法(重名的方法只需要重写一次即可)
(3)接口和接口
继承关系,单继承,多继承(实现类实现了子接口,要重写所有的方法)
5.扩展
(1)JDK8以后的接口
1)定义有方法体的方法
<1>解决接口升级的问题
<2>默认方法的定义
格式:
public default 返回值类型 方法名(参数列表){}
public default void show(){}
—>默认方法不是抽象方法,所以不被强制重写。但如果被重写,重写时去掉关键字default
—>public可以省略,default不能省略
—>如果事先多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
2)定义静态方法
<1>需要用static修饰
<2>定义格式
public static 返回值类型 方法名(参数列表){}
public static void show(){}
<3>注意
—>静态方法只能通过接口名调用,不能通过类名或对象名调用
—>public可以省略,static不能省略
—>不能被重写
(2)JDK9新增方法
1)接口中的私有方法
<1>普通私有方法(服务普通的私有方法)
格式:
private 返回值类型 方法名(参数列表){}
private void show(){}
<2>静态私有方法(服务于静态私有方法)
格式:
private static 返回值类型 方法名(参数列表){}
private static void methond(){}
(3)接口的应用
1)接口代表规则,是行为的实现
2)当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式成为接口多态
(4)适配器设计模式
1)解决各种问题的套路
2)如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?
可以在接口跟实现类中间,新建一个中间类(适配器类)
让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
三、内部类
—>类的五大成员之一
—>在一个类里再定义一个类
—>应用场景:
一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用(内部类单独没有意义)
—>可以直接访问外部类的成员,包括私有;外部类要访问内部类的成员,必须创建对象
1.成员内部类*
写在成员位置,外部类的成员
(1)创建
protected修饰—>其他包的子类,本包的类
1)外部直接创建成员内部类的对象(非私有)
外部类.内部类 变量 = new 外部类().new 内部类();
private修饰成员内部类,其他类就不能直接创建对象,只能在本类中创建对象。但是可以使用下面这种方法创建(在外部类中定义一个方法提供内部类的对象)
2)在外部类中定义一个方法提供内部类的对象(private修饰内部类时)
public class Outer{
String name;
private class Inner{}
public Inner getInstance(){
return Inner();
}
}
psvm{
Outer o = new Outer();
o.getInstance();
Outer.Inner inner = o.getInstance();//报错,因为Inner是私有的
Object inner = o.getInstance();//正确
}
(2)内部类获取外部类成员变量
psvm{
Outet.Inner oi = new Outer().new Inner();
oi.show();
public class Outer{
private int a = 10;
private class Inner{
private int a = 20;
public void show(){
int a = 30;
sout(??)//10
sout(??)//20
sout(??)//30
}
}
}
就近原则
sout(a);//30
sout(this.a);//20
sout(Outer.this.a);//10
内存图
1.Outer.class,Outer$Inner.class,Test.class字节码文件加载到方法区
2.栈内存Outer.Inner oi
3.(Outet.Inner oi = new Outer())new关键字—>在堆内存中开辟外部类对象内存空间<001>(int a = 10)
4.new Inner(),在堆内存中开辟内部类的地址空间<002>(int a = 20),与此同时,给内部类添加一个隐藏的内部类成员(Outer this <001>)用来记录外部类变量的地址值
5.此时oi存储的是002
6.show()方法加载到栈内存,int a=30入栈,sout(a)先在本方法里找,打印出30
7.sout(this.a)输出调用者地址值中的a,既内部类对象里<002>的a,即打印20
8.sout(Outer.this.a),输出Outer this<001>(外部类对象的地址值)的a,即10
2.静态内部类*
静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态需要创建对象(根据内存理解)
特殊的成员内部类
(1)创建格式
外部类.内部类 变量 = new 外部类.内部类构造器;//实际new的是内部类的对象
(2)调用
* 调用非静态方法的格式:先创建对象,用对象调用
* 调用静态方法的格式:外部类名.内部类名.方法名();
3.局部内部类*
(1)定义在方法中的类。
(2)外界无法直接使用,需要在方法内部创建对象并使用
(3)该类可以直接访问外部类的成员,也可以访问方法内的局部变量
(public、static、final只能修饰成员变量,不能修饰局部变量)
public class Outer{
int a = 10;
public void show(){
int b = 20;
class Inner{//局部内部类
String name;
int age;
public void methond1(){
sout(局部内部类中的methond1方法);
}
public static void methond2(){
sout(局部内部类中的methond2静态方法);
}
}
//创建局部内部类的对象
Inner i = new Inner();
sout(i.name);
sout(i.age);
i.methond1();
Inner.methond2();//关于静态方法的调用
}
}
测试类
public class Test{
public static void main(String[] args){
Outer o = new Outer();
o.show();//nulll,20,10,sout(methond1),sout(methond2)
}
}
4.匿名内部类
是内部类的简化写法。他是一个隐含了名字的内部类。开发中,最常用到的内部类就是匿名内部类了。
匿名内部类是成员内部类的一种。❌
可以写在成员位置,也可以写在局部位置
(1)格式
new 类名或者接口名() {
重写方法;//重写所有的抽象方法,
};
包含了:
- 继承或者实现关系
- 方法重写
- 创建对象
所以从语法上来讲,这个整体其实是匿名内部类对象
(2)推导
以前:
public class Student implements Swim{//Swim是接口
@Override
public void swim(){
sout(重写了游泳的方法);
}
}
匿名内部类:
new Swim(){
@Override
public void swim(){
sout(重写了游泳的方法);
}
};//这个整体是一个对象,大括号和里面的内容才是一个类
1.public class Student implements Swim实际上是类名
2.删去public class Student implements后的内容,这个才是真正的内容
3.删去public class Student implements,变成了一个没有名字的类
4.这个没有名字的类想要实现Swim接口,把Swim写在大括号前面,表示这个没有名字的类实现了Swim接口,所以需要在类中重写接口里的所有抽象方法
5.还想要创建这个没有名字的类的对象,new。既下面的代码,不创建一个类,直接用用匿名内部类实现接口.
new后面如果是接口,则是实现关系;如果是类,则是继承关系
匿名内部类必须继承一个父类或者实现一个父接口。
(3)使用
mthond(Animal a)是定义在测试类里的方法,其中Animal是一个类
//使用
methond(
new Animal(){
@verride
public void swim(){
sout(重写了游泳的方法);
}
}//这个整体可以看作是Animal的子类对象
);
(4)扩展
1)本质
new Swim(){
@Override
public void swim(){
sout(重写了游泳的方法);
}
};//整体可以理解为Swim接口的实现类对象
Swim s = new Swim(){
@Override
public void swim(){
sout(重写了游泳的方法);
}
};
s.swim();//编译看左边,运行看右边
2)调用
new Swim(){
@Override
public void swim(){
sout(重写了游泳的方法);
}
}.swim();
(5)使用场景
1)当方法的参数是接口或者类时
2)以接口为例,可以传递这个接口的实现类对象
3)如果实现类只要使用一次,就可以用匿名内部类简化代码