篮球哥温馨提示:编程的同时不要忘记锻炼哦!
潇洒的甩了一下头,假发却飞了出去 —— 程序猿
1、抽象类
1.1 抽象类的概念
首先来看到抽象这两个字,抽象其实是与具体对应的概念,在我们面向对象的概念中,所有的对象都是由类来描述的,如果我们反过来呢?所有类都是用来描述对象的吗?不一定!如果一个类中没有包含足够的信息来描述对象,这样的类就是抽象类。
拿我们上期举过的动物类来说,狗和猫,分别吃的是狗粮和猫粮,为了实现多态,子类重写了父类的 eat 方法,那其实很显然我们父类 eat 其实并没有什么实际的工作,主要的工作都是子类重写的 eat 方法,像这种没有实际工作的方法,我们就可以把他设计成一个**抽象方法(abstract mewthod),而包含抽象方法的类,我们称为抽象类(abstract class)**。
1.2 抽象类的语法
在Java中,被 abstract 修饰的类为抽象类, 抽象类中被 abstract 修饰的方法为抽象方法,抽象方法不能有具体的实现。
public abstract class Animal {
private String name;
private int age;
//抽象类中可以有构造方法,用来初始化抽象类的成员变量
public Animal() {};
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
//抽象类的方法没有加限定符,默认是public
int getAge() {
return age;
}
abstract void eat(); //抽象方法没有加访问限定符时,默认是public
abstract void cry(); //抽象方法没有加访问限定符时,默认是public
}
上述代码我们来分析一下:
首先 Animal 是一个抽象类,从上往下看,定义了两个成员变量,还有构造方法,普通方法,所以可以说明抽象类也是类,内部可以包含普通方法和属性以及构造方法,在往下, 被 abstrac t修饰的方法就是抽象方法,抽象方法不能实现。
1.3 抽象类的特性
那么既然上面说抽象类也是类,抽象类可以实例化吗?不可以,他是抽象的,天生就是被继承的!
抽象类的抽象方法可以被 private 修饰吗?这显然不可以啊,private 是只能在当前类访问,抽象类是被继承的,如果是非抽象类继承,是需要重写抽象类里面的方法的,你都只能在本类中访问,如何重写呢?(抽象方法没有加访问限定符,默认是public)
抽象方法可以被 static 和 final 修饰吗?先说 static,被他修饰的,是类的属性,每个对象公共的,不是用来给你继承的,所以你如何重写被static修饰的抽象方法?再来说 final,final 修饰的不能被重写,之前就提到过。
抽象类必须被继承吗?这个问题你想一想,抽象类本身不能实例化对象,如果你不继承的话,你这个类有什么意义呢?并且子类是普通类继承了抽象类则必须重写抽象类的抽象方法,如果不想重写子类也必须被 abstract 修饰,也就是子类也得是抽象类,那如果还有一个类去继承了这个子类,则要重写这两个抽象类的抽象方法。
抽象类一定要包含抽象方法吗?不一定,只要你想就可以不包含,但是包含抽象方法的一定是抽象类!
抽象类可以有构造方法吗? 为什么抽象类不能实例化还要有构造方法?如果有这个问题,完全是前面没有学好,抽象类是被继承的,他是父类,那么子类总要实例化吧?实例化子类先通过子类构造方法得调用父类的构造方法吧,父类构造方法是用来初始化父类成员变量的吧?你说抽象类能不能有构造方法?
1.4 抽象类的作用
既然抽象类不能被实例化,想使用还必须创建该子类对象,还得子类重写抽象方法,那有什么意义呢?我用普通类也可以这样啊,普通的方法难道不能被重写吗?我就不用抽象类!
如果思维真的被这样限制的话,请你一定要打开,上面确实没毛病,但是,使用抽象类相当于多了一重编译器的校验,就比如说,实际工作并不需要父类去完成,如果你不小心使用了父类呢?如果父类是抽象类,编译器则会提示错误,让我们尽早发现,很多语法存在都是为了"预防出错",比如 final 修饰变量,我就告诉你编译器了,这个变量我不能修改,不小心修改也能提醒我们,所以合理的利用编译器的校验,在实际开发中是有意义的。
2、接口
2.1 接口的概念
在我们实际生活中,有特别多的接口,随便想几个就是,手机的充电器接口,3.5毫米耳机接口,但现在几乎消失了,再比如我们电脑USB接口,插座接口,只要你使用规范合理,都能使用这些接口,那如果你不规范,你拿一个耳机线的接口,想去接USB接口,显然不符合规范啊,那你还能用吗?
所以从上述的讲述中,接口就是公共行为规范标准,在大家实现(使用)接口时,只要符合规范标准,就可以通用,在Java中,可以把接口看作:多个类的公共规范,是一种引用数据类型。
2.2 接口的语法规则
接口的定义与类的定义是大幅度相同的,把 class 关键字 换成 interface 关键字:
public interface ITestInterface {
public static final int a = 10;
int b = 10; //接口中的变量默认是 public static final修饰的
public abstract void test1(); //默认是 public abstract修饰的,可以不写
void test2(); //如果没有写修饰符,默认也是public abstract 修饰的
}
建议(软性):
- 创建接口的时候,用大写字母 I 开口,显然易见是表示接口
- 接口的方法和属性不要加任何修饰符,保持代码的简洁
2.3 接口的简单使用
前面学习我们知道父类和子类之间是 extends 继承关系,而接口与类之间是使用 implements 实现关系。
接口不能直接使用,必须要有一个类来实现接口,并实现接口中的所有抽象方法,如果不想,则用抽象类来实现接口。
这里我们就来结合前面的学习,简单实现一个USB接口:
- USB接口:包含打开设备和关闭设备功能
- 鼠标类:实现USB接口,并且具备点击功能
- 电脑类:实现USB接口,并具备输入功能
- 电脑类:包含开机关机功能,使用USB设备功能
//USB接口
public interface IUsb {
void openDevice();
void closeDevice();
}
//鼠标类
class Mouse implements IUsb {
@Override
public void openDevice() {
System.out.println("打开鼠标!");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标!");
}
public void click() {
System.out.println("单击鼠标!");
}
}
//键盘类
class Keyboard implements IUsb {
@Override
public void openDevice() {
System.out.println("打开键盘!");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘!");
}
public void input() {
System.out.println("键盘输入!");
}
}
//电脑类
class Computer implements IUsb {
@Override
public void openDevice() {
System.out.println("打开电脑!");
}
@Override
public void closeDevice() {
System.out.println("关闭电脑!");
}
public void useDevice(IUsb usb) {
usb.openDevice(); //根据你传递的对象执行打开对应设备
if (usb instanceof Mouse) {
Mouse m = (Mouse)usb;
m.click(); //鼠标单击
} else if(usb instanceof Keyboard) {
Keyboard k = (Keyboard)usb;
k.input(); //键盘输入
}
usb.closeDevice();
}
}
//测试部分:
class TestComputer {
public static void main(String[] args) {
Computer c = new Computer();
c.openDevice(); //打开电脑
c.useDevice(new Mouse()); //传递鼠标引用
c.useDevice(new Keyboard()); //传递键盘引用
c.closeDevice(); //关闭电脑
}
}
这个代码可以读一读,涉及到前面的知识点还是有一点的,比如用到了重写,以及向下转型,进行向下转型要先进行判断,然后main方法通过传递的引用不同调用不同的方法,实现了多态,最终打印的结果大家可以简单分析测试下。
2.4 接口的特性
接口可以实例化吗?虽然接口是一种引用类型,但不能直接 new 接口的对象
接口中可以实现方法吗?接口中默认的方法被 public abstract 修饰,从 Java8 开始,接口允许实现方法,但必须被 default 修饰,同时从 Java8 开始,也允许有静态的方法.
default void function1() {
//code...
}
static void function2() {
//code...
}
接口中可以包含变量吗?与其说是变量,不是说是常量, 因为在接口中定义的变量都会隐式的指定为 public static final 修饰的。
接口中能包包含构造方法和代码块吗?不可以,因为接口不能实例化,虽然能出现变量,但是是静态的是在类加载就开辟的,接口只能理解成是特殊的类,并不是类,也不存在构造方法!
接口不是类那他会生成什么字节码文件呢?虽然接口不是类,但生成的字节码也是 class 为后缀的!
类实现接口可以不重写方法吗?这个问题跟抽象类问题很像,必须重写,接口就是用来实现的,如果不想重写,用抽象类实现接口!
2.5 实现多个接口
在类的学习中,我们学到Java中的类是不能多继承的,但是一个类可以实现多个接口,就拿我们之前的动物类来说,我有一个猫,他是动物,他既能跑但是不会游泳,但是青蛙,也是动物,他既能跑又会游泳,我们该如何用代码表示呢?
interface IRun {
void run();
}
interface ISwimming {
void swimming();
}
public class Animal {
private String name;
public Animal() {} //构造方法
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Cat extends Animal implements IRun {
public Cat() {} //构造方法
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(super.getName() + "正在跑!");
}
}
class Frog extends Animal implements IRun, ISwimming {
public Frog() {} //构造方法
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(super.getName() + "正在跑!");
}
@Override
public void swimming() {
System.out.println(super.getName() + "正在游泳!");
}
}
class Test {
public static void running(IRun r) {
r.run();
}
public static void swimming(ISwimming s) {
s.swimming();
}
public static void main(String[] args) {
new Cat("小猫").run();
Frog frog = new Frog("青蛙");
frog.run();
frog.swimming();
}
}
如上代码就是Java面向对象最常见的:一个类继承了一个类实现了多种接口,当然这里我们只是很简单的演示一下,像上面也就是说,青蛙既能跑又能游泳,所以实现了跑和游泳的接口,接着我们自己在外部实现一个对应行为的方法比如 running 方法,这样一来,我们就不去关心类型,只用关心某个类能否具备某种能力(是否实现了对应接口),甚至你添加一个机器人类,实现跑的接口,仍然可以放到 running 这个方法中一样能跑,因为我们只关心机器人具不具备跑的能力,而不去关心他机器人的类型是什么!
2.6 接口之间的继承
学习类我们之类,Java中的类不支持多继承,但是一个类可以实现多个接口,而接口与接口之间可以达到多继承的目的。
接口可以继承另一个接口,达到复用的效果,使用 extends 关键字:
interface IRunning {
void run();
}
interface ISwimming {
void swimming();
}
//两栖动物,既能跑也能游泳,还能吃饭
interface IAmphibious extends IRunning, ISwimming {
void eat();
}
class Frog implements IAmphibious {
//...code 需要实现IRunning,ISwimming,IAmphibious方法
}
接口之间的继承相当于把多个接口合并在一起。
2.7 抽象类和接口的区别
这个也是面试可能会问到一道题,那么抽象类和接口有什么区别呢?
从结构组成来看:抽象类是由普通类加抽象方法构成的,而接口是由抽象方法加全局变量构成的。
从关系上来看:一个抽象类可以实现若干个接口,接口不能继承抽象类, 但可以使用 extends 继承多个父类接口
从子类限制来看:一个子类只能继承一个抽象类,一个子类可以实现多个接口。
核心区别:抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用(不用重写),而接口中不能包含普通方法(default,static修饰除外),子类必须重写所有的抽象方法。抽象类的存在是为了让编译器更好的校验!
3、Object 类
3.1 认识Object类
这个类其实每个人都用过,但是对于初学者可能没发现,Object 是Java默认提供的一个类,Java中除了 Object 类,所有的类都是存在继承关系,也就是说,都会默认继承 Object 类,即所有对象的都可以使用 Object 的引用来接收,例如:
class Student {}
class Teacher {}
public class TestDemo {
public static void function(Object o) {
System.out.println(o);
}
public static void main(String[] args) {
function(new Student());
function(new Teacher());
}
}
打印结果:
Student@1b6d3586
Teacher@4554617c
当然Objcet类中里面也有许多方法,后期我们会随着学习的深入,都会学习到,而本期我们就简单的了解两个方法:toString,equals 方法
3.2 获取对象信息 toString
不知道在打印数组的时候我们使用 Arrays.toString(array),就可以将array数组转换成字符串返回,我们来简单看下 Object 类中 toString 的实现方法:
这里小伙伴就纳闷了,这行代码为何能将数组转换成字符串呢?错了错了!看清楚,是Arrays类里面的toString是将数组转换成字符串返回的,为什么 Arrays 类里面的可以实现?因为除 Object 类之外所有类都默认继承了 Object 类,也就是可以重写 Object 类中的方法, 也即 Arrays 类中重写的 toString 方法:
所以如果我们要将对象信息转换成字符串,在没有重写 toString 方法的前提下,也就会打印像我们上面举例的代码一样,打印出:Student@1b6d3586 类似这种东西,那我们就来重写下 toString:
class Student {
private String name;
private int age;
private double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
public class TestDemo {
public static void main(String[] args) {
Student student = new Student("李四", 15, 83);
String ret = student.toString();
System.out.println(ret);
System.out.println(new Student("张三", 12, 59.9).toString());
}
}
当然这个重写是我自己用编译器生成的,如果你是 IDEA 可以在要重写的类中右击选择 **Generat -> toString()**,可以根据情况选择内容,然后确认即可,当然生成之后你也可以做修改。
上面代码第一种打印方法,是把 toString 的返回的字符串给了一个字符串引用,在通过打印方法进行打印,而第二种则是 new 了一个匿名对象直接访问重写的 toString方法,然后配合打印方法进行打印,当然匿名对象会在后续学习。
3.3 对象的比较 equals
在Java中使用 == 号比较的话,如果左右两侧是基本类型,比较的是变量中的值是否相同,但如果左右两边是引用类型,则比较引用变量的地址是否相同,因为引用变量中存的就是地址!
显然我们要比较对象的大小肯定是要重写的,我们可以先来看下 Object 类中的 equals 方法:
那我们要比较对象的大小,对象中又有这么多的成员变量,肯定是需要挑一个属性作为比较的,所以我们就选择 age 作为比较:
class Student {
private String name;
private int age;
private double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true; //当前对象引用的地址是否等于要比较对象的地址
}
//如果不是Student类对象
if (!(o instanceof Student)) {
return false;
}
//使用age作比较 注意:因为o是Object类型,向下转型才能访问Student类的属性
return (this.age == ((Student) o).age);
}
}
public class TestDemo {
public static void main(String[] args) {
Student student1 = new Student("李四", 15, 83);
Student student2 = new Student("张三", 20, 59.9);
if (student1.equals(student2)) {
System.out.println("student1 > student2");
} else {
System.out.println("student1 < student2");
}
}
}
那么我们重写的 equals 就是按照年龄进行比较的, 如果 student1的 age 等于 student2的 age 我们就返回 true 否则返回 false,当然我们也可以按照姓名来比较,使用 this.name.equals 这个 equals就行,因为String类型本身就是引用类型,他里面也重写了 equals,至于如何操作,就交给你们啦!
下期预告:【Java SE】String类
版权归原作者 程序猿教你打篮球 所有, 如有侵权,请联系我们删除。