1. 抽象类
1.1 什么是抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
1.2 抽象类语法
// 抽象类:被abstract修饰的类
public abstract class Shape {
// 抽象方法:被abstract修饰的方法,没有方法体
abstract public void draw();
abstract void calcArea();
// 抽象类也是类,也可以增加普通方法和属性
public double getArea(){
return area;
}
}
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
1.3 抽象类特性
我们可以接着用继承里面的画图形的例子来看。
//1. 抽象类不是一个具体的类,不可以进行实例化。(不能new)
abstract class Shape { //有一个方法是抽象的,类也必须要是抽象类
//2. 抽象类当中可以定义和普通类一样的数据成员或者方法
public int a;
public static int b;
public void test() {
System.out.println();
}
public abstract void drow(); // 抽象方法
}
//3. 当一个普通类A继承了一个抽象类B之后,此时普通类A一定要重写抽象类B里面的抽象方法。
class Cycle extends Shape {
@Override
public void drow() {
System.out.println("画○!");
}
}
//4. 和第三点相反,如果一个抽象类C继承了一个抽象类B,此时可以不重写抽象了哦B的抽象方法。
class Rect extends Shape {
@Override
public void drow() {
System.out.println("画◇!");
}
}
//5. 不管怎么样,只要其他类再次继承,你需要重写所有积攒的(继承的)方法都要重写。
//6. 抽象类最大的意义就是为了被继承。
//7. final和abstract不能共存,抽象方法也不可以是有static修饰的 或者 private
class Flower extends Shape {
@Override
public void drow() {
System.out.println("画❀!");
}
}
class Triangle extends Shape {
@Override
public void drow() {
System.out.println("画△!");
}
}
class A extends Rect {
@Override
public void drow() {
System.out.println();
}
}
public class TestDemo {
public static void draMap(Shape shape) {
shape.drow();
}
public static void main(String[] args) {
Cycle cycle = new Cycle();
draMap(cycle);
draMap(new Rect());
draMap(new Flower());
draMap(new Triangle());
}
}
1.4 抽象类的作用
抽象类是用来捕捉子类的通用特性的,是被用来创建继承层级里子类的模板。 现实中有些父类中的方法确实没有必要写,因为各个子类中的这个方法肯定会有不同;而写成抽象类,这样看代码时,就知道这是抽象方法,而知道这个方法是在子类中实现的,所以有提示作用。
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。
充分利用编译器的校验, 在实际开发中是非常有意义的
2. 接口
2.1 接口的概念
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2.2 语法规则
接口的定义格式与定义类的格式基本相同,将class关键字换成interface关键字,就定义了一个接口。
interface Ishape {
//public int a; 报错:在接口里面定义需要初始化
public int b = 1;
public static int c = 2;
public static final int d = 3;
public abstract void draw();
}
提示:
1. 创建接口时, 接口的命名一般以大写字母 I 开头.
2. 接口的命名一般使用 "形容词" 词性的单词.
3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
2.3 接口使用
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
public class 类名称 implements 接口名称{
// ...
}
子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
2.4 接口特性
//1. 接口的关键字 interface
interface Ishape {
//public int a; 报错:在接口里面定义需要初始化
public int b = 1;
public static int c = 2;
public static final int d = 3;
//2. 接口当中的成员变量默认是public static final 的
//3. 接口当中的抽象方法默认是 public abstract 的
public abstract void draw();
//4. 接口当中可以定义实现了的方法。要么被default修饰 要么被static修饰。
//5. 接口依然不可以实例化
/*
public static void test2() {
System.out.println();
}*/
//7. default方法是接口的默认方法 当然 你也可以在实现类当中 重写接口的这个默认方法
/*default public void test() {
System.out.println("这就是一个默认的方法");
}*/
}
//6. 接口和类的关系,可以使用implements, 此时这个类 要重写接口当中的所有的抽象方法
class Cycle implements Ishape {
@Override
public void draw() {
System.out.println("画○!");
}
}
class Rect implements Ishape {
@Override
public void draw() {
System.out.println("画◇!");
}
}
class Flower implements Ishape {
@Override
public void draw() {
System.out.println("画❀!");
}
}
class Triangle implements Ishape {
@Override
public void draw() {
System.out.println("画△!");
}
}
public class TestDemo1 {
public static void drawMap(Ishape shape) {
shape.draw();
}
public static void main(String[] args) {
// Ishape ishape = new Ishape();
Ishape ishape1 = new Cycle();
Ishape ishape2 = new Rect();
Ishape ishape3 = new Flower();
drawMap(ishape1);
drawMap(new Cycle());
drawMap(ishape2);
drawMap(ishape3);
}
}
2.5 实现多个接口
::一个类可以实现多个接口,使用逗号隔开。同时所有接口当中的抽象方法,必须进行重写。
::一个类可以继承一个抽象类/普通类,同时可以实现多个接口。但是 要先继承然后实现接口。
interface IA {
void test1();
}
interface IB {
void test2();
}
interface IC {
void test3();
}
interface ID extends IA,IB,IC {
void test5();
}
class A implements IA,IB {
@Override
public void test1() {
}
@Override
public void test2() {
}
}
abstract class B implements IA,IB{
public abstract void test4();
}
class C extends B implements IC {
@Override
public void test1() {
}
@Override
public void test2() {
}
@Override
public void test3() {
}
@Override
public void test4() {
}
}
2.6 接口间的继承
::当一个接口使用extends 拓展了其他的接口,此时当一个类实现了这个接口,需要重写所有的接口。
interface IA {
void test1();
}
interface IB {
void test2();
}
interface IC {
void test3();
}
interface ID extends IA,IB,IC {
void test5();
}
class DD implements ID {
@Override
public void test1() {
}
@Override
public void test2() {
}
@Override
public void test3() {
}
@Override
public void test5() {
}
}
接口间的继承相当于把多个接口合并在一起。
2.7 接口使用实例
1. 给对象数组排序
class Student {
public String name;
public int age;
public 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 +
'}';
}
}
public class TestDemo {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("张三",18,88);
students[1] = new Student("李四",19,78);
students[2] = new Student("王五",20,45);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
----》我们发现结果报错了。
此时我们就要用到第一个接口Comparable。
此时要做的就是重写compareTo方法。

在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0;
注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 "可比较" 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.
class Student implements Comparable<Student>{
//...
@Override
public int compareTo(Student o) {
if(this.age > o.age) {
return 1;
}else if(this.age == o.age) {
return 0;
}else{
return -1;
}
}
}
-->我们发现此时可以正常排序,使用的是年龄大小排序。

或者
写一个比较器:
class Student {
public String name;
public int age;
public 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);
}
}
public class TestDemo {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("张三",18,88);
students[1] = new Student("李四",79,78);
students[2] = new Student("王五",50,45);
//对类的侵入非常小
AgeComparator ageComparator = new AgeComparator();
ScoreComparator scoreComparator = new ScoreComparator();
System.out.println("排序前:"+Arrays.toString(students));
Arrays.sort(students,ageComparator);
System.out.println("age排序后:"+Arrays.toString(students));
Arrays.sort(students,scoreComparator);
System.out.println("score排序后:"+Arrays.toString(students));
/*AgeComparator ageComparator = new AgeComparator();
int ret = ageComparator.compare(students[0],students[1]);
System.out.println(ret);
ScoreComparator scoreComparator = new ScoreComparator();
ret = scoreComparator.compare(students[0],students[1]);
System.out.println(ret);*/
}
}
2.8 Clonable 接口和深拷贝
Java 中内置了一些很有用的接口, Clonable 就是其中之一.
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 "拷贝". 但是要想合法调用 clone 方法, 必须要先
实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.
class Person implements Cloneable {//空接口,标记接口,
public int id;
public Person(int id) {
this.id = id;
}
@Override
public String toString() {
return "Person{" +
"name='" + id + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo3 {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person(8);
System.out.println(person);
Person person1 = (Person) person.clone();
System.out.println(person1);
}
浅拷贝 VS 深拷贝
浅拷贝:
class Money {
public double money = 88.8;
}
class Person implements Cloneable {//空接口,标记接口,
public int id;
Money m = new Money();
public Person(int id) {
this.id = id;
}
@Override
public String toString() {
return "Person{" +
"name='" + id + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo3 {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person(8);
System.out.println(person);
Person person1 = (Person) person.clone();
System.out.println(person1);
System.out.println("修改一下money");
person1.m.money = 99.9;
System.out.println(person.m.money);
System.out.println(person1.m.money);
}
}
深拷贝:
class Money implements Cloneable {
public double money = 88.8;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable {//空接口,标记接口,
public int id;
Money m = new Money();
public Person(int id) {
this.id = id;
}
@Override
public String toString() {
return "Person{" +
"name='" + id + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person personCopy = (Person) super.clone();
personCopy.m = (Money) this.m.clone();
return personCopy;
}
}
public class TestDemo3 {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person(8);
System.out.println(person);
Person person1 = (Person) person.clone();
System.out.println(person1);
System.out.println();
System.out.println("修改一下money");
person1.m.money = 99.9;
System.out.println(person.m.money);
System.out.println(person1.m.money);
}
}
深拷贝与浅拷贝的区别:
他两的最根本区别就是在于是否zhenzheng获取一个对象的复制实体,而不是引用。
==》浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
==》深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
2.9 抽象类和接口的区别
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。
抽象类存在的意义是为了让编译器更好的校验,

3. Object类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。
class A {
}
class B {
}
public class TestDemo2 {
public static void func(Object obj) {
System.out.println(obj);
}
public static void main(String[] args) {
func(new A());
func(new B());
}
}
---》结果

所以在开发之中,Object类是参数的最高统一类型。但是Object类也存在有定义好的一些方法。
我们可以在编译器中找到。
2.2 获取对象信息
如果要打印对象中的内容,可以直接重写Object类中的toString()方法。
2.3 对象比较equals方法
在Java中,==进行比较时:
1. 如果==左右两侧是基本类型变量,比较的是变量中值是否相同
2. 如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
3. 如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:
class A {
public int id;
public A(int id) {
this.id = id;
}
public boolean equals(Object obj){
if(obj == null) {
return false;
}
if (this == obj){
return true;
}
if(!(obj instanceof A)){
return false;
}
A tmp = (A)obj;
return true;
}
}
class B {
}
public class TestDemo2 {
public static void main(String[] args) {
A a1 = new A(1);
String str = "123";
System.out.println((a1.equals(str)));
}
}
结论:比较对象中内容是否相同的时候,一定要重写equals方法。
2.4 hashcode方法
hashcode方法源码: public native int hashCode();
(该方法是一个native方法,底层是由C/C++代码写的。我们看不到。)
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class TestDemo4 {
public static void main(String[] args) {
Person per1 = new Person("11", 20) ;
Person per2 = new Person("11", 20) ;
System.out.println(per1.hashCode());
System.out.println(per2.hashCode());
}
}
//执行结果
460141958
1163157884
注意事项:两个对象的hash值不一样。
像重写equals方法一样,我们也可以重写hashcode()方法。此时我们再来看看。
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class TestDemo4 {
public static void main(String[] args) {
Person per1 = new Person("11", 20) ;
Person per2 = new Person("11", 20) ;
System.out.println(per1.hashCode());
System.out.println(per2.hashCode());
}
}
//执行结果
460141958
460141958
结论:
1、hashcode方法用来确定对象在内存中存储的位置是否相同
2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
2.5 接收引用数据类型
在之前已经分析了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
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来实现的。
END
本文详细介绍了抽象类和接口在Java中的概念、语法、特性及应用场景。抽象类用于捕捉子类的通用特性,提供模板,不可实例化,但可以包含普通方法和属性。接口则定义了公共行为规范,类实现接口必须实现所有抽象方法。一个类可以实现多个接口,而抽象类可以有多个抽象方法。此外,文章还探讨了对象排序、克隆以及Object类的方法如equals和hashCode。最后,对比了抽象类与接口的区别,强调了它们在多态和设计模式中的不同角色。
1138

被折叠的 条评论
为什么被折叠?



