对abstract关键字的误解

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

import org.junit.*;
import static org.junit.Assert.*;
public class MyTest
{
	@Test
	public void testAbc() {
		assertEquals(1, 1);
	}
}

然后运行报错

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

修改,让类继承TestCase类:

import org.junit.*;
import junit.framework.TestCase;
import static org.junit.Assert.*;

public class MyTest extends TestCase 
{
  //里面不变
}

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

java -cp lib/junit-4.13.2.jar;. junit.textui.TestRunner MyTest
.
Time: 0.003

OK (1 test)

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

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

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

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

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

赶紧写个类试试:

public abstract class Abs
{
}

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

于是翻出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.

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值