目录
一、抽象类
1、抽象类语法
抽象类:用abstract修饰的类。在抽象类中,一般都有抽象方法(顺序和个数不限),普通成员属性,成员方法,构造方法可有可无。abstract尽量放在抽象类或
抽象方法最前面,便于观察。
代码示例:
//抽象类(用abstract修饰)
abstract class Shape {
//抽象方法(用abstract修饰) 一般都有
abstract public void draw();
//普通成员变量和方法 可有可无
private String name;
public void func() {
}
public Shape() {
}
}
2、抽象类特征
一、抽象类不能直接实例化对象
Shape shape = new Shape()
//编译错误
//Shape是抽象类,无法实例化
二、抽象方法不能被private的修饰
//抽象类
abstract class Shape {
//抽象方法
abstract private void draw();
}
//编译出错
//非法的修饰组合:abstract和private
注意:抽象方法没有加限定符时,默认是public
三、抽象方法不能被final和static修饰,因为抽象方法要被子类重写
//抽象类
abstract class Shape {
//抽象方法
abstract public final void drawA();
abstract public static void drawB();
}
//编译错误
//非法的修饰符组合: abstract和final
//非法的修饰符组合: abstract和static
四、抽象类必须被继承,并且继承后父类中的所有抽象方法,子类都要重写。若不重写,子类也是抽象类,必须使用abstract修饰。
//抽象类
abstract class Shape {
//抽象方法
abstract public void drawA();
abstract public void drawB();
}
//子类继承父类并重写抽象方法
class Circle extends Shape {
public void drawA() {
}
public void drawB() {
}
}
//继承父类但仍为抽象方法,此时它是一个有三个抽象方法的抽象类
abstract class All extends Shape{
abstract public void func();
}
抽象类特征汇总:
- 抽象类不能直接实例化对象
- 抽象类不能被private修饰
- 抽象方法不能被private的修饰
- 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
- 抽象类必须被继承,并且继承后父类中的所有抽象方法,子类都要重写。若不重写,子类也是抽象类,必须使用abstract修饰。
- 抽象类中不一定包含抽象方法(这种抽象方法用不了),但有抽象方法的类一定是抽象类。
- 抽象类可以有构造方法,供子类创建对象时,初始化父类的成员变量
3、抽象类作用
说明:抽象类本身不能被实例化,要想使用只能创建该抽象类的子类,然后让子类重写该抽象类的方法。
优点:使用抽象类相当于多了一重编译器的校验。例如:
- 使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成,,而应由子类完成. 那么此时如果不小心误用成父类 了, 使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。
二、接口
1、接口语法
一、什么是接口?
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
二、语法规则
格式:接口定义和类的定义相似,将class换成interface,就定义了一个接口。
//A为接口名称,同样用一个项目中只能有一个public类或public接口
public interface A {
//抽象方法
abstract public void method1();
void method2();//默认为abstract public(可交换位置),抽象类中则不能省略
abstract void method3();
public void method4();
//以上四种都是抽象方法,但更推荐第二中,更为简洁
}
2、接口使用
说明:接口不能直接使用,必须要有一个类来实现该接口,在这个类中实现该接口的所有抽象方法
注意:
- 类和类之间(接口和接口之间)为extends,接口和类之间是implements实现关系
- 接口继承:可以用一个接口继承另外两个接口,再用一个类去实现这个接口。
//Flyable接口
interface Flyable {
void method();
}
//实现接口的类
class Bird implements Flyable {
public void method() {
}
}
例子:USB接口+Mouse类+KeyBoard类+Computer类+TestUSB类
USB接口:
package interface_USB;
public interface USB {
//抽象方法
void openDevice();
void closeDevice();
}
Mouse类:
package interface_USB;
public class Mouse implements USB{
//重写USB接口中的方法
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
//Mouse特有的方法
public void click() {
System.out.println("点击鼠标");
}
}
KeyBoard类:
package interface_USB;
public class KeyBoard implements USB {
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
public void input() {
System.out.println("键盘输入");
}
}
Computer类:
package interface_USB;
public class Computer {//在Computer内使用KeyBoard和Mouse两个类
public void powerOn() {
System.out.println("打开笔记本电脑");
}
public void powerOff() {
System.out.println("关闭笔记本电脑");
}
public void useDevice(USB usb) { //向上转型
usb.openDevice(); //无论是鼠标还是键盘都需要打开和关闭,因此放在if-else外
if(usb instanceof Mouse) {
Mouse mouse = (Mouse) usb;//向下转型
mouse.click();
}else if(usb instanceof KeyBoard) {
KeyBoard keyBoard = (KeyBoard) usb;
keyBoard.input();
}
usb.closeDevice();
}
}
TestUSB类:
package interface_USB;
public class TestUSB {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
//使用鼠标设备
computer.useDevice(new Mouse());
//使用键盘设备
computer.useDevice(new KeyBoard());
computer.powerOff();
}
}
结果演示:
打开笔记本电脑
打开鼠标
点击鼠标
关闭鼠标
打开键盘
键盘输入
关闭键盘
关闭笔记本电脑
3、接口的特性
- 接口类型是一种引用类型,但不能直接new接口的对象
- 接口的成员变量只能是常量(默认:public static final),成员方法只能是抽象方法(默认:public abstract)。和抽象类不同。
- 接口中的方法不能在接口中实现,只能由实现接口的类来实现
- 接口中的抽象方法只能被public修饰。(默认为public)
- 接口中不能有静态代码块和构造方法(在类中实现初始化)
- 接口虽然不是类,但是经过编译后字节码文件后缀也是.class
- jdk8中,接口还包含default方法
4、实现多个接口
在Java中,没有多继承,即一个类只能有一个父类。但是一个类可以实现多个接口。如下
1、先给出Flying、Running、Swimming三个接口:
Flying:
package interface_;
public interface IFlying {
void flying();
}
Running:
package interface_;
public interface IRunning {
void running();
}
Swimming:
package interface_;
public interface ISwimming {
void swimming();
}
2、给出父类Animal,子类Dog、Chicken
Animal:
package interface_;
public class Animal {
public String name;
//构造方法,便于初始化Animal中的name
public Animal(String name){
this.name=name;
}
public void bark(){
System.out.println("叫");
}
}
Dog:
package interface_;
//要先extends(继承),后implements(实现)
public class Dog extends Animal implements ISwimming,IRunning{
public int age;
//重写接口中的抽象方法
@Override
public void swimming() {
System.out.println(super.name + this.age + "swimming");
}
@Override
public void running() {
System.out.println(super.name + this.age + "running");
}
public Dog(String name,int age){
super(name); //调用父类构造完成初始化初始化
this.age=age;
}
//重写从父类继承的bark()方法,以发生多态
@Override
public void bark() {
System.out.println(this.name + "汪汪叫");
}
}
Chicken:
package interface_;
public class Chicken extends Animal implements IRunning,IFlying{
//重写接口中的抽象方法
@Override
public void running() {
System.out.println(super.name + "在疾跑中");
}
@Override
public void flying() {
System.out.println(super.name + "在飞翔");
}
public Chicken(String name) {
super(name);
}
//重写从父类继承的方法
@Override
public void bark() {
System.out.println(super.name + "在报晓");
}
}
3、最后给测试的类Interface
Interface:
package interface_;
public class Interface {
//实现多态
public static void show(Animal animal) { //向上转型,实现多态
animal.bark(); //动态绑定,实现多态
}
// 法一
//实现接口重写方法的多态
public static void run(IRunning irunning) {
irunning.running();
}
public static void fly(IFlying iflying) {
iflying.flying();
}
public static void swim(ISwimming iswimming) {
iswimming.swimming();
}
// 法二
//访问接口抽象类的重写方法
public static void scan(Animal animal) {//注意static
if(animal instanceof Dog) {
((Dog) animal).swimming();
((Dog) animal).running();//重写后,Dog的running和Chicken的running不一样
}else if(animal instanceof Chicken){
((Chicken)animal).flying();
((Chicken)animal).running();
}
}
public static void main(String[] args) {
Dog dog = new Dog("油条",3);
Chicken chicken = new Chicken("鸡你太美");
//dog实现接口抽象方法重写,实现多态
System.out.println("法一");
System.out.println("dog实现接口抽象方法重写,实现多态");
run(dog);
//fly(dog); Dog没有继承Flying这个接口,所以该行会报错
swim(dog);
System.out.println("----------------");
//chicken实现接口抽象方法重写,实现多态
System.out.println("chicken实现接口抽象方法重写,实现多态");
run(chicken);
fly(chicken);
//swim(chicken); Chicken没有继承Swimming这个接口,所以该行会报错
System.out.println("----------------");
//调用方法从而访问接口中抽象方法的重写方法
System.out.println("法二");
System.out.println("调用方法从而访问接口中抽象方法的重写方法");
scan(dog);
scan(chicken);
System.out.println("----------------");
//重写从父类继承的方法,实现多态
System.out.println("重写从父类继承的方法,实现多态");
dog.bark();
chicken.bark();
}
}
4、结果演示:
法一
dog实现接口抽象方法重写,实现多态
油条3running
油条3swimming
----------------
chicken实现接口抽象方法重写,实现多态
鸡你太美在疾跑中
鸡你太美在飞翔
----------------
法二
调用方法从而访问接口中抽象方法的重写方法
油条3swimming
油条3running
鸡你太美在飞翔
鸡你太美在疾跑中
----------------
重写从父类继承的方法,实现多态
油条汪汪叫
鸡你太美在报晓
5、接口使用实例
5.1、给对象数组排序
法一法二核心:让Person类实现Comparable接口
法一:重写compareTo,toString,利用Arrays下的sort,toString(age排序)
package object_class;
import java.util.Arrays;
class Person implements Comparable{
private String name;
private int age;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
//重写Comparable中抽象方法
@Override
public int compareTo(Object object) {
if(object instanceof Person) {
Person person = (Person) object;
if(this.age>person.age) {
return -1;
}else if(this.age<person.age) {
return 1;
}else {
return 0;
}
}
return 0;
}
//重写toString,和Arrays下的toString配合使用
@Override
public String toString() { //如果不重写toString,返回值是一个地址
return "{"+this.name+","+this.age+"}";
}
}
//测试类
public class SortTest {
public static void main(String[] args) {
Person[] peoples = {
new Person("zhangsan",19),
new Person("wangwu",17),
new Person("alan",21)
};
//利用Arrays下的sort,toString
Arrays.sort(peoples);
System.out.println(Arrays.toString(peoples));
}
}
结果演示:
[{alan,21}, {zhangsan,19}, {wangwu,17}]
解释:
- Person类中:重写接口Comparable下的抽象方法compareTo,重写从Object类继承的方法toString
- 利用Arrays下的sort排序,调用sort时,会自动调用重写的compareTo方法
- 利用Arrays下的toString输出,实现把Person型的对象中的成员转变成字符串输出(但输出的是地址),重写的toString保证输出的是对象成员
法二:重写compareTo,toString,在测试类中重新实现sort,利用Arrays下的toString(name排序)
package object_class;
import java.util.Arrays;
//Person类实现Comparable
class Person implements Comparable{
private String name;
private int age;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
//重写Comparable中抽象方法compareTo
@Override
public int compareTo(Object object) {
if(object instanceof Person) {
Person person = (Person) object;
if(this.name.compareTo(person.name)>0) { //compareTo在这里是源码重写的
return -1;
}else if(this.name.compareTo(person.name)<0) {
return 1;
}else {
return 0;
}
}
return 0;
}
//重写toString,和Arrays下的toString配合使用
@Override
public String toString() { //如果不重写toString,返回值是一个地址
return "{"+this.name+","+this.age+"}";
}
}
//测试类
public class SortTest {
//我们自己重新实现sort
public static void bubbleSort(Comparable[] peoples) {
for (int i = 0; i < peoples.length-1; i++) {
for (int j = 0; j < peoples.length-1-i; j++) {
if(peoples[j].compareTo(peoples[j+1])>0) {
Comparable tmp = peoples[j];
peoples[j] = peoples[j+1];
peoples[j+1] = tmp;
}
}
}
}
public static void main(String[] args) {
Person[] peoples = {
new Person("zhangsan",19),
new Person("wangwu",17),
new Person("alan",21)
};
//重新实现sort,利用Arrays下的toString
bubbleSort(peoples);
System.out.println(Arrays.toString(peoples));
}
}
结果演示:
[{zhangsan,19}, {wangwu,17}, {alan,21}]
解释:
- Person类中:重写接口Comparable下的抽象方法compareTo,重写从Object类继承的方法toString
- 在测试类SortTest中自己写一个bubbleSort方法,作用和Arrays下的sort一致
- 利用Arrays下的toString输出,重写从Object类继承的方法toString保证:输出的是对象成员,而不是对象地址
法三:创建新类成为比较的类(泛型,用<>),传类名
法三核心:创建以年龄(姓名)比较的类,来实现Comparator接口
package compare_class;
import java.util.Arrays;
import java.util.Comparator;
class Person {
public String name;
public int age;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
//重写toString,和Arrays下的toString配合使用
@Override
public String toString() { //如果不重写toString,报错
return "{"+this.name+","+this.age+"}";
}
}
//Comparator中有compare和equals,但是equals是从object类继承的,不算是Comparator的抽象方法
// 所以用到Comparator不需要重写equals
//成员变量非private都可以用这种类实现比较
//以年龄比较
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1,Person p2) {
return p1.age-p2.age;
}
}
//以姓名比较
class NameComparator implements Comparator<Person> {
public int compare(Person p1,Person p2) {
//这里的compareTo是Arrays下的,
// 所以不需要Person去实现Comparable,再重写compareTo
return p1.name.compareTo(p2.name);
}
}
//测试类
public class Test {
public static void main(String[] args) {
Person[] peoples = {
new Person("zhangsan",19),
new Person("wangwu",17),
new Person("alan",21)
};
System.out.println("以年龄排序:");
AgeComparator ageComparator = new AgeComparator();//创建年龄的类
Arrays.sort(peoples,ageComparator);
System.out.println(Arrays.toString(peoples));
System.out.println("--------------------------------------");
System.out.println("以姓名排序:");
NameComparator nameComparator = new NameComparator();
Arrays.sort(peoples,nameComparator);
System.out.println(Arrays.toString(peoples));
}
}
结果演示:
以年龄排序:
[{wangwu,17}, {zhangsan,19}, {alan,21}]
--------------------------------------
以姓名排序:
[{alan,21}, {wangwu,17}, {zhangsan,19}]
解释:
- Comparator接口中有compare和equals两个方法,但是equals是从object类继承的,不算是Comparator的抽象方法 。所以用到Comparator不需要重写equals
- 成员变量非private就可以用这种类实现比较,如Penson类中成员变量是public
- 以姓名排序时,用到的compareTo可以通过Comparator找到,所以不需要Person去实现Comparable,再重写compareTo。
- 在Test中,AgeComparator和NameComparator要先new对象,然后再把对象引用传给Arrays下的sort。
5.2、总结
以后使用自定义类型比较大小,那么必须让这个类具备可比较的功能。
- 可以让目标类实现接口Comparable,再重写compareTo实现比较大小
- 或者新建类,在实现泛型Comparator接口的类中,再重写compare来实现比较大小
6、Clonable接口和深拷贝
6.1、Clonable接口及浅拷贝
package clonable_deepcopy;
class Student implements Cloneable{
public String id;
public Student(String name) {
this.id = name;
}
@Override
public String toString() {
return "Person{" +
"id='" + id + '\'' +
'}';
}
//重写Cloneable接口的克隆方法
//throws CloneNotSupportedException表示异常
@Override
protected Object clone() throws CloneNotSupportedException { //返回Object类型
return super.clone();//protected修饰,不同包中需要用super访问
}
}
public class Clon{
public static void main(String[] args) throws CloneNotSupportedException {
Student person1 = new Student("1234");
Student person2 = (Student) person1.clone();//向下转型
System.out.println(person2);
}
}
总结克隆:
1、实现Cloneable 2、重写clone 3、声明异常 4、向下转型
6.2、深拷贝
package clonable_deepcopy;
class Money implements Cloneable {
public int m=100;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Student implements Cloneable{
public Money money = new Money();
//深拷贝
@Override
protected Object clone() throws CloneNotSupportedException { //返回Object类型
Student tmp = (Student) super.clone();//克隆了student1,使用Cloneable中的克隆 这里必须是super才行
tmp.money = (Money) this.money.clone();//克隆了money,使用Money中的克隆
return tmp;
}
}
public class Clon{
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student();
System.out.println(student1.money.m);
Student student2 = (Student) student1.clone();
student1.money.m=1234;
System.out.println(student1.money.m);
System.out.println(student2.money.m);
}
}
100 //Student类中Money类型money初始值
1234 //改变对象student1中money类型中money值
100 //student2不受影响,说明student1和student2中money指向的不是同一块空间,即完成了深拷贝
解释:一个类作为另一个类的成员,此时需要深拷贝
1、实现Cloneable 2、重写clone 3、声明异常(这三部分都需要对Student和Money类进行操作 )4、向下转型
注意:深拷贝时第一步利用Cloneable中的clone,用super.colone()拷贝student1对象,第二步利用Money中的重写的clone,用this.clone克隆这个对象(student1)的成员(money),最后返回克隆对象的引用
6.3、浅拷贝和深拷贝的区别
一、浅拷贝:
二、深拷贝:
7、抽象类和接口的区别
7.1、相同点
- 都不能实例化对象
- 若要使用,都需要重写抽象方法
7.2、不同点
核心区别
- 抽象类中可以包含非抽象类方法和成员, 这样的普通方法和成员可以被子类直接使用(不必重写),而接口中不能包含普通方法,子类必须重写所有的抽象方法
其它区别
抽象类不能由private修饰,而接口只能由public修饰- 子类通过extends继承抽象类,子类通过implements实现接口,并且都需要实现抽象方法
- 抽象类中子类只能继承一个抽象类,但接口中子类能实现多个接口
- 抽象类可以有构造方法,而接口不能有构造方法。
- 抽象类可以有成员变量,而接口只有常量,默认为public static final。
8、object类以及相关知识
8.1、object类
object类是所有类的父类。我们写的类默认继承object类。所以所以类的对象都可以用object的引用进行接收
8.2、toString()
Object类中的toString()方法实现:
// Object类中的toString()方法实现:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
8.3、equals()
在 Java 中, == 进行比较时:
- 如果==左右两侧是基本类型变量,比较的是变量值是否相同
- 如果==左右两侧是引用类型变量,比较的是引用变量的地址是否相同
- 若要比较对象中的内容,必须重写Object类的equals()方法,因为equals()方法也是默认按地址比较的
1、Object类中的equals方法实现(比较地址):
// Object类中的equals方法
public boolean equals(Object obj) {
return (this == obj); // 使用引用中的地址直接来进行比较
}
//this(person1): person@483
//obj(person2): person@484
2、重写equals(),使之可以比较引用类型(比较对象具体内容):
@Override
public boolean equals(Object object) {
if(object==null) {
return false;
}
if(this==object){ //比较二者引用在同一个地址 //调用equals的this不可能是null
return true;
}
if(!(object instanceof Person)) { //子类不是Person类,就返回false
return false;
}
Person person = (Person) object; //向下转型
//这里的equals比较String类型,已经被系统重写,不再是object的equals
return this.name.equals(person.name) && this.age==person.age;
}
8.4、hashcode()
Object类中hashCode()源码:
解释:该方法是一个native方法,底层是由C/C++写的。
public native int hashCode();
1、但如果不重写hashCode,相同内容的对象的hashCode值不一样
package object_class;
class Person {
private String name;
private int age;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
}
public class Test {
public static void main(String[] args) {
Person person1 = new Person("zhangsan",18);
Person person2 = new Person("zhangsan",18);
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());
}
}
结果演示:
460141958
1163157884
2、重写后相同内容的对象hashCode相同(在数据结构哈希表中):
//重写hashCode
@Override
public int hashCode() {
//计算对象的位置
return Objects.hash(name,age);
}
结果演示:
-1461067297
-1461067297