JavaSE(Java标准版)-03

目录

一、多态

1、什么是多态

2、多态的好处

3、多态下的类型转换

二、final,抽象类和接口

1、final关键字

2、常量

3、抽象类

4、接口

三、内部类、枚举和泛型

1、内部类

2、枚举

3、泛型


一、多态

面向对象三大特征:封装、继承、多态

1、什么是多态

多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态。对象多态就好比我们自己,在学校我们是学生,在公司我们是员工,在xxx我们是xxx......,同一个对象表现出不同的角色。行为多态就好比有的人唱歌唱得好,有的唱的不好,同一个唱歌的行为展现出多种状态。

p1和p2都是People类对象,但是它们的角色不同一个学生一个老师,Peopel类的范围更大,因此小范围可以给大范围赋值(自动类型转换),这就是对象多态。People类中只有一个run方法,但是p1,p2调用同一个方法呈现出的是不同的状态。

发生多态现象的前提:有继承/实现关系;存在父类引用子类对象;存在方法重写。注意:多态一般都是对象、行为的多态,java中的成员变量不谈多态。

2、多态的好处

在多态形式下,右边对象是解耦合的(右边对象可以随时切换,随业务变化而变化),更便于扩展和维护。解耦合就是减少模块之间的依赖性,提高程序的独立性,好比汽车和轮胎就是解耦合的,轮胎报废了直接卸下来换一个新的即可。而强耦合就好比汽车和轮胎焊死了,轮胎坏了整个汽车全报废了。

在定义方法时,可以使用父类类型的变量作为形参,可以接收一切子类对象。

多态的弊端:多态下无法使用子类的独有功能

解决方法:多态下的强制类型转换,就是将父类变量p1强制类型转换成子类变量,这样就可以使用子类独有的方法了。

3、多态下的类型转换

自动类型转换:(小范围可以自动转换到大范围)父类 变量名 = new 子类();(有弊端)

强制类型转换:(大范围--->小范围)子类 变量名 = (子类)父类变量

注意事项:只要存在继承/实现关系,在编译阶段就能够进行强制类型转换,不报错(写好代码不标红)。但是在运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常的错误:ClassCastException

显然p1是由Student类转换而来,而强制类型转换却要将其转换为Teacher类,与真实类型不一致。由于在某些特殊的场景下我们并不清楚被强转的变量真实类型是什么,为了避免运行时出现强制类型转换异常,java官方建议使用instanceof关键字,判断当前变量的真实类型,然后在进行强转:

二、final,抽象类和接口

1、final关键字

final关键字是最终的意思,可以修饰类,方法,变量。

修饰类:该类被称为最终类,特点是不能被继承了。一般在工具类上加final,因为工具类没必要继承,它只是提供一些实现了某些功能的静态方法。

修饰方法:该方法被称为最终方法,特点是不能被重写了

修饰变量:该变量有且仅能赋值一次

注意事项:final修饰基本类型的变量,变量存储的数据不能改变。final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的。

2、常量

使用static final修饰的成员变量称之为常量,常用于记录系统的配置信息。常量的命名规范:全部大写字母,多个单词使用_连接起来。

利用常量记录系统的配置信息,可以提示代码的可读性,以及可维护性:

程序编译后,常量会被"宏替换":出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的,不存在通过常量名SCHOOL_NAME寻找值的过程。我们可以打开其class文件,反编译看看是否进行了宏替换。

3、抽象类

(1)什么是抽象类

java中有一个abstract关键字,它就是抽象的意思,可以用来修饰类(抽象类),成员方法(抽象方法)。注意抽象方法只能有方法签名,一定不能有方法体。

抽象类的注意事项,特点:

A:抽象类中不一定有抽象方法,但是有抽象方法的类一定是抽象类(类中有抽象方法那么这个类必须使用abstract修饰)

B:类该有的成员(成员变量,方法,构造器)抽象类也都可以有

C:抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现(抽象抽象因此不能实例化)

D:一个类继承抽象类,必须重写抽象类的全部抽象方法(Alt + 回车),否则这个类也必须定义为抽象类

(2)抽象类的好处

父类知道每个子类都要做某个行为,但每个子类要做的情况不一样,父类就定义成抽象方法,交给子类去重写实现。总之设计抽象类的目的就是为了更好的支持多态,具体来讲是行为多态。

抽象类并不是必须的,只是用了抽象类显得我们更加专业。例如上面的代码可以改为下面的代码,结果也一样。

(3)抽象类的应用场景---模板方法设计模式

问题:模板方法设计模式解决了什么问题?解决方法中存在重复代码的问题

写法:首先定义一个抽象类(父类);

           然后在抽象类里面定义两个方法:一个是模板方法,把相同代码放里面去;一个是抽象方法,具体实现交给子类完成。

假设目前我们有如下场景:学生、老师都要写一篇作文《我的爸爸》,要求第一段一样,正文部分自由发挥,最后一段也一样。目前我们不用抽象类实现:

显然有很多代码重复,使用抽象类实现如下:

试一下:People p1 = new Student()   p1.write();

              People p2 = new Teacher()   p2.write();

建议使用final关键字来修饰模板方法,final关键字修饰的方法称为最终方法,不可以重写。从上面所学我们知道模板方法是相同的代码段(就是供其他类使用的方法),没必要重写。一旦重写了,模板方法就失效了,相当于又回到原来不使用抽象类的实现方式了,因此我们一般使用final关键字禁止模板方法重写。

4、接口

(1)什么是接口

java提供了一个interface关键字,使用这个关键字我们可以定义出一个特殊的结构:接口。

语法:

接口不像前面所学的普通类可以定义成员变量,方法,构造器,代码块,....,接口内部只能定义成员变量和成员方法。且定义的成员变量java默认认为其是常量(必须赋值,前面加不加static final,java都默认会加上),定义的成员方法java默认认为其是抽象方法(加不加abstract,java都默认会加上)。

常量建议使用大写和_命名:

由于接口不能创建构造器,且接口也是抽象的,因此接口同样不可以创建对象。接口是用来被类通过implements关键字实现(可以理解为继承)的,实现接口的类称为实现类(子类)。

一个类可以实现多个接口(接口可以理解成研究生导师,它只提供一个大体的研究方向,具体的研究自己实现),实现类实现多个接口必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。接口也可以理解成干爹,一个类extends的类是亲爹只能有一个,干爹可以有多个。

(2)接口的好处

A:弥补了类单继承的不足(java是单继承的),一个类同时可以实现多个接口。

A类不能在继承别的类了,想让A类扩展其他的特征,需要用接口实现:

思考:实现接口Driver,Singer不就是为类A多添加一些额外的方法吗?我直接在类A中实现不就完了,为什么多此一举先创建接口,再实现接口呢?

对照现实生活来讲,有些同学会开车,但是如果没有驾驶证,别人根本不会知道你会开车,不会把你当成司机。程序也是这样,A implements Driver就好比告诉别人A类是有驾驶证的,Driver会强迫A学会开车(重写Driver的抽象方法)。一个类实现了很多接口,就好比你自己有各种各样的证书帖子你脑袋上,被人看到你后根据你脑袋上的证书就知道你会干什么。

B:让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。

同一个接口可以被不同的类实现比如上面的Driver接口被A类实现,我们现在让B类也实现Driver接口。比如A公司实现了Driver的功能,B公司也实现了Driver的功能,开始我们使用A公司的Driver功能,但是某天A公司的Driver满足不了我们的需求了,我们可以直接切换使用B公司的Driver功能,后面的代码不用修改。

与多态很类似,右边对象是解耦合的:

(3)接口的综合案例---班级学生信息管理模块开发

首先新建一个Student类,用于封装学生的信息

创建一个ClassManager类,用于处理学生信息

创建一个接口,定义上述要实现的两个功能:

有接口就一定有实现类,由于有两套方案,因此定义两个实现类分别实现方案一和方案二:注意实现类的命名一般是:借口名+impl

回到ClassManager类,完成两个功能方法的实现:

上述就是面向接口编程,定义一个大范围的类(父类)= new 小范围的类 (子类)

定义一个测试类Test验证功能是否实现:

切换业务方案:

(4)JDK8开始接口中新增的三种方法

传统的接口只能定义常量和抽象方法,在JDK8开始java支持在接口中定义三种新的方法,分别是:默认方法、私有方法、静态方法(类方法)

可以看出接口中的默认方法与普通类的public方法一样,B实现了A,A中所有默认方法B都继承过来了,通过实例化B对象可以直接使用。

接口中的私有方法与普通类中定义的private方法一样,只能在接口内部访问。

由于接口的设计就是为了让别的类去实现的,去调用的。因此一般接口中定义的变量,方法默认使用public修饰。

思考:java为什么要在接口中新增这三种方法呢?由于原来的接口只能定义常量和抽象方法,新增这三种方法首先可以扩充接口的功能。其次有了这三种方法可以很方便的进行后期维护工作。原来扩展功能必须定义抽象方法,然后在实现类中重写很麻烦(比如想要重写的方法所有的实现类代码都一样)。总之新增这三种方法的目的是使开发工作变得更加灵活。

(5)接口的多继承

接口之间是可以进行继承的,且一个接口可以同时继承多个接口(这与类只能是单继承是不同的):

(6)使用接口的注意事项

A:一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。

B:一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。(与A一个道理)

C:一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优秀用父类的。

D:一个类实现了多个接口,多个接口中存在同名的默认方法,会报错但是我们可以解决这个冲突,只需要让这个类重写该方法即可。

三、内部类、枚举和泛型

1、内部类

(1)什么是内部类

内部类是类中的五大成分之一(成员变量,方法,构造器,代码块,内部类),如果一个类定义在另一个类的内部,那这个类就是内部类。

内部类的使用场景:当一个类的内部,包含了一个完整的事务,且这个事务没有必要单独设计时,就可以把这个事务设计成内部类。

内部类有四种形式分别是:成员内部类,静态内部类,局部内部类和匿名内部类。

(2)成员内部类

就是类中的一个普通成员,类似前面我们学过的普通的成员变量,成员方法。在成员内部类中,类可以定义的东西,成员内部类里都可以定义。

创建成员内部类的对象:需要先创建出外部类的对象,然后在此基础上创建成员内部类的对象。

思考:假设外部类有自己的成员变量(实例变量、类变量),那成员内部类是否可以访问这些变量呢?可以访问,成员内部类也是外部类的成员,就好比类中的成员方法一样,肯定也能直接访问外部类的变量。

(3)静态内部类

有static修饰的内部类就是静态内部类,它属于外部类自己特有。在静态内部类中,类可以定义的东西,静态内部类里都可以定义。

静态内部类创建对象: 

思考:在静态内部类中是否可以访问外部类的实例变量和类变量?可以访问类变量,不能访问实例变量。静态内部类就好比类中定义的静态方法,类变量可以访问因为只有一份,实例变量必须通过外部类的实例对象来访问。

(4)局部内部类

局部内部类是定义在方法中,代码块中,构造器等执行体中的类。没什么应用场景,一般不使用。

(5)匿名内部类

匿名内部类是一种特殊的局部内部类(定义在方法中,代码块中,构造器等执行体中的类)。所谓匿名指的是程序员不需要为这个类声明名字。它是目前在开发中用的做多的一种内部类。

语法:

在开发中,有些时候我们只需要创建一个子类对象来使用一下就行了,并不需要经常的去创建这个子类对象,那此时为了创建一个不经常使用的子类对象而单独创建一个子类Car出来,显得比较麻烦和多余。那有没有更方便的方式可以直接得到一个Anima的子类对象出来呢?匿名内部类就可以实现。

匿名内部类本质就是一个子类。代码执行以后,查看编译后的文件:

匿名内部类的常见使用场景:通常作为一个参数传输给方法。

我们可以直接将匿名内部类作为参数,传给go方法:

扩展:匿名内部类真实使用场景--以GUI编程为例讲解(就是开发桌面程序如下图:)

btn.addActionListener(ActionListerner L):给按钮添加单机事件监听器,需要传入一个监听器对象ActionListerner。当传入监听器对象后,这个监听器对象就可以监听按钮的单机事件。一旦监听到按钮被按了,此时会执行actionPerformed方法。

思考:在真实的开发环境中,什么时候会用匿名内部类呢?java代码想让我们用的时候我们才用,比如我们发现:btn.addActionListener方法需要传入一个监听器对象,我们打开这个监听器对象发现它是个接口,对于接口要么用它的实现类来构建对象,要门使用匿名内部类构建对象,使用实现类构建比较麻烦,而且需要将这个监听器对象作为参数传给方法,因此此时需要使用匿名内部类。

匿名内部类的使用,不是我们主动去用,而是需要我们用我们才用。恰好某个方法需要传入一个对象,而该对象还是个接口,此时需要用匿名内部类。匿名内部类需要被动的去用。

匿名内部类的核心目的还是为了简化代码:(简化代码后续会讲,使用Lambda表达式可以直接将匿名内部类简化为一行代码)

2、枚举

(1)什么是枚举

枚举是一种特殊的类,语法如下:枚举类中的第一行只能写一些合法的标识符(名称),多个名称用逗号隔开。这些名称本质是常量,每个常量都会记住枚举类的一个对象。其他成员可以定义类的五大成分。

执行Test代码进行编译,然后打开A.class文件查看枚举真正的样子(通过命令行方式反编译,在ideal里反编译无法看出枚举的真正样子)。

枚举类不能创建对象,即只有在代码第一行中定义的那几个对象。

抽象枚举:就是在枚举类中定义了抽象方法,既然都变抽象了那就不能创建对象了,因此抽象枚举的第一行就不能像原来那样直接定义对象了。此时需要调用枚举类的构造器,然后在构造器内部重写抽象方法,以这种方式构建枚举类对象。

在Test中测试:

(2)使用枚举类实现单例设计模式

回顾:单例设计模式即一个类只有一个对象。

因此枚举类中第一行只定义一个对象,就是单例设计模式。

(3)枚举的应用场景

枚举可以用来表示一组信息,然后作为参数进行传输,即做信息标志和分类。假设我们有一个需求:一个网页根据用户是男还是女展示不同的页面内容,用户是男展示美女游戏信息,用户是女展示帅哥土豪信息。(简单了解)

3、泛型

(1)什么是泛型

泛型就是在定义类,接口,方法时,同时声明了一个或多个类型变量(如:<E>,类型变量建议使用大写英文字母如E,T,K,V等),称为泛型类,泛型接口,泛型方法,它们统称为泛型。比如我们学过的ArrayList类就是一个泛型类。

没有使用泛型时,接收、获取的类型默认是Object类型。使用泛型可以在编译阶段约束我们能够操作的数据类型,例如ArrayList<String>即在集合中只能存储String类型数据。泛型的本质就是把具体的数据类型String作为参数传给类型变量E。

(2)泛型类的定义

假设当前定义了一个Animal类,他有两个子类分别是Car,和Dog类:

相当于限定传入的E类型变量必须与Animal类有关。

(3)泛型接口的定义

同理接口中的泛型也可以继承某个类,此时实现类中传入的类型变量只能是泛型继承的类或其子类

(4)泛型方法的定义

注意在泛型类中定义的方法不是泛型方法,因为泛型类中的方法只是借用了一下类中定义的<E>,只有类型变量的定义在方法中,该方法才叫泛型方法。<E> :定义类型变量;E:使用类型变量。

假设定义一个Car类(父类),BMW(宝马车子类继承Car),BENZ(奔驰车子类继承Car)

此时有一个问题是假设有一个Dog类,实例化一个Dog类型的ArrayList<Dog> dogs集合,向该集合中添加一些狗对象,调用go(dogs)发现也可以调用,显然是不合理的够又不是汽车,此时可以对T加以限定。

<T extends Car>是我们在方法中定义的类型变量,在ArrayList<T>中的T是在使用前面定义的类型变量,这里可不是定义T。此时可以将代码简化,即使用通配符?:在使用泛型T的时候可以代表一切类型,相当于定义了<T>

注意:? extends Car(上限):表示?必须是Car或Car的子类

           ? super Car (上限)  :表示?必须是Car或Car的父类

(5)泛型的其他注意事项

泛型是工作在编译阶段的,一旦程序javac编译成class文件,class文件中就不存在泛型了,这就是泛型擦除。

补充:通过ideal,以及cmd命令行的反编译(javap命令)结果往往无法获取详细的源码,此时需要借助Xjad工具进行class文件的反编译:

泛型不支持基本数据类型,只能支持对象类型(引用数据类型)

思考:那ArrayList身为集合,我就想只存储int类型数据,无法存储吗?那ArrayList是不是很鸡肋呢?其实ArrayList是可以只存储int类型的:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值