一、面向对象进阶简介
面向对象知识的学习建议:
- 多关注语法点的基本作用。
- 多进行思考和记忆、练习。
- 要自信,不要在短期想能做什么。
面向对象进阶课程:需要学习哪些语法?
- static关键字:定义的成员变量——name,age属于每个对象的。如果表示共享的信息?如在线人数等。
- 设计模式:单例——有些类只需要一个对象就可以了,如任务管理器对象,如何实现一个类只能对外产生一个对象?
- 面向对象三个特征:继承——系统中很多实体类的属性和行为存在重复代码,如何把这些类信息进行优化,降低代码冗余,提升代码复用?
1.static静态关键字
a.static是什么、修饰成员变量的用法
static是什么?
- static是静态的意思,可以修饰成员变量和成员方法。
- static修饰成员变量表示该成员变量只在内存中存储一份,可以被共享访问、修改。
成员变量可以分为2类:
静态成员变量(有statci修饰,属于类,内存中加载一次):常表示如在线人数信息、等需要被共享的信息,可以被共享访问。
public class User { /* * 在线人数 * 注意:static修饰的成员变量:静态成员变量 在内存中只有一份 * */ public static int onlineNumber = 100; } public class StaticFieldDemo1 { public static void main(String[] args) { // 目标:理解static修饰成员变量的作用和访问特点。 // 1.类名.静态成员变量 (推荐) System.out.println(User.onlineNumber); // 2.对象.静态成员变量 (不推荐) User user = new User(); System.out.println(user.onlineNumber); } }
实例成员变量(无static修饰,存在于每个对象中):常表示姓名name、年龄age、等属于每个对象的信息。
public class User { /* * 在线人数 * 注意:static修饰的成员变量:静态成员变量 在内存中只有一份 * */ public static int onlineNumber = 100; /* * 实例成员变量 无static修饰 属于每个对象的 必须用对象名访问 * */ private String name; private int age; public static void main(String[] args) { User user = new User(); user.name = "张三"; user.age = 21; System.out.println(user.name); System.out.println(user.age); User.onlineNumber++; // 同一个静态成员变量的访问可以省略类名 System.out.println(onlineNumber); } }
总结:
成员变量的分类和访问分别是什么样的?
静态成员变量在(有static修饰,属于类,加载一次,可以被共享访问),访问格式
类名.静态成员变量(推荐) 对象.静态成员变量(不推荐)
实例成员变量(无static修饰,属于对象),访问格式:
对象.实例成员变量
两种成员变量各自在什么情况下定义?
- 静态成员变量:表示在线人数等需要被共享的信息。
b.static修饰成员变量的内存原理
c.static修饰成员方法的基本用法
之前我们定义的方法有的时候有static修饰,有的是没有,有什么不同?
public void run() {
System.out.println(name);
}
public static int getMax(int a, int b) {
return a > b ? a:b;
}
成员方法的分类:
- 静态成员方法(有static修饰,归属于类),建议用类名访问,也可以用对象访问。
- 实例成员方法(无static修饰,归属于对象),只能用对象触发访问。
Student.java
public class Student {
/*
* 实例成员变量:无static修饰 属于对象
* */
private String name;
/*
* 静态成员方法: 有static修饰 归属于类 可以被共享访问 用类名或对象名都可以访问
* */
public static int getMax(int a, int b) {
return a > b ? a : b;
}
/*
* 实例方法:属于对象 只能用对象触发访问
* */
public void study() {
System.out.println(name);
}
public static void main(String[] args) {
// 类名.静态成员方法
System.out.println(Student.getMax(1, 9));
// 注意:同一个类中 访问静态方法 类名可以省略不写
System.out.println(getMax(99, 100));
// 对象.实例方法
Student student = new Student();
student.name = "白子画";
student.study();
// 对象.静态方法 (语法可行 但是不推荐)
System.out.println(student.getMax(9, 10));
}
}
使用场景:
- 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
- 如果该方法是执行一个共用功能为目的,则可以申明成静态方法。
总结:
成员方法的分类和访问分别是什么样的?
静态成员方法(有static修饰,属于类和对象共享)访问格式:
类名.静态成员方法 // 不推荐 对象.静态成员方法
实例成语方法(无static修饰,属于对象)的访问格式:
对象.实例成员方法
每种成员方法的使用场景是怎么样的?
- 表示对象自己的行为,且方法中需要直接访问实例成员,则该方法必须声明成实例方法。
- 如果该方法是以执行一个通用功能为目的,或者需要方便访问,则可以申明成静态方法。
d.static修饰成员方法的内存原理
static修饰成员方法的内存原理
e.static的注意事项
- 静态方法只能访问静态的成员,不可以直接访问实例成员。
- 实例方法可以访问静态的成员,也可以访问实例成员。
- 静态方法中是不可以出现this关键字的。
Test.java
public class Test {
// 静态成员
public static int onlineNumber = 0;
/**
* 静态成员方法
*/
public static void testPrint() {
System.out.println("===testPrint==");
}
// 实例成员
private String name;
/**
* 实例方法
*/
public void run() {
System.out.println(name + "跑得快");
}
/**
* 静态方法只能访问静态成员 不能直接访问实例成员
*/
public static void test() {
// 访问静态成员变量
System.out.println(onlineNumber);
// 访问静态方法
testPrint();
// 静态方法不能直接访问实例成员变量
// System.out.println(name);
// 静态方法不能直接调用实例方法
// run();
// 静态方法中不可以出现this关键字 this只能代表当前对象
// System.out.println(this);
}
/**
* 实例方法可以访问静态成员 也可以访问实例成员
*/
public void go() {
// 访问静态成员变量
System.out.println(onlineNumber);
// 访问静态方法
testPrint();
// 访问实例成员变量
System.out.println(name);
// 调用实例方法
run();
// this关键字可以出现在实例方法中
this.name = "白子画";
System.out.println(this.name);
}
public static void main(String[] args) {
// 目标:理解static 访问相关的语法 面试笔试题 或者以后理解程序很重要的知识。
// 静态方法
test();
// 实例方法
Test testExample = new Test();
testExample.go();
}
}
2.static应用知识:工具类
工具类是什么?
- 类中都是一些静态方法,每个方法都是以完成一个共用的功能为目的,这个类用来给系统开发人员共同使用。
案例导学:
- 在企业的管理系统中,通常需要在一个系统的很多业务处使用验证码进行防刷新等安全控制。
问题:
- 同一个功能多处开发,会出现代码重复度过高。
ToolsClass.java
import java.util.Random;
/**
* 工具类
*/
public class ToolsClass {
/**
* 生成验证码
*/
public static String createVerifyCode(int number) {
// 开发一个验证码
String code = "";
// 定义一个变量记住全部验证码字符
String data = "abcdefghijklmnoptrstuvwxyzABCDEFGHIJKLMNOPTRSTUVWXZY0123456789";
Random random = new Random();
// 随机获取5个字符 组装成的验证码
for (int i = 0; i < number; i++) {
int index = random.nextInt(62);
code += data.charAt(index);
}
return code;
}
}
Login.java
public class Login {
public static void main(String[] args) {
System.out.println(ToolsClass.createVerifyCode(5));
}
}
使用工具类的好处:
- 一是调用方便。
- 二是提高了代码复用(一次编写,处处可用)。
为什么工具类中的方法不用实例方法做?
- 实例方法需要创建对象调用。
- 此时用对象只是为了调用方法,这样只会浪费内存。
工具类定义时的其他要求:
由于工具里面都是静态方法,直接用类名即可访问,因此,工具类无需创建对象,建议将工具类的构造器进行私有。
import java.util.Random; /** * 工具类 */ public class ToolsClass { /** * 构造器私有化 * * 由于工具类无需创建对象 所以把构造器私有化会显得很专业 */ private ToolsClass() { } /** * 生成验证码 */ public static String createVerifyCode(int number) { // 开发一个验证码 String code = ""; // 定义一个变量记住全部验证码字符 String data = "abcdefghijklmnoptrstuvwxyzABCDEFGHIJKLMNOPTRSTUVWXZY0123456789"; Random random = new Random(); // 随机获取5个字符 组装成的验证码 for (int i = 0; i < number; i++) { int index = random.nextInt(62); code += data.charAt(index); } return code; } }
总结:
- 工具类是什么?有什么好处?
- 内部都是一些静态方法,每个方法完成一个功能。
- 一次编写,处处可用,提高代码的重用性。
- 工具类有什么要求?
- 建议工具类的构造器私有化处理。
练习题:定义数组工具类
需求:在实例开发中,经常会遇到一些数组使用的工具类。请按照如下要求编写一个数组的工具类:ArrayTools。
- 我们知道数组对象直接输出的时候是输出对象的地址,而项目中很多地方都需要返回数组的内容,请在ArrayTools类中提供一个工具类方法toString,用于返回整数数组的内容,返回的字符串格式如:[10, 20, 30, 40, 50, 60]。(只考虑整数数组,且只考虑一维数组)
- 经常需要统计平均值,平均值为去掉最低分和最高分的分值,请提供这样一个工具方法getAverage,用于返回平均分。(只考虑浮点型数组,且只考虑一维数组)。
- 定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果。
ArrayTools.java
/**
* 练习:完成数组的工具类的设计
*/
public class ArrayTools {
/**
* 构造器私有化
*/
private ArrayTools() {
}
/**
* 工具类 数组格式化字符串
*
* @param array 数组
* @return 数组格式化字符串
*/
public static String toString(int[] array) {
if (array == null) {
return null;
}
String result = "[";
for (int i = 0; i < array.length; i++) {
result += (i == array.length - 1 ? array[i] : array[i] + ", ");
}
result += "]";
return result;
}
/**
* 工具类 数组平均分
*
* @param array 数组
* @return 数组平均分
*/
public static double getAverage(double[] array) {
if (array == null || array.length == 0) {
return 0.0;
}
double max = 0.0;
double min = 0.0;
double sum = 0.0;
for (double v : array) {
if (max < v) {
max = v;
}
if (min > v) {
min = v;
}
sum += v;
}
return (sum - max - min) / (array.length - 2);
}
}
TestDemo.java
public class TestDemo {
public static void main(String[] args) {
//需求:在实例开发中,经常会遇到一些数组使用的工具类。请按照如下要求编写一个数组的工具类:ArrayTools。
//
//1. 我们知道数组对象直接输出的时候是输出对象的地址,而项目中很多地方都需要返回数组的内容,请在ArrayTools类中提供一个工具类方法toString,用于返回整数数组的内容,返回的字符串格式如:[10, 20, 30, 40, 50, 60]。(只考虑整数数组,且只考虑一维数组)
//2. 经常需要统计平均值,平均值为去掉最低分和最高分的分值,请提供这样一个工具方法getAverage,用于返回平均分。(只考虑浮点型数组,且只考虑一维数组)。
//3. 定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果。
// 数组格式化字符串
int[] array = null;
int[] array1 = {};
int[] array2 = {10, 9, 8, 7, 6};
System.out.println(ArrayTools.toString(array));
System.out.println(ArrayTools.toString(array1));
System.out.println(ArrayTools.toString(array2));
// 数组平均分
double[] scoreArray = null;
double[] scoreArray1 = {};
double[] scoreArray2 = {1, 2, 3, 4, 5};
System.out.println(ArrayTools.getAverage(scoreArray));
System.out.println(ArrayTools.getAverage(scoreArray1));
System.out.println(ArrayTools.getAverage(scoreArray2));
}
}
3.static应用知识:代码块
a.代码块的分类、作用
代码块概述:
- 代码块是类的五大成分之一(成员变量、构造器、方法、代码块、内部类),定义在类中方法外。
- 在Java类下,使用{}括起来的代码被称为代码块。
代码块分为:
静态代码块:
- 格式:static{}
- 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发,只执行一次。
- 使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
public class StaticDemo { public static String schoolName; /** * 静态代码块:有static修饰,属于类,与类一起优先加载一次,有自动触发执行。 * * 作用:初始化静态资源 */ static { System.out.println("-------静态代码块被触发执行-------"); schoolName = "XX大学"; } public static void main(String[] args) { System.out.println("-------main方法执行-------"); } }
构造代码块(实例代码块,属于对象,了解,见的少):
- 格式:{}
- 特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行。
- 使用场景:初始化实例资源。
public class StaticDemo { private double balance; public StaticDemo() { System.out.println("-------无参构造器被触发执行-------"); } /** * 实例代码块:无static修饰,属于对象,每次构建对象是,都会被触发一次执行。 */ { balance = 0; System.out.println("-------实例代码块被触发执行-------"); } public static void main(String[] args) { StaticDemo staticDemo1 = new StaticDemo(); System.out.println(staticDemo1.balance); StaticDemo staticDemo2 = new StaticDemo(); System.out.println(staticDemo2.balance); } }
b.静态代码块的应用案例
案例:斗地主游戏
需求:在启动游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据。
分析:
- 该房间只需要一副牌。
- 定义一个静态的ArrayList集合存储54张牌对象,静态的集合只会加载一份。
- 在启动游戏房间前,应该将54张牌初始化好。
- 当系统启动的同时需要准备好54张牌数据,此时可以用静态代码块完成。
StaticDemo.java
package com.javase.staticdemo4;
import java.util.ArrayList;
public class StaticDemo {
/* 定义一个静态的金河,这样这个集合只加载一次。因为当前房间只需要一副牌 */
public static ArrayList<String> cards = new ArrayList<>();
/* 在程序真正运行main方法前,把54张牌放进去,后续游戏可以直接使用 */
static {
// 正式做牌,放到集合中去。
// 定义一个数组存储全部点数:类型确定、个数确定
String[] sizes = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
String[] colors = {"♥", "♣", "♦", "♠"};
// 组合花色和点数
for (String size : sizes) {
for (String color : colors) {
// 加入到集合中去
cards.add(color + size);
}
}
// 加入大小王
cards.add("大王");
cards.add("小王");
}
public static void main(String[] args) {
// 目标:模拟游戏启动前,初始化54张牌数据
System.out.println(cards);
}
}
总结:
- 静态代码块的作用是什么?
- 如果要在启动系统时对静态资源进行初始化,则建议使用静态代码块完成数据的初始化操作。
4.static应用知识:单例
a.设计模式
什么是设计模式(Design pattern)
- 开发中经常遇到一些问题,一个问题通常有n中解法,但其中肯定有一种解法是最优的,这个最优的解法被人再弄逛街出来了,称之为设计模式。
- 设计模式有20多种,对应20多种软件开发中会遇到的问题。
- 学设计模式主要是学2点:
- 第一:这种模式用来解决什么问题。
- 第二:遇到了这种问题了,这种模式是怎么写的,如何解决这个问题的。
b.单例模式介绍
单例模式:
- 可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象。
- 例如任务管理器对象,我们只需要一个就可以解决问题,这样可以节省内存空间。
单例的实现方式很多:
- 饿汉单例模式。
- 懒汉单例模式。
- …
c.饿汉单例模式
饿汉单例模式
- 在类获取对象的时候,对象已经提前为你创建好了。
/**
* 饿汉单例模式 定义一个单例类
*/
public class SingleInstance {
// 定义一个静态变量存储一个对象即可:属于类 与类一起加载一次
public static SingleInstance instance = new SingleInstance();
private SingleInstance() {
System.out.println("创建了一个对象");
}
}
设计步骤:
- 定义一个类,把构造器私有化。
- 定义一个静态变量存储一个对象。
SingleInstanceDemo1.java
/**
* 使用饿汉单例模式 定义一个单例类
*/
public class SingleInstanceDemo1 {
/* 饿汉单例是在获取对象前 对象已经提前准备好了一个 */
public static SingleInstanceDemo1 singleInstanceDemo1 = new SingleInstanceDemo1();
/**
* 构造器私有化
*/
private SingleInstanceDemo1() {
}
}
TestDemo1.java
public class TestDemo1 {
public static void main(String[] args) {
// 目标:理解饿汉单例的设计步骤
SingleInstanceDemo1 singleInstanceDemo1 = SingleInstanceDemo1.singleInstanceDemo1;
SingleInstanceDemo1 singleInstanceDemo2 = SingleInstanceDemo1.singleInstanceDemo1;
// 判断两个对象是否为同一个
System.out.println(singleInstanceDemo1 == singleInstanceDemo2);
}
}
总结:
- 饿汉单例的实现步骤?
- 定义一个类,把构造器私有化。
- 定义一个静态变量存储一个对象。
b.懒汉单例模式
懒汉单例设计模式
- 在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
/**
* 使用懒汉单例模式 定义一个单例类
*/
public class SingleInstance {
// 定义一个静态变量存储一个对象即可:属于类 与类一起加载一次
private static SingleInstance instance;
// 单例模式构造器必须私有化
private SingleInstance getInstance() {
}
// 必须提供一个方法返回一个单例对象
public static SingleInstance getInstance() {
...
return ...;
}
}
设计步骤:
- 定义一个类,把构造器私有。
- 定义一个静态变量存储一个对象。
- 提供一个返回单例对象的方法。
SingleInstanceDemo2.java
/**
* 使用懒汉单例模式 定义一个单例类
*
* 懒汉单例模式 需要用的时候才创建单例对象
*/
public class SingleInstanceDemo2 {
// 定义一个静态成员变量存储一个对象 只加载一次 只有一份
// 注意:最好私有化 避免给别人挖坑
private static SingleInstanceDemo2 singleInstanceDemo2;
/**
* 构造器私有化
*/
private SingleInstanceDemo2() {
}
/**
* 获取单例对象
*
* @return 单例对象
*/
public static SingleInstanceDemo2 getInstance() {
// 首次获取单例对象
if (singleInstanceDemo2 == null) {
// 赋值为单例对象
singleInstanceDemo2 = new SingleInstanceDemo2();
}
// 返回单例对象
return singleInstanceDemo2;
}
}
TestDemo2.java
public class TestDemo2 {
public static void main(String[] args) {
// 目标:掌握懒汉单例的设计,理解其思想
SingleInstanceDemo2 singleInstance1 = SingleInstanceDemo2.getInstance();
SingleInstanceDemo2 singleInstance2 = SingleInstanceDemo2.getInstance();
System.out.println(singleInstance1);
System.out.println(singleInstance2);
System.out.println(singleInstance1 == singleInstance2);
}
}
/**
* 构造器私有化
*/
private SingleInstanceDemo2() {
}
/**
* 获取单例对象
*
* @return 单例对象
*/
public static SingleInstanceDemo2 getInstance() {
// 首次获取单例对象
if (singleInstanceDemo2 == null) {
// 赋值为单例对象
singleInstanceDemo2 = new SingleInstanceDemo2();
}
// 返回单例对象
return singleInstanceDemo2;
}
}
**TestDemo2.java**
```java
public class TestDemo2 {
public static void main(String[] args) {
// 目标:掌握懒汉单例的设计,理解其思想
SingleInstanceDemo2 singleInstance1 = SingleInstanceDemo2.getInstance();
SingleInstanceDemo2 singleInstance2 = SingleInstanceDemo2.getInstance();
System.out.println(singleInstance1);
System.out.println(singleInstance2);
System.out.println(singleInstance1 == singleInstance2);
}
}