抽象类
1 为什么要有抽象类?
在编写项目时,会发现类的层次结构很多,(父类还有父类),这个时候发现靠上层的父类,会越来越抽象,即它里面的方法一般都没有办法编写具体的实现代码。
如果按照现有的知识,有返回值的方法又不能不写方法体。如果父类中干脆不写这个方法的话,多态引用时就无法调用子类的方法。
2 抽象类的格式
【修饰符】 abstract class 类名 {
}
3 抽象方法的格式
【修饰符】abstract 返回值类型 方法名(【形参类型】);
注意:抽象方法没有方法体,直接在方法签名后面加;
4.抽象类的特点和要求
(1)抽象类是不能直接创建对象的,即不能实例化。
(2)如果一个类中包含了抽象方法,那么这个类必须声明为抽象类。
(3)如果一个类继承了抽象类,那么它要么也被声明为抽象类,要么就必须重写实现父类中的所有抽象方法。
(4)一个类中也可能没有抽象方法。(这样做的目的是为了防止被创建对象)
(5)抽象类一定有构造器。虽然它自己不能直接new对象,但是创建子类对象时可能会调用父类(抽象类)对父类的成员变量进行初始化。
public abstract class Graphic {
public abstract double getArea();
}
//圆类
public class Circle extends Graphic{
private double radius;
@Override
public double getArea() {
return Math.PI*radius*radius;
}
public Circle(double radius) {
this.radius = radius;
}
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
}
//长方形类
public class Rectangle extends Graphic{
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;
}
@Override
public double getArea() {
return width*length;
}
}
虽然Graphic类是抽象类,不能直接创建它的对象,但是我们可以创建Graphic[]这样的数组对象。
接口
1 什么是接口?
接口是统一的标准规范。两者之间链接的一种方式。
Java程序是需要和其他程序交互的,例如:数据库。数据库有很多品牌:mysql,oracle,sql server,db 2,redis,mangodb....这些数据库内部都不一样,Java程序员想要去链接数据库的化妹妹一个数据库都需要提供不同的API,这就会造成程序员学习成本大大增加,对于jdk开发人员来说,他们也需要额外开发很多套API。
(1)sql语言标准
(2)数据库厂商必须提供一些具体的API。这些API必须有统一的接口。接口由Java提供,数据库厂商来实现这些接口。
2 如何声明接口
【修饰符】 interface 接口名{
}
3 如何实现接口
【修饰符】 class 类名 implement 接口名{
}
//或着
【修饰符】 class 类名 implements 接口名1,接口名2{
}
实现接口的类,叫做实现类。
4 接口的特点
(1)接口不能直接创建对象,就是不能实例化。(它只是一个标准,new它的对象没有意义)。
(2)接口中的成员有哪些?
①公共的静态的常量
(public static final可省略,默认就是这个类型)
②公共的抽象方法
(因为接口是标准,只是说明遵循这个标准的类(产品)中,应该具备什么功能,但是这个功能的具体实现,由产品的具体商家来实现。因为不同的产品,功能的具体事项是不一样的)
③公共的静态方法
其中的public 可以省略,static不能省略。
public static 返回值类型 方法名(【形参列表】){方法体}
在jdk1.8之前,只能有①和②;在jdk1.8之后,接口又增加了两类成员③和④。jdk1.9之后,接口又增加了⑤。
⑤私有方法
为什么要增加静态方法?
因为早期出现了很多成对的API,即类和接口,例如:Path接口和Paths类,Collection接口和Collections工具类。Paths是工具类,它里面全是静态方法,这些静态方法为Path接口以及它们的实现类服务的。那么如果按着之前设计,就会多了很多的.java文件,而我们知道工具类的静态方法调用时不需要对象的,接口也不能创建对象,那么干脆把这些静态方法声明到接口中。这些方法都和接口有关,把这些方法放在接口中更好管理。放在接口中更好管理,遵循高内聚的原则。
④公共的默认方法
public可以省略,default不能省略。
public default 返回值类型 方法名(【形参列表】){方法体}
为什么要增加默认方法?
原因一:
如果某个接口的所有实现类,某个功能的实现代码是一样的,那么每一个类协议跟,有点太冗余了。
原因二(主要):
随着版本的更替,早期的接口出现了一些问题,需要增加功能(方法),按照之前的语法要求,只能增加抽象方法。这就会导致原来所有的接口的实现类全部需要改写,增加实现这个抽象方法,这个工作量太大,那么就改为增加默认方法,哪怕默认方法中是空实现。如果实现类觉得这个接口的默认方法的实现不适合它,实现类可以选择进行重写。 类中重写接口的默认方法时,default要去掉。
jdk1.9之后,接口又增加了一种成员:
⑤私有方法
private不能省略,之前接口中所有成员都是public,因为接口时标准,标注是给所有人遵守的,给大家访问。
为什么要增加私有方法?
因为自从Java允许增加静态方法和默认方法之后,如果静态方法和默认方法有多个之后,特别重载之后出现了很多重复代码,如果按照之前的语法,抽取出来的方法也必须是public,一旦是公共的,外边就可以调用了。但是这个抽取的方法的代码只是别的功能的一部分,不适合给外边调用,所以声明为private更合适的。
现在接口变得与抽象类越来越像?不同的是:
①接口中没有非常量的成员变量
②接口中是没有构造器的
③抽象类支持单继承,接口支持多实现,就是一个类可以实现多个接口。
(3)实现类在实现接口时,必须重写(实现)接口的所有抽象方法,否则只能声明为抽象类。
(4)Java的类是有单继承限制的,Java的接口是支持多实现的。即一个Java类可以同时实现多个接口。
(5)接口可以继承接口,并且支持多继承
【修饰符】 interface 子接口名 extends 父接口名{
}
关系总结: 类与类:继承关系,单继承 类与接口:实现关系,多实现 接口与接口:继承关系,多继承 接口 不能继承或实现类。
5 接口的方法调用
(1)接口的静态(static)方法调用格式
接口名.静态方法
//实现类.接口的静态方法(是错误的)
调用父类的静态方法:
父类.父类的静态方法
子类.父类的静态方法
子类和父类的关系是is-a的关系,比较亲密,可以比喻为父亲和儿子。
实现类与接口的关系是has-a/like-a的关系,没有那么紧密。
(2)接口的默认(default)方法如何调用
实现类的对象.默认方法
父接口的变量(实际存储的是实现类的对象).默认方法
不光
父类 变量 = 子类对象;
属于多态引用。
父接口 变量 = 实现类对象;
也属于多态引用。
关于默认方法调用,运行时执行的是接口中的方法实现还是实现类的方法实现?
如果实现类重写了,就执行重写的代码,否则就执行接口中的默认实现。
6 实现类在实现接口时
(1)当接口的默认方法,没有冲突的情况,实现类可以选择重写或者补充些
(2)当接口的默认方法,有冲突的情况,实现类必须进行重写
冲突:多个继承的接口中具有相同方法名的默认方法(方法名和形参列表都相同)
如果有冲突,如何重写?
①和两个接口完全没有无关的重写
②选择保留其中一个接口的实现
接口名.super.默认方法名
(3)当接口既继承父类,又实现接口,当父类中的默认方法(default)与父接口中的默认方法名相同时,
①默认保留父类的方法;
②也可以对父类方法进行重写;
③如果要保留接口的方法,需要
接口名.super.默认方法名
7 接口中的常量调用格式
接口名.常量对象
//当实现类中有同名变量时,也可以使用这个形式返回到父类中的常量对象。
为什么要设计抽象类,又要设计接口?
(1)抽象类与子类之间是可以表示is-a的关系。Person抽象类,与子类Student,Teacher等,这些子类属于近亲关系。Flyable接口,里面包含fly方法,实现类Bird,Plane,Kite等之间就没有近亲关系,只是具有相同的某个行为特征。
(2)抽象类包含多个子类的共同特征,除了方法之外,还有相同的成员变量,这些成员变量也可以抽取到父类中,抽象类可以包含非常量,而接口是不允许的。
(3)接口的好处就是可以多实现。
API经典接口
1.java.lang.Cloneable接口
当我们需要重写Object类的clone方法时,必须实现的一个接口。Object类的clone()方法:protected Object Clone() throws CloneNotSupportedException 子类重写这个方法时,必须实现Cloneable接口,否则就报CloneNotSupportedException异常。clone()方法可以返回一个新的对象,这个对象中存储的值与源对象一致,但是地址不同。
2.java.lang.Comparable接口
和对象的比较大小有关
java.lang.Comparable接口的抽象方法: int compareTo(Object o) 当this对象 “大于” o对象时,返回正整数 当this对象 “小于” o对象时,返回负整数 当this对象 “等于” o对象时,返回0
我们希望它们有共同的方法:比较大小的方法,只能让它们实现同一个接口。比如:java.lang.Comparable,那么参数列表接受进来的是提升为Object类型的数组,那么我们将他进行类型转换成Comparable类型,就可以使用被我们实现的compareTo方法。
public class MyArrays {
//int[]类型的数组,只能接收int[]类型数组
public static String toString(int[] arr){
String result = "[";
for(int i=0; i<arr.length; i++){
if(i==0){
result += arr[i];
}else{
result += "," + arr[i];
}
}
return result + "]";
}
//Object[]类型的数组,可以接收任意的对象数组
public static String toString(Object[] arr){
String result = "[";
for(int i=0; i<arr.length; i++){
if(i==0){
result += arr[i];
}else{
result += "," + arr[i];
}
}
return result + "]";
}
public static void sort(int[] arr){
for (int i=1; i<arr.length; i++){
for (int j=0; j<arr.length-i; j++){
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] =temp;
}
}
}
}
public static void sort(Object[] arr){
for (int i=1; i<arr.length; i++){
for (int j=0; j<arr.length-i; j++){
//元素arr[j]的编译时类型是Object
//需要先将arr[j]转为Comparable
// if(arr[j] > arr[j+1]){
if(((Comparable)arr[j]).compareTo(arr[j+1])>0){
//不管任何类型的对象,只要实现了Comparable接口和这个方法,就可以使用这个方法
//防止出现类型转换失败问题
Object temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
3.java.lang.Comparator接口
用于比较大小的接口,当我们需要给连个对象比较大小时,就可以给这个对象的类型实现java.lang.Comparable接口,重写int compareTo(T o)方法。
但是,有时候,要比较的对象的类并不是我们自己写的,是第三方提供,而且没有给我们实现Comparable接口。那么这个时候我们只能借助Java.util.Comparator接口来进行补救。或者当我们项目中同时有多个需求,例如学生类,在一个位置是要求按照成绩排序,在另一个位置要求按照年龄排序,第三个位置按照姓名排序。我们只能在学生类中实现Comparator接口,按照成绩排序,在另一个地方单独再写一个类实现Comparable接口,在第三个地方再写一个类实现Comparable接口。(可以在不同类中实现Comparable接口/外比较器,但是Comparator接口只能给实体类实现/内比较器)
java.lang.Comparator这个接口中有一个抽象方法int compare(T o1,T o2),返回值类型也是int ,当o1 “大于” o2时,返回正整数 当o1 “小于” o2时,返回负整数 当o1 “等于”o2时,返回0
public class TestComparator {
public static void main(String[] args) {
Teacher[] arr = new Teacher[2];
arr[0] = new Teacher("张三",12000);
arr[1] = new Teacher("李四",11000);
//排序:
// MyArrays2.sort(arr);//报错:ClassCastException类型转换异常
//因为Teacher没有实现java.lang.Comparable接口,不能转为这个类型
// 先创建一个Comparator的实现类对象
TeacherComparator tc = new TeacherComparator();
MyArrays2.sort(arr , tc);
//遍历数组
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
//为Teacher类的对象比较,写一个Comparator接口的实现类
class TeacherComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
//假设两个Teacher对象按照薪资比较大小
//先将o1,o2向下转为Teacher
Teacher t1 = (Teacher) o1;
Teacher t2 = (Teacher) o2;
/* if(t1.getSalary() > t2.getSalary()){
return 1;
}else if(t1.getSalary() < t2.getSalary()){
return -1;
}
return 0;*/
return Double.compare(t1.getSalary(), t2.getSalary());
}
}
//假设Teacher类的代码不能修改了。他没有实现java.lang.Comparable接口,
class Teacher{
private String name;
private double salary;
public Teacher() {
}
public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
public class MyArrays2 {
public static void sort(Object[] arr){
for (int i=1; i<arr.length; i++){
for (int j=0; j<arr.length-i; j++){
//元素arr[j]的编译时类型是Object
//需要先将arr[j]转为Comparable
if(((Comparable)arr[j]).compareTo(arr[j+1])>0){// if(arr[j] > arr[j+1]){
Object temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
public static void sort(Object[] arr, Comparator com){
for (int i=1; i<arr.length; i++){
for (int j=0; j<arr.length-i; j++){
//元素arr[j]的编译时类型是Object
//需要先将arr[j]转为Comparable
if(com.compare(arr[j], arr[j+1]) >0){
Object temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}