- 注意:本文中会多次使用上一节(第九章:Java多态理解、多态实现、重写、转型和抽象类)中的抽象类
一:接口基本概念
(1)接口是什么
接口:接口就是公共的行为准则,实现时只要符合规范准则,就可以通用。在Java中,接口被看做是多个类的公共规范,是一种引用数据类型
(2)语法规则
语法规则:与定义类的格式一致,只需要将关键字class
换成interface
即可
interface Shape {
void draw();
}
(3)接口使用及其特性
接口的使用:接口不可以直接使用,必须要有一个“实现类”来实现该接口的所有抽象方法,需要借助关键字implements
。注意以下特性
- 接口中的成员变量,默认会被
public static final
修饰 - 接口中的成员方法,默认为抽象方法,也即被
public abstract
修饰 - 接口中的普通成员方法,是不能有具体实现的(在jdk 8之后,加上default后可以有具体实现)
- 接口中可以有静态成员方法
- 接口中不可以有静态代码块和构造方法
- 接口不可以被实例化
- 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是
.class
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
interface Shape {
void draw();
}
class Rectangle implements Shape{
@Override
public void draw(){
System.out.println("画一个矩形");
}
}
class Triangle implements Shape{
@Override
public void draw(){
System.out.println("画一个三角形");
}
}
class Circle implements Shape{
@Override
public void draw(){
System.out.println("画一个三角形");
}
}
public class TestDemo {
public static void drawing(Shape input){
input.draw();
}
public static void main(String[] args) {
drawing(new Circle());
}
}
二:使用多个接口完成多继承
如下是一个多态体系
Animal
为父类(抽象类),内含抽象方法eat()
Dog
和Bird
分别继承自Animal
abstract class Animal{
public String name;
public int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public abstract void eat();
}
class Dog extends Animal{
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name + "正在吃狗粮");
}
}
class Bird extends Animal{
public Bird(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name + "正在吃鸟食");
}
}
public class TestDemo {
public static void eating(Animal animal){
animal.eat();
}
public static void main(String[] args) {
eating(new Dog("旺财", 2));
eating(new Bird("啾啾", 3));
}
}
这里我们加入一个新的抽象方法fly()
,用于描述“飞翔”这种行为。但是这里就会出现一个很难受的地方,所有的动物都会eat()
,但不一定都会fly()
,比如狗就不会飞翔。所以这个抽象方法fly()
放到Animal
中并不合适,那应该怎么解决呢?
(1)Java无法像C++那样直接实现多继承
由于C++中是支持多继承语法的,所以我们可以再建立一个新的类AnimlFly
用于描述某些动物具有的飞翔行为
- 注意:多继承是有很大弊端的,而且会使整个继承体系变得混乱不堪,所以Java中直接取消了这种语法
class Animal{
public:
eat()
private:
name;
age;
}
class AnimalFly(){
public:
fly();
private:
name;
age;
}
class Dog:public Animal{
eat();
}
//多继承
class Bird:public Animal, public AnimalFly{
eat();
fly();
}
但这样的写法在Java中是会报错的
- 纠错:下方的
AnimalSpecial
应该是AnimalFly
(2)接口实现多继承
在Java中,可以用接口完成这样的需求。建立一个接口AnimalFly
,需要这种行为的类重写接口下的方法即可
abstract class Animal{
public String name;
public int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public abstract void eat();
}
interface AnimalFly{
void fly();
}
class Dog extends Animal{
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name + "正在吃狗粮");
}
}
class Bird extends Animal implements AnimalFly{
public Bird(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name + "正在吃鸟食");
}
@Override
public void fly() {
System.out.println(name + "正在天上飞");
}
}
再比如Dog
和Bird
都可以跑,所以可以建立一个接口AnimalRun
abstract class Animal{
public String name;
public int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public abstract void eat();
}
//飞的行为
interface AnimalFly{
void fly();
}
//跑的行为
interface AnimalRun{
void run();
}
class Dog extends Animal implements AnimalRun{
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name + "正在吃狗粮");
}
@Override
public void run(){
System.out.println(name + "正在用四条腿跑路");
}
}
class Bird extends Animal implements AnimalFly, AnimalRun{
public Bird(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name + "正在吃鸟食");
}
@Override
public void fly() {
System.out.println(name + "正在天上飞");
}
@Override
public void run(){
System.out.println(name + "正在用两个脚丫跑路");
}
}
(3)多继承使用
使用时,直接用接口作为形参接受对象即可,也即向上转型
abstract class Animal{
public String name;
public int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public abstract void eat();
}
//飞的行为
interface AnimalFly{
void fly();
}
//跑的行为
interface AnimalRun{
void run();
}
class Dog extends Animal implements AnimalRun{
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name + "正在吃狗粮");
}
@Override
public void run(){
System.out.println(name + "正在用四条腿跑路");
}
}
class Bird extends Animal implements AnimalFly, AnimalRun{
public Bird(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name + "正在吃鸟食");
}
@Override
public void fly() {
System.out.println(name + "正在天上飞");
}
@Override
public void run(){
System.out.println(name + "正在用两个脚丫跑路");
}
}
public class TestDemo {
//通用行为
public static void eating(Animal animal){
animal.eat();
}
//飞翔行为
public static void flying(AnimalFly animalFly){
animalFly.fly();
}
//跑路行为
public static void running(AnimalRun animalRun){
animalRun.run();
}
public static void main(String[] args) {
Dog dog = new Dog("旺财", 2);
Bird bird = new Bird("啾啾", 3);
eating(dog);
eating(bird);
System.out.println("----------------------------");
flying(bird);
System.out.println("----------------------------");
running(dog);
running(bird);
}
}
三:接口之间的继承
在Java中,接口也可以继承一个接口,达到复用的效果。接口之间的继承相当于把多个接口合并在一起
interface A{
void A_method();
}
interface B{
void B_method();
}
interface C extends A, B{
}
class D implements C{
@Override
public void A_method(){
}
@Override
public void B_method(){
}
}
四:接口使用示例
- Java中为我们提供了很多的接口,大大简化了我们编写程序的困难,这里举一个个例子,展示一下它们的基本使用方法
如下是一个Student
类,内含成员变量name
、age
和score
,实例化两个对象student1
和student2
class Student{
public String name;
public int age;
public double score;
public Student(String name, int age, double score){
this.name = name;
this.age = age;
this.score = score;
}
}
public class TestDemo {
public static void main(String[] args) {
Student student1 = new Student("张三", 27, 92.3);
Student student2 = new Student("李四", 25, 96.3);
}
}
对于基本数据类型,例如int a = 10
,int b = 20
的大小比较,Java是很容易做到的。但是对于自定义类型,例如上面的student1
和student2
是不能直接比较的,因为Java对于它们的比较“无从下手”(是比较age
呢还是比较score
呢?)。在C++中可以通过运算符重载达到,但是这个机制会显得代码十分臃肿,比如下面
bool operator==(Date& d1,Date& d2)//运算符重载函数
{
return d1._year==d2.year
&&d1.month==d2.month
&&d3.day=d3.day;
}
而在Java则可以通过相关接口实现。在这里例子中需要借助接口Comparable
,让Student
类实现该接口
其中Comparable<T>
中的<T>
在Java泛型中会讲到,这里在<T>
中填入你想要比较的类型即可,所以我们填入Student
。接着在Student类中重写该接口的抽象方法compareTo
class Student implements Comparable<Student>{
public String name;
public int age;
public double score;
public Student(String name, int age, double score){
this.name = name;
this.age = age;
this.score = score;
}
@Override
public int compareTo(Student student){
if(this.age > student.age){
return 1;
}else if(this.age == student.age){
return 0;
}else{
return -1;
}
}
}
public class TestDemo {
public static void main(String[] args) {
Student student1 = new Student("张三", 27, 92.3);
Student student2 = new Student("李四", 25, 96.3);
if(student1.compareTo(student2) > 0){
System.out.println(student1.name + "的年龄大");
}else if(student1.compareTo(student2) == 0){
System.out.println(student1.name + "和" +student2.name + "的年龄一样的");
}else{
System.out.println(student2.name + "的年龄大");
}
}
}
上面这种方法其实并不好,因为它对类的“入侵”太过严重,在Student
类已经写死了使用age
比较。如果有一天需求换了,需要使用score
比较,那么你又得重新更改
所以为了解决这样的问题,我们要借助Java设计模式中的适配器思想。就这个例子来说,可以实现两个比较器AgeComparator
和ScoreComparator
专门用于age
和score
的比较。由于不在类的内部实现了,所以这里要引入一个新的接口Comparator<T>
它有很多抽象方法,这里我们使用int compare(T o1, T o2)
这个方法。最终代码如下
class Student{
public String name;
public int age;
public double score;
public Student(String name, int age, double score){
this.name = name;
this.age = age;
this.score = score;
}
}
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student student1, Student student2){
return student1.age - student2.age;
}
}
class ScoreComparator implements Comparator<Student>{
@Override
public int compare(Student student1, Student student2){
return (int)(student1.score - student2.score);
}
}
public class TestDemo {
public static void main(String[] args) {
Student student1 = new Student("张三", 27, 92.3);
Student student2 = new Student("李四", 25, 96.3);
//使用age进行比较,那么就建立一个关于age的比较器
AgeComparator ageComparator = new AgeComparator();
int ret = ageComparator.compare(student1, student2);
if(ret > 0){
System.out.println(student1.name + "的年龄大");
}else if(ret == 0){
System.out.println(student1.name + "和" +student2.name + "的年龄一样的");
}else {
System.out.println(student2.name + "的年龄大");
}
System.out.println("--------------------------------------------------------");
//使用score进行比较,那么就建立一个关于score的比较器
ScoreComparator scoreComparator = new ScoreComparator();
int ret1 = scoreComparator.compare(student1, student2);
if(ret1 > 0){
System.out.println(student1.name + "的成绩高");
}else if(ret1 == 0){
System.out.println(student1.name + "和" +student2.name + "的成绩一样");
}else{
System.out.println(student2.name + "的成绩高");
}
}
}
五:Object类
Object类:Java中,Object是所有类的父类,即任何一个类都默认继承Object。换句话说,所有类的对象都可以使用Object的引用来接收
class Person{
String name = "人类";
@Override
public String toString(){
return name;
}
}
class Student{
String name = "学生";
@Override
public String toString(){
return name;
}
}
public class TestDemo {
public static void test(Object o){
System.out.println(o.toString());
}
public static void main(String[] args) {
test(new Person());
test(new Student());
}
}
下面挑选Object比较重要的两个方法进行讲解
(1)equals()方法:对象比较
Java中,如果使用==
对自定义类型进行比较,那么它所比较的只是引用变量的地址是否相同,并不会去看你语义是否相等。比如下面有两个Person
对象,他们的身份证号IdNum
相同,所以按理说应该是相等的,因为是同一个人,但是如果用==
比较你会发现结果是false
class Person{
String IdNum;
public Person(String IdNum){
this.IdNum = IdNum;
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1 = new Person("123456");
Person person2 = new Person("123456");
System.out.println(person1 == person2);
}
}
因此这时必须重写Object类中的equals
方法
class Person{
String IdNum;
public Person(String IdNum){
this.IdNum = IdNum;
}
@Override
public boolean equals(Object obj){
//空对象
if(obj == null){
return false;
}
//自己和自己比较
if(this == obj){
return true;
}
//不是Person对象
if(!(obj instanceof Person)){
return false;
}
Person person = (Person)obj;//向下转型
return this.IdNum.equals(person.IdNum);
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1 = new Person("123456");
Person person2 = new Person("123456");
System.out.println(person1.equals(person2));
}
}
(2)hashcode方法
接上面的例子,调用两个对象的hashcode
方法,可以看到虽然两个对象在语义上是相等的,但是Java认为这两个对象是不一样的
public class TestDemo {
public static void main(String[] args) {
Person person1 = new Person("123456");
Person person2 = new Person("123456");
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());
}
}
所以我们可以重写hashcode
方法,让其哈希值变得一样
class Person{
String IdNum;
public Person(String IdNum){
this.IdNum = IdNum;
}
@Override
public int hashCode(){
return Objects.hash(IdNum);
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1 = new Person("123456");
Person person2 = new Person("123456");
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());
}
}
评论区