Java抽象类、接口、类的特殊成员
抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是并不是所有的类都是用来描绘对象的.
如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形 这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
抽象方法
:一种特殊的方法,它只有声明,而没有具体的实现(无方法体)。抽象方法的声明格式为:
abstract void f();
抽象方法必须用abstract关键字进行修饰。abstract 只能修饰类或类中的成员方法,不能修饰属性。被修饰的类或方法分别称作抽象类或抽象方法。其中抽象方法不能有方法体,而抽象类不能实例化,如果一个类含有抽象方法,则这个类一定为抽象类,抽象类必须在类前用abstract关键字修饰。当然,抽象类也可以没有抽象方法。
abstract class Test1{
abstract void f();//正确, 抽象方法不能有方法体
abstract void g(){;}//编译错, 抽象方法不能有方法体
void h();//编译错, 非抽象类必须有方法体
}
【注意】:abstract 不能修饰最终方法、静态方法或构造函数,因为这三类方法都不能被子类重写。
abstract class Test2{
abstract Test2();//编译错, 构造函数不能被abstract修饰
abstract final void f();编译错, final方法不能被abstract修饰
abstract static void g();编译错, 静态方法不能被abstract修饰
}
- 抽象类是契约的重量级应用方式
- 接口是契约的轻量级应用方式
接口
Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。
特点:
- 就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。
- 接口指明了一个类必须要做什么和不能做什么,相当于类的蓝图。
- 一个接口就是描述一种能力,比如“运动员”也可以作为一个接口,并且任何实现“运动员”接口的类都必须有能力实现奔跑这个动作(或者implement move()方法),所以接口的作用就是告诉类,你要实现我这种接口代表的功能,你就必须实现某些方法,我才能承认你确实拥有该接口代表的某种能力。
- 如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类)。
为什么要用接口:
- 接口被用来描述一种抽象。
- 因为Java不像C++一样支持多继承,所以Java可以通过实现接口来弥补这个局限。
- 接口也被用来实现解耦。
- 接口被用来实现抽象,而抽象类也被用来实现抽象,为什么一定要用接口呢?- 接口和抽象类之间又有什么区别呢?原因是抽象类内部可能包含非final的变量,但是在接口中存在的变量一定是final,public, static的。
接口的实现:
[修饰符] interface 接口名 [extends 父接口列表]{
[public][static][final]类型 成员常量 = 常量值
[public][abstract] 返回类型 成员方法名([参数列表])
}
具体的例子:
我们知道,如果某个设备需要向电脑中读取或者写入某些东西,这些设备一般都是采用USB方式与电脑连接的,我们发现,只要带有USB功能的设备就可以插入电脑中使用了,那么我们可以认为USB就是一种功能,这种功能能够做出很多的事情(实现很多的方法),其实USB就可以看做是一种标准,一种接口,只要实现了USB标准的设备我就认为你已经拥有了USB这种功能。(因为你实现了我USB标准中规定的方法)。
//先声明USB接口:其中规定了要实现USB接口就必须实现接口规定实现的read( )和write( )这两个方法。
interface USB {
void read();
void write();
}
//然后在写一个U盘类和一个键盘类,这两个类都去实现USB接口。(实现其中的方法)
class YouPan implements USB {
@Override
public void read() {
System.out.println("U盘正在通过USB功能读取数据");
}
@Override
public void write() {
System.out.println("U盘正在通过USB功能写入数据");
}
}
class JianPan implements USB {
@Override
public void read() {
System.out.println("键盘正在通过USB功能读取数据");
}
@Override
public void write() {
System.out.println("键盘正在通过USB功能写入数据");
}
}
//那么,现在U盘和键盘都实现了USB功能,也就是说U盘和键盘都能够调用USB接口中规定的方法,并且他们实现的方式都不一样。
public class Main {
public static void main(String[] args) {
//生成一个实现可USB接口(标准)的U盘对象
YouPan youPan = new YouPan();
//调用U盘的read( )方法读取数据
youPan.read();
//调用U盘的write( )方法写入数据
youPan.write();
//生成一个实现可USB接口(标准)的键盘对象
JianPan jianPan = new JianPan();
//调用键盘的read( )方法读取数据
jianPan.read();
//调用键盘的write( )方法写入数据
jianPan.write();
}
}
运行结果
U盘正在通过USB功能读取数据
U盘正在通过USB功能写入数据
键盘正在通过USB功能读取数据
键盘正在通过USB功能写入数据
【注意】:
1)接口不是类,接口中的方法都是抽象的,是没有方法体的没有构造函数,也不能实例化出对象。
2)一个类可以实现不止一个接口。
3)一个接口可以继承于另一个接口,或者另一些接口,接口也可以继承,并且可以多继承。
4)一个类如果要实现某个接口的话,那么它必须要实现这个接口中的所有方法。
5)接口中所有的方法都是抽象的和public的,所有的属性都是public,static,final的。
6)接口用来弥补类无法实现多继承的局限。
7)接口也可以用来实现解耦。
interface A{
int x = 1;
}
interface B{
int y = 2;
}
interface C extends A,B{
int z = 3;
}//也称C为复合接口,它有A,B两个父接口
类中的特殊成员
——内嵌类型、初始化块、本地方法
内嵌类型
内嵌类型就是在类或接口内部定义的自定义类型,你包括内部类和内部接口。包围内部类或内部接口的类称为囿类型,或包围类型、外部类型等。
class A{ //A是包围类
class B{ //内部类
int x;
}
interface C{ //内部接口
int y = 0;
}
}
编译后将产生三个文件:A.class、A$B.class、A$C.class
interface X{ //X是包围接口
class Y{ //内部类
int x;
}
interface Z{ //内部接口
int y = 0;
}
}
编译后将产生三个文件:X.class、X$Y.class、X$Z.class
内部类存取规则:
1、作为囿类的成员,内部类可以存取囿类的其他所有成员,包括私有成员。
2、存取内部类及其成员必须要借助囿类或囿类的对象。显然,若囿类对象不能访问,那么内部类也不能访问。
public class Ch_4_27 {
public static void main (String[] args) {
Z a=new Z();
Z.B ab=a.g(); //★★★借助囿类提供的方法获得内部类对象
ab.h2(); //★★★正确,可调用内部类的public方法
//ab.y=6; //编译错,不能访问私有成员
//A.B ab1=new A.B(); //编译错,不能以这种方式创建内部类对象
//A.B ab1=new a.B(); //编译错,不能以这种方式创建内部类对象
Z.B ab1=new Z().new B(); //★★★可用这种方式直接创建内部类对象
Z.B ab2=a.new B(); //★★★可用这种方式直接创建内部类对象
}
}
class Z{
public class B{ //内部类定义
private int y;
private void h1(){x=10;} //内部类的成员方法可直接访问囿类的私有成员
public void h2(){ //用于测试对外部内部类成员的调用
System.out.println("Hi, Executed innerClass Method!");
Z.this.x=5; //★★★此句显示:在内部类中如何引用囿类对象自身
}
}
private int x;
private void f(){
//y=5; //编译错,囿类成员方法不能直接访问内部类成员
B b=new B(); b.y=5; //★★★只能借助内部类对象访问内部类的成员
}
public B g(){ return new B(); }//返回内部类的实例对象
}
1、内部类和囿类可以任意访问,没有权限上的限制。但囿类成员方法访问内部类成员,必须要借助内部类的对象,反之则无此限制。
2、在囿类外部,只要权限允许,也可以通过囿类对象访问内部类的成员,如ab.h2();。但是,ab.y=6则编译错误,因为权限不允许。
3、两种在外部获取内部类对象的方式:
1)通过囿类的public方法返回,如a.g();。
2)直接调用内部类的构造函数,但方式特殊,如new A().new B();或者a.new B();。
静态内部类
Java规定:若内部类中有静态成员,则该内部类必须是静态内部类。
class A{
static class A1{
int x;
static int y;//正确
}
class A2{
int a ;
static int b; //编译错,非静态内部类中不能有静态成员
}
}
A.A1.y = 10;//合法
测试静态类和非静态类
成员内部类(可以使用private、default、 protected、 public任意进行修饰。类文件:外部类$内部类.class)
a)非静态内部类
(外部类里使用非静态内部类和平时使用其他类没什么不同)
i. 非静态内部类必须寄存在一个外部类对象里。因此,如果有一个非静态内部类对象那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对象。
ii. 非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类成员。
iii. 非静态内部类不能有静态方法、静态属性和静态初始化块。
iv. 外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例。
v. 成员变量访问要点:
1.内部类里方法的局部变量:变量名。
2.内部类属性: this.变量名。
3.外部类属性:外部类名.this.变量名。
public class TestInnerClass {
public static void main(String[] args) {
//创建内部类对象
Outer.Inner inner = new Outer().new Inner();
inner.show();
}
}
class Outer{
private int age = 10;
public void testOuter() {
System.out.println("Outer.testOuter()");
}
class Inner{
int age = 20;
public void show() {
int age = 30;
System.out.println("外部类的成员变量age:"+Outer.this.age);
System.out.println("内部类的成员变量age:"+this.age);
System.out.println("局部变量age:"+age);
}
}
}
局部内部类和匿名内部类
成员方法中只有局部变量、常量、内部类相应地称作局部内部类。局部变量不能用权限属性、static、abstract等属性的修饰,局部内部类也是如此。局部内部类的作用范围,仅限于其所在的方法。
class A{
public void f(){
int x;
class B{
int a;
public void g(){
a = x; //编译错误 局部内部类不能引用所在方法中定义的变量。因为局部变量x存于f()的栈空间,f运行结束x将自动销毁。但对象存在于堆空间,若允许存取x,将可能导致错误。
}
}//局部内部类
B b = new B();
}
没有名字的内部类成为匿名类
public class Ch_4_28{
public static void main (String[] args) {
A a=new A() { //定义A的匿名子类(匿名的局部内部类)
public void f(){System.out.println("匿名类.f()");}
public void g(){;} //可通过编译但无法使用
}; //作为语句结束符的分号不能少,匿名类定义结束
a.f();
// a.g(); //编译错,因为a是A类型,A中无g()方法
}
}
class A{
public void f(){ System.out.println("A.f()"); }
}
初始化块
初始化块就是在类中独立于成员方法之外的代码段,它没有名字,不带参数,无返回值。被static修饰就是静态初始化块,否则就是实力初始化块。初始化块的执行遵循以下规则:
1)初始块的执行顺序遵循其出现的次序。
2)实例初始化块先于构造函数。
3)静态初始化块在类中的初次加载时执行,仅执行一次,且先于实例初始化块。
public class Ch_4_29{
public static void main(String[] args) {
System.out.print("ppppp== ");
new TestBlock();
new TestBlock(99);
}
}
class TestBlock {
TestBlock (int x){
System.out.print("1== ");
}
TestBlock (){
System.out.print("2== ");
}
static {
System.out.print("Static 3 == ");
} //静态初始化块
{
System.out.print("4== ");
} //实例初始化块
{
System.out.print("5== ");
} //实例初始化块
static {
System.out.print("Static 6== ");
} //静态初始化块
}