面向对象
多态
1.向上转型
父类引用 引用子类对象
class Shape {
public void draw() {System.out.println("Shape::draw()");}
}
class Rect extends Shape{
@Override
public void draw() {System.out.println("♦");}
}
class Cycle extends Shape {
@Override
public void draw() {System.out.println("●");}
}
public static void main1(String[] args) {
Shape shape1 = new Rect(); //通过Shape来引用子类对象,向上转型
shape1.draw(); //父类引用调用draw方法,子类也重写了draw方法,那么就发生了动态绑定
Shape shape2 = new Cycle();
shape2.draw();
}
//其中:
Rect rect = new Rect();
//向上转型可以写成
Shape shape1 = new Rect();
//或
Rect rect = new Rect();
Shape shape1 = rect;
此时 shape1是一个父类 (Shape) 的引用, 指向一个子类 (Rect) 的实例. 这种写法称为 向上转型.
2.动态绑定
如上面的程序,子类Rect、Cycle重写了父类Shape的draw方法,父类引用调用draw方法时,调用子类重写的draw方法,此时叫发生动态绑定
运行时绑定:通过父类引用 调用父类和子类同名的覆盖方法。
3.方法重写
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
a、方法名称相同
b、参数列表相同【参数的个数+参数的类型】
c、返回值相同【特殊:返回值也可以是协变类型】
注意:
1、普通方法可以重写, static方法不能重写
2、private修饰的方法不能重写
3、final修饰的方法不能重写
4、子类方法的访问权限要大于等于父类的访问权限
编译时绑定:通过函数的重载实现的。编译的时候,会根据你给的参数的个数和类型,在编译期,确定你最终调用的一个方法。
在构造方法中调用重写的方法(一个坑)
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
// 执行结果
D.func() 0
多态总结:无论是哪种编程语言, 多态的核心都是让调用者不必关注对象的具体类型. 这是降低用户使用成本的一种重要方式.
抽象类
在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个抽象方法(abstract method), 包含抽象方法的类我们称为抽象类(abstract class)
class Shape {
public abstruct void draw();
}
在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体
代码).
对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类。
1、包含抽象方法的类,叫做抽象类。
2、 是抽象方法: 一个没有具体实现的方法,被abstract修饰。
3、 抽象类是不可以被实例化的。new
Shape shape = new Shape();
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
4、因为不能被实例化,所以,这个抽象类,其实只能被继承。抽象类最大的作用,就是为了被继承。
5、抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用
abstract class Shape {
abstract public void draw();
void func() {
System.out.println("func");
}
}
class Rect extends Shape {
...
}
public class Test {
public static void main(String[] args) {
Shape shape = new Rect();
shape.func();
}
}
// 执行结果
func
6、一个普通类,继承了一个抽象类,那么这个普通类当中,需要重写这个抽象类的所有的抽象方法。
7、抽象方法不能是 private 的。
abstract class Shape {
abstract private void draw();
}
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private
8、一个抽象类A,如果继承了一个抽象类B,那么这个抽象类A,可以不实现抽象父类B的抽象方法
9、结合第8点,当A类再次被一个普通类继承后,那么A和B的这两个抽象类当中的抽象方法,必须被重写
10、抽象类不能被final修饰,抽象方法也不可以被final修饰。
抽象类的作用:抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
普通的类也可以被继承, 普通的方法也可以被重写, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验。使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
接口
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.
-
使用interface来修饰一个接口。interface IA{}
-
接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).
-
接口中的方法一定是抽象方法, 因此可以省略 abstract,
-
接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字default来修饰这个方法。
interface IShape { public abstract void draw();//抽象方法 默认public abstract //public void func(){ }//普通方法不能有具体方法的实现 default public void func(){ //如果想实现,就用关键字default System.out.println("hahaha"); } /* default public void func2(){ System.out.println("hahaha"); } public static void funcStatic(){ //可以用static System.out.println("hahaha"); }*/ //一键注释ctrl+shift+/ ,消除注释ctrl+shift+/ }
-
接口当中,可以有static的方法。
-
接口里面的所有的方法都是public的,因此可以省略 public
-
抽象方法,默认是public abstract的
-
接口是不可以被通过关键字new来实例化的
-
类和接口之间的关系是通过implements实现的。
-
当一个类实现了一个接口,就必须要重写接口当中的抽象方法。
-
接口当中的成员变量,默认是public static final修饰的。
-
当一个类实现一个接口之后,重写这个方法的时候,这个方法前面必须加上public.
-
一个类可以通过关键字extends继承一个抽象类或者普通类,但是只能继承一个类。同时,也可以通过implements实现多个接口,接口之间使用逗号隔开就好。
-
那么接口和接口之间会存在什么样的关系呢?
接口和接口之间可以使用extends来操作他们的关系,此时,这里面意为:拓展。 implements 继承接口. 此时表达的含义是 “实现”。
一个接口B 通过extends来 拓展另一个接口C的功能。 此时当一个类通过implements实现这个接口B的时候,此时重写的方法不仅仅是B的抽象方法,还有他从C接口,拓展来的功能[方法]。
一个错误的代码:
interface IShape {
abstract void draw() ; // 即便不写public,也是public
}
class Rect implements IShape {
void draw() { //这里必须写public
System.out.println("□") ; //权限更加严格了,所以无法覆写。
}
}
完整格式是:
interface IMessage{
public static final int a = 10;
public abstract void func();
}
简略版是:
interface IMessage{
int a = 10;
void func();
}
实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
现在我们通过类来表示一组动物
class Animal{
protected String name;
public Animal(String name){
this.name = name;
}
}
//不是所有的动物都会飞,所以,不能写到Animal类当中。如果写到另一个类当中,也不行,因为
//一个类不能继承多个类。 所以,接口。
interface IFlying{
void fly();
}
interface ISwimming{
void swim();
}
interface IRunning{
void run();
}
//鸟飞
class Bird extends Animal implements IFlying{
public Bird(String name){
super(name);
}
@Override
public void fly() {System.out.println(this.name+"正在飞!");}
}
//青蛙跑,游泳
class Frog extends Animal implements IRunning,ISwimming{
public Frog(String name) {
super(name);
}
@Override
public void run() {System.out.println(this.name+"正在跑");}
@Override
public void swim() {System.out.println(this.name+"正在游泳");}
}
//鸭子飞,跑,游泳
class Duck extends Animal implements IRunning,ISwimming,IFlying{
public Duck(String name){
super(name);
}
@Override
public void run() { System.out.println(this.name+"正在跑");}
@Override
public void swim() { System.out.println(this.name+"正在游泳");}
@Override
public void fly() { System.out.println(this.name+"正在飞!");}
}
public class Test4 {
public static void runFunc(IRunning iRunning){
iRunning.run();
}
public static void swimFunc(ISwimming iSwimming) {
iSwimming.swim();
}
public static void flyFunc(IFlying iFlying){
iFlying.fly();
}
public static void main1(String[] args) {
runFunc(new Duck("鸭子"));
runFunc(new Frog(("青蛙")));
}
public static void main2(String[] args){
flyFunc(new Duck("鸭子"));
flyFunc(new Bird("鸟"));
}
public static void main(String[] args) {
swimFunc(new Duck("鸭子"));
swimFunc(new Frog("青蛙"));
}
}
接口使用实例
给对象数组排序:
给定一个学生:
class Student{
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序)
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12,"张杰",99.9);
students[1] = new Student(23,"任嘉伦",90.5);
students[2] = new Student(36,"朱一龙",85);
System.out.println(Arrays.toString(students));
}
按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?
Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to
java.lang.Comparable
仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定?是按照年龄还是名字还是分数呢?这就需要我们额外指定.
让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法
class Student implements Comparable<Student>{
......
//谁调用这个方法 谁就是this
@Override
public int compareTo(Student o){
/*if (this.age>o.age){
return 1;
}else if (this.age==o.age){
return 0;
}else{
return -1;
}*/
//return this.age-o.age;
//return o.age-this.age;
return (int)(this.score-o.score);
//return this.name.compareTo(o.name);
}
}
public static void main(String[] args){
......
/*if (student1.compareTo(student2)>0){
}*/
System.out.println(student[0].compareTo(student[1]));
}
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于 0 的数字;如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0;再次执行程序, 结果就符合预期了.
注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.
以为 Comparable方法麻烦一些,所以用Comparator更灵活,更改比较规则更方便
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);
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1,Student o2){
return o1.name.compareTo(o2.name);
}
}
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12,"张杰",99.9);
students[1] = new Student(23,"任嘉伦",90.5);
students[2] = new Student(36,"朱一龙",85);
AgeComparator ageComparator = new AgeComparator();
ScoreComparator scoreComparator = new ScoreComparator();
NameComparator nameComparator = new NameComparator();
Arrays.sort(students,ageComparator);//比较 默认是从小到大的顺序
}