目录
抽象类为什么存在?
class Circle extends DrawShape {
@Override
public void draw() {
System.out.println("
");
}
}
class Rectangle extends DrawShape {
@Override
public void draw() {
System.out.println("▭");
}
}
public class Test {
public static void Draw(DrawShape drawShape) {
drawShape.draw();
}
public static void main(String[] args) {
DrawShape drawShape = new Circle();
DrawShape drawShape1 = new Rectangle();
Draw(drawShape);
Draw(drawShape1);
}
}
运行结果:
![]()
我们发现,我们定义的这个类中的方法其实并没有被执行,因为它会被子类重写,因此它起到的作用就是
构建一个大多数情况下不被执行的方法
,此外还有另外一个作用:
被继承
。
class DrawShape {
public void draw() {
System.out.println("画图形");
}
}
那么方法体就可以被忽略掉了
但是我们发现如果这么做的话编译器会报错,此时,抽象类就出现了,解决了我们此时的难题。
如何使用抽象类 关键字:abstract
当我们在不想实现方法体的成员方法(或者说
会
被子类重写
的方法
)前面加上
关键字abstract
,
此时,这个方法就变成了抽象方法,在class前面也需要加上abstract关键字,这个类就变成了抽象类。当另一个类继承该类时,子类需重写该类中的所有抽象方法。
abstract class Draw {
public abstract void draw();
}
抽象类与普通类有何不同
1.抽象类不可以被实例化但可以被继承 。
2.抽象类的引用可以引用子类的对象(向上转型)。
3.抽象类与普通类最大最突出的特点就是不能实例化,其余与普通类一样,可以有普通成变量,静态变量,静态代码块,实例代码块,构造方法,普通方法,静态方法。
4.子类必须重写所继承的抽象类中的所有抽象方法。
5.如果子类不想重写抽象方法,可以将子类设置为抽象类。(但你迟早要重写)
6.抽象方法不可以被static、final、private修饰。
接口
接口可以说是抽象类的进一步抽象。但是我认为接口使用起来更加的方便,我们将此接口想象成usb接口,当我们所有的设备使用的都是usb接口,那么我们只需准备
同一种线
就可以了,不需要再额外准备其他的线以备不时之需。此接口也类似,当我们用的时候,随便将它安到哪里它就可以使用,多方便,最重要的是,它可以多插口,此usb插口是传输文件,此usb插口是充电,此usb插口是给手机备份。按照Java来讲,它实现了类所不具备的多继承,不过我们需要将叫法改一改,
一个类可以
实现
多个接口
。所需
关键字:interface implements
接口定义
将class替换为interface
接口有何不同???
先抛个疑问:
定义在类中的成员变量不初始化会报错?? 为啥???
不同点:
1.接口用
interface
来修饰。
2.类与接口之间用
implements
来关联。
3.可以定义成员变量,但成员变量默认
被public static final修饰,需进行初始化
。
4.不可以定义 构造方法、静态代码块、实例代码块。
5.普通方法
默认为抽象方法 被public abstract修饰
。
6.如果普通方法想要
有具体实现
,我们可以
用default修饰
。
7.可以定义静态方法,且静态方法也可以有具体实现。
8.不管是static方法还是default方法,都默认被public修饰。
9.所实现接口的类中要
重写
接口中的抽象方法。
10.接口
不可以被实例化
,但接口可以引用所实现类的对象。
11.一个抽象类实现一个接口可以不重写接口中的抽象方法
12.接口继承接口:接口与接口之间可以用extends来连接,可理解为扩展
13.
一个类可以实现多个接口
,接口与接口之间用 , 隔开
14.一个类先继承父类后实现接口
三个重要接口的使用
排序
class Animal {
public String name;
public int age;
public int speed;
public Animal(String name, int age, int speed) {
this.name = name;
this.age = age;
this.speed = speed;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
", speed=" + speed +
'}';
}
}
当我们定一个这个的一个类,并且创建了如下数组,且想要将这个数组进行排序,当我们运行的时候,我们发现,编译器报错了。
public static void main(String[] args) {
Animal[] animals = new Animal[3];
animals[0] = new Animal("猎豹",13,210);
animals[1] = new Animal("猎犬",15,130);
animals[2] = new Animal("老虎",7,180);
Arrays.sort(animals);
}
让我们来思考一下编译器为什么会报错???
我们是以什么为标准给这个数组来排序的呢???我们会神奇的发现我们没有标准就把数组扔给了Arrays.sort进行排序,编译器不发脾气才怪呢。
接下来我们来讲如何将它排序。
Comparable
首先我们先让Anima这个类实现Comparable这个接口
报错是因为我们还没有重写接口中的抽象方法,
<Animal>
的意思为对Aniaml进行排序。
我们将鼠标放在Comparable上,按住Alt再按回车,就可以重写抽象方法了。
![]()
然后后我们需要将其改写为:
![]()
解释 this 和 o :
this表示对当前对象的引用,o表示对下面对象的引用。
如何确定当前对象呢???看谁调用它。
public static void main(String[] args) { Animal[] animals = new Animal[3]; animals[0] = new Animal("猎豹",13,210); animals[1] = new Animal("猎犬",15,130); animals[2] = new Animal("老虎",7,180); System.out.println(animals[0].compareTo(animals[1])); // 输出结果为:80 }
此时这个
animals[0]
就是this,
animals[1]
就是o
好啦,当我们重写完成后,再运行。
//排序前
[Animal{name='猎豹', age=13, speed=210},
Animal{name='猎犬', age=15, speed=130},
Animal{name='老虎', age=7, speed=180}]
//排序后
[Animal{name='猎犬', age=15, speed=130},
Animal{name='老虎', age=7, speed=180},
Animal{name='猎豹', age=13, speed=210}]
此时我们清楚地看到,它按照动物的速度进行排序了。
完整代码
class Animal implements Comparable<Animal> {
public String name;
public int age;
public int speed;
public Animal(String name, int age, int speed) {
this.name = name;
this.age = age;
this.speed = speed;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
", speed=" + speed +
'}';
}
@Override
public int compareTo(Animal o) {
return this.speed - o.speed;
}
}
public class Test3 {
public static void main(String[] args) {
Animal[] animals = new Animal[3];
animals[0] = new Animal("猎豹",13,210);
animals[1] = new Animal("猎犬",15,130);
animals[2] = new Animal("老虎",7,180);
System.out.println(Arrays.toString(animals));
Arrays.sort(animals);
System.out.println(Arrays.toString(animals));
}
}
Comparator
我们发现,Comparable接口可以帮助我们对数组进行排序,但很快我们又发现了新的问题,
@Override
public int compareTo(Animal o) {
return this.speed - o.speed;
}
我们只能按照动物的奔跑速度的大小进行排序,假如我们想按照动物的年龄或者名字来进行排序,那么我们就要将此方法删掉再写另外的方法,很不方便,那么有没有一劳永逸的办法呢?比较方法实现后,可以随自己调用,想按照年龄排序就传一个年龄排序的方法,想按照名字排序就传一个按年龄排序的方法,一次实现多次使用。接下来就要向大家介绍
Comparator
——构造器。
使用方法
我们首先要创建一个类,并实现Comparato,重写抽象方法。(要铭记快捷键,很好用 Alt + Enter)。
class AgeComparator implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.age - o2.age;
}
}
class NameComparator implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.name.compareTo(o2.name);
}
}
class SpeedComparator implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.speed - o2.speed;
}
}
这其中的o1和o2的意思大家应该很清晰吧,那么就不再多说了。
我们来讲一下比较字符串的这个,字符串是引用类型的与int类型的不一样,我们将鼠标放在String类型上,按住Ctrl并鼠标左键单击String,可以转到定义,我们发现它其中实现了Comparable接口,我们再用同样的方法转到Comparable的定义,发现其中有compareTo的方法,因此我们便可调用它。
o1.name.compareTo(o2.name);
![]()
![]()
当我们写好各种比较方法后,我们只需要在Arrays.sort中,连同待排序的数组一起传进去即可。
完整代码
class Animal {
public String name;
public int age;
public int speed;
public Animal(String name, int age, int speed) {
this.name = name;
this.age = age;
this.speed = speed;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
", speed=" + speed +
'}';
}
}
class AgeComparator implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.age - o2.age;
}
}
class NameComparator implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.name.compareTo(o2.name);
}
}
class SpeedComparator implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.speed - o2.speed;
}
}
public class TestDemo {
public static void main(String[] args) {
Animal[] animals = new Animal[3];
animals[0] = new Animal("猎豹",13,210);
animals[1] = new Animal("猎犬",15,130);
animals[2] = new Animal("老虎",7,180);
NameComparator nameComparator = new NameComparator();
System.out.println(Arrays.toString(animals));
Arrays.sort(animals,nameComparator);
System.out.println(Arrays.toString(animals));
}
}
Cloneable — 克隆
我定义了一个钱包类。
我们给一个类实现Clonable后,右击鼠标找到Generate,找到重写方法后,点击clone,最后确定。
此时我们的类中就有克隆方法了,我们来试试吧。
我们先实例化一个对象A,再实例化一个B当作容器,用对象A调用克隆方法,clone方法是父类Object的,因此我们要强制类型转换一下,可是我们发现他还是报错,我们可以这样解决。
(我听说这是抛异常,等我仔细学学再来告诉你们,咱们先把这个克隆用起来噢,不急)
![]()
来调用一个我们的副本:
![]()
此时,副本就产生了,从而又产生了另外一个问题,深拷贝与浅拷贝。
深拷贝与浅拷贝
深与浅又是如何定义的呢?我来为大家解读一下,对于拷贝来说,什么地方或者说从什么方面来讲会涉及到深与浅的问题?答案是:内容是否会被更改 完整来说是:
当我们更改副本的内容时,
源头是否会被改掉
;如果源头
未被改掉
则是
深拷贝
,反之,则是浅拷贝。
例子
深拷贝
public static void main(String[] args) {
int[] array1 = {1,7,9};
int[] array2 = Arrays.copyOf(array1,array1.length);//此时数组1被拷贝到数组2中
System.out.println(Arrays.toString(array2)); //我们打印来看一下
array2[0] = 10; //此时我们更改一个数据 看看源头是否被改掉
System.out.println(Arrays.toString(array1));
}
源头没有被改掉,因此是深拷贝
![]()
浅拷贝
class Number { public int val = 9; public int num = 10; } public class TestDemo3 { public static void main(String[] args) { Number number1 = new Number(); Number number2 = new Number(); Number[] array1 = {number1,number2}; Number[] array2 = Arrays.copyOf(array1,array1.length); System.out.println(Arrays.toString(array2)); System.out.println("number1.num : "+number1.num); array2[0].num = 19; System.out.println("number1.num : "+number1.num); } }
![]()
解释:
我们在数组中存的是两个引用,指向了根据Number这个类创建的两个对象。
![]()
而我们拷贝array1后,
我们发现数组二中的引用也完完全全被拷贝了过来,因此当我们通过引用来修改数据时,源 头也会被改掉,此时为浅拷贝。
克隆的深拷贝
我们将Cloneable中提到的代码修改一下,变成这样:
需变动的部分:
定义一个小偷类,偷钱包中的钱,看看我们还剩多少钱。
![]()
![]()
测试结果:
![]()
现在小偷偷了1000元,我们已经分文不剩了。
原因想必大家很清楚啦,通过引用修改数据,源头也被改掉啦,接下来让我们着手将他便为浅拷贝。
我们的目的就是要保护源数据,而拷贝的时候我们只是机械的把引用拷贝过去了,而没有拷贝引用所指向的对象,我们只需把引用所指向的对象也拷贝一份就可以了。
这部分需要变动一下,要让这个方法也将引用所指向的对象拷贝一份出来。
![]()
首先,我们要将Thief这个类实现Cloneable接口,让它也拥有克隆的能力,具体实现参照上方的讲解。
class Thief implements Cloneable{ public int stealMoney = 800; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
@Override protected Object clone() throws CloneNotSupportedException { Wallet ret = (Wallet) super.clone(); ret.thief = (Thief) this.thief.clone(); return ret; }
当我们再次运行:
![]()
我们还剩200块啦!!!