1.抽象类
若强制要求子类覆写一些方法,就可以使用抽象类和抽象方法。没有具体方法实现的都应该设计为抽象方法。
在Java中使用abstract关键字定义抽象方法,且抽象方法所在的类一定是抽象类,且抽象方法没有方法体。
PS:没有方法体的方法一定是空方法?-->错,native方法(本地方法)没有方法体只有方法声明。(JVM其实是使用C++实现的,Java允许通过特定的方式来调用C++方法,这些方法就属于本地方法)
抽象类也是使用abstract关键字声明,可以包含0~N种g抽象方法,抽象类无法直接实例化子类,子类继承抽象类必须抽象重写所有抽象类中的抽象方法。
抽象类是普通的超集,普通类优点,抽象类全都有。抽象类中的普通方法、构造方法,抽象类中全都可以存在,简言之,抽象类就是比普通类多了一些抽象方法。
1》抽象类的使用原则:
1> 被abstract定义的类称之为抽象类,抽象类无法直接实例化对象,可以通过向上转型进行实例化。
2> 抽象类中可以没有抽象方法。只有一个类中定义了抽象方法,则这个类一定是抽象方法。子类继承抽象类,必须覆写所有的抽象方法(前提是子类是普通类)。
PS:子类继承抽象类必须覆写所有抽象方法?--->不一定,子类仍是抽象类就可以不覆写任何抽象方法。
PS:可以利用抽象类中的抽象方法强制要求子类覆写方法。
3> abstract和final / private不能同时出现。
PS:abstract能否和关键字final一起出现?-->坚决不能同时出现。
PS:抽象方法能否和私有权限private一起出现?-->坚决不能,私有方法无法覆写,抽象方法必须覆写。
4> 抽象类是普通类的超集,抽象类中仍然可以存在普通方法和构造方法,且子类实例化时满足继承的原则,再调用子类的构造方法。
2》抽象类和抽象方法仍然存在的意义就在于强制要求子类覆写某些方法,但抽象类仍满足is a原则,子类必须是满足继承关系才能继承抽象类,且一个子类只能继承一个抽象类。(单继承局限)
抽象类一般就是一个模板的存在。(模板设计模板就是基于抽象类的设计,Java中的servlet就是抽象类的使用)
2.接口:更加“纯粹”的抽象类
接口的场景:要么表示一种规范(如Type C接口、usb接口),要么表示一种能力 / 行为(随机接口)。
1》接口的定义与使用
1> 在Java中,使用interface关键字来定义接口,接口中只有全局常量和程序方法。
PS:在Java中子类使用implements表示实现一个父接口,必须覆写覆写接口中所有的程序方法。
2> 正因为接口中普遍只存在全局常量和抽象方法,因此,接口中public abstract static final都可以省略不写。(阿里编码规约中也要求,接口中这些关键字都不出现)---->只有接口中可以省略这些关键字,抽象类必须写(public就是default权限)。
3> 接口也无法直接实例化对象,必须通过子类向上转型为其实例化。
4> 一个子类可以同时使用implements关键字来实现多个父接口。(接口的多实现)(开发中的接口优先原则,优先考虑使用接口)(接口表示一种能力和行为)
一般而言,在Java中,除了一些特殊的接口外(USB接口,现实生活中存在的接口),接口一般采用大写的 I 开头,子类使用 impl 结尾表示是一个接口的实现子类。
子类若实现了多个父接口,必须覆写这些父接口中的所有抽象方法。(前提:子类是普通类)eg:
-->
2》接口和类之间的关系:子类同时继承父类和实现接口的顺序。
若有子类同时要继承一个抽象类、实现父接口,要先用extends继承一个父类,然后使用implements来实现接口。--> 要先单继承,再多实现。extends单继承,implements多实现。
接口和接口之间的关系:可以使用extends关键字,接口的继承是多继承的,没有is a原则。
PS:从JDK8开始,接口中也运行存在普通方法,接口中的普通方法使用关键字default关键字来定义,有方法体,子类实现接口之后可以直接使用接口中的普通方法。
--> 只有抽象方法是强制要求子类实现的,普通方法不需要强制实现。
PS:接口中只有全局常量和抽象方法?--> 若没说明版本,这句话严格意义上是错的。应是JDK8之前的都如此,才是对的。
PS:构造方法能被继承吗?--> 构造方法是为了给对象做初始化的,也就是通过构造方法产生一个类的对象,。父类构造方法是为了产生父类的对象,子类到底有没有继承这些方法或属性,最直观的一点就是子类能否直接或间接的调用这些方法或属性,若能用,就是继承了。通过子类对象十五分直接产生父类对象的,不可能也没必要,严格来说,父类的构造方法不能算是子类继承的。
3》JDK中常用的两大接口
1> java.long.Comparable接口,当一个类实现了Comparable接口,就表示当前类具备了可比较的能力。
对于对象数组而言,此时并没有新开辟对象,所有数组的值默认为null。
----> int[ ] data=new int[3]; // 整型数组,每个元素的默认值为0
对于两个不同的student对象而言,有可能按年龄比大小,也有可能按分数比大小,优点场景还需要按姓名比大小,编译器就不知道到底按照哪种属性来比较两Student对象的大小关系了。
对于自定义的类,要想让这个类具备“可比较的能力”,可以实现 java.lang.Comparable接口,覆写其中的compareTo方法。
在JVM看来,它只看compareTo方法的返回值,返回小于0的就认为小,返回大于0的就认为大,返回等于0的就认为相等。-->可以利用这个返回值,当成绩越大,返回-1,排序之后,syudent对象就会跑到前面去,从而实现降序。
PS:调用Array.aort(students)是怎么帮助按照comparableTo方法的返回值将对象调到前面或后面的?
2> java.lang.Clonable接口和深浅拷贝
1)当一个类实现了Clonable接口,这个类就具备了可被“克隆”的能力。克隆指的就是对象B就是根据对象A“复制来”的,这个复制不是简单的创建了两个引用指向同一个对象,确实产生了俩个独立的对象,对象的所有属性和方法都是完全从大学A那里复制而来的,换句话说,对象A、B除了地址不同,其它完全相同,只有的存在称之为对象的“克隆”。
实现克隆之后,什么方法也没写,也不报错。--> JVM只会识别所有带clonable接口的子类,打赏看复制的标记。eg:(这俩个引用指向不同的对象,但是per1是通过per克隆来的,属性都完全相同。)
2)深浅拷贝以及如何实现对象的深拷贝
浅拷贝:对象内容若包含其它类对象,浅拷贝只会复制地址值(只会复制原对象的地址),并不会创建新的对象。
深拷贝:克隆对象内部若包含其它对象的引用,会创建新的对象,将原对象的内容复制过来。
Java中可以通过递归调用clone方法实现深拷贝或者通过序列化(将任何对象变为字符串的过程--> 从字符串中还原原来对象的过程称之为反序列化)的方式来实现深拷贝。