面向对象进阶
第三课:JAVA面向对象进阶
在上周我们已经了解了java面向对象的基础部分,面向对象的概念还有面向对象的三大特性(封装,继承,多态),相信大家应该已经比较熟悉了吧。Java的面向对象是很重要的一部分,这节课我们会在上次课的基础之上继续讲解Java面向对象的进阶部分。
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是__抽象类__。
简而言之,就是抽象类不能__实例化__为一个对象。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在 Java 语言中使用 abstract 关键字来定义抽象类。比如我们把刚刚的Dog类改为抽象类:
| |
这个时候我们去创建一个Dog类的对象,就会报错。但是Dog的子类Husky和Corgi还是可以实例化。
抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
先来看看两者的区别:
抽象类特点
继承了抽象类的子类,要么对父类的抽象方法进行重写,要么自己也是抽象类
抽象类也可以拥有普通方法
抽象类不能创建对象
抽象类也有构造方法,但是是为了子类创建对象使用
接口的特点
接口是行为的抽象,是一种行为的规范,接口是like a 的关系;抽象是对类的抽象,是一种模板设计,抽象类是is a 的关系。
接口没有构造方法,而抽象类有构造方法,其方法一般给子类使用
接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
抽象体现出了继承关系,继承只能单继承。接口体现出来了实现的关系,实现可以多实现。接口强调特定功能的实现,而抽象类强调所属关系。
接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
简单来说,抽象类定义了他的子类的具体类别,而接口定义了他的实现类的行为。
如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为__抽象方法__。
abstract 关键字同样可以用来声明抽象方法,抽象方法只有声明,而没有具体的实现,如果父类中的方法还不能确定如何具体实现,那么这就应该写成一个抽象方法。(抽象方法在抽象类中只需要声明而不需要实现,即表示在这个类中有这么一个方法,但是他的具体实现由子类定义);
| |
TIPS:抽象方法的访问权限不能为private
因为抽象方法一定要由子类实现,如果子类都访问不了,那么还有什么意义呢?所以说不能为私有。
final关键字我们在上节课提到过,这节课我们详细讲讲。
final的意思是最终,不可改变。从字面上大概也能猜出他的使用方法和效果。
1.当final修饰一个类时,表示这个类不能被继承,格式为:
| |
2.当final修饰一个方法时,表示这个方法不能被覆盖重写,格式为:
| |
注意:对于类和方法final和abstract关键字不能同时使用,因为矛盾。
3.当final用来修饰一个局部变量时,这个变量就不能进行更改。
对于基本类型来说,不可变说的是变量中的数据不可改变;
对于引用类型来说,不可变说的是变量中的地址值不可改变。
4.当final用来修饰一个成员变量时,这个变量同样也是不能进行更改。
由于成员变量有默认值,所以使用final之后就必须手动赋值,不会再给默认值了。
对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值,二选其一。
必须保障类当中的所有重载的构造方法,都最终会对final的成员变量赋值。
static就是静态的意思,他主要有以下一些用法:
1.当static用来修饰一个成员变量,那么这个变量就不再属于某一个对象,而是属于这个类,多个对象共享一份数据。
经典案例:生成对象个数计数器:
| |
2.当static用来修饰成员方法,那么这个方法也同样不属于某一个对象,而是属于这个类。
对于静态方法来说,可以通过对象名来调用,也可以通过类名调用,更推荐用类名调用。
为什么 main 方法是静态的?
static是静态修饰符,被他修饰的方法我们称之为静态方法,静态方法有一个特点,那就是静态方法独立于该类的任何对象,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。而对于main方法来说,他的调用过程是经历了类加载、链接和初始化的。但是并没有被实例化过,这时候如果想要调用一个类中的方法。那么这个方法必须是静态方法,否则是无法调用的。(简言之,在程序刚刚启动时还不存在任何的对象,此时静态的main方法将执行并构造程序所需要的对象)
注意事项:
(1)静态方法不能直接访问非静态属性和方法。
在程序运行的过程中,static修饰的静态变量和全局变量都是储存在一个叫全局数据区的地方。也并不是说全局变量在定义时加了static关键字才是静态存储,不加static就是动态存储,不是的。不管加不加static,全局变量都是存储在静态存储区的,都是在编译时分配存储空间的,两者只是作用域不同,全局变量默认具有外部链接性,作用域是整个工程,全局静态变量的作用域仅限本文件,不能在其他文件中引用
(2)在静态方法中不能使用this关键字。
this代表当前对象,通过谁调用的方法,谁即为当前对象,但是静态方法并不会属于某个对象,而是属于整个类。
3.静态代码块
静态代码块直接写在类当中:
| |
当第一次执行本类时,静态代码块唯一执行一次。
4.使用static静态关键字的好处
不需要创建具体的对象,便可以直接调用类当中的静态方法。
在类中修饰变量时,可以实现同一个类当中的所有对象共享一份数据。
被static修饰的部分只在类第一次加载时被初始化一次,可以提高程序性能。
Lambda表达式是在java8所新增的新特性。
lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。 Lambda 表达式可以看作是一个匿名函数。(这个其实用的也不多,在以后学习Stream流的时候用的多一点,现在稍作了解即可)
基本语法:
| |
可以将上述表达式看成一个完整的函数,只是隐藏了修饰符,返回值类型和方法名称:
| |
一部分函数在调用时要传入的参数是一个函数式接口(一种特殊的接口,有且只有一个抽象方法),这种时候就可以用Lmabda表达式进行简写。
如果一个事物内部含有另一个事物,那么这就是一个类内部包含另一个类。
如同身体和心脏的关系。
| |
注意:内用外,随意访问;外用内,一定要有内部类对象
成员内部类的使用:
1.间接方式:在外部类的方法中,使用内部类,然后main只是调用外部类方法。
2.直接方式:
| |
如果出现重名现象,那么格式是:外部类名称.this.外部类成员变量
| |
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”,只有当前所属的方法才能使用它,出了这个方法外面就不能用了
定义格式:
| |
注意事项:
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是有效final的。
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略。
原因:
new 出来的对象在堆内存中。
局部变量是跟着方法走,在栈内存中。
方法运行结束后,立即出栈,局部变量就会消失。
但是new出来的对象会在堆内存中持续存在,直到垃圾回收消失。
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略该类的定义,而改为使用匿名内部类,直接new接口或父类
匿名内部类的定义格式:
| |
new代表创建对象的动作
接口名称就是匿名内部类需要实现哪个接口,也可以是抽象父类
{……}这才是匿名内部类的内容。
匿名内部类所需要注意的事项
匿名内部类中不能存在任何静态成员或方法
匿名内部类是没有构造方法的,因为它没有类名
与局部内部相同匿名内部类也可以引用局部变量。此变量也必须声明为 final(局部变量与匿名内部类的生命周期不同)(从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略)
复习前两节课的内容,写一下课件中提到的代码(无需提交)