8 面向对象编程 8.5. final 关键字 8.6 抽象类 8.7 抽象类最佳实践-模板设计模式
8 面向对象编程
8.5. final 关键字
8.5.1 基本介绍
final 中文意思:最后的, 最终的.
final 可以修饰类、属性、方法和局部变量.
在某些情况下,程序员可能有以下需求, 就会使用到final:
-
- 当不希望类被继承时,可以用final修饰.【案例演示】
-
- 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。【案例演示:访问修饰符 final 返回类型 方法名 】
-
- 当不希望类的的某个属性的值被修改,可以用final修饰.【案例演示: public final double TAX_RATE=0.08】
-
- 当不希望某个局部变量被修改, 可以使用final修饰【案例演示: final double TAX_RATE=0.08 】
public class Final01 {
public static void main(String[] args) {
E e = new E();
// e.TAX_RATE = 0.09; // 编译错误,不能修改final属性的值
}
}
// 如果我们要求A类不能被其他类继承,可以使用final修饰A类
final class A { }
// class B extends A {} // 编译错误,不能继承final类
class C {
// 如果我们要求hi不能被子类重写,可以使用final修饰hi方法
public final void hi() {}
}
class D extends C {
// 编译错误,不能重写final方法
/*
@Override
public void hi() {
System.out.println("重写了 C 类的 hi 方法..");
}
*/
}
// 当不希望类的某个属性的值被修改,可以用final修饰
class E {
public final double TAX_RATE = 0.08; // 常量
}
// 当不希望某个局部变量被修改,可以使用final修饰
class F {
public void cry() {
// 这时,NUM也称为局部常量
final double NUM = 0.01;
// NUM = 0.9; // 编译错误,不能修改final局部变量的值
System.out.println("NUM=" + NUM);
}
}
8.5.2 final 使用注意事项和细节讨论
-
- final修饰的属性又叫常量,一般用XX_XX_XX来命名
-
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
① 定义时:如public final double TAX_RATE=0.08;
② 在构造器中
③ 在代码块中。
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
-
- 如果final修饰的属性是静态的,则初始化的位置只能是
① 定义时 ② 在静态代码块 不能在构造器中赋值。
- 如果final修饰的属性是静态的,则初始化的位置只能是
-
- final类不能继承,但是可以实例化对象。[A2类]
-
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。[A3类]
public class FinalDetail01 { public static void main(String[] args) { CC cc = new CC(); // 修正了变量名的语法错误 new EE().cal(); } } class AA { /* 1. 定义时:如 public final double TAX_RATE=0.08; 2. 在构造器中 3. 在代码块中 */ public final double TAX_RATE = 0.08; // 1.定义时赋值 public final double TAX_RATE2; public final double TAX_RATE3; public AA() { // 构造器中赋值 TAX_RATE2 = 1.1; } { // 在代码块中赋值 TAX_RATE3 = 8.8; } } class BB { /* 如果final修饰的属性是静态的,则初始化的位置只能是 1. 定义时 2. 在静态代码块 不能在构造器中赋值。 */ public static final double TAX_RATE = 99.9; // 静态常量定义时赋值 public static final double TAX_RATE2; static { // 静态代码块中为静态常量赋值 TAX_RATE2 = 3.3; } } // final类不能继承,但是可以实例化对象 final class CC { } // 非final类可以被继承 class DD { // final方法不能被重写,但可以被继承 public final void cal() { System.out.println("cal()方法"); } } // 继承DD类,可以使用父类的final方法 class EE extends DD { }
-
- 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
-
- final不能修饰构造方法(即构造器)
-
- final 和 static 往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。
class Demo{ public static final int i=16; // static{ System.out.println("~666"); } } 此处若调用Demo.i 不会导致类加载故~666不会输出
-
- 包装类(Integer,Double,Float,Boolean等都是final),String也是final类。
public class FinalDetail02 { public static void main(String[] args) { System.out.println(BBB.num); // 包装类和String是final类,不能被继承 // 例如: class MyString extends String {} // 编译错误 // 例如: class MyInteger extends Integer {} // 编译错误 } } // final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理 class BBB { public final static int num = 10000; static { System.out.println("BBB静态代码块被执行"); } } final class AAA { // 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法 // public final void cry() {} // 这里注释掉了多余的final修饰 }
8.6 抽象类
8.6.1 先看一个问题 Abstract01.java
public class Abstract01 {
public static void main(String[] args) {
// 抽象类不能被实例化
// Animal animal = new Animal("动物"); // 编译错误
}
}
//思考:这里eat 这里你实现了,其实没有什么意义
//即: 父类方法不确定性的问题
//===> 考虑将该方法设计为抽象(abstract)方法
//===> 所谓抽象方法就是没有实现的方法
//===> 所谓没有实现就是指,没有方法体
//===> 当一个类中存在抽象方法时,需要将该类声明为abstract类
//===> 一般来说,抽象类会被继承,有其子类来实现抽象方法
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
// 原有的具体方法实现(已注释,因为与抽象方法冲突)
/*
public void eat() {
System.out.println("这是一个动物,但是不知道吃什么..");
}
*/
// 抽象方法:没有方法体,需要子类实现
public abstract void eat();
}
8.6.2 解决之道-抽象类快速入门
当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法, 这个方法就是抽象方法,用abstract 来修饰该类就是抽象类。
我们看看如何把Animal做成抽象类, 并让子类Cat类实现。
abstract class Animal{
String name;
int age;
abstract public void cry();
}
8.6.3 抽象类的介绍
-
- 用abstract 关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{
}
- 用abstract 关键字来修饰一个类时,这个类就叫抽象类
-
- 用abstract 关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
- 用abstract 关键字来修饰一个方法时,这个方法就是抽象方法
-
- 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
-
- 抽象类,是考官比较爱问的知识点, 在框架和设计模式使用较多
8.6.4 抽象类使用的注意事项和细节讨论 AbstractDetail01.java
-
- 抽象类不能被实例化 [举例]
-
- 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法 [举例]
-
- 一旦类包含了abstract方法,则这个类必须声明为abstract [说明]
-
- abstract 只能修饰类和方法,不能修饰属性和其它的。[说明]
public class AbstractDetail01 {
public static void main(String[] args) {
// 抽象类不能被实例化
// new A(); // 编译错误
}
}
// 抽象类不一定要包含abstract方法,也可以有实现的方法
abstract class A {
public void hi() {
System.out.println("hi");
}
}
// 一旦类包含了abstract方法,则这个类必须声明为abstract
abstract class B {
public abstract void hi();
}
// abstract只能修饰类和方法,不能修饰属性和其它元素
class C {
// public abstract int n1 = 1; // 编译错误,abstract不能修饰属性
}
8.6.5 抽象类使用的注意事项和细节讨论 AbstractDetail02.java
-
- 抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等 [举例]
-
- 抽象方法不能有主体,即不能实现.如图所示
abstract void aaa(){}; (此处大括号上有红色叉号示意错误 )
- 抽象方法不能有主体,即不能实现.如图所示
-
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。[举例 A类,B类,C类]
-
- 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
public class AbstractDetail02 { public static void main(String[] args) { System.out.println("hello"); } } // 抽象方法不能使用private、final和static来修饰,因为这些关键字与重写相违背 abstract class H { // 以下都是错误的抽象方法声明方式 // private abstract void hi(); // 错误:private与重写冲突 // final abstract void hi(); // 错误:final与重写冲突 // static abstract void hi(); // 错误:static与重写冲突 public abstract void hi(); // 正确的抽象方法声明 } // 如果一个类继承了抽象类,则它必须实现所有抽象方法,除非自身也声明为abstract abstract class E { public abstract void hi(); } // F类继承了抽象类E,但没有实现抽象方法,因此自身必须声明为abstract abstract class F extends E { } // G类继承了抽象类E,必须实现所有抽象方法 class G extends E { @Override public void hi() { // 实现父类的抽象方法,必须有方法体 System.out.println("G类实现了E类的hi()方法"); } } // 抽象类的本质还是类,所以可以有类的各种成员 abstract class D { // 非静态属性 public int n1 = 10; // 静态属性 public static String name = "韩顺平教育"; // 非抽象方法 public void hi() { System.out.println("hi"); } // 抽象方法 public abstract void hello(); // 静态方法 public static void ok() { System.out.println("ok"); } }
8.7 抽象类最佳实践-模板设计模式
8..7.1 基本介绍
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
8.7.2 模板设计模式能解决的问题
-
- 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
-
- 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
8.7.3 最佳实践
需求
-
- 有多个类,完成不同的任务job
-
- 要求统计得到各自完成任务的时间
-
- 精确性实现 TestTemplate.java
-
- 矫情的自然浓缩
感情的自然浓缩
- 矫情的自然浓缩
- 先用最容易想到的方法-》代码实现
- 分析问题,提出使用模板设计模式
package com.ming.abstract_;
// 抽象类-模板设计模式
public abstract class Template {
// 抽象方法,由子类实现具体任务
public abstract void job();
// 模板方法,计算任务执行时间
public void calculateTime() {
// 记录开始时间
long start = System.currentTimeMillis();
job(); // 动态绑定,调用子类的实现
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("任务执行时间: " + (end - start) + " 毫秒");
}
}
package com.ming.abstract_;
public class AA extends Template {
// 实现Template的抽象方法job,完成累加任务
@Override
public void job() {
long num = 0;
for (long i = 1; i <= 800000; i++) {
num += i;
}
}
}
package com.ming.abstract_;
public class BB extends Template {
// 重写Template的job方法,完成累乘任务
@Override
public void job() {
long num = 1;
for (long i = 1; i <= 80000; i++) {
num *= i;
}
}
}
package com.ming.abstract_;
public class TestTemplate {
public static void main(String[] args) {
// 创建AA对象并计算其任务执行时间
AA aa = new AA();
aa.calculateTime(); // 利用多态调用模板方法
// 创建BB对象并计算其任务执行时间
BB bb = new BB();
bb.calculateTime();
}
}