1.抽象类
1.1抽象类介绍及示例代码
在java中,含有抽象方法的类称为抽象类,同样不能生成对象,即抽象类不可以实例化。
所谓的抽象方法,就是方法只有方法声明,但是没有方法体,直接在方法声明后面以分号结束,此时需要在方法名前使用一个关键字abstract。
如public abstract void show();这个就是抽象方法,没有了方法体(即没有{}),直接在后面以分号结束。
代码如下:
抽象类:
//因为含有抽象方法,所以是抽象类,因此在class前面被abstract修饰
public abstract class Worker {
//下面是成员变量,不可以被abstract修饰,否则会发生报错,提示make wage not abstract
public String name;
public int age;
public double wage;
public Worker(){
//构造无参的构造方法
}
//构建抽象方法,方法声明后面没有方法体,直接以分号结束,同时被abstract修饰
public abstract void work();
public void show(){
//非抽象方法
System.out.println("I'm a worker");
}
}
子类1:
//因为子类实现了父类中全部的抽象方法,那么子类就不是抽象类了,那么此时没有在class前面被abstract修饰
public class Programmer extends Worker {
public Programmer(String name, int age, double wage){
/**这一步可写可不写,因为如果不写,就会默认调用父类中无参的构造方法,同时
* 注意父类中必须要有这个构造方法,否则会发生报错,如果写的话,则必须要写在
* 第一行,因为要先将父类进行初始化
*/
super();//调用父类中无参的构造方法
this.name = name;//这里使用了this,说明了这个子类继承了父类(抽象类)中的成员变量
this.age = age;
this.wage = wage;
}
@Override
//重写了父类中的抽象方法,因为已经将父类中的全部抽象方法都已经重写了,那么这个子类就不是抽象类,否则这个子类也是抽象类
public void work() {
System.out.println("my work is programming");
}
@Override
//重写了父类中的非抽象方法
public void show() {
System.out.println("我的名字是 "+ this.name+" ,今年 "+this.age +" 岁了,从事程序员工作,工资每月 "+this.wage+" 元");
}
}
子类2:
//因为子类实现了父类中全部的抽象方法,那么子类就不是抽象类了,那么此时没有在class前面被abstract修饰
public class Manager extends Worker {
public int bonus;//奖金
public Manager(String name, int age, double wage,int bonus){
/**这一步可写可不写,因为如果不写,就会默认调用父类中无参的构造方法,同时
* 注意父类中必须要有这个构造方法,否则会发生报错,如果写的话,则必须要写在
* 第一行,因为要先将父类进行初始化
*/
super();//调用父类中无参的构造方法
this.name = name;//这里使用了this,说明了这个子类继承了父类(抽象类)中的成员变量
this.age = age;
this.wage = wage;
}
@Override
//重写了父类中的抽象方法,因为已经将父类中的全部抽象方法都已经重写了,那么这个子类就不是抽象类,否则这个子类也是抽象类
public void work() {
System.out.println("my work is managing");
}
@Override
//重写了父类中的非抽象方法
public void show() {
System.out.println("我的名字是 "+ this.name+" ,今年 "+this.age +" 岁了,从事经理工作,工资每月 "+this.wage+" 元,奖金有 "+ this.bonus+" 元");
}
}
测试类:
public class AbstractTest {
public static void main(String[] args){
Programmer p = new Programmer("张三",25,10000);
Manager m = new Manager("李四",30,14000,5000);
p.work();//子类调用对象的抽象方法
p.show();//子类调用抽象类的非抽象方法
m.work();//子类调用抽象类的抽象方法
m.show();//子类调用抽象类中的非抽象方法
}
}
结果:
1.2抽象类的注意事项
2.抽象类的注意事项:
①抽象类中不可以实例化,即不可以通过new来创建对象,但是并不说明了抽象类没有构造方法,抽象类必须有构造方法,因为要用于子类的初始化。
②抽象类中可以含有非抽象方法。抽象类中不一定有抽象方法,抽象方法一定存在于抽象类中。
③抽象类的子类必须要重写父类的全部抽象类方法,否则,如果没有重写完,那么子类依然是一个抽象类。
④abstract关键字不可以和static、final、private等关键字放在一起。
⑤abstract不能用于修饰成员变量,不能用于修饰局部变量,即没有抽象变量、没有抽象成员变量等说法;abstract也不能用于修饰构造器、没有抽象构造器,抽象类里定义的构造器只能是普通构造器,如果修饰,会有一个错误提示,提醒make xxxx not abstract,因此抽象类中的成员变量、构造方法不可以被abstract修饰。
⑥抽象类一定是一个父类。基于第①点,我们可以得证,因为抽象类不能实例化,因此一定要通过子类实现其相应的方法,因此抽象类一定是一个父类。
这里详细讲解第四点:
首先abstract不可以和private、final放在一起,比如如果抽象方法被private修饰,那么子类就没有办法访问这个方法了,那么就无法重写抽象方法,此时这个子类又是一个抽象类,不可以实例化,那么这时候就已经和你的目的冲突了,因为你的目的就是要实现抽象类的这个方法,从而得证结论;同样的道理,如果抽象方法被final修饰,那么子类虽然能够继承这个方法,但是没有办法重写这个抽象方法,同样的,final修饰的类被称为最终类,是不允许被继承的,而抽象类是一定要继承的,此时两者相悖,从而表明了final是不允许和abstract放在一起的。综上,abstract不可以和private、final共存。
然后介绍abstract不可以和static放在一起,static修饰的方法是不被重写的,那么这时候如果放在了一起,那么这时候这个抽象方法是一直不会被重写,这就和我们的意图相悖了,因此不可以让static和abstract放在一起。
static 修饰的变量、方法是可以被它的子类继承,但是子类不可以修改这个方法,但是可以修改这个静态变量,因为继承是一个动态绑定的过程。
package extend.bean;
import java.util.Date;
import java.util.GregorianCalendar;
public class Employee {
/*
如果一个类被final修饰,那么就没有办法被其他类继承,一旦定义一个类继
承这个final类,那么就会发生报错。同样的,如果一个类被final修饰了,那么
它的方法就会变成了final
*/
private String name;
private double salary;
private Date hireDay;
public static String school = "华中科技大学";//静态变量可以被子类继承,以及修改
/*
如果没有显示定义构造方法,那么系统就会自动添加构造方法,并且
这个构造方法是无参的;否则,如果我们已经定义了构造方法,那么
系统不会自动添加无参的构造方法了,需要我们自己添加。
*/
public Employee(String name,double salary,int year,int month,int day){
//方法中的变量和全局变量有相同的名字,那么在方法中使用的是局部变量的
//因此要区分全局变量,那么需要使用this关键字
this.name = name;
this.salary = salary;
GregorianCalendar calendar = new GregorianCalendar(year,month - 1,day);
hireDay = calendar.getTime();//如果方法中没有和全局变量相同名字的,可以直接使用全局变量
}
public static void introduce(){
//静态方法可以被子类继承,但是不可以修改
System.out.println("I am a worker");
}
}
public class Manager extends Employee{
private double bonus;//全局变量会有初始值,而局部变量没有初始值
public Manager(String name,double salary,int year,int month,int day){
//在构造子类的构造方法之前,需要执行父类的构造方法(需要将super放在第一行)
super(name,salary,year,month,day);
bonus = 0;//这一步可以不要,因为bonus是一个全局变量,会将其初始化为0
}
@Override
public static void introduce(){
/*
重写父类的静态方法introduce,在这个方法中打上@Override之后,就会出现一个红色的波浪线
运行时提示有方法没有被覆盖(重写),就是因为子类重写父类的这个静态方法导致的。
如果没有打上@Overrid,那么不会发生报错,并且子类调用这个方法的时候并不会因为父类中存在这个
一样的方法而受影响,输出I am a manager.
如果子类中没有写这个方法,然后调用introduce方法,输出的是I am a worker,表示父类的静态方法
可以被子类继承
*/
System.out.println("I am a manager");
}
//修改父类中的静态变量school
public void setSchool(String name){
school = name;
}
}
public class ManagerTest {
public static void main(String[] args) {
Manager boss = new Manager("Carl Cracker",80000,1987,12,15);
boss.setSchool("清华大学");
System.out.println(boss.school);
boss.introduce();
}
}
运行时:
2.接口
2.1接口的介绍及示例代码
接口:
1)方法全部是抽象方法。java接口的方法默认都是public abstract类型,这里需要注意的是接口默认都是public abstrac类型,因此方法可以省略这两个修饰符,但是在抽象类中则不可以省略,否则会发生报错,此时接口这个类定义时不再时class,而是interface
2)接口中的成员变量都是一个常量,即public static final修饰的,但是再接口中并不太可能会定义成员变量。
特点:接口同样不可以被实例化,因此,需要通过子类将这些抽象方法全部实现,此时子类不再是extends,而是另一个关键字implement。
接口类:
//注意这时候这里并不是class,而是interface
public interface Yunsuan {
public abstract int divide(int c, int d);//抽象方法,进行除法运算
int add(int a, int b);//抽象方法(由于方法默认时public abstrac类型,所以省略写发现并没有发生报错,但是在抽象类中则不可以省略写),进行加法运算
}
子类1:接口的子类中必须将全部的抽象方法实现,否则发生报错
但是如果没有将所有的抽象方法是完,是否可以将这个子类定义成一个接口呢?和抽象类中一样,如果子类没有将抽象类的抽象方法实现完这个子类还是个抽象类一个道理呢?答案是否定的,没想到吧?我们再一次回归到接口的定义中去,接口中的所有的方法都是抽象方法,那么就不可能存在非抽象方法,因此*如果子类没有将接口的抽象方法都实现完,此时不可以将这个子类定义成一个接口,但是将这个子类定义成一个抽象类就可以。
public class DemoImpl1 implements Yunsuan {
@Override
//实现接口中的除法运算
public int divide(int a, int b) {
if(b == 0)//如果除数为0,那么就抛出异常
throw new RuntimeException("除数不可以为0");
return a/b;
}
@Override
//实现接口中的加法运算
public int add(int c, int d) {
return c + d;
}
//进行减法运算
public int subject(int a, int b){
return a - b;
}
}
子类2:在实现接口的基础上,这个类还继承于子类1,从而含有了子类1中的所有方法,此时当将接口中的抽象方法都注释掉,可以发现没有发生异常,这是因为这个子类2已经继承了子类1的所有方法,已经实现了接口的抽象方法,所以子类2中已经实现了接口的抽象方法了,所以可以注释掉。
public class DemoImpl2 extends DemoImpl1 implements Yunsuan {
@Override
public int divide(int a, int b) {
if(b == 0)//如果除数为0,那么就会发生异常
throw new RuntimeException("除数不可以为0");
return a/b;
}
@Override
public int add(int c, int d) {
return c + d;
}
//进行乘法运算
public int multiply(int a, int b){
return a * b;
}
}
测试类:
public class InterfaceTest {
public static void main(String[] args){
//由这里可以知道,括号里并没有任何参数,那么默认DemoImpl1这个类的构造方法是无参的
DemoImpl1 m = new DemoImpl1();
//由这里可以知道,括号里并没有任何参数,那么默认DemoImpl2这个类的构造方法是无参的,此时并不会发生报错
//那么这时候再DemoImpl1的构造方法有参数,即new DemoImpl1(int num),那么就会发生报错,因为这个类中并没有无参的构造方法
DemoImpl2 p = new DemoImpl2();
try {
System.out.println("加法运算: " + m.add(3, 5));//实现接口的方法
System.out.println("减法运算:"+m.subject(9,3));//自身这个类含有这个方法
System.out.println("除法运算: " + p.divide(8, 6));//实现接口的方法
System.out.println("乘法运算:"+p.multiply(4,8));//自身这个类含有这个方法
System.out.println("减法运算:"+p.subject(4,8));//p继承了DemoImpl1类,所以这个类可以调用其方法subject,进行加法运算
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
结果:
2.2接口的注意事项
①接口中的方法都是抽象方法,即使在代码中没有看到public abstract,依旧是抽象方法,因为在java中省略了。但是在抽象类中不可以省略,否则发生报错。
②接口中的成员必然是public,否则无法被子类所继承(常量)、实现(抽象方法),从而因为无法实现抽象方法发生报错。
③在接口中不可以有构造方法,在接口里写入构造方法时,编译器提示:Interfaces cannot have constructors。但是抽象类有构造方法,从而对其子类进行初始化。接口不可以有构造方法的原因:
1) 构造方法用于初始化成员变量,但是接口成员变量是常量(接口中的变量默认是public static final),无需修改。接口是一种规范,被调用时,主要关注的是里边的方法,而方法是不需要初始化的。
2) 类可以实现多个接口,若多个接口都有自己的构造方法,则不好决定构造方法链的调用次序。
④子类必须要实现接口的全部抽象类方法,否则,如果没有重写完,那么子类是一个抽象类。
3.接口和抽象类的异同
同:
①不能实例化。抽象类和接口都没有办法通过new来新建对象。
②子类都是通过不断向上抽取而来,个人理解是子类都要求重写、实现他们的抽象方法。
③如果他们的子类都没实现完对应的抽象方法,那么这个子类是一个抽象类。
异:
①有无构造函数:抽象类有构造函数,但是接口没有构造函数。
②方法是否绝对全是抽象方法:抽象类中可以有或者没有抽象方法,但是接口必须有抽象方法,而且是全部都是抽象方法。
③类与类之间是继承关系,类与接口直间是实现关系。抽象类只能被继承,而且是单继承,但是接口则是需要被实现,并且可以是多实现。