本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、基本概念
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
- 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
- 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
1、接口与类
相似:
- 一个接口可以有多个方法。
- 接口文件保存在.java结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在.class结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
区别:
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有方法必须是抽象方法。Java8以后接口中可以使用default关键字修饰的非抽象方法。
- 接口不能包含成员变量,除了static和final变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
抽象类和接口的区别:
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.
注: 在JDK1.8以后
1、接口中可以有静态方法和方法体。
2、接口允许包含具体实现的方法,该方法称为“默认方法”,默认方法使用default关键字修饰。
在JDK1.9以后允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。
2、特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报a编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
二、语法
声明
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
接口的命名: 一般以大写字母I开头。一般使用“形容词”词性的单词。阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性。
三、使用
1、单个接口
//IAnimal.java
public interface IAnimal {
void eat();
void bark();
}
//Dog.java
public class Dog implements IAnimal {
@Override
public void eat() {
System.out.println("Dog.eat");
}
@Override
public void bark() {
System.out.println("Dog.bark");
}
}
//Test.java
public class Test {
public static void main(String[] args) {
IAnimal animal = new Dog();
animal.bark();
animal.eat();
}
}
2、实现多个接口
//Animal.java
public abstract class Animal {
String breed;
String name;
public Animal(String breed, String name) {
this.breed = breed;
this.name = name;
}
}
//IFly.java
public interface IFly {
void fly();
}
//IRun.java
public interface IRun {
void run();
}
//ISwim.java
public interface ISwim {
void swim();
}
//Cat.java
public class Cat extends Animal implements IRun,ISwim{ //猫既能跑又能游泳
public Cat(String breed, String name) {
super(breed, name);
}
@Override
public void run() {
System.out.println("Cat.run");
}
@Override
public void swim() {
System.out.println("Cat.swim");
}
}
注: 一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。使用ctrl+i可以快速实现接口。
3、接口间的继承
在Java中,类和类之间是单继承的,一个类只有一个父类,类和接口之间可以多实现,一个类实现多个接口,接口和接口之间也可以多继承。所以可以使用接口来达到多继承的目的。 如下:
//两栖动物既能跑又能游泳
public interface IAmphibious extends IRun,ISwim{
}
//青蛙既能跑又能游泳
class Frog implements IAmphibious {
}
接口间继承功能就是将多个接口合并到了一起。
四、实例
//不实现comparable方法,使用sort排序对象会报错
class Student{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "{" +"name='" + name + '\'' + ", score=" + score + '}';
}
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("张三",90),
new Student("李四",96),
new Student("王五",91),
new Student("赵六",79),
};
Arrays.sort(students);
for (int i = 0; i < students.length; i++) {
// println:在打印对象时,会默认调用对象的toString方法
System.out.println(students[i]);
}
}
}
//实现接口Comparable中的compareTo方法,sort正常排序对象
class Student implements Comparable{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "{" +"name='" + name + '\'' + ", score=" + score + '}';
}
//默认规则,Java中所有的类都继承自Object
@Override
//这里的Object就是传入的Student类型的对象
//然后比较当前对象和参数对象的大小关系(我这里实现的是按分数来算)
//1.如果当前对象应排在参数对象之前, 返回小于 0 的数字;
//2.如果当前对象应排在参数对象之后, 返回大于 0 的数字;
//3.如果当前对象和参数对象不分先后, 返回 0;
public int compareTo(Object o) {
Student s = (Student)o;
if (this.score>s.score){
return -1;
}else if (this.score>s.score){
return 1;
}else{
return 0;
}
}
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("张三",90),
new Student("李四",96),
new Student("王五",91),
new Student("赵六",79),
};
Arrays.sort(students); //实现了comparable即可正常排序
for (int i = 0; i < students.length; i++) {
// println:在打印对象时,会默认调用对象的toString方法
System.out.println(students[i]);
}
}
}
五、实用的接口和方法
1、Object类
第五节开始想先介绍下Object类。Object类是Java默认提供的一个类,它是所有类的基类,和所有类都是继承关系,即所有类的对象都可以使用Object的引用进行接收。
示例(使用Object接收所有类的对象):
class Student{}
class Class{}
public class Test {
public static void main(String[] args) {
function(new Class());
function(new Student());
}
public static void function(Object obj) {
System.out.println(obj);
}
}
1.1 使用Object接收接口对象
interface IMessage {
public void getMessage() ;
}
class MessageImpl implements IMessage {
@Override
public String toString() {
return "Test Code 1024" ;
}
public void getMessage() {
System.out.println("Code 1024");
}
}
public class Test {
public static void main(String[] args) {
IMessage msg = new MessageImpl() ; // 子类向父接口转型
Object obj = msg ; // 接口向Obejct转型
System.out.println(obj);
IMessage temp = (IMessage) obj ; // 强制类型转换
temp.getMessage();
}
}
Object真正实现了参数的统一,如果一个类希望接收所有的数据类型,那么就用Object类完成。
1.2 使用Object接收数组对象
public class Test {
public static void main(String[] args) {
Object obj = new int[]{1, 2, 3, 4, 5, 6}; // Object接收数组对象,向上转型
int[] data = (int[]) obj; // 向下转型,需要强转
for (int i : data) {
System.out.print(i + " ");
}
}
}
2、toString()方法
- 在使用对象直接输出的时候,默认输出的是一个地址编码。
- 使用的是
String类,该类对象直接输出的是内容。
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
function(new Student("李华",20));
}
public static void function(Object obj) {
System.out.println(obj.toString());
}
}
toString()方法的源码为:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
默认的toString()方法功能不足时,可以覆写该方法。
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//使用快捷键alt+insert选择toString()方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
function(new Student("李华",20));
}
public static void function(Object obj) {
System.out.println(obj.toString());
}
}
该方法的核心在于获取对象的信息。
3、equals()方法
对比两个对象是否相同,可以使用equals()方法
显然在对比对象的时候不能使用==来判断对象是否相同。因为对象名是引用变量,里面保存的是所指向对象的“地址”,两个不同的对象“地址”显然不同。
示例(使用==判断):
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小明",20);
Student student1 = new Student("小明",20);
System.out.println(student == student1);
}
}
使用
equals()方法:
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小明",20);
Student student1 = new Student("小明",20);
System.out.println(student.equals(student1));
}
}
还是false,咱们来看下
equals()源码:
public boolean equals(Object obj) {
return (this == obj);
}
equals()方法中的实现使用的也是==,本质依然是比较两个引用是否相同。
比较引用类型是否相同的时候,需要重写equals方法。
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
////使用快捷键alt+insert重写方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
name.equals(student.name);
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小明",20);
Student student1 = new Student("小明",20);
System.out.println(student.equals(student1));
}
}
4、hashcode()方法
先看源码:
public native int hashCode();
该方法是一个native方法,底层是由C/C++代码写的。具体内容我们看不到。
示例:
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小明",20);
Student student1 = new Student("小明",20);
System.out.println(student.hashCode());
System.out.println(student1.hashCode());
}
}
将
hashCode()方法进行重写:
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//equals()和hashCode()方法一般一起重写
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
name.equals(student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小明",20);
Student student1 = new Student("小明",20);
System.out.println(student.hashCode());
System.out.println(student1.hashCode());
}
}
可以看出是否重写
hashCode()方法对产生的结果是会产生影响的。
==对象的物理地址跟这个hashcode地址不一样,hashcode代表对象的地址说的是对象在hash表中的位置。==
hashCode()的存在主要是为了查找的快捷性,hashCode()是用来在散列存储结构中。
hashCode()是用于查找使用,应用在散列表中,而equals()是用于比较两个对象的是否相等的。
5、Clonable接口
Clonable是一个很有用的接口。
Object类中存在一个clone方法,调用这个方法可以创建一个对象的 "拷贝"。但是要想合法调用 clone 方法,必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常。
class Student{
public double score = 100;
}
class Stu implements Cloneable{
public Student student = new Student();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Stu stu1 = new Stu();
Stu stu2 = (Stu) stu1.clone();
System.out.println("stu1的成绩:"+stu1.student.score);
System.out.println("stu2的成绩:"+stu2.student.score);
stu2.student.score = 96; //只修改stu2种得score值
System.out.println("==============修改后的成绩===============");
System.out.println("stu1的成绩:"+stu1.student.score+" stu1的hashcode:"+stu1.hashCode());
System.out.println("stu2的成绩:"+stu2.student.score+" stu2的hashcode:"+stu2.hashCode());
}
}
只修改了
stu2的score值,但是stu1的score值也修改了,说明两个score是同一个score。stu1和stu2的hashcode不同,说明clone只拷贝了Stu的对象,但是Stu中的Student对象并没有发生拷贝。在这块就是发生了浅拷贝。
注: clone只能实现浅拷贝,想要实现深拷贝就需要对clone再进行重写。