• 对abstract关键字的误解


    起因是手写了一个JUnit4测试类:

    1. import org.junit.*;
    2. import static org.junit.Assert.*;
    3. public class MyTest
    4. {
    5. @Test
    6. public void testAbc() {
    7. assertEquals(1, 1);
    8. }
    9. }

    然后运行报错

    1. java -cp lib/junit-4.13.2.jar;. junit.textui.TestRunner MyTest
    2. .F
    3. Time: 0.005
    4. There was 1 failure:
    5. 1) warning(junit.framework.TestSuite$1)junit.framework.AssertionFailedError: No tests found in MyTest

    修改,让类继承TestCase类:

    1. import org.junit.*;
    2. import junit.framework.TestCase;
    3. import static org.junit.Assert.*;
    4. public class MyTest extends TestCase
    5. {
    6. //里面不变
    7. }

    这下OK了(其实这个代码改法和命令行调的主类都有问题,等于混用了JUnit3)

    1. java -cp lib/junit-4.13.2.jar;. junit.textui.TestRunner MyTest
    2. .
    3. Time: 0.003
    4. OK (1 test)

    接着看了一眼TestCase类的源码,发现它是个抽象类:

    1. public abstract class TestCase extends Assert implements Test {
    2. //略...
    3. }

    当时心里的想法是:既然是抽象类,就一定至少有个抽象方法,让子类来实现。但是MyTestCase里的方法都是自己手写的测试方法(testXXX),没有专门去override覆盖父类的什么方法,为什么MyTestCase能成功编译?见鬼了?

    又在TestCase类源码里找了半天,但真的没有找到abstract抽象方法!

    所以这时想到:难道没有抽象方法,也能标记为抽象类?

    赶紧写个类试试:

    1. public abstract class Abs
    2. {
    3. }

    果然,编译通过!为什么,难道之前看书不仔细导致理解不正确?

    于是翻出James Gosling的《The Java Programming Language》第4版,找到对应的章节

    3.7 抽象类和抽象方法

            面向对象编程有个极其有用的特性:抽象类的概念。通过使用抽象类,你可以声明只定义了一部分实现的类,而把部分甚至全部的方法都留给扩展类,让它们提供特定的实现。抽象的反面是具体,只有具体方法的类叫做具体类,这些具体方法也包括了从超类继承过来的抽象方法的实现。

            抽象类在这种情况下很有用:如果某些行为对某个类型的大部分或者全部对象都是确定的,而其他行为只对部分的类有意义,但对通用的超类无意义。这样的类就能声明为abstract,而这个类中所有不实现的方法都标记为abstract。(如果你想定义一些方法但不想给出任何实现,你可能需要的是接口interface,参见第4章)

            接下来举一个基准测试套件的例子。它知道怎么驱动并度量,但没法事先知道要跑什么基准测试。大部分抽象类都匹配“模板方法”这个设计模式。大部分情况下模板方法可以加上final修饰,防止被篡改。

    abstract class Benchmark

    {

            abstract void benchmark();

            public final long repeat(int count) {

                    long start = System.nanoTime();

                    for (int i = 0; i < count; i++)

                            benchmark();

                    return (System.nanoTime() - start);

            }

    }

            类只要包含有abstract抽象方法,就必须声明为抽象abstract的。这样做虽然有些冗余,但好处是读者不需要去扫描一个类中所有的方法,才能确定这个类是否抽象的。【注:这一段虽然明确了“只要有抽象方法就必须是抽象类”,但是没说“只要是抽象类就会有抽象方法”。所以这里理解错了。另外,冗余两个字也很微妙,它形容的是把“类声明为abstract”,感觉类的abstract声明好像只是个附属品。】

            【省略两段。。。】

            任何类都能将超类继承过来的方法覆写(override)并声明为abstract。这这个技巧很有用,比如当超类的默认实现对这部分子类无效的时候。

            不能创建一个抽象类,因为这会导致一些方法调用时找不到实现。【注:但如果这个抽象类里面没有抽象方法,那应该是没问题的,估计java省掉了这个运行时检测】

    书里只说了那么多,还是有些含糊。找找Java语言规范看看,链接是 Chapter 8. Classesicon-default.png?t=N7T8https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.1.1.1

    第一句就很含糊,“认为不完整”,是程序员还是编译器认为不完整?

    An abstract class is a class that is incomplete, or to be considered incomplete.

    后面给出了一些样例,但也没有说明什么情况下把一个没有抽象方法的类声明为抽象的。

    不过倒是专门提到,abstract是用来给子类补充实现的,如果你不想让人实例化你的类,不是去对类加abstract,而是只留一个private的无参构造器并永远不去调用它。(Java1.6就有这段了)

    虚拟机规范里面,好像也没有说类和方法两者的ACC_ABSTRACT标志的关系。

    Chapter 4. The class File Format (oracle.com)icon-default.png?t=N7T8https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html#jvms-4.10

    估计还得看看JUnit的文档,搞清楚作者对TestCase加abstract的意图。

    不过TestCase是JUnit3的类,太老了。十分怀疑这就是十年前的Java代码风格,即作者为了不让用户直接实例化一个实体类,而专门对这个实体类加上abstract。

    另外,还找到一个Oracle的Java教程(这个教程是Java8时写的,他们后面都不维护了)

    Abstract Methods and Classes (The Java™ Tutorials > Learning the Java Language > Interfaces and Inheritance) (oracle.com)icon-default.png?t=N7T8https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html里面明确说了抽象类可以不包含抽象方法:An abstract class is a class that is declared abstract—it may or may not include abstract methods.

    不过这个说法也很含糊,万一它的抽象方法是继承来的呢?

  • 相关阅读:
    springMVC/spring/Mybatis三大框架整合 看完必懂 秒杀阿里面试官
    vue3学习(十四)--- vue3中css新特性
    神策数据指标拆解工具正式上线
    Java中的IO操作
    基于STM32的智能小车--舵机云台设计
    Error: Cannot find module ‘webpack/lib/RuleSet‘
    JAVA1.8 jdk安装教程
    项目管理实战总结(一)
    Python自动化之跨平台GUI利器PyAutoGUI
    Git操作笔记
  • 原文地址:https://blog.csdn.net/baluzju/article/details/133634344