1. 接口
1.1 概述
接口,是Java语言中的一种引用类型,是方法的集合,如果说类的内部封装了成员变量,构造方法和成员方法,那么接口的内部主要就是封装方法,包括抽象方法,默认方法和静态方法,私有方法.
接口的定义:它与定义类的方式相似,但是使用interface关键字,它也会被编译乘.class文件,但它并不是类,而是另外一种引用数据类型
引用数据类型:数组,接口,类
接口的使用,它不能创建对象,但可以被是心啊(implements,类似与被继承).一个实现接口的类(可以看作是接口的子类),需要实现接口中所有的抽象方法,创建该类对象就可以调用方法了,否则它必须是一个抽象类.
1.2 定义格式
public interface 接口名称 {
// 抽象方法
// 抽象方法
// 抽象方法
// 抽象方法
}
含有抽象方法
抽象方法:使用abstract关键字修饰,可以省略,没有方法体,该方法供子类实现使用
public interface interFaceName {
public abstract void mothod();
}
含有默认方法和静态方法
默认方法:使用default修饰,不可省略供子类调用或者子类重写.
静态方法:使用static修饰,供接口直接调用
public interface interFaceName {
public default void method () {
// 执行语句
}
public static void metnod2 () {
// 执行语句
}
}
含有私有方法和私有静态方法
私有方法:使用private修饰,供接口中的默认方法或静态方法调用
public interface interFaceName {
private void method() {
// 执行语句
}
}
1.3 基本的实现
实现的概述
类与接口的关系是实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类.实现的动作类似继承,格式相仿,只是关键字不同,实现使用implements关键字.
- 非抽象子类实现接口:
- 必须重写接口中的所有抽象方法
- 继承了接口的默认方法即可以直接调用,也可以重写
实现格式
class 类名 implements 接口名 {
// 重写接口抽象方法[必须]
// 重写接口中默认方法[可选]
}
抽象方法的使用
class Test1 {
public static void main(String[] args) {
// 创建子类
Animal a = new Animal();
// 调用实现后的方法
a.eat();
a.sleep();
}
}
interface LiveAble {
public abstract void eat();
public abstract void sleep();
}
class Animal implements LiveAble {
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void sleep() {
System.out.println("晚上睡");
}
}
默认方法的使用
可以继承,可以重写,二选一,但是之能通过类的实现对象来调用
- 继承默认方法
class Test1 {
public static void main(String[] args) {
// 创建子类
Animal a = new Animal();
// 调用默认方法
a.fly();
}
}
interface LiveAble {
public default void fly() {
System.out.println("天上飞");
}
}
class Animal implements LiveAble {
// 继承,什么都不用写,直接调用
}
- 重写默认方法
class Test1 {
public static void main(String[] args) {
// 创建子类
Animal a = new Animal();
// 调用默认方法
a.fly();
}
}
interface LiveAble {
public default void fly() {
System.out.println("天上飞");
}
}
class Animal implements LiveAble {
@Override
public void fly() {
System.out.println("自由自在的飞");
}
}
静态方法的使用
静态与.class文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现了了类的对象调用
class Test1 {
public static void main(String[] args) {
// Animal.run(); //错误,无法继承方法,也无法调用
LiveAble.run();
}
}
interface LiveAble {
public static void run() {
System.out.println("跑起来---");
}
}
class Animal implements LiveAble {
// 无法重写静态方法
}
}
私有方法的使用
- 私有方法:只有默认方法可以调用
- 私有静态方法:默认方法和静态方法可以调用
如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法 去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助
interface LiveAble {
public default void run() {
func1();
func2();
}
private void func1(){
System.out.println("跑起来--");
}
private void func2(){
System.out.println("跑起来--");
}
}
1.4 接口的多实现
在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
class 类名 implements 接口1,接口2,接口3...{
// 重写接口中的抽象方法[必须]
// 重写接口中的默认方法[不重名时可选]
}
抽象方法
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
interface A {
public abstract void showA();
public abstract void showB();
}
interface B {
public abstract void showB();
public abstract void show();
}
class C implements A, B {
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
@Override
public void show() {
System.out.println("show");
}
}
默认方法
接口中,有多个默认方法时,实现类都可继承使用。如果默认方法有重名的,必须重写一次
interface A {
public default void showA(){}
public default void show(){}
}
interface B {
public default void showB(){}
public default void show(){}
}
class C implements A, B {
@Override
public void show() {
System.out.println("method");
}
}
静态方法
接口中,存在同名的静态方法并不会重突,原因是只能通过各自接口名访问静态方法。
优先级问题
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执 行父类的成员方法。
class Test1 {
public static void main(String[] args) {
C c = new C();
c.showA();//dddddd
}
}
interface A {
public default void showA(){
System.out.println("aaaaaa");
}
}
class D{
public void showA(){
System.out.println("dddddd");
}
}
class C extends D implements A {
// 未重写showA方法
}
1.5 接口的多继承
一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends关键字,子接口继承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次
interface A {
public default void show(){
System.out.println("aaaaaa");
}
}
interface B{
public default show(){
System.out.println("bbbbbb");
}
}
interface C extends A,B{
public default show(){
System.out.println("cccccc");
}
}
- 子接口重写默认方法时,default关键字可以保留。
- 子类重写默认方法时,default关键字不可以保留
1.6 其他成员特点
- 接口中,无法定义成员变量,但是可定义常量,其值不可以改变,默认使用public static final修饰。
- 接口中,没有构造方法,不能创建对象。
- 接口中,没有静态代码块。
2. 内部类
2.1 概述
什么是内部类
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类
成员内部类
- 成员内部类:定义在类中方法外的类
定义格式
class 外部类 {
class 内部类{
}
}
在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构.比如,汽车Car中包含发动机类Engine,这时,Engine就可以使用内部类来描述,定义在成员位置.
calss Car { // 外部类
class Engine { // 内部类
}
}
访问特点
- 内部类可以直接访问外部类的成员,包括私有成员
- 外部类要访问内部类的成员,必须要建立内部类的对象.
创建内部类的格式:
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
class Test1 {
public static void main(String[] args) {
// 创建外部类对象
Person p = new Person();
// 创建内部类对象
Person.Heart heart = p.new Heart();
// 调用内部类方法
heart.jump();
// 调用外部类方法
p.setLive(false);
// 调用内部类方法
heart.jump();
}
}
class Person{
private boolean live = true;
class Heart {
public void jump(){
// 直接访问外部类成员
if (live) {
System.out.println("心脏在跳动");
}else {
System.out.println("心脏不跳了");
}
}
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
}
内部类仍然是一个独立的类,在编译后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和 Heart.clss
2.2 匿名内部类
- 匿名内部类:是内部类的简化写法,它的本质是一个带具体实现的父类或父接口的匿名的子类对象.开发中最常用到的内部类就是匿名内部类了,以接口举例,当你使用一个接口时,做以下操作:
- 定义子类
- 重写接口中的方法
- 创建子类对象
- 调用重写后的方法
我们的目的只是为了调用方法,匿名内部类就是做这样的快捷方式
前提
匿名内部类必须继承一个父类或实现一个父接口
格式
new 父类名或者接口名 () {
// 方法重写
@Override
public void method() {
// 执行语句
}
}
使用方式
abstract interface FlyAble{
public abstract void fiy();
class Test1 {
public static void main(String[] args) {
/*
1. 等号右边:是匿名内部类,定义并创建该接口的子类对象
2.等号左边:是多态的赋值,接口类型引用指向子类对象
*/
FlyAble f = new FlyAble() {
@Override
public void fiy() {
System.out.println("我飞了---");
}
};
// 调用fly方法,执行重写后的方法
f.fiy();
}
}
通常在方法的形式参数是接口或者抽象类时也可以将匿名内部类作为参数传递
class Test1 {
public static void main(String[] args) {
/*
1. 等号右边:是匿名内部类,定义并创建该接口的子类对象
2.等号左边:是多态的赋值,接口类型引用指向子类对象
*/
FlyAble f = new FlyAble() {
@Override
public void fiy() {
System.out.println("我飞了---");
}
};
// 将f传递给showFly方法中
showFly(f);
}
public static void showFly(FlyAble f){
f.fiy();
}
}
以上两步,也可以简化为一步:
class Test1 {
public static void main(String[] args) {
/*
创建匿名内部类,直接传递给showFly(FlyAble f)
*/
showFly(new FlyAble() {
@Override
public void fiy() {
System.out.println("我飞了---");
}
});
}
public static void showFly(FlyAble f){
f.fiy();
}
}
3. 引用类型用法
实际开发中,引用类型的使用非常重要,也是非常普遍的.基本类型可以作为成员变量,作为方法参数,作为方法的返回值,引用类型也是可以的
3.1 class作为成员变量
定义一个类Role(游戏角色)
class Role {
int id; // 角色id
int blood; // 生命值
String name; // 角色名称
}
使用int类型表示角色的id和生命值,使用String类型表示姓名,此时String本身就是引用类型,由于使用的方式类似常量,所以常常忽略了它时引用类型的存在
定义武器类,增加攻击力
class Weapon {
String name; // 武器名称
int hurt; // 伤害值
}
定义盔甲类,增加防御能力,也就是提升生命值;
class Armour {
String name; //装备名称
int protect; // 防御值
}
定义角色类
class Role {
int id;
int blood;
String name;
//添加武器属性
Weapon wp;
// 添加盔甲属性
Armour ar;
public Weapon getWp() {
return wp;
}
public void setWp(Weapon wp) {
this.wp = wp;
}
public Armour getAr() {
return ar;
}
public void setAr(Armour ar) {
this.ar = ar;
}
// 攻击方法
public void attack(){
System.out.println("使用" + wp.name + ",造成" + wp.hurt + "点伤害");
}
// 穿戴盔甲
public void wear(){
this.blood += ar.protect;
System.out.println("穿上" + ar.name + ",生命子增加" + ar.protect);
}
}
测试类
class Test1 {
public static void main(String[] args) {
// 创建Weapon对象
Weapon wp = new Weapon("屠龙刀", 999999);
// 创建Armour 对象
Armour ar = new Armour("麒麟甲", 10000);
// 创建Role 对象
Role r = new Role();
// 设置武器值
r.setWp(wp);
//设置盔甲属性
r.setAr(ar);
// 攻击
r.attack();
// 穿戴盔甲
r.wear();
}
}
输出结果:
使用屠龙刀,造成999999点伤害
穿上麒麟甲,生命子增加10000
类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象
3.2 interface 作为成员变量
接口时对方法的封装,对应游戏当中,可以看作时扩展游戏角色的技能.所以如果想扩展更强大技能,我们在Role中,可以增加接口作为成员变量,来设置不同的技能.
定义接口
interface FaShuKill {
public abstract void faShuAttack();
}
定义角色类
class Role {
FaShuKill faShuKill;
public void setFaShuKill(FaShuKill faShuKill) {
this.faShuKill = faShuKill;
}
// 法术攻击
public void faShuKillAttack(){
System.out.print("发动法术攻击:");
faShuKill.faShuAttack();
System.out.println("攻击完毕");
}
}
定义测试类
class Test1 {
public static void main(String[] args) {
// 创建游戏角色
Role role = new Role();
// 设置角色法术技能
role.setFaShuKill(new FaShuKill() {
@Override
public void faShuAttack() {
System.out.println("纵横天下");
}
});
// 发动法术攻击
role.faShuKillAttack();
// 更换技能
role.setFaShuKill(new FaShuKill() {
@Override
public void faShuAttack() {
System.out.println("逆转乾坤");
}
});
// 发动法术攻击
role.faShuKillAttack();
}
}
输出结果:
发动法术攻击:纵横天下
攻击完毕
发动法术攻击:逆转乾坤
攻击完毕
我们使用一个接口作为成员变量,以便随时更换,这样的设计更为灵活,增加了程序分扩展性
接口作为成员变量时,对它进行赋值的操作,实际上时赋给它该接口的一个子类对象
4.3 interface作为方法参数和返回值类型
当接口作为方法的参数和返回值类型时,需要传递子类对象
获取集合中所有的偶数.
定义方法:
private static List<Integer> getEvenNum(ArrayList<Integer> list) {
// 创建保存偶数的集合
ArrayList<Integer> evenList = new ArrayList<>();
//遍历集合list,判断元素是否为偶数,是就添加到evenList中
for (int i = 0; i < list.size(); i++) {
Integer integer = list.get(i);
if (integer % 2 == 0) {
evenList.add(integer);
}
}
/*
返回偶数集合
因为getEvenNum方法的返回值类型是List,而ArrayLsit是List的子类
所以evenList可以返回
*/
return evenList;
}
调用方法:
public static void main(String[] args) {
// 创建ArrayList集合,并添加数字
ArrayList<Integer> secList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
secList.add(i);
}
/*
获取偶数集合
因为getEvenMum方法的参数是List,而ArrayList是List的子类
所以secList可以传递
*/
List list = getEvenNum(secList);
System.out.println(list);
}
输出结果:
[0, 2, 4, 6, 8]
- 接口作为参数时,传递它的子类对象。
- 接口作为返回值类型时,返回它的子类对象