好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

一.内容

抽象类

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

抽象方法与抽象类

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

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

抽象类与抽象方法必须使用abstract关键字进行修饰,抽象方法不能有方法体。 抽象类不能被实例化。即使抽象类不包含抽象方法,也不能被实例化 抽象类可以包含field、方法、构造器、初始化块、内部类5种成分。 包含抽象方法的类,只能被定义成抽象类。

语法格式:抽象方法

?

1

2

3

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

//示例:

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

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

示例代码:抽象类定义

?

1

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

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

抽象类的使用

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

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

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

示例代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

//定义抽象父类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关键字。

语法格式如下 :

?

1

2

3

4

【 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修饰符来进行修饰,不可更改。

示例代码

?

1

2

3

4

5

6

7

8

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修饰符。因此不管定义接口方法时是否使用了public abstract修饰符,系统都会默认方法使用public abstract修饰符来进行修饰。

示例代码:

?

1

2

3

4

5

6

7

8

public interface DataConnection{

     /**

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

     public abstract void getConnection();

     /**

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

     void close();

}

接口的继承

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

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

示例代码:

?

1

2

3

4

5

6

7

8

9

public interface InterA{ //定义接口A

     void a();

}

public interface InterB{ //定义接口B

     void b();

}

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

     voidc();

}

接口的实现/使用

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

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

单继承多实现

示例代码:

?

1

2

3

4

5

6

7

8

9

10

public interface InterA {

   void a();

}

public class InterAImpl implements InterA

{

     @Override

     public void a(){

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

     }

}

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

示例代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

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,抽象类里则都可以。 接口里不包含构造器,抽象类里可以包含构造器,抽象类里的构造器并不是用来创建对象,而是让其子类调用这些构造器完成属于抽象类的初始化操作。 接口里不能包含初始化块,但抽象类则完全可以包含初始化块。 一个类最多只有一个父类,包括抽象类,但是一个类可以实现多个接口。

面向接口编程

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

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

//定义接口规范

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成员。

示例代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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的成员变量即可。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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修饰的内部类被称为静态内部类

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

示例代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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 DiningRoom {

         public static void eat(){

             diningFood(Factory.noodle);

         }

         public void managerEat() {

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

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

             madamFood( "abc" );

         }

     }

}

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

局部内部类

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

示例代码:

?

1

2

3

4

5

6

7

8

9

10

11

public void run() {

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

     class InnerTest {

         public int num2= 5 ;

         public void run(){

             System.out.println(num2);

         }

     }

     InnerTest it= new InnerTest();

     it.run();

}

匿名内部类

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

语法格式:

?

1

2

3

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

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

}

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

匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。 匿名内部类不能定义构造器,因为匿名内部类没有类名,也就无法定义构造器,但是匿名内部类可以定义实例初始化块,通过初始化块来完成初始化操作。

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

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

示例代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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 <Integer>(){

             @Override

             public int compare (Integer int1,Integer int2){

                 if (int1>int2){

                     return - 1 ;              

                 } else {

                     return1;

                 }

             }

         });

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

     }

}

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

总结

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

原文链接:https://blog.csdn.net/weixin_51975776/article/details/120015417

查看更多关于JAVA抽象类,接口,内部类详解的详细内容...

  阅读:12次