抽象类
在面对对象的概念中,所有对象的创建都是通过类来创建的。但是并不是所有的类都会创建对应的对象。如果一个类中没有足够的数据创建一个对象,这样的类就是抽象类。
如同经常使用的Animal动物类,Dog类,Cat类,Dog类和Cat类都是继承于Animal类,但是Animal类中没有足够的数据去创建Animal动物,所以我们就可以将Animal设置为抽象类。
在日常书写代码中,我们发现创建动物类时,会给里面定义Eat方法,并且这个方法中也不进行具体。我们将这种方法中不实现任何功能的方法设置为抽象方法,包含抽象方法的类称为抽象类。
抽象类的基本语法
注
抽象类也是类,内部可以包含普通方法和属性,构造方法
抽象类的特点
不能实例化对象
抽象方法不能设置为private
抽象方法不能被final和static修饰,因为抽象类中的方法需要被重写
抽象类必需被继承,并且继承后的子类一定要重写父类中的抽象方法,否则子类也是抽象方法。抽象类一定要用abstract修饰。
abstract class Animal {//抽象方法:被abstract修饰的方法,没有方法体abstract public void Eat();//抽象类也是类,也可以增加普通方法和属性abstract public void Run();protected int age;//年龄}class Dog extends Animal{public void Eat(){System.out.println("吃骨头");}public void Run(){System.out.println("用腿跑");} }
抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类。
抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量。
抽象类的作用
使用抽象类相当于多了一重编译器的校验.
使用抽象类的场景就如上面的代码,实际工作都不通过父类完成,而通过子类完成.那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的.但是父类是抽象类就会在实例化的时候提示错误,让我们尽早发现问题。
很多语法存在的意义都是为了"预防出错",例如我们曾经用过的final也是类似.创建的变量用户不去修改,不就相当于常量嘛?但是加上final能够在不小心误修改的时候,让编译器及时提醒我们。
充分利用编译器的校验,在实际开发中是非常有意义的.
接口
接口的概念
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
接口的基本语法
接口的定义和类的定义格式基本相同,将class关键词换成interface关键字
public interface接口名称{
//抽象方法
public abstract void method1(); // public abstract是固定搭配,可以不写
public void method2();
abstract void method3();
void method4();
//在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}
注
创建接口时,接口的命名一般以大写字母开头
接口的命名一般使用"形容词"词性的单词
接口中的方法和属性不要加任何修饰符号,保持代码的简洁性
接口的使用
接口不能直接使用,必须要有一个实现类来实现该接口,实现接口中的抽象方法。
public class 类名称 implements 接口名称{
}
注
子类和父类之间是extends继承关系,类与接口之间是implents实现关系。
如请实现
笔记本电脑使用USB鼠标、USB键盘的例子
USB接口:包含打开设备、关闭设备功能
笔记本类:包含开机功能、关机功能、使用USB设备功能
鼠标类:实现USB接口,并具备点击功能
键盘类:实现USB接口,并具备输入功能
//USB接口interface USB {void openDevice();void closeDevice();
}//鼠标类,实现USB接口class Mouse implements USB{@Overridepublic void openDevice() {System.out.println("打开鼠标");}@Overridepublic void closeDevice(){System.out.println("关闭鼠标");}public void click(){System.out.println("鼠标点击");}
}//键盘类,实现USB接口class KeyBoard implements USB{@Overridepublic void openDevice() {System.out.println("打开键盘");}@Overridepublic void closeDevice() {System.out.println("关闭键盘");}public void inPut(){System.out.println("键盘输入");}
}//笔记本类:使用USB设备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();}usb.closeDevice();}
}public class Main {public static void main(String[] args) {Computer computer = new Computer();computer.powerOn();//使用鼠标设备computer.useDevice(new Mouse());//使用键盘设备computer.useDevice(new KeyBoard());computer.powerOff();}
}
类似上述的使用就实现了接口的用法。切记在继承接口时一定不用忘记重写他的所有方法。
接口特性
接口类型是一种引用类型,但是不能直接new接口的对象
public class Main {public static void main(String[] args) {USB usb = new USB();}
}
会发生报错,因为接口是抽象的,所以无法实例化对象。
接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)
interface USB {priavte void openDevice();//会发生报错无法解析privateprotected pvoid closeDevice();//和上述情况一样
}
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
interface USB {void openDevice();void closeDevice(){//接口中的方法默认为抽象方法不能在其中实现System.out.println("...");};
}
重写接口中方法时,不能使用默认的访问权限
//USB接口interface USB {void openDevice();void closeDevice();
}//鼠标类,实现USB接口class Mouse implements USB{@Overridevoid openDevice() {//'Mouse'中的 ‘openDevice()'与'USB'中的 'openDevice()冲突,尝试分配较弱的访问权限(package-private');曾为'public'System.out.println("打开鼠标");}@Overridepublic void closeDevice(){System.out.println("关闭鼠标");}public void click(){System.out.println("鼠标点击");}
}
接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
interface USB {int n = 3;void openDevice();void closeDevice();
}
public class Main {public static void main(String[] args) {System.out.println(USB.n);//可以通过类名直接输出,其为静态变量USB.n = 2;//不能修改,其被final修饰}
}
.接口中不能有静态代码块和构造方法
接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
jdk8中:接口中还可以包含default方法
实现多个接口
一个类可以实现多个接口
interface lFlying {void fly();
}
interface lRunning {void run();
}
interface lSwimming {void swim();
}class Frog implements lRunning, lSwimming {青蛙这个类实现了游泳和跑这两个接口private String name ;public Frog(String name){this.name = name;
}@Overridepublic void run() {System.out.println(this.name+"正在往前跳");}@Overridepublic void swim(){System.out.println(this.name +"正在蹬腿游泳");}
}
接口之间也可以继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到 多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字。
interface lRunning {void run();
}
interface lSwimming {void swim();
}
interface IAmphibious extends lRunning,lSwimming{//接口与接口之间的继承,可以达到复用,达到了拼接的效果一个接口有几个接口的特性}class Frog implements IAmphibious{private String name ;public Frog(String name){this.name = name;
}@Overridepublic void run() {System.out.println(this.name+"正在往前跳");}@Overridepublic void swim(){System.out.println(this.name +"正在蹬腿游泳");}
}
案例使用接口
给对象数组排序
import java.util.Arrays;class Student {private String name;private int score;public Student(String name, int score) {this.name = name;this.score = score;}@Overridepublic String toString(){return "[" + this.name + ":" + this.score + "]";}
}public class Main {public static void main(String[] args) {Student[] students = new Student[] {new Student("张三",95),new Student("李四",96),new Student("王五",97),new Student("赵六",92),};Arrays.sort(Student);}
}
建立一个学生数组想要通过sort方法来对学生数组进行排序发生报错,其只能对整数类型的数组进行排序,要想实现排序只能通过实现Comparable接口,并实现其中的compareTo方法.
import java.util.Arrays;class Student implements Comparable{private String name;private int score;public Student(String name, int score) {this.name = name;this.score = score;}@Overridepublic String toString(){return "[" + this.name + ":" + this.score + "]";}public int compareTo(Object o){Student s = (Student)o;if (this.score > s.score){return -1;}else if (this.score < s.score) {return 1;}else {return 0;}}}public class Main {public static void main(String[] args) {Student[] students = new Student[] {new Student("张三",95),new Student("李四",96),new Student("王五",97),new Student("赵六",92)};Arrays.sort(students);}
}
通过在Student类中重写comparTo方法会使Array.sort中会多出来一个这个为Student类的对象比较大小的sort方法。
注 对于 sort 方法来说, 需要传入的数组的每个对象都是 "可比较" 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.
抽象类和接口的区别
抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中 不能包含普通方法, 子类必须重写所有的抽象方法
使用抽象类的意义是为了让编译器更好的校验, 像 Animal 这样的类我们并不会直接使用, 而是使用它的子类. 万一不小心创建了 Animal 的实例, 编译器会及时提醒我们.
Object类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父 类。即所有类的对象都可以使用Object的引用进行接收。
案例
class Person{}
class Student{}
public class Main {public static void function(Object obj){System.out.println(obj);}public static void main(String[]args){function(new Person());function(new Student());}}
所以在开发之中,Object类是参数的最高统一类型。但是Object类也存在有定义好的一些方法。
如toString()方法,equals()方法,hashcode()方法
toString方法
如果要打印对象中的内容,可以直接重写Object类中的toString()方法。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
对象比较equals方法
在Java中,==进行比较时:
如果==左右两侧是基本类型变量,比较的是变量中值是否相同
如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的
//object类中的equals方法
public boolean equals(Object obj){return (this == obj);//使用引用中的地址直接来进行比较
}
案例
class Person{private String name ;private int age;public Person(String name, int age){this.age = age ;this.name = name ;}}
public class Main {public static void main(String[] args) {Person p1 = new Person("李四", 20);Person p2 = new Person("李四", 20);System.out.println(p1 == p2);// 输出falseSystem.out.println(p1.equals(p2));//输出false}
}
因为Person是引用类型所以其比较的是引用变量地址是否相同,equals默认也是比较地址.改正这种错误只能进行重写equals.
class Person{private String name ;private int age;public Person(String name, int age){this.age = age ;this.name = name ;}@Overridepublic boolean equals(Object obj) {if (obj == null) {return false ;}if(this == obj) {return true ;}//如果不是Person对象 instanceof判断左边是不是右边创建的对象是返回true否则是falseif (!(obj instanceof Person)) {return false;}Person person = (Person) obj ; // 将传进来的参数重新变为要比较的形式向下转型,比较属性值return this.name.equals(person.name) && this.age==person.age ;}
}
public class Main {public static void main(String[] args) {Person p1 = new Person("gaobo", 20);Person p2 = new Person("gaobo", 20);int a = 10;int b = 10;System.out.println(a == b);//输出trueSystem.out.println(p1 == p2);// 输出falseSystem.out.println(p1.equals(p2));//输出false}
}
注:比较对象中内容是否相同的时候,一定要重写equals方法。
hashcode方法
我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法,我们可以来看案例
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}
}
public class Main{public static void main(String[] args) {Person per1 = new Person("李四", 20) ;Person per2 = new Person("李四", 20) ;System.out.println(per1.hashCode());System.out.println(per2.hashCode());}
}460141958
1163157884
可以看出两个对象的hash值不一样重写hash方法
import java.util.Objects;class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}public int hashCode() {return Objects.hash(name, age);}
}
public class Main{public static void main(String[] args) {Person per1 = new Person("李四", 20) ;Person per2 = new Person("李四", 20) ;System.out.println(per1.hashCode());System.out.println(per2.hashCode());}
}
26104872
26104872
这一次两者的hash值相同因为这一次的hash的值是根据姓名和年龄来判断的。
注
hashcode方法用来确定对象在内存中存储的位置是否相同
事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的 散列码,进而确定该对象在散列表中的位置。