目录
一、抽象类
1.1 概念语法
- 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
- 一个类被abstract修饰,称为抽象类,被abstract修饰的方法叫做抽象方法。
abstract class Shape{
//抽象方法
public abstract void draw();
}
1.2 抽象类的特性
- 在抽象类当中,可以有和普通类一样的方法、成员。最突出的区别就是抽象类不能实例化对象。
abstract class Shape{ public int a; public static int b; public void fun(){ } //抽象方法 public abstract void draw(); }
- 抽象类虽然不能实例化,但是可以被继承(抽象类就是要被继承的)。
- 当一个普通类继承了抽象类,并且这个抽象类当中含有抽象方法,那么就要重写这个抽象方法,否则编译器不能通过编译。
abstract class Shape{ //抽象方法 public abstract void draw(); } class React extends Shape{ @Override public void draw() { } } public class Test1 { public static void main(String[] args) { } }
- 如果一个抽象类A继承另一个抽象类B,那么此时抽象类A可以不重写B中的抽象方法。
- 抽象方法不能被private、static、final的关键字修饰的,因为抽象方法是要被继承的。
抽象类的作用
- 抽象类的作用就是相当于多了一层编译器的校验,在实际开发中是非常有意义的。
二、接口
2.1 概念
在生活中接口非常常见,例如:电脑上USB接口,所有USB都可以插入这个接口,不管是鼠标、键盘、U盘都可以插入。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型
2.2 接口的使用
- 语法规则:
public interface 接口名称{
// 抽象方法
public abstract void method1(); // public abstract 是固定搭配,可以不写}
- 接口的使用 (接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。)
public class 类名称 implements 接口名称{
// ...
}注:接口名称一般以大写的 I 开头
- 接口的特性:
public interface IShape {
public int a = 1;
int b = 2;
final int c = 3;
default void test(){
}
public void fun();
}
class React implements IShape{
@Override
public void fun() {
}
}
- 接口中的成员变量,默认都是由 public static final 修饰的。
- 接口当中的成员方法,默认都是抽象方法,由 public abstract 修饰的
- 接口中的普通成员方法,是不能有具体的实现的,如果要有具体的实现,必须加上default 修饰。
- 接口当中可以有静态成员方法,,但无论是静态方法还是 default方法,都是由public修饰的。
- 接口是不能实例化的
- 一个接口可以引用具体实现类的,向上转型。
- 接口当中不能有静态代码块、实例代码块、构造方法
- 如果一个抽象类实现一个接口,那这个抽象类就可以不重写抽象方法,但是还有其他类继承这个抽象类,那么还是要重写抽象方法的。
2.3 实现多个接口
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。
例如:
我们先创建一个动物类:
class Animal{
public String name;
public Animal(String name) {
this.name = name;
}
}
再创建几个动物行为的接口:
interface fly{
void fly();
}
interface running{
void running();
}
interface ISwimming{
void swim();
}
然后创建具体的动物;
狗是用四条腿跑的:
class Dog extends Animal implements IRunning{
public Dog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(name + "正在用四条腿跑!");
}
}
小鸟是用翅膀飞的:
class Bird extends Animal implements IFly{
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(name+"正在用翅膀飞");
}
}
鱼是用尾巴在水里游的:
class Fish extends Animal implements ISwimming{
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(name + "正在水里用尾巴游");
}
}
而鸭子既可以跑又可以飞还可以游泳:
class Duck extends Animal implements ISwimming,IFly,IRunning{
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(name+"正在用翅膀飞");
}
@Override
public void run() {
System.out.println(name +"正在用鸭掌跑");
}
@Override
public void swim() {
System.out.println(name+"正在用鸭掌游泳");
}
}
public class Test2 {
public static void fly(IFly iFly){
iFly.fly();
}
public static void swim(ISwimming iSwimming){
iSwimming.swim();
}
public static void run(IRunning iRunning){
iRunning.run();
}
public static void main(String[] args) {
fly(new Duck("唐老鸭"));
run(new Duck("唐老鸭"));
swim(new Duck("唐老鸭"));
}
}
注:
- 一个类实现多个接口时,每个接口的抽象方法都要重写,除非是抽象类可以不用重写。
- 接口表达的含义是具有xxx特性。
2.4 接口之间的继承(功能的拓展)
接口和接口之间可以达到多继承:
interface IFly{
void fly();
}
interface IRunning{
void run();
}
interface ISwimming{
void swim();
}
interface IAnimal extends IRunning,IFly,ISwimming{
}
2.5 自定义类型排序
Comparable接口可以用来排序我们自定义类型的排序:
class Student implements Comparable<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 int compareTo(Student o) {
return this.age - o.age;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小明",10,100);
Student student1 = new Student("小红",13,99);
if (student.compareTo(student1) > 0){
System.out.println("student > student1");
}else if(student.compareTo(student1) == 0){
System.out.println("student = student1");
}else{
System.out.println("student < student2");
}
}
}
注:
自定义数据牵扯到比较大小,需要指明比较大小的数据类型,在我们重写的compareTo方法中实现的。
上面这种情况我们就只能比较年龄,如果还想比较其他数据的话就必须进行修改,其实我们实现一个比较器:
年龄比较器:
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小明",10,100);
Student student1 = new Student("小红",13,99);
AgeComparator ageComparator = new AgeComparator();
int ret = ageComparator.compare(student,student1);
if (ret > 0){
System.out.println("student > student1");
}else if(ret == 0){
System.out.println("student = student1");
}else{
System.out.println("student < student2");
}
}
分数比较器:
class ScoreComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return (int) (o1.score - o2.score);
}
}
对于自定义类型排序我们还可以使用Array.sort排序:
class ScoreComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return (int) (o2.score - o1.score);
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("小明",10,100);
students[1] = new Student("小红",12,50);
students[2] = new Student("小花",13,76);
ScoreComparator scoreComparator = new ScoreComparator();
Arrays.sort(students,scoreComparator);
System.out.println(Arrays.toString(students));
}
}
2.6 Clonable接口和深拷贝
Clonable接口克隆一个对象就是产生这个对象的副本:
class Person implements Cloneable{
public int age = 10;
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException{
Person person = new Person();
Person person1 = (Person) person.clone();
System.out.println(person1);
}
}
当我们修改这个副本的值时,主体不会受到影响:
其内存布局如下:
但是当我们再定义一个钱类,在人类里面对它实例化后,并且将克隆副本的钱的值修改,再打印就会发现主体和副本钱的值都修改:
class Money{
public int money = 20;
}
class Person implements Cloneable{
public int age = 10;
public Money m = new Money();
}
它的内存分布如下:
这个就是浅拷贝,只是拷贝了Money的地址,并没有创建一个新的副本。我们可以给Money也实现一个接口,从而实现深拷贝:
2.7 抽象类和接口的区别
- 抽象类是被abstract修饰,其方法为抽象方法且不用具体实现,与普通类最大的区别就是不能实例化;接口也不能实例化,其成员方法默认为抽象方法。
- 抽象类被继承后要重写抽象方法(extends),接口被实现后也要重写抽象方法(interface);
- 一个子类只能继承一个抽象类,一个子类可以实现多个接口;
- 一个抽象类可以实现多个接口,接口不能继承抽象类,但是接口可以使用extends关键字继承多个接口。
- 子类继承抽象类,重写抽象方法时其限定修饰符权限必须大于抽象方法的修饰符;而接口就是public;
三、Object类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。
class Person{
}
class Student{
}
public class Test {
public static void main(String[] args) {
Object o = new Person();
Object o1 = new Student();
}
}
也可以方法传参接收:
class Person{
public String name = "hello";
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student{
}
public class Test {
public static void func(Object o){
String str = o.toString();
System.out.println(str);
}
public static void main(String[] args) {
func(new Student());
func(new Person());
}
public static void main1(String[] args) {
Object o = new Person();
Object o1 = new Student();
}
}
3.1 对象比较equals方法
class Person{
public int age;
public Person(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age;
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person(10);
Person person1 = new Person(10);
System.out.println(person.equals(person1));
}
注:比较对象中内容是否相同的时候,一定要重写equals方法。
3.2 hashcode方法
hashcode()这个方法帮我们算出一个具体对象的地址,也可以称为内存地址,然后将其打印出来。
class Person{
public int age;
public Person(int age) {
this.age = age;
}
@Override
public int hashCode() {
return Objects.hash(age);
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person(10);
Person person1 = new Person(10);
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());
}
- 我们可以重写hashcode()方法,来实现对相同年龄位置定位相同的位置,所以运行结果是相同的数值。