大家好呀,这期我就继续在代码中带大家回顾Java中的基础知识,可以自己敲一下看看,加深理解。根据这些知识我还整理了一张图,基本上每个知识点都有注释详解
点这里加群领取资料
多态
定义
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。 多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数实现的。
作用
允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。 把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。 赋值之后,父类型的引用就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就是说,父亲的行为像儿子,而不是儿子的行为像父亲。 举个例子:从一个基类中派生,响应一个虚命令,产生不同的结果。 比如从某个基类派生出多个子类,其基类有一个虚方法Tdoit,然后其子类也有这个方法,但行为不同,然后这些子类对象中的任何一个可以赋给其基类对象的引用,或者说将子对象地址赋给基类指针,这样其基类的对象就可以执行不同的操作了。实际上你是在通过其基类的引用来访问其子类对象的,你要做的就是一个赋值操作。 使用继承性的结果就是当创建了一个类的家族,在认识这个类的家族时,就是把子类的对象当作基类的对象,这种认识又叫作upcasting(向上转型)。这样认识的重要性在于:我们可以只针对基类写出一段程序,但它可以适应于这个类的家族,因为编译器会自动找出合适的对象来执行操作。这种现象又称为多态性。而实现多态性的手段又叫称动态绑定(dynamic binding)。 简单的说,建立一个父类对象的引用,它所指对象可以是这个父类的对象,也可以是它的子类的对象。java中当子类拥有和父类同样的函数,当通过这个父类对象的引用调用这个函数的时候,调用到的是子类中的函数。
实现多态的方法
虚函数,抽象类,覆盖,模板
覆写
定义
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。 例如,在Person类中,我们定义了run()方法:
class Person {
public void run() {
System.out.println("Person.run");
}
}
在子类Student中,覆写这个run()方法:
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
Override和Overload不同的是,如果方法签名如果不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override。 注意:方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在Java程序中,出现这种情况,编译器会报错。
class Person {
public void run() { … }
}
class Student extends Person {
public void run(String s) { … }
public int run() { … }
}
加上@Override可以让编译器帮助检查是否进行了正确的覆写。希望进行覆写,但是不小心写错了方法签名,编译器会报错,但是@Override不是必需的。
public class Main {
public static void main(String[] args) {
}
}
class Person {
public void run() {}
}
public class Student extends Person {
@Override
public void run(String s) {}
}
覆写Object方法
因为所有的class最终都继承自Object,而Object定义了几个重要的方法: toString():把instance输出为String; equals():判断两个instance是否逻辑相等; hashCode():计算一个instance的哈希值。 在必要的情况下,我们可以覆写Object的这几个方法。例如: class Person {
@Override
public String toString() {
return "Person:name=" + name;
}
@Override
public boolean equals(Object o) {
if (o instanceof Person) {
Person p = (Person) o;
return this.name.equals(p.name);
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode();
}
}
税收例子
假设我们定义一种收入,需要给它报税,那么先定义一个Income类:
class Income {
protected double income;
public double getTax() {
return income * 0.1;
}
}
对于工资收入,可以减去一个基数,那么我们可以从Income派生出SalaryIncome,并覆写getTax():
class Salary extends Income {
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}
如果你享受国务院特殊津贴,那么按照规定,可以全部免税:
class StateCouncilSpecialAllowance extends Income {
@Override
public double getTax() {
return 0;
}
}
现在,我们要编写一个报税的财务软件,对于一个人的所有收入进行报税,可以这么写:
public double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}
来试一下:
public class Main {
public static void main(String[] args) {
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}
public static double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}
}
class Income {
protected double income;
public Income(double income) {
this.income = income;
}
public double getTax() {
return income * 0.1;
}
}
class Salary extends Income {
public Salary(double income) {
super(income);
}
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}
class StateCouncilSpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}
@Override
public double getTax() {
return 0;
}
}
运行结果:
800.0
观察totalTax()方法:利用多态,totalTax()方法只需要和Income打交道,它完全不需要知道Salary和StateCouncilSpecialAllowance的存在,就可以正确计算出总的税。如果我们要新增一种稿费收入,只需要从Income派生,然后正确覆写getTax()方法就可以。把新的类型传入totalTax(),不需要修改任何代码。 可见,多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
关键字final:用final修饰method可以阻止被子类覆写
Java还提供了一个final修饰符。final与访问权限不冲突,它有很多作用。 用final修饰class可以阻止被继承:
package abc;
public final class Hello {
private int n = 0;
protected void hi(int t) {
long i = t;
}
}
用final修饰method可以阻止被子类覆写:
package abc;
public class Hello {
protected final void hi() {
}
}
用final修饰field可以阻止被重新赋值:
package abc;
public class Hello {
private final int n = 0;
protected void hi() {
this.n = 1;
}
}
用final修饰局部变量可以阻止被重新赋值:
package abc;
public class Hello {
protected void hi(final int t) {
t = 1;
}
}
抽象类
定义
使用abstract修饰的类就是抽象类。
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。 因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。 使用abstract修饰的类就是抽象类。我们无法实例化一个抽象类:
Person p = new Person();
作用
因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。 例如,Person类定义了抽象方法run(),那么,在实现子类Student的时候,就必须覆写run()方法:
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run();
}
}
abstract class Person {
public abstract void run();
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
运行结果:
Student.run
面向抽象编程
当我们定义了抽象类Person,以及具体的Student、Teacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例:
Person s = new Student();
Person t = new Teacher();
这种引用抽象类的好处在于,我们对其进行方法调用,并不关心Person类型变量的具体子类型:
s.run();
t.run();
同样的代码,如果引用的是一个新的子类,我们仍然不关心具体类型:
Person e = new Employee();
e.run();
这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。 面向抽象编程的本质就是: 上层代码只定义规范(例如:abstract class Person); 不需要子类就可以实现业务逻辑(正常编译); 具体的业务逻辑由不同的子类实现,调用者并不关心。
关键字abstract
接口
定义
interface就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。 在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。 如果一个抽象类没有字段,所有方法全部都是抽象方法:
abstract class Person {
public abstract void run();
public abstract String getName();
}
就可以把该抽象类改写为接口:interface 。 在Java中,使用interface可以声明一个接口:
interface Person {
void run();
String getName();
}
所谓interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。 当一个具体的class去实现一个interface时,需要使用implements关键字。举个例子:
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
@Override
public String getName() {
return this.name;
}
}
我们知道,在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface,例如:
class Student implements Person, Hello {
...
}
抽象类与接口的区别
Java的接口特指interface的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。 抽象类和接口的对比如下:
| abstract class | interface |
---|
继承 | 只能extends一个class | 可以implements多个interface | 字段 | 可以定义实例字段 | 不能定义实例字段 | 抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 | 非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |
关键字interface
default方法
在接口中,可以定义default方法。例如,把Person接口的run()方法改为default方法:
public class Main {
public static void main(String[] args) {
Person p = new Student("Xiao Ming");
p.run();
}
}
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
运行结果:
Xiao Ming run
实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。 default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。
最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。 可以的话请给我一个三连支持一下我哟,我们下期再见
|