JAVA——抽象类、内部类和接口
一、抽象类
如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类可以包含抽象方法与普通方法和属性。
在Java中,一个类如果被abstract修饰称为抽象类,抽象类中被abstract修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
//abstract修饰的类是抽象类 public abstract class Animal { String name; int age;//可以包含普通属性与普通方法 public void func(){ } //abstract修饰的方法是抽象方法,没有方法体,不用给出具体实现 public abstract void bark(); } class Dog extends Animal{ @Override public void bark() { //子类继承了抽象类,则一定要重写其中的抽象方法,否则子类也是抽象类 System.out.println(this.name + "正在汪汪叫"); } }- 抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
- 抽象类不能直接实例化对象
- 抽象方法不能是private,且不能被final和static修饰,因为抽象方法要被子类重写
- 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用abstract修饰
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法,抽象类的作用相当于多了一重编译器的校验
二、接口
2.1接口的概念和语法
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
接口的定义格式与定义类的格式相同,将class关键字换成interface关键字,就定义了一个接口。
public interface 接口名称{ }
2.2接口的使用与特性
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
public class 类名 implements 接口名{ }
注意:子类和父类之间是extends继承关系,类与接口之间是implements实现关系。
例子:请实现笔记本电脑使用USB鼠标、USB键盘的例子
1. USB接口:包含打开设备、关闭设备功能
2.笔记本类:包含开机功能、关机功能、使用USB设备功能
3.鼠标类:实现USB接口,并具备点击功能
4.键盘类:实现USB接口,并具备输入功能
//IUSB.java public interface IUSB { void openDevice(); void closeDevice(); } class Mouse implements IUSB { @Override public void openDevice() { System.out.println("开启鼠标"); } @Override public void closeDevice() { System.out.println("关闭鼠标"); } public void click(){ System.out.println("使用鼠标点击"); } } class KeyBoard implements IUSB { @Override public void openDevice() { System.out.println("开启键盘"); } @Override public void closeDevice() { System.out.println("关闭键盘"); } public void boardInput() { System.out.println("使用键盘输入"); } } class Computer { public void open() { System.out.println("开机"); } public void close() { System.out.println("关机"); } public void useUsb(IUSB iusb) { iusb.openDevice(); if (iusb instanceof Mouse){ Mouse mouse = (Mouse) iusb; mouse.click(); } else if (iusb instanceof KeyBoard) { KeyBoard keyBoard = (KeyBoard) iusb; keyBoard.boardInput(); } iusb.closeDevice(); } } //Test.java public class Text { public static void main(String[] args) { Computer computer = new Computer(); Mouse mouse = new Mouse(); KeyBoard keyBoard = new KeyBoard(); computer.open(); computer.useUsb(mouse); computer.useUsb(keyBoard); computer.close(); } }- 接口类型是一种引用类型,不能直接实例化对象
- 接口中的方法会被隐式的指定为public abstract(JDK 8以前)
- 接口中的变量会被隐式的指定为public static final变量
- 一个类可以实现多个接口,接口之间可以用 extends 实现多继承
- 从JDK 8 开始,接口中可以有 default / static 方法(有方法体);从JDK 9 开始,接口中可以有 private 方法
- 接口中不能有静态代码块和构造方法
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
2.3实现多个接口与接口间的继承
在Java中,类和类之间是单继承的,一个类只能有一个父类,但是一个类可以实现多个接口。
//Animal.java class Dog implements IRunning, ISwimming{ //实现多个接口则必须重写多个接口中的所有抽象方法,否则类须定义为抽象类 @Override public void run() { System.out.println("狗正在奔跑"); } @Override public void swimming() { System.out.println("狗正在游泳"); } } class Cat implements IRunning { @Override public void run() { System.out.println("猫正在奔跑"); } } //IRunning.java public interface IRunning { void run(); } interface ISwimming { void swimming(); } //Text.java public class Text { public static void main(String[] args) { //实现多个接口,并通过接口来实现多态 Dog dog = new Dog(); run(dog); swim(dog); Cat cat = new Cat(); run(cat); } public static void run(IRunning running) { //在该方法中,不需要关注running指向的对象具体是猫还是狗,只要是会跑的, //即实现了IRunning接口的类,都可以实现多态 running.run(); } public static void swim(ISwimming swimming) { swimming.swimming(); } }上面的代码展示了Java面向对象编程中最常见的用法:一个类同时实现多种接口。
继承表达的含义是is - a语义,即是什么,而接口表达的含义是具有xxx特性。这样设计的好处:有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。
注意:接口与接口之间可以多继承,使用 extends 关键字
public interface IRunning { void run(); } interface ISwimming { void swimming(); } interface BothSwimandRun extends IRunning, ISwimming { }通过接口继承创建一个新的接口. 此时实现该接口创建的类, 就继续要实现 run 方法, 也需要实现 swim 方法。接口间的继承相当于把多个接口合并在一起
三、Object 类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。
Object类也存在有定义好的一些方法,如toString()方法,equals()方法,hashcode()方法。
//object类中的toString方法 public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }如果要打印对象中的内容,可以直接重写Object类中的toString()方法,可以直接使用IDE中的construct功能进行构造
//object中的equals方法 public boolean equals(Object obj) { return (this == obj); }如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认使用 “==” 比较,
如果==左右两侧是基本类型变量,比较的是变量中值是否相同;如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
//object中的hascode方法 public native int hashCode();该方法是一个native方法,底层是由C/C++代码实现的,hashcode方法主要用于支持基于哈希的数据结构,目前不展开
四、内部类
在Java中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
根据内部类定义的位置不同,一般可以分为以下几种形式:
- 实例内部类:未被static修饰的成员内部类
- 静态内部类:被static修饰的成员内部类
- 局部内部类:定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少
- 匿名内部类:通常在实现接口的时候使用
//Outclass.java public class Outclass { private int a; static int b; private int c = 30; public void func1() { System.out.println("外部类的普通func1方法"); } public static void func2() { System.out.println("外部类的静态func2方法"); } public static class StaticInnerclass { //静态内部类 public void method() { System.out.println(b); func2();//静态内部类方法中只能访问外部类中的静态成员 } } public class NoStaticInnerclass { //实例内部类 int c = 3; public void func2() { a = 1; b = 2; func1(); Outclass.func2();//实例内部类方法中可以访问外部类中的任意成员 System.out.println(c); System.out.println(Outclass.this.c); } } } //Text.java public class Text { public static void main(String[] args) { //实例内部类 //外部类.内部类 内部类对象名 = new 外部类().new 内部类() Outclass.NoStaticInnerclass noStaticInnerclass = new Outclass().new NoStaticInnerclass(); //也可以先创建外部类对象,再创建实例内部类对象 Outclass outclass = new Outclass(); Outclass.NoStaticInnerclass noStaticInnerclass1 = outclass.new NoStaticInnerclass(); noStaticInnerclass1.func2(); //静态内部类 //外部类.内部类 内部类对象名 = new 外部类.内部类() Outclass.StaticInnerclass staticInnerclass = new Outclass.StaticInnerclass(); staticInnerclass.method(); } }内部类中,最常用的是匿名内部类。Java中如果想要使用某个接口则需要写一个类来实现该接口,但是若该接口只使用一次,还专门写一个类来实现则会产生很多不必要的代码,这时可使用匿名内部类来实现该接口,用完即丢,代码更加简洁
public interface IA { void func1(); } class A implements IA { @Override public void func1() { System.out.println("普通类重写的func1方法"); } } //Text.java public class Text { public static void main(String[] args) { //匿名内部类 IA ia = new A(); ia.func1();//如果该接口只使用一次,则会产生很多冗余的代码 //此时,可以用匿名内部来来实现 IA ia1 = new IA() { @Override public void func1() { System.out.println("匿名内部类重写的func1方法-1"); } }; ia1.func1(); //第二种写法: new IA() { @Override public void func1() { System.out.println("匿名内部类重写的func1方法-2"); } }.func1(); } }五、接口使用实例
5.1Comparable接口
Comparable 接口是 Java 中用于定义对象自然排序规则的核心接口,其中只有一个方法compareTo(),该方法的返回值约定为:为负数,则表示当前对象小于参数对象,为0,则表示当前对象等于参数对象,为正数,则当前对象大于参数对象,相当于制定了一种比较的准则
//Student.java public class Student implements Comparable{ private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public int compareTo(Object o) { Student student = (Student) o; return this.age - student.age; } } //Text.java import java.util.Arrays; public class Text { public static void main(String[] args) { Student[] students = { new Student("zhangsan",10), new Student("lisi",20), new Student("wangwu",30), new Student("zhaoliu",40) }; Arrays.sort(students); //由于Student实现了Comparable接口,则可以使用Java中的内置的工具类直接调用已有的排序方法 System.out.println(Arrays.toString(students)); mySort(students); //若Student没有实现Comparable接口,则必须重写排序方法的底层实现 //由此可见官方内置接口存在的意义即通过接口制定标准,让底层代码去识别标准然后执行 System.out.println(Arrays.toString(students)); } public static void mySort(Comparable[] comparable){ //手写的底层实现的排序算法 for (int i = 1; i < comparable.length; i++) { for (int j = 0; j < comparable.length - i; j++) { if (comparable[j].compareTo(comparable[j + 1]) < 0){ Comparable tmp = comparable[j]; comparable[j] = comparable[j + 1]; comparable[j + 1] = tmp; } } } } }5.2Clonable 接口和深拷贝
Clonable 接口是Java中的一个非常特殊的标记接口,其中不含有任何方法,唯一的作用是改变 object.clone() 方法的行为,也就是说,Clonable是一个"开关":实现了它,object.clone()才被允许工作;否则会抛出异常
//Person.java public class Person implements Cloneable{ private String name; private int age; Money m = new Money(); public Person(String name, int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class Money implements Cloneable{ public double money = 10; } //Text.java public class Text { public static void main(String[] args) throws CloneNotSupportedException { //浅拷贝 Person person1 = new Person("zhangsan",10); Person person2 = (Person) person1.clone(); System.out.println(person1.m.money + " " + person2.m.money); person1.m.money = 20; System.out.println(person1.m.money + " " + person2.m.money); } }注意到修改 person1.m.money 后 person2.m.money 的值也会同步发生改变,这是因为 clone 方法默认实现的是一份浅拷贝,其只复制基本类型字段的值和引用类型字段的引用,不复制引用指向的对象,与之相对应的还有深拷贝,即将引用类型字段的引用所指向的对象也复制一份
//Person.java public class Person implements Cloneable{ private String name; private int age; Money m = new Money(); public Person(String name, int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { Person tmp = (Person) super.clone(); tmp.m = (Money) this.m.clone(); return tmp; } } class Money implements Cloneable{ public double money = 10; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } //Text.java public class Text { public static void main(String[] args) throws CloneNotSupportedException { //深拷贝 Person person1 = new Person("zhangsan",10); Person person2 = (Person) person1.clone(); System.out.println(person1.m.money + " " + person2.m.money); person1.m.money = 20; System.out.println(person1.m.money + " " + person2.m.money); } }此时修改 person1.m.money 后 person2.m.money 的值不会同步发生改变