Interfaces in Java
Java接口为引用类型,包含:常量/方法签名/默认方法/静态方法等
Defining an Interface
接口由修饰符,关键字interface,接口名称,继承接口列表,以及接口主体组成
支持public/package-private修饰符(顶级)
一个接口可以扩展继承自任意数量的接口
接口内,可以包含抽象方法,默认方法和静态方法
抽象方法(Abstract Methods),仅包含方法的声明,不包含方法的具体实现
抽象方法参数括号后,直接接分号,没有方法体
接口中所有抽象/默认/静态方法都是隐式public ,因此可以省略public修饰符
接口中的抽象方法隐式具有public abstract修饰
接口中可以声明常量,常量值是隐式public static final, 因此可以省略
CC:接口中的方法/常量,均省略多余的修饰符
CC:接口命名:可以是名词或名词短语;形容能力的接口以对应的形容词(able后缀)命名;基于服务的接口声明服务类型后缀
定义一个类,通过implements关键词声明实现指定接口
实现类实现多接口时,使用逗号分隔接口列表
实现接口,就必须实现接口中所有的抽象方法
Undergraduate类实现了 Learnable/Playable接口 则必须实现2个接口中的全部抽象方法 与普通类一样可以声明自己的属性/构造函数等
@Override注解 声明此方法为一个重写的方法
本科生具有Learnable与Playable的能力 因此,可以基于本科生创建独有相应能力的对象 研究生没有Playable的能力 因此,无法创建相应能力的对象
Using an Interface as a Type
定义了一个接口,就是定义了一个可以引用的类型,像类一样,在任何需要的地方作为类型使用
抽象出相同的状态(属性),设计为实体类
抽象出相同的行为(方法),设计为接口
接口,以行为能力分类
组合一系列 行为(方法)
组合一系列 属性
将方法聚合为能力/服务
将属性聚合为实体类
Evolving Interfaces
试图添加新的play能力,但是原接口实现者可能并没有该能力
Java8以前,接口中仅允许定义抽象方法,无法声明方法的具体实现,增加了接口的维护成本 例如,Java8在集合的顶级接口Iterable中,添加支持Lambda表达式的forEach()迭代方法,而仅JDK(不算其他第三方库) 其子接口有数十个,涉及到的实现类有数百个,但是其实现代码却是相同的,直接添加抽象方法的代价过高 因此在Java8后,允许通过default关键词在接口中声明方法的具体实现
将方法声明为default方法 则支持在接口中直接实现该方法
无需修改任何Playable接口实现类
所有实现了Learnable接口类型的对象 均可直接调用该接口中实现的方法
Java8之前,通过定义工具类以及静态方法,提供辅助性操作 Java8之后,允许在接口中定义静态方法,从而更容易组织工具库以及创建代理类
在接口中声明static方法,通过接口名称 可直接调用 无需任何实现类
Java接口支持 公有常量 抽象方法,通过类实现使用 默认方法,通过实现类直接使用 私有方法,为默认方法提供支持 公有静态方法,通过接口直接使用 私有静态方法,为公有静态方法提供支持
支持多继承
不支持包级成员与私有常量
默认/私有方法,依然是单向调用
-
默认方法:默认方法可以被接口中的其他方法调用,包括其他默认方法和抽象方法。在接口的实现类中,可以选择重写默认方法或者直接继承它们。但默认方法不能被实现类或其他类直接调用。
-
私有方法:私有方法只能在接口内部被默认方法或其他私有方法调用,它们对于接口的实现类和其他类是不可见的。
-
静态方法:静态方法可以被接口中的其他静态方法调用,但它们不能被默认方法或私有方法调用。在接口的实现类中,可以直接通过接口名调用静态方法。
Inheritance
Subclass,从另一个类派生出的类,称为子类(派生类,扩展类等)
Superclass,派生子类的类,称为超类(基类)
习惯上称子类的直接超类为,父类
每个类能且仅能,直接继承自一个父类(单继承)
没有显式声明继承时,每个类都隐式继承自Object类
类A显示继承自类B,类B显示继承自类C,类C没有显式继承任何类,但类C依然隐式继承自Object类
因此,Java中所有类都是Object派生的子类
Inheritance,继承,是面向对象程序设计语言基本特征之一(封装/继承/多态)
继承意义在于:当需要创建一个新类,且当前已经存在一个包含需要代码的类时,可以从现有的类中派生出一个新类,从而重用现有类的成员,而无需重新编写/调试
Using the super Keyword
关键词this,指向当前对象的引用
关键词super,代表当前对象的超类(不仅仅是直接的父类,可追溯到Object类)。用以区分自己的/超类的
*可通过super调用超类public/protected成员
*可通过super调用超类构造函数
不能理解为是指向父类的引用,虽然实例化时会调用超类构造函数,但并不会创建超类对象。因此,不存在超类对象
解释一下:当你调用super()构造函数时,它会返回一个代表父类的对象,并在子类中使用这个对象来调用父类的构造函数,这个过程中不会创建新的超类对象,只是初始化子类对象中的父类部分,所以,super()调用的结果是调用父类构造函数初始化子类对象中的父类部分,而不是创建新的超类对象
public interface Moveable {
void move();
}
public class Animal implements Moveable {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void move() {
System.out.println(name+"moving");
}
}
支持在子类中声明子类自有的新属性
支持在子类中声明子类自有的新方法
子类继承超类所有public/protected成员(变量/方法)
可以调用父类public成员方法,超类必然包括Object类,因此可以调用Object public/protected成员方法
在子类/超类的方法没有名称冲突时 可直接调用超类中的方法 无需声明super
System.out.println(getName() + "is flying")
等效
System.out.println(super.getName() + "is flying")
*子类必须满足超类特性。因此,逻辑上,能够构造初始化子类的前提,是必须能够构造初始化超类
*即使超类未显式提供有参构造函数,也会调用超类无参构造函数
//两个参数的构造函数,但一个为父类属性,且父类提供了构造函数,因此可通过父类初始化封装一个属性,再自己初始化一个属性
public Bird(String name,String color) {
super(name);//在构造函数中,通过super调用超类构造函数语句必须置于构造函数第一行
this.color = color;
}
//父类没有显式提供有参构造函数时
// 父类
class Parent {
private String name;
// 父类没有显式提供有参构造函数
// Getter 和 Setter 方法省略
}
// 子类
class Child extends Parent {
private int age;
// 子类的构造函数
public Child(String name, int age) {
// 在子类的构造函数中调用父类的无参构造函数
super();
this.age = age;
}
// Getter 和 Setter 方法省略
}
子类无法继承超类的private成员,但可以通过属性的getter/setter方法访问超类的属性
public class Bird extends Animal implements Flyable {
private String color;
//两个参数的构造函数,但一个为父类属性,且父类提供了构造函数,因此可通过父类初始化封装一个属性,再自己初始化一个属性
public Bird(String name,String color) {
super(name);//在构造函数中,通过super调用超类构造函数语句必须置于构造函数第一行
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
//Bird类,继承自Animal类,因此继承父类name属性(通过父类构造函数以及继承getter/setter方法实现)
//继承父类Movable能力,同时具有独有color属性,具有独有Flyable能力
@Override
public void fly() {
System.out.println(getName() + "is flying");
}
public static void main(String[] args) {
Bird bird = new Bird("Raven","black");
bird.move();
bird.fly();
System.out.println(bird.getName() + "/" + bird.getColor());
}
}
支持在子类中声明一个与超类中方法签名相同的,新实例方法,从而overriding覆盖超类方法(方法的重写)
由于方法签名与父类中的方法签名相同,为避免歧义,使用@Override注解显式声明重写超类方法
@Override
public void fly() {
System.out.println(getName() + "is flying");
}
public static void main(String[] args) {
Bird bird = new Bird("Raven","black");
bird.move();
Animal animal = new Animal("Lion");
animal.move();
}
子类/父类对象均调用move()方法 但子类重写了父类的方法 因此实现已不同
Casting Objects
相关的不同类型间的转换,是多态的表现形式
Bird继承自Animal以及Object,因此Bird也是Animal类型,也是Object类型。但是反过来则无法确定
Bird bird = new Bird("Raven","black");
System.out.println(bird instanceof Bird);
System.out.println(bird instanceof Animal);
System.out.println(bird instanceof Object);
因此,在类的继承与接口的实现中,允许将一种类型转换为其继承/实现的另一种类型,称为上转型(隐式转换,implicit casting)
Bird bird = new Bird("Raven","black");
Animal animal = bird;
Object obj = bird;
变量bird/animal/obj 声明的类型不同,但引用的 是同一个对象
System.out.println(bird.getClass().getName());
System.out.println(animal.getClass().getName());
System.out.println(obj.getClass().getName());
说明 变量的类型为 其实际引用对象类型的超类时 可自动完成上转型 其实际类型仍为引用对象类型 而非变量声明的类型
相反,当试图将Animal或Object转换为Bird时,称为下转型(explicit casting,显式转换),需显式声明强制转换
Object obj = bird;
Bird bird2 = (Bird) obj;
bird2.move();
由于obj引用的对象为bird类型 因此可以类型转换且没有异常
隐式的上转型,子类一定具有超类的特性,因此编译器可自动实现类型转换
显式的下转型,超类未必一定是子类,因此需显式声明强制转换,且只有运行时才能知道错误
Bird,animal变量引用的是同一个类型对象,但变量类型不同
Animal变量无法调用bird中的fly()方法,声明的变量类型(Animal类型)限制约束了他的行为
即,此时animal只能表现出Animal类型的行为状态,即使他实际上引用的是一个Bird类型对象
Bird重写了父类的move()方法
即,声明的变量类型仅约束行为(限制方法的调用),不影响方法的实现
基于接口实现类创建对象
接口类型变量的实际类型为 引用对象的类型
接口无法实例化,因此,声明的接口类型变量,是其实际引用对象的类型,永远不能为其声明的接口类型,只能是接口其中一个实现类的的类型
注意:1、:通过接口类型的引用,可以访问到实现类中继承或重写了接口中定义的方法。因为 Moveable
接口可能包含了一些方法,而 Animal
类实现了这些方法,所以通过 Moveable
接口类型的引用可以调用 Animal
类中实现的这些方法。
2、:使用接口类型的引用无法直接访问实现类中未在接口中定义的方法。即使 Animal
类中有一些自己特有的方法,在使用接口类型的引用时也无法直接调用这些方法。如果需要调用实现类中特有的方法,需要将接口类型的引用强制转换为实现类的类型,然后才能调用这些特有方法。
animal变量无法调用其自身声明的方法,声明的变量类型(Movable类型)限制约束了他的行为 即,此时animal只能表现出Movable类型的能力,即使他实际上引用的是一个Animal类型对象
bird对象具有move() fly()的行为,但声明的变量类型(Flyable类型)限制约束了他的行为 即,此时bird只能表现出Flyable类型的能力,即使他实际上引用的是一个Bird类型对象
Bird具有move()与fly()的行为 但此时仅需表现他的Flyable能力
Polymorphism
通过继承的超类类型,引用不同子类类型对象实现多态
通过实现的接口类型,引用不同实现类类型对象实现多态
同一个对象,通过不同的约束,表现出不同的行为能力
即,Java通过声明不同的超类类型或实现的接口类型,实现多态
面向对象程序语言实现多态的意义: 可以只关注希望使用的能力/服务,无需关心该能力/服务的具体实现
即,面向接口编程/面向切面编程(AOP)/面向服务编程(SOP)。通过定义具有不同服务的接口,由容器或工具动态注入实现对象,而完全屏蔽具体实现类型
1、基于多态/转型的特性,重写的方法支持改变返回类型为,`小于等于`超类要求的类型。
超类要求此方法必须返回 具有Movable能力的对象
则支持重写时改变类型为 任何实现了Movable接口类型的对象 或其子接口类型
2、重写超类方法,返回类型为基本数据类型的禁止改变
3、重写方法的访问范围,必须大于等于超类声明的范围
超类声明特性为 protected
类必须至少 具有超类的特性 因此禁止缩减范围 但是可以扩大范围
4、子类可直接调用超类中静态成员(public/protected)
5、支持在子类中声明一个与超类中方法签名相同的静态方法,从而hiding隐藏超类静态方法(静态方法的隐藏无需@Override注解修饰)
What You Can Do in a Subclass - Summary
1、支持在子类中声明子类自有的新属性
2、支持在子类中声明子类自有的新方法
3、子类继承超类所有public/protected成员(变量/方法)
4、支持在子类中声明一个与超类中方法签名相同的,新实例方法,从而overriding覆盖超类方法(方法的重写)
5、支持在子类中声明一个与超类中方法签名相同的,新的静态方法,从而hiding隐藏超类静态方法
6、创建子类对象,必然调用超类构造函数
7、支持编写子类构造函数时,通过关键字super显式调用超类构造函数
8、子类无法继承超类private成员,但可以通过属性的getter/setter方法访问超类的属性(如果提供)
Abstract Methods and Classes
1、抽象类,可以定义抽象没有实现的方法
2、抽象类无法被实例化,但是可以被继承使用(抽象类就是为了继承使用的模板)
3、抽象类中可以声明抽象的方法,也可以声明普通方法,普通方法可以被子类继承使用
4、继承了抽象类的子类,则必须实现抽象类中所有抽象方法,否则也必须声明为抽象类 抽象类可以实现接口,而无需实现接口中全部抽象方法,未实现的抽象方法可由子类实现
5、抽象类可以继承非抽象类的普通类
//定义抽象类,虽然不能被实例化,但是可以声明子类共有的属性,并提供构造函数拱子类封装使用
public abstract class Organism {
private String name;
public Organism(String name) {
this.name = name;
}
//声明普通成员方法
public void sleep() {
System.out.println(name + "is sleeping");
}
//声明抽象方法
public abstract void move();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Human extends Organism{
public Human(String name) {
super(name);
}
//子类继承抽象的父类则必须重写抽象的方法,调用父类构造函数封装父类属性,可直接调用继承自父类的方法
@Override
public void move() {
System.out.println(getName() + "move as human.");
}
}
public class Animal extends Organism{
public Animal(String name) {
super(name);
}
@Override
public void move() {
System.out.println(getName() + "move in many ways.");
}
}
子类可直接调用父类中继承的方法 子类重写父类中抽象方法 从而完成不同的实现 可基于多态声明符合类型的变量
public static void main(String[] args) {
Human yu = new Human("yu");
Organism sun = new Human("SUN");
sun.move();
sun.sleep();
Organism lion = new Animal("Lion");
lion.move();
lion.sleep();
}
无法创建抽象类的对象
Abstract Classes Compared to Interfaces
抽象类与接口相似,均不能被实例化,均可声明抽象/非抽象/公有/私有/静态等等成员
抽象类,关注于为子类提供支持,单继承限制子类扩展
接口,关注于能力服务,不耦合具体实现
使用抽象类场景
期望相关类共享公共代码及约束(包含抽象方法的模板)
期望相关类有具有各自特有的属性行为
使用接口场景
期望一些类能提供相同的能力但又彼此无关
期望使用指定的能力服务,而不关心其实现
Anonymous Classes
匿名类/匿名内部类,能够使代码更加简洁
匿名内部类,能够同时的一步到位的,声明和创建一个实现/继承的接口/抽象类的实现类和对象。
动态创建的实现类/子类没有名称,但可以像正常类一样使用
适合于仅声明使用一次的不会被复用的类
public class Student {
private String name;
//将学习能力作为类的一个拥有属性,从而每一个学生拥有自己的姓名以及自己的学习能力实现方式
private Learnable learnable;
public Student(String name) {
this.name = name;
}
//在main方法中创建匿名内部类
public static void main(String[] args) {
Student s1 = new Student("Yu");
Learnable l1 = new Learnable() {
@Override
public void read() {
System.out.println("我要按自己的方式阅读");
}
};
s1.setLearnable(l1);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Learnable getLearnable() {
return learnable;
}
public void setLearnable(Learnable learnable) {
this.learnable = learnable;
}
}
//省略声明变量,一次性构建
public static void main(String[] args) {
Student s2 = new Student("Son");
s2.setLearnable(new Learnable() {
@Override
public void read() {
int number = 10;
System.out.println("我要读" + number +"本书");
}
});
System.out.println(s2.getLearnable());
s2.getLearnable().read();
}
同理 虽然无法创建抽象类的对象 但基于匿名内部类 可以直接创建抽象类子类的对象
即使普通类 也支持直接创建一个匿名内部类子类 及对象
public class Bicycle {
private int gear;
public Bicycle() {
this.gear = gear;
}
public static void main(String[] args) {
//基于匿名内部类直接创建Bicycle的子类及对象
//实现子类的操作。此处重写了Bicycle父类中的方法
Bicycle b = new Bicycle() {
@Override
public int getGear() {
return super.getGear();
}
};
}
public int getGear() {
return gear;
}
public void setGear(int gear) {
this.gear = gear;
}
}
Final
final修饰的方法,无法被子类重写
final修饰的类,无法被继承
Object as a Superclass
java.lang.Object类,位于类层次结构树的顶部
每个类都直接或间接继承自Object类
每个类都继承Object类中的实例方法
public final Class getClass()
无法重写此方法
用于动态获取类型信息,方法返回Class类型对象,对象中具有获取有关类型信息的方法。例如,获取类型的全限定类名/超类/实现的接口/声明的方法/修饰的注解等等
Order of Java Initialization
public class A {
public static String getS() {
System.out.println("A:static method getS()");
return S;
}
private static String S = create();
private static String create() {
System.out.println("A:static method create()");
return "";
}
static {
System.out.println("A:static block 2");
}
static {
System.out.println("A:static block");
}
public static void main(String[] args) {
A.getS();
}
}
执行A.getS()方法时:
首先加载类至类加载器(仅加载,并非实例化)
只要类被加载,即执行static相关操作
按static变量或static代码块声明的先后顺序,初始化static变量或执行static代码块
当初始化static变量S时,执行create()方法
执行调用的static方法getS(),返回变量
第二次调用getS()方法,直接返回static变量的值/引用,而不会再次执行create()方法
Static变量是在类加载时完成的初始化,与是否调用getS()方法无关
即,加载类时,初始化static变量,执行static代码块 Static变量一旦赋值即置于堆空间的静态区共享使用
追加了构造函数
当执行A.getS()时 A当然未被实例化!
即,调用static方法加载类时,不会实例化该类 进一步说明了,static是类相关,与类的实例无关
即使,没有调用 static方法getA() 依然,初始化了static变量 后调用A构造函数初始化A
静态方法,不主动调用是不会执行的
当实例化A时 需先加载类,因此,会先执行static相关操作 后调用构造函数初始化A
只要类被加载,无论是由于被实例化,还是被调用static成员(变量/常量/方法),均按声明的先后顺序,初始化static成员或执行static代码块,后实例化类
实例化子类
预实例化子类B
1,加载父类A,执行A static相关
2,加载子类B,执行B static相关
3,调用子类B构造函数,必先调用父类A构造函数
4,调用子类B构造函数
类C中,包含类型A的属性变量 但构造C时,并没有加载类A 即,仅声明类型变量 不会加载该类型
public class A {
public A() {
System.out.println("A:constructor");
}
static {
System.out.println("A:static block");
}
private static String S = create();
private static String create() {
System.out.println("A:static method create()");
return "";
}
}
public class C {
private A a = new A();
public C(A a) {
this.a = a;
System.out.println("C: Constructor");
}
static {
System.out.println("C:static block");
}
public static void main(String[] args) {
new C(new A());
}
}
实例化一个类:
先,基于类加载的顺序(父类-子类),执行类static相关操作
再,执行类中属性初始化操作
最后,调用构造函数完成类的初始化 因此,构造函数中的初始化,可以覆盖属性的初始化
在进行new C(new A())操作时,我们要实例化C,所以先基于类C加载的顺序,执行static相关操作,输出C:static block,接着执行类C中属性初始化操作,初始化A,进行new(A)操作,跟刚刚类C的static操作一样输出A:static block,再进行类A中属性初始化,初始化S,输出A:static method create(),最后调用构造函数,输出A:constructor,此时我们类C中的属性初始化操作已经完成,最后需要调用构造函数,传入参数new A(),进行了一次A的构造,所以输出A:constructor,进行new C(new A()),最后输出C: Constructor