面向对象
- 程序是为了模拟现实世界,解决现实问题而使用计算机语言编写的指令集合。
1、面向对象的思想(Object Oriented Programming)
- 一切客观存在的事物都是对象,万物皆对象。
- 任何对象,一定具有自己的特征和行为
- 特征:称为属性,一般为名词,代表对象有什么。
- 行为:称为方法,一般为动词,代表对象能做什么。
1.1 面向对象的定义
- 面向对象区别于面向过程而存在的,在早期是先固定功能,然后根据功能来编写代码,每一个功能对应一段代码,这段代码只能完成本功能;
- 这样做的方式及其繁琐,代码量太大,而且检查代码会花费大量的时间,为了提高效率,人们将一些可以重复使用的代码提取出来,封装成一个方法,这样就达到了重复使用的目的;
- 再后来,我们将一些性质相近的方法放到一起,就出现了一个类,我们将可以完成一系列功能的类称之为对象;
- 所谓的对象,就是具有一定功能的独立个体;
- 不再局限于完成固定功能,不再局限于代码的实现,以创造具备某些功能的个体,让这些个体去完成我们的指令为编程方式,这就是面向对象的思想;
1.2 说明
- 早期的编程语言更像是程序员自己去执行命令,比如说扫地,我需要先拿到扫把,然后从一个地方开始,挥舞扫把,到一个地方结束,然后回头,重复扫地动作,直到结束,然后将扫把放回原处,每次扫地都需要重复这样的步骤;
- 面向对象的编程语言更像是创造一个仆人,你只需要告诉仆人,将那个地方打扫干净,仆人就会自己去做这件事,中间的步骤我只要设定一次就行,这样的结果就是程序员得到了解放;
1.3 特点
- 更符合人类的思想习惯
- 将复杂的事情简单化
- 将我们从执行者编程了指挥者
1.4 开发过程和设计
- 就是不断的创造对象,使用对象,指挥对象去做事情
- 面向对象编程的设计其实就是在管理和维护对象之间的关系
1.5 面向对象的特点
- 封装
- 继承
- 多态
1.6 现实中的对象
-
请分析以下对象都有哪些属性和方法?
-
比如:手机
-
属性
- 品牌
- 颜色
- 价格
- 重量
- …
-
方法
- 收发短信
- 接打电话
- …
-
-
比如:汽车
- 属性
- 品牌
- 颜色
- 型号
- 产地
- …
- 方法
- 前进
- 后退
- …
- 属性
-
1.7 程序中的对象
- 如何使用程序模拟现实世界,解决现实问题?
- 首先,在程序当中,必须具有和现实中相同的对象,用以模拟现实世界。
- 然后,使用程序中的对象代表现实中的对象,并执行操作,进而解决现实问题。
- 如何在程序中创造出与现实中一样的对象?
- 现实中的对象多数来自于"模板(图纸)“,程序中的对象也应该具有"模板”。
2、类的使用
2.1 定义
-
我们学习编程的目的就是为了将生活中的事和物用程序语言描述出来
-
类是一种事物的统称,是对象的模板。类是一种虚拟的概念。看不见、摸不着。
-
什么是类?举个例子:汽车是怎么创造出来的?
- 汽车设计图纸规定了该款汽车所有的组成部分,包括外观形状、内部结构、发动机型号、安全参数等具体的信息。这即为现实对象的模板。程序中的模板也有相同作用,称为"类"。
- 按照设计图纸创造出来的汽车,才是真实存在、切实可用的实体,所以汽车实体被称为现实中的对象。而通过程序中的模板创造出来的实体,即为程序中的对象,称为"对象"。
-
类的抽取
- 在一组相同或类似的对象中,抽取出共性的特征和行为(属性、方法),保留所关注的部分。
2.2 如何描述
- 生活中的描述
- 属性:就是该事物的信息(事物身上的名词)
- 行为:就是该事物能够做什么(事物身上的动词)
- java 中的描述
- 成员变量:就是事物的属性
- 成员方法:就是事物的行为
- 我们描述的是一类事物
2.3 如何用java定义
- 成员变量:等同于定义变量,只是位置发生了变化,在类中,方法外;
- 成员方法:等同于以前定义的方法,
static
暂时不要了;
2.4 面向对象的案例
2.4.1 案例(一)
/**
* class >> 类
* 类:是一种事物的统称,是对象的模板。类是一种虚拟的概念。看不见,摸不着。
* 比如:人类、桌子、凳子、汽车、飞机。
* 人会吃饭 == > 人指的是类。
* 对象:万事万物皆对象。对象是这个世界上实实在在存在的个体。你看到的任何一个东西都是一个对象。
* 今天我跟小红取吃饭 == > 我、小红就是人类的实体 == 对象
*
* 类和对象的关系
* 类是模板,是图纸,对象是根据模板(图纸)创建出来的一个实实在在的个体。
* 造汽车:需要先准备图纸,宝马先造出一辆,然后开发布会,开始量产
*
* */
-
创建一个学生类
package com.goshawk.object; /** * 定义一个学生类-学生的模板 * 此时是在定义一个学生具备怎么的属性和功能(方法) * * */ public class Student { // 属性:成员变量(局部变量) long id; String name; int age; // 能力/功能/方法:比如 学习、吃饭、睡觉 void study(){ System.out.println("正在学习..."); } void eat(){ System.out.println("正在吃饭..."); } void sleep(){ System.out.println("现在睡觉..."); } }
-
创建一个老师类
package com.goshawk.object; public class Teacher { String name; int age; String schoolName; void teach(){ System.out.println("开始上课..."); } }
-
创建一个业务类
package com.goshawk.object; /** * 面向对象的第一个案例 * * */ public class TestObject { public static void main(String[] args){ //1> 苍鹰老师开始上课 //2> 小明同学开始学习 // 创建一个老师的对象 ==> 用Teacher类来床加你一个对象 Teacher teacher= new Teacher(); // 给老师取名 teacher.name = "Goshawk"; // 让老师上课 teacher.teach(); // 使用Student类来创建一个实实在在的学生个体 Student student = new Student(); student.name = "小明"; student.age = 22; student.id = 10001L; student.study(); } }
2.4.2 案例(二)
-
如何定义类?
/** 属性:通过变量表示,又称实例变量。 语法:数据类型 属性名; 位置:类的内部,方法的外部。 方法:通过函数表示,又称为实例方法。 语法: public 返回值类型 方法名(形参){ // 方法主体 } */ public class Dog{ String breed; // 品种 int age; String sex; String furColor; // 毛色 public void eat(){ System.out.println("eating"); } public void sleep(){ System.out.println("sleep"); } }
-
如何创建对象?
/** 将对象保存在相同类型的dog变量中,dog变量称为对象名或者引用名 */ public class TestCreatObect{ public static void main(String[] args){ Dog dog = new Dog(); // 基于Dog类创建对象 dog.breed = "萨摩"; dog.age = 2; dog.sex = "公"; dog.furColor = "白色"; // 访问属性:对象名.属性名; --> 取值 System.out.println(dog.breed + "\t" + dog.age + "\t" + dog.sex + "\t" + dog.furColor); // 调用方法:对象名.方法名(); dog.eat(); dog.sleep(); } }
2.4.3 案例(三)
- 定义学生类:
- 属性:姓名(name)、年龄(age)、性别(sex)、分数(score)
- 方法:打招呼(sayHi) // 打印学生所有信息
- 创建多个学生对象,为其各个属性赋值,并调用方法
package com.goshawk.chap01;
public class Student {
String name;
int age;
String sex;
double score;
public void sayHi(){
System.out.println("大家好,我是"+name+"\t"+"现在"+age+"岁,性别"+sex+",分数:"+score);
}
}
package com.goshawk.chap01;
public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "小明";
s1.age = 18;
s1.sex = "男";
s1.score = 98;
s1.sayHi();
Student s2 = new Student();
s2.name = "小丽";
s2.age = 18;
s2.sex = "女";
s2.score = 99;
s2.sayHi();
}
}
2.5 使用类创建对象过程的内存变化
2.5.1 对象创建的过程
public class TestConstructors{
public static void main(String[] args){
// new Student(); 触发对象创建
Student s = new Student();
}
}
class Student{
String name;
int age;
String sex;
double score;
public Student(){
System.out.println("Student() Executed");
}
}
- 对象的创建过程;
- 开辟空间并为属性赋予默认值
- 执行构造方法的代码
- 将对象的地址赋值给变量
2.5.2 对象内存分配
Student s = new Student();
s.name = "tom";
s.age = 20;
s.sex = "male";
s.score = 98.5;
# 变量s(引用)中保存对象的地址,通过变量中的地址访问对象的属性和方法。
2.5.3 案例
结合 2.4
面向对象的第一个案例
Student stu = new Student();
:
- 在内存中开辟该对象所需要的空间
- 给对象中的属性赋予默认值
- 把对象的该空间的内存地址赋给栈空间对象的变量
3、对象的使用
3.1 类与对象的关系
- 类:定义了对象应具有的特征和行为,类是对象的模板。
- 对象:拥有多个特征和行为的实体,对象是类的实例。
3.2 案例
package com.goshawk.object;
/**
* 对象的使用
* 对象的使用就是在使用对象中的属性和方法
* 此时对象的使用关键是如何通过对象名来找到属性和方法?
* 格式:
* `对象.属性` 和 `对象.方法()`的形式来使用对象*/
public class TestObject2 {
public static void main(String[] args){
Student stu = new Student();
// 使用对象属性
stu.id = 1001L;
// 通过 . 的方式,找到对象这块空间里面的name空间
stu.name = "xiaoming";
System.out.println("学生:"+stu.name+"id:"+stu.id);
// 调用对象的方法
stu.study();
stu.eat();
stu.sleep();
}
}
4、如何设计面向对象的类
基于面向对象的程序设计是怎么做的?
-
面向对象的重点是什么?
重点不是使用对象;使用对象相当于是设计阶段已经结束了;
面向对象的重点是:设计类!
-
在设计好了类以后如何使用类?
- 使用类来创建对象
- 使用对象:调用对象的属性和方法
-
如何设计类呢?
设计类就是要设计类的内容。类的内容包括五个部分。这五个部分不是必须都得全部出现。
- 属性(成员变量)
- 方法(功能)
- 构造器(构造方法)
- 代码块
- 内部类
4.1 类的属性(实例变量)
什么是类的属性,其实就是类的成员变量;
思考:之前学习局部变量时,要求必须先赋值再使用,否则编译错误。对于实例变量而言,未赋值并不会编译错误,能否直接访问。
- 这是因为实例变量是有默认值的。
- 整数:0
- 小数:0.0
- 字符:\u0000
- 布尔:false
- 其它:null
4.1.1 变量的分类
-
从声明的位置来划分:成员变量和局部变量
- 成员变量:定义在类的内部、方法的外部
- 局部变量:定义在方法的内部
-
成员变量和局部变量的区别
- 作用域不同
- 创建时机不同,垃圾回收的时机也是不同的
- 成员变量,在创建对象时,成员变量的空间就被开辟了
- 局部变量:当调用该方法时,该局部变量才会被创建
- 权限不同
- 成员变量是可以修饰权限的,局部变量是不能修饰权限,局部变量的权限与其所在的方法的权限是相同的。
局部变量 实例变量 定义位置 方法或者方法内的结构当中 类的内部,方法的外部 默认值 无默认值 字面值(与数组相同) 使用范围 从定义行到包含其结构结束 本类有效 命名冲突 不允许重名 可与局部变量重名,局部变量优先
举个例子:
package com.goshawk.object;
/**
* 基于面向对象的程序设计是怎么做的?
* */
// 一个文件可以有两个类,但是只有一个类是被public修饰
public class DesignClass {
public static void main(String[] args) {
Implement implement = new Implement();
implement.getName();
}
}
class Implement{
// name属性:成员变量,在创建对象时,成员变量的空间就被开辟了
String name;
public void show(){
// 局部变量:当调用该方法时,该局部变量才会被创建(堆空间)
String address = "abc";
}
public void getName(){
System.out.println(name);
// System.out.println(address);
}
}
4.2 类的方法
就是一段代码的整合;
举个例子:
package com.goshawk.object;
public class TestDemo {
public static void main(String[] args) {
Student student = new Student();
student.eat();
student.sleep();
}
}
下图是分析上述代码的内存变化:
方法在调用时,会创建一个方法的栈帧,存入到栈中,随着方法执行完毕,该栈帧会出栈。于是方法中的局部变量也会被销毁。
4.2.1 实例方法
将一些描述一个功能的相关代码整合在一起。
对象的实例方法包含两部分:
- 方法的声明:
- 代表对象能做什么?
- 组成:修饰符 返回值类型 方法名(形参列表)
- 方法的实现:
- 代表对象怎么做:即如何实现对象的功能?
- 组成:{方法体}
4.2.2 方法的重载
-
有些情况下,对象的同一种行为可能存在多种实现过程。
-
例如:人对象的吃行为,吃饭和吃药的过程就存在差异。
-
到底采用哪种实现形式,需要取决于调用者给定的参数。
public class Person{ public void eat(食物 a){ // 食物放入口中 // 咀嚼 // 咽下 } public void eat(药物 b){ // 药物放入口中 // 喝水 // 咽下 } public void eat(口香糖 c){ // 口香糖放入口中 // 咀嚼 // 吐出 } }
-
-
重载(Overload):一个类中定义多个相同名称的方法
-
要求:
- 方法名称相同。
- 参数列表不同(类型、个数、顺序)。
- 与访问修饰符、返回值类型无关。
-
调用带有重载的方法时,需要根据传入的实参去找到与之匹配的方法。
-
好处:屏蔽使用差异,灵活、方便。
4.2.3 方法重载的案例
package com.goshawk.chap01;
public class Operation {
public void show(){
System.out.println("无参数方法");
}
public void show(int num){
System.out.println("一个int类型参数"+num);
}
public void show(String name){
System.out.println("一个String类型参数"+name);
}
public void show(int num, String name){
System.out.println("两个参数 int、String:"+num+"\t"+name);
}
public void show(String name, int num){
System.out.println("两个参数 String、int:"+name+"\t"+num);
}
}
package com.goshawk.chap01;
public class TestOperation {
public static void main(String[] args) {
Operation operation = new Operation();
operation.show();
operation.show(99);
operation.show("小李");
operation.show(88, "小赵");
operation.show("小钱", 77);
}
}
4.2.4 代码调错
-
思考:以下方法是不是重载?
public void m(int a){} public void m(int v){}
-
两个方法的方法名称和参数列表都相同,只有参数名称不一样,编译报错。
-
注意:只是参数名称不同,并不能构成方法的重载。
4.3 构造器(构造方法)
4.3.1 构造器的概念
什么是构造方法:类中用于创建对象的特殊方法,名称与类名相同,没有返回值类型,不可通过句点调用。
4.3.2 构造方法的特点
- 名称与类名玩完全相同。
- 没有返回值类型。
- 创建对象时,调用构造方法,不可通过句点调用。
- 注意:如果没有在类中显示定义域构造方法,则编译器默认提供无参构造方法。
举个例子:
package com.goshawk.object;
public class Cat {
String name;
int age;
public Cat(){
// System.out.println("cat的构造方法");
name = "Hellokity";
age = 2;
}
}
package com.goshawk.object;
/**
* 类的第三个成员:构造器(构造方法)
* ne Student(); 是使用new关键字+构造方法()
* 什么是构造方法:就是用来构造对象的,创造对象的。
* 一般情况下,可以通过构造方法给对象的属性赋值
* 构造方法的作用:
* 1> 创建对象
* 2> 给对象的属性赋值
* */
public class Constructor {
public static void main(String[] args) {
// 声明一个对象: Student student
// new 一个对象:new Student(); Student():调用方法 - 方法名();
// Student student = new Student();
// 调用Cat()这个构造方法
Cat cat = new Cat();
System.out.println(cat.name);
System.out.println(cat.age);
}
}
上述代码的内存变化分析
4.3.2 构造器的作用
- 创建对象
- 给对象的属性赋值
4.3.3 构造器的声明格式
package com.goshawk.object;
/**
* 构造器的格式与构造器的重载
* 1、构造器的声明格式
* public(或者其它权限修饰符) 类名(参数列表){具体要执行的内容}
* 注意:方法名必须是类名,没有返回值(比如:void,如果加上void就会编程一个普通方法)
*
* */
public class TestObject3 {
}
class Dog{
String name;
int age;
// 构造方法的名称要与类名一致
// public(或者其它权限修饰符) 类名(参数列表){具体要执行的内容}
public Dog(){
}
}
4.3.4 构造器(构造方法)的重载
- 构造方法也可重载,遵循重载规则。(可以规避属性在构造方法写死)
- 创建对象时,根据传入参数,匹配对应的构造方法。
- 由构造方法为各个属性赋值,然后在创建对象的同时,将值传入构造方法。
4.3.4.1 案例(一)
package com.goshawk.object;
/**
*
* 构造器的重载
* 构造器的重载与方法的重载是一个意思:
* 在一个类中,有多个构造器,但是多个构造器的参数列表是不同的
* 参数列表的不同指的是:参数的个数、类型、顺序不同。
*
* */
public class TestObject3 {
public static void main(String[] args) {
Dog d1 = new Dog();
System.out.println(d1.name);
Dog d2 = new Dog("lucky");
System.out.println(d2.name);
Dog d3 = new Dog("lili", 1);
System.out.println(d3.name);
}
}
class Dog{
String name;
int age;
public Dog(){
name = "xiaoqiang";
}
public Dog(String n){
name = n;
}
public Dog(int a){
age = a;
}
public Dog(String n, int a){
name = n;
age = a;
}
}
4.3.4.2 案例(二)
package com.goshawk.chap01_2;
public class StudentStructure {
String name;
int age;
String sex;
double score;
public StudentStructure(){
System.out.println("StudentStructure() Executed");
System.out.println("空参构造器");
}
public StudentStructure(String n){
System.out.println("StudentStructure(String n) Executed");
name = n;
System.out.println("一个参数,name:"+name);
}
public StudentStructure(String n, int a){
System.out.println("StudentStructure(String n, int ) Executed");
name = n;
age = a;
System.out.println("两个参数,name、age:"+name +"\t" + age);
}
public StudentStructure(String n, int a, String s){
System.out.println("StudentStructure(String n, int a, String s) Executed");
name = n;
age = a;
sex = s;
System.out.println("三个参数,name、age、sex:"+name +"\t" + age +"\t" + sex);
}
public StudentStructure(String n, int a, String s, double sc){
System.out.println("StudentStructure(String n, int a, String s, double sc) Executed");
name = n;
age = a;
sex = s;
score = sc;
System.out.println("四个参数,name、age、sex、score:"+name +"\t" + age +"\t" + sex +"\t" + score);
}
public void sayHi(){
System.out.println("大家好,我是"+name+"\t"+"现在"+age+"岁,性别:"+sex+",分数:"+score);
}
}
package com.goshawk.chap01_2;
public class TestStudentStructure {
public static void main(String[] args) {
// 创建对象时,根据传入参数,匹配对应的构造方法。
StudentStructure s1 = new StudentStructure();
StudentStructure s2 = new StudentStructure("tom");
s2.sayHi();
StudentStructure s3 = new StudentStructure("jack", 21);
s3.sayHi();
StudentStructure s4 = new StudentStructure("smith", 22, "male");
s4.sayHi();
StudentStructure s5 = new StudentStructure("tony", 23, "male", 97.5);
s5.sayHi();
}
}
4.3.4.3 默认构造方法
class Student{
String name;
int age;
String sex;
double score;
public Student(String n,int a,String s,double sc){
}
}
public class TestStudent{
public static void main(String[] args){
// 编译错误:无参构造方法未定义
Student s1 = new Student();
}
}
- 在类中,如果没有显示定义构造方法,则编译器默认提供无参构造方法。
- 如已手动添加有参构造方法,则无参构造方法不再默认提供,可根据需求自行添加。
4.3.5 创建类时不声明构造器和只声明了带参的构造器的区别
- 创建类时不声明构造器并不意味着没有构造器,而是jvm会给一个默认的空参的构造器。
- 创建类时如果声明了任意一个构造器,那么jvm就不会给默认的空参的构造器。
- 建议当在类中声明带参的构造器的同时,要声明一个空参的构造器。
package com.goshawk.object;
public class Fox {
String name;
int age;
public Fox(){
System.out.println("hello fox...");
}
public Fox(String n){
name = n;
}
}
package com.goshawk.object;
/**
* 创建类时不声明构造器和只声明了带参的构造器的区别:
*
* 创建类时不声明构造器并不意味着没有构造器,而是jvm会给一个默认的空参的构造器。
* 创建类时如果声明了任意一个构造器,那么jvm就不会给默认的空参的构造器。
* 因此,建议当在类中声明带参的构造器的同时,要声明一个空参的构造器
* */
public class TestObject4 {
public static void main(String[] args) {
Fox fox = new Fox();
System.out.println(fox.name);
Fox fox1 = new Fox("abc");
System.out.println(fox1.name);
}
}
5、this关键字
5.1 什么是this关键字
- 代表当前实例,通过
this.
访问实例成员;通过this()/this(xxx)
访问本类中的其他构造方法。
5.2 this关键字的特点
- this是一个关键字,翻译为:这个。
- this是一个引用,this变量中保存了内存地址指向自身,this存储在JVM内存java对象内部。
- 创建1001个java对象,每一个对象都有this,也就说有100个不同的this
- this可以实现出现在“实例方法中”当中,this指向当前正在执行这个动作的对象。
- this可以使用在实例方法中,不能使用在静态方法中。
- this代表的是当前对象,而静态方法的调用不需要对象。
- 无法从静态上下文中引用非静态 变量 this
- this关键字大部分情况下可以省略,什么时候不能省略呢?
- 在实例方法中,或者构造方法中,为了区分局部变量和实例变量,这种情况下:this. 是不能省略的。
public class TestThisKeyword{
/**
类是模板,可服务于此类的所有对象;
this是类中的默认引用,代表当前实例;
当类服务于某个对象时,this则指向这个对象。
*/
public static void main(String[] args){
// 0x0000A001
Student s1 = new Student();
s1.sayHi();
// 0x0000B002
Student s2 = new Student();
s2.sayHi();
}
}
class Student{
String name;
int age;
String sex;
double score;
public void sayHi(){
/**
当创建s1对象时,this指向0x0000A001,访问的name属性即是0x0000A001地址中的name空间;
当创建s2对象时,this指向0x0000A001,访问的name属性即是0x0000B002地址中的name空间;
*/
System.out.println("this.name");
}
}
5.3 this修饰属性
-
如果成员变量与局部变量名相同,由于局部变量没那的优先级高,那么怎么访问实例变量?
- 使用
this
- 使用
以下是成员变量与局部变量名不同(即不冲突)用法:
package com.goshawk.chap01_2;
public class StudentStructure {
String name;
int age;
String sex;
double score;
public StudentStructure(){
System.out.println("StudentStructure() Executed");
System.out.println("空参构造器");
}
public StudentStructure(String n){
System.out.println("StudentStructure(String n) Executed");
name = n;
System.out.println("一个参数,name:"+name);
}
public StudentStructure(String n, int a){
System.out.println("StudentStructure(String n, int ) Executed");
name = n;
age = a;
System.out.println("两个参数,name、age:"+name +"\t" + age);
}
public void sayHi(){
System.out.println("大家好,我是"+name+"\t"+"现在"+age+"岁,性别:"+sex+",分数:"+score);
}
}
以下是成员变量与局部变量名相同(即冲突),使用this
访问用法:
package com.goshawk.chap01_2;
public class StudentStructure {
String name;
int age;
String sex;
double score;
public StudentStructure(){
System.out.println("StudentStructure() Executed");
System.out.println("空参构造器");
}
public StudentStructure(String name){
System.out.println("StudentStructure(String name) Executed");
this.name = name;
System.out.println("一个参数,name:"+this.name);
}
public StudentStructure(String name, int age){
System.out.println("StudentStructure(String name, int age) Executed");
this.name = name;
this.age = age;
System.out.println("两个参数,name、age:"+this.name +"\t" + this.age);
}
public void sayHi(){
System.out.println("大家好,我是"+this.name+"\t"+"现在"+this.age+"岁,性别:"+this.sex+",分数:"+this.score);
}
}
5.3.1 案例
package com.goshawk.object;
/**
* this关键字(类中出现)
* 1、this修饰属性
* 比如: this.name = name;
* 表示当前对象的属性,this.name表示当前对象的name属性
* */
public class TestObject5 {
public static void main(String[] args) {
// 创建对象(构造器是创建对象时使用),变量值默认为null,然后执行构造器,执行this.name=name的时候,执行的就是当前对象的name属性,即将dujian赋予this.name
Bird bird = new Bird("dujian");
System.out.println(bird.name);
}
}
class Bird{
String name; // 成员变量也是有作用域的
int age;
public Bird(){
}
// 局部变量 == 标识符 == 见名知意 :成员变量的name不能使用其它,比如使用n代替,要与下方声明的一致,这样我们才能见名知意
// 局部变量与成员变量发生冲突(变量名相同),局部变量一旦冲突,则距离作用域越近则优先级越高
public Bird(String name){
System.out.println("hello bird");
// 当此时 name = name时,上方main入口打印bird.name为null,当使用this.name=name即可打印传进来的 "dujian"
name = name; // 左值和右值都是局部变量的name,而此时局部变量name并不等同于成员变量的name,使用this使两者等同
//this.name = name;
}
public Bird(String name, int age){
this.name = name;
this.age = age;
}
}
5.4 this修饰方法
5.4.1 案例
package com.goshawk.object;
/**
* this关键字(类中出现)
* 1、this修饰方法
* 在类的方法中,如果调用属性和方法,如果没有显式的使用"this.",相当于是默认给了"this."
* */
public class TestObject6 {
public static void main(String[] args) {
Pig pig = new Pig();
pig.show();
}
}
class Pig{
String name;
int age;
public void getName() {
System.out.println(name);
}
public void setName() {
name = "xiaozhu"; // 等同于this.name = "xiaozhu"
}
public void show() {
this.getName(); // 等同于getName(); 即没有写this.的时候会默认加上
}
}
5.5 this修饰构造器
5.5.1 案例
package com.goshawk.object;
/**
* this关键字(类中出现)
* 1、this修饰构造器
* 格式:
* this(参数列表);
* 注意:1> 只有在构造器中,可以通过this(参数列表)来调用类中的其它构造器。
* 2> 一个类中最多能出现的this(参数列表)的次数是当前类中所有构造器的数量-1,避免造成死循环。。
* 比如下方的例子,如果在空参构造使用this("a",2,"b");就会形成一个死循环;
* 3> 对于this()的调用只能出现在构造方法的首行。
* 4> 只能调用一次。
*
* 2、为什么不能在方法中使用呢?
* 因为在调用方法的时候,对象是已经创建了,而构造器的作用就是创建对象且给属性赋值。
*
* 3、this(参数列表)的作用?
* 实现代码复用。
* */
public class TestObject7 {
public static void main(String[] args) {
Rabbit rabbit = new Rabbit("xiaobai",2,"ai");
}
}
class Rabbit{
String name;
int age;
String gender;
public Rabbit(){
System.out.println("Rabbit()");
}
public Rabbit(String name){
this();
this.name = name;
System.out.println("Rabbit(String name)");
}
// 带两个参数的构造器
public Rabbit(String name, int age){
// this.name = name;
this(name);
this.age = age;
System.out.println("Rabbit(String name, int age)");
}
// 带三个参数的构造器
public Rabbit(String name, int age, String gender){
/*
* 这里注意:
* this.name = name;与this.age = age;这两行代码与上方带两个参数的构造器重复了,那可否复用?
* 如何复用?
* this(); 表示调用类中的另外一个构造器
* 当使用 this(name,age); 则代表调用了带两个参数的构造器,且这里的name与age传入进去
* */
// this.name = name;
// this.age = age;
this(name,age);
this.gender = gender;
System.out.println("Rabbit(String name, int age, String gender)");
}
}
6、封装
面向对象的三大特性之一
6.1 封装的必要性
public class TestEncapsulation{
public static void main(String[] args){
/**
在对象的外部,为对象的属性赋值,可能存在非法数据的录入。
*/
Student s1 = new Student();
s1.name = "tom";
s1.age = 20000;
s1.sex = "人妖";
s1.score = 98;
}
}
class Student{
String name;
int age;
String sex;
double score;
}
6.2 什么是封装
- 概念:尽可能隐藏对象的内部实现细节,控制对象的修改及访问的权限。
- 访问修饰符:
private
(可将属性修饰为私有,仅本类可见)。
6.3 案例(一)
- 私有属性在类的外部不可访问。
public class TestEncapsulation{
public static void main(String[] args){
Student s1 = new Student();
/**
编译错误:私有属性在类的外部不可访问。
*/
s1.age = 20000;
}
}
public Student{
String name;
int age;
String sex;
double score;
}
- 那么在提供正常的对外访问渠道的同时,又能控制录入的数据为有效?
- 添加公共访问方法
public class TestEncapsulation{
public static void main(String[] args){
/**
以访问方法的形式,进而完成赋值与取值操作。
问题:依旧没有解决到非法数据录入!
*/
Student s1 = new Student();
s1.setAge(20000);
System.out.println("s1.getAge()");
s1.setSex("人妖");
System.out.println("s1.getSex()");
}
}
public Student{
/**
提供公共访问方法,以保证数据的正常录入。
命名规范:
赋值:setXXX() // 使用方法参数实现赋值
取值:getXXX() // 使用方法返回值实现取值
*/
String name;
private int age;
private String sex;
double score;
public void setAge(int age){
if(age>0&&age<=120){
this.age = age;
}else{
this.age = 18;
}
}
public int getAge(){
return this.age;
}
public void setSex(String sex){
if(sex.equals("男")||sex.equals("女"){
this.sex = sex;
}else{
this.sex = "男";
}
}
public String getSex(){
return this.sex;
}
}
- 过滤有效数据
public class TestEncapsulation{
public static void main(String[] args){
Student s1 = new Student();
s1.setAge(20000);
System.out.println("s1.getAge()");
}
}
public Student{
/**
在公共的访问方法内部,添加逻辑判断,进而过滤掉非法数据,以保证数据安全。
*/
String name;
private int age;
String sex;
double score;
public void setAge(int age){
// 指定有效范围
if(age>0 && age<=120){
this.age = age;
}else{
this.age = 18; // 录入非法数据时的默认值
}
}
public int getAge(){
return this.age;
}
}
6.4 案例(二)
package com.goshawk.object;
public class Animal {
// 动物腿的数量
// 权限修饰符 private(私有的 == public对立),只有类的内部才能访问被private修饰的属性。
// 涉及到对属性赋值的动作,就全部封装到方法里面,让用户不能直接访问属性
private int leg;
/**
* public,大家都能访问
* 提供一个设置腿的数量方法
* 约束腿的数量不能是奇数
* */
public void setLeg(int leg){
if (leg%2==1){
System.out.println("参数有误");
return;
}
this.leg = leg;
}
/**
* 获得腿数量的方法
* @return
*/
public int getLeg(){
return this.leg;
}
}
package com.goshawk.object;
/**
* 封装
* 面向对象有三大核心思想:继承、封装、多态
* 1、冲突的出现
* 如果说为对象的属性赋值,不加任何约束的话,很有可能会和现实世界造成冲突。比如:声明动物的腿数量为3;
* 2、使用封装来解决这样的冲突
* 把属性进行私有化,把对属性约束的逻辑体现在公共(public)的方法里。
* 3、封装的含义:
* 把功能封装在一个方法里面,一般来说,对属性的操作都是通过方法来实现的,而不是直接操作对象的属性。
* 4、封装操作的一般实现步骤
* 4.1> 要把属性私有化,隐藏起来--使用private进行修饰
* 4.2> 提供公共的方法来操作私有的属性
* 4.3> 在公共的方法里在其中加入一些约束逻辑
*
* */
public class TestObject8 {
public static void main(String[] args) {
Animal a1 = new Animal();
// 因为a1的leg属性被private修饰,于是访问不到(不能直接使用a1.name的形式调用,因为这样调用可能造成与世界不符的情况,或者说希望加上一些约束条件)。
// a1.leg = 3;
a1.setLeg(3);
System.out.println(a1.getLeg());
}
}
package com.goshawk.object;
public class Person {
private String name;
// String name;
private int age;
public void setName(String name){
// 属性没有私有化,使用这种方式避开这种约束,则是可以使用person.name这种形式来调用。
// 如果属性进行私有化之后,只能调用这个方法来约束并进行调用
if (name==null || "".equals(name)){
System.out.println("参数有误");
return;
}
this.name = name;
}
public void setAge(int age){
if (age>200){
System.out.println("参数有误");
return;
}
}
}
6.5 案例(三)
- 实现银行功能
- 用户输入正确卡号和密码后可执行以下操作
- 菜单如下:
- 1、存款
- 2、取款
- 3、转账
- 4、查询余额
- 5、修改密码
- 0、退出
- 分析:
User
类(cardNo、identify、username、password、phone、balance
)Bank
类主要包括以下功能:- 初始化用户(
initial
) - 用户登录(
login
) - 显示菜单(
showMenu
) - 存款(
save
)、取款(withDraw
)、转账(trans
)、查询余额(query
)、修改密码(modifyPassword
)
- 初始化用户(
- 代码如下
package com.goshawk.encap_1;
/**
* 用户类
* */
public class User {
// 卡号
private String cardNo;
// 身份证号
private String indentity;
// 用户名
private String username;
// 密码
private String password;
// 电话
private String phone;
// 余额
private double balance;
public User(){
}
public User(String cardNo, String indentity, String username, String password, String phone, double balance) {
this.cardNo = cardNo;
this.indentity = indentity;
this.username = username;
this.password = password;
this.phone = phone;
this.balance = balance;
}
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public String getIndentity() {
return indentity;
}
public void setIndentity(String indentity) {
this.indentity = indentity;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
package com.goshawk.encap_1;
import java.util.Scanner;
/**
* 银行类
* */
public class Bank {
// 保存用户的数组
private User[] users = new User[5];
// 保存个数
private int size;
// 初始化方法
public void initial(){
// 创建用户1
User user1 = new User();
user1.setCardNo("622208806601122");
user1.setIndentity("158343423");
user1.setUsername("李强");
user1.setPassword("123456");
user1.setPhone("18100000001");
user1.setBalance(10000);
// 创建用户2
User user2 = new User("622208806601133", "1244324324", "赵武", "123456", "18100000002", 20000);
// 放入数组中
users[0] = user1;
users[1] = user2;
size = 2;
// System.out.println("用户初始化完成");
}
public Bank(){
initial();
}
// 用户登录
public void login(){
Scanner input = new Scanner(System.in);
System.out.println("请输入卡号");
String cardNo = input.next();
System.out.println("请输入密码");
String password = input.next();
// 遍历数组
User u = null; // u -> 保存找到的用户
for (int i = 0;i<size;i++){
if (users[i].getCardNo().equals(cardNo)&&users[i].getPassword().equals(password)){
u = users[i];
break;
}
}
if (u!=null){
// 登录成功 显示菜单
System.out.println("登录成功");
showMenu(u);
}else {
System.out.println("卡号或密码错误");
}
}
// 显示菜单
public void showMenu(User u){
Scanner input = new Scanner(System.in);
System.out.println("=================欢迎进入xxx银行系统==========【当前用户:"+u.getCardNo() + "】");
do {
System.out.println("=============1.存款 2.取款 3.转账 4.查询余额 5.修改密码 0.退出=============");
int choice = input.nextInt();
switch (choice) {
case 1:
this.save(u);
break;
case 2:
this.withDraw(u);
break;
case 3:
this.trans(u);
break;
case 4:
this.query(u);
break;
case 5:
this.modifyPassword(u);
break;
case 0:
return;
default:
break;
}
} while (true);
}
/**
* 存款
* @param u
*/
public void save(User u){
Scanner input = new Scanner(System.in);
System.out.println("请输入存款金额");
double m = input.nextDouble();
if (m > 0){
u.setBalance(u.getBalance()+m);
System.out.println("存钱成功:余额"+u.getBalance());
}else {
System.out.println("存钱失败,请重新输入");
}
}
/**
* 取款
* @param u
*/
public void withDraw(User u){
Scanner input = new Scanner(System.in);
System.out.println("请输入取款金额");
double m = input.nextDouble();
if (m > 0){
if (u.getBalance()>m) {
u.setBalance(u.getBalance() - m);
System.out.println("取款成功:余额" + u.getBalance());
}else {
System.out.println("余额不足");
}
}else {
System.out.println("取款失败,请重新输入");
}
}
/**
* 转账
* @param u
*/
public void trans(User u){
Scanner input = new Scanner(System.in);
System.out.println("请输入目标转账卡号");
String cardNo = input.next();
System.out.println("请输入转账金额");
double m = input.nextDouble();
// 判断目标卡号是否存在
User toUser = null;
for (int i = 0; i < size; i++) {
if (users[i].getCardNo().equals(cardNo)) {
toUser = users[i];
break;
}
}
// 判断存在目标卡号
if (toUser!=null){
if (u.getBalance()>m){
// 转账 自己账户扣钱
u.setBalance(u.getBalance()-m);
// 目标账户加钱
toUser.setBalance(toUser.getBalance()+m);
System.out.println("转账成功");
}else {
System.out.println("转账失败,余额不足");
}
}else {
System.out.println("目标卡号不存在,请重新输入");
}
}
/**
* 查询余额
* @param u
*/
public void query(User u){
System.out.println("卡号:"+u.getCardNo()+" 用户名:"+u.getUsername()+" 余额:"+u.getBalance());
}
/**
* 修改密码
* @param u
*/
public void modifyPassword(User u){
Scanner input = new Scanner(System.in);
System.out.println("请输入请的密码");
String newpassword = input.next();
if (newpassword.length() == 6) {
u.setPassword(newpassword);
System.out.println("修改成功");
}else {
System.out.println("输入密码不符合要求");
}
}
}
package com.goshawk.encap_1;
public class TestBank {
public static void main(String[] args) {
/**
* 622208806601122
* */
Bank bank = new Bank();
bank.login();
}
}
7、代码块
7.1 定义
- 代码块就是定义一个无名称的代码块,用
{ }
括起来
7.2 分类
- 局部代码块:方法中
- 构造代码块:类中方法外
- 静态代码块:类中方法外
- 同步代码块:作用域多线程中
7.3 应用
- 局部代码块
- 限定局部变量的生命周期,及早释放,提高内存利用率
- 在方法中创建一个域,限制变量的作用范围
- 构造代码块
- 创建对象的时候优先于构造方法执行,多个构造方法中相同的代码存放到构造代码块中,节省代码量
- 静态代码块
- 给构造代码块加上static关键字就变成了静态代码块,用于类的初始化,在类加载的时候就执行,并且只在类加载的时候执行一次,一般用于加载驱动。
7.4 案例
package com.goshawk.object;
public class Teacher1 {
private String name;
// 构造代码块
{
System.out.println("构造代码块");
}
public Teacher1(){
System.out.println("构造器");
}
// 普通代码块
public String getName(){
int a = 10;
int b = 20;
return name+a+b;
}
}
package com.goshawk.object;
/**
* 代码块
* 分类:
* 1、普通(局部)代码块:定义在方法的内部
* 2、构造代码块:跟床加你对象相关的代码块,与构造器相同,也起到初始化对象的作用。
* 构造代码块和构造器,谁先谁后?构造代码块会早于构造器,先执行。
* 3、静态代码块
* 4、同步代码块
*
* */
public class TestObject9 {
public static void main(String[] args) {
Teacher1 t1 = new Teacher1();
}
}
8、静态static
(静态成员、类加载)
8.1 实例属性
- 实例属性是每个对象各自持有的独立空间(多份),对象单单方面修改,不会影响其他对象。
8.2 什么是静态?
- 用于修饰类的成员,表示静态。
- 一旦被修饰,类的成员将不再属于这个类创建的对象。
- 静态(
static
)可以修饰属性和方法。 - 称为静态属性(类属性)、静态方法(类方法)。
- 静态成员是全类所有对象共享的成员。
- 在全类中只有一份,不因创建多个对象而产生多份。
8.3 特点
- 随着类的加载而加载
- 优先于对象存在
- 被类的所有对象共享
- 通过类名调用
- 其实它本身也可以通过对象名调用
- 推荐使用类名调用
- 优先于类的其他成员
- 类的所有静态成员都存储于静态方法区
8.4 static
关键字的介绍
如果说要想使用某一个工具类的方法,目前只能先给工具类创建对象才能使用其中的方法。显然这样有些复杂。
那么要如何简化呢?
直接在方法前面加上static,于是可以直接通过"类名.静态方法"
的方式来调用,而不需要创建对象。
演示:
package com.goshawk.object;
/**
* 数学计算的工具类
*/
public class MyMath {
/**
* 实现两数相加(静态方法)
* @param a
* @param b
* @return
*/
public static int add(int a,int b){
return a+b;
}
}
package com.goshawk.object;
/**
* 静态 static
* 1、static的介绍
* 如果说要想使用某一个工具类的方法,目前只能先给工具类创建对象才能使用其中的方法。显然这样有些复杂。
* 如何简化呢?
* 直接在方法前面加上static,于是可以直接通过"类名.静态方法"的方式来调用,而不需要创建对象。
*
* 2、static在类加载的过程中的变化
* 所有使用static修饰的静态部分,都会随着类的加载而加载进元空间(方法区)。类中静态成员,在内存中只有一份。这一份是共用的。
* */
public class TestObject10 {
public static void main(String[] args) {
// 实现两数相加
// MyMath math = new MyMath();
// int result = math.add(10, 20);
int result = MyMath.add(10, 20);
System.out.println(result);
}
}
类中静态的成员包括:
- 静态的属性(static修饰属性)
- 静态的方法(static修饰方法)
- 静态的代码块
8.5 静态属性
8.5.1 特点
- 类的静态属性会随着类的加载而加载,当类加载进来后,可以直接使用类属性,而不用创建对象。
- 类变量在内存中只有一份,多个对象可以共享。(静态属性是整个类共同持有的共享空间(一份),任何对象修改,都会影响其他对象)
- 类变量早于对象的创建,晚于对象的回收。
- 类变量的使用建议且一定是通过"类名.类变量"的方式来使用。
8.5.2 案例(一)
package com.goshawk.object;
public class SportMan {
private String name;
private int age;
// 静态的属性==类变量 => 可以直接通过类名.类变量的方式来访问
static String nation;
// private String nation;
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;
}
@Override
public String toString() {
return "SportMan{" +
"name='" + name + '\'' +
", age=" + age + '\'' +
", nation=" + nation +
'}';
}
// public String getNation(){
// return nation;
// }
//
// public void setNation(String nation){
// this.nation = nation;
// }
}
package com.goshawk.object;
/**
* static修饰属性
* 特点:
* 1> 类的静态属性==类变量,=> 可以直接通过类名.类变量的方式来访问。
* 2> 类变量在内存中只有一份,对象1修改了该属性,会影响其它对象对该属性值。
* */
public class TestObject11 {
public static void main(String[] args) {
// 设置类变量,此时所有的对象都共用这一份变量
SportMan.nation = "China";
// 举办一个省级比赛
SportMan s1 = new SportMan();
s1.setName("小明");
s1.setAge(20);
// s1.setNation("China");
SportMan s2 = new SportMan();
s2.setName("小红");
s2.setAge(22);
// s1.setNation("China");
SportMan s3 = new SportMan();
s3.setName("小李");
s3.setAge(23);
// s3.setNation("China");
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
8.5.3 static修饰属性内存变化分析
8.5.3.1 图一
8.5.3.2 图二
8.5.4 案例(二)
package com.goshawk.static_1;
public class Student {
String name;
int age;
// 学生数量 == 静态属性,属于整个类共有的属性
static int count;
public void show(){
System.out.println(name + ">>>" + age);
}
}
package com.goshawk.static_1;
public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "小明";
s1.age = 21;
Student s2 = new Student();
s2.name = "小赵";
s2.age = 22;
s1.show();
s2.show();
// 调用静态类名.静态属性名
Student.count = 50;
// s1.count = 100; 不建议使用
System.out.println("学生数量:"+Student.count);
}
}
8.5.5 案例(三)
- 统计一个类的对象被创建过多少次?
package com.goshawk.static_1;
public class Teacher {
String name;
int age;
double salary; // 工资
// 保存对象创建的次数
static int count = 0;
public Teacher(){
// Teacher.count++;
count++;
}
public void show(){
System.out.println(name + "--->" + age + "--->" + salary);
}
}
package com.goshawk.static_1;
public class TestTeacher {
public static void main(String[] args) {
System.out.println("对象创建之前次数:" + Teacher.count);
Teacher t1 = new Teacher();
Teacher t2 = new Teacher();
Teacher t3 = new Teacher();
System.out.println("对象创建之后次数:" + Teacher.count);
}
}
8.5 静态方法
public class TestStaticMethod {
public static void main(String[] args) {
// 可在其他类中,通过 类名.静态方法名 访问。
MyClass.method1();
}
}
class MyClass{
public static void method1(){
Ststem.out.println("MyClass static method1()");
// 可在本类中,通过静态方法名访问
method2();
}
// 由static修饰的静态方法
public static void method2(){
Ststem.out.println("MyClass static method2()");
}
}
- 已知静态方法
Arrays.copyOf();
Arrays.sort();
Math.random();
Math.sqrt();
8.5.1 静态方法的特点
- 静态方法允许直接访问静态成员。
- 静态方法不能直接访问非静态成员。
- 静态方法中不允许使用
this
或是super
关键字。 - 静态方法可以继承,不能重写、没有多态。
8.5.2 案例
package com.goshawk.object;
/**
* 数学计算的工具类
*/
public class MyMath {
// int num = 10;
static int num = 10;
/**
* 实现两数相加(静态方法)
* @param a
* @param b
* @return
*/
public static int add(int a, int b){
System.out.println(a+b);
return a+b;
}
public void show(){
System.out.println("普通方法...");
}
public static int mul(int a, int b){
// 在静态方法使用非静态成员会报错,因为静态的部分的加载是早于对象的创建
// return a*b*num
add(a, b);
return a*b*num;
}
}
package com.goshawk.object;
/**
* static修饰方法
* 1、类方法
* static修饰方法称为类方法。
* 直接通过"类名.类方法()" 来调用即可。
*
* 2、注意事项
* 2.1> 类方法的使用往往是直接通过类来调用,而非通过创建对象来调用。
* 2.2> 类方法中不能出现类中的普通属性(成员变量),因为类方法的加载早于对象的创建。只有对象创建了,对象的属性才会存在。
* 也就是说,类方法中的变量除了局部变量以外,只能是类变量。
* 2.3> 类方法中不能出现出现this关键字,因为this描述的是当前对象,类方法的加载是早于对象的创建。
* */
public class TestObject12 {
public static void main(String[] args) {
int result = MyMath.mul(10, 5);
System.out.println(result);
}
}
8.6 静态代码块
8.6.1 案例(一)
/**
类加载时,触发静态代码块的执行(仅一次)。
执行地位:静态属性初始化之后。
作用:可为静态属性赋值,或必要的初始行为。
*/
public class TestStaticBlock{
public static void main(String[] args){
MyClass.method();
}
}
class MyClass{
static String sField = "静态属性";
static{
System.out.println(sField);
System.out.println("静态代码块");
}
public static void method(){
/**
无代码
只为触发静态属性的初始化
和静态代码块的执行
*/
}
}
8.6.2 案例(二)
package com.goshawk.static_1;
public class Person {
String name;
// 静态成员 人的最大数量
static int max=0;
// 静态代码块
// 类加载的时候,则执行静态代码块,只执行一次(即使创建了多个对象,也是执行一次)
static {
max = 10000;
System.out.println("人的最大数量:"+max);
}
// 静态方法
public static void method(){}
}
package com.goshawk.static_1;
public class TestPerson {
public static void main(String[] args) {
// Person p; // 注意此时是不会加载静态代码块的
Person p = new Person(); // 触发方法一:创建对象
// Person.method(); // 触发方式二:调用静态方法
}
}
8.6.3 案例(三)
package com.goshawk.object;
public class MyArrays {
// 类变量
public static int num=10;
// 需求:对类变量进行初始化
// 静态代码块什么时候执行?
static {
num=20;
System.out.println("静态代码块,打印num:"+num);
}
public static int getNum(){
return num;
}
public static void setNum(int num){
MyArrays.num = num;
}
}
package com.goshawk.object;
/**
* static修饰代码块
* 1、静态代码块
* 应用场景:
* 用于对一个类中的类变量进行初始化
*
* 2、使用格式
* 在类中: static{静态代码块中的代码}
*
* 3、当类加载的时候,静态代码块要被执行。
* */
public class TestObject13 {
public static void main(String[] args) {
// 需求:在加载类变量的时候,类变量就被初始化了。
System.out.println(MyArrays.getNum());
}
}
8.7 静态和非静态的区别
8.7.1 this
关键字的问题
- 静态方法中无法使用
this
关键字,因为静态方法调用的时候,不需要对象。 - 非静态方法中可以使用
this
关键字,因为非静态方法调用的时候必须要有对象。
8.7.2 访问成员变量
- 静态方法只能访问静态的成员变量,如果要使用非静态的成员变量,必须先创建对象
- 非静态的方法可以直接访问静态的成员变量,也可以直接访问非静态的成员变量
8.7.3 访问成员方法
- 静态方法只能访问静态方法,如果想使用就必须要创建对象
- 非静态方法可以直接访问静态方法,也可以直接访问非静态方法
8.7.4 注意
- 静态只能访问静态(直接调用)
8.8 什么是类加载?
JVM
首次使用某个类时,需通过CLASSPATH
查找该类的.class
文件。- 将
.class
文件中对类的描述信息加载到内存中,进行保存。- 如:包名、类名、父类、属性、方法、构造方法…
- 加载时机:
- 创建对象
- 创建子类对象
- 访问静态属性
- 调用静态方法
- 主动加载:
Class.forName
(全限定名。)
8.9 static
的总结
static
修饰的成员为静态成员,无需创建对象,可直接通过类名访问。- 静态方法不能直接访问非静态成员。
- 静态方法中不能使用
this
或者super
。 - 静态方法可以继承、不能重写、没有多态。
- 静态代码块在类加载时不能被执行,且只执行一次。
9、继承(Inheritance
)
面向对象的三大特性之一
9.1 定义
-
从生活中的继承,我们可以明白,继承就是从前辈手里获取一些好处
- 生活中的继承是施方的一种赠予,受方的一种获得。
- 将一方所拥有的东西给予另一方。
- 二者具有继承关系(直系、亲属)
-
程序中的继承
- 编程语言中的继承是指让类之间产生关系,子父类关系
- 类与类之间特征和行为的一种赠予或获得。
- 两个类之间的继承关系,必须满足
is a
的关系(比如:Dog is an Anima
==> 成立)Dog
:子类、派生类Animal
:父类、超类
9.1.1 父类的选择
- 现实生活中,很多类别之间都存在着继承关系,都满足
is a
的关系。 - 狗是一种动物、狗是一种生物、狗是一种物质。
- 多个类别都可作为"狗"的父类,需要从中选择出最适合的父类。
- 功能越精细,重合点越多,越接近直接父类。
- 功能越粗略,重合点越少,越接近
Object
类(万物皆对象的概念)。
9.1.2 父类的抽取
-
实战:可根据程序需要使用到的多个具体类,进行共性抽取,进而定义父类。
-
父类抽取的案例
package com.goshawk.Inheritance;
/**
* 父类
*/
public class Animal {
// 品种
String breed;
// 年龄
int age;
// 性别
String sex;
// 吃
public void eat(){
System.out.println("吃饭");
}
// 睡
public void sleep(){
System.out.println("睡觉");
}
}
package com.goshawk.Inheritance;
/**
* 狗类
* */
public class Dog extends Animal{
// 毛色
String furColor;
// 跑
public void run(){
System.out.println("跑");
}
}
package com.goshawk.Inheritance;
/**
* 鸟类
* */
public class Bird extends Animal{
// 毛色
String furColor;
// 飞
public void fly(){
System.out.println("飞");
}
}
package com.goshawk.Inheritance;
/**
* 蛇类
* */
public class Snake extends Animal{
// 爬
public void climb(){
System.out.println("爬");
}
}
package com.goshawk.Inheritance;
public class TestDog {
public static void main(String[] args) {
Dog wangcai = new Dog();
wangcai.breed = "萨摩";
wangcai.age = 2;
wangcai.sex = "公";
wangcai.furColor = "白色";
wangcai.eat();
wangcai.sleep();
wangcai.run();
}
}
9.2 继承的特点
9.3 优缺点
- 优点
- 就是为了获取到父类中的方法和属性
- 提高了代码的复用性、可扩展性以及维护性
- 让类和类之间产生关系,是多态的前提
- 缺点
- 类的耦合性增强了,我们开发的原则是高内聚,低耦合
- 耦合:类与类的关系
- 内聚:独立完成工作的能力
9.4 开发步骤
- 使用 extends关键字让子类和父类产生联系
- 语法:
class 子类 extends 父类{} // 定义子类时,显示继承父类
- 应用:产生继承关系后,子类可以使用父类中的属性和方法,也可以使用子类独有的属性和方法。
9.5 继承案例(一)
9.5.1 未使用继承时的代码:
package com.goshawk.extend;
public class Person {
private String name;
private int age;
private String gender;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getGender() {
return gender;
}
public void eat(){}
public void sleep(){}
}
package com.goshawk.extend;
public class Teacher {
// 作为人类的基本属性
private String name;
private int age;
private String gender;
// 作为老师,额外的属性
private String schoolName;
private String major;
// 功能
public void eat(){}
public void sleep(){}
// 额外的功能
public void teach(){
System.out.println("老师上课");
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public void setMajor(String major) {
this.major = major;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getGender() {
return gender;
}
public String getSchoolName() {
return schoolName;
}
public String getMajor() {
return major;
}
}
package com.goshawk.extend;
public class Teacher {
// 作为人类的基本属性
private String name;
private int age;
private String gender;
// 作为老师,额外的属性
private String schoolName;
private String major;
// 功能
public void eat(){}
public void sleep(){}
// 额外的功能
public void teach(){
System.out.println("老师上课");
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public void setMajor(String major) {
this.major = major;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getGender() {
return gender;
}
public String getSchoolName() {
return schoolName;
}
public String getMajor() {
return major;
}
}
9.5.2 使用继承后的代码:
package com.goshawk.extend;
public class Person {
private String name;
private int age;
private String gender;
public Person(){
System.out.println("public Person() >> 构造方法");
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getGender() {
return gender;
}
public void eat(){
System.out.println("eat");
}
public void sleep(){
System.out.println("sleep");
}
}
package com.goshawk.extend;
public class Teacher extends Person{
// 作为老师,额外的属性
private String schoolName;
private String major;
// 额外的功能
public void teach(){
System.out.println("老师上课");
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public void setMajor(String major) {
this.major = major;
}
public String getSchoolName() {
return schoolName;
}
public String getMajor() {
return major;
}
}
package com.goshawk.extend;
public class Student extends Person{
// 额外的属性
private Long stuId;
private String address;
// 额外的功能
public void study(){}
public Long getStuId() {
return stuId;
}
public String getAddress() {
return address;
}
public void setStuId(Long stuId) {
this.stuId = stuId;
}
public void setAddress(String address) {
this.address = address;
}
}
package com.goshawk.extend;
/**
* 继承
* 1、继承的目的
* 在创建一个类时,可以通过继承,快速的获得该类中已经定义好的内容,而避免了重复定义(重复造轮子)。
*
* 2、继承的使用
* class 子类 extends 父类{}
*
* */
public class Demo1 {
public static void main(String[] args) {
Teacher t1 = new Teacher();
t1.setName("tom");
t1.setAge(22);
t1.eat();
t1.setMajor("Java");
t1.teach();
System.out.println(t1.getName());
}
}
9.6 子类对象实例化的过程
9.6.1 子类对象实例化的过程
- 当创建子类对象时,子类继承链上所有父类的构造器都会被调用,调用顺序是继承链最顶端开始,依次往下,直到调用到子类本身的构造器位置。
- 因此,子类对象中包含了父类所有的东西。
- 子类可以通过自身的构造器来指明调用父类的哪一个构造器 ?在super关键字中介绍。
9.6.2 子类对象创建的过程
9.6.2.1 继承中的对象创建
- 在具有继承关系的对象创建中,构建子类对象会先构建父类对象。
- 由父类的共性内容,叠加子类的独有内容,组合成完整的子类对象。
/**
下述案例的子类Son所持有的属性和方法:
int a;
int b;
int c;
m1();
m2();
*/
class Father{
int a;
int b;
public void m1(){
}
}
class Son extends Father{
int c;
public void m2(){}
}
9.6.2.2 继承后的对象构建过程
public class TestSuperKeyword{
public static void main(String[] args){
C c = new C();
}
}
class A{}
class B extends A{}
class C extends B{}
9.6.2.3 案例
package com.goshawk.Inheritance_1;
public class A {
int num1;
int num2;
public A(){
System.out.println("A的默认构造方法");
}
public void m1(){
System.out.println("A类的m1方法");
}
}
package com.goshawk.Inheritance_1;
public class B extends A{
int num3;
public B(){
System.out.println(
"B的默认构造方法"
);
}
public void m2(){
System.out.println("B类的m2方法");
}
}
package com.goshawk.Inheritance_1;
public class TestB{
public static void main(String[] args) {
B b = new B();
b.m1();
b.m2();
}
}
- 上述案例的内存分配图如下:
9.7 继承案例(二)
9.7.1 公司员工(父类)
- 关系
- 程序员
- 人事
- 共有方法:打卡sign();
- 特有方法
- 程序员:敲代码
- 人事:招聘
代码如下:
package com.goshawk.extend;
public class Employee {
private String name;
private int age;
private Long workerId;
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Long getWorkerId(){
return workerId;
}
public void setWorkerId(Long workerId){
this.workerId = workerId;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void sign(){
System.out.println("打卡");
}
}
package com.goshawk.extend;
/**
* 程序员
* */
public class Programmer extends Employee {
public void coder(){
System.out.println("程序员喜欢敲代码");
}
}
package com.goshawk.extend;
public class HR extends Employee{
public void recruit(){
System.out.println("招聘");
}
}
package com.goshawk.extend;
public class Demo2 {
public static void main(String[] args) {
Programmer p1 = new Programmer();
p1.sign();
p1.coder();
}
}
9.8 不可继承
- 构造方法
- 类中的构造方法,只负责创建本类对象,不可继承。
private
修饰的属性和方法。- 访问修饰符的一种,仅本类可见。
- 父子类不再同一个
package
中时,default
修饰的属性和方法。
9.9 访问修饰符
本类 | 同包 | 非同包子类 | 其他(非同包非子类) | |
---|---|---|---|---|
private | ✓ | × | × | × |
default (默认的) | ✓ | ✓ | × | × |
protected (保护的) | ✓ | ✓ | ✓ | × |
public | ✓ | ✓ | ✓ | ✓ |
-
✓:可用
-
×:不可用
-
从上到下:严 --> 宽
-
注意:
default
(默认的),而它是不用显示写出来的,如果写出来是会报错的。
9.9.1 使用public、protected
修饰属性
在package_A
创建一个父类Animal
:
package com.goshawk.Inheritance;
/**
* 父类
*/
public class Animal {
// 品种
public String breed;
// 年龄
protected int age;
// 性别
public String sex;
// 吃
public void eat(){
System.out.println("吃饭");
}
// 睡
public void sleep(){
System.out.println("睡觉");
}
}
在package_B
创建一个子类Cat
:
package com.goshawk.package_B;
package com.goshawk.package_A;
/**
* 猫类
* */
public class Cat extends Animal{
String furColor;
String hobby;
public void playBall(){
// 此时被保护的age是可以被调用的
System.out.println(this.age);
}
}
在package_B
创建一个测试文件TestCat
:
package com.goshawk.package_B;
public class TestCat{
public static void main(String[] args){
Cat bosimao = new Cat();
// baosimao.age = 2; 这里的用法是错误的,因为非同包非子类是不能被调用的。
}
}
10、super关键字
-
super指的是父类、超类、基类;
-
super描述的是 “父类的” ==== this描述的是"当前对象的";
-
在子类中,可直接访问从父类继承到的属性和方法,但如果父子类的属性或方法存在重名(属性遮蔽、方法覆盖)时,需要加以区分,才可专项访问。
public class TestSuperKeyword{
public static void main(String[] args){
B b = new B();
b.upload();
}
}
class A{
/**
父类具有上传文件的功能
*/
public void upload(){
// 上传文件的100行逻辑代码
}
}
class B extends A{
/**
子类既要上传文件,又要更改文件名称,进而覆盖了父类的方法。
但上传文件的逻辑代码相同,如何复用? 参考:10.3 super修饰方法
*/
public void upload(){
// 上传文件的100行逻辑代码
// 修改文件名称的1行代码
}
}
10.1 super修饰构造器
10.1.1 格式
super(参数列表);
10.1.2 注意事项
- 只能在子类的构造器中使用super调用父类的构造器。
- 如果不显示的定义任何super()调用父类构造器,那么java会默认的在子类构造器中提供一个"super()",此时注意,如果父类没有空参构造器,那么子类必须手动来指明调用父类的一个构造器。
- 使用super(参数列表)来指定调用父类中的某一个构造器
- super必须出现在构造器的首行;因此,在一个构造器中,不能同时出现this()、super()。
10.1.3 super调用父类空参(无参)构造方法
10.1.3.1 案例(一)
package com.goshawk.supe;
public class A {
private String name;
private int age;
public A(){
System.out.println("public A()");
}
public A(String name){
this.name = name;
System.out.println("public A(String name)");
}
public A(String name, int age){
this.name = name;
this.age = age;
System.out.println("public A(String name, int age)");
}
}
package com.goshawk.supe;
public class B extends A{
private String address;
// 如果这里没有定义空参构造器,系统会默认提供如下的空参构造器,如果父类也没有定义空参构造器,会报错
// 那么要怎么办呢?
// 如果父类有定义带参的构造器,可以直接调用父类带参的构造器,比如:super(参数列表);
// public B(){
// super();
// }
public B(){
// 在调用父类的空参构造器
super();
System.out.println("public B()");
}
}
package com.goshawk.supe;
/**
* super关键字
* super指的是父类、超类、基类
* super描述的是 "父类的" ==== this描述的是"当前对象的"
*/
public class Demo1 {
public static void main(String[] args) {
B b = new B();
}
}
10.1.3.2 案例(二)
/**
super():表示调用父类无参构造方法。
如果没有显示书写,隐式存在于子类构造方法的首行。
*/
class A{
public A(){
System.out.println("A类的无参构造方法")
}
}
class B extends A{
public B(){
super(); // 调用父类的无参构造方法(不写会默认调用。)
System.out.println("B类的无参构造方法")
}
}
class C extends B{
public C(){
super();
System.out.println("C类的无参构造方法")
}
}
public class TestSuperKeyword{
public static void main(String[] args){
new C();
}
}
// 执行new C()
// 首先打印:A类的无参构造方法
// 然后打印:B类的无参构造方法
// 最后打印:C类的无参构造方法
10.1.4 super调用父类的带参构造方法
10.1.4.1 案例(一)
/**
super():表示调用父类无参构造方法。
super(实参):表示调用父类带参的构造方法。
*/
class A{
public A(){
System.out.println("A类的无参构造方法")
}
public A(int value){
System.out.println("A类的带参构造方法")
}
}
class B extends A{
public B(){
super();
System.out.println("B类的无参构造方法")
}
public B(int value){
super(value);
System.out.println("B类的带参构造方法")
}
}
public class TestSuperKeyword{
public static void main(String[] args){
new B();
new B(10);
}
}
// 先执行new B(),然后执行new B(10);
// 首先打印:A类的无参构造方法
// 然后打印:B类的无参构造方法
// 再然后打印:A类的带参构造方法
// 最后打印:B类的带参构造方法
10.1.4.2 案例(二)
package com.goshawk.Inheritance_1;
public class A {
int num1;
int num2;
public A(){
System.out.println("A的默认构造方法");
}
public A(int num1, int num2){
System.out.println("A的带参构造方法");
this.num1 = num1;
this.num2 = num2;
}
public void m1(){
System.out.println("A类的m1方法");
}
}
package com.goshawk.Inheritance_1;
public class B extends A{
int num3;
public B(){
super(); // 调用父类的无参构造方法(不写会默认调用。)
System.out.println(
"B的默认构造方法"
);
}
public B(int num1, int num2, int num3){
// super(); // 默认调用父类的无参构造方法
super(num1, num2); // 调用父类的带参构造方法
System.out.println("B的带参构造方法");
// 以下两行由于父类的带参构造方法存在(super(num1, num2)),所以省略
// this.num1 = num1;
// this.num2 = num2;
this.num3 = num3;
}
public void m2(){
System.out.println("B类的m2方法");
}
}
package com.goshawk.Inheritance_1;
/**
* super调用父类的构造方法
*
* super调用父类的带参构造方法
*/
public class TestB{
public static void main(String[] args) {
B b = new B(100, 200, 300);
b.m1();
b.m2();
}
}
10.1.4.3 案例(三)
package com.goshawk.supe;
public class A {
private String name;
private int age;
public A(String name){
this.name = name;
System.out.println("public A(String name)");
}
public A(String name, int age){
this.name = name;
this.age = age;
System.out.println("public A(String name, int age)");
}
}
package com.goshawk.supe;
public class B extends A{
private String address;
public B(){
// 使用super(参数列表)来指定调用父类中的某一个构造器
super("abc");
System.out.println("public B()");
}
}
package com.goshawk.supe;
/**
* super关键字
* super指的是父类、超类、基类
* super描述的是 "父类的" ==== this描述的是"当前对象的"
*/
public class Demo1 {
public static void main(String[] args) {
B b = new B();
}
}
10.2 super修饰属性
10.2.1 注意事项
- 如果父类和子类中有同名属性,通过super可以指明调用父类的属性;如果没有同名属性,那么super可以省略。
10.2.2 案例(一)
package com.goshawk.supe;
public class Person {
// 身份证号
String id = "2002";
String name;
public void eat(){
System.out.println("吃饭");
}
}
package com.goshawk.supe;
public class Student extends Person{
// 学号
private String id = "1001";
public void getName(){
// 这里的name为什么调用仍然是父类的name,因为子类没有声明name属性
System.out.println(name);
}
public void getId(){
System.out.println("子类ID属性:"+id);
// 打印身份证号,使用super,显式的告知要调用父类的id属性
System.out.println("父类ID属性:"+super.id);
}
}
package com.goshawk.supe;
/**
* super修饰属性
*/
public class Demo2 {
public static void main(String[] args) {
Student student = new Student();
student.getId();
}
}
10.2.3 案例(二)
public class TestSuperKeyword{
public static void main(String[] args){
B b = new B();
b.print();
}
}
class A{
int value = 10;
}
class B extends A{
int value = 20;
public void print(){
int value = 30;
System.out.println(value); // 局部变量 打印 30
System.out.println(this.value); // 本类的实例变量 打印 20
System.out.println(super.value); // 父类的实例变量 打印 10
}
}
10.3 super修饰方法
public class TestSuperKeyword{
public static void main(String[] args){
B b = new B();
b.upload();
}
}
class A{
/**
父类具有上传文件的功能
*/
public void upload(){
// 上传文件的100行逻辑代码
}
}
class B extends A{
public void upload(){
// 上传文件的100行逻辑代码
// 修改文件名称的1行代码
// super关键字可在子类中访问父类的方法。
// 使用"super."的形式访问父类的方法,进而完成在子类中的复用;再叠加额外的功能代码,组成新的功能。
super.upload();
}
}
10.3.1 格式
super.方法(); // 在子类类中的方法中使用
10.3.2 案例
package com.goshawk.supe;
public class Person {
// 身份证号
String id = "2002";
String name;
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
package com.goshawk.supe;
public class Student extends Person{
// 学号
private String id = "1001";
public void getName(){
// 这里的name为什么调用仍然是父类的name,因为子类没有声明name属性
System.out.println(name);
}
public void getId(){
System.out.println("子类ID属性:"+id);
// 打印身份证号,使用super,显式的告知要调用父类的id属性
System.out.println("父类ID属性:"+super.id);
}
public void eat(){
System.out.println("学生要吃有营养的东西");
}
public void show(){
// eat(); // 等同于this.eat();调用子类的方法
// 调用父类的方法
super.eat();
}
}
package com.goshawk.supe;
/**
* super修饰方法
* 1、格式
* super.方法();
* 在子类类中的方法使用。
* 此时就是指明调用父类中的方法。
*
* 2、方法重写的应用场景
* 为什么要用super调用方法,就是因为子类中出现了和父类中的同名方法(方法名相同、参数列表完全相同)。
* 如果需要使用父类的同名方法,那么就需要在子类中使用super来调用。
* 3、父类的静态方法不能被重写。
*/
public class Demo3 {
public static void main(String[] args) {
Student s1 = new Student();
s1.show();
}
}
10.4 super与this的区别
this
表示当前对象引用,调用本类(包括继承)的属性、方法、本类构造方法。supper
表示父类对象引用,调用父类的属性、方法、构造方法。
10.4.1 案例
class A{
public A(){
System.out.println("A-无参构造");
}
public A(int value){
System.out.println("A-有参构造");
}
}
class B extends A{
public B(){
super();
System.out.println("B-无参构造");
}
public B(int value){
this(); // super(); 注意不能同时存在
System.out.println("B-有参构造");
}
}
public class TestSuperKeyword{
public static void main(String[] args){
new B(10);
}
}
/**
this或super使用在构造方法中时,都要求在首行。
当子类构造中使用了this();或者this(实参),
即不可再同时书写super();或者super(实参),
会由this()指向的构造方法完成super()的调用。
如上述案例运行结果:
A-无参构造
B-无参构造
B-有参构造
*/
11、方法的重写/方法的覆盖(overriding)
- 思考:子类中是否可以定义和父类相同的方法?
- 思考:为什么需要在子类中定义和父类相同的方法?
- 分析:当父类提供的方法无法满足子类需求时,可在子类中定义和父类相同的方法进行覆盖(
Override
)
11.1 定义
- 子父类中出现一模一样的方法
- 重写是一种现象,也是一个动作
- 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类的方法,这样,既延续了父类的功能,又定义了子类特有的内容。
11.2 方法覆盖的原则
- 方法名称、参数列表、返回值类型必须与父类相同。
- 访问修饰符不能比父类更严格。
11.3 方法覆盖的执行
- 子类覆盖父类方法后,调用时优先执行子类覆盖后的方法。
11.4 案例
package com.goshawk.overriding;
public class Person {
private String name;
public void eat(){
System.out.println("吃饭");
}
}
package com.goshawk.overriding;
public class Student extends Person{
// 会得到eat的方法
public void eat(){
System.out.println("吃有营养的晚餐");
}
}
package com.goshawk.overriding;
/**
* 方法的重写
* 1、为什么需要方法的重写
* 当子类继承父类时,父类的方法对子类不适用,于是子类可以重写父类的方法。
*
* 2、方法的重写和方法的重载之间区别
* - 方法的重载:方法名相同,参数列表不同,跟返回值和权限修饰符无关。
* - 方法的重写:子类要去覆盖掉父类中的方法,就要防止把覆盖变成了重载的失误。(子类重写的方法的方法名、参数列表、返回值类型必须和父类的完全一致),且访问权限修饰符不能小于父类(比如:public > private)。
*
* 3、父类的静态方法不能被重写。
*/
public class Demo1 {
public static void main(String[] args) {
Student stu = new Student();
stu.eat();
}
}
11.5 重写的意义
- 首先,我们定义的方法本体和父类中的方法是一样的,只是我们的实现更加先进而已
- 别人已经熟悉了父类中的这个方法,如果子类出现同样的方法会提高使用着的辨识度,同时又能让使用者用上新的功能,这是版本升级所需要的。
11.6 测试题
-
程序员敲代码的方式,最开始的时候使用notepad++写代码,后来经过升级改造,使用了eclipse编程工具写代码,有的升级到IDEA
package com.goshawk.overriding; public class Programmer { private String name; private String major; public void codingTool(){ System.out.println("Notepad++"); } }
package com.goshawk.overriding; public class ProgrammerPlus extends Programmer{ @Override public void codingTool() { System.out.println("Eclipse"); } }
package com.goshawk.overriding; public class ProgrammerMaster extends Programmer{ @Override public void codingTool() { System.out.println("IDEA"); } }
package com.goshawk.overriding; public class Demo2 { public static void main(String[] args) { Programmer programmer = new Programmer(); programmer.codingTool(); ProgrammerPlus programmerPlus = new ProgrammerPlus(); programmerPlus.codingTool(); ProgrammerMaster programmerMaster = new ProgrammerMaster(); programmerMaster.codingTool(); } }
-
老师都有授课的能力,但每个老师授课的风格内容不一样,编程模拟
package com.goshawk.overriding; public class Teacher { public void teach(){ System.out.println("老师认真的授课"); } }
package com.goshawk.overriding; public class SportTeacher extends Teacher{ @Override public void teach() { System.out.println("演示运动项目"); } }
package com.goshawk.overriding; public class EnglishTeacher extends Teacher{ @Override public void teach() { System.out.println("教授生动的课程"); } }
package com.goshawk.overriding; public class Demo3 { public static void main(String[] args) { SportTeacher sportTeacher = new SportTeacher(); sportTeacher.teach(); EnglishTeacher englishTeacher = new EnglishTeacher(); englishTeacher.teach(); } }
11.7 重写的注意事项
11.7.1 私有方法
- 父类中私有方法不能被重写
- 父类私有方法子类可以继承,但是受到访问权限的限制,子类没有办法访问
11.7.2 权限
- 子类重写父类方法时,访问权限不能更低
- 最好保持一致
11.7.3 静态方法(不能被重写)
- 父类的静态方法,子类也必须通过静态方法重写
- 重写其实是全遮挡,调用者无法在通过任何方式去调用和子类关联的父类对象中的方法
- 但是静态无法做到全遮挡,我们依然可以直接使用父类中的方法
11.7.4 为什么添加注解:@Override
?
- 验证是否遵循重写的规则(如果没有遵循规则会报错)。
12、多态(Polymorphism
)
面向对象的三大特性之一
12.1 定义
- 一个事物的多种形态
12.1.1 生活中的多态
- 生活中的人物视角
- 生活中,不同人物角色看待同一个对象的视角不同,关注点也不同。
- 生活中的多态是指 客观事物在人脑中的主观反应。
- 主观意识上的类别与客观存在的对象具有
is a
关系时,即形成多态。
12.1.2 程序中的多态
- 概念:父类引用指向子类对象,从而产生多种形态。
Animal a = new Dog();
// Animal a:父类引用(引用类型)
// new Dog():子类对象(对象类型)
// 1、从逻辑上讲:Dog is a Animal
// 2、从语法上讲:自动类型转换
- 二者具有直接或间接的继承关系时,父类引用可指向子类对象,即形成多态。
- 父类引用仅可调用父类所声明的属性和方法,不可调用子类独有的属性和方法。
12.2 多态的前提
- 要有继承关系
- 要有方法重写
- 要有父类引用指向子类对象
12.3 注意事项
- 可以做哪些工作看父类类型
- 实际做工作得看子类对象
- 优点
- 提高了代码的扩展性、维护性,可以当作形参,用来接收任意类型的子类对象
- 缺点
- 不能使用子类的特有属性和方法
12.4 多态中的方法覆盖
- 思考:如果子类中覆盖了父类中的方法,以父类型引用调用此方法时,优先执行父类还是子类中的方法?
- 实际运行过程中,依旧遵循覆盖原则,如果子类覆盖了父类中的方法,则执行子类中覆盖后的方法,否则执行父类中的方法。
12.4.1 案例
package com.goshawk.Inheritance_1;
/**
* 父类
*/
public class Animal {
// 品种
String breed;
// 年龄
protected int age;
// 性别
public String sex;
// 吃
public void eat(){
System.out.println("吃饭");
}
// 睡
public void sleep(){
System.out.println("睡觉");
}
}
package com.goshawk.Inheritance_1;
import com.goshawk.Inheritance_1.Animal;
/**
* 狗类
* */
public class Dog extends Animal {
// 毛色
String furColor;
@Override
public void eat(){
System.out.println("狗狗开始吃狗粮");
}
}
package com.goshawk.Inheritance_1;
import com.goshawk.Inheritance_1.Animal;
/**
* 鸟类
* */
public class Bird extends Animal {
// 毛色
String furColor;
@Override
public void eat() {
System.out.println("鸟儿开始吃虫子");
}
// 飞
public void fly(){
System.out.println("飞");
}
}
package com.goshawk.Inheritance_1;
public class TestAnimal {
public static void main(String[] args) {
// 1、从逻辑上讲:Dog is a Animal
// 2、从语法上讲:自动类型转换
Animal a = new Dog();
a.age = 2;
a.breed = "萨摩";
a.sex = "公";
a.eat();
Animal b = new Bird();
b.eat();
}
}
12.5 多态的应用
-
场景一
-
使用父类作为方法形参实现多态,使方法参数的类型更为宽泛。
package com.goshawk.Inheritance_1; /** * 主人类 */ public class Master { String name; /** * 喂狗 * @param dog */ public void feed(Dog dog){ System.out.println(this.name + "喂食"); dog.eat(); } /** * 喂鸟 */ public void feed(Bird bird){ System.out.println(this.name + "喂食"); bird.eat(); } /** * 使用多态优化 */ public void feed(Animal animal){ System.out.println(this.name + "喂食"); animal.eat(); } }
-
-
场景二
-
使用父类作为方法返回值实现多态,使方法可以返回不同子类对象。
/** Master.java */ package com.goshawk.Inheritance_1; /** * 主人类 */ public class Master { String name; /** * 购买动物 */ public Animal buy(int type) { Animal animal = null; if (type == 1){ animal = new Dog(); }else if(type == 2){ animal = new Bird(); } return animal; } } /** TestMaster.java */ package com.goshawk.Inheritance_1; import java.util.Scanner; public class TestMaster2 { public static void main(String[] args) { System.out.println("=============欢迎来到动物市场============="); System.out.println("=============1.买狗狗 2. 买鸟儿============="); System.out.println("请选择"); Scanner input = new Scanner(System.in); int choice = input.nextInt(); Master master = new Master(); Animal animal = master.buy(choice); if (animal!=null){ System.out.println("购买成功"); }else { System.out.println("购买失败"); } } }
-
12.6 多态出现的场景
12.6.1 向上转型(装箱)
-
向上转型:父类的引用指向子类对象的实体。
- 将子类对象看作是父类类型,也就是我们平时使用的多态的形式。
- 这种情况下,无法调用子类特有的功能。
-
多态使用的情况下,编译时看等号的左边,运行时看等号的右边。
-
因此没有办法通过多态来调用到子类独有的方法。
-
如果一定要调用独有的方法,就需要通过向下转型来实现。
public class TestConvert{
public static void main(String[] args){
// 父类引用中保存真实子类对象,称为向上转型(即多态核心概念)。
// 注意:仅可调用Animal中所声明的属性和方法。
Animal a = new Dog();
}
}
class Animal{
public void eat(){
System.out.println("动物在吃东西");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗在吃狗粮");
}
}
12.6.2 接口的引用指向接口实现类的实体
12.6.3 向下转型(拆箱)
12.6.3.1 概念
- 强制把子类的对象(的父类的引用)转换成子类的引用。
- 或者说将父类引用中的真实子类对象,强制转回子类本身类型,称为向下转型
12.6.3.2 向下转型的前提
- 必须先向上转型,然后才能向下转型,否则会出现问题。
12.6.3.3 注意事项
- 只有转换回子类真实类型,才能调用子类独有的属性和方法。
12.6.3.4 类型转换异常
-
向下转型时,如果父类引用中的子类对象类型和目标类型不匹配,则会发生类型转换异常。
-
Exception in thread "main" java.lang.ClassCastException
如下:
class Animal{
public void eat(){
System.out.println("动物在吃东西");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗在吃狗粮");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫在吃猫粮");
}
}
public class TestConvert{
public static void main(String[] args){
/**
在进行使用多态进行向下转换时,没有转换回子类的真实类型。比如:父类为Animal,实际上子类的类型为Dog,但是在进行向下转型的时候却转成Cat,这样就不能调用原本Dog这个子类独有的属性和方法。
*
Animal a = new Dog();
Cat cat = (Cat)a;
}
}
12.6.3.5 向下转型的案例
public class TestConvert{
public static void main(String[] args){
// 通过a只能调用父类的属性和方法或者子类重写的方法,就不能调用子类特有的属性和方法,那么要怎么办呢?
Animal a = new Dog();
// 将a强转成Dog,只能这样才能调用子类特有的属性和方法。
Dog dog = (Dog)a;
}
}
class Animal{
public void eat(){
System.out.println("动物在吃东西");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗在吃狗粮");
}
}
12.6.4 instanceof关键字
-
在向下转型前,应判断引用中的对象真实类型,保证类型转换的正确性。
-
向下转型可能涉及到数据类型转换的异常,因此在做向下转型之前,先做一次 instanceof 数据类型的判断。
-
将父类引用指向子类对象再转回子类类型。
-
这种转型是有风险的,因为是强制性,一旦转向的类型跟这个对象不匹配,就会报错。
java.lang.ClassCastException
(类型转换异常,属于运行时的异常) -
语法:
引用 instanceof 类型 // 返回boolean(布尔值)类型结果,如果匹配则返回true。
//if (对象 instanceof 类) ==> 对象是否是该类的实体
12.6.4.1 案例(一)
public class TestConvert{
public static void main(String[] args){
Animal a = new Dog();
// 当 a 引用中存储的对象类型确实为Dog时,再进行类型转换,进而调用Dog中的独有方法。
if (a instanceof Dog){
Dog dog = (Dog)a;
dog.eat();
}else if(a instanceof Cat){
Cat cat = (Cat)a;
cat.eat();
}
}
}
12.6.4.2 案例(二)
package com.goshawk.Inheritance_1;
/**
* 父类
*/
public class Animal {
// 品种
String breed;
// 年龄
protected int age;
// 性别
public String sex;
// 吃
public void eat(){
System.out.println("吃饭");
}
// 睡
public void sleep(){
System.out.println("睡觉");
}
}
package com.goshawk.Inheritance_1;
import com.goshawk.Inheritance_1.Animal;
/**
* 鸟类
* */
public class Bird extends Animal {
// 毛色
String furColor;
@Override
public void eat() {
System.out.println("鸟儿开始吃虫子");
}
// 飞
public void fly(){
System.out.println("飞");
}
}
package com.goshawk.Inheritance_1;
import com.goshawk.Inheritance_1.Animal;
/**
* 狗类
* */
public class Dog extends Animal {
// 毛色
String furColor;
@Override
public void eat(){
System.out.println("狗狗开始吃狗粮");
}
public void run(){
System.out.println("跑");
}
}
package com.goshawk.Inheritance_1;
/**
* 主人类
*/
public class Master {
String name;
/**
* 喂狗
* @param dog
*/
public void feed(Dog dog){
System.out.println(this.name + "喂食");
dog.eat();
}
/**
* 喂鸟
*/
public void feed(Bird bird){
System.out.println(this.name + "喂食");
bird.eat();
}
/**
* 使用多态优化:喂狗和喂鸟
*/
public void feed(Animal animal){
System.out.println(this.name + "喂食");
animal.eat();
}
/**
* 购买动物
*/
public Animal buy(int type) {
Animal animal = null;
if (type == 1){
animal = new Dog();
}else if(type == 2){
animal = new Bird();
}
return animal;
}
}
package com.goshawk.Inheritance_1;
import java.util.Scanner;
public class TestMaster2 {
public static void main(String[] args) {
System.out.println("=============欢迎来到动物市场=============");
System.out.println("=============1.买狗狗 2. 买鸟儿=============");
System.out.println("请选择");
Scanner input = new Scanner(System.in);
int choice = input.nextInt();
Master master = new Master();
Animal animal = master.buy(choice);
if (animal!=null){
System.out.println("购买成功");
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.run();
} else if (animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.fly();
}
}else {
System.out.println("购买失败");
}
}
}
12.7 多态的综合案例
package com.goshawk.oop;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void dancing(){
System.out.println("跳舞");
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
package com.goshawk.oop;
public class Man extends Person{
@Override
public void dancing() {
System.out.println("男生跳街舞");
}
/**
* 男生独有的方法
*/
public void sporting(){
System.out.println("男生喜欢运动");
}
}
package com.goshawk.oop;
public class Woman extends Person{
@Override
public void dancing() {
System.out.println("女生跳民族舞");
}
public void shopping(){
System.out.println("购物");
}
}
package com.goshawk.oop;
/**
* 多态
* 1、定义
* 一种事物的多种形态
*
* 2、多态出现的场景
* 2.1> 父类的引用指向子类对象的实体 == 也称为向上转型
* - 多态使用的情况下,编译时看等号的左边,运行时看等号的右边。
* - 因此没有办法通过多态来调用到子类独有的方法。
* - 如果一定要调用独有的方法,就需要通过向下转型来实现。
* 2.3> 接口的引用指向接口实现类的实体
*
* 3、向上转型和向下转型(instanceof 向下)
* 3.1> 向上转型:父类的引用指向子类对象的实体
* 3.2> 向下转型:强制把子类的对象(的父类的引用)转换成子类的引用。
* 向下转型可能涉及到数据类型转换的异常,因此在做向下转型之前,先做一次 instanceof 数据类型的判断。
* 4、instanceof的使用(产生一个布尔值的结果)
* 格式:
* 对象 instanceof 类 ==> 对象是否是该类的实体
*/
public class Demo1 {
public static void main(String[] args) {
Man man = new Man();
Woman woman = new Woman();
// 场景一:父类的引用指向指向子类对象的实体
Person person = new Man();
Person person1 = new Woman();
// 男生进入到舞厅跳舞
enter(man);
// 女生进入到舞厅跳舞
enter(woman);
}
/**
* 今天晚上有一个聚会,大家去舞厅跳舞。进入到舞厅的人。如果是男生就跳男生的舞,如果是女生就跳女生的舞。
*/
// public static void enter(Man man){
// man.dancing();
//
// }
//
// public static void enter(Woman woman){
// woman.dancing();
//
// }
// 父类的引用指向了子类对象的实体
// Person person = new Man();
public static void enter(Person person){
person.dancing();
}
}
package com.goshawk.oop;
/**
* 多态
*/
public class Demo2 {
public static void main(String[] args) {
Person person = new Man();
Man man = new Man();
// 实际上执行的是Man对象中的dancing方法
person.dancing();
// 多态使用的情况下,编译时看等号的左边,运行时看等号的右边
// person.sporting(); 这样调用会报错,也就说,编译时找的是Person(),运行时找的是Man()
man.sporting();
}
}
package com.goshawk.oop;
/**
* 多态
*/
public class Demo3 {
public static void main(String[] args) {
// 向上转型 子类对象赋予给父类 == 父类的引用指向子类对象的实体
// 父类类型的变量引用子类类型的实例对象
Person p1 = new Man();
Person p2 = new Woman();
show(p1);
show(p2);
}
// 向下转型 ==> 强制类型转换
// 需要通过关键字(instanceof)先做一次判断
/**
* 如果是person参数是男生的话,就调用sporting方法;如果是女生的话就调用shopping方法
* @param person
*/
public static void show(Person person){
// // person.sporting(); 这里会报错,因为show方法的形参类型是Person,而传进来的实参类型为Man(),所以这里需要进行强转
// Man man = (Man) person; // new Man()
// man.sporting();
// instanceof左边为对象,右边为类;可以理解为 person对象属于Man的实例
if (person instanceof Man){
Man man = (Man) person;
man.sporting();
}else if (person instanceof Woman){
Woman woman = (Woman) person;
woman.shopping();
}
}
}
12.8 多态的总结
- 父类引用指向子类对象,从而产生多种形态。
- 多态的两种应用场景:
- 使用父类作为方法形参,实现多态。
- 使用父类作为方法返回值,实现多态。
- 多态的作用:
- 屏蔽子类间的差异。
- 灵活、耦合度低。
12.9 测试题
- 动物都有吃饭的功能,但是小狗吃的是狗粮,小猫吃的是猫粮,当我们将小猫小狗体貌特征遮盖住的时候,如何判定吃东西的是小猫还是小狗?如果是小猫,就调用小猫独有的粘人功能,如果是小狗,就调用小狗独有的看门功能。
package com.goshawk.oop;
/**
* 多态的测试题
* 动物都有吃饭的功能,但是小狗吃的是狗粮,小猫吃的是猫粮,当我们将小猫小狗体貌特征遮盖住的时候,如何判定吃东西的是小猫还是小狗?
* 如果是小猫,就调用小猫独有的粘人功能,如果是小狗,就调用小狗独有的看门功能。
*/
public class Demo4 {
public static void main(String[] args) {
Animal a1 = new Cat();
Animal a2 = new Dog();
checkAnimal(a1);
checkAnimal(a2);
}
public static void checkAnimal(Animal animal){
if (animal instanceof Cat){
animal.eat();
Cat cat = (Cat) animal;
cat.show();
}
if (animal instanceof Dog){
animal.eat();
Dog dog = (Dog) animal;
dog.watch();
}
}
}
class Animal{
public int eat(){
System.out.println("动物吃饭");
return 0;
}
}
class Cat extends Animal{
@Override
public int eat() {
System.out.println("小猫吃猫粮");
return 1;
}
public void show(){
System.out.println("粘人");
}
}
class Dog extends Animal{
@Override
public int eat() {
System.out.println("小狗吃狗粮");
return 2;
}
public void watch(){
System.out.println("看门");
}
}
- 多态练习二
package com.goshawk.oop;
public class Demo6 {
public static void main(String[] args) {
A a = new B(); // 多态
a.show();
B b = new C();
b.show(); // C()的show()
}
}
class A{
public void show(){
show2();
}
public void show2(){
System.out.println("我");
}
}
class B extends A{
public void show2(){
System.out.println("是");
}
}
class C extends B{
public void show(){
super.show(); // B类的show
}
public void show2(){
System.out.println("谁");
}
}
13、多态成员访问的特点
13.1 成员变量
- 编译看左边(父类),运行看右边(父类)
- 一般情况使用极少,成员变量一般都是私有化的。
package com.goshawk.oop;
public class Demo5 {
public static void main(String[] args) {
Father father = new Son();
System.out.println(father.getName()); // 小明
System.out.println(father.name); // 小红 属性不存在被覆盖
}
}
class Father{
String name = "小红";
public String getName() {
return name;
}
public void setName(String name) {
this.name = this.name;
}
}
class Son extends Father{
String name ="小明";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
13.2 成员方法
- 编译看左边(父类),运行看右边(子类),动态绑定
13.3 静态方法
- 编译看左边(父类),运行看左边(父类)
14、final(最终类、方法、常量)
14.1 定义
final
表示最终的,无法被修改的。
14.2 final
修饰的特点
- 修饰类(最终类),类不能被继承
- 修饰变量(最终变量),变量就编程了常量,只能被赋值一次(初始化的时候赋值)
- 修饰方法(最终方法),方法不能被重写
14.3 final
修饰类
final
修饰类:此类不能被继承。String、Math、System
均为final
修饰的类,不能被继承。
14.3.1 案例
package com.goshawk.finaldemo;
public final class Car {
String brand;
String color;
public void run(){
System.out.println("汽车正在前进");
}
}
14.4 final
修饰方法
final
修饰方法:此方法不能被覆盖(重写),但是可以被继承。
14.4.1 案例
package com.goshawk.finaldemo;
public class Car {
String brand;
String color;
// final方法,最终方法,不能被重写覆盖,但是可以被继承
public final void run(){
System.out.println("汽车正在前进");
}
}
package com.goshawk.finaldemo;
public class SmallCar extends Car {
}
package com.goshawk.finaldemo;
public class TestCar {
public static void main(String[] args) {
SmallCar smallCar = new SmallCar();
smallCar.run();
}
}
14.5 局部常量
final
修饰局部变量 ,则称为局部常量。final
修饰变量:此变量值不能被改变(常量)。final
可以修饰形参。
14.5.1 局部常量的特点
-
基本数据类型,是值不能被改变。
-
引用数据类型,是地址值不能被改变,对象中的属性可以改变。
14.5.2 案例(一)
package com.goshawk.finaldemo;
public class Person {
private String name = "小明";
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
package com.goshawk.finaldemo;
/**
* final修饰引用数据类型的变量,此时该变量的属性值能否改变?可以修改,前提是该对象的该属性没有被final修饰。
*/
public class Demo3 {
public static void main(String[] args) {
final Person person = new Person();
person.setName("小李");
// person = new Person(); 这里不能改
System.out.println(person.getName());
}
}
14.5.3 案例(二)
public class TestFinal{
public static void main(String[] args){
final int num = 10;
// 所有final修饰的变量只能赋值一次,值不允许改变。
// num =20; 错误:无法为最终变量num分配值
}
}
14.6 实例常量
final
修饰实例变量。- 实例常量不再提供默认值,必须手动赋予初始值。
- 赋值时机:显示初始化、构造方法。
- 注意:如果在构造方法中为实例常量赋值,必须保证所有的构造方法都能对其正确赋值。
14.6.1 案例(一)
public class TestFinal{
public static void main(String[] args){
new Student();
}
}
class Student{
final String name; // = "Tom"
public Student(){
// name = "Tom";
}
}
14.6.2 案例(二)
package com.goshawk.finaldemo;
public class Car {
final String brand; // 实例常量,不再提供默认值,必须赋值,并且只能赋值一次。
public Car(){
this.brand = "宝马";
}
public Car(String brand) {
this.brand = brand;
}
String color;
// final方法,最终方法,不能被重写覆盖,但是可以被继承
public final void run(){
System.out.println("汽车正在前进");
}
}
14.7 静态常量
- 静态常量不再提供默认值,必须手动赋予初始值。
- 赋值时机:显示初始化、静态代码块。
14.7.1 案例(一)
public class TestFinal{
public static void main(String[] args){
System.out.println(Student.SCHOOL_NAME);
}
}
class Student{
static final String SCHOOL_NAME = "中心小学";
}
14.7.2 案例(二)
public class Car {
final static String ADDRESS;
static {
ADDRESS = "德国";
}
}
14.8 对象常量
14.8.1 案例
package com.goshawk.finaldemo;
public class Student {
String name;
}
package com.goshawk.finaldemo;
import java.util.Arrays;
public class TestStudent {
public static void main(String[] args) {
// final修饰基本类型
final int num = 20;
// num = 30; final修饰基本类型(值不可改变)
final int[] nums = new int[] {11, 22, 33}; // nums不能再赋值,但是里面的元素是可以改变的
// nums = new int[5];
nums[0] = 100;
System.out.println(Arrays.toString(nums));
final Student s = new Student();
// 这里的s不能再赋值,但是可以s的属性进行赋值
// s = new Student(); final修饰引用类型(地址不可改变)
s.name = "abc";
System.out.println(s.name);
}
}
14.9 final修饰成员变量的初始化时机
- 初始化完毕之前
- 类初始化完成前(静态)
14.10 综合案例(一)
package com.goshawk.finaldemo;
/**
* final修饰类:也称为太监类(没有子孙)。
* 应用场景:这个类不希望再被修改。
* */
public final class A {
private String name;
public void eat(){}
}
package com.goshawk.finaldemo;
/**
*
* final修饰属性
*/
public class B {
// 变量不能再被改变 == 常量
final int a = 10;
public void add(int b){
// a = 20; // 这里会报错,因为不能对常量赋值
int result = this.a + b;
System.out.println(result);
}
}
package com.goshawk.finaldemo;
/**
*
* final修饰属性
*/
public class C {
/**
// 第一种初始化的方式
final int MY_NUM = 10; // 如果不初始化,会报错,在这里即等同赋值
*/
/**
// 第二种初始化的方式
final int MY_NUM_1;
{
MY_NUM_1 = 10;
}
*/
/**
// 第三种初始化的方式
final int MY_NUM_2;
public C(){
MY_NUM_2 = 10;
}
*/
/**
// 第四种初始化的方式
final int MY_NUM_3;
public C(int a){
MY_NUM_3 = a;
}
*/
}
package com.goshawk.finaldemo;
public class D {
final static String MONDAY="星期一";
}
package com.goshawk.finaldemo;
public class E {
public final void eat(){
System.out.println("吃饭");
}
}
package com.goshawk.finaldemo;
public class F extends E{
// 父类E的eat加了final,因此不能被重写
// @Override
// public void eat(){
// System.out.println("吃饭");
// }
}
package com.goshawk.finaldemo;
/**
* final关键字
* 1、定义
* final表示最终的
* 2、final修饰类:也称为太监类(没有子孙)。
* 应用场景:这个类不希望再被修改。
*
* 3、final修饰变量
* 3.1> 此时改变量就是常量,不能再被赋值(改变)。
3.2> 常量必须赋初始化值且只能被赋值一次。
* 比如:
* final int MY_NUM;然后声明一个空参构造器,但是没有对MY_NUM赋值就会报错;
* 3.3> final+static修饰的常量,相当于是类的常量。 可以直接通过"类名.常量"的方式直接调用。
* 4、final修饰方法:方法不能再被重写
* */
public class Demo1 {
public static void main(String[] args) {
System.out.println(D.MONDAY);
}
}
14.11 综合案例(二)
package com.goshawk.finaldemo;
/**
* final练习二
* */
public class Demo2 {
final String name;
{
name = "小明";
}
public void method(){
System.out.println(name);
}
public void method2(final String name){
System.out.println(name);
final int age;
age = 10;
System.out.println(age);
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
demo2.method();
demo2.method2("小赵");
}
}
14.12 final
的总结
final
修饰类:此类不能被继承。final
修饰方法:此方法不能被覆盖。final
修饰变量:此变量值不能被改变(无初始值、只允许赋值一次)。- 局部常量:显示初始化。
- 实例常量:显示初始化、构造方法。
- 静态常量:显示初始化、构造方法。
- 基本类型常量:值不可变。
- 引用类型常量:地址不可变。
15、abstract(抽象类、抽象方法)
15.1 什么是抽象?
15.1.1 定义
-
似是而非的,像却又不是;具备某种对象的特征,但又不完整。
-
抽象类没有具体的代码实现,只是规定了方法的基本属性,然后由子类去实现具体的代码,抽象类主要是为了定义规则而存在的。
15.1.2 生活中的抽象
- 比如:在百度中搜索动物关键字,然而搜索出来的结果都是动物的子类对象,而没有动物对象。
15.1.3 不该被创建的对象
/**
Animal仅是一种会吃会睡的对象,再无其他行为,不够具体、不够完整。
程序是用来模拟现实世界、解决现实问题的;
现实世界中存在的都是 动物 具体的子类对象,并不存在动物对象,所以,Animal不应该被独立创建成对象。
*/
public class TestAbstract{
public static void main(String[] args){
Animal a = new Animal();
}
}
class Animal{
String breed;
int age;
String sex;
public void Animal(){}
public void eat(){
System.out.println("动物在吃东西");
}
public void sleep(){
System.out.println("动物在睡觉");
}
}
- 如何限制这种对象的创建呢?
15.2 抽象类
- 应用:
abstract
修饰类,此类不能new对象。 - 被
abstract
修饰的类,称为抽象类。 - 抽象类意为不够完整的类、不够具体的类,抽象类对象无法独立存在,即不能
new
对象。
public class TestAbstract{
public static void main(String[] args){
// Animal a = new Animal(); 这里的Animal是抽象的,无法实例化。
}
}
abstract class Animal{
String breed;
int age;
String sex;
public void Animal(){}
public void eat(){
System.out.println("动物在吃东西");
}
public void sleep(){
System.out.println("动物在睡觉");
}
}
15.3 特点
- 抽象类和抽象方法必须用
abstract
关键字修饰abstract class 类名{}
abstract 返回值类型 方法名();
- 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类或接口。
- 抽象类不能实例化,必须由子类继承并重写抽象方法来实例化。
- 抽象类的子类
- 要么是抽象类
- 要么重写抽象类中的所有抽象方法(使其成为一个普通类)
15.4 抽象类的作用
- 可以被子类继承,提供共性属性和方法。
- 可以声明为引用,更自然的使用多态。
/**
使用抽象的经验:
抽象父类,可以作为子类的组成部分,依附于子类对象存在,由 父类共性+子类独有 组成完整的子类对象。
*/
public class TestAbstract{
public static void main(String[] args){
Animal a1 = new Dog();
Animal a2 = new Cat();
}
}
abstract class Animal{
public Animal(){}
public void eat(){
System.out.println("动物在吃东西")
}
public void sleep(){
System.out.println("动物在睡觉")
}
}
class Dog extends Animal{}
class Cat extends Animal{}
15.5 案例(一)
package com.goshawk.abstractdemo;
public abstract class Person {
public abstract void eat();
}
package com.goshawk.abstractdemo;
public class Teacher extends Person{
@Override
public void eat() {
System.out.println("在一个优雅的环境下吃饭");
}
}
package com.goshawk.abstractdemo;
public class Student extends Person{
@Override
public void eat() {
System.out.println("吃有营养的东西");
}
}
package com.goshawk.abstractdemo;
public class Worker extends Person{
// 如果子类继承了Person(),就必须要重写这个抽象方法,或者本身也是一个抽象类
@Override
public void eat() {
}
}
package com.goshawk.abstractdemo;
public abstract class Programmer extends Person{
}
package com.goshawk.abstractdemo;
public abstract class Animal {
private String name;
public void eat(){
System.out.println("eat...");
}
public abstract void sleep();
}
package com.goshawk.abstractdemo;
/**
* 抽象类
* 1、应用场景
* 我们发现一个父类定义的功能,所有的子类都会去重写,那么父类的这个功能(方法)的内容就没有什么实际意义了。
* 但是这个功能又必须有(内容没意义,但功能必须在)。
* 于是就把父类中的这个方法定义成抽象方法。抽象方法没有方法体,由子类去做具体的实现。
* 比如:
* Worker类继承Person;
* ----> 父类既定义了规则,又提升了开发的效率。
*/
public class Demo1 {
public static void main(String[] args) {
Teacher t1 = new Teacher();
t1.eat();
Worker worker = new Worker();
worker.eat();
}
}
package com.goshawk.abstractdemo;
/**
* abstract(抽象的)
* 1、抽象方法
* 使用abstract关键字修饰的方法
* 格式:
* 权限修饰符 abstract 返回值类型 方法名(参数列表);
* # 注意:抽象方法没有方法体
*
* 2、抽象类
* 有抽象方法的类称为抽象类。没有抽象方法的类也可以是抽象类。
* 抽象类和抽象方法的具体逻辑:
* 2.1> 抽象类不能被实例化(也就是不能创建类的对象)
* 2.2> 有抽象方法所在的类一定是抽象类。
* 2.3> 子类继承父类,如果子类要想被实例化,那么子类中的抽象方法必须全部被重写,否则子类也只能是抽象类。
* 2.4> 抽象类可以没有任何抽象方法。
*/
public class Demo2 {
public static void main(String[] args) {
}
}
15.6 案例(二)
package com.goshawk.abstractdemo;
public abstract class A {
public abstract void method1();
}
package com.goshawk.abstractdemo;
public abstract class B extends A{
public abstract void method2();
}
package com.goshawk.abstractdemo;
public abstract class C extends A{
public abstract void method3();
}
package com.goshawk.abstractdemo;
public class D extends C{
// 重写所有的抽象方法
public void method1(){};
public void method2(){};
public void method3(){};
}
15.7 不该被实现的方法
/**
需求:
Dog中的eat()应输出:狗在吃骨头
Cat中的eat()应输出:猫在吃鱼
*/
abstract class Animal{
public void eat(){
System.out.println("动物在吃东西");
}
public void sleep(){
System.out.println("动物在睡觉");
}
}
clas Dog extends Animal{}
class Cat extends Animal{}
- 父类提供的方法很难满足子类不同需求,如果不定义,则表示所有动物都不会吃、睡。
如果定义,略显多余,多数会被子类覆盖。 - 方法声明必要,方法实现多余。这时怎么办呢?
15.8 抽象方法
- 被
abstract
修饰的方法,称为抽象方法,只有方法声明,没有方法实现({}
的部分)。意为不完整的方法,必须包含在抽象类中。 - 产生继承关系后,子类必须重写父类中所有的抽象方法,否则子类还是抽象类。
abstract class Animal{
public abstract void eat();
public void sleep(){
System.out.println("动物在睡觉");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗在吃骨头");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫在吃鱼");
}
}
15.9 抽象类成员方法的特点
- 抽象方法,强制要求子类做的事情。
- 非抽象方法,子类继承的方法,提高代码的复用性。
15.10 优点
- 强制子类实现父类的方法功能。
- 提高代码的扩展性,便于后期的维护
- 形成一套规范(重点)
15.11 总结
abstract
修饰类:不能new
对象,但可以声明引用。abstract
修饰方法:只有方法声明,没有方法实现。(需包含在抽象类中)- 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类。
- 子类继承抽象类后,必须重写父类中所有的抽象方法,否则子类还是抽象类。
15.12 案例
package com.goshawk.abs2;
/**
* 交通工具类
*/
public abstract class Vehicle {
private String brand;
public Vehicle() {
}
public Vehicle(String brand) {
super();
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public abstract void run();
}
package com.goshawk.abs2;
public class Car extends Vehicle{
public Car() {
}
public Car(String brand) {
super(brand);
}
@Override
public void run() {
System.out.println(super.getBrand()+"牌的汽车正在前进");
}
}
package com.goshawk.abs2;
public class Bike extends Vehicle{
public Bike() {
}
public Bike(String brand) {
super(brand);
}
@Override
public void run() {
System.out.println(super.getBrand()+"牌的自行车正在前进");
}
}
package com.goshawk.abs2;
public class Master {
private String name;
public Master() {
}
public Master(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void goHome(Vehicle vehicle){
System.out.println(this.name+"下班回家了");
vehicle.run();
}
}
package com.goshawk.abs2;
public class TestMaster {
public static void main(String[] args) {
Master xiaoming = new Master("小明");
Vehicle car = new Car("奔驰");
xiaoming.goHome(car);
Vehicle yongjiu = new Bike("永久");
xiaoming.goHome(yongjiu);
}
}
15.13 测试题(一)
- 汽车工厂已经将车的主体完成,汽车已经可以开动了,后期需要根据不同的用途安装不同的部件,货车需要安装货箱,用来拉货,客车需要安装座椅,用来拉人。
package com.goshawk.abstractdemo;
/**
* 抽象的练习
* 汽车工厂已经将车的主体完成,汽车已经可以开动了,
* 后期需要根据不同的用途安装不同的部件,
* 货车需要安装货箱,用来拉货,客车需要安装座椅,用来拉人。
*/
public class Demo3 {
}
abstract class Car{
/**
* 安装部件
*/
public abstract void setParts();
}
class Truck extends Car{
/**
* 货车
*/
@Override
public void setParts() {
System.out.println("安装货箱");
}
}
class PassengerCar extends Car{
/**
* 客车
*/
@Override
public void setParts() {
System.out.println("安装座椅");
}
}
15.14 测试题(二)
- 具体事物:猫、狗
- 共性:姓名、年龄、吃饭(抽象类中成员变量的使用和普通类是一样的)
- 猫的特性:抓老鼠
- 狗的特性:看家
16、接口
16.1 什么是接口?
-
接口其实就是抽象类的升级版,接口里面都是抽象方法。
-
从狭义上来讲,接口就是指
java
中的interface
。 -
从广义上来讲,就是对外提供规则的都是接口。
-
微观概念:接口是一种能力。
- 接口的定义:代表了某种能力。
- 方法的定义:能力的具体要求。
-
宏观概念:接口是一种标准(标准的规范)。
- 举个例子:
- 接口的实现者:U盘、USB风扇、USB台灯、手机充电线
- 接口/标准:USB
- 接口的使用者:笔记本电脑
- 举个例子:
-
经验:
Java
为单继承,当父类的方法种类无法满足子类需求时,可实现接口扩充子类能力。 -
接口支持多实现,可为类扩充多种能力。
16.2 接口的语法
- 接口相当于特殊的抽象类,定义方式、组成部分与抽象类类似。
16.2.1 案例(一)
package com.goshawk.interfacedemo;
interface MyInterface{
// 公开的静态常量
public static final String FIELD = "value";
String NAME = "接口"; // 不显示加 public static final 则默认
// 公开的抽象方法
public abstract void method();
void method2(); // 不显示加public abstract 则默认公开的抽象方法
}
package com.goshawk.interfacedemo;
public class TestMyInterface {
public static void main(String[] args) {
MyInterface myInterface; // 使用接口创建一个变量
}
}
- 使用
interface
关键字定义接口 - 没有构造方法,不能创建对象
- 只能包含:公开静态常量、公开抽象方法
16.2.2 案例(二)
package com.goshawk.interfacedemo;
interface MyInterface{
// 公开的静态常量
public static final String FIELD = "value";
String NAME = "接口"; // 不显示加 public static final 则默认
// 公开的抽象方法
// public abstract void method();
void method2(); // 不显示加public abstract 则默认公开的抽象方法
}
package com.goshawk.interfacedemo;
public class Impl implements MyInterface{
/**
* 类实现接口
*/
@Override
public void method2() {
System.out.println("method");
}
}
package com.goshawk.interfacedemo;
public class TestMyInterface {
public static void main(String[] args) {
MyInterface myInterface = new Impl();
myInterface.method2();
}
}
16.3 接口的特点
- 接口用关键字
interface
表示interface 接口名{}
- 接口中方法上的
abstract
关键字可以省略
- 类实现接口用
implements
表示class 类名 implements 接口名{}
- 接口不能实例化
- 接口按照多态的方法实例化
- 接口的子类
- 可以是抽象类,但意义不大
- 可以具体类,要重写接口中的所有抽象方法(主要使用)
16.4 接口与抽象类的区别
16.4.1 相同点
- 可编译成字节码文件
- 不能创建对象
- 可以作为引用类型。
- 具备
Object
类中所定义的方法
16.4.2 不同点
- 所有属性都是公开静态常量,隐式使用
public static final
修饰。 - 所有方法都是公开抽象方法,隐式使用
public abstract
修饰。 - 没有构造方法、动态代码块、静态代码块。
16.5 接口的应用
- 接口可以在一个类继承别的父类后,如果父类不满足当前的需求,可以通过接口的形式添加方法,进行功能的扩充。
16.5.1 接口表示能力
接口允许多实现,一个类可以具备多个能力,同时实现多个父接口,若实现多个父接口,子类(普通类),必须覆写所有的抽象方法。
package com.goshawk.interface_1;
/**
* 飞行能力
*/
public interface Flyable {
void fly();
}
package com.goshawk.interface_1;
/**
* 喷火能力
*/
public interface Fireable {
void fire();
}
package com.goshawk.interface_1;
/**
* 接口支持多实现,可为类扩充多种能力
*/
public class Person implements Flyable, Fireable{
String name;
int age;
public Person(){
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃东西");
}
public void sleep(){
System.out.println("睡觉");
}
@Override
public void fly() {
System.out.println(name + "开始飞");
}
@Override
public void fire() {
System.out.println(name + "开始喷火");
}
}
package com.goshawk.interface_1;
public class TestPerson {
public static void main(String[] args) {
Person xiaoming = new Person("小明", 20);
xiaoming.fly();
System.out.println("_________多态_________");
Flyable flyable = new Person("小赵", 21);
flyable.fly();
Fireable fireable = new Person();
fireable.fire();
}
}
16.5.2 接口表示标准
- USB案例
USB接口表示一种规范,鼠标、风扇、U盘等等这些外接设备都属于USB接口的子类,我们在日常生活中使用电脑的时候,我们并不关心到底哪个具体设备插入了我们的电脑,只要这个设备满足了USB接口,就都能被电脑识别并使用。就是说,一个接口,可以接受无数种设备,只要这个设备满足USB接口,都可以插入电脑并被电脑识别。兼容所有的USB子类对象。
package com.goshawk.interface_4;
public interface Usb {
void service();
}
package com.goshawk.interface_4;
public class Upan implements Usb{
@Override
public void service() {
System.out.println("U盘连接成功,开始工作");
}
}
package com.goshawk.interface_4;
public class Fan implements Usb{
@Override
public void service() {
System.out.println("风扇连接电脑成功,开始工作");
}
}
package com.goshawk.interface_4;
public class Mouse implements Usb{
@Override
public void service() {
System.out.println("鼠标连接成功,开始工作");
}
}
package com.goshawk.interface_4;
public class Computer {
Usb usb1;
Usb usb2;
Usb usb3;
public void run(){
System.out.println("电脑开始工作");
if (usb1!=null){
usb1.service();
}
if (usb2!=null){
usb2.service();
}
if (usb3!=null){
usb3.service();
}
}
}
package com.goshawk.interface_4;
public class TestComputer {
public static void main(String[] args) {
Computer lenovo = new Computer();
Usb mouse = new Mouse();
Usb fan = new Fan();
Usb upan = new Upan();
// 把usb连接到电脑上
lenovo.usb1 = mouse;
lenovo.usb2 = fan;
lenovo.usb3 = upan;
lenovo.run();
}
}
16.6 接口的规范
- 任何类在实现接口时,必须实现接口中所有的抽象方法,否则此类为抽象类。
- 实现接口中的抽象方法时,访问修饰符必须是
public
。
16.7 接口的引用
- 同父类一样,接口也可声明为引用,并指向实现类对象。
- 注意:
- 仅可调用接口中所声明的方法,不可调用实现类中独有的方法。
- 可强转回实现类本身类型,进行独有方法的调用。
- 通过接口可调用
Object
中的公开方法(特殊)。
16.8 接口的多态
package com.goshawk.interface_2;
public abstract class Animal {
// 父类方法
public void eat(){
System.out.println("吃");
}
// 父类方法
public void sleep(){
System.out.println("睡觉");
}
}
package com.goshawk.interface_2;
public interface Runnable {
void run();
}
package com.goshawk.interface_2;
public interface Swimmable {
void swim();
}
package com.goshawk.interface_2;
/**
* 注意:继承在前面,实现在后面
*/
public class Dog extends Animal implements Runnable, Swimmable{
// 独有方法
public void shout(){
System.out.println("狗开始叫");
}
// 接口方法
@Override
public void run() {
System.out.println("狗在跑");
}
// 接口方法
@Override
public void swim() {
System.out.println("狗在游泳");
}
}
package com.goshawk.interface_2;
public class TestDog {
public static void main(String[] args) {
/**
多种不同类型的引用指向同一个对象时,表示看待对象的视角不同
Dog dog:将狗当狗看
Animal a:将狗当动物看
Runnable r:将狗当会跑的东西看
Swimmable s:将狗当会游的东西看
不同引用所能看到的对象范围不同,只能调用自身类型中所声明的部分。
*/
Dog dog = new Dog();
Animal a = dog;
Runnable r = dog;
Swimmable s = dog;
}
}
16.9 常见关系
-
类与类:
- 单继承
extends 父类名称
-
类与接口:
- 多实现
implements 接口名称1, 接口名称2, 接口名称n
-
接口与接口:
- 多继承
extends 父类接口1, 父类接口2, 父类接口n
package com.goshawk.interface_2; import java.io.Serializable; public interface Runnable extends Swimmable, Serializable, Cloneable { void run(); }
16.10 常量接口
- 常量接口将多个常用于表示状态或固定值的变量,以静态常量的形式定义在接口中统一管理,提高代码可读性。
16.10.1 案例
package com.goshawk.interface_3;
/**
* 常量接口
*/
public interface ConstInterface {
String CONST1 = "aaa";
String CONST2 = "bbb";
String CONST3 = "ccc";
}
package com.goshawk.interface_3;
public class TestConstInterface {
public static void main(String[] args) {
if (ConstInterface.CONST1.equals("aaa")){
System.out.println("COUNT1");
}
}
}
16.11 标记接口
- 标记接口中没有包含任意成员,仅仅用作标记。
Serializable
(可序列化)Cloneable
(可克隆的)
16.12 接口回调
16.13 综合案例
16.13.1 接口的定义、格式、以及内容
package com.goshawk.interfacedemo;
//public abstract class Person {
// public abstract void eat();
// public abstract void sleep();
// public abstract void walk();
//}
// ---->
// 定义一个规则
public interface Person {
// 等同于 public static final String MALE = "男性";
String MALE = "男性";
String FEMALE = "女性";
// 等同于 public abstract void eat();
void eat();
void sleep();
void walk();
}
package com.goshawk.interfacedemo;
public class Teacher implements Person{
@Override
public void eat() {
}
@Override
public void sleep() {
}
@Override
public void walk() {
}
}
package com.goshawk.interfacedemo;
/**
* 接口:
* 1、定义
* 接口是一个极端的抽象类,因为接口中只有抽象方法和全局常量(静态的常量)。
* 抽象类Person的eat方法是一个抽象方法,定义的是一种规则,如果一个类中全部都只是规则,没有其他的普通的属性、普通的方法等,
* 那么这个类就可以直接用接口来描述。
*
* 2、接口使用的格式
* 类的class改用interface即可。
*
* 3、接口中的内容
* 3.1> 抽象方法:方法前默认使用public abstract
* 3.2> 全局静态常量:属性前默认使用public static final修饰
*
*
*/
public class Demo1 {
public static void main(String[] args) {
System.out.println(Person.MALE);
}
}
interface A{}
16.13.2 接口的使用
package com.goshawk.interfacedemo;
public interface Animal {
String show();
}
package com.goshawk.interfacedemo;
// implements ==> 实现某一种功能/规则
public class Cat implements Animal{
@Override
public String show() {
return null;
}
}
package com.goshawk.interfacedemo;
/**
* 接口的使用
* implements 一个类去实现一个接口
* 如果一个类没有重写接口的所有抽象方法,该类必须是抽象类。
*
* public class Cat implements Animal
*
*
*/
public class Demo2 {
}
16.14 接口的意义(优点)
- 程序的耦合度降低。
- 耦合度:程序与程序之间的紧密程度。
- 更自然的使用多态。
- 设计与实现完全分离。
- 更容易搭建程序框架。
- 更容易更换具体实现。
16.15 接口的总结
- 什么是接口
- 微观:接口是一种能力和约定。
- 宏观:接口是一种标准。
- 接口与类的区别
- 没有构造方法,仅可定义公开静态常量与公开抽象方法。
- 接口的应用
Java
为单继承,当父类的方法种类无法满足子类需求时,可实现接口扩充子类能力。
- 接口的规范
- 任何类在实现接口时,必须实现接口中所有的抽象方法,否则此类为抽象类。
- 实现接口中的抽象方法时,访问修饰符必须是
public
。
- 什么是常量接口?
- 将多个常用于表示状态或固定值的变量,以静态常量的形式定义在接口中统一管理。
- 什么是接口回调?
- 先有接口的使用者,后有接口的实现者。