文章目录
一、抽象类
1、抽象类概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
像这种没有实际工作的方法,我们可以把它设计成一个抽象方法(abstract
method),包含抽象方法的类我们称为抽象类(abstract class)。
2、抽象类语法
在Java中,一个类如果被 abstract
修饰称为抽象类,抽象类中被 abstract
修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
// 抽象类:被abstract修饰的类
public abstract class Shape {
protected double area; // 面积 -->属性
// 抽象方法:被abstract修饰的方法,没有方法体
abstract public void draw();
abstract void calcArea(); // 抽象类也是类,也可以增加普通方法和属性
// 普通方法
public double getArea(){
return area;
}
}
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
3、抽象类特性
(1)抽象类不能直接实例化对象,但可以发生向上转型,进一步发生多态
abstract class Shape {
public abstract void draw();
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("○");
}
}
public static void main(String[] args) {
Shape shape = new Cycle();
}
(2)抽象方法不能是 private
的
注意:抽象方法没有加访问限定符时,默认是public.
(3)抽象方法不能被 final
和 static
修饰,因为所有抽象方法都要被子类重写
(4)抽象类必须被继承,并且继承后子类要重写父类中的所有抽象方法,否则子类也是抽象类,必须要使用 abstract
修饰;若此时抽象类A继承抽象类Shape,在此基础上抽象类B继承抽象类A,那么抽象类B必须重写Shape和A中所有抽象方法。
abstract class Shape {
public abstract void draw();
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("○");
}
}
abstract class A extends Shape {
// 一个抽象类继承另一个抽象,不需要重写方法
public abstract void func();
}
class B extends A {
// Alt + Enter 重写所有方法
@Override
public void draw() {
}
@Override
public void func() {
}
}
(5)抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
(6)抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
二、接口
1、接口概念
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2、语法规则
接口的定义格式与定义类的格式基本相同,将 class 关键字换成 interface
关键字,就定义了一个接口。
public interface 接口名称{
// 抽象方法
public abstract void method1(); // public abstract 是固定搭配,可以不写
public void method2();
abstract void method3();
void method4();
// 注意:在接口中上述写法都是抽象方法,但更推荐方式4,代码更简洁
}
3、接口使用
(1)接口当中的成员方法只能是抽象方法,默认是 public abstract
修饰,且只能是 public abstract
(2)接口当中的成员变量默认是 public static final ,必须初始化
(3)接口当中的方法如果要实现,需要使用 default 修饰
(4)接口当中的静态方法,可以有具体的实现
(5)接口不能实例化,不能 new 接口,只能使用一个普通的类用 implements
实现接口当中的方法。接口中的方法:抽象的方法必须重写(重写的方法必须是public),默认方法可以重写也可以不重写,静态方法直接类调用。
interface IShape {
// 成员变量
public static final int a = 10;
int b = 20; // 默认是 public static final ,必须初始化
// 接口当中的方法如果要实现,需要使用 default 修饰
default void func() {
System.out.println("默认的方法");
} // 默认方法可以重写也可以不重写
// 接口当中的静态方法,可以有具体的实现
public static void func2() {
System.out.println("静态的方法");
}
void draw(); // 抽象方法必须重写
}
class A implements IShape {
@Override
public void func() {
System.out.println("func()重写");
}
@Override
public void draw() {
System.out.println("draw()重写");
}
}
public class Test {
public static void main(String[] args) {
A a = new A();
a.draw();
a.func();
IShape.func2(); // 静态的方法直接类调用
}
}
注意:子类和父类之间是 extends
继承关系,类与接口之间是 implements
实现关系。
(6)接口中不能有静态代码块和构造方法
(7)接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
(8)如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
(1)创建接口时, 接口的命名一般以大写字母 I 开头.
(2)接口的命名一般使用 “形容词” 词性的单词.
(3)阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
4、 实现多个接口
class Animal {
public String name;
public int age;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "吃饭!");
}
// 跑、飞、游泳不能写到这里
}
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
class Dog extends Animal implements IRunning, ISwimming{
public Dog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "is dog, 正在跑!");
}
@Override
public void swim() {
System.out.println(this.name + "is dog, 正在游泳!");
}
}
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "is cat, 正在跑");
}
}
public class Test {
public static void walk(IRunning iRunning) {
iRunning.run();
}
public static void main(String[] args) {
Cat cat = new Cat("咪咪");
//cat.run();
walk(cat);
Dog dog = new Dog("旺财");
//dog.run();
walk(dog);
}
}
从上述代码可以看出:一个类可以继承抽象类,并实现多个接口,每个接口用逗号隔开
5、接口间的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
@Override
public void run() {
System.out.println("正在跑");
}
@Override
public void swim() {
System.out.println("正在游泳");
}
}
通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”,此时实现接口创建的 Frog 类,就继续要实现 run 方法,也需要实现 swim 方法。ctrl + i
可以实现快速重写。
接口间的继承相当于把多个接口合并在一起。
6、接口使用实例
给对象数组排序
class Student implements Comparable<Student>{
String name;
int age;
double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
// 以年龄排序
// @Override
// public int compareTo(Student o) {
// if (this.age > o.age) {
// return 1;
// }else if (this.age == o.age) {
// return 0;
// }else {
// return -1;
// }
// // return this.age - o.age;
// // return (int)(this.score - o.score);
// }
// 以名字排序
@Override
public int compareTo(Student o) {
// if (this.name.compareTo(o.name) > 0) {
// return 1;
// }else if (this.name.compareTo(o.name) == 0) {
// return 0;
// }else {
// return -1;
// }
return this.name.compareTo(o.name);
}
}
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan", 38, 98.4);
students[1] = new Student("lisi", 73, 42);
students[2] = new Student("wangwu", 18, 10.5);
// 以名字排序
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
上述代码对类改动太大,可以传比较器指定用年龄/名字/分数排序:
class Student {
String name;
int age;
double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
// 比较器
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return (int) (o1.score - o2.score);
}
}
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan", 38, 98.4);
students[1] = new Student("lisi", 73, 42);
students[2] = new Student("wangwu", 18, 10.5);
// 以年龄排序
AgeComparator ageComparator = new AgeComparator();
Arrays.sort(students,ageComparator);
System.out.println(Arrays.toString(students));
}
7、Clonable 接口和深拷贝
Java 中内置了一些很有用的接口, Clonable 就是其中之一.
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出CloneNotSupportedException 异常
class Person implements Cloneable{
public String name;
public int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
person1.age = 12;
person1.name = "frost";
Person person2 = (Person)person1.clone();
System.out.println(person2);
}
}
Cloneable 拷贝出的对象是一份 “浅拷贝”:
class Money {
public double money = 19.9;
}
class Person implements Cloneable{
public String name;
public int age = 123;
public Money m = new Money();
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
person1.age = 12;
person1.name = "frost";
Person person2 = (Person)person1.clone();
System.out.println(person1.m.money);
System.out.println(person2.m.money);
System.out.println("======================");
person1.m.money = 29.1;
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
}
深拷贝:
class Money implements Cloneable{
public double money = 19.9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public String name;
public int age = 123;
public Money m = new Money();
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.m = (Money) this.m.clone();
return tmp;
//return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
person1.age = 12;
person1.name = "frost";
Person person2 = (Person)person1.clone();
System.out.println(person1.m.money);
System.out.println(person2.m.money);
System.out.println("======================");
person1.m.money = 29.1;
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
}
8、抽象类和接口的区别
核心区别:抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口。
No | 区别 | 抽象类(abstract) | 接口(interface) |
---|---|---|---|
1 | 结构组成 | 普通类 + 抽象方法 | 抽象方法 + 全局常量 |
2 | 权限 | 各种权限 | public |
3 | 子类使用 | 使用 extends 关键字继承抽象类 | 使用 implements 关键字实现接口 |
4 | 关系 | 一个抽象类可以实现若干接口 | 接口不能继承抽象类,但是接口可以使用 extends 关键字继承多个父接口 |
5 | 子类限制 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |
三、Object类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。
1、获取对象信息
如果要打印对象中的内容,可以直接重写Object类中的toString()方法。
2、对象比较equals方法
在Java中,=/=进行比较时:
(1)如果=/=左右两侧是基本类型变量,比较的是变量中值是否相同
(2)如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
(3)如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:
Student类重写equals方法后,然后比较:
class Student {
String name;
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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student("frost", 10);
Student student2 = new Student("frost", 10);
System.out.println(student1.equals(student2));
}
}
结论:比较对象中内容是否相同的时候,一定要重写equals方法。
3、hashcode方法
hashcode方法用来算对象在内存中存储的位置,与equals一样,我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法,则
重写之后:
class Student {
String name;
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 hashCode() {
return Objects.hash(name, age);
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student("frost", 10);
Student student2 = new Student("frost", 10);
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
}
}
结论:
(1)hashcode方法用来确定对象在内存中存储的位置是否相同
(2)事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
4、接收引用数据类型
在之前已经分析了Object可以接收任意的对象,因为Object是所有类的父类,但是Obejct并不局限于此,它可以接收所有数据类型,包括:类、数组、接口。
范例:使用Object来接受数组对象
public static void main(String[] args) {
// Object接收数组对象,向上转型
Object obj = new int[]{1,2,3,4,5,6} ;
// 向下转型,需要强转
int[] data = (int[]) obj ;
for(int i :data){
System.out.println(i+"、");
}
}
而Object可以接收接口是Java中的强制要求,因为接口本身不能继承任何类。
范例:使用Object接收接口对象
interface IMessage {
public void getMessage() ;
}
class MessageImpl implements IMessage {
@Override
public String toString() {
return "I am small biter" ;
}
public void getMessage() {
System.out.println("比特欢迎你");
}
}
public class Test {
public static void main(String[] args) {
IMessage msg = new MessageImpl() ; // 子类向父接口转型
Object obj = msg ; // 接口向Obejct转型
System.out.println(obj);
IMessage temp = (IMessage) obj ; // 强制类型转换
temp.getMessage();
}
}
Object真正达到了参数的统一,如果一个类希望接收所有的数据类型,就是用Object完成,在Java中,泛型就是底层就是通过Object来实现的。