作者:
@小鱼不会骑车
专栏:
《java练级之旅》
个人简介:
一名专科大一在读的小比特,努力学习编程是我唯一的出路
任务一:抽象类
任务二:接口
我们在讲抽象类之前,先带大家熟悉一下普通类
class Fruits {
String name;
public void colour() {
System.out.println("颜色");
}
public Fruits(String name) {
this.name = name;
}
}
class Apple extends Fruits {
Apple(String name) {
super(name);
}
@Override
public void colour() {
System.out.println(name+" 是红色的");
}
}
class Banana extends Fruits{
@Override
public void colour() {
System.out.println(name+" 是黄色的");
}
public Banana(String name) {
super(name);
}
}
public class Test {
public static void draw(Fruits fruits) {
fruits.colour();
}
public static void main(String[] args) {
draw(new Apple("苹果"));
draw(new Banana("香蕉"));
}
}
上述代码它们的父类是一个水果,有没有说他是哪一个水果?没有吧!我们的水果有很多种,他并没有办法完整的描述一个图型,所以形容这样一个东西,就要把它的类设计为一个抽象类,下面的篇幅就将解释,什么是抽象类?
要完成的任务
任务一:抽象类
普通类是一个完善的功能类,可以直接产生实例化对象,并且在普通类中可以包含有构造方法、普通方法、static方法、常量和变量等内容。而抽象类是指在普通类的结构里面增加抽象方法的组成部分,那么具体的组成部分是什么呢?请往下看
普通类
class Fruits1 {
String name;
public void colour() {
System.out.println("颜色");
}
}
抽象类
abstract class Fruits2 {//用abstract修饰就是抽象类
String name;
public void colour() {
System.out.println("颜色");
}
}
他们直接我们能够直接看到的区别就是抽象类被abstract
修饰了
大家再看这串代码块
public static void main(String[] args) {
Fruits1 fruits1=new Fruits1("苹果");
Fruits2 fruits2=new Fruits2("香蕉");
// 会报错,因为我们的抽象类不能实例化
}
如果想要解决的话,我们可以
Fruits2 fruits2=new Banana("香蕉");//发生了类型提升
虽然我们不可以对抽象类进行实例化,但是我们可以对它的子类进行实例化,最后再把这个对象赋值给Fruits2这个类的引用。(也就是类型提升)
总结:
前面不是讲到了嘛,我们并不能知道我们抽象类里面的方法到底是形容哪个子类的,所以我这个类里面的方法就可以不用具体的实现,但是不去具体实现是有一个前提的,那就是需要用abstract
修饰
例一:不用abstract修饰(两个都是抽象类进行对比)
abstract class Fruits1 {
String name;
public void colour() {
System.out.println("颜色");
}
}
例二:用abstract修饰
abstract class Fruits2 {
String name;
//我们的方法被abstract修饰
abstract public void colour();
}
大家经过对比是不是发现,咦?原来用了abstract
修饰就可以不用去具体实现这个方法。
再去反过头看例一,我们父类中的方法更多的是会被重写的,所以不需要去管父类的方法究竟实现了什么,也就是可以直接如例二一样不去实现这个方法,是不是更简洁,看起来更高大上。
但是!!!
如果我们的类不是抽象类,那么就无法对其方法进行abstract
修饰,只有是抽象类的方法才可以!!!
class Fruits1 {
String name;
abstract public void colour() {
System.out.println("颜色");
}
public Fruits1(String name) {
this.name = name;
}
}
错误信息
不过我们的抽象类中是可以有抽象方法和非抽象方法的。
abstract class Fruits1 {//抽象类
public void print() {//非抽象方法
System.out.println("我是一个抽象类");
}
}
public class Test1 extends Fruits1{
public static void main(String[] args) {
Fruits1 fruits1=new Test1();
fruits1.print();
}
}
抽象类中有构造方法么?
由于抽象类里会存在一些属性,那么抽象类中一定存在构造方法,其存在目的是为了属性的初始化。并且子类对象实例化的时候,依然满足先执行父类构造,再执行子类构造的顺序。
abstract class A{//定义一个抽象类
public A(){
System.out.println("我是A类构造方法");
}
public abstract void print();//抽象方法,没有方法体,有abstract关键字做修饰
}
//单继承
class B extends A{//B类是抽象类的子类,是一个普通类
public B(){
super();//父类的构造方法没有参数,可以不写。
System.out.println("我是B类构造方法");
}
@Override
public void print() {//强制要求覆写
System.out.println("Hello World !");
}
}
public class Test1 {
public static void main(String[] args) {
A a = new B();//向上转型
}
}
可以直接调用抽象类中用static声明的方法么?
任何时候,如果要执行类中的static方法的时候,都可以在没有对象的情况下直接调用,对于抽象类也一样。
总结:
abstract
修饰的,没有具体的实现abstract class Fruits {//抽象方法
String name;//成员变量
abstract public void colour();//抽象方法
public Fruits(String name) {//构造方法,用于初始化
this.name = name;
}
}
//普通类继承抽象类
class Apple extends Fruits {
Apple(String name) {
super(name);
}
@Override
public void colour() {
System.out.println(name+" 是红色的");
}
}
//普通类继承抽象类
class Banana extends Fruits{
@Override
public void colour() {
System.out.println(name+" 是黄色的");
}
public Banana(String name) {
super(name);
}
}
public class Test {
public static void draw(Fruits fruits) {
fruits.colour();
}
public static void main(String[] args) {
draw(new Apple("苹果"));
draw(new Banana("香蕉"));
}
}
class Apple extends Fruits {
//报错!!!
@Override
public void colour() {
System.out.println(name+" 是红色的");
}
}
class Banana extends Fruits{
//报错!!!
public Banana(String name) {
super(name);
}
}
但是如果我们继承的是普通类,就不会去报错了,其实抽象类这个做法就是为了让我们的代码更严谨些,如果我写的是普通类,我忘记对父类的方法进行重写,,那么系统也没有报错,我以后运行的话,就可能会有未知的错误随时可能发生。
所以我们要注意,继承抽象类时一定要重写抽象类的方法!!!
如果我真的真的不想去重写这个方法呢?
也有解决的办法
abstract class Fruits {
String name;
abstract public void colour();
public Fruits(String name) {
this.name = name;
}
}
//就是将我的子类也变成抽象类
abstract class Apple extends Fruits {
public Apple(String name) {
super(name);
}
}
不过啊,出来混总归是要还的,你这次可以不去重写,那么你下次呢?只要你想用这个抽象类(哪怕是这个抽象类其中的任意一个方法),你就必须得重写这个抽象类中全部的方法,不过你如果想单独使用其中一个方法也有解决的方法,答案就在接口中!
abstract
修饰的抽象类。其实大家就可以理解为,重写需要满足的条件是什么?
用private
修饰时
如果我们父类中的方法被private
修饰之后,我们还能访问到这个方法嘛?不能啊.
具体关于权限修饰符大家可以访问这篇博客public权限修饰符
abstract class Fruits {
String name;
abstract private void colour();//编译出错
public Fruits(String name) {
this.name = name;
}
}
错误信息
如果我们的方法被static
修饰时?
当我们的方法被static
修饰时,他就不属于对象,而是属于类,这样我们再去运行也会出错
abstract class Fruits {
String name;
abstract static void colour();
//运行出错,不可以用static
public Fruits(String name) {
this.name = name;
}
}
当我们用final
修饰时
被final
修饰的方法是不可以再去改变的,就像变量被final修饰之后成为常量,你还可以去修改这个常量嘛?不能啊!
abstract class Fruits {
String name;
final abstract void colour();//编译出错
public Fruits(String name) {
this.name = name;
}
}
抽象类 | 普通类 |
---|---|
抽象类不能实例化 | 普通类可以实例化 |
抽象类中可以包含非抽象方法和抽象方法 | 只能包含非抽象方法 |
1.抽象类使用
abstract
修饰。
2.抽象类不能实例化,需要依靠子类采用向上转型的方式处理。
3.抽象类当中可以有抽象方法或者非抽象方法。
4.抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
5.什么是抽象方法?一个方法被abstruct修饰的,没有具体的实现。(只要包含抽象方法就一定是抽象类!!!)。
6.当一个普通(子)类继承了这个抽象类,就必须重写这个抽象类当中的抽象方法。
7.抽象类存在的最大意义就是为了被继承。
8.抽象方法不能被private,static,final
修饰。
9.当一个子类没有重写父类(抽象了)的方法,可以把当前的子类用abstruct
修饰。
10.抽象类当中不一定包含抽象方法。
11.抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
12.抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类。
虽然一个类的子类可以去继承任意的一个普通类,可是从开发的实际要求来讲,普通类尽量不要去继承另外一个普通类,而是去继承抽象类。
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法
既然普通的类也可以被继承, 普通的方法也可以被重写, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类 了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛?
但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们. 充分利用编译器的校验, 在实际开发中是非常有意义的
我如果想在屏幕上打印红 红 绿 红 绿 黄下面这串代码怎么做到?
abstract class Fruits {
abstract void colour();
}
class Read extends Fruits {
@Override
public void colour() {
System.out.println("红 ");
}
}
class Yellow extends Fruits{
@Override
public void colour() {
System.out.println("黄 ");
}
}
class Green extends Fruits{
@Override
void colour() {
System.out.println("绿 ");
}
}
在我们没有学习过多态以及抽象类时,我们的想法应该就是写一个逻辑判断条件,就像下面的代码,虽然是正确的打印出来了想要的,但是有些麻烦。
public static void draw() {
// 红 红 绿 红 绿 黄
Red red=new Red();
Green green=new Green();
Yellow yellow=new Yellow();
String[] p={"Red","Red","Green","Red","Green","Yellow"};
for (String colour:p) {
if(colour.equals("Red")) {
red.colour();
} else if (colour.equals("Green")) {
green.colour();
} else {
yellow.colour();
}
}
}
但是!在我们学过了多态和抽象类!那真的就跟砍瓜切菜一样轻松,所以我们要熟练的掌握这些方法和语法。
public static void draw() {
// 红 红 绿 红 绿 黄
Red red=new Red();
Green green=new Green();
Yellow yellow=new Yellow();
Fruits [] fruits={new Red(),new Red(),new Green(),new Red(),new Green(),new Yellow()};
//Fruits 是类型 cloluR是引用变量 fruits是数组名
for (Fruits colouR:fruits) {
colouR.colour();
}
}
官方解释:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
通俗易懂的解释:接口可以理解为一种 特殊的类,里面全部是由 全局常量和 公共的抽象方法所组成。,既接口中的方法必须全部是抽象方法.
就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。
总结了四点:
接口的定义格式与定义类的格式基本相同,将class关键字换成 interface
关键字,就定义了一个接口。
interface Iusb//接口名称 {
//全局常量
int a=10;
//抽象方法
void read();//读取数据
void save();//保存数据
}
这样我们就定义好了一个接口,并且在接口里面写入了一个全局常量a和两个抽象方法,但是有同学就会好奇?
这时候同学就有两个疑问?
为什么是全局常量呢?为什么你的抽象方法可以不用去被修饰呢?
小鱼一个一个回答:
问题一:
在我们的接口中,所有的变量都是默认被public static final
修饰的,并且!我们的常量也需要在接口中进行初始化,不然爆出的错误信息就是
问题二:
接口中,默认里面的方法都是被public abstract
修饰的,并且不能对这个方法进行具体的实现,否则就会
总结:
protected static int a=10;
这样的做法,我们的接口里面的数据成员全部都是被public
修饰的,不可以进行修改。public abstract
修饰的。下面是一些实现接口的规范
- 创建接口时, 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 “形容词” 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性(
void read();
这样的写法)
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
这里也就是涉及到了关键字implements
interface Iusb {
int a=10;
void read();//读取数据
void Save();//保存数据
}
//当然,现在这么写是错误的,只是带大家熟悉一下这个关键字
class mouse implements Iusb{
}
我们继承普通类,抽象类用到的是extends
,实现接口用到的就是implements
.
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
在我们印象里,如果某个设备需要向电脑中读取或者写入某些东西,这些设备一般都是采用USB方式与电脑连接的,所以只要是带有USB功能的设备就可以插入电脑中使用,那么我们可以认为USB就是一种功能,这种功能能够做出很多的事情(实现很多的方法或者行为),我们也可以把USB就可以看做是一种标准,一种接口,只要实现了USB标准的设备我就认为你已经拥有了USB这种功能。(因为你实现了我USB标准中规定的方法)
所以我们就可以尝试完成下面的这个题
请实现笔记本电脑使用USB鼠标、USB键盘的例子
- USB接口:包含打开设备、关闭设备功能
- 笔记本类:包含开机功能、关机功能、使用USB设备功能
- 鼠标类:实现USB接口,并具备点击功能
- 键盘类:实现USB接口,并具备输入功能
这里报错的错误信息就是,我的抽象类方法没有被重写,告诉大家个快捷键
Alt+Enter(将光标移动到类名上)
这样就可以快速创建需要重写的方法了。
(1)先声明USB接口:其中规定了要实现USB接口就必须实现接口规定实现的open( )和close( )这两个方法
interface IUSB {//Iusb接口
void open();//打开
void close();//关闭
}
(2)然后在写一个鼠标类和一个键盘类,这两个类都去实现USB接口。(实现其中的方法)
这是鼠标的具体实现
class Mouse implements IUSB {//鼠标类
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
public void click() {//鼠标类自己的方法
System.out.println("点击鼠标");
}
}
这是键盘的具体实现
class Keyboard implements IUSB {//键盘类
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
public void typing() {//键盘类自己的方法
System.out.println("敲击键盘输入数据");
}
}
那么,现在鼠标和键盘都实现了USB功能,也就是说鼠标和键盘都能够调用USB接口中规定的方法,并且他们实现的方式都不一样。
我们写个测试,具体实现一下(用main函数去调用draw这个方法)
class computer {//电脑
public static void opencomputer() {
System.out.println("打开电脑");
}
public static void closecomputer() {
System.out.println("关闭电脑");
}
public static void draw(IUSB iusb) {
opencomputer();//打开电脑
iusb.open();
if(iusb instanceof Mouse) {
((Mouse) iusb).click();//鼠标这个设备
} else {
((Keyboard) iusb).typing();//键盘这个设备
}
iusb.close();//关闭设备
closecomputer();//关闭电脑
}
}
主类
//测试类
public class Test {
public static void main(String[] args) {
computer.draw(new Mouse());//去调用这个方法
System.out.println("==========");//分隔线
computer.draw(new Keyboard());
}
}
interface A {//接口
void print();//写入一个抽象方法
}
public class Test1 {
public static void main(String[] args) {
A a=new A() ;//实例化这个接口
}
}
错误信息
2. 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是public abstract,其他修饰符都会报错)
interface A {
// Error:(4, 18) java: 此处不允许使用修饰符private
private void print();
}
interface A {
void print() {
System.out.println("我是接口");
}
//编译出错
// Error:(5, 18) java: 接口抽象方法不能带有主体
}
interface A {
void print();// 默认是public的
}
class B implements A {
@Override
void print() {
System.out.println("我是子类");
}
//error 正在尝试分配更低的访问权限; 以前为public
// 编译报错,重写A中print()方法时,不能使用默认修饰符
}
interface A {
int p=10;//默认被public static final
void print();
}
public class Test1 {
public static void main(String[] args) {
System.out.println(A.p); // 可以直接通过接口名访问,说明是静态的
// 编译报错:Error:(12, 12) java: 无法为最终变量p分配值
A.p = 2; // 说明brand具有final属性
}
}
interface A {
//编译失败
public A {
}
{}//编译失败
}
在Java中,但是一个类可以实现多个接口。类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,下面通过类来表示一组动物.
abstract class Animal {//抽象类
String name;
int age;
public Animal(String name, int age) {//抽象类的构造方法
this.name = name;//初始化name
this.age = age;//初始化年龄
}
}
另外我们再提供一组接口,分别是"飞" “跑” “游泳”。
//跑的接口
interface Run {
void run();
}
//飞的接口
interface Fly {
void fly();
}
//游泳的接口
interface Swim {
void swim();
}
我们分别创建具体的动物,分别是狗类,乌龟类,鸭子类
因为我们的狗是会跑的,我们就可以利用接口
//狗类
class Dog extends Animal implements Run{
public dog(String name, int age) {
super(name, age);
}
@Override
public void run() {
System.out.println(name+" 用四条腿跑");
}
}
因为乌龟既可以跑,又可以游泳,所以可以给乌龟类两个接口(可以实现多接口)
//乌龟类
class Tortoise extends Animal implements Run,Swim{
public tortoise(String name, int age) {
super(name, age);
}
@Override
public void run() {
System.out.println(name+" 用四条腿爬");
}
@Override
public void swim() {
System.out.println(name+" 用四条腿扑腾");
}
}
还有一种神奇的动物, 水陆空三栖, 叫做 “鸭子”
//鸭子类
class Duck extends Animal implements Run,Swim,Fly{
public duck(String name, int age) {
super(name, age);
}
@Override
public void run() {
System.out.println(name+" 正在用两条腿跑");
}
@Override
public void fly() {
System.out.println(name+" 正在用翅膀飞");
}
@Override
public void swim() {
System.out.println(name+" 正在飘在水上");
}
主类
public class Test1 {
public static void main(String[] args) {
Dog dog=new Dog("哮天犬",888);
dog.run();
System.out.println("===========================");
Tortoise tortoise=new Tortoise("杰尼龟",88);
tortoise.run();
tortoise.swim();
System.out.println("===========================");
Duck duck=new Duck("唐老鸭",12);
duck.run();
duck.swim();
duck.fly();
}
}
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx特性
例如
狗是一种动物, 具有会跑的特性.
乌龟也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力.
我们先看这串代码,
//继承Run,Swim这两个接口
interface IAmphibious extends Run,Swim {
//可以自己加上属于自己的抽象方法
//也可以不去加,默认就是继承的这两个接口
}
//乌龟类
class Tortoise extends Animal implements IAmphibious{
public Tortoise(String name, int age) {
super(name, age);
}
@Override
public void run() {
System.out.println(name+" 用四条腿爬");
}
@Override
public void swim() {
System.out.println(name+" 用四条腿扑腾");
}
}
public class Test1 {
public static void main(String[] args) {
Tortoise tortoise=new Tortoise("杰尼龟",88);
tortoise.run();
tortoise.swim();
}
}
extends
关键字.IAmphibious
表示 “两栖的”. 此时实现接口给Tortoise
类, 就需要实现 run
方法, 并且还需要实现 swim
方法.虽然接口内部定义了一些抽象方法,但是并不是所有的接口内部都必须要有方法,比如cloneable
接口,cloneable
接口的作用是使对象能够“被克隆”,但是cloneable
接口中却没有任何内容,也就是说,如果有一个类需要实现“克隆”的功能,则这个类必须去实现cloneable
接口,但是却并不用实现方法(因为接口中没有方法),此时,这cloneable
接口就仅仅是一个“标识”接口,是用来标志一个类的,标志这个类具有这个“克隆”功能。
比如,我们把亮度设置为一个“标准”,或者说亮度就是一个“接口”,这个接口有一个方法,就是调整亮度,任何台灯如果想称之为可调节亮度的台灯,那么必须实现“亮度”这个接口,也就必须实现“亮度”接口中规定实现的“调节亮度”的方法,这样才算是真正的实现了“亮度”这个接口,实现了“调节亮度”这个功能。
当我们的台灯实现了这个“接口”时,也就具备了这个功能,那我们就可以直接使用一个“调节亮度”的“按键”去命令空调,虽然我们只是使用了调节亮度的这个按键,但是我们的台灯上拥有这个接口,我们通过这个按键去调整亮度也是行得通的。
其实,在我们的生活当中,有很多地方都体现了“接口”的思想。大家平时都经常用到手机吧,手机有苹果的和安卓的类型,但是如果我们想对手机充电呢?那就需要数据线,大家想一想,如果我的每一个手机都需要有一个专门的数据线来充电,那将会多么糟糕啊?并且每次换新手机还需要再去重新买相匹配的数据线。
因此,每个手机为了能够都使用同一款数据线,就将自己的接口改为了市面上普遍的标准,小米也好,华为也好,只要你按照我数据线接口的标准去生产你们的手机充电接口,那么你的手机都能很好的用数据线进行充电,传输信息等操作。
因此,当我们打开某东,想去买数据线时,就不难发现我们需要根据自己手机的品牌来挑选特定的数据线,这样手机才能被我们的数据线正常进行充电等操作。
回到Java上面来说,其实接口给我们带来的最大的好处就是“解耦”了,数据线能够搭配不同的手机,于是便可以实现方便,快捷的效果。在软件系统中也是一样的,接口可以有很多不同“特色”的实现类,我们只需要声明同一个接口,却可以引用很多个该“接口”引申出来的“子类”,这不也大大增强了我们软件系统中组件的灵活性吗?