抽象类
定义(作用)
在面向对象的概念中,所有的对象都是通过类来描述的,但是反过来,并不是所有的类都式用来描述对象的,如果一个类中没有包含足够的信息来描述给一个具体的对象,这样的类就是抽象类
语法
abstract method(抽象方法)
【访问权限符】 abstract class 类名{
成员……
}
特性
- 抽象类可以包含普通类所能包含的成员
- 抽象类与普通类最大的区别是:普通类中不能包含抽象方法,抽象类中能包含抽象方法
3.抽象类的方法用abstract修饰的,抽象方法不能有具体的实现;抽象方法不能被static、final、private修饰,因为抽象类存在的最大意义就是必须被继承,如果子类是抽象类,无需重写父类的方法;如果子类是普通类,就必须重写父类的抽象方法
抽象方法的语法:
abstrate [访问限制符] 返回类型 方法名();
4. 抽象类不能实例化对象new
5.如果一个抽象类A继承了抽象类B,此时A中不要求重写B中的抽象方法,但是A继承被普通类继承,就这个普通类就需要重写抽象方法——出来混要还的
//如果一个抽象类Cycle继承了一个抽象类的Shape,这个子类Cycle不要求重写父类Shape的抽象方法
abstract class Cycle extends Shape{
abstract void fun9();
}
//如果这个子类抽象类被普通类继承,就需要重写爷爷和爸爸的抽象方法
class Triangle extends Cycle{
@Override
public void fun4() {
}
@Override
void fun9() {
}
}
//如果一个普通类继承一个继承抽象类的普通类,则也不需重写爷爷的抽象方法
//————爷爷是抽象类,爸爸是普通类,儿子是普通类,儿子无须重写抽象方法
class Rectangle extends Triangle{
}
//一个抽象类能能继承普通类
abstract class A extends Triangle{
}
6.抽象类中可以有构造方法,为了方便子类能够调用,初始化抽象类当中成员变量。
接口
定义(作用)
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在java中,接口可以看出是:多个类的公共(public)规范,是一种引用数据类型。
语法
接口的定义格式与定义类的格式基本相同,将class关键字换成interface关键字,就定义了一个接口。
接口可以理解成统一的"协议",而接口中的属性也属于协议中的内容;但是接口的属性都是公共的
public interface 接口名称{
静态常数变量;
抽象方法;
default修饰的方法;
}
提示:
1、创建接口时,接口的命名一般以大写字母I开头
2、接口的命名一般使用“形容词”词性的单词
特性
- 接口中的成员:
A.成员变量:只能是静态常量——public static final修饰的成员变量,一定要就地赋值
B.成员方法:
1、抽象方法——==【访问权限——满足子类重写父类的规则——子类>=父类】 abstract 返回值 方法名();==不能有具体实现的方法
2、JDK1.8开始,允许接口有具体实现的方法,只能default修饰方法
3、接口中能定义具体的静态方法——static。
-
接口中不能有静态代码块和构造方法
-
接口不能被实例化
-
类和接口之间采用implements来实现多接口
-
如果一个类是普通类应用(implements)一个接口,则必须重写接口中的抽象方法;如果一个类是抽象类应用(implements)一个接口,可以不用重写接口中的抽象方法,但是这个抽象类被普通类继承,仍然需要重写抽象方法。
-
重写接口的抽象方法也必须满足大于接口的访问权限。
-
一个类可以实现多个接口,使用implements逗号隔开。【可以解决多继承的问题】
-
注意:实现接口也相当于继承给子类,这是接口的继承(其实就是应用/实现)
-
IDEA中使用ctrl+i快速实现接口
接口的使用:
package demo;
/**
* Created with IntelliJ IDEA
* Description:
* User:恋恋
* Date:2022-09-16
* Time:8:28
*/
interface USB{
void openDevice();
void closeDevice();
}
//鼠标类,实现USB接口
class Mouse implements USB{
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
public void click(){
System.out.println("鼠标点击");
}
}
//键盘类,实现USB接口
class KeyBoard implements USB{
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
public void inPut(){
System.out.println("键盘输入");
}
}
//笔记本类:使用USB设备
class Computer{
public void power(){
System.out.println("打开笔记本电脑");
}
public void powerOff(){
System.out.println("关闭笔记本电脑");
}
//应用接口的类,其实也是子类
//向上转型
public void useDevice(USB usb){
usb.openDevice();
//向下转型——instanceof——是为了应用子类特殊的方法
if(usb instanceof Mouse){
Mouse mouse=(Mouse) usb;
mouse.click();
}else if(usb instanceof KeyBoard){
KeyBoard keyBoard=(KeyBoard) usb;
keyBoard.inPut();
}
usb.openDevice();
}
}
public class TestDome {
public static void main(String[] args) {
Computer computer=new Computer();
computer.power();
computer.useDevice(new Mouse());
computer.useDevice(new KeyBoard());
computer.powerOff();
}
}
抽象类和接口的区别
【相同】
- 抽象类和接口都可以定义抽象方法(非static、非final、非private修饰)
- 都不能实例化对象
- 抽象类和接口被继承/应用,都要重写抽象方法,若没有重写,则该类必须设置为抽象类
【不同】 - 抽象类是类,接口只是一个引用类型,接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class。
- 成员变量:抽象类都可以;但是接口只能定义静态常量(final)。
- 成员方法:抽象类都可以;但是接口只能定义由default修饰和static修饰具体实现的方法
- 构造方法和静态代码块:抽象类有;接口没有
实现多接口
package demo0;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
/**
* Created with IntelliJ IDEA
* Description:
* User:恋恋
* Date:2022-09-16
* Time:10:16
*/
class Animal{
protected String name;
public Animal(String name){
this.name=name;
}
}
interface IFlying{
//默认的public abstract
void fly();
}
interface IRunning{
void run();
}
interface ISwimming{
void swim();
}
//因为父类Animal的构造方法有参数,因此需重新写;应用接口IRunning,因此重写抽象方法
class cat extends Animal implements IRunning{
public cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name+"正在用四条腿跑");
}
}
class bird extends Animal implements IFlying{
public bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+"正在用翅膀飞");
}
}
//青蛙既能跑,又能跳(两嘻动物)
class Frog extends Animal implements IRunning,ISwimming{
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name+"正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name+"正在蹬腿游泳");
}
}
public class TestDemo {
public static void main(String[] args) {
Frog frog=new Frog("青蛙王子");
frog.run();
frog.swim();
}
}
上面代码展示了java面向对象编程中最常见的用法:一个类继承一个父类,同时实现多种接口
继承表达的含义是is-a,而接口表达的含义是具有XXX特性
接口间的继承
在java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口,到达复用的效果,使用extend关键字。
interface IFlying{
//默认的public abstract
void fly();
}
interface IRunning{
void run();
}
interface ISwimming{
void swim();
}
//两析动物:既能跑,也能游
interface IAmphibious extends IRunning,ISwimming{
}
class Frog implements IAmphibious{
@Override
public void run() {
}
@Override
public void swim() {
}
@Override
public void fun() {
}
}
总结:可以理解为将特性聚集在一起,到达1+1=2的功效。,无论一个接口继承多少个,都需要把抽象方法全部重写,一个不落
接口使用实例
给对象数组排序
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 "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class Testdemo {
public static void main(String[] args) {
Student[] students=new Student[]{
new Student("你好",90),
new Student("直接",100),
new Student("再见",98),
new Student("回见",99),
};//静态初始化---对象数组
Arrays.sort(students);
System.out.println(Arrays.toString(students));//Arrays.toString:将数组转化成字符串打印
}
}
按照之前的理解,数组有一个现成的sort方法,能否直接使用这个方法呢?
我们点击查看其中320的错误
运行错误,抛出异常——ClassCastException: demo.Student cannot be cast to java.lang.Comparable(不能将Student转换为java.lang.Comparable)
思考,不难发现,和普通的整数不一样,两个整数是可以直接比较的,大小关系明确,而两个学生对象的大小关系怎么确定呢?我们知道构成一个对象很少只有一种成员变量,更多是由多种成员变量来修饰对象滴!需要我们额外指定,可以根据姓名、成绩、年龄等多种因素来比较大小
让我们的Student类实现
***Comparable接口***
并实现其中的compateTo方法。
点击查看Comparable接口中:只有一个抽象方法需要重写
package demo;
import java.util.Arrays;
/**
* Created with IntelliJ IDEA
* Description:给对象排序
* User:恋恋
* Date:2022-09-18
* Time:23:08
*/
class Student implements Comparable<Student>{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(Student student) {
if(this.score>student.score){
return 1;
}else if(this.score<student.score){
return -1;
}else{
return 0;
}
}
}
public class TestDemo {
public static void main(String[] args) {
Student[] students=new Student[]{
new Student("你好",90),
new Student("直接",100),
new Student("再见",98),
new Student("回见",99),
};//静态初始化---对象数组
// Arrays.sort(students);
// System.out.println(Arrays.toString(students));
if(students[0].compareTo(students[1])==1){
System.out.println("students[0]>students[1]");
}else if(students[0].compareTo(students[1])==-1){
System.out.println("student[0]<student[1]");
}else{
System.out.println("student[0]==student[1]");
}
}
}
大概了解一下泛型就是将c参数类型(Object类)转换成自己的类型。
总结:当我们自定义类型时,如果要比较大小,一定要具备比较的功能,这样可以选择实现接口Comparable。
但是这个也有缺点:当我们改变需求时,不再比较分数,想根据姓名比较大小?
直接从重写的地方修改了,但是风险很大,如果这个代码已经跑了一个月了,就不好改了。
这里我们再认识一个比较接口Comparator
- 先创建关于比较分数的类应用了
Comparator接口
2.在main方法中创建比较分数的对象,将比较分数的对象,放在sort方法中的第二个参数。
看到这里我相信大家脑袋上面都要一个大大的问哈?
1.为什么sort方法可以放两个参数,我们可以点(ctrl+点击方法)过去查看这个方法
不难发现这个方法是可以有两个参数的——T[] var0, Comparator<? super T> var1
第一个参数是类型为T的数组;第二个参数是Comparator类型的对象(隐藏部分),<泛型>——抽象方法重写的参数类型。
2.查看Compartor接口当中,不止一个抽象方法,为什么只需要实现一个compare方法就可以了
以上就是Comparator的源码,我们可以看到Comparator接口包含很多方法,但是真正需要我们实现的只有第一第二个 ,即:
int compare(T var1, T var2);
boolean equals(Object var1);
但是equals方法是从基类Object继承的,有默认的实现(以地址)。
所以真的需要我们实现的只有方法int compare.
ScoreComparator除了可以放在sort,还可以直接传递参数到ScoreComparator的对象中的compare方法中
除了以上的分数的比较类,还可以自定义关于姓名的比较类,但是姓名的类型是字符串,则又该如何比较呢,然后查看了string类中也有关于比较的字符串的方法,因此我们直接调用姓名成员变量中的comparator方法
==总结一下:==两个比较器,Comparator侵入性比较弱,容易根据自己的需求发生改变;Comparable对类的侵入性比较强。
为了进一步加深对接口的理解,我们可以尝试自己实现一个sort方法来完成刚才的排序过程(使用冒泡排序)
这里为什么用Compareble形式来接收数组,因为让每个元素都可以实现Comparable接口。
并且接收元素的形式也是Comparable类型。
Clonable接口(浅拷贝)和深拷贝
浅拷贝
在java中可以拷贝对象,存储不同的位置,但是成员是相同的。
Object类中存在一个clone方法,调用这个方法可以创建一个对象的“拷贝”,但是要想合法调用clone方法,必须要先实现Clonable接口,否则就会抛出异常。
class Animal implements Cloneable{//标记当前类是可以克隆的
public String name;
public Animal(String name) {
this.name = name;
}
public void work(){
System.out.println(this.name+"工作");
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args)throws CloneNotSupportedException {
Animal animal1=new Animal("Helloword");
Animal animal2=(Animal) animal1.clone();
System.out.println(animal1==animal2);
}
}
总结:自定义类要被克隆需要满足以下几种
- 首先标记当前类可以被克隆
点击查看这个接口,发现这个接口里面什么都没有
说明这个Clonable接口是一个空接口/标记接口
2.有了接口的允许,就可以重写父类中的clone方法
重写的clone()方法
3. 声明异常,就是在调用clone()的方法参数后面加上throws CloneNotSupportedException
4. 最后一步,也是关键一步,必须细心观察这个clone()方法返回的类型是Object类,所以我们需要强制转换成Student类型
为了探究浅拷贝的问题,我们又创建了一个Age类,在Aniaml类中创建了这个Age类的对象,这样每个Aniaml对象都有Age的对象。
class Age{
public int a=10;
}
class Animal implements Cloneable{//标记当前类是可以克隆的
public String name;
Age age=new Age();
public Animal(String name) {
this.name = name;
}
public void work(){
System.out.println(this.name+"工作");
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args)throws CloneNotSupportedException {
Animal animal1=new Animal("Helloword");
Animal animal2=(Animal) animal1.clone();
System.out.println(animal1==animal2);
System.out.println("======修改animal1中Age对象的a之前=====");
System.out.println("aniaml1中的a"+animal1.age.a);
System.out.println("aniaml2中的a"+animal2.age.a);
animal1.age.a=500;//修改
System.out.println("=====修改之后=====");
System.out.println("aniaml1中的a"+animal1.age.a);
System.out.println("aniaml2中的a"+animal2.age.a);
}
}
其中的main方法:
观察到当改变了其中一个Animal类对象中age对象的a时,另一个Aniaml类对象中age对象的a也发生了改变。
总结:Cloneable拷贝出的对象是一份“浅拷贝”,只拷贝了浅层的对象,而深层的对象却没有拷贝,只是让浅层对象中同时都指向了同一个深层对象。
深拷贝
根据上面理解,就是当浅层对象拷贝时,深层的对象也需要拷贝,即此时aniaml中的age对象是不属于同一个对象。即,这样当一个浅层对象的深层对象的成员发生改变时都不会影响到其他浅层对象的深层对象的成员
用画图理解就是:
为了满足深层拷贝,因此需要浅层类的clone()方法中需要重新实现clone方法,深层类也需要重写clone方法。
- 首先重写深层类的clone()
2.其次重新实现浅层的clone()方法
解析第二步:
Animal animal=(Animal) super.clone():这行代码是将此时的对象animal1拷贝一份给栈上临时开辟的animal,因为此时只拷贝了animal1,所以两者Aniaml类对象都同时指向了堆上的Age类对象age——浅拷贝
animal.age=(Age) this.age.clone():这行代码是将当前对象(animal1)所指向的age对象拷贝一份给栈上临时开辟的animal的age——深拷贝
最终将栈上临时开辟的animal返回回去。
这里我想问几个问题看一下你们懂吗?
1、为什么这里第一行代码是用super不用this?
答:刚开始重写代码时,返回的也是super.clone(),
后面重新实现clone()时也是一样的,super.clone()调用Object clone()方法将现在的Animal类对象克隆出去,肯定不能用this啊,因为这是父类(Object类)自带的clone()方法,子类(Animal)原本是没有这个方法的。
而为什么这个又是this呢?
this是谁是this?谁调用clone()方法,谁就是this。this.age.clone():是将当前对象(animal1)中的成员age对象拷贝一份,给在栈上临时创建的aniaml的成员age对象。
2. 为什么这里都需要强行转换
因为这个clone()方法返回的都是Object类,都是父类。