接口是Java语言中的一种引用类型,是方法的"集合",所以接口的内部主要就是定义方法,包含常量,抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(jdk9)。接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。接口的使用,它不能创建对象,但是可以被实现(implements
,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。
定义格式
代码示例
public interface Demo {
// 常量(jdk7及其以前) 使用public static final关键字修饰,这三个关键字都可以省略
public static final int NUM1 = 10;
int NUM2 = 20;
// 抽象方法(jdk7及其以前) 使用public abstract关键字修饰,这2个关键字都可以省略
public abstract void method1();
void method2();
// 默认方法(jdk8) 使用public default关键字修饰,public可以省略,default不可以省略
public default void method3(){
System.out.println("默认方法 method3");
}
// 静态方法(jdk8) 使用public static关键字修饰,public可以省略,static不可以省略
public static void method4(){
System.out.println("静态方法 method4");
}
// 私有方法(jdk9) 使用private关键字修饰,private不可以省略
private static void method5(){
System.out.println("私有静态方法 method5");
}
private void method6(){
System.out.println("私有非静态方法 method6");
}
}
如何实现接口
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字
实现方式:类可以实现一个接口,也可以同时实现多个接口。类实现接口后,必须重写接口中所有的抽象方法,否则该类必须是一个“抽象类”。默认方法可以选择保留,也可以重写。 重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
注意:不能重写静态方法
代码示例
public interface IA {
public void show1();
}
public interface IB {
public void show2();
}
public class Zi implements IA, IB {
public void show1() {
}
public void show2() {
}
}
类可以在“继承一个类”的同时,实现一个、多个接口
public class Fu{}//父类
public interface IA{}//父接口
public interface IB{}//父接口
public class Zi extends Fu implements IA,IB{//一定要先继承,后实现
}
接口中成员访问特点
interface IA {
// 常量
public static final int NUM = 10;
// 抽象方法
public abstract void method1();
// 默认方法
public default void method2(){
//method4();
//method5(); 私有方法: 只能在本接口中调用
System.out.println("IA 接口中的默认方法method2");
}
// 静态方法
public static void method3(){
//method5(); 私有方法: 只能在本接口中调用
System.out.println("IA 接口中的静态方法method3");
}
// 私有方法
private void method4(){
System.out.println("IA 接口中的私有方法method4");
}
private static void method5(){
System.out.println("IA 接口中的私有方法method5");
}
}
class Imp implements IA {
// 重写接口的抽象方法
@Override
public void method1() {
System.out.println("实现类重写IA接口中的抽象方法");
}
// 重写接口的默认方法
@Override
public void method2() {
System.out.println("实现类重写IA接口中的默认方法");
}
}
public class Test {
public static void main(String[] args) {
/*
接口中成员的访问特点:
常量:主要是供接口名直接访问
抽象方法:就是供实现类重写
默认方法:就是供实现类重写或者实现类对象直接调用
静态方法: 只供接口名直接调用
私有方法:只能在本接口中调用
*/
// 访问接口常量
System.out.println(IA.NUM);// 10 推荐
//System.out.println(Imp.NUM);// 10 不推荐 常量被实现类继承了
// 创建实现类对象调用方法
Imp imp = new Imp();
// 访问抽象方法
imp.method1();
// 访问默认方法
imp.method2();
// 接口名访问静态方法
IA.method3();
//Imp.method3();// 编译报错,没有继承
}
}
实现类实现多个接口,其中接口中发生了冲突怎么办?冲突就是说多个父接口拥有相同名字的方法或者属性
interface A{
public static final int NUM1 = 10;
}
interface B{
public static final int NUM1 = 20;
public static final int NUM2 = 30;
}
class Imp implements A,B{
}
public class Test {
public static void main(String[] args) {
/*
公有静态常量的冲突: 如果多个接口中有相同的常量,那么实现类就无法继承
*/
//System.out.println(Imp.NUM1);// 编译报错,无法访问
System.out.println(Imp.NUM2);// 30
}
}
interface A{
public abstract void method();
}
interface B{
public abstract void method();
}
class Imp implements A,B{
@Override
public void method() {
System.out.println("实现类重写");
}
}
public class Test {
public static void main(String[] args) {
/*
公有抽象方法的冲突:实现类只需要重写一个
*/
}
}
interface A{
public default void method(){
System.out.println("A 接口的默认方法method");
}
}
interface B{
public default void method(){
System.out.println("B 接口的默认方法method");
}
}
class Imp implements A,B{
@Override
public void method() {
System.out.println("实现类重写的默认方法");
}
}
public class Test {
public static void main(String[] args) {
/*
公有默认方法的冲突:实现类必须重写一次最终版本
*/
Imp imp = new Imp();
imp.method();
}
}
接口与接口之间的关系
接口可以“继承”自另一个“接口”,而且可以“多继承”。
代码示例
interface IA {//父接口
}
interface IB {//父接口
}
interface IC extends IA, IB {//是“继承”,而且可以“多继承”
}
接口多继承接口的冲突情况,我们应该怎么办?
interface A{
public static final int NUM1 = 10;
}
interface B{
public static final int NUM1 = 20;
public static final int NUM2 = 30;
}
interface C extends A,B{
}
public class Test {
public static void main(String[] args) {
/*
公有静态常量的冲突: 子接口无法继承父接口中冲突的常量
*/
//System.out.println(C.NUM1);// 编译报错,说明无法继承
System.out.println(C.NUM2);// 30
}
}
interface A{
public abstract void method();
}
interface B{
public abstract void method();
}
interface C extends A,B{
}
class Imp implements C{
@Override
public void method() {
System.out.println("实现接口的抽象方法");
}
}
public class Test {
public static void main(String[] args) {
/*
公有抽象方法的冲突:子接口只会继承一个有冲突的抽象方法
*/
Imp imp = new Imp();
imp.method();
}
}
interface A{
public default void method(){
System.out.println("A 接口中的默认方法method");
}
}
interface B{
public default void method(){
System.out.println("B 接口中的默认方法method");
}
}
interface C extends A,B{
@Override
public default void method() {
System.out.println("重写父接口中的method方法");
}
}
class Imp implements C{
}
public class Test {
public static void main(String[] args) {
/*
公有默认方法的冲突:子接口中必须重写一次有冲突的默认方法
*/
Imp imp = new Imp();
imp.method();// 重写父接口中的method方法
}
}
实现类继承父类又实现接口时的冲突
使用场景
额外的功能: 在接口中定义,让实现类实现
共性的功能: 在父类中定义,让子类继承
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。程序中多态: 是指同一方法,对于不同的对象具有不同的实现.
前提条件
多态的体现:
父类的引用指向它的子类的对象
代码示例
class Animal{//父类
public void eat(){
System.out.println("吃东西");
}
}
class Dog extends Animal{//子类Dog
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
class Cat extends Animal{//子类Cat
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}
public class Test1 {
public static void main(String[] args) {
/*
多态: 同一种行为,不同的事物具有不同的表现形态
实现多态:
1.继承或者实现
2.父类引用指向子类对象\接口引用指向实现类对象
3.方法重写
*/
// 父类引用指向子类对象
Animal anl = new Dog();// 多态
anl.eat();// 狗吃骨头...
Animal anl1 = new Cat();
anl1.eat();// 猫吃鱼...
}
}
多态的几种形式
普通父类多态
public class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
}
抽象父类多态
public abstract class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
}
父接口多态
public interface A{}
public class AImp implements A{}
public class Demo{
public static void main(String[] args){
A a = new AImp();
}
}
多态情况下,创建子类对象,去访问成员,到底是访问父类的还是子类的??
成员变量的访问特点
编译看左边,运行看左边。简而言之:多态的情况下,访问的是父类的成员变量
成员方法的访问特点
代码示例
class Animal {
int num = 10;
public void method1() {
System.out.println("Animal 非静态method1方法");
}
public static void method2() {
System.out.println("Animal 静态method2方法");
}
}
class Dog extends Animal {
int num = 20;
public void method1() {
System.out.println("Dog 非静态method1方法");
}
public static void method2() {
System.out.println("Dog 静态method2方法");
}
}
public class Test {
public static void main(String[] args) {
/*
多态时访问成员的特点:
成员变量:编译看父类,运行看父类(编译看左边,运行看左边)
成员方法:
非静态方法:编译看父类,运行看子类(编译看左边,运行看右边)
静态方法: 编译看父类,运行看父类(编译看左边,运行看左边)
结论:除了非静态方法是编译看父类,运行看子类,其余都是看父类
*/
// 父类的引用指向子类的对象
Animal anl = new Dog();
System.out.println(anl.num);// 10
anl.method1();// Dog 非静态method1方法
anl.method2();// Animal 静态method2方法
}
}
多态的应用场景:
多态的好处和弊端
引用类型转换
多态,无法访问子类独有的方法或者成员变量,因为多态成员访问的特点是,编译看父类。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换。不管是向上转型还是向下转型,一定满足父子类关系或者实现关系
向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型。此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了此时,一定是安全的,而且也是自动完成的。例如:
//父类是Animal 子类是Cat
Aniaml anl = new Cat();
向下转型:当左边的变量的类型(子类)<右边对象/变量的类型(父类),我们就称为向下转型。此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了。
Aniaml anl = new Cat();
Cat c = (Cat)anl;//向下转型
不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
返回值:
所以,转换前,我们最好先做一个判断,代码如下:
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
总结一下
class Animal {//父类
public void eat() {
System.out.println("吃东西...");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
// 特有的功能
public void lookHome() {
System.out.println("狗在看家...");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
// 特有的功能
public void catchMouse() {
System.out.println("猫抓老鼠...");
}
}
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
method(d);
System.out.println("==========================");
Cat c = new Cat();
method(c);
}
// 形参多态: 如果父类类型作为方法的形参类型,那么就可以接收该父类类型的对象或者其所有子类的对象
public static void method(Animal anl) {
anl.eat();
//anl.lookHome();// 编译报错
// anl.catchMouse();// 编译报错
if (anl instanceof Dog) {
Dog d = (Dog) anl;// 向下转型 Dog类型
d.lookHome();
}
if (anl instanceof Cat) {
Cat c = (Cat) anl;// 向下转型 Cat类型
c.catchMouse();
}
}
}
注意:
什么是内部类
成员内部类
定义在类中方法外的类就是成员内部类
格式:
在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。比如,汽车类Car
中包含发动机类Engine
,这时,Engine
就可以使用内部类来描述,定义在成员位置。
代码举例:
class Car { //外部类
class Engine { //内部类
}
}
访问特点
创建内部类对象格式:
代码示例
package demo02;
// 外部类
public class Body {
public void methodW1() {
// 访问内部类的成员
Heart bh = new Heart();
System.out.println(bh.numN);// 10
bh.methodN1();// 内部类的成员方法 methodN1
}
// 成员变量
private int numW = 100;
// 成员方法
private void methodW2() {
System.out.println("外部类的成员方法 methodW2");
}
// 内部类
public class Heart {
// 成员变量
int numN = 10;
// 成员方法
public void methodN1() {
System.out.println("内部类的成员方法 methodN1");
}
public void methodN2() {
// 访问外部类的成员
System.out.println(numW);
methodW2();
}
}
}
定义测试类
package demo02;
public class Test {
public static void main(String[] args) {
/*
- 什么是内部类:将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,外面的那个B类则称为外部类。
- 成员内部类的格式:
public class 外部类{
public class 内部类{
}
}
- 成员内部类的访问特点:
在其他类中,访问内部类的成员,得先创建内部类对象:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
在外部类中,访问内部类的成员,得先创建内部类对象:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
内部类名 对象名 = new 内部类名();
在内部类中,可以直接访问外部类的一切成员(包含私有的):
*/
// 创建内部类的对象
Body.Heart bh = new Body().new Heart();
System.out.println(bh.numN);// 10
bh.methodN1();// 内部类的成员方法 methodN1
// 创建外部类对象
Body b = new Body();
b.methodW1();
bh.methodN2();// 100 外部类的成员方法 methodW2
}
}
私有成员内部类
将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用。
静态成员内部类
class Outer {
static class Inner {
public void show(){
System.out.println("inner..show");
}
public static void method(){
System.out.println("inner..method");
}
}
}
public class Test3Innerclass {
/*
静态成员内部类演示
*/
public static void main(String[] args) {
// 外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner oi = new Outer.Inner();
oi.show();
Outer.Inner.method();
}
}
局部内部类
局部内部类使用方式:
匿名内部类
匿名内部类属于局部内部类中的一种。它的本质是一个带具体实现的
父类或者父接口的
匿名的
子类对象。
代码示例
package Demo03;
/*
游泳接口
*/
interface Swimming {
void swim();
}
public class TestSwimming {
public static void main(String[] args) {
//匿名内部类作为参数传递
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
});
}
/**
* 使用接口的方法
*/
public static void goSwimming(Swimming swimming){
swimming.swim();
}
}
实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。他们传递的都是地址值。