一.内容
抽象类
当编写一个类时,常常会为该类定义一些方法,这些方法用于描述这个类的行为。但在某些情况下只需要定义出一些方法,而不需要具体的去实现这些行为。也就是说这些方法没有方法体,只是一些签名而已,这样的方法被称为抽象方法,包含抽象方法的类被称为抽象类。
抽象方法与抽象类
抽象方法与抽象类必须使用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