Java–抽象类与接口
一.抽象类:
1.定义:
如果一个类没有足够的信息去描述一个具体的对象,那么这样的类就叫做抽象类
2.抽象方法的语法:
public abstract class Shape{//抽象类
abstract public void draw();//抽象方法
abstract void color();//抽象方法
public double getArea(){//可以使用普通方法,因为抽象类也是类
return area;
}
}
抽象类:被abstract修饰的类
抽象方法:被abstract修饰的方法,没有方法体
3.抽象类特征:
(1)使用abstract修饰的类都是抽象类
(2)使用abstract修饰的方法都是抽象方法
(3)抽象类是不可以进行实例化的
(4)当一个普通类继承了这个抽象类,那么在这个普通类当中就要把抽象类当中的所有抽象方法全部重写
(5)抽象类可以和普通类一样定义成员方法和变量
(6)抽象类的出现就是为了被继承
(7)abstract和final是天敌,不可以同时出现
(8)抽象方法不能被private和static修饰
二.接口:
1.接口的概念:
在Java当中,接口可以看作是:多个类的公共规范,是一种引用数据类型
2.接口的语法规则:
public interface 接口的名称{
public abstract void method1();//这里的public abstract是固定的,可以省略不写
void method2();//这样写方法是不会报错的
}
3.接口的特征:
(1)接口是用interface来修饰的
(2)接口中不能有被实现的方法,也就是说只能有抽象方法。除了static和default修饰方法可以被实现
(3)接口当中的方法默认使用public abstract进行修饰
(4)接口当中的成员默认是public static final进行修饰的,所以要在定义变量的时候就对其初始化,并且变量名是要大写的
(5)接口不能进行实例化
(6)类和接口之间的关系,可以使用implements来进行关联
(7)接口也是有对应的字节码文件
4.接口的使用:
注意:接口不能直接被使用,必须要有一个实现类来实现该接口,实现接口中所有的抽象方法
例子:请实现笔记本电脑使用USB鼠标、USB键盘的例子
-
USB接口:包含打开设备、关闭设备功能
-
笔记本类:包含开机功能、关机功能、使用USB设备功能
-
鼠标类:实现USB接口,并具备点击功能
-
键盘类:实现USB接口,并具备输入功能
interface USB{ void openDevice(); void closeDevice(); } class Mouse implements USB{ public void openDevice(){ System.out.println("打开鼠标"); } public void closeDevice(){ System.out.println("关闭鼠标"); } public void click(){ System.out.println("鼠标点击"); } } class Keyboard implements USB{ public void openDevice(){ System.out.println("打开键盘"); } public void closeDevice(){ System.out.println("关闭键盘"); } public void inPut(){ System.out.println("键盘输入"); } } class Computer{ public void powerOn(){ System.out.println("打开电脑"); } public void powerOff(){ System.out.println("关闭电脑"); } public void useDevice(USB usb){ usb.openDevice(); if(usb instanceof Mouse){//下面解析 Mouse mouse=(Mouse)usb; mouse.click(); }else if(usb instanceof Keyboard){ Keyboard keyboard=(Keyboard)usb; keyboard.inPut(); } } } public class Test1 { public static void main(String[] args) { Computer computer=new Computer(); computer.powerOn(); computer.useDevice(new Mouse()); computer.useDevice(new Keyboard()); computer.powerOff(); } }
这串代码中instanceof是Java的一个保留关键字,其左边是对象,右边是类,返回值是boolean类
作用是:测试左边对象是否是右边类的实例对象
注意:一般在强转的时候用instanceof进行判断。先有继承,再有instanceof的使用
另外:computer.useDevice(new Mouse()) 和computer.useDevice(new Keyboard());其实是简写,正式的版本应该这么写:
Mouse mouse= new Mouse();
computer.useDevice(mouse);
Keyboard keyboard=new Keyboard();
computer.useDevice(keyboard);
那么这时肯定会有人有疑问了:
public void useDevice(USB usb){
usb.openDevice();
if(usb instanceof Mouse){
Mouse mouse=(Mouse)usb;
mouse.click();
}else if(usb instanceof Keyboard){
Keyboard keyboard=(Keyboard)usb;
keyboard.inPut();
}
}
}
这串代码的第一行的括号里为什么会写USB usb,其实这个可以理解成电脑的插槽,如果没有插槽,那么鼠标和键盘就不会运行,然后进行下面的判断,判断这个插槽是鼠标的类型还是键盘的类型,如果是鼠标的类型,就要把usb强转为Mouse类。如果是键盘类,那么要将usb强转为Keyboard类。这样才可以调用鼠标类独有的click()方法,或者是键盘类独有的input()方法
5.实现多个接口:
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java不支持多继承,但是一个类可以实现多个接口
class Animals{
String name;
public Animals(String name){
this.name=name;
}
}
interface IFlying{
void fly();
}
interface IRunning{
void run();
}
interface ISwimming{
void swim();
}
class Cat extends Animals implements IRunning{
public Cat(String name){
super(name);
}
public void run(){
System.out.println(this.name+"正在用四只腿跑");
}
}
class Fish extends Animals implements ISwimming{
public Fish(String name){
super(name);
}
public void swim(){
System.out.println(this.name+"正在用尾巴游泳");
}
}
class Durk extends Animals implements ISwimming,IRunning,IFlying{
public Durk(String name){
super(name);
}
public void swim(){
System.out.println(this.name+"正在游泳");
}
public void run(){
System.out.println(this.name+"正在跑步");
}
public void fly(){
System.out.println(this.name+"正在飞");
}
}
public class Test2 {
public static void Swimming(ISwimming iswimming){
iswimming.swim();
}
public static void Running(IRunning irunning){
irunning.run();
}
public static void Flying(IFlying iflying){
iflying.fly();
}
public static void main(String[] args) {
Swimming(new Durk("汤汤"));
Swimming(new Fish("凉凉"));
Running(new Cat("花花"));
Running(new Durk("汤汤"));
Flying(new Durk("汤汤"));
}
}
这个地方就是使用向上转型的方法把每一个对应的动物进行分类打印
从各个动物类的写法可以看到,一个类是可以有多个接口的,但是不能继承多个类,因此,可以使用接口来弥补不能使用多继承多缺点
Test2类里的写法就非常灵活,使用了灵活的向上转型方法,可以让动物精准的进入对应的动作里,无论以后加多少个动物需求,都可以少量的修改代码,实现需求。
6.接口间的继承:
接口与接口之间是可以多继承的
接口可以继承一个接口,达到复用多效果,使用extends关键字
interface IRunning{
void run();
}
interface ISwimming{
void swim();
}
interface IFix extends IRunning,ISwimming{
}
class Frog implements IFix{
...//这里要把run和swim方法改写
}
7.接口使用实例:
(1)直接进行比较
class Student implements Comparable<Student>{
public String name;
public int score;
public Student(String name,int score){
this.name=name;
this.score=score;
}
public int compareTo(Student o){
if(this.score>o.score){
return 1;
}else if(this.score<o.score){
return -1;
}else{
return 0;
}
}
}
public class Text3 {
public static void main(String[] args) {
Student student1=new Student("小明",90);
Student student2=new Student("李华",60);
System.out.println(student1.compareTo(student2));
}
}
这里就是使用Compara接口,来实现compareTo方法
想必大家对 System.out.println(student1.compareTo(student2));这句代码很疑惑,其实这句代码是在调用compareTo方法,这里的student2就是compareTo方法里的compare o。这里的student1其实就是compareTo方法里的this.xxxxxx。在main方法里只是使用student1.compareTo(student2)这样的形式,来对其调用compareTo方法做准备
(2)比较器
import java.util.Comparator;
class Students{
public String name;
public int age;
public Students(String name,int age){
this.name=name;
this.age=age;
}
}
class AgeComparator implements Comparator<Students> {
public int compare(Students o1,Students o2){
return o1.age-o2.age;
}
}
class NameComparator implements Comparator<Students>{
public int compare(Students o1,Students o2){
return o1.name.compareTo(o2.name);
}
}
public class Test4 {
public static void main(String[] args) {
Students student1=new Students("zz",20);
Students student2=new Students("hh",12);
AgeComparator ageComparator=new AgeComparator();
System.out.println(ageComparator.compare(student1,student2));
NameComparator nameComparator=new NameComparator();
System.out.println(nameComparator.compare(student1,student2));
}
}
这里的comparator就是比较器,在AgeComparator和NameComparator类里面都使用Comparator这个接口
注意在使用comparator接口时,类里面要对compare进行重写,就如同上面代码的方式,如果进行数字的比较,就只需o1.age-o2.age即可,如果进行名字之间进行比较,那么就要使用o1.name.compareTo(o2.name)的形式进行比较
在最后的main方法里面一定不要忘记对类进行实例化,在打印时可以直接调用xxx.compare(student1,student2)进行打印
通过上面两种比较的写法,可以看出,使用比较器的写法会更灵活多变些。
8.cloneable接口和深浅拷贝:
(1)cloneable接口
从这张图可以看到,如果什么都不写,直接用person.是没有想要的对象的,因此如果想实现person2克隆person那么就要加上应有的克隆格式
那么就要在Person方法里写一份克隆,其实这个是可以让编译器自动生成的,方法如下:
右击鼠标----找到generate----找到override method----找到clone----点击OK即可生成克隆
那么这时还是报错,原因是自动生成的clone()里throws CloneNotSupportedException可能会抛出异常,因此我们要在main方法后面加上相同的东西
这是会发现依然报错,因为clone方法的返回值是object,那么Person person2=person.clone();就相当于把父类给子类,那么一定会报错,因此要在这里进行强转才可以正常运行
但是当我们打印的时候,就会发现系统进行了报错
其实这个问题是很好解决的,只要在最开始的才class Person后面加上implements cloneable即可
这样再运行,就不会出错了
整体代码如下:
class Person implements Cloneable{
public String name;
public int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test5 {
public static void main(String[] args)throws CloneNotSupportedException {
Person person=new Person("张三",12);
Person person2=(Person)person.clone();
System.out.println(person);
System.out.println(person2);
}
}
(2)浅拷贝:
最后的结果是19.9 19.9 99.99 99.9
下面使用画图的方法进行演示上面图片中代码的运行过程:
这其实就是浅拷贝的基本原理,两个属性指向同一个数字,如果这个m更改,那么money的值也会随之更改,因为浅拷贝里的money虽然被拷贝,但是它的地址是不变的
(3)深拷贝
就上面的代码进行延伸
class Money implements Cloneable{
public double m=20;
}
class Person implements Cloneable{
public String name;
public int age;
public Money money=new Money();
public Person(String name,int age){
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp=(Person)super.clone();//一定要强转才可以,不然会报错
tmp.money=(Money)this.money.clone();
return tmp;
}
}
public class Test5 {
public static void main(String[] args)throws CloneNotSupportedException {
Person person=new Person("张三",12);
Person person2=(Person)person.clone();
System.out.println(person.money.m);
System.out.println(person2.money.m);
System.out.println("===============");
person.money.m=99;
System.out.println("Person"+person.money.m);
System.out.println("Person2"+person2.money.m);
}
}
区别就在与对克隆方法里的内容进行了修改,此时Person tmp=(Person)super.clone();的意思就是在克隆一份整体的name,age,money,而tmp.money=(Money)this.money.clone();的意思就是克隆m的值。
注意:克隆会改变地址,但是不会改变被克隆变量的内容
具体过程:
最后tmp会消失,从而把所有内容交给person2