1、extends和final
继承基本就是避免多个类重复定义共同的行为。java的类只能单继承,不能像C++那样从多个类继承(接口除外),Java只有多层继承。java中所有的类都是从java.lang.Object直接或间接派生,所以类都具有其方法。使用extends来从指定类继承,如果不想类当做父类即不能派生其它类,可以使用final来声明这个类(String、System都是final类型)。
与C++不同,java类的声明不以分号结尾,而且在定义对象的时候构造方法的括号不能省略:
class Base{}
final class sub extends Base{}
Base b = new Base();
final用于声明变量的话表示该变量的值不能被修改,类似于c++中的const。如果类的成员变量使用了final声明但没有指定初始化值的话(如下所示)表示延迟对象成员值的指定,在构造函数中一定要有对该成员变量的赋值操作,否则编译不成功。final也可以用来声明方法,表明该方法不能被子类重写。
class Foo{
Foo(){
this(10); //调用另一个构造方法,如果没有这行的话编译出错
}
Foo(int x){
m_x = x;
}
final int m_x;
}
2、override
java中子类重写(覆盖)父类方法时,子类方法参数列表应该相同,子类方法的返回值类型应该与父类方法相同或是其子类,子类方法抛出的异常类也是应与父类方法相等或更小。与C++不同,Java中没有virtual关键字,但我们可以重写任何方法来实现多态 (除了static、native等特殊方法),所以其实Java类中的普通函数就是C++的虚函数。可以在子类重写的方法的声明前加上@Override表明是重写的子类方法。可以使用super来调用父类中被覆盖的方法(如果是调用父类中没有被子类覆盖的方法的话可以直接在子类中调用,而不用加super),跟this一样,super也不能出现在静态方法中,因为它是通过一个对象而不是类来调用的:
class Base
{
public int num;
public void func()
{
System.out.println("Base func");
}
}
class Sub extends Base
{
public int num = 10;
@Override
public void func()
{
System.out.println("Sub func");
}
public void test()
{
int i = num; //使用子类的num
int j = super.num; //使用父类的num
func(); //调用子类的func
super.func(); //调用父类的func
}
}
子类重写父类的成员变量时,相当于隐藏了父类中的成员变量:
class Parent
{
public String tag = "Parent";
}
class Child extends Parent
{
private String tag = "Child";
}
public class Test
{
public static void main(String[] args)
{
Child d = new Child();
//String s = d.tag; //错误,父类中tag被隐藏
String str = ((Parent)d).tag; //强制转换后可以访问tag
}
}
Java的多态:
class Parent{
void test(){
System.out.println("parent");
}
}
class Child extends Parent{
@Override
void test(){
System.out.println("child");
}
}
public class Main {
public static void main(String[] args){
Parent p = new Child();
p.test(); //输出为child
}
}
3、初始化块
初始化块在构造函数之前执行,使用{}来定义初始化块,初始化块与成员变量初始化的执行先后顺序为代码的先后顺序,且如果初始化块在成员变量初始化之前的话,初始化块内只能对该成员变量进行初始化,不能引用该成员变量。static{}用来定义静态初始化块,静态初始化块只能初始化静态成员。当创建类的实例的时候会先执行静态初始化(静态初始化块和静态成员变量的初始化),然后执行非静态初始化。当引用类的成员(静态方法或静态成员变量)的时候会执行静态初始化(静态初始化块和静态成员变量的初始化)。
public class Test
{
{
//第二执行
num = 10;
//System.out.println(num); //错误! 现在只能初始化num,不能引用num
}
public int num = 20; //第三执行
{
//第四执行
num = 30;
System.out.println(num);
}
public Test()
{
//构造函数在初始化完成后才执行
num = 40;
System.out.println(num);
}
static
{
//静态初始化块最先执行
//num = 0; //如果num是静态成员的话
System.out.println("static");
}
public static void main(String[] args)
{
Test t = new Test();
}
}
下面运用Class.forName()加载Driver操作类的位码文件来实现加载JDBC驱动程序中,这个代码会将Driver.class载入JVM,而
Driver类的原始码中就是在static区块中进行驱动程序实例注册的动作:
Class.forName("com.mysql.jdbc.Driver");
4、构造函数
java类中默认也存在一个不带任何参数的构造函数,当自定义了构造函数后就不能再使用它,除非自己显示定义了一个无参数的构造函数。类的对象在内存分配后会自动进行成员变量的初始化,初始化规则与数组自动初始化规则相同,即数值类元素初始化为0、boolean类型为false、引用类型为null。默认情况下父类的构造会使用其无参数的构造函数,子类中可以通过super()来指定父类使用的构造函数,super()只能出现在构造函数的第一行:
class Parent
{
public String name;
public double size;
public Parent(String name, double size)
{
this.name = name;
this.size = size;
}
}
class Child extends Parent
{
public String color;
public Child(String name, double size, String color)
{
super(name, size); //构造父类对象
this.color = color;
}
public static void main(String[] args)
{
Child c = new Child("测试", 5.6, "红色");
}
}
如下所示代码会编译错误,因为子类会调用父类的默认构造函数,而父类中因为自定义了构造函数,所以默认构造函数已经不存在了:
class Parent
{
Parent(int i){}
}
class Child extends Parent
{
Child(){}
}
java中的构造函数可以调用同一个类中的另一个重载的构造函数,通过调用this(),需要注意的是this()同super()一样只能出现在构造函数的第一行:
public class Apple
{
public String name;
public String color;
public double weight;
public Apple(String name, String color)
{
this.name = name;
this.color = color;
}
public Apple(String name, String color, double weight)
{
this(name, color);
this.weight = weight;
}
}
java中没有C++中的析构函数。
5、父类子类的转换
子类对象可以赋值给父类引用变量;父类对象不能赋值给子类引用变量;父类引用可以强制转换为子类引用,此时父类引用必须实际指向的是一个子类对象,如:
Object obj = "test"; //子类String对象赋值给父类Object引用变量
String s = new Object(); //子类引用不能指向父类对象
String str = (String)obj; //可以将obj强转为String对象,因为对象obj实际上是一个String类型
在强制转换之前可以使用instanceof运算符来判断这个对象是否是指定类型或其子类或其实现类:
Object obj = "test";
if(obj instanceof String)
{
String str = (String)obj;
System.out.println("df");
}
6、多态
class Base
{
public int id = 0;
public void func()
{
System.out.println("Base func");
}
}
class Sub extends Base
{
public int id = 1;
public void func()
{
System.out.println("Sub func");
}
public void test(){}
public static void main(String[] args)
{
Base b; //b编译时类型为父类Base
b = new Sub(); //允许将子类对象赋给父类引用变量
b.func(); //输出为"Sub func",即b运行时类型为子类Sub,这种通过父类引用调用子类方法,是多态的表现
int i = b.id; //i为0,成员变量不具备多态性
//b.test(); //错误,不能调用非重写函数
((Sub)b).test(); //强制类型转换后可以调用非重写函数,Base父类对象b实际上是一个Sub子类对象,所以可以强制类型转换
}
}