Java接口技术

一.概述

上一篇讲了反射技术,它可以在运行时分析类信息,也能分析对象信息,还能在运行时调用任意方法,功能确实很强大,同时也存在着很多的风险,所以在使用的时候一定要多加小心,能直接创建对象来调用方法,就不要使用反射技术。链接:Java反射技术
接下来讲解Java三大特性中多态使用场景最多,体现真正价值的接口技术。它主要用来描述类的能力,而并不给出每个功能的具体实现。
还需要讲解继承层次中抽象类的概念,它属于继承里的一部分以及和接口之间的关系。

二.抽象类

1.概念

抽象类从字面意思就是抽象的类,比如男人更抽象化就是人类。它更加变的通用,抽象类的目的就是要把更通用的方法和实例域抽离出来进行封装,这样抽象类能更加的提高代码的复用性,通过继承抽象类可以实现各自特有的方法实现。

2.使用和说明

像人类,动物类,这种抽象类,实例化一个对象对我们来说是没有任何意义的,所以抽象类规定是不可以进行实例化的。

/**
 * 子类雇员类
 */
public class AbstractEmployee extends AbstractPerson {
	private Double salary;
	private LocalDate hireDay;

	/**
	 * 必须要继承父类的构造器
	 */
	public AbstractEmployee(String name, Double salary, int year, int month, int day) {
		super(name);
		this.salary = salary;
		hireDay = LocalDate.of(year,month,day);
	}
	/**
	 * 实现抽象方法
	 * @return
	 */
	@Override
	public String getDescription() {
		return String.format("an employee with a salary of $%.2f",salary);
	}
}

/**
 * 子类学生
 */
public class AbstractStudent extends AbstractPerson{
	private String majoy;

	public AbstractStudent(String name, String majoy) {
		super(name);
		this.majoy = majoy;
	}

	/**
	 * 实现抽象方法
	 * @return
	 */
	@Override
	public String getDescription() {
		return "a student majoring in " + majoy;
	}
}
/**
 * 抽象测试
 */
public class AbstractDemo {
	public static void main(String[] args) {
		// 通过继承我们知道可以存放子类对象
		AbstractPerson[] people = new AbstractPerson[2];
		people[0] = new AbstractEmployee("Harry",5000.0,1998,11,1);
		people[1] = new AbstractStudent("Maria","computer");

		for (AbstractPerson person : people) {
			// 调用各自类实现的抽象方法(多态)
			System.out.println(person.getName() + "," + person.getDescription());
		}
	}
}

总结:
  • 抽象类可以没有抽象方法,只是说当前类只能由别的类来继承,如果不覆盖抽象类的方法,则默认使用抽象类的方法,否则自行在子类中进行覆盖
  • 存在抽象方法必须是抽象类,因为类中不允许存在没有实现的方法,所以必须要将类声明成抽象类
  • 继承抽象类,可以不实现抽象方法,那么子类也要成为抽象类。 子类必须实现继承的抽象类中的抽象方法。

抽象类和继承并没有很大的区别,只是变的更加抽象,让代码更加具有复用性,但是能将类封装成一个好的抽象类还是需要技术功底的。

三.接口

1.接口概念

接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
实例:生活中常见的接口就是usb接口,或者是手机充电接口。表明如果我们想要使用某种充电接口,就要使用对应接口的充电器,否则不能使用。
接口中所有的方法默认是public,所以在声明接口方法的时候不需要使用权限修饰符。在接口中也可以声明常量,并且只能声明常量,所以接口中声明实例域的时候默认是public static final的修饰符,可以省略。可以理解为没有实例域的抽象类。

/**
 * 接口声明
 */
public interface InterfaceDemo {
	/**
	 * 接口中声明的实例域默认全部是public static final,所以在定义的时候可以省略
	 */
	public static final int NUM = 1;
	/**
	 * 推荐使用
	 */
	int NUM1 = 2;

	/**
	 * 声明接口方法,默认是public的修饰符,可以省略
	 * @return
	 */
	public String getString();

	/**
	 * 推荐使用
	 * @return
	 */
	String getString1();


}

2.实现一个接口

/**
 * 测试实现接口
 * implements 实现接口关键字
 * <>表示范型 后面会讲到 暂时理解为固定类型
 */
public class Employee implements Comparable<Employee> {

	private String name;
	private Double salary;

	public Employee(String name, Double salary) {
		this.name = name;
		this.salary = salary;
	}
	
	@Override
	public String toString() {
		return "Employee{" +
				"name='" + name + '\'' +
				", salary=" + salary +
				'}';
	}

	/**
	 *
	 * @param o
	 * @return
	 */
	@Override
	public int compareTo(Employee o) {
		// 调用Double的静态方法进行比较
		return Double.compare(salary,o.salary);
	}

	public static void main(String[] args) {
		Employee[] employee = new Employee[3];
		employee[0] = new Employee("Carl",1997.0);
		employee[1] = new Employee("Harry",2222.0);
		employee[2] = new Employee("Tony",3333.0);

		Arrays.sort(employee);
		for (Employee employee1 : employee) {
			System.out.println(employee1);
		}
	}
}

这里会有一个疑问?如果不实现接口在类中直接声明方法可以么?这个当然可以,但是例如本例中的compareTo方法,如果不写,我们可以自己进行比较,不能使用Arrays.sort()方法,因为该方法使用了Comparable接口的compareTo方法,所以虚拟机在运行时会检查该方法是否存在,不存在将会抛出异常。所以接口就好比一种约定,如果你想使用我的方法,那么就必须实现特定的接口方法,否则将会抛出异常。并且如果不使用接口,那就要在每个类中实现一个比较方法,在创建对象的时候调用来进行比较,没有达到代码的复用性。

3.接口特性

  • 接口不是类,不能使用new操作符创建对象。
  • 可以声明接口变量,并且引用实现该接口的对象。
  • 可以使用instanceof检查一个对象是否实现了某个特定的接口。
  • 接口可以进行扩展并且可以实现多扩展,也就是多继承。
  • 实现多接口时使用逗号将接口隔开

四.接口与抽象类

区别:
  1. 接口不是类,抽象类是类
  2. 类只能单继承,继承一个抽象类,但是可以实现多个接口。所以接口更加灵活。
  3. 接口不存在实例,抽象类可以存在实例。
  4. 接口仅仅表示一个能力,抽象类是使具体事物更加抽象化。

五.Java8接口新特性

1.静态方法和默认方法

在Java8出来之前在定义一个接口后,需要给出接口的默认实现,会为声明的接口提供一个实现类,并将接口中想要实现的默认方法以静态方法的形式实现,提供调用,例如:Collection/Collections。现在就可以不用提供这样的实现类了,Java8支持接口方法的默认实现。

/**
 * 接口的默认实现
 */
public interface InterfaceDefaultDemo {
	/**
	 * 声明方法 并提供默认实现
	 * default 提供默认实现的关键字
	 * @return
	 */
	default String getString(){
		return "a";
	}
}
接口默认实现的好处:
  • 我们可以不关心默认实现的方法,只关心我们需要覆盖的方法,因为我们不用在实现接口的默认方法。
  • 接口演化:当需要为老接口增加新的方法时,如果不提供新增加方法的默认实现,那么实现老接口的类就会报错,因为必须要实现接口的方法。及时不重新编译原来的类,如果对实现该接口的类使用新增加的方法就会抛出异常。

2.解决默认方法的冲突

情况:如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法。

规则:

(1)超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
(2)接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)形同的方法,必须覆盖这个方法来解决冲突。
解决:demo实例

/**
 * 接口A
 */
public interface InterfaceA {
	default String getString(){
		return "a";
	}
}
/**
 * 接口B
 */
public interface InterfaceB {
	default String getString(){
		return "b";
	}
}
/**
 * 接口c
 */
public interface InterfaceC {
	/**
	 * 不提供默认实现但但是方法相同
	 * @return
	 */
	String getString();
}
/**
 * 作为父类
 */
public class ClassD {
	public  String getString(){
		return "d";
	}
}
/**
 * 定一个抽象类 类E
 */
public abstract class ClassE {
	public abstract String getString();
}
四种情况:
/**
 * 测试实现两个有同样默认方法的接口A,B
 */
public class ClassTestAB implements InterfaceA,InterfaceB{
	/**
	 * 必须实现方法,解决二义性
	 * @return
	 */
	@Override
	public String getString() {
		// 使用接口A的默认实现来解决
		return InterfaceA.super.getString();
	}
}
/**
 * 测试实现两个同名接口但是一个是默认方法,一个不是默认方法
 */
public class ClassTestAC implements InterfaceA,InterfaceC{
	/**
	 * 编译器不知道是否采用默认方法还是要必须实现该方法,所以需要解决二义性
	 * @return
	 */
	@Override
	public String getString() {
		return null;
	}
}
/**
 * 测试继承一个父类和实现一个接口
 */
public class ClassTestAD extends ClassD implements InterfaceA{
	public static void main(String[] args) {
		ClassTestAD a = new ClassTestAD();
		System.out.println(a.getString());
	}
}

/**
 * 测试继承一个抽象类和实现一个接口
 */
public class ClassTestAE extends ClassE implements InterfaceA{
	/**
	 * 类优先原则
	 * @return
	 */
	@Override
	public String getString() {
		return "AE";
	}

	public static void main(String[] args) {
		ClassTestAE s = new ClassTestAE();
		System.out.println(s.getString());
	}
}

类优先的规则可以确保Java7的兼容性。如果为一个接口增加默认方法,这对于有这个默认方法之前能正常工作的代码不会有任何影响。

六.接口实例

1.接口与回调

回调是一种常见的程序设计模式,可以指出某个特定事件发生时应该采取的动作。

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;


/**
 * 接口与回调
 */
public class TimerDemo {
	public static void main(String[] args) {
		ActionListener listener = new TimerPrinter();

		// 每隔10秒中调用一次接口方法然后响一声。
		Timer t = new Timer(10000,listener);
		// 启动定时服务
		t.start();
		// 对话框点击确定后退出程序
		JOptionPane.showMessageDialog(null,"Quit program?");
		System.exit(0);
	}
}

/**
 * 定时器类
 */
class TimerPrinter implements ActionListener{

	/**
	 * 当定时器到时间后所调用的方法
	 * @param e
	 */
	@Override
	public void actionPerformed(ActionEvent e) {
		System.out.println("At the tone, the time is " + new Date());
		// 发出系统铃响
		Toolkit.getDefaultToolkit().beep();
	}
}

2.对象克隆

默认的克隆操作是浅拷贝,并没有克隆对象中引用的其他对象。如果愿对象和浅克隆对象共享的子对象是不可变的,那么这种共享就是安全的。但是通常类中的子对象引用都是我们自定义的可变对象,所以需要实现深拷贝。

import java.util.Date;
import java.util.GregorianCalendar;

/**
 * 克隆接口
 * 这个接口是个标记接口,目的只是告诉此类可以进行克隆,如果没有这个标记则会抛出异常
 * 它唯一的作用是允许在类型查询中使用instanceof
 */
public class Employee1 implements Cloneable{

	private String name;
	private Double salary;
	private Date hireDay;
	
	public static void main(String[] args) {
		// 处理异常,后面会讲
		try {
			Employee1 original = new Employee1("John",400.0);
			original.setHireDay(2000,1,1);
			// 进行深拷贝
			Employee1 copy = original.clone();
			copy.raiseSalary(10);
			copy.setHireDay(2001,1,2);
			
			System.out.println("original = " + original);
			System.out.println("copy = " + copy);
			
		}catch (CloneNotSupportedException e) {
			e.getStackTrace();
		}
	}
	public Employee1(String name, Double salary) {
		this.name = name;
		this.salary = salary;
	}

	/**
	 * 覆盖了Object中的clone()方法
	 * @return
	 * @throws CloneNotSupportedException
	 */
	public Employee1 clone() throws CloneNotSupportedException {
		// 调用父类的克隆方法
		Employee1 cloned = (Employee1)super.clone();
		// 使用date类的克隆方法
		cloned.hireDay = (Date)hireDay.clone();
		return cloned;
	}

	public void setHireDay(int year, int month, int day) {
		Date newHirDay = new GregorianCalendar(year,month - 1,day).getTime();
		hireDay.setTime(newHirDay.getTime());
	}
	public void raiseSalary(double byPercent){
		double raise = this.salary * byPercent / 100;
		this.salary += raise;
	}

}
注意:
  • 默认的clone方法是否满足需求。方法(1)
  • 是否可以在可变的自对象上调用clone来修补默认的clone方法。方法(2)
  • 是否不该使用clone。
解决方法:

(1)实现Cloneable接口。
(2)重新定义clone方法,并指定public访问修饰符。

建议在自己的程序中不要使用标记接口,即使clone是默认实现能够满足, 还是需要实现Cloneable接口,将clone接口定义为public。

public class Employee1 implements Cloneable{

	public Employee1 clone() throws CloneNotSupportedException {
		// 调用父类的克隆方法
		return (Employee1)super.clone();
	}

七.总结:

接口技术也是后面使用最多的技术,封装,继承,多态。这三个特性的基本使用方法,特性,区别暂时就先讲这么多,当然还有更深层次的地方,以后在高级应用,或者对某些点有更深的理解时在做补充。前面这些部分基础很重要。希望大家多理解理解,消化一下。多敲代码。

下一篇讲lambda表达式的基本使用,这是Java8特性中改变最大的地方后面还有一个流操作,这两个技术让我们编写的效率大幅度提高。
有些可能我理解的不够深刻,大家如果觉得我说的不够详细可以参考我的推荐书,详细的看一下。欢迎大家评论。第一时间我会回复大家。谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值