一、抽象类(abstract)
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一·般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
说明:子类的对象都已经很具体了,什么功能都能实现了,完全不用再创建父类的对象了,这样的父类,就成了抽象类。
1.1 abstract关键字
abstract关键字只能用来修饰类和方法。(不能修饰属性,构造器、final等)
(1)abstract 修饰类 即抽象类
abstract修饰类以后,这个类就成了抽象类,即不能实例化对象的类了。所有的事情交给他的子类去做,所以抽象类一定有子类,实例化子类的对象去完成相关开发操作
语法:
【权限修饰符】 abstract class 类名 {}
说明:虽然抽象类没法实例化了,但是还是需要提供构造器(区分接口,接口没有构造器,就是因为没子类,又没法实例化),因为抽象类的子类会通过super继承这个抽象类的属性和方法。
注意:不能实例化 不代表 不能声明
(2)abstract 修饰方法 抽象方法
abstract修饰方法以后,这个方法就成了抽象方法,即只有方法声明,没有方法体。抽象方法的方法体是留给后代去实现的,因此,后代必须重新该抽象方法,除非后代继续是抽象类。直到后代重写了父辈们的所有抽象类方法后,后代才可以实例化对象。换句话说,如果子类没有写完父辈的所有抽象方法,那么该子类也是抽象类(必须用abstract修饰)。
语法:【权限修饰符】 abstract 返回值类型 方法名(形参列表); //注意 没有{ },即没有方法体
说明:既然没有方法体,意味着对象或类没法调用该抽象方法,自然意味着抽象方法一定要在抽象类里面,即有抽象方法的类一定是抽象类。但是抽象类中可以没有抽象方法,抽象类只是不让这个类造对象而已。
既然必须在子类重写抽象方法,显然,抽象类不允许是私有private,要不其他类根本就看不到。
同理,抽象方法不允许是static的,因为static方法根本不能重写,类方法各是各的,不会覆盖。(因为static是类方法,每个类都可以有多个子类,子类继承父类方法的时候,先从静态开始,没法覆盖父类方法的,所以不能重写)。
语法:public abstract 返回值类型 方法名(); //注意,抽象方法没有方法体,所以没有大括号{}
(3)抽象类使用场景
比如求几何图形面积。父类就是几何图形,定义长宽高,体积,面积。但是由于不知道几何图形是啥,所以没法写他的面积公式。就可以把父类几何图形写成抽象类,让子类(具体的矩形,圆,三角形),来重写面积,周长公式。
或者比如乘坐某交通工具去美国,交通工具的燃油消耗率,行驶时间,都是根据具体交通工具而异,因此抽象交通工具,燃油消耗率,行驶时间等方法,让子类(具体的飞机,轮船),来重写这些方法。
抽象类具有多态性:即可以 抽象类 类名 = new 子类(); 然后 类名.子类方法();
扩展:匿名子类 (综合匿名对象)
所谓匿名,就是只能调用一次,就是为了省事。
匿名子类,就是一个没有名字的子类。抽象类 + 重写抽象类中的方法,就成了一个匿名子类。
语法: 抽象父类 类名 = new 抽象父类空参构造器(){ //子类什么名字不知道,这里的类名是父类类型,多态性体现
抽象父类中的抽象方法(形参列表){方法体;} //匿名子类,需要重写完抽象方法后,才可实例化出类名
}
说明:这里的父类类名用了多态,其实实例化的是子类对象,不过子类没有名字。匿名子类可以综合匿名对象一起使用,形成一个大匿名,更省事。
exp:某方法(new 抽象类(){ //main中的某方法,直接调用一个匿名子类的匿名对象
抽象方法1() //匿名子类的匿名对象需要完成抽象类的抽象方法
抽象方法2(){
})
二、接口(Interface)
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是, Java不支持多重继承。有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a(篮球运动员is a 运动员)的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接,但他们并不是is-a关系,而是has-a 关系,比如跨栏运动员和大学生都具备学习的技能,但显然学习技能不是他们的父类。飞机、风筝、热气球都可以飞等等。这种has-a关系,就可以用接口。
· 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是. 则必须能.”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
2.1 接口的使用 interface关键字
在java中,接口和类是并列关系,(可以想象成接口是一种特别的类)
接口中,只能定义全局常量和抽象方法(JDK7以前 稳定版),静态方法、默认方法(JDK8以后)。
一个类可以实现多个接口。
接口可以继承接口,并且可以多重继承。
接口也能体现多态性。即 接口 类名 = new 实现类();
全局常量:public static final 声明的常量
抽象方法:public abstract 声明的方法
静态方法(Java8),工具方法: 接口中定义的静态方法,只能通过接口来调用。实现类继承不到(唯一继承不到的),没法调用,很像工具类。
默认方法(Java8):通过实现类的对象,可以调用接口中的默认方法,并且实现类可重写接口中的默认方法。如果实现类的父类和继承的接口中有同名同参数的方法,而且实现类中没有重写这个方法,在main中调用实现类这个同名方法时,默认用父类方法,(类优先原则)。如果实现类 实现了 多个接口,且实现类没有父类,多个接口之间都有同名同参数的默认方法,此时在main中调用实现类的同名方式时,报错(接口冲突),解决办法,在实现类中重写此方法。
如果想在实现类中调同名接口的默认方法的语法:接口名.super.接口方法名; (如果方法不同名,就直接用实现类对象调就是了)
说明:接口和父类是两回事,类是先继承父类,再implements接口。类的对象可以继承父类和接口的所有(除了接口的静态方法)。接口的默认方法,就是类的普通方法。
语法: interface 接口名 【extends 接口1,接口2...】{ //接口可以继承,并且是多重继承
【public static final】 数据类型 变量名 【=默认值】;//由于接口中只能是全局常量,因此public static final 可以省略
【public abstract】返回值类型 方法名();//抽象方法没有方法体,所以没有大括号
public static 返回值类型 方法名(); //接口的静态方法,工具,只能接口调用。实现类没法继承
【public】 default 返回值类型 方法名();//接口的默认方法,实现类可以重写,也可以调用
}
说明:接口中不能定义构造器,因为接口是has-a关系,没有子类,同时意味着接口不可以实例化(又没子类,又不能实例化,那自然不用构造器了)。为了实现接口的价值,java让类(非子类)去实现接口的价值(implements),这个类就是实现类。如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。如果实现类没有覆盖完接口中的所有方法,则此实现类仍为抽象类。
注意:不能实例化,不代表不能声明
语法:class 类名 【extends 父类 】implements 接口1【,接口2...】{ } //类可以实现多个接口
exp:
class Aircraft{
public void taxi(){
System.out.println("航空器滑行");
}
interface Flyable{
int MAX_SPEED = 7900; //省略了【public static final】
public void taxi(){
System.out.println("飞起来了");
}
void abstract void fly(); //抽象方法fly(),没有大括号,等实现类来重写。省略了【public abstract】
}
class Plane extends Aircraft implements Flyable{ //实现类Plane 继承 Aircraft 实现接口 Flyable
public void taxi(){
System.out.println("飞机滑行");
}
public void fly(){ //重写接口中的fly()方法,此时可以构造对象
System.out.println("蝴蝶飞飞");}
taxi(); //调用自己重写的“飞机滑行”
super.taxi();//调用父类的“航空器滑行”
Flyable.super.taxi();//调用接口的“飞起来了” 如果方法名不重名,就直接用实例化对象调了
}
扩展:接口也可以像抽象类一样玩匿名
三、内部类
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B是外部类 (内部类是相对的)
内部类分为成员内部类(静态、非静态 )和 局部内部类 (方法内、代码块内、构造器内)
3.1成员内部类
1、作为外部类的成员(类似成员变量),内部类方法中可以调用外部类的属性和方法,注意如果内部类是静态的,就没法调用外部类非静态的属性和方法(生命周期不同)。同时,内部类还可以用权限修饰符修饰
2、内部类也是一个类,可以定义类的属性,方法,甚至再嵌套内部类,
3、允许内部类调用外部类的属性和方法,如果不重名,直接调。如果重名,需要加入 外部类.this.属性/方法
4、内部类对象的实例
语法:外部类.内部类名 变量名 = new 外部类.内部类构造器(); //静态内部类实例化,直接用外部类来实例化
外部类 外部类对象; //先实例化一个外部类对象
外部类.内部类名 变量名 = 外部类对象.new 内部类构造器(); //非静态内部类实例化,需要通过外部类对象来实例化
变量名.内部类方法(); //调用内部类方法
exp: class Animal{ //外部类动物
String name;
public void eat(){
System.out.println("吃饭");}
static class Dog{ //静态内部类 狗
String name; //狗名
public void spark(){ //狗会叫
eat(); //报错,静态内部类没法调用外部类的非静态方法,生命周期不同,先有静态,实例化后才有对象
System.out.println("旺旺旺"); }
}
class Bird { //非静态内部类 鸟
String name; // 鸟名字
public Bird(); //鸟构造器
public void sing(){ //鸟会唱歌
System.out.println("我是小鸟");
eat(); } //非静态内部类调用外部类 eat方法,允许。省略了person.this.eat()。直接this.eat(),就是调用Bird的eat了
}
main(){
//直接用外部类就可以实例化静态内部类Dog,如果是实例化非静态内部类,还要外部类的对象来实例化
Animal.Dog dog = new Animal.Dog();
dog.spark();
//实例化非静态内部类Bird,需要先实例化外部类对象才行
Animal animal = new Animal(); //先实例化外部类对象 animal
Animal.Bird bird = animal.new Bird(); // 通过animal对象 来实例化 内部类bird对象
bird.sing(); //调用唱歌方法
}
3.2 局部内部类 就是在方法中写内部类
注意:如果在方法中定义有方法的局部变量,如果内部类的方法要使用这个变量,则必须声明成final
exp: public Comparable getComparable(){ //定义一个方法,用于获取一个实现了comparable接口的对象
int sum = 10; //jdk8以上 如果sum被内部类使用,自动变成 final int sum
class MyComparable implements Comparable{ //在实现类MyComparable 重写接口Comparable的方法
System.out.println(sum);// 打印外部方法的变量 sum
public int compareTo(Object o){
return 0;}
}
return new MyComparable(); //重写了方法后,MyComparable()可以实例化,返回给外部
}
扩展:节约代码写法(匿名写法)
public Comparable getComparable(){ //匿名,省略了MyComparable()
return new Comparable(){
public int compareTo(Object o){
return 0;}
}
}