Loading

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== ");
    	} //静态初始化块
}
posted @ 2020-03-05 13:47  XiaoJ_c  阅读(33)  评论(0编辑  收藏  举报