起因是手写了一个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. Classeshttps://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)https://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)https://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.
不过这个说法也很含糊,万一它的抽象方法是继承来的呢?