一、接口(难点、重点)
1.1 需求
-
声明一个抽象父类Animal,包含public abstract void eat();
-
声明一个子类Bird,继承Animal,
-
重写eat方法
-
新增一个方法:public void fly()
-
-
声明一个Plane,没有继承Animal,也包含一个方法:public void fly()
package com.atguigu.jiekou;
public abstract class Animal {public abstract void eat();
}
package com.atguigu.jiekou;
public class Bird extends Animal{@Overridepublic void eat() {System.out.println("吃虫子");}public void fly(){System.out.println("我要飞的更高.....");}
}
package com.atguigu.jiekou;
public class Plane {public void fly(){System.out.println("我要飞入云霄");}
}
问题1:抽取共同父类
问题:此时Bird类与Plane类有共同的特征,即共同的fly方法,那么按照类的定义(一类具有相同特性的事物的抽象描述),应该把fly这个方法抽取到一个父类中。
package com.atguigu.jiekou;
public abstract class FlyThings {public abstract void fly();
}
问题2:遇到了Java的单继承限制
package com.atguigu.jiekou;
public class Bird extends Animal,FlyThings{ //错误代码@Overridepublic void eat() {System.out.println("吃虫子");}public void fly(){System.out.println("我要飞的更高.....");}
}
解决办法:引入接口
要声明一种与类相似的类型,但是又不会有单继承限制的问题,这样的类型就是接口。
1.2 接口的声明 interface
语法格式:
【权限修饰符】 interface 接口名{【权限修饰符】 abstract 返回值类型 方法名(【形参列表】);
}
package com.atguigu.jiekou;
/*
public abstract class FlyThings {//不合适public abstract void fly();
}
*/
public interface FlyThings {public abstract void fly();
}
package com.atguigu.jiekou;
public interface RunThings {public abstract void run();
}
1.3 类与接口的实现关系 implements
语法格式:
【权限修饰符】 class 类名 extends 父类名 implements 父接口们{}
子类与父类直接只能是单继承。
子类与父接口之间是多实现,一个子类可以同时实现多个接口。
父类:比喻亲爹
父接口们:比喻干爹
package com.atguigu.jiekou;
public class Bird extends Animal implements FlyThings,RunThings{@Overridepublic void eat() {System.out.println("吃虫子");}
@Overridepublic void fly(){System.out.println("我要飞的更高.....");}
@Overridepublic void run() {System.out.println("我也会跑...");}
}
package com.atguigu.jiekou;
public class Plane implements FlyThings{@Overridepublic void fly(){System.out.println("我要飞入云霄");}
}
1.4 抽象类与接口的区别
接口可以多实现,抽象类只能单继承。
接口的关键字:interface,抽象类的关键字:abstract class。
1.5 接口的特点与要求
-
接口也不能直接new对象。
-
类与接口之间支持多实现。
-
接口与接口之间支持多继承。
-
子类(对于接口来说又称为实现类)实现接口时,如果子类不是抽象类,要求子类必须重写或实现父接口的所有的抽象方法。
-
接口的成员有限制:
-
成员变量只能是public static final,称为公共的静态的常量。public static final可以省略。
-
接口没有构造器。
-
JDK8之前,接口中只能有公共的抽象方法,即public abstract。public abstract可以省略。
-
JDK8,接口中又增加了两种非抽象方法
-
公共的静态方法:public static,这里的public可以省略。
-
公共的默认方法:public default,这里的public可以省略。
-
静态方法不允许被重写,默认方法允许实现类进行重写。
-
-
JDK9之后,接口中又增加了一种非抽象方法:
-
私有的方法,可以是静态的,可以是非静态,但是不是默认的,不是抽象的。
-
-
示例代码1:接口的成员
package com.atguigu.jiekou;
public interface MyInter {//public static final 可以省略,完全是多余的//因为接口的成员变量必须是 public static final
// public static final int VALUE = 1;int VALUE = 1;
//public abstract可以省略,完全是多余的//因为接口中的抽象方法,必须是 public abstract
// public abstract void fly();void fly();
//JDK8之后//公共的静态方法1public static void fun1(){print();System.out.println("fun1");}//公共的静态方法2public static void fun2(){print();System.out.println("fun2");}//JDK9之后//私有的静态方法private static void print() {System.out.println("接口的静态方法");}
//JDK8之后//公共的默认方法1public default void method1(){printDefault();System.out.println("method1");}//公共的默认方法2public default void method2(){printDefault();System.out.println("method2");}
//JDK9之后//私有的方法private void printDefault() {System.out.println("接口的默认方法");}
}
示例代码2:接口之间多继承
package com.atguigu.jiekou;
public interface A {public abstract void a();
}
package com.atguigu.jiekou;
public interface B {public abstract void b();
}
package com.atguigu.jiekou;
public interface C extends A,B{public abstract void c();
}
package com.atguigu.jiekou;
public class D implements C{@Overridepublic void a() {
}
@Overridepublic void b() {
}
@Overridepublic void c() {
}
}
1.6 接口与实现类对象构成多态引用
接口类型的变量与实现类对象之间也构成多态引用。这一点与父类变量与子类对象之间构成多态引用是一样的。
package com.atguigu.jiekou;
public class Kite implements FlyThings{@Overridepublic void fly() {System.out.println("我飞的再高,线也在你手里!");}
}
package com.atguigu.jiekou;
public class TestFlyThings {public static void main(String[] args) {
// FlyThings f = new FlyThings();//错误//接口不可以直接new对象
//现在想要调用Bird和Plane等的共同的方法 fly方法FlyThings[] all = new FlyThings[3];all[0] = new Bird();//多态引用。 左边all[0]是父接口FlyThings类型,右边是Bird实现类对象all[1] = new Plane();all[2] = new Kite();
for (int i = 0; i < all.length; i++) {all[i].fly();}}
}
1.7 冲突情况
1.7.1 父类的成员变量与父接口的常量重名
package com.atguigu.chongtu;
public class Base {//父类int a = 1;
}
package com.atguigu.chongtu;
public interface BaseInter {int a = 2;
}
package com.atguigu.chongtu;
public class Sub extends Base implements BaseInter{public void test(){System.out.println("父类的a = " + super.a);System.out.println("父接口的a = " + BaseInter.a);//因为接口中的a是静态的,通过接口名称就可以方法}
}
1.7.2 父接口们的默认方法重名
父接口名.super.默认方法名(); 进行区别
package com.atguigu.chongtu;
public interface One {public default void m(){System.out.println("One.m默认方法");}
}
package com.atguigu.chongtu;
public interface Two {public default void m(){System.out.println("Two.m默认方法");}
}
package com.atguigu.chongtu;
public class Three implements One,Two{@Overridepublic void m() {One.super.m();//接收One父接口的}
}
1.7.3 父类的方法与父接口的默认方法重名
子类默认选择是父类的。
如果子类想要选择父接口的,必须通过 父接口名.super.默认方法名();
package com.atguigu.chongtu;
public class Fu {public void m(){System.out.println("Fu.m普通方法");}
}
package com.atguigu.chongtu;
public interface GanBaBa{public default void m(){System.out.println("GanBaBa.m普通方法");}
}
package com.atguigu.chongtu;
public class Son extends Fu implements GanBaBa{@Overridepublic void m() {super.m();
// GanBaBa.super.m();}
}
package com.atguigu.chongtu;
public class TestSon {public static void main(String[] args) {Son s = new Son();s.m();//Fu.m普通方法}
}
1.8 访问接口的成员
1.8.1 在其他类(例如测试类)中访问接口的成员
1、公共的静态的常量
接口名.静态常量名
2、公共的静态方法
接口名.静态方法名();
3、公共的抽象方法
接口名 变量名 = new 接口实现类(); //必须创建接口实现类的对象
变量名.抽象方法名();
4、公共的默认方法
接口名 变量名 = new 接口实现类(); //必须创建接口实现类的对象
变量名.默认方法名();
//如果实现类没有重写默认方法,仍然执行接口的默认方法体
//如果实现类重写了默认方法,执行重写后的方法体
MyInter接口
package com.atguigu.jiekou;
public interface MyInter {//public static final 可以省略,完全是多余的//因为接口的成员变量必须是 public static final
// public static final int VALUE = 1;int VALUE = 1;
//public abstract可以省略,完全是多余的//因为接口中的抽象方法,必须是 public abstract
// public abstract void fly();void fly();
//JDK8之后//公共的静态方法1public static void fun1(){print();System.out.println("fun1");}//公共的静态方法2public static void fun2(){print();System.out.println("fun2");}
//JDK9之后//私有的静态方法private static void print() {System.out.println("接口的静态方法");}
//JDK8之后//公共的默认方法1public default void method1(){printDefault();System.out.println("method1");}//公共的默认方法2public default void method2(){printDefault();System.out.println("method2");}
//JDK9之后//私有的方法private void printDefault() {System.out.println("接口的默认方法");}
}
MySub实现类
package com.atguigu.jiekou;
public class MySub implements MyInter{@Overridepublic void fly() {System.out.println("重写接口的抽象方法");}
//重写默认方法,default要去掉@Overridepublic void method1() {System.out.println("重写接口的默认方法method1");}
}
测试类
package com.atguigu.jiekou;
public class TestMyInter {public static void main(String[] args) {//访问MyInter接口的公共的静态常量System.out.println("MyInter.VALUE = " + MyInter.VALUE);
//调用MyInter接口的公共的静态方法MyInter.fun1();MyInter.fun2();
//调用MyInter接口的公共的抽象方法//调用MyInter接口的公共的默认方法MyInter my = new MySub();my.fly();//抽象方法my.method1();//默认方法my.method2();//默认方法}
}
1.8.2 在实现类中访问接口的成员
1、公共的静态的常量
情况一:没有重名问题
直接访问接口的公共的静态的常量,不需要加xx.
情况二:重名问题
父类的成员变量与父接口的常量重名了
super.成员变量 代表的是父类的
父接口名.常量 代表的是父接口的
2、公共的静态方法
实现类不会继承父接口的静态,也不能重写父接口的静态方法。
接口名.静态方法();
3、公共的默认方法
情况一:如果实现类没有重写父接口的默认方法或其他重名问题
默认方法名();
情况二:如果实现类重写父接口的默认方法或其他重名问题
父接口名.super.默认方法名();
YourInter父接口
package com.atguigu.jiekou;
public interface YourInter {
public default void method2() {System.out.println("另一个父接口的默认方法method2");}
}
MySub子类/实现类
package com.atguigu.jiekou;
public class MySon implements MyInter,YourInter{@Overridepublic void fly() {System.out.println("重写接口的抽象方法");}
//演示在实现类中,怎么调用或使用接口的各个成员public void test(){//使用父接口的公共的静态常量System.out.println("MyInter父接口的公共的静态常量:" + VALUE);
//使用父接口的公共的静态方法
// fun1();//接口的静态方法不会继承到实现类中,所以无法直接使用MyInter.fun1();MyInter.fun2();
//使用父接口的默认方法method1();
// method2();//调用自己重写的方法MyInter.super.method2();}
@Overridepublic void method2() {System.out.println("实现类重写了父接口的默认方法method2");}
}
测试类
package com.atguigu.jiekou;
public class TestMySon {public static void main(String[] args) {MySon son = new MySon();son.test();}
}
1.9 关于静态方法重写的问题
结论:无论是父类的,还是父接口,静态方法都不能被重写。
父类的静态方法,可以被子类继承,可以通过 父类名.静态方法()(推荐), 子类名.静态方法(), 对象名.静态方法()。
父接口的静态方法,不可以被子类继承,唯一的调用方式:父接口名.静态方法()。
package com.atguigu.ask;
public class Fu {public static void m(){System.out.println("(1)Fu.m静态方法");}
}
package com.atguigu.ask;
public interface GanDie {public static void n(){System.out.println("(2)GanDie.n静态方法");}
}
package com.atguigu.ask;
public class Zi extends Fu implements GanDie{
// @Override //加上它报错,因为它不是重写方法//这里只是Zi类自己又定义了一个同名,同参的方法//这种写法实际开发中,就不应该出现//因为父类的m方法是静态的,表示这个方法是全局共享,包括父类,子类都可以共享//那么子类就不能也不需要再定义一个同样的方法了。/*public static void m(){System.out.println("(3)Zi.m静态方法");}*/
//下面这个方法,更是与接口中的n方法无关。public static void n(){System.out.println("(4)Zi.n静态方法");}
}
package com.atguigu.ask;
public class TestZi {public static void main(String[] args) {Fu f = new Zi();//多态引用f.m();/*假设Zi类可以重写Fu类的m方法,那么就会遵循:编译看左边,运行看右边。执行的结果是(3)但是 执行的结果是(1),说明Zi类中m()方法并未重写(覆盖,覆写)Fu的m方法*/System.out.println("==================");Zi.m();//要么用子类名调用它Zi z = new Zi();z.m();//要么用子类对象调用它
System.out.println("==================");Fu.m();Zi.m();//如果Zi类没有自定定义m()方法,那么它这种方式也可以调用父类的静态方法m
System.out.println("=======================");
GanDie g = new Zi();// g.n();//错误,编译器直接报错,//因为接口的静态方法,不能继承到子类中,唯一调用方式就是 接口名.静态方法GanDie.n();Zi.n();}
}
1.10 接口的应用场景
问题:
-
编写一个数组工具类
-
方法1:可以给所有int[]数组实现排序,从小到大
-
方法2:可以给所有对象数组实现排序,从小到大
-
问题1:对象无法直接比较大小
package com.atguigu.tools;
import java.util.Comparator;
public class MyArrayTools {public static void sort(int[] arr){for(int i=0; i<arr.length-1; i++){for(int j=0; j<arr.length-1-i; j++){if(arr[j] > arr[j+1]){int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}}
/*Object[]类型表示可以接收所有对象数组。*/public static void sort(Object[] arr){for(int i=0; i<arr.length-1; i++){for(int j=0; j<arr.length-1-i; j++){// arr[j]和 arr[j+1]里面存储的都是对象的首地址,首地址是无法比较大小。if(arr[j].xx > arr[j+1].xx){//报错Object temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}}
}
解决:用比较器接口Comparator
Java的核心类库中有一个比较器接口:java.util.Comparator,这个接口有一个抽象方法 int compare(Object o1, Object o2),这个抽象方法用于确定如何比较两个对象的大小。规定:
-
o1 > o2 返回正整数
-
o1 < o2 返回负整数
-
o1 = o2 返回0
自定义数组工具类
package com.atguigu.tools;
import java.util.Comparator;
public class MyArrayTools {public static void sort(int[] arr){for(int i=0; i<arr.length-1; i++){for(int j=0; j<arr.length-1-i; j++){if(arr[j] > arr[j+1]){int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}}
/*Object[]类型表示可以接收所有对象数组。多态的应用:形参1是Object[]数组类型实参可能是String[],可能是Employee[],可能是Rectangle[]等
形参2是Comparator接口类型,而且compare方法是一个抽象方法,没有方法体实参一定 Comparator接口的实现类对象,实现类必须重写compare方法*/public static void sort(Object[] arr, Comparator com){for(int i=0; i<arr.length-1; i++){for(int j=0; j<arr.length-1-i; j++){/*arr[j]和 arr[j+1]里面存储的都是对象的首地址,首地址是无法比较大小。
// if(arr[j].xx > arr[j+1].xx)//(1)我们在写sort方法时,并不能确定 数组元素的运行时类型是什么,所以,无法确定该.xx//(2)也无法进行向下转型,因为不知道该向下转为什么类型//这是是一个通用方法,适用于所有的对象数组的排序方法
使用Comparator接口的compare方法,来比较 arr[j], arr[j+1]元素的大小。int compare(Object o1, Object o2),这个抽象方法用于确定如何比较两个对象的大小。规定:- o1 > o2 返回正整数- o1 < o2 返回负整数- o1 = o2 返回0
com.compare(arr[j], arr[j+1]) >0表示 arr[j] 大于 arr[j+1]*/if(com.compare(arr[j], arr[j+1]) >0){Object temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}}
}
案例1
矩形类
package com.atguigu.tools;
public class Rectangle {private double length;private double width;
public Rectangle() {}
public Rectangle(double length, double width) {this.length = length;this.width = width;}
public double getLength() {return length;}
public void setLength(double length) {this.length = length;}
public double getWidth() {return width;}
public void setWidth(double width) {this.width = width;}
@Overridepublic String toString() {return "Rectangle{" +"length=" + length +", width=" + width +'}';}
}
矩形比较器实现类
package com.atguigu.tools;
import java.util.Comparator;
public class RectangleComparator implements Comparator {/*这里也是多态的应用:形参的类型是Object类型,实参的类型应该是Rectangle类型,因为这个比较器对象用于比较两个矩形对象的length值的大小*/@Overridepublic int compare(Object o1, Object o2) {//在这里要明确,如何比较两个Rectangle矩形对象的大小//这里为了调用Rectangle的getLength方法,怎么办?必须向下转型Rectangle r1 = (Rectangle) o1;Rectangle r2 = (Rectangle) o2;//r1的length 大于 r2的length 返回正整数//r1的length 小于 r2的length 返回负整数//r1的length 等于 r2的length 返回0if(r1.getLength() > r2.getLength()){return 1;}else if(r1.getLength() < r2.getLength()){return -1;}else{return 0;}}
}
矩形数组测试类
package com.atguigu.tools;
public class TestRectangles {public static void main(String[] args) {Rectangle[] rectangles = new Rectangle[5];//随机产生矩形的长和宽的值,范围[0,10)for (int i = 0; i < rectangles.length; i++) {int length = (int)(Math.random()*10);int width = (int)(Math.random()*10);rectangles[i] = new Rectangle(length, width);System.out.println(rectangles[i]);}
System.out.println("排序(按照length值从小到大排序):");RectangleComparator rc = new RectangleComparator();MyArrayTools.sort(rectangles, rc);//排序后,输出结果for (int i = 0; i < rectangles.length; i++) {System.out.println(rectangles[i]);}}
}
案例2
员工类
package com.atguigu.tools;
public class Employee {private int id;private String name;private int age;private double salary;//薪资
public Employee() {}
public Employee(int id, String name, int age, double salary) {this.id = id;this.name = name;this.age = age;this.salary = salary;}
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public double getSalary() {return salary;}
public void setSalary(double salary) {this.salary = salary;}
@Overridepublic String toString() {return "Employee{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", salary=" + salary +'}';}
}
员工比较器实现类
package com.atguigu.tools;
import java.util.Comparator;
public class EmployeeAgeComparator implements Comparator {@Overridepublic int compare(Object o1, Object o2) {Employee e1 = (Employee) o1;Employee e2 = (Employee) o2;return e1.getAge() - e2.getAge();}
}
员工数组测试类
package com.atguigu.tools;
public class TestEmployees {public static void main(String[] args) {Employee[] arr = new Employee[4];arr[0] = new Employee(1,"张三",23,15000);arr[1] = new Employee(2,"李四",21,16000);arr[2] = new Employee(3,"王五",22,12000);arr[3] = new Employee(4,"赵六",24,18000);
for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}
System.out.println("按照年龄从小到大排序:");//按照年龄从小到大排序EmployeeAgeComparator ec = new EmployeeAgeComparator();MyArrayTools.sort(arr,ec );//输出排序后的结果for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}}
}
结论
把众多不相关的类对象的共同行为方法提取出来,构成接口。用接口来管理这些实现类的对象。
例如:
-
Bird(鸟)、Plane(飞机)、Kite(风筝)等共同行为方法void fly() 提前出来 ,构成FlyThings 接口。请看$2.6 小节。
-
Rectangle(矩形)、Employee(员工)等共同行为方法 int compare(对象1,对象2)提前出来,构成Comparator接口。请看$2.10小节。
1.11 代码块(了解)
类的成员: (1)成员变量(重要) (2)构造器(重要) (3)成员方法(重要) (4)代码块(了解)
1、代码块的分类
有两种:
(1)静态代码块
(2)非静态代码块
【修饰符】 class 类名{static{//静态代码块}{//非静态代码块}
}
package com.atguigu.block;
public class Demo {
/* int a = 1;System.out.println("a = " + a);//这些代码不应该在方法外,即方法外面是不能写语句*/
{int a = 1;System.out.println("a = " + a);//代码块中,就和方法体中一样,可以写语句}
}
2、静态代码块(偶尔会用)
(1)静态代码块的作用:为类的静态变量进行初始化。
(2)什么时候执行:在类加载时初始化完最后执行,而且一个类只会被加载1次。
(3)如果有父子类,先加载父类后加载子类。如果父类加载过了,不会重复加载。
示例代码
package com.atguigu.block;
public class DemoA {private static int num ;
static {System.out.println("初始化之前num = " + num);//例如:随机产生[0,100)之间的整数为num赋值num = (int)(Math.random() * 100);//这里这是一个简单的例子,后期咱们为静态变量的赋值可能需要读取配置文件等复杂的代码才能完成初始化,就必须用代码块System.out.println("初始化之后num = " + num);}
public static void fun(){System.out.println("DemoA.fun静态方法");}
}
package com.atguigu.block;
public class TestDemoA {public static void main(String[] args) {DemoA.fun();}
}
示例代码
package com.atguigu.block;
public class DemoB {static {System.out.println("DemoB的静态代码块");}
}
package com.atguigu.block;
public class SubDemoB extends DemoB{static {System.out.println("SubDemoB静态代码块");}
}
package com.atguigu.block;
public class TestSubDemoB {public static void main(String[] args) {DemoB d = new DemoB();System.out.println("===============");SubDemoB s = new SubDemoB();}
}
3、非静态代码块(除了面试题,几乎不用)
(1)非静态代码块的作用:为实例变量初始化。这一点与构造器的作用相同。所以非静态代码块和构造器的代码一定是一起执行的。
(2)执行特点:
-
每次new对象时执行,每new一次,执行1次。
-
非静态代码块比构造器的初始化代码要早执行。
示例代码
package com.atguigu.block;
public class ExampleA {int num;
public ExampleA(){System.out.println("(2)无参构造");}
public ExampleA(int num){System.out.println("(3)有参构造初始化之前:num = " + this.num);this.num = num;System.out.println("(3)有参构造初始化之后:num = " + this.num);}
{System.out.println("(1)非静态代码块,初始化之前num = " + num);num = (int)(Math.random()*100);System.out.println("(1)非静态代码块,初始化之后num = " + num);}
public static void fun(){System.out.println("ExampleA.fun静态方法");}
}
package com.atguigu.block;
public class TestExampleA {public static void main(String[] args) {ExampleA.fun();//不会执行构造器和非静态代码块,因为没有new对象。
System.out.println("================");ExampleA a1 = new ExampleA();//调用无参构造创建对象
System.out.println("=================");ExampleA a2 = new ExampleA(200);}
}
示例代码
-
先完成类的加载,再new对象
-
new对象时,子类的构造器一定会调用父类的构造器。父类的非静态代码块一定和父类的构造器一起执行。子类的非静态代码块与子类的构造器一起执行。
package com.atguigu.block;
public class ExampleB {static {System.out.println("(5)ExampleB父类的静态代码块");}{System.out.println("(1)ExampleB父类的非静态代码块");}
public ExampleB(){System.out.println("(2)ExampleB父类无参构造");}
}
package com.atguigu.block;
public class SubExampleB extends ExampleB{static {System.out.println("(6)SubExampleB子类的静态代码块");}{System.out.println("(3)SubExampleB子类的非静态代码块");}
public SubExampleB(){super();System.out.println("(4)SubExampleB子类无参构造");}
}
package com.atguigu.block;
public class TestSubExampleB {public static void main(String[] args) {SubExampleB b = new SubExampleB();}
}
//5,6,1,2,3,4