JAVA抽象类,接口,内部类详解

网友投稿 250 2022-12-13

JAVA抽象类,接口,内部类详解

目录一.内容抽象类抽象方法与抽象类抽象类的使用接口接口的概念接口的定义接口中的成员变量接口中的方法接口的继承接口的实现/使用接口与抽象类的差异面向接口编程内部类非静态内部类静态内部类局部内部类匿名内部类总结

一.内容

抽象类

当编写一个类时,常常会为该类定义一些方法,这些方法用于描述这个类的行为。但在某些情况下只需要定义出一些方法,而不需要具体的去实现这些行为。也就是说这些方法没有方法体,只是一些签名而已,这样的方法被称为抽象方法,包含抽象方法的类被称为抽象类。

抽象方法与抽象类

抽象方法与抽象类必须使用abstract关键字进行修饰,有抽象方法的类必须被定义成抽象类,抽象类里面可以没有抽象方法。

抽象类与抽象方法的规则如下:

抽象类与抽象方法必须使用abstract关键字进行修饰,抽象方法不能有方法体。

抽象类不能被实例化。即使抽象类不包含抽象方法,也不能被实例化

抽象类可以包含field、方法、构造器、初始化块、内部类5种成分。

包含抽象方法的类,只能被定义成抽象类。

语法格式:抽象方法

【修饰符】 abstract 返回值类型 methodName(形参列表);

//示例:

public abstract void runWay();//定义一个行进的方法

注意:抽象方法是没有方法体,仅仅是一个方法声明而已。所有java要求其子类必须将父类中定义的抽象方法进行实现。这也就意味着子类需要先将该方法继承过来,所以修饰符也就只能是public或者protected。

示例代码:抽象类定义

public abstract class Piece{}//定义一个棋子类

注意:抽象类不能实例化,只能被继承,抽象类中可以没有抽象方法,即使抽象类中没有抽象方法也不能被实例化。

抽象类的使用

抽象类不能创建实例,只能当成父类来被继承。抽象类可以看成是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出来的一个抽象类,以这个抽象类作为其子类的模板,从而避免子类设计的随意性。

抽象类的体现就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,这就是一种模板模式,模板模式也是十分常见的设计模式。

示例代码:

//定义抽象父类Piece

public abstract class Piece{

/**

*定义棋子的行进方法

*/

public abstract void runWay();

/**

*定义棋子进攻的方法

*/

public void attack() {

System.out.println("吃掉下面的棋子");

}

}

//Piece的子类Horse

public class Horse extends Piece{

/**

*实现父类中定义的抽象方法

*/

public void runWay() {

System.out.println("按照日字格行进!");

}

}

//Piece的子类Cannon

public class Cannon extends Piece{

/**

*实现父类中定义的抽象方法

*/

public void runWay() {

System.out.println("按照直线方式行进!");

}

}

模板模式在面向对象的软件中很常用,其原理简单,实现也很简单。使用模板模式有如下规则:抽象父类可以只定义需要使用的方法,把不能实现的部分抽象成抽象方法留给子类去实现。

接口

抽象类是从多个类中抽象出来的模板,如果将这种抽象进行得更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(interface)。接口里不能包含普通方法,接口里的所有方法都是抽象方法。接口里面可以放:常量,抽象方法,默认方法,静态方法,私有化方法

接口的概念

Java中的接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

接口的定义

接口和类定义不同,定义接口不在使用class关键字,而是使用interface关键字。

语法格式如下:

【public】interface 接口名 extends 父接口1,父接口2{

//零到多个常量定义

//零到多个抽象方法定义

}

语法分析:

修饰符可以是public或者protected,大多数是使用public,protected省略采用默认包权限访问控制符。

接口名应与类名采用相同的命名规则。

一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。

接口中的成员变量

接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义。接口里可以包含field、方法、内部类定义。因为接口没有构造器与初始化块,因此系统不能为field进行默认的初始化操作,只能由程序编写人员为field指定默认的值,所以field只能是常量。又因为field只能是常量,所以系统自动为这些field增加了static和final两个修饰符。也就是说在接口中定义的Field不管是否使用了public static final修饰符,接口里的Field总是默认使用public static final修饰符来进行修饰,不可更改。

示例代码

interface DBobjectType{

//对于在接口中定义的成员变量,用或不用public static final,意义都是相同的

public static final int ROOT=0;

public static final int DATABASE=1;

int TABLE=2;

int COLUMN=3;

int INDEX=4;

}

接口中的方法

接口里定义的方法都是抽象方法,因此系统会自动为方法增加public abstract修饰符。因此不管aZTongHEr定义接口方法时是否使用了public abstract修饰符,系统都会默认方法使用public abstract修饰符来进行修饰。

示例代码:

public interface DataConnection{

/**

*定义获取数据库连接的方法*/

public abstract void getConnection();

/**

*定义关闭数据库连接的方法,在接口中是否使用public abstract意义相同*/

void close();

}

接口的继承

接口的继承与类的继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口。和继承相似,子接口扩展父接口,将会获得父接口里定义的所有抽象方法、field、内部类和枚举定义。

一个接口继承多个父接口时,多个父接口排在extends关键字之后,多个父接口之间使用英文逗号(,)进行分隔。

示例代码:

public interface InterA{//定义接口A

void a();

}

public interface InterB{//定义接口B

void b();

}

public interface Inter extends InterA,InterB{//定义接口Inter继承A、B

voidc();

}

接口的实现/使用

接口不能用于创建实例,但接口可以用于声明引用类型变量。当使用接口来声明引用类型变量时,这个引用类型变量必须引用到其实现类的对象。除此之外,接口的主要用途就是被实现类进行实现。

一个类可以实现多个接口,继承使用extends关键字,而实现则使用implements关键字。

单继承多实现

示例代码:

public interface InterA {

void a();

}

public class InterAImpl implements InterA

{

@Override

public void a(){

System.out.println("将接口InterA中定义的抽象方法进行实现!");

}

}

实现接口与继承类相似,一样可以获得所实现接口里定义的常量field、抽象方法、内部类和枚举类定义。让类实现接口需要在类定义后面增加implements部分,当需要实现多个接口时,多个接口之间以英文逗号(,)隔开。一个类可以继承一个父类并同时实现多个接口,implements部分必须放在extends部分之后。

示例代码:

public interface DBobjectType {

public static final int ROOT =0;

public static final int DATABASE =1;

int TABLE=2;

int COLUMN=3;

int INDEX=4;

}

public interface DataConnection{

/**

*定义获取数据库连接的方法

*/

public abstract void getConnection();

/**

*定义关闭数据库连接的方法,在接口中是否使用publicabstract意义相同

*/

void close();

}

public class ConnectionImpl implements

DBobjectType,DataConnection {

@Override

public void getConnection() {

System.out.println("获取一个连接对象!");

}

@Override

public void close() {

System.out.println("将连接进行关闭!");

}

public static void main(String[]args){

ConnectionImpl impl=new ConnectionImpl();

//直接使用从DBobjectType继承过来的成员变量定义

System.out.println("数据对象类型:"+ConnectionImpl.ROOT);

impl.getConnection();

}

}

一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法,否则该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。

接口与抽象类的差异

1、接口和抽象类都不能进行实例化,它们都位于继承树的顶端,用于被其他类实现和继承。

2、接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务。对于接口的调用者而言,接口规定了调用者可以调用哪些服务。当在一个程序中使用接口时,接口是多个模块间的耦合标准,当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。

接口类似于系统的总纲,一旦接口发生变化,对于整个系统是辐射式的,所有实现这个接口的普通类都要进行改写。

分析:JDBC编程是后面需要给大家讲到的数据库编程,在此示例中不涉及到具体代码,只看结构图。Java程序不可能为行业内使用个各种数据库都提供一套连接方式。它只会提供一套标准的接口,告诉数据库生产厂商应该提供哪些实现。所以不同的数据库生产厂商需要将Java提出的接口进行底层的实现,当我们需要进行JDBC编程时,只需要将不同数据库生产厂商提供的JAR包导入进来,按照接口的方式进行编程即可

抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模版式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品。这个中间产品已经实现了系统的部分功能,但这个类不能称为最终产品,必须有更进一步的完善,这种完善可能有几种不同的方式来实现。

接口与抽象类在用法上也存在如下差异:

接口里只能包含抽象方法,不包含已经提供实现的方法,抽象类则完全可以包含普通方法。

接口和抽象类里都可以定义静态方法。

接口里只能定义静态常量Field,不能定义普通的Field,抽象类里则都可以。

接口里不包含构造器,抽象类里可以包含构造器,抽象类里的构造器并不是用来创建对象,而是让其子类调用这些构造器完成属于抽象类的初始化操作。

接口里不能包含初始化块,但抽象类则完全可以包含初始化块。

一个类最多只有一个父类,包括抽象类,但是一个类可以实现多个接口。

面向接口编程

接口体现的是一种规范和实现分离的设计模式,充分利用接口可以很好降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。

基于这种原则,软件架构设计理论都倡导“面向接口”编程,而不是面向实现类编程,希望通过面向接口编程来降低程序的耦合。下面使用数据库编程这种场景来示范面向接口编程的优势。

//定义接口规范

public interface DataConnection{

/**

*定义获取数据库连接的方法*/

public abstract void getConnection();

/**

*定义关闭数据库连接的方法

*/

public abstract void close();

}

//定义不同数据库的实现类mysql

public class MySqlConnection implements DataConnection {

@Override

public void getConnection() {

System.out.println("获取MySql数据库的连接。。。");

}

@Override

public void close() {

System.out.println("关闭MySql数据库的连接。。。");

}

}

//定义不同数据库的实现类Oracle

public class OracleConnection implements DataConnection {

@Override

public void getConnection(){

System.out.println("获取Oracle数据库的连接。。。");

}

@Override

public void close() {

System.out.println("关闭Oracle数据库的连接。。。");

}

}

//定义不同数据库的实现类SqlServer

public class SqlServerConnection implements DataConnection {

@Override

public void getConnection() {

System.out.println("获取SqlServer数据库的连接。。。");

}

@Override

public void close() {

System.out.println("关闭SqlServer数据库的连接。。。");

}

}

内部类

在定义类的时候,我们一般把类定义成一个独立的程序单元。但是在某些情况下,我们会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,也可以称为嵌套类。包含内部类的类也被称为外部类,也可以称为宿主类。Java从JDK1.1开始引入内部类,内部类的主要作用如下:

内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中其他类访问该类。

内部类成员可以直接访问外部类的私有数据,因为内部类被当成外部类的成员,同一个类成员之间可以相互访问。

匿名内部类适合用于创建那些仅需要一次使用的类。

非静态内部类

定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处的“内部类”包括类中的任何位置,甚至在方法中也可以定义内部类,在方法中定义的内部类叫做局部内部类。

通常情况下,内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与成员field、成员方法、构造方法和初始化块相同级别的类成员。

成员内部类分为:静态内部类和非静态内部类两种,使用static修饰的成员内部类就是静态内部类,没有使用static修饰的成员内部类就是非静态内部类。

因为内部类作为其外部类的成员,所以可以使用任意访问控制符:private、protected、public修饰的Field成员。

示例代码:

public class DiningRoom {

private String egg="鸡蛋";

class Cook {

public void makeFood() {

//使用了外部类DiningRoom中定义的私有成员egg

System.out.println("厨师使用"+egg+",做了一份炒鸡蛋!");

}

}

/**

*DiningRoom类对外提供的做炒鸡蛋的功能*实际该功能是由内部类Cook来执行的

*/

public void fireEgg(){

new Cook().makeFood();

}

}

代码优化

假设内部类Cook提供了多个方法,而外部类中定义的多个方法又多次用到了Cook中提供的方法,那么类似上述示例中的调用方式,就会每次调用都会创建一个Cook的对象,用完即丢弃了,造成了程序上性能的降低。像这种情况我们就可以在父类DiningRoom中定义Cook的成员变量即可。

public class DiningRoom {

private String egg="鸡蛋";

private Cook cook=new Cook();

class Cook {

public void makeFood() {

//使用了外部类DiningRoom中定义的私有成员egg

System.out.println("厨师使用"+egg+",做了一份炒鸡蛋!");

}

}

/**

*DiningRoom类对外提供的做炒鸡蛋的功能*实际该功能是由内部类Cook来执行的

*/

public void fireEgg(){

this.cook.makeFood();

}

}

分析:通过代码的改写,那么即使父类DiningRoom中的多个方法多次调用Cook类中定义的方法时,也只会创建Cook的一个对象,而不是多个对象。

静态内部类

使用static修饰符来修饰内部类就称为静态内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为静态内部类

静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。

示例代码:

public class Factory {

public static String noodle="面条";

public static String dumplings="水饺";

private String chicken="鸡肉";

public void madamFood(String food){

System.out.println("厂长吃的是自己夫人做的饭!"+food);

}

public static void diningFood(String food){

System.out.println("工人吃的是食堂做的饭!"+food);

}

static class Dinhttp://ingRoom {

public static void eat(){

diningFood(Factory.noodle);

}

public void managerEat() {

//编译报错,在静态内部类中,即使是非静态的成员

//也不能访问外部类的非静态的成员

madamFood("abc");

}

}

}

分析:在上述示例中,静态内部类DiningRoom在进行编译时报错,提示对象的实例上调用了不在范围内的数据信息。因为静态内部类会跟随外部类的静态的信息同时存在,此时可以创建静态内部类的实例对象,但是并不一定会创建外部类的实例对象,那么去访问外部类实例对象的成员就会出问题,因为对象都没有,怎么访问对象上的成员呢?

局部内部类

如果把一个内部类定义在方法里面定义,则这个内部类就是一个局部内部类。

示例代码:

public void run() {

//定义一个局部内部类,作用范围更小,在方法外根本无法访问

class InnerTest {

public int num2=5;

public void run(){

System.out.println(num2);

}

}

InnerTest it=new InnerTest();

it.run();

}

匿名内部类

匿名内部类的语法有些特别,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。因此匿名内部类适合创建那种只需要一次使用的类。

语法格式:

new 父类构造器|实现接口 (){

//匿名内部类的类体部分

}

匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,实现一个接口。

匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。

匿名内部类不能定义构造器,因为匿名内部类没有类名,也就无法定义构造器,但是匿名内部类可以定义实例初始化块,通过初始化块来完成初始化操作。

在Java的类库中,有很多非常有用的工具类提供了大量的底层操作,可以让程序开发人员能够快速的进行软件的业务逻辑开发,而不用去关注底层的实现。但是有些方法确实需要接收一些参数的,而这些参数都是接口类型,当程序开发人员调用此方法时,就必须要提供该接口的一个实现类,再去创建该实现类的对象才能去调用那些方法。

如果仅仅是为了调用某个方法,就为此去创建一个新的类就有点得不偿失了。因此Java提供了匿名内部类的方式可以非常有效的解决此类问题。

示例代码:

public class Test {

public static void main(String[]args) {

Integer [] nums={1,3,10,21,14,5,27};

//sort方法需要接收一个Comparator排序器对象,Comparator是接口类型

Arrays.sort (nums,new Comparator (){

@Override

public int compare (Integer int1,Integer int2){

if(int1>int2){

return-1;

}else {

return1;

}

}

});

System.out.println(Arrays.toString(nums));

}

}

代码分析:Comparator是一个排序器接口,用于进行两个数据之间的比较,数据类型需要通过<>这种方式在其中定义出来,compare就是接口中定义的方法,如果大于返回1,小于返回1,等于返回0。就是普通的升序排序,而如果反过来就是降序排序。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:详解Zookeeper基础知识
下一篇:BeanUtils.copyProperties复制不生效的解决
相关文章

 发表评论

暂时没有评论,来抢沙发吧~