Java 里的 abstract 和 final 关键字


一. abstract

abstract 的中文意思就是抽象的, 所谓抽象就是这个东西在现实里不存在, 也就是不能直接实例化的意思.
abstract 可以修饰类, 类的方法


1.1 abstract 修饰 类.

现实中有一些类是不应该实例化的. 例如

                 植物
               /          \
        开花植物  蕨类
       /               \
    裸子植物   被子植物
                            /       \
                       苹果     芒果


上面那个简单的类结构体中,  前面3层都是抽象的, 在自然界没有现实的对象存在, 所以在面向过程中应该把它们设为抽象类.
只有下面的若干层派生类才应该可以被实例化.

也可以看出, 抽象类一般是类族的最上面若干层.


看下面的例子.

abstract class Abs_A1{
	int id;
	String name;	
	abstract void print();
}


class A2 extends Abs_A1{
	void print(){
		System.out.printf("A2\n");
	}	
}

abstract class Abs_A3 extends A2{
	abstract void print();	
}

public class Abstract_1{
	public static void f(){
		//Abs_A1 a1 = new Abs_A1();   //error
		A2 a2 = new A2(); //ok
		//Abs_A3 a3 = new Abs_A3() //error

		Abs_A1 a1 = new A2(); //ok,  
		a1.print();
	}
}


上面定义了2个抽象类A1,A3,   1个非抽象类A2.   它们的关系是 A1 派生出 A2, A2 派生出 A3.

然后我们来从下面的f() 函数里分析一下抽象类的实现.


1.1.1  抽象类不能直接实例化

这个很明显了, 上面的例子里尝试直接实例化, 都会编译失败.

1.1.2  非抽象类可以派生出抽象类

例如上面A2 派生出Abs_A3

1.1.3  抽象类一般和多态技术相结合

Abs_A1 a1 = new A2(); //ok,  
a1.print();

看上面两句,  虽然定义了1个Abs_A1 的引用, 但是它指向了它的派生类的实例化对象,  这样虽然a1 是1个抽象类的引用, 但是它仍然可以调用派生类重写后的方法.
这个就是多态的关键点之一.



1.2 abstract 修饰方法.

abstract 修饰一个方法就是表示这个方法是一个抽象方法, 所谓抽象方法就是没有方法体的函数.  也就是必须在派生类中重写后才能调用.

例子: 

abstract class Abs_A4{
	int id;
	String name;
	public Abs_A4(int id, String name){  //abstract class can have non-abstract function.
		this.id = id;
		this.name = name;
	}

	public abstract void print();  //abstract function, with no function body, must have symbol ";"

	public static void print2(){
		System.out.print("A4:function print2\n");	
	}

	//cannot use "private" & "abstract" togethe
	//private abstract void print3();

	// cannot use "static" & "abstract" together
	//public static abstract void print3();
}

class A5 extends Abs_A4{
	public A5(int id, String name){
		super(id, name);
	}

	//note: must subclass must overwrite all the abstract function of it's superclass
	public void print(){  //overwrite superclass' abstract function
		System.out.printf("A5: id is %d, name is %s\n", id, name);
	}

	//error!  cannot define an abstract function in a non-abstract class
	//public abstract void print4();


}


public class Abstract_2{
	public static void f(){
		Abs_A4 a4 = new A5(1, "Jack");
		a4.print();

	}
}


上面定义了1个抽线类Abs_A4 和非抽象类 A5,  下面就利用上面的例子列出抽象方法的一些特性.

1.2.1  抽象方法不能包含函数体, 而且要用分号结尾

例如上面的类Abs_A4 里的
public abstract void print(); 

1.2.2  一个抽象类可以存在非抽象方法

例如上面的类Abs_A4的构造方法就是非抽象的.


1.2.3  抽象方法不能存在与非抽象类中

例如上面例子这例子中:
//error!  cannot define an abstract function in a non-abstract class
//public abstract void print4();

这句是错误的, 因为尝试在非抽象类A5中定义1个抽象方法, 必须注释掉

也就说, 如果1个类只要存在1个抽象方法, 那么它必须是抽象类.

1.2.4  如果1个非抽象的派生类继承1个抽象类, 那么这个非抽象类必须重写所有它的抽象超类的所有抽象方法

例如上面的例子中, Abs_A4里存在1个抽象方法print(), 如何非抽象类A5 继承 Abs_A4, 则A5 必须重写print(), 否则编译失败.

其实这跟上面的1.2.3 特征是同一道理,  也就是: 非抽象类不能存在抽象方法!


1.2.5  不能用private 修饰抽象方法.

Java里, private修饰的成员是能被子类隐藏继承的.   但是private修饰的方法不能被继承.

如果1个超类存在1个private方法f(),   子类再定义1个同名方法f(),  那么这个两个方法只是简单的同名方法, 并没有继承关系.

所以如果在一个抽象超类定义1个private abstract 方法, 那么非抽象子类则无法继承这个方法, 也就是说无法重写了.

所以Java里是不允许private 和 abstract 同时使用修饰1个方法的.  编译会失败.

实际上private 方法都是final 方法, 下面会提到.


1.2.6  不能用static 修饰抽象方法.

static 方法是属于类本身的方法.  而且static 方法不需要实例化就可以调用, 如果定义1个static抽象方法, 那么直接用利用类名来调用这个方法就很奇怪了, 因为抽象方法没有函数体啊.


现实中理解:

假如1个植物类族,  那么植物就是抽象类.

重量, 体积 这些可以定作为成员, 可以被所有子类继承.

繁殖可以定为抽象方法, 因为各种植物的繁殖方法很多种, 由各个子类重写.

求重量, 求体积的方法可以定义成公共可继承方法.  因为这些方法的函数体基本不变.

一些公式转换, 例如光合作用的公式可以定义成静态方法, 这些方法不必实例化就可以使用.

至于抽象静态方法,  本人觉得现实中还是存在的, 例如高等植物和低等植物(蓝藻等)的RNA/DNA 合成公式可能不同, 有必要重写.    但是JAVA中不允许抽象静态方法的存在, 原因上面已经提到.



1.3 abstract 不允许修饰成员.

原因也很简单啦, 因为成员是没有重写这个概念的.

也即系讲, java中冇抽象成员这个概念.





二. final


2.1 final 修饰 类.

final 修饰类则表示这个类是1个无法被继承的类, 也就是在类族树中最底层的类,  如果你不想1个类被派生出子类, 那么可以用final 来修饰这个类.

下面例子:

package Object_kng.Final_kng;

final class Fnl_A8{
	int id;
	String name;

	public void print(){

	}
}

//class A9 extends Fnl_A8{   //error. cannot inherit a final class
class A9{
	int id;
	String name;
	public void print(){
		System.out.printf("A5: id is %d, name is %s\n", id, name);	
	}
}


public class Final_1{
	public static void f(){

	}
}


上面的Fnl_A8 是1个最终类,   当A9尝试继承Fnl_A8 会编译失败..

没什么特别要注意的地方.


2.2 final 修饰方法

Final 修饰1个方法表示这个方法是无法被派生类重写.   如果你希望1个方法不再被其派生类重写, 可以用final来修饰这个方法.

下面例子:
class A10{
	int id;
	String name;

	public final void print(){
		System.out.printf("A10: id is %d, name is %s\n", id, name);	
	}

	public final static void print2(){
		System.out.printf("A10:print2\n");
	}

	private void print3(){  // Java will treat all the private mothods as final methods
		System.out.printf("A10:print3\n");
	}
}

class A11 extends A10{
	//error ,  final method cannot be overwrited in subclasses 
	//public void print(){

	//}

	public void print3(){ // it's not overwriting superclass's print3, another mother
	                                 //have a same name with superclass's print3
		System.out.printf("A11:print3\n");
	}
}


public class Final_2{
	public static void f(){
		A11 a11 = new A11();
		a11.id = 1;
		a11.name = "jack";	
		a11.print();
		a11.print2(); //  A10:print2
		a11.print3(); //  A11:print3
	}
}

上面定义两个类其中A11 继承 A10

下面分析下fina方法的一些特征

2.2.1 final方法可以被子类继承, 但是不能重写.

这个是最基本的了, 如上面A10 的方法 print 和 print2.



2.2.2 final类里可以定义final方法.

编译会通过, 但是final类不能被继承, 所以里面定义final无业务上的意义. 但是貌似会轻微加快编译和运行速度.




2.2.3 final 和 abstract 关键字不能一齐使用

也就是无论类或方法, 都不能同时是抽象的也是final的.



2.2.4 private方法会被认为是final方法.

也就是说private 方法不能被继承和重写.


但是还是有区别的, 如上面的例子,  A10 定义了1个public final 方法print().   当类A11尝试定义1个名字为print()  (同名同参数)的方法是, 编译会失败.

但是A10还定义了1个private方法print3().        

而A11 还是允许定义1个名字叫print3()的方法的, 这不过这两个print3()方法不是重写关系, 只是简单的重名.





2.3 final 修饰成员

final修饰成员or变量就表示这是1个常变量,  相当c/c++ 的const 常量.


final成员必须赋值且只能赋一次值. 但是 默认赋值并不是真正的赋值.     

这里所谓的默认赋值是指  int  i;  i的默认值就是0, 但是final成员不允许这样赋值

也就是说 final成员允许两种赋值,  1是定义成语时同时赋值.   2是在构造函数中赋值.

例子:


class A12{
	final int id;
	final int id2 = 2;
	//final int id3; //error
	public A12(int id){
		this.id = id;
		//this.id2 = 2; //error
	}

	public void f(){
	 	//id2 = 2;  //error
	}
}

public class Final_3{

}


  

2.3.1  final变量必须赋值.

例如上面

//final int id3;   没有在定义时赋值, 也没有在构造函数中赋值,则编译失败.

2.3.2  final变量不能赋两次值

例如上面的id2,  在定义时赋值了, 如果尝试在构造函数再次赋值则编译失败


2.3.3  final变量能在非构造方法赋值

因为构造方法只会在实例化时执行一次, 而普通方法的执行次数是不定的.









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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值