09-接口和抽象类

子类继承父类,如果多个子类各自行为(方法)和父类行为(方法具体功能)不一样,所以都需要继承后各自重写父类方法。

此时父类方法其实已经没有实现的必要了。在Java中有个概念叫抽象方法的概念:

  1. 抽象方法只声明,没有实现的方法。即没有方法体,是一个不完整的方法。
  2. 定义了抽象方法的类必须是抽象类。
  3. 抽象类不能被实例化(无法创建抽象类的对象),必须被继承,由子类进行实例化。

抽象类

用abstract关键字来修饰一个类时,该类叫做抽象类。

语法:[访问权限] abstract class 类名{ 类体... }

  1. 抽象类必须被继承。--- 抽象方法,类无法自己实例化对象
  2. 无法直接对抽象类进行实例化,必须由子类实例化对象实体。
  3. 子类继承抽象类时,子类必须重写抽象类中的所有的抽象方法。
  4. 如果不想重写父类的抽象方法,子类也必须定义成抽象类

抽象方法

  1. 只声明,没有实现的方法;——即没有方法体,是一个不完整的方法。
  2. 抽象方法必须定义在抽象类或者接口;
  3. 语法
    访问权限 abstract 返回值类型 方法名(参数【可选】); 
    //子类继承抽象父类,如果存在抽象方法,子类必须去重写父类抽象方法。
    //重写原则: 子类的访问权限 不能比 父类方法访问权限 还严格==>public~ 缺省。  private最严格
    
    //声明一个普通方法
    public final static void 方法名(){ 
        //方法体:    if/for / 代码段
    }
    //抽象方法
    public final static abstract void 抽象方法名();    // 声明了一个抽象方法,不完整的方法————抽象方法只有声明,不能有`{ }`
  1. 抽象方法不能被private修饰,即只要影响子类无法重写的因素都不可有。

抽象类的特点

  1. 抽象类和抽象方法必须用abstract修饰。
  2. 抽象类不一定有抽象方法,但有抽象方法的一定是抽象类或者接口;

抽象类不能被直接实例化,只能由子类实例化对象实体。

  1. ——抽象类只能被继承,作为其它类的超类,
  2. 这一点与最终类(final类)正好相反。
  3. final修饰类的特点:不能被继承,不能被重写,与抽象类正好相反
  4. 抽象类的子类:
    • 子类必须重写抽象类中的所有的抽象方法;
    • 如果子类不想实现,那么必须将自已也定义一个抽象类。

例子:水果类Fruit、苹果Apple

/**
 * 当父类的方法,每次都需要被不同的子类进行重写,此时父类方法已经没有实现的必要,只需要声明就好了.
 * 在Java中有个概念叫抽象方法的概念:
 * 抽象方法:
 * 1.抽象方法只声明,没有实现的方法。即没有方法体,是一个不完整的方法。
 * 2.抽象方法的语法:由abstract关键字修饰方法,并且没有{}, 以';'结尾
 * 3.抽象方法必须定义在抽象类或者接口
 * 抽象类:
 * 1.由abstract关键字修饰类;
 * 2.抽象类必须被继承,无法直接对抽象类进行实例化,必须由子类实例化对象实体.
 * 3.子类继承抽象类,必须重写父类中所有的抽象方法
 * 4.如果不想重写父类的抽象方法,子类也必须定义成抽象类
 * @author wyn
 */
abstract class Fruit{ 
	int num;
	String color;
	
	abstract  void eat();
//	public abstract void cycly();//结果周期
	public void cycly1() {//结果周期
		System.out.println("水果从开花到结果需要一定的生长周期");
	}
}
//继承抽象类,子类必须重写父类中所有的抽象方法;
//如果不重写父类的抽象方法,子类也必须定义成抽象类
//abstract class Apple extends Fruit{
class Apple extends Fruit{ 


	@Override
	public void eat() {
		System.out.println("吃苹果要先洗一洗,削皮后,再吃..");
	}
}


//测试类
public class TestAbstract {
	public static void main(String[] args) {
//		Fruit f = new Fruit(); //无法直接对抽象类进行实例化对象实体
		Fruit f = new Apple(); //必须由子类进行实例化对象实体
		Apple a = new Apple(); 
	}
}

什么时候用抽象类?

当一个类总被继承,并且存在某个方法,父类无法去描述它,只能交给子类自己定义时,可以考虑用抽象方法,此时父类为抽象类或者接口。

创建接口类:右击包名--new-- interface

接口

接口(interface)就是方法定义和常量值的集合。接口是公开的抽象类,并且是通过关键字interface定义接口。不能有私有的方法或变量,接口中的所有方法都没有方法体。

——接口是特殊的抽象类,本质上并不是类。

• 通过接口可以实现不相关类的相同行为,而不需要考虑这些类之间的层次关系。

• 通过接口可以指明多个类需要实现的方法。

• 通过接口可以了解对象的交互界面,而不需了解对象所对应的类。

接口的定义

  1. 通过关键字interface修饰
  2. 只有常量,没有变量
  3. 只有抽象方法(JDK1.8之前的版本)
  4. 某些方法是可以有方法体的(JDK1.8版本之后)
    • 由static修饰方法
    • 由default修饰方法
//语法:
[public] interface 接口名 [extends SuperInterfaceList] {
    //1.常量 +
    //2.抽象方法()+
    //3.由static或者default修饰的方法(JDK1.8版本之后)
}

注意:

  • 接口中的方法和常量必须是public的;
  • 一个public接口只能定义在同名的.java文件中;

接口与类的关系

由于接口本质不属于类,所以接口可实现多继承。与类继承不同,一个接口可以继承多个父接口。

//接口的实现: 
class 类名 implements 接口名1,接口名2 { ... }
  1. 用implements子句来表示一个类实现某个接口;
  2. 子类可以实现多个接口,在implements子句中用逗号分隔。
  3. 子类可以是抽象类,但意义不大。
  4. 也可以是具体类,需要重写接口中所有的抽象方法(推荐)
  5. 在子类体中可以使用接口中定义的常量,而且必须实现接口中定义的所有方法。

接口的案例分析:

接口的成员特点

/**
 * 接口:是特殊的抽象类,本质上不算类。是常量和方法定义(抽象方法)的集合。
 * 1.接口是公开的抽象类,并且由interface定义接口;
 * 2.成员变量:只能是常量,并且是静态的公开的;
 * 			默认修饰符:public static final
 * 			建议手动给出;
 * 3.成员方法:1)方法定义(抽象方法):
 * 			     默认修饰符: public abstract;---> 建议手动给出
 * 			2)某些方法是可以有方法体的(JDK1.8版本之后):
 * 			      方法必修由 static修饰 或者 default修饰;
 * 4.构造方法:接口没有构造方法
 * 5.接口本质上不算类,但是同一个.java文件也只能有一个public修饰的接口
 * @author wyn
 *
 */
public interface MyInterface1 {
	int num =100;  //没有权限修饰,默认由public static final修饰
//	int num2; //不能定义变量,必须给定初始值
//	protected int[] arr = {1,2};  权限修饰符只能是public
	public final String SUCC_CODE ="1004";
	public static final String ERR_CODE ="1004";
	
//	protected void looks();
	//Illegal modifier for the interface method look1; only public, 
	//abstract, default, static and strictfp are permitted
	void fly();//方法定义不写时,默认public abstract
	public abstract void fly1();
	
	public static void fun1() {
		System.out.println(num);//在静态方法中可以直接访问,num是静态的
		//num无法修改,默认由final修饰的常量.
//		num=20;//The final field MyInterface1.num cannot be assigned
		System.out.println("JDK1.8及以上版本,由static修饰的方法可以有方法体..");
	}
	public default void fun2() {
		this.fun2();
		System.out.println("JDK1.8及以上版本,由default修饰的方法可以有方法体..");
	}
	
	//构造函数  Interfaces cannot have constructors
//	public MyInterface1() {}
}


interface MyInterface2 {
	int num =100;  //没有权限修饰
}
  1. 接口的成员变量
  2. 只能是常量,并且是静态的公开的;
    • 默认修饰符:public static final
    • 建议手动给出;
  3. 接口的成员方法
  • 1)方法定义(抽象方法):
    • 认修饰符: public abstract;---> 建议手动给出
  1. 2)某些方法是可以有方法体的(JDK1.8版本之后):
    • 方法必修由 static修饰 或者 default修饰;
  2. 接口的构造方法
    • 接口没有构造方法

接口为父接口及简单应用

接口的子接口(interface),可以继承多个接口

  1. 由extends表示继承父接口,且不必重写父接口的抽象方法
/**
 *接口可以实现多继承,即可以继承extends多个接口,由逗号分隔
 */
public interface MyInterface3 extends MyInterface1,MyInterface2 {
}

接口的子类(class),可以实现多个接口

  1. 由implements表示实现接口:可以是具体类,也可以是抽象类(意义不大)。
/**
 * 接口的子类只能由implements关键字表示子类实现接口,且子类可以实现多个接口
 * 1.接口的子类是具体类时:
 * 	1)只能由implements关键字表示子类'实现'接口,且子类可以实现多个接口
 * 	2)必须重写接口中所有的抽象方法;
 * 	3)可以使用父接口中定义的常量;
 * 	4)子类在调用父类的静态方法,必须用[父接口.静态方法名];
 	5)如果在子类中直接使用 静态方法名,会导致无法获取到父接口的静态方法,因为会默认在子类中进行寻找静态方法
 * 2.也可以是抽象类,但意义不大大-->接口是特殊的抽象类,所以抽象类实现接口可以不重写父接口的抽象方法
 * 
 * 注意:子类实现多个接口时,存在同名的
 * 	1)成员变量时,需要用[父接口.]的方式,指定具体调用哪一个;
 * 	2)静态方法时,需要用[接口/类名.静态方法],指定具体调用哪一个;
 * 	  若该静态方法如果在子类中也定义了,它是不算被重写的。
 *	3)default修饰的[方法名和参数]都相同的方法,子类必须重写该方法;
 *
 * @author wyn
 */
//public class MyClass1 extends MyInterface1 {
//The type MyInterface1 cannot be the superclass of MyClass1; a superclass must be a class
public class MyClass1 implements MyInterface2,MyInterface1 {
	int num=5;
	public static void fun1() {
		System.out.println("Myclass的fun1()..");
	}
	@Override
	public void run() {
		System.out.println(SUCC_CODE);
		//子类实现多个接口时,存在同名的成员变量/方法时,需要用'父接口.'的方式,指定具体调用哪一个
		System.out.println(num);
		System.out.println(MyInterface1.num);
		System.out.println(MyInterface2.num);
		//调用父接口的静态方法,必须用'接口名.'的方式;
		//若父接口的静态方法如果在子类中也定义了,它是不算被重写的。
		//fun11();
		MyInterface2.fun1(num);
		MyInterface1.fun1();
		fun1();//本类中可以忽略类名
	}
	@Override
	public void fly() {
	}
	@Override
	public void fly1() {
	}
	//Duplicate default methods named fun2 with the parameters () 
	//and () are inherited from the types MyInterface2 and MyInterface1
	//如果多个父接口存在default修饰的方法名和参数相同的方法,子类必须重写该方法
	@Override
	public void fun2() {
		MyInterface1.super.fun2();
	}
}
//可以是抽象类,可以不重写父接口的抽象方法-->接口是特殊的抽象类
abstract class MyClass2 implements MyInterface1 {
	
}

子类继承父类又实现父接口

  • 注意:当一个类实现一个接口的default方法,同时又继承另一个类的方法,则子类的方法优先和父类方法一致

设计接口的目的
例子1:如果有一个抽象类是鸟类,此时乌鸦和飞机都会飞,但是乌鸦是鸟类,此时飞机也是鸟类的子类吗?

public abstract class Bird {
	String name;
	public Bird(String name) {
		super();
		this.name = name;
	}
	public String getName() {
		return name;
	}
	abstract void fly();
}
class Crow extends Bird {
	@Override
	void fly() {
		System.out.println("乌鸦飞。。。。。。");
	}
}
//class Plane extends Bird  ==> 是bird的子类吗??

例子2:定义一个飞(Fly)的接口,由飞机去实现这个功能.

public interface Fly {
	void fly();
}


class AirPlane implements Fly {
	@Override
	public void fly() {
		System.out.println("我是飞机,我会飞");
	}
}


class Crow implements Fly {
    @Override
	public void fly() {
		System.out.println("我是乌鸦,我会飞");
	}
}

接口设计理念:

  • 接口只关心功能,并不关心功能的具体实现;
  • 接口的思想在于它可以增加很多类都需要的功能,使用相同的接口不一定有继承关系。

java中抽象类和接口的区别是什么?

都是有抽象方法

接口是特殊的抽象类。

接口本质上不属于类, 抽象类本质上还一个类,但是我们会认为抽象类不是一个完整的类(不完整的方法 抽象方法)。

为了让这个父类(是抽象类,但是它没有抽象方法),为了让这个类使用子类实例化出来的对象实体

最后还是 需要引用子类的对象实体。

1.成员区别

抽象类接口
变量、常量、访问权限控制符都不限制(可以是四个访问权限)成员变量只能是public static final修饰的常量 访问权限(只能是public,其他三个访问权限不允许)
构造函数无(因为接口不是类,同时接口没有实现方法(除非是由static/default修饰,构造函数没有这两个修饰词))
抽象和非抽象都可以,并且抽象方法的访问权限符除了private都可以修饰(抽象方法 必须要被子类重写,私有的只允许本类去调用)成员方法抽象方法只能由public abstract修饰;非抽象方法必须由static修饰或者default修饰

2.关系区别

继承关系(extends):拿来就用实现关系(implements):把不完整的方法实现成 具体的
类与类只能单继承,若父类是抽象类,要重写父类中的所有抽象方法无实现关系
类与接口无继承关系可以实现多个父接口,并且要重写所有抽象方法;若多个父接口有同名、同参数的default方法,子类必须重写
如果是抽象类实现接口,意义不大,抽象类可以不用重写接口的抽象方法,(一般都是由具体类去实现接口。重写我们的接口抽象方法,这样的去调用一个具体的方法)
接口与接口可以多继承()无实现关系

3.理念区别

  • 抽象类与接口 都必须被继承
  • 抽象类,被继承体现的是is-a的关系。关心的是类之间的层次关系以及他们的共性功能;
  • 接口,被实现体现的是has-a的关系。关心的是类之间具有的相同功能而不关心他们的从属问题;

抽象类与接口的相同点:

抽象类和接口不能直接实例化,都必须通过其它子类实现才能使用。


内部类(inner)

内部类:在一个类的内部声明的类,称为内部类。内部类编译后也会形成一个单独的class,但它附属于其包含类。

分类

1.定义在类体中的:

  1. 定义内部类:由class定义。
    • 由static修饰,表示静态内部类可以定义静态成员和非静态成员,但不可以访问非静态成员;
    • 无static修饰,表示实例内部类,不可以定义静态的成员,可以访问外部类所有的成员
  2. 内部类可以直接使用其附属的外部类的成员变量和成员方法;
  3. 内部类相当于类的成员变量,可以在类体中或者被其他类使用:
    • 被附属的外部类调用:可以把内部类当作正常类来访问;
    • 在其他类中调用内部类,必须先导包。再根据内部类的分类,即:
    • 静态内部类,直接通过 new 内部类名()的方式创建内部类的对象实体;
    • 实例内部类,通过外部类对象.new 内部类名()的方式创建内部类对象实体。
    • 内部类的实例化对象需要绑定一个外围类的实例化对象,而静态嵌套类的实例化对象不能也无法绑定外围类的实例化对象。
  4. 字节码文件的命名格式:外部类$内部类.class eg: Outer$Inner.class
/**
 * 内部类:在一个类的内部声明的类;内部类编译后会有单独的.class文件,但是附属于包含它的外部类下.
 * 内部类的分类:
 * 1.定义在类体中:
 * 	1)内部类可以直接使用其附属的外部类的成员变量和成员方法;
 *  2)外部类要调用内部类的成员变量和方法,需要把内部类当作正常类来访问;
 *  3)在其他类中调用内部类,可以看作是外部类的成员变量,如果要使用内部类,
 *    必须先导包,再根据内部类的分类,判断是否需要通过 [外部类的对象.new 内部类名()]的方式创建内部类的对象实体.
 * 2.定义在方法中:
 * 	
 * @author wyn
 *
 */
public class TestInner {
	public static void main(String[] args) {
		Outer out = new Outer();
		out.test();
		//内部类在外部类中可以看成是成员变量,所以创建内部类的对象实体为[外部类的对象.new 内部类名()]的方式
//		Inner in2 = new Inner();
		Inner in2 = out.new Inner();
		in2.inNum=55;
		System.out.println("in2.inNum="+in2.inNum);
		out.new Inner().print();
	}
}
//外部类
class Outer{
	int outNum =100;
	static String city="厦门";
	public static void eat() {
		System.out.println("会吃饭...");
	}
	public void test() {
		//System.out.println("Outer.test().."+inNum);
		//外部类使用内部类的实例变量和实例方法,必须创建对象
		Inner in = new Inner();
		System.out.println("Outer.test() 外部类只能通过创建内部类的对象,通过[对象.]的方式访问内部类实例变量:"+in.inNum);
		new Inner().print();
//		System.out.println("外部类使用Inner2中的静态变量:"+school);//不算本类
		System.out.println("外部类使用Inner2中的静态变量通过[类名.]的方式:"+Inner2.school);
		System.out.println("外部类使用Inner2中的静态变量也可以[对象.]的方式:"+new Inner2().school);
	}
	//定义内部类:由class定义,可以由static修饰
	class Inner{
		int inNum =90;
		//除非使用常量表达式初始化,否则不能在非静态内部类型中声明字段类型为静态
		//static String school="厦大";
		public void print() {
			System.out.println("Inner.print() 可以直接打印外部类的实例变量 outNum="+outNum);
			System.out.println("Inner.print() 可以直接打印外部类的静态变量 city="+city);
			eat();
		}
	}
	static class Inner2{
		static String school="厦大";
	}
}

2.定义在方法体中的:

在方体中(代码块{}中),都可以声明:有名内部类(只要在{}中定义都可以)+匿名内部类(一般用于实现抽象方法)

  1. 只能在当前方法中可以直接使用内部类的变量和方法;
  2. 生命周期:随着方法消失而消失
  3. 字节码文件命名格式:
    • 有名内部类:外部类$数字内部类.class
    • 匿名内部类:外部类$数字.class -- 由编译器产生一个数字表示匿名内部类
  4. 好处:如果抽象类/接口只被执行一次,则可以用匿名内部类充当抽象类的子类,进行实例化创建对象,并且重写抽象方法;
/**
 * 内部类定义在方体中(代码块{}中),都可以声明
 * 有名内部类(只要在{}中定义都可以)  +  匿名内部类(一般用于实现抽象方法):
 * 1.在当前方法中可以直接使用内部类的成员,其他方法无法调用到内部类;-->随着方法消失而消失
 * 2.字节码文件命名格式:
 * 	有名内部类:外部类$数字内部类.class
 * 	匿名内部类:外部类$数字.class   --由编译器产生一个数字表示匿名内部类
 * 
 * 3.匿名内部类的好处:
 * 		匿名内部类可以充当抽象类/接口的子类对象实体,并在匿名内部类中实现抽象方法的重写:
 * 		即抽象类不能直接实例化,但是可以通过子类和匿名内部类创建对象实体;
 * 4.定义在方体中的内部类,随着方法消失而消失;
 * @author wyn
 *
 */
public abstract class OutFun {
	int num;
	static String country="China";
	public void fun1() {
		//Inner3 i3= new Inner3();  //无法使用其他方法中定义的内部类
		//匿名外部类,只能使用一次,随着该方法调用完毕,匿名内部类不再被引用时,处于游离状态等待被垃圾回收
		OutFun of = new OutFun() {//匿名内部类  OutFun$1.class
			@Override
			public void fun2() {
				System.out.println("重写抽象方法 fun2()...");
			}
		};
	}
	public abstract void fun2(); //抽象方法
	public void outFun() {
		//有名字的内部类:无法声明静态的变量/方法
		//内部类可以直接使用外部类和其他类的成员
		//只有当前的方法中才能使用内部类的成员,方法外则无法调用到内部类成员
		class Inner3{ //OutFun$1Inner3.class
			int xx=66;
//			static int yy;
//			public static void inFun() {
			public void inFun() {
				System.out.println("outFun()方法中声明的Inner3.inFun()");
				System.out.println("Inner3.inFun()  访问外部类成员变量:num="+num+",country="+country);
				fun1();
				class Inner02{//OutFun$1Inner3$1Inner02.class
					int xx1;	
				}
			}
		}
		Inner3 i3= new Inner3();
		System.out.println("OutFun.outFun() 使用内部类成员 xx="+i3.xx);
		i3.inFun();
	}
}

3.何时使用内部类:

  1. 使用内部类好处: 可以直接使用该包含类的变量和方法。 内部类只能在包含它的类中使用,同时它可以看作是该包含类的一段特殊代码。
  2. 当一个类能直接访问另一个类中的成员时,将第一个类定义为第二个类的内部类。
  3. java中内部类多用在GUI(图形用户界面)部分的事件处理机制中。

面试题:

  1. 一个抽象类如果没有抽象方法,可不可以定义为抽象类?若可以,有什么意义?
    • 可以,意义:为了不让该类创建本类对象实体,只能交给子类去完成
  2. abstract 不能和哪些关键字共存?
    • final、static、private