Java用extends
关键字来标识继承关系:
class Test {
public Test() {
TODO();
}
protected void doSomething() {
TODO();
}
protected Test doIt() {
return new Test();
}
}
class Test2 extends Test {
public Test2() {
super(); // 调用父类的构造方法
super.doSomething(); // 调用父类的成员函数
}
public void doSomethingNew() {
TODO(); // 新的成员函数
}
public void doSomething() { // 重写父类方法
TODONEW();
}
protected Test2 doIt() { // 重写父类方法
return new Test2();
}
}
子类可以用super
关键字调用父类的方法;
子类没有权限调用父类中被修饰为private
的方法,只能是public
和protected
;
继承还可以重写父类的成员方法:保留名称,修改实现,更改权限,甚至是返回值(但类型必须是同一方法返回值类型的子类);
特殊的重写:函数签名和返回值不变,但内容不同,叫做重构;
重写父类方法时,修改权限只能扩大范围:public > protected > private
;
在实例化子类对象时,编译器会自动调用父类的默认构造函数,其他构造函数则需要显式调用。
举例即可,无过多细节:
class Base {
public static void f(Base base) {
TODO();
}
}
class Extend1 extends Base {
...
}
class Extend2 extends Base {
...
}
此时像这样调用函数:
Base.f(new Extend1());
Base.f(new Extend2());
都是可以的,这就是多态,利用这种特性,可以减少在子类中重复编写代码。
解决实际问题时常常将父类定义为抽象类,派生类是其的具象化:
public abstract class Test {
abstract void testbstract();
}
由于抽象的特性,抽象类是无法被实例化对象的,但其子类可以;
抽象方法没有方法体,因为没有意义,但抽象类可以有非抽象方法;
只要类中有抽象方法,它就是抽象类;
抽象类被继承后需要实现其中所有的抽象方法。
背景:Java规定类不能同时继承多个父类,为应对这些问题,接口应运而生。
接口(Interface
)算是纯粹的抽象类,因为它的方法必须全是抽象的:
public interface InterfaceTest {
// 接口内的方法全是抽象的,所以不再需要abstract关键字
void f();
void g();
}
一个类实现一个接口,用的是implements
关键字:
public class MyClass extends BaseClass implements InterfaceTest {
...
}
在接口中定义的任何字段都自动是
static
和final
的.
Java
中不支持多重继承,但使用接口就可以实现:
class MyClass implements Interface1, Interface2, ...
背景:同一个文件夹中的类不断增加,很容易出现两个名字相同但功能不同的类,这个时候就需要将这两个类放入不同的包里。
一个完整的类名需要包名和类名共同组成,例如我们常用的Math
包,导入时要写下:
import java.lang.Math;
这里java.lang
就是包的名称,后面的Math
是类名。
package net.java.util;
class MyClass {
...
}
那么它的路径就是net/java/util/MyClass.java
这里我们不想再依赖IDE,比如Eclipse
去帮我们创建包,而是去手动构建。
例子:在文件夹中创建两个文件,Animal.java
和MammalInt.java
,
/* 文件名: Animal.java */
package animals;
interface Animal {
public void eat();
public void travel();
}
/* 文件名 : MammalInt.java */
package animals;
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
然后我们在终端做如下处理:
> mkdir animals # 对应包名
> cp Animal.class MammalInt.class animals # 将文件移到与包名相同的子文件夹中
> java animals/MammalInt # 编译运行
Mammal eats
Mammal travel
例子摘自https://www.runoob.com/java/java-package.html
import java.lang.Math;
import java.lang.*
在Java中将Java源文件与类文件放在一起管理是极为不好的管理方式。我们可以在编译时使用-d
参数设置编译后类文件产生的位置:
javac -d ./bin/ ./xyz/welts/*.java
这样编译成功后将在当前运行路径下的bin
目录中产生xyz/welts
路径,并在该路径下出现相应的类文件。
我们还可以使用import
导入静态成员,使我们编程更加方便:
import static java.lang.System.out;
public class Main {
public static void main(String[] args) {
out.println("Hello world");
}
}
如此,我们就可以减少代码的书写量。
下面是管理自己java
文件的一种简单方式:
将类,接口等类型放在一个文本中,这个文件的名字就是这个类型的名字,并且以.java作为扩展名:
package matriculate;
public class Matrix {
...
}
接下来将源文件放在一个目录中,这个目录要对应所在包的名字:
./matriculate/Matrix.java
现在,正确的类名和路径将会是如下样子:
类名:matrixculate.Matrix
路径名:matrixculate/Matrix.java
通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是welts.xyz
,所有的包名都以xyz.welts
开头。包名中的每一个部分对应一个子目录。
例如有个叫xyz.welts.matriculate
的包,这个包包含一个叫Matrix.java
的源文件,那相应的,应该有如下一连串子目录:
./xyz/welts/matriculate/Matrix.java
这时再用上面所说-d
选项编译文件,可以放在同一文件夹,也可以放在不同文件夹以便于分类管理。
我们在之前已经接触过final
变量:
final int x = 10;
事实上,final
也可以修饰对象引用,当一个对象引用被修饰为final
后,他只能恒定指向一个对象,无法改变其指向另一个对象(类似于C++
中的Type* const ptr
),一个既是static
又是final
的字段只占据一段不能改变的存储空间:
public final Matrix A = new Matrix(3, 3);
只在一个对象中无法改变,每个对象都有自己不变的A;
public static final Matrix A = new Matrix(3, 3);
被所有对象共享,而且不变,所以只占据一段不变的内存空间。
在Java中定义全局变量,通常使用
public static final
修饰,这样的常量只能在定义时被赋值。
final
方法就是不能被override
的方法,final
类就是不能被继承的类。
我们在前面提过,父类中的private
方法没有对子类开放,所以它被默认为final
方法,因此就不存在private final
这种写法。
final
类的所有方法都被隐式设置为final
形式,但是final
类中成员变量可以被定义成final
或非final
形式。
public class OuterClass {
private Type MemberVar;
public class InnerClass {
... // 你可在这里使用`MemberVar`
}
...
}
此时可以在内部类中直接存取其所在类的私有成员变量和方法,反之不然,但可以在外部类通过内部类对象引用调用成员变量。
你还可以像这样定义一个类:
class OuterClass {
public int function(final String x) {
class InnerClass {
InnerClss(String s) {
s = x;
System.out.println(s);
}
}
return new InnerClass("Hello");
}
}
作用域告诉我们,此时OuterClass
类中其他部分无法使用InnerClass
;此外,我们发现function
的参数是final
类型,因为在方法中定义的内部类只能访问方法中的final
类型局部变量。
我们来看一个有趣的程序:
class OuterClass {
public InnerClass function() {
return new InnerClass() {
private int i = 0;
public int getValue() {
return i;
}
};
}
}
这就是一个匿名内部类的创建,由于匿名函数没有名称,所以所有匿名内部类使用默认构造方法来生成OutClass
对象。