【从零开始的Java开发】1-4-4 多态与内部类:接口:定义并测试、抽象方法、常量、默认方法、静态方法、重名默认方法和重名静态方法的解决方案、继承;成员、静态、方法、匿名 内部类

引入

Java只支持单继承:一个子类只有唯一的一个父类。
那么,如何解决一个类型中需要兼容多种类型特征呢?以及多个不同类型具有相同特征呢?

举一个手机的例子:
一开始的手机Telphone只能打电话:

//原始手机
public class Telphone {
	private String brand;
	private int price;
	
	public Telphone() {
		
	}
	
	//打电话
	public void call() {
		System.out.println("打电话");
	}

	public String getBrand() {
		return brand;
	}

	public void setBrand(String brand) {
		this.brand = brand;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}
	
	
}

之后的手机Telphone2可以发短信:

public class Telphone2 extends Telphone{
	public void message() {
		System.out.println("发短信");
	}
}

再之后的手机Telphone3可以听音乐和看视频:

public class Telphone3 extends Telphone2{
	public void vedio() {
		System.out.println("看视频");
	}
	public void music() {
		System.out.println("听音乐");
	}
}

现在的手机Telphone4可以上网:

public class Telphone4 extends Telphone3{
	public void network() {
		System.out.println("上网");
	}
}

写一个测试类:

public class PhoneTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Telphone4 phone=new Telphone4();
		phone.call();
		phone.message();
		phone.vedio();
		phone.music();
		phone.network();
	}

}

输出:

打电话
发短信
看视频
听音乐
上网

由此可见,我们可以通过继承关系来描述手机演变史的记录。
但是,电脑也可以上网,智能手表也可以打电话,MP3也可以听音乐…这些类有手机的部分功能,但它们无法组合成一个公共的父类。我们如果想实现电脑、智能手表、MP3的功能,难道要一个个重复地去实现这些类(和它们重复的功能)吗?

答案是否定的,接下来我们介绍的接口可以完美地解决上述的问题。

接口:定义接口并测试

定义一个“拍照”接口Interface

public interface IPhoto {
	//具有拍照能力
	public void phone();
}

Camera类里调用这个接口:(调用关键字implements

public class Camera implements IPhoto{
	
	@Override
	public void phone() {
		System.out.println("相机可以拍照");
	}
}

在第四代手机Telphone4里也调用这个接口:

public class Telphone4 extends Telphone3 implements IPhoto{
	public void network() {
		System.out.println("上网");
	}
	
	@Override
	public void phone() {
		System.out.println("手机可以拍照");
	}
}

测试类:调用不同类的phone方法。

IPhoto ip=new Telphone4();
ip.phone();
ip=new Camera();
ip.phone();

输出:

手机可以拍照
相机可以拍照

注意:这里的ip只能访问photo,不能访问如network这种类的其他方法。换言之,我们可以通过接口来描述不同类型具有相似的行为特征,从而建立关系后,以接口引用指向实现类的方式来描述不同类型对接口行为的具体表现。

接口成员:抽象方法、常量

概念

  • 接口定义了某一批类所需要遵守的规范
  • 接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定了这些类里必须提供某些方法

如:Smartwatch类要用INet接口,那么它要么实现接口中的所有方法,要么让Smartwatch类自身变为abstract的类。
在这里插入图片描述

举例:电脑、智能手表、第四代手机都有上网的能力。

接口INet

//接口访问修饰符:通常是public 或 默认 
public interface INet {
	/*接口中抽象方法可以不写abstract关键字
	 *访问修饰符默认public
	 *当类实现接口时,需要去实现接口中的所有抽象方法,否则要将其类变为抽象类
	 */
	public void network();
	
	//接口中可以包含常量,默认public static final
	int temp=20;
}

电脑Computer使用这个接口:

public class Computer implements INet{
	@Override
	public void network() {
		System.out.println("电脑可以上网");
	}
}

智能手表Smartwatch

public class Smartwatch implements INet{

	@Override
	public void network() {
		// TODO Auto-generated method stub		
		System.out.println("智能手表可以上网");		
	}
}

第四代手机Telphone4

public class Telphone4 extends Telphone3 implements INet{
	@Override
	public void network() {
		System.out.println("手机可以上网");
	}
}

测试类:

INet it=new Computer();
it.network();
it=new Smartwatch();
it.network();
it=new Telphone4();
it.network();

输出:

电脑可以上网
智能手表可以上网
手机可以上网

关于常量

接下来测试一下INet中的常量:

System.out.println(INet.temp);
INet it=new Computer();
System.out.println(it.temp);

输出:

20
20

如果我们在接口中的常量temp为20,在实现类中定义一个temp为30,那么在测试类中会输出哪一个呢?

System.out.println(INet.temp);
INet it=new Computer();
System.out.println(it.temp);
Computer com=new Computer();
System.out.println(com.temp);

输出:

20
20
30

注意:接口的访问修饰符通常是public 或 默认
因为其他的类要重写接口里的方法,所以不能是private
而如果是protected的话,会报错。

在这里插入图片描述
注意:接口中的常量默认public static final

接口成员:默认方法、静态方法

默认方法

//default:默认方法 可以带方法体
default void connection() {
	System.out.println("我是接口中的默认链接");
}

这样在实现类中没有connection这个方法也不会报错。
在这里插入图片描述
静态方法

//static:静态方法 可以带方法体
static void stop() {
	System.out.println("我是接口中的静态方法");
}

实现类中没有重写它也不会报错。

如何调用默认方法和静态方法
默认方法可以实例化一个类后调用,静态方法用接口名调用。
ps:默认方法和静态方法在jdk 1.8之后新增

INet.stop();
INet it=new Computer();
it.connection();

输出:

我是接口中的静态方法
我是接口中的默认链接

使用场景:想用一个接口,但接口中某些方法对类来说没有意义。

重写默认方法
按Alt+/,会自动补全重写的默认方法。
我们发现,尽管在接口中的默认方法没有写访问权限的修饰,它是默认为public的。

如果没有super,那么只能访问接口中的静态成员。
在这里插入图片描述

@Override
public void connection() {
	// TODO Auto-generated method stub
	INet.super.connection();//调用接口中默认的方法
}

default默认方法可以在实现类中重写,并可以通过接口的引用调用,而static静态方法不可以在实现类中重写,可以通过接口名调用。

关于多接口中重名默认方法处理的解决方案

在Java中一个类可以实现多个接口:在implements后通过逗号添加新的接口名即可。

public class Smartwatch implements INet,IPhoto{
}

IPhoto中的默认方法为:

default void connection() {
		System.out.println("我是IPhoto的默认方法");
	}

INet中的默认方法为:

default void connection() {
		System.out.println("我是INet的默认方法");
	}

那么,Smartwatch会报错——不知道应该调用哪个connection
在这里插入图片描述
解决方法——重写connection,说明要调用哪个connection,如:说明要调用INetconnection,这样就不报错了。

@Override
	public void connection() {
		// TODO Auto-generated method stub
		INet.super.connection();
	}

或者:重写connection,定义一个自己的connection

public void connection() {
		// TODO Auto-generated method stub
		System.out.println("Smartwatch中的connection");
	}

接下来测试一下:

INet it=new Smartwatch();
it.connection();
IPhoto itt=new Smartwatch();
itt.connection();

输出:

Smartwatch中的connection
Smartwatch中的connection

说明调用的都是重写的connection

一个类只能继承自一个类,但是可以实现若干接口。

那么,如果一个类继承自一个类,同时又实现两个接口,且这三者都有同名的方法,那么它是否可以分辨这些方法呢?

INetIPhoto中已经有connection方法。给Telphone3中也添加一个connection方法:

public class Telphone3 extends Telphone2{	
	public void connection() {
		System.out.println("Telphone3的connection方法");
	}
}

Telphone4继承Telphone3

public class Telphone4 extends Telphone3 implements INet,IPhoto{
}

测试类:

INet it=new Telphone4();
it.connection();
IPhoto itt=new Telphone4();
itt.connection();

输出:

Telphone3的connection方法
Telphone3的connection方法

由此可见,子类会选择父类派生下来的方法。当然,如果子类重写了这个方法,那么在调用的时候会选择子类的方法。

总结

  1. 当一个类实现多个接口,而接口中有同名的默认方法时,实现类要重写这个方法
  2. 如果这个类已经继承自父类(且自身没有重写方法),那么默认情况下会调用父类的同名方法
  3. 如果这个类已经重写了这个方法,那么会调用自身重写的方法

关于多接口中重名常量处理的解决方案

三段代码是否正确?若正确,输出是什么?若错误,请说明原因。
在这里插入图片描述
代码1:错误。不知道是哪个x(接口One的还是接口Two的?)。
代码2:错误。不知道是哪个x(接口One的还是接口Two的还是父类Three的?)。
代码3:正确。输出44.

接口的继承

接口可以实现继承关系,并且可以继承多个父接口。一个类如果实现了某个子接口,那么它要实现所有的方法。

如:
父接口1:

public interface IFather {
	void run();
}

父接口2:

public interface IFather2 {
	void fly();
}

子接口继承两个父接口:

public interface ISon extends IFather,IFather2{
	void say();
}

实现类实现子接口——要实现所有方法:

public class Demo implements ISon{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void say() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		
	}

}

如果继承的多个接口中有同名的默认方法,那么子接口也要重写这个方法。实现类调用方法的时候调用的也是子接口的方法。

内部类

  • 在Java中,可以将一个类定义在另一个类里面或一个方法里面,这样的类称为内部类
  • 与之对应,包含内部类的类称为外部类

如:

//外部类:人
public class Person {
	int age;
	
	public Heart getHeart() {
		return new Heart();
	}
	
	//内部类:心脏
	class Heart{
		public String beat() {
			return "心脏在跳";
		}
	}
}

内部类隐藏在外部类之内,更好的实现了信息隐藏——是更好的封装手段。

内部类的分类

  • 成员内部类
  • 静态内部类
  • 方法内部类
  • 匿名内部类

成员内部类

内部类中最常见的就是成员内部类,也成为普通内部类

定义一个person类:

//外部类
public class person {
	int age;
	
	public Heart getHeart() {
		return new Heart();
	}
	
	//成员内部类
	class Heart {
		public String beat() {
			return "心脏跳动";
		}
	}
}

如何获取内部类的实例对象

方案1:new一个外部类的实例对象,再new它的内部类

//new 外部类.new 内部类
person.Heart myHeart=new person().new Heart();

方案2:使用外部类对象获取内部类实例

person a=new person();
a.age=1;

person.Heart myHeart=a.new Heart();

方案3:通过外部类对象的获取方法GetHeart
ps:一般外部类都会设置一个获取内部类的方法,以便于内部类对象的实例化操作

person a=new person();
a.age=1;

person.Heart myHeart=a.getHeart();

总结

  • 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
  • 内部类访问修饰符可以任意,但访问范围会受到影响
  • 内部类可以直接访问外部类的成员:如果出现同名属性,优先访问内部类定义的属性
  • 若想访问外部类的属性,可以通过外部类.this.成员的方式访问外部类同名的信息
  • 若外部类想访问内部类的信息,要通过内部类的实例获取到,否则无法访问
  • 内部类编译后.class文件命名是外部类$内部类.class

查看内部类在类中的结构
点击:
在这里插入图片描述
会弹出文件夹:
在这里插入图片描述
src放的是源码。

点击bin->com->people,可以看到:内部类的命名是外部类$内部类
在这里插入图片描述

静态内部类

静态内部类对象可以不依赖于外部类对象,直接创建。

  1. 静态内部类中只能直接访问外部类的静态成员,若想调用非静态成员,可以通过对象实例
  2. 静态内部类对象实例时,可以不依赖于外部类对象
  3. 可以通过外部类.内部类.静态成员的方式,访问内部类中的静态成员
  4. 当内部类属性与外部类属性重名,默认直接调用内部类中的成员;如果要访问外部类中的静态属性,则可通过外部类.属性名 来访问,如果要访问外部类中的非静态属性,则可通过new 外部类().属性名 来访问

获取静态对象内部类对象实例

person.Heart myHeart = new person.Heart();

访问内部类中的静态成员
类:

//外部类
public class person {	
	//静态内部类
	public static class Heart {
		public static int temp=1;		
	}
}

测试类:System.out.println(person.Heart.temp);
输出:1

方法内部类

定义在外部类方法中的内部类,也称局部内部类。

//外部类
public class person {

	public String getHeart() {
		//方法内部类
		class Heart{
			public int age=23;
			
			public void eat() {
				System.out.println("方法内部类的eat");
			}
			
			public String beat() {
				return "心脏跳动";
			}
		}
	
	return new Heart().beat();
	
	}
}

注意,方法内部类里的方法只能在方法内部类里用。

测试类:关于如何调用方法内部类的方法:其结果是方法内部类的返回值。

person a=new person();
System.out.println(a.getHeart());

输出:

心脏跳动

查看方法内部类的命名规则——查看文件结构
对工程名右键,点击show in,一路向下点击得到:
在这里插入图片描述
可以看到,内部类是person$1Heart.class

总结:方法内部类

  1. 定义在方法内部,作用范围也在方法内
  2. 和方法内部成员使用规则一样,class前面不可以添加publicprivateprotectedstatic
  3. 类中不能包含静态成员
  4. 类中可以包含finalabstract成员

匿名内部类

一个Person类:

public abstract class Person {
	private String name;
	
	public Person() {
		
	}

	public String getName() {
		return name;
	}

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

Man类:

public class Man extends Person{

	@Override
	public void read() {
		// TODO Auto-generated method stub
		System.out.println("man read");
	}

}

Women类:

public class Women extends Person{

	@Override
	public void read() {
		// TODO Auto-generated method stub
		System.out.println("women read");
	}
	
}

要求:根据传入的不同的人的类型,调用对应的read方法。

方案一:重载getRead

public class PersonTest {
	//根据传入的不同的人的类型,调用对应的read方法
	
	//方案1:
	public void getRead(Man man) {
		man.read();
	}
	
	public void getRead(Women women) {
		women.read();
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		PersonTest test=new PersonTest();
		Man man=new Man();
		Women women=new Women();
		
		test.getRead(man);
		test.getRead(women);
	}

}

输出:

man read
women read

方案2:传入父类。

//方案2:
	public void getRead(Person p) {
		p.read();
	}

测试输出正确。

方案3:匿名内部类——在实例对象的同时完成对对象内容的编写
这里是没有对象名字也没有实例名字,因此叫做匿名

public class PersonTest {
	// 根据传入的不同的人的类型,调用对应的read方法

	// 方案2:
	public void getRead(Person p) {
		p.read();
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		PersonTest test = new PersonTest();

		// 方案3:匿名内部类——在这里对抽象方法进行重写
		test.getRead(new Person() {

			@Override
			public void read() {
				System.out.println("Man read");
			}
		});

		test.getRead(new Person() {

			@Override
			public void read() {
				System.out.println("women read");
			}
		});

	}

}

输出:

Man read
women read

使用场景

  • 只用到类的一个实例
  • 类在定义后马上用到
  • 给类命名并不会导致代码更容易被理解

匿名内部类编译后的文件结构
先这样:
在这里插入图片描述
再这样:
在这里插入图片描述

可见:匿名内部类的.class文件命名方式和方法内部类的相似,因为编译器无法获得匿名内部类的类名信息,所以只能以$+数字序号的方式命名

总结

  1. 匿名内部类没有类型名称、实例对象名称
  2. 编译后的文件命名:外部类$数字.class
  3. 无法使用privatepublicprotectedabstractstatic等修饰
  4. 无法编写构造方法(因为没有类名)
  5. 可以用构造代码块
  6. 不能出现静态成员
  7. 匿名内部类可以实现接口,也可以继承父类——但不能兼得

总结

接口

  • 接口定义了某一批类所需要遵守的规范
  • 接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类里必须提供某些方法

语法

在这里插入图片描述
注意

  • 接口可以实现多继承,即一个子接口可以同时继承多个父接口
  • 实现接口的类如果不能实现所有接口中待重写的方法,则必须设置为抽象类
  • 一个类可以继承自一个父类,同时实现多个接口

内部类

  • 在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类
  • 与之对应,包含内部类的类被称为外部类

分类

  • 成员内部类
  • 静态内部类
  • 方法内部类
  • 匿名内部类

优势
内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类,更好的实现了信息隐藏

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值