- 3 面向对象编程
- 3.1 编程语言的发展
- 3.2 面向过程和面向对象的区别
- 3.3 类(Class)和对象(Object)的概念
- 3.4 类(对象)之间的关系
- 3.5 类的定义
- 3.6 对象和引用
- 3.7 构造方法(构造函数)
- 3.8 对象的创建和使用内存分析
- 3.9 方法的重载(Overload)
- 3.10 关键字-this
- 3.11 关键字-static
- 3.12 关键字-package、import
- 3.13 类的继承(extends)
- 3.14 关键字-super
- 3.15 访问控制/权限控制符
- 3.16 类的封装
- 3.17 方法的重写(Override)
- 3.18 关键字-final
- 3.19 对象转型
- 3.20 动态绑定/多态
- 3.21 抽象类(abstract)
- 3.22 接口(interface)
- 3.23 匿名对象
- 3.24 内部类
- 3.25 Object类
3 面向对象编程
3.1 编程语言的发展
- 第一代语言:机器语言
第一代计算机语言称为机器语言。机器语言就是 0/1 代码。计算机只能识别 0 和 1。在计算机内部,无论是一部电影还是一首歌曲或是一张图片,最终保存的都是 0/1 代码,因为 CPU 只能执行 0/1 代码。那么这是不是就意味着我们编程一定要用 0/1 代码
- 第二代语言:汇编语言
如果直接用机器语言编写的话,这几乎是无法实现的。因为用机器语言太难记忆了,也没人能看得懂。所以后来就设计出了第二种语言,即将 0/1 代码翻译为英文单词,这些英文单词直接对应着一串 0/1 指令。这个就是汇编语言。
通过专门的软件就可以将这些英文单词转化成 0/1 代码并由计算机执行,这种专门起翻译的作用的软件叫作编译器。这些英文单词和与它们对应的 0/1 代码之间的对应关系,以及语言的语法,在编写这个软件的时候就已经写在里面了。我们只要通过编译器就可以将这些都转化成 0/1 代码。这样大大方便了我们对程序的编写。
- 第三代语言:高级语言
汇编语言之后又出现了第三代语言。第三代语言又叫“高级语言”。高级语言的发展分为两个阶段,以 1980 年为分界线,前一阶段属于结构化语言或者称为面向过程的语言,后一阶段属于面向对象的语言。
- 面向过程的语言
面向过程的语言也称为结构化程序设计语言,是高级语言的一种。在面向过程程序设计中,问题被看作一系列需要完成的任务,函数则用于完成这些任务,解决问题的焦点集中于函数。
- 面向对象的语言
面向对象语言(Object-Oriented Language)是一类以对象作为基本程序结构单位的程序设计语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。语言中提供了类、继承等成分,有识认性、多态性、类别性和继承性四个主要特点。
20 世纪 80 年代以后,有了面向对象分析(OOA)、 面向对象设计(OOD)、面向对象程序设计(OOP)等新的系统开发方式模型的研究。对 Java 语言来说,一切皆是对象。把现实世界中的对象抽象地体现在编程世界中,一个对象代表了某个具体的操作。一个个对象最终组成了完整的程序设计,这些对象可以是独立存在的,也可以是从别的对象继承过来的。对象之间通过相互作用传递信息,实现程序开发。
3.2 面向过程和面向对象的区别
- 面向过程
是一种以过程为中心的编程思想。就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用。
- 面向对象
是一类以对象作为基本程序结构单位的程序设计语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。系统中的基本构件可识认为一组可识别的离散对象,对象具有唯一的静态类型和多个可能的动态类型,在基本层次关系的不同类中共享数据和操作。
面向对象的特点是:**封装**,**多态**,**继承**。其中多态有分为重载和重写。面向对象的编程思想更加接近现实的事物。因为面向对象更接近于现实,所以你可以从现实的东西出发,进行适当的抽象,使编程更加容易。在软件工程上,面向对象可以使工程更加模块化,实现更低的耦合和更高的内聚。在设计模式上,面向对象可以更好的实现开-闭原则,使代码更易阅读和维护。面向过程的程序设计是面向对象程序设计的基础。面向对象的设计出发点是为了更直接的描述客观存在的事务。
3.3 类(Class)和对象(Object)的概念
在面向对象中,类和对象是最基本、最重要的组成单元。类实际上是表示一个客观世界某类群体的一些基本特征抽象。对象就是表示一个个具体的东西。
类就是对象的模板,对象就是类的一个个具体实例。
类中定义了这一类对象所具有的静态属性和动态属性。
对象是计算机语言对某种事物的描述,通过属性(attribute)和方法(method)来区别事物中对应的静态和动态属性。
3.4 类(对象)之间的关系
- 关联关系
通常是一个类中的某些方法或参数中包含对另一个类的引用
- 继承关系(一般和特殊)
一个类继承另一个类后,这个类会拥有被继承类的一些属性和方法。
- 聚合关系(整体和部分)
两个或几个类共同构成了某个类(聚集和组合,组合的关系更加紧密)
- 实现关系
一个类实现接口(interface)类后,重写接口中的所有方法
面向对象程序开发思维:
- 写程序时先考虑需要有那些类和对象
- 这些类和对象中应该具有什么样的属性和方法
- 类和类之间应该具备什么关系
3.5 类的定义
3.5.1 概念
定义类其实在定义类中的成员(成员变量和成员函数)
3.5.2 类名命名规则
- 类名应该以下划线(
_)或字母开头,最好以字母开头 - 第一个字母最好大写,如果类名由多个单词组成,则每个单词的首字母最好都大写
- 类名不能为 Java 中的关键字,例如
boolean、this、int等 - 类名不能包含任何嵌入的空格或点号以及除了下划线(
_)和美元符号($)字符之外的特殊字符
// 定义类的格式public class 类名 {[访问修饰符] 数据类型 属性名; //定义属性部分[访问修饰符] 返回值类型 方法名(参数){ } //定义方法部分}
3.5.3 代码示例
/*** 类的定义*/public class ClassDefinition {// idea查看类中的属性和方法快捷键: alt + 7static int a; // 成员变量 有默认值public static void main(String[] args) {test();}public static void test(){ // 成员函数、方法int a = 1; // 局部变量a 没有默认值,使用前需要初始化,可以和成员变量同名,优先级更高int b = 0; // 局部变量b// int a = 2; // 不可以重名a = 2;System.out.println("a= " + a);System.out.println("b= " + b);}public static void test2(){System.out.println(a);// System.out.println(b); // 不能访问其他方法中的局部变量}public static void test3(int a){ // 形参属于局部变量System.out.println(a);}}
| 区别 | 成员变量 | 局部变量 |
|---|---|---|
| 定义的位置 | 直接在类中,方法的外部定义 | 定义在方法内,代码块内,方法的形参部分 |
| 声明赋值 | 可以在声明时赋初始值,也可以不用赋值,系统会自动根据不同数据类型赋默认值(默认值参考数据类型章节) | 使用前一定要有显式的赋值,没有默认初始化值 |
| 作用域 | 整个类内部都可以访问,所哟成员方法也可以访问,如果访问权限允许,还可以在类外部被访问 | 在变量所在方法内部可以访问,方法外部不可以 |
| 访问修饰符 | public private protected default |
没有修饰符,与它所在的方法修饰符一样 |
| 在内存中的位置 | 堆空间中 | 栈空间中 |
注意:
在同一作用域中,变量名不可以重复定义,不可以重名
作用域不同,变量可以重名,如果重名,在方法中的变量比外部成员变量有更高优先级
方法中的参数如果是基本数据类型,那么传递的是值本身,如果是引用数据类型,则传递的是对象的引用
一般约定的命名规则
| 分类 | 命名规则 | 案例 |
|---|---|---|
| 包名 | 所有字母小写 | helloworld |
| 类名 | 每个单词首字母大写 | HelloWorld{} |
| 变量名 | 驼峰(第一个单词首字母小写,后面每个单词首字母大写) | helloWorld |
| 方法名 | 驼峰 | helloWorld() |
3.6 对象和引用
3.6.1 格式
// 创建并引用对象的格式类名 对象名 = new 类名(); // 这里其实是new的构造方法对象名.属性名; // 引用某个对象的属性(成员变量)对象名.方法名(...); // 引用某个对象的方法
3.6.2 代码示例
/*** 对象和引用*/public class ObjectReferences {public static void main(String[] args) {Car myCar = new Car(); // 创建Car的对象,对象名为myCarSystem.out.println(myCar); // myCar -> @1b6d3586 对象的引用,而不是对象本身String carName = myCar.name;int carSeat = myCar.seat;System.out.println(carName);System.out.println(carSeat);myCar.run();myCar.stop();}}class Car{String name;int seat; // 座位public void run(){System.out.println("开车...");}public void stop(){System.out.println("刹车...");}}
3.6.3 内存结构图

3.7 构造方法(构造函数)
3.7.1 构造函数作用
用来给给对象进行初始化,例如:new 构造方法(...);
3.7.2 构造函数特点
- 构造方法名与类名相同
- 构造方法没有返回值,不需要定义返回值类型,更不能写
void,只要声明了返回值类型,就是普通方法 - 如果类中构造方法一个都没写时,
JVM会自动添加一个无参的构造方法,如果我们写了了构造方法,JVM不会再次添加无参构造方法,我们创建对象只能通过new我们自己写的构造方法 - 构造方法可以写多个,构成方法重载
3.7.3 代码示例
/*** 构造函数*/public class Constructor {public static void main(String[] args) {Person person = new Person();System.out.println(person.name + ": " + person.age);Person person1 = new Person("tom", 20);System.out.println(person1.name + ": " + person1.age);}}class Person{String name;int age;Person(){ // 无参构造函数name = "jason";age = 18;}Person(String _name, int _age){ // 有参构造函数,多个同名方法构成方法重载name = _name;age = _age;}}
3.7.4 内存结构图

3.8 对象的创建和使用内存分析
3.8.1 注意
- 同一个类的每个对象,在内存中有不同的存储空间
同一个类的每个对象,共享该类的方法
- 非静态方法是针对每个对象进行调用
- 静态(
static)方法是针对该类进行调用
3.8.2 代码示例
/*** 对象的创建和引用,及内存分析案例*/public class ObjectCreation {public static void main(String[] args) {Circle c = new Circle(3.0);double area = c.getAreaOfCricle();System.out.println(area);c.setR(2.0);System.out.println(c.getAreaOfCricle());System.out.println(c.getAreaOfCricle(new Circle(2.0))); // 匿名对象}}class Circle{double r; // 半径Circle(double _r){r = _r;}// 求圆的面积public double getAreaOfCricle(){return 3.14*r*r;}public double getAreaOfCricle(Circle c){ // 方法重载return 3.14*c.r*c.r;}public void setR(double _x){r = _x;}}
内存结构图:
略
3.9 方法的重载(Overload)
3.9.1 概念
方法重载是指在类中可以定义有相同名字但参数类型或数目不同的多个方法,调用时会根据不同参数信息选择调用对应的方法
3.9.2 特点
- 参数类型或数量不能相同
- 与返回值无关
- 构造方法也可以重载,参考上面的案例
3.9.3 代码示例
/*** 方法的重载*/public class MethodOverload {public static void main(String[] args) {getMax(3, 2);short s1 = 1;short s2 = 2;getMax(s1, s2);getMax(2, 3, 4);}public static void getMax(int a, int b){int max = a > b ? a : b;System.out.println("int最大值是:" + max);}// public static int getMax(int a, int b){ // 方法重名,不行// int max = a > b ? a : b;// return max;// }public static void getMax(short a, short b){int max = a > b ? a : b;System.out.println("short最大值是:" + max);}public static void getMax(int a, int b, int c){int max = a > b ? a : b;max = max > c ? max : c;System.out.println("int最大值是:" + max);}}
3.10 关键字-this
3.10.1 概念
this:是本类对象的…的意思,代表所在函数所属对象的引用,就是对本类对象的引用
3.10.2 特点
- 当函数内有局部变量和成员变量同名的时候,用
this区分 - 当函数内需要用到调用该函数的对象,用
this - 可以在构造器中通过“
this([形参...])”,方式来显式的调用本类中其他重载的制定的构造器
3.10.3 代码示例
/*** this关键字*/public class Keyword_this {public static void main(String[] args) {Student s = new Student();s.show();s.test();}}class Student {String name;int age;Student(){this("jason", 18);};Student(String name, int age){this.name = name;this.age = age;}public void show(){String name = "lily";int age = 20;System.out.println("name : " + name + " age : " + age);// 通过this调用本类对象的成员变量System.out.println("this.name : " + this.name + " this.age : " + this.age);}public void test(){this.show(); // 通过this调用本类成员方法}}
3.11 关键字-static
3.11.1 概念
static:静态的意思,用来修饰成员(成员变量,成员函数),static修饰的成员变量叫类变量,static修饰的成员函数叫类函数
3.11.2 特点
- 可以直接被类名(不需要创建对象实例化)调用
- 随着类的加载而加载,优先于对象存在
- 静态函数只能调用静态成员变量和静态成员函数
- 静态方法中不可以写
this、super关键字 - 被所有该类的对象所共享,内存中只存在一份,而普通成员变量在内存中每个本类的对象都有单独一份
- 静态方法不能被重写,参考方法的重写 代码示例
3.11.3 代码示例
/*** static关键字*/public class Keyword_static {public static void main(String[] args) {// TestStatic.getName(); // 不能通过类名调用非静态方法System.out.println(TestStatic.name); // 直接调用静态属性TestStatic.test(); // 直接调用静态方法TestStatic ts = new TestStatic();String name = ts.getName(); // 非静态方法只能通过对象引用调用System.out.println(name);TestStatic ts1 = new TestStatic();ts1.setName("tom");System.out.println(ts.getName()); // 静态成员被所有对象共享System.out.println(ts1.getName());}}class TestStatic {int age;static String name = "jason";public static void test(){// age = 18; // 静态方法中不能调用非静态成员变量name = "lily";// System.out.println(this.age); // 报错,不能写this关键字}public String getName(){// test(); // 可以直接调用本类静态方法return name;}public void setName(String name){this.name = name;}}
3.12 关键字-package、import
3.12.1 package包
目的:解决类名命名冲突问题,引入包(package)机制,提供类的多重类命名空间,控制访问范围
用法:放在类的代码第一行,使用“.”来指明包(目录)层次
命名规则:一般是利用公司或组织域名的反写,加上项目模块名等,例如:package cn.com.liyanlong;
3.12.2 import 引入
目的:如果一个类中用到了别的包下的某个类中的引用,则使用哦个import关键字引入相应的包下的类
用法:在package关键字下面,类名上面,例如:improt cn.com.liyanlong.Person,表示引入,如果访问的是同一个包下的类,不需要引入
注意:
java.lang包下的类在使用的时候,不需要引入,直接使用就可以,其他包下的的需要引入(idea可以设置自动导入)了解
rt.jarjava.lang包:包含一些Java语言的核心类,如:String,Math,Integer,System,Thread等,提供常用的功能java.net包:提供与网络相关的操作的类java.io包:提供多种输入输出的类java.util包:提供一些常用工具类,如日期日历相关操作类java.awt包:构成抽象窗口工具集,用来构建和管理应用程序图形用户界面(GUI)(了解)java.applet包:包含applet(Java小程序)运行的一些类(了解)
- 学会打包,打包命令:
jar -cvf xxx.jar *.*
cmd代码示例:
C:\Users\LYL>jar用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...选项:-c 创建新档案-t 列出档案目录-x 从档案中提取指定的 (或所有) 文件-u 更新现有档案-v 在标准输出中生成详细输出-f 指定档案文件名-m 包含指定清单文件中的清单信息-n 创建新档案后执行 Pack200 规范化-e 为捆绑到可执行 jar 文件的独立应用程序指定应用程序入口点-0 仅存储; 不使用任何 ZIP 压缩-P 保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件-M 不创建条目的清单文件-i 为指定的 jar 文件生成索引信息-C 更改为指定的目录并包含以下文件如果任何文件为目录, 则对其进行递归处理。清单文件名, 档案文件名和入口点名称的指定顺序与 'm', 'f' 和 'e' 标记的指定顺序相同。示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:jar cvf classes.jar Foo.class Bar.class示例 2: 使用现有的清单文件 'mymanifest' 并将 foo/ 目录中的所有文件归档到 'classes.jar' 中:jar cvfm classes.jar mymanifest -C foo/ .
3.12.3 代码示例
package cn.com.liyanlong;//import java.util.Date; // 单独引入Date类import java.util.*; // * 表示当前包下的所有类public class Package_and_Import {public static void main(String[] args) {Date date = new Date();System.out.println(date.toString());}}
3.13 类的继承(extends)
3.13.1 概念
继承:如果多个类中存在相同的属性和行为,可以将这些内容单独抽取到一个类中,其他类就无须再重复定义,直接继承单独的那个类即可。通过继承,子类可以拥有父类的所有成员(成员变量和成员方法),解决代码复用问题
备注:
- “子类”也可称为:派生类,孩子类
- “父类”也可称为:基类,超类
- Java只支持单继承,不支持多继承,一个类只可以有一个基类,一个基类可以派生出多个子类
- 子类不能继承父类构造方法
3.13.2 特点
通过extends关键字实现
3.13.3 代码示例
/*** extends关键字 /继承*/public class Keyword_extends {public static void main(String[] args) {Sun sun = new Sun();sun.height = 180; // 父类属性sun.setWeight(80); // 父类方法sun.getHeight();sun.getWeight();sun.school = "北大"; // 子类属性sun.playGames(); // 子类方法}}class Father {// 快捷键: alt + Insert 快速生成set、get方法public int height;public int weight;public int getHeight() {return height;}public void setHeight(int height) {this.height = height;}public int getWeight() {return weight;}public void setWeight(int weight) {this.weight = weight;}}class Sun extends Father {public String school;public void playGames() {System.out.println("玩游戏...");}}
3.13.4 内存结构图

3.13.5 继承中的构造方法
参考3.14.4和3.14.5
3.14 关键字-super
3.14.1 概念
super:代表对父类的引用,指代父类对象的…的意思
3.14.2 特点
- 子类和父类出现同名成员,可以用
super区分 - 子类调用父类构造函数时,可以使用“
super([形参...])”语句表示。 - 子类不能通过
super调用父类private(私有的)修饰的成员
3.14.3 代码示例
/*** super关键字*/public class Keyword_super extends SuperClass {Keyword_super(){// 子类通过super()调用父类构造器super();// super("lily");}public static void main(String[] args) {Keyword_super ks = new Keyword_super();// ks.show(); // 子类调用父类方法ks.showSuperInfo();}public void showSuperInfo(){System.out.println("showSuperInfo...");System.out.println(super.name); // 调用父类属性// System.out.println(super.age); // 子类不能调用父类private修饰的成员super.show(); // 调用父类方法}}class SuperClass {public String name;private int age;SuperClass(){name = "jason";age = 18;}SuperClass(String _name){name = _name;age = 20;}public void show(){System.out.println("I am SuperClass...");System.out.println("name: " + name + "\nage : " + age);}}
3.14.4 继承中的构造方法
- 子类在构造时一定会调用父类构造方法来实例化父类
子类可以在自己的构造方法中使用
super(...)调用基类的构造方法- 使用
this(...)可以调用本类其他构造方法
- 使用
- 如果子类构造方法中没有显示的调用父类构造方法,则系统默认首先调用父类无参构造方法
- 如果子类构造方法中没有显示的调用父类构造方法,且父类没有无参构造方法,则会编译出错
- 如果显式的调用了
super(),则必须写在子类构造方法的第一行(先有父类,才可以有子类)
3.14.5 代码示例
/*** 关于继承中的构造方法问题*/public class InheritConstructor {public static void main(String[] args) {new ChildClass();// new ChildClass("jason");}}class FatherClass {FatherClass(){System.out.println("调用了 -- FatherClass()");}FatherClass(String name){System.out.println("调用了 -- FatherClass()" + " name : " + name);}}class ChildClass extends FatherClass {ChildClass(){ // 如果不写super(),系统会默认调用父类无参构造函数,相当于加了一个super()方法System.out.println("调用了 -- ChildClass()");}ChildClass(String name){super(name);System.out.println("调用了 -- ChildClass(String name)");// super(name); // 不写在第一行,会报错}}
3.15 访问控制/权限控制符
3.15.1 作用
用来进行访问控制,可以修饰类,属性,方法,修饰类时只能使用public或默认
3.15.2 分类
| 访问修饰符 | public(公共的) | protected(受保护的) | 默认的/default | private(私有的) |
|---|---|---|---|---|
| 同一个类中 | √ | √ | √ | √ |
| 同一个包,子类 | √ | √ | √ | ❌ |
| 同一个包,非子类 | √ | √ | √ | ❌ |
| 不同包,子类 | √ | √ | ❌ | ❌ |
| 不同包,非子类 | √ | ❌ | ❌ | ❌ |
3.15.3 代码示例
/*** 权限控制/访问修饰符*/public class AccessModifier {public String name; // public 公共的protected int age; // protected 受保护的String sex; // 默认/defaultprivate int height; // private 私有的public void test(){ // 本类中的普通方法,都可以访问System.out.println(name);System.out.println(age);System.out.println(sex);System.out.println(height);}}
/*** 权限控制/访问修饰符 测试 - 同一包下子类*/public class AccessModifierSun extends AccessModifier {public static void main(String[] args) {AccessModifierSun ams = new AccessModifierSun();System.out.println(ams.name);System.out.println(ams.age);System.out.println(ams.sex);// System.out.println(ams.height); // 不能访问私有属性}}
/*** 权限控制/访问修饰符 测试 - 同一包下非子类*/public class AccessModifierTest {public static void main(String[] args) {AccessModifier am = new AccessModifier();System.out.println(am.name);System.out.println(am.age);System.out.println(am.sex);// System.out.println(am.height); // 不能访问私有属性}}
/*** 权限控制/访问修饰符 测试 - 不同包下子类*/public class AccessModifierSun extends AccessModifier {public static void main(String[] args) {AccessModifierSun ams = new AccessModifierSun();System.out.println(ams.name);System.out.println(ams.age);// System.out.println(ams.sex); // 不能访问默认属性// System.out.println(ams.height); // 不能访问私有属性}}
/*** 权限控制/访问修饰符 测试 - 不同包下非子类*/public class AccessModifierTest {public static void main(String[] args) {AccessModifier am = new AccessModifier();System.out.println(am.name);// System.out.println(am.age); // 不能访问受保护的属性// System.out.println(am.sex); // 不能访问默认属性// System.out.println(am.height); // 不能访问私有属性}}
3.16 类的封装
3.16.1 概念
把抽象的数据和对数据进行的操作封装在一起,数据被保存在内部,程序的其他部分只有通过被授权的操作(成员方法)才能对数据进行操作,保护数据不被外部直接修改
3.16.2 特点
- 数据封装:保护数据成员,不让类以外的程序直接访问或修改,只能通过提供的公共的接口访问
- 方法封装:方法的细节对用户是隐藏的,只要接口不变,内容的修改不会影响到外部的调用者
- 当对象含有完整的属性和与之对应的方法时称为封装
- 从对象外面不能直接访问对象的属性,只能通过和该属性对应的方法访问,提供公共访问方法(
setter,getter) - 对象的方法可以接收对象外面的消息
- 通过访问修饰符(
private)实现,私有化属性
3.16.3 代码示例
/*** 封装*/public class Encapsulation {public static void main(String[] args) {Person person = new Person();person.name = "jason";System.out.println(person.name);person.setAge(100);System.out.println(person.getAge());}}class Person {public String name;// 数据封装,使用private隔绝外部直接访问private int age;// 对外提供访问方法public int getAge() {return age;}// 对外提供访问方法,可以接受参数public void setAge(int age) {if(this.checkAge(age))this.age = age;}// 方法封装,只允许当前类内部访问private boolean checkAge(int age){return age < 0 ? false : age > 150 ? false : true;}}
3.17 方法的重写(Override)
3.17.1 概念
当子类中出现和父类一模一样的方法时,会出现覆盖操作,也叫做重写,复写
3.17.2 特点
- 子类方法必须和父类方法完全一样才能覆盖(名称、参数类型和参数个数都一样)
- 如果想继续调用父类方法,可以使用
super关键字 - 父类中的私有的方法,子类不能访问
- 重写的方法不能拥有比被重写方法更严格的访问权限
- 使用场景:当父类中的实现不能满足子类时,子类可以根据需要重写相应的方法的实现
- 返回值类型可以是被重写方法返回值类型的子类
3.17.3 代码示例
/*** 方法的重写/覆盖*/public class MethodOverride extends SuperMethodOverride {// 重写父类方法快捷键 ctrl + opublic static void main(String[] args) {MethodOverride mo = new MethodOverride();mo.testOverride(); // 此时子类方法覆盖父类方法,本方法则会调用子类方法mo.testSuper(); // 如果想调用父类方法,可以使用super关键字来调用mo.testOther(); // 如果子类中没有覆盖父类方法,则会继续调用父类方法}public void testSuper(){testOverride();super.testOverride(); // 可以通过super调用父类方法}@Overridepublic void testOverride(){System.out.println("I am MethodOverride...");}@Overridepublic void testAccessOverride() { // 访问权限不能比父类方法权限更严格,可以更宽松super.testAccessOverride();}@Overridepublic MethodOverride testReturnTypeOverride() { // 返回值类型可以是被重写方法返回值类型的子类return new MethodOverride();}// 静态方法不能被重写// @Override// public static void testStatic(){}/** @Override 表示覆盖(重写)父类方法,这里是静态方法所以不能被覆盖(重写)* 不写 @Override 注解,且方法与父类一模一样时,此时父类相应方法相对于子类会隐藏,* 而不是被子类重写,调用时是调用的子类方法*/public static void testStatic(){System.out.println("testStatic...MethodOverride");}}class SuperMethodOverride {public void testOverride(){System.out.println("I am SuperMethodOverride...");}void testAccessOverride(){System.out.println("testAccessOverride...");}public SuperMethodOverride testReturnTypeOverride(){System.out.println("testReturnTypeOverride...");SuperMethodOverride s = new SuperMethodOverride();return s;}public void testOther(){System.out.println("没有被覆盖的方法...");}// 静态方法public static void testStatic(){System.out.println("testStatic...");}}
3.18 关键字-final
3.18.1 概念
final:最终的,不可改变的
3.18.2 特点
- 可以修饰类,方法,变量
- 修饰的类不可以被继承
- 修饰的方法不可以被覆盖
- 修饰的变量,是一个常量,只能被赋值一次
- 命名规范
3.18.3 代码示例
/*** fianl 关键字*/public class Keyword_final extends SuperFinal {public static void main(String[] args) {new Keyword_final().test();SuperFinal superFinal = new SuperFinal();// superFinal.USER_NAME = "abc"; // 不能修改final修饰的变量superFinal.age = 28;superFinal.testFianl();}@Overridepublic void test() {super.test();}// 不可以覆盖final方法// @Override// public void testFianl() {// }// final 修饰的参数不能被改变public void test2(final int num){// num++;// num = 0;}}// final修饰的类final class Final {}// 被final修饰的类不可以被继承//class SubFinal extends Final {////}class SuperFinal {public final String USER_NAME = "jason";public int age = 18;public final void testFianl(){System.out.println("USER_NAME: " + USER_NAME + " age: " + age);}public void test(){System.out.println("USER_NAME: " + USER_NAME + " age: " + age);}}
3.19 对象转型
3.19.1 概念
一种事物的多种存在形态
3.19.2 特点
- 父类或接口的引用“指向”子类对象
- 父类引用不能访问子类新增的成员(属性和方法)
- 可以使用
引用变量 instanceof 类名来判断该引用类型变量所“指向”的对象是否是属于该类或该类子类 - 子类对象可以作为基类对象来使用,叫向上转型,反之叫向下转型
3.19.3 代码示例
/*** 对象转型*/public class ObjectCasting {public static void main(String[] args) {// 父类引用指向子类对象Aniaml aniaml = new Dog("小黑"); // 向上转型Aniaml aniaml2 = new Bird("鹦鹉");Dog dog = (Dog) aniaml; // 向下转型,需要强制转换// Dog dog2 = (Dog) aniaml2; // 错误转型 异常 : java.lang.ClassCastException/** 使用 instanceof 关键字,进行判断* 作用:判断是否属于某个类型,返回boolean类型值* 前提: 使用instanceof时,对象类型必须和instanceof后面的参数所指定的类在继承上有上下级关系* 语法: boolean bn = 子类类型 instanceof 父类类型*/System.out.println(aniaml instanceof Dog); // instanceof 判断是否属于某个类型System.out.println(aniaml2 instanceof Dog);if(aniaml instanceof Dog){Dog d1 = (Dog) aniaml;}if(aniaml2 instanceof Dog){Dog d21 = (Dog) aniaml;}}}// 所有动物类的父类class Aniaml {public String name;public void run(){ // 此处根据实际情况,可以写方法默认实现,也可以不写,如果不写,则需定义为抽象方法System.out.println("默认方法...");}// 静态成员变量static String flag = "我是父类...";// 静态成员方法public static void eat(){System.out.println("吃某种食物...");}}class Dog extends Aniaml {Dog (String name){this.name = name;}// 子类特有的属性public int leg = 4;// 重写父类方法@Overridepublic void run(){System.out.println("四条腿跑...");}// 子类特有的方法public void swimming(){System.out.println("dog 会游泳...");}// 静态成员变量static String flag = "我是子类...";// 静态成员方法public static void eat(){System.out.println("吃狗粮...");}}class Bird extends Aniaml {Bird (String name){this.name = name;}// 重写父类方法@Overridepublic void run(){System.out.println("张开翅膀飞...");}}
3.20 动态绑定/多态
3.20.1 概念
动态绑定/多态:指在程序执行期间判断所引用对象的实际类型,根据实际类型调用相应方法
3.20.2 特点
多态前提:
- 需要存在继承或者实现关系
- 要有覆盖操作(Override)
- 父类应用指向子类对象
- 多态的存在提高了程序的扩展性和后期可维护性
3.20.3 代码示例
/*** 多态*/public class Polymorphism {// 多态前提: 存在继承或实现关系,要有覆盖操作/** 多态特点:* 1非静态成员变量: 编译看左边(父类引用),运行看左边(父类引用)* 2静态成员变量: 编译看左边(父类引用),运行看左边(父类引用)* 3非静态成员函数: 编译看左边(父类引用),运行看右边(子类对象)* 4静态成员函数: 编译看左边(父类引用),运行看左边(父类引用)*/public static void main(String[] args) {// 向上转型Aniaml aniaml = new Dog("小黑");Aniaml aniaml2 = new Bird("鹦鹉");System.out.println("aniaml: " + aniaml.name); // 特点 1非静态成员变量aniaml.run(); // 特点 3非静态成员函数System.out.println("animal-flag: " + aniaml.flag); // 特点 2静态成员变量aniaml.eat(); // 特点 4静态成员函数System.out.println("aniaml2: " + aniaml2.name);aniaml2.run();// System.out.println(aniaml.leg); // 父类引用不能调用子类特有属性// aniaml.swimming(); // 父类引用不能调用子类特有方法// 向下转型,需要强制转换,转换后可以调用子类特有的属性和方法Dog dog = (Dog) aniaml;System.out.println(dog.name);System.out.println(dog.leg);dog.run();dog.swimming();System.out.println("dog-flag: " + dog.flag);dog.eat();}}
/*** 多态案例*/public class PolymorphismTest {public static void main(String[] args) {User user1 = new User("张三",new HouYi());User user2 = new User("李四",new LiBai());GameRole user1Role = user1.getRole();GameRole user2Role = user2.getRole();user1Role.skill();user2Role.skill();}}// 用户class User {User(String userName, GameRole role) {this.userName = userName;this.role = role;}private String userName;private GameRole role;public GameRole getRole() {System.out.println(userName + " 的游戏角色是: " + role.roleName);return role;}}// 游戏角色-父类class GameRole {public String roleName;public void skill(){System.out.println("拥有某项技能...");}}// 游戏角色-子类class HouYi extends GameRole{HouYi() {this.roleName = "后羿";}@Overridepublic void skill() {System.out.println("后羿大招:火鸟之翼");}}// 游戏角色-子类class LiBai extends GameRole{LiBai() {this.roleName = "李白";}@Overridepublic void skill() {System.out.println("李白大招:青莲剑歌");}}// 后期如果添加新的角色,可以直接新建对应的类即可,不用在原文件中修改,增强程序扩展性
3.21 抽象类(abstract)
3.21.1 概念
使用abstract关键字修饰的类是抽象类(使用abstract修饰的方法为抽象方法)
3.21.2 特点
- 抽象类和抽象方法必须用
abstract来修饰 - 含有抽象方法的类必须被声明为抽象类,抽象类中可以有普通方法
- 抽象类不能被实例化(抽象类中可以有构造函数)
- 抽象类可以通过子类实例化,子类需要覆盖抽象类中所有的抽象方法才能创建对象,否则该子类还是一个抽象类
- 抽象方法只有方法声明,没有方法体(不需实现),定义在抽象类中
abstract和static不能共存abstract和private不能共存
3.21.3 代码示例
/*** 抽象类*/public abstract class AbsClass {public static void main(String[] args) {// AbsClass absClass = new AbsClass(); // 不可以实例化AbsClass absClass = new AbsChild(); // 通过子类实例化absClass.testAbs();AbsClass.testStatic();new AbsChild().testStatic();}AbsClass() {} // 可以存在构造方法// 不能使用static修饰成员变量// public staitc String name;// 普通成员变量public int age;// 普通方法public void test(){System.out.println("test...");}// 抽象方法不需要实现,由子类在继承这个抽象类时进行实现public abstract void testAbs();// 静态方法属于类方法,不需要类实例化即可调用,一般不建议放在抽象类中使用public static void testStatic(){System.out.println("testStatic...AbsClass");}// private 不能和 abstract 同时使用,当方法私有后,就不能被子类重写// private abstract void testPrivate();// static 不能和 abstract 同时使用,静态方法为类方法,不能被子类重写// static abstract void testPrivate();}class AbsChild extends AbsClass {// 子类继承抽象类后,需要实现抽象类中所有的抽象方法@Overridepublic void testAbs() {System.out.println("testAbs...");}/** @Override 表示覆盖(重写)父类方法,这里是静态方法所以不能被覆盖(重写)* 当不写 @Override 注解,且方法与父类一模一样时,此时父类相应方法相对于子类会隐藏,* 而不是被子类重写,调用时是调用的子类方法*/// @Overridepublic static void testStatic(){System.out.println("testStatic...AbsChild");}}// 子类是抽象类,可以不覆盖父类抽象类中的抽象方法abstract class AbsChild_02 extends AbsClass {}
3.22 接口(interface)
3.22.1 概念
interface:接口,一种特殊的抽象类,只包含常量和方法的定义,没有变量和方法的实现
3.22.2 特点
- 一个类可以实现多个接口
- 多个无关的类可以实现同一个接口
- 一个接口可以继承多个其他接口,子接口不需要实现被继承的接口中的方法
- 一个抽象类也可以继承其他接口,子抽象类可以不用实现被继承的接口中的方法
- 使用
interface修饰,格式:public interface 类名 {} - 使用
implements实现某个接口,格式:public calss 类名 implements 接口名 [, 接口名...] {} - 接口中的成员都是常量和抽象方法
- 成员常量默认修饰:
public static final ... - 成员函数默认修饰:
public abstract 返回值类型 xxx() - 接口和实现类之间存在多态性
3.22.3 代码示例
/*** 接口*/public interface InterfaceClass {// 接口中不能有构造方法,不能被直接实例化,只能通过子类实例化// InterfaceClass(){//// }String name = "jason"; // 相当于自动省略了 public static finalpublic static final String name2 = "jason";void test(); // 相当于自动省略 public abstractpublic abstract void test2();// 接口中不可以有普通方法// public void test3(){//// }// jdk8新增default void test3(){}}// 一个类实现某个接口,这个类就是接口的实现类,必须重写接口中所有的方法class TestInterface implements InterfaceClass {public static void main(String[] args) {// 接口是抽象的,不可以被直接实例化// InterfaceClass in = new InterfaceClass();// 可以通过子类实例化接口InterfaceClass in = new TestInterface();}@Overridepublic void test() {}@Overridepublic void test2() {}}interface InterfaceClass2 {}// 一个类可以实现多个接口class TestInterface2 implements InterfaceClass, InterfaceClass2{@Overridepublic void test() {}@Overridepublic void test2() {}}// 接口可以多继承其他接口,子接口不需要实现被继承的接口中的方法interface InterfaceClass3 extends InterfaceClass, InterfaceClass2 {}// 抽象类也可以继承其他接口,子抽象类可以不用实现被继承的接口中的方法abstract class SubClass implements InterfaceClass, InterfaceClass2 {}
/*** 接口练习*/public class InterfaceTest {public static void main(String[] args) {// User user = new User();// user.eat();// user.work();// user.readBook();// 接口只能访问实现类中的一部分代码IHuman human = new User();human.eat();human.work();IStudent student = new User();student.readBook();// 可以画内存图进行辅助理解IStudent student2 = (IStudent) human;student2.readBook();}}interface IHuman {void eat();void work();// int readBook(); // 避免实现的两个不同接口中有相同方法名但是返回值类型不一样的情况}interface IStudent {void readBook();}class User implements IHuman, IStudent {@Overridepublic void eat() {System.out.println("吃饭...");}@Overridepublic void work() {System.out.println("工作是:敲代码...");}@Overridepublic void readBook() {System.out.println("我会读书...");}}
3.23 匿名对象
3.23.1 概念
只创建对象,但是不用变量来接收
3.23.2 特点
- 匿名对象也是一个对象,具有对象的所有功能
- 每一次使用匿名对象时,都是一个新的对象,每次创建匿名对象都是不同的对象,所以一个匿名对象,只能使用一次
3.23.3 代码示例
/*** 匿名对象*/public class AnonymousObject {public static void main(String[] args) {// 创建有名的对象Person person = new Person();// 创建匿名对象new Person();// 调用匿名对象的属性和方法new Person().name = "jason";System.out.println(new Person().name); // nullnew Person().sleep(); // sleeping...}}class Person {String name;public void sleep() {System.out.println("sleeping...");}}
3.24 内部类
3.24.1 概念
成员内部类:将一个类定义在另一个类里面,里面的类叫做内部类(内置类,嵌套类)
局部内部类:定义在局部(方法或代码块中)位置
3.24.2 特点
成员内部类:
- 可以被
public、private、protected、static等成员修饰符修饰 - 被 static 修饰的内部类,只能访问外部类中静态成员
- 可以被
局部内部类:
- 不能被
public、private、protected、static等成员修饰符修饰 - 可以访问外部类中的成员
- 也可以访问所在局部中的局部变量,必须是
final修饰(JDK1.8之前) - 从JDK8.0开始,还可以访问所在方法的实际上的最终变量或参数(没有被
final修饰但只进行了一次赋值的变量或参数) 为什么是
final或只进行了一次赋值的变量- 当方法执行完后,就会被销毁,包括局部变量,而局部内部类并不一定会被销毁,有可能还在执行相应方法,如果这时访问了外部方法中定义的局部变量,则访问不到
- 为了解决这个矛盾,如果局部内部类中访问了所在方法的某个变量,就将该方法中的变量复制一份作为内部类的成员变量,当内部类访问所在方法中的变量时,就让它去访问复制出来的成员变量。这样在内部类所在方法中的变量消失的之后,仍然可以访问它,当然这里并不是真正地访问它,而是访问它的复制品
- 由于是将局部内部类所在方法的变量复制一份作为局部内部类的成员变量,故在定义局部内部类之前,一定要对局部内部类所在方法的变量进行初始化,没有初始化是无法复制的
- 为了保证复制得到的那一份成员变量的值和原来的局部变量的值相同。如果在外部类中修改了局部变量的值,那就要修改局部内部类中复制得到的那一份成员变量的值;如果在局部内部类中修改了复制得到的那一份成员变量的值,那就要修改外部类中局部变量的值(前提是这个局部变量还存在),这样做是非常困难的。于是Java干脆就不允许局部内部类要访问的局部变量的值发生改变,即局部内部类中只能访问所在方法的最终变量或实际上的最终变量
- 不能被
3.24.3 代码示例
/*** 内部类*/public class InnerClass {public static void main(String[] args) {new InnerClass().new MemberInnerClass().test();new StaticMemberInnerClass().test();new InnerClass().testMemberInnerClass();new InnerClass().testLocalInnerClass(0);}// 外部类成员属性int a = 1;private int b = 2;static int c = 3;// 成员内部类class MemberInnerClass {int a = 4;public void test(){System.out.println("test MemberInnerClass");System.out.println(a); // 4System.out.println(b); // 2System.out.println(c); // 3}}// 静态成员内部类static class StaticMemberInnerClass{public void test(){System.out.println("test StaticMemberInnerClass");// System.out.println(a); // 无法访问非静态变量// System.out.println(b); // 无法访问非静态变量System.out.println(c); // 3}}// 成员函数中访问内部类public void testMemberInnerClass(){MemberInnerClass memberInnerClass = new MemberInnerClass();memberInnerClass.test();StaticMemberInnerClass staticMemberInnerClass = new StaticMemberInnerClass();staticMemberInnerClass.test();// 不能访问 局部内部类// LocalInnerClass localInnerClass = new LocalInnerClass();}// 成员函数中的局部内部类public void testLocalInnerClass(final int i) {int f = 5;// f = 7; // 不能修改,只能进行一次赋值final int g = 6;// 定义局部内部类class LocalInnerClass {public void test() {System.out.println("test LocalInnerClass");System.out.println(i); // 0System.out.println(f); // 5System.out.println(g); // 6// f = 7; // 不能修改,只能进行一次赋值}// 不能修改,只能进行一次赋值// public void change(){// f = 7;// }}// 访问局部内部类,在局部访问LocalInnerClass localInnerClass = new LocalInnerClass();localInnerClass.test();}}
/*** 内部类使用案例,技能类接口*/public interface Skill {// 获取终极大招String getBigSkill();}
/*** 内部类使用案例,第1种方式访问内部类* 在成员方法中访问局部内部类*/public class TestInnerClass_01 {public static void main(String[] args) {new TestInnerClass_01().test();}public void test() {class InnerSkill {public void bigSkill() {System.out.println("终极大招...");}}// 在成员方法中访问成员内部类InnerSkill innerSkill = new InnerSkill();innerSkill.bigSkill();}}
/*** 内部类使用案例,第2种方式访问内部类* 定义成员方法,返回封装的成员内部类实例*/public class TestInnerClass_02 {public static void main(String[] args) {TestInnerClass_02 testInnerClass_02 = new TestInnerClass_02();Skill houYi = testInnerClass_02.getHouYi();Skill liBai = testInnerClass_02.getLiBai();System.out.println(houYi.getBigSkill());System.out.println(liBai.getBigSkill());}private class HouYi implements Skill {private String bigSkill = "后羿大招:火鸟之翼";@Overridepublic String getBigSkill() {return bigSkill;}}private class LiBai implements Skill {private String bigSkill = "李白大招:青莲剑歌";@Overridepublic String getBigSkill() {return bigSkill;}}// 定义成员方法,返回封装的成员内部类实例public Skill getHouYi() {return new HouYi();}public Skill getLiBai() {return new LiBai();}}
/*** 内部类使用案例,第3种方式访问内部类* 在方法中返回局部内部类实例*/public class TestInnerClass_03 {public static void main(String[] args) {Skill skill = new TestInnerClass_03().getSkill();String bigSkill = skill.getBigSkill();System.out.println(bigSkill);}public Skill getSkill() {String skill = "我的技能...";class Person implements Skill {@Overridepublic String getBigSkill() {test(); // 访问局部内部类中的其他方法return skill; // 访问外部方法中的变量}private void test() {System.out.println(skill + " + 1");}}return new Person();}}
3.25 Object类
3.25.1 简介
Ojbect是所有Java类的跟基类(祖先),如果在类中没有使用extends指明基类,则默认基类为Object类Object类有一个默认构造方法pubilc Object(),在构造子类实例时, 都会先调用这个默认构造方法- 可以使用类型为
Object的变量指向任意类型的对象(参考对象转型) Object类的变量只能用作各种值的通用持有者,要对他们进行任何专 门的操作,都需要知道它们的原始类型并进行类型转换(参考对象转型)
3.25.2 常用方法
toString()方法Object类中toString()方法,描述当前对象相关信息(类名和引用信息),返回值为String类型- 在进行
String与其它类型数据的连接操作时,自动调用toString()方法 - 可根据需要,重写
toString()方法
equals()方法- 用来判断两个对象(不同的对象的属性值)是否相等,返回
true或false - JDK一些常用类重写了该方法,如
String,Date等类 - 可以根据需要,重写
equals()方法 - 重写
equals()方法时,需要同时重写hashCode()方法 Ojbect中equals()方法默认使用双等号来实现,并不能判断对象是否相等,所以需要时要重写与“==”关系:
- “==”:比较的是基本数据类型的值,
- 如果是引用数据类型,没有重写
equals()方法前提下,两者都是比较对象的值(地址引用是否指向同一个对象)
- 用来判断两个对象(不同的对象的属性值)是否相等,返回
hashCode()方法- 返回该对象的哈希代码值
- 在 Java 应用程序执行期间,在对同一对象多次调用
hashCode()方法时,必 须一致地返回相同的整数,前提是将对象进行equals比较时所用的信息没有 被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数 无需保持一致 - 如果根据
equals(Object)方法,两个对象是相等的,那么对这两个对象中的 每个对象调用hashCode方法都必须生成相同的整数结果 - 如果根据
equals(java.lang.Object)方法,两个对象不相等,那么对这两个 对象中的任一对象上调用hashCode方法不 要求一定生成不同的整数结果。 但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能
getClass()方法- 获取当前对象所属的类信息,返回
Class对象
- 获取当前对象所属的类信息,返回
// Object类中toString()的默认实现public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}// Object类中equals()的默认实现public boolean equals(Object obj) {return (this == obj);}// Object类中hashCode()的默认实现public native int hashCode();// Object类中getClass()的默认实现public final native Class<?> getClass();
3.25.3 代码示例
/*** Object类相关操作*/public class ObjectClass {public static void main(String[] args) {Object obj = new TestToString(); // 多态特性TestToString tts = new TestToString();System.out.println(tts);System.out.println(tts.toString());TestEquals te = new TestEquals();te.setName("jason");te.setAge(18);TestEquals te2 = new TestEquals();te2.setName("jason");te2.setAge(18);System.out.println(te.equals(te2));System.out.println(te.hashCode());System.out.println(te2.hashCode());String str = new String("jason");String str2 = new String("jason");System.out.println("str == str2 : " + str == str2); // falseSystem.out.println("str.equals(str2) : " + str.equals(str2)); // trueSystem.out.println(te.getClass());}}class TestToString {public String name;public int age;@Overridepublic String toString() {return "TestToString{" +"name='" + name + '\'' +", age=" + age +'}';}}class TestEquals {public String name;public int age;@Overridepublic boolean equals(Object o) {System.out.println("调用了 -- equals(Object o)");if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;TestEquals that = (TestEquals) o;return age == that.age &&name.equals(that.name);}@Overridepublic int hashCode() {System.out.println("调用了 -- hashCode()");return Objects.hash(name, age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
