Java 面向对象进阶
面向对象知识学习建议
- 多关注语法的基本作用和流程。
- 多进行思考和语法记忆。
- 自信,不要在短期想能做什么?
1. static 关键字
静态关键字
static的作用,修饰成员变量的用法
static关键字的作用
- static是静态的意思,可以修饰成员变量,表示该成员变量只在内存中存储一份,可以被共享访问,修改。
成员变量可以分成两类
- 静态成员变量(有static修饰,属于类,内存中加载一次):常表示如在线人数信息,等需要被共享的信息,可以被共同访问。
public class User{
//静态成员变量
public static String onlineNumber = 160;
}
//访问方式
类名.静态成员变量(推荐)
对象.静态成员变量(不推荐)
- 实例成员变量(无static修饰,存在于每一个对象中):常表示姓名name,年龄age,等属于每一个对象的信息。
public class User{
//静态成员变量
public static String onlineNumber = 160;
//实例成员变量
private String name;
private int age;
}
//实例成员变量的调用方式
对象.成员变量
static修饰成员变量的内存原理
static修饰成员方法的基本用法
成员方法的分类
- 静态成员方法(有static修饰,属于类),建议用类名访问,也可以用对象访问
- 实例成员方法(无static修饰,属于对象),只能用对象触发访问。
使用场景
- 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须声明成实例方法
- 如果该方法是以执行一个通用功能为目的的,或者需要方便访问,则可以申明成静态方法。
public class Student {
private String name;
private int age;
/*
* 实例方法:无static修饰,属于对象的,通常表示对象自己的行为,可以访问对象的成员变量
*
* */
public void study(){
System.out.println(name + "在好好学习!");
}
/**
* 静态方法,有static修饰,属于类,可以被类和对象共享访问。
*
* */
public static void getMax(int a, int b){
System.out.println(a>b?a:b);
}
public static void main(String[] args) {
//1.类名.静态方法
Student.getMax(3, 5);
//注意:同一个类中访问静态成员,可以省略类名不写
getMax(3,4);
//2.对象.实例方法
//study();会报错,虽然study()实在类中定义的,但它属于对象的行为,无法直接调用
Student student = new Student();
student.name = "智者";
student.study(); //输出:智者在好好学习!
//不推荐使用方式
student.getMax(1,2);
}
}
static修饰成员方法的内存原理
static实际应用案例:设计工具类
工具类:
- 对于一些应用程序中多次需要用到的功能,可以将这些功能封装成静态方法,放在一个类中,这个类就是工具类。
- 工具类的作用:一是方便调用,二是提高了代码复用
工具类原理和延申
- 一次编写,处处可用
- 建议将工具类的构造器私有,不让工具类对外产生对象。
//工具类
import java.util.Random;
public class Tools {
//构造器私有化,工具类不需要产生对象,直接可以通过类进行调用,产生对象反而会占用内存
private Tools(){
}
//定义创建验证码的静态方法
public static String crateCode(int numbner){
//定义变量存储验证码
String verifyCode = "";
//创建随机数对象
Random r = new Random();
String code = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
//生成验证码
for (int i = 0; i < numbner; i++) {
int index = r.nextInt(code.length());
verifyCode += code.charAt(index);
}
return verifyCode;
}
}
//测试类
/**
* 该类主要用于测试工具类中的静态方法的作用
* */
public class LogIn {
public static void main(String[] args) {
//验证码
System.out.println("验证码" + Tools.crateCode(5) );
}
}
具体案例:
/**
* 工具类案例:
* 需求:在实际开发中,经常会遇到一些数组使用的工具类。请按照如下要求编写一个数组的工具类:ArraysUtils
* 1.我们知道数组对象直接输出的时候是输出对象的地址,而项目中很多地方都需要返回数组的内容,请在
* ArraysUtils中提供一个工具类方法toString,用于返回整数数组的内容,返回的字符串格式如:[10,20,50,34,100](只考虑整数数组,且只考虑一维数组)
* 2.经常需要统计平均值,平均值为去掉最低分和最高分后的分值,请提供这样一个工具方法getAverage,用于返回平均分(只考虑浮点型数组,且只考虑一维数组)
* 3.定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果
*
*
* */
//工具类
public class ArraysUtils {
//构造器私有化
private ArraysUtils(){
}
//定义工具类方法:用于返回整数数组的内容,返回的字符串格式如:[10,20,50,34,100](只考虑整数数组,且只考虑一维数组)
public static String toString(int[] array){
//定义返回值变量
String str = "[";
for (int i = 0; i < array.length; i++) {
str += (i == array.length-1?array[i]:array[i]+",");
}
str += "]";
return str;
}
//定义工具类方法,用于返回平均分
public static double getAverage(double[] array){
//定义变量存储平均值
double avg = 0;
//定义变量存储数组中的最大值
double max = array[0];
//定义变量存储数组中的最小值
double min = array[0];
//定义变量存储总和
double sum = 0;
//获取最值,总和
//判断数组的长度是否大于2,以保证存在最高分与最低分
if(array.length>2){
for (int i = 0; i < array.length; i++) {
max = max>array[i]?max:array[i];
min = min<array[i]?min:array[i];
sum += array[i];
}
//获取平均分
avg = (sum-max-min)/(array.length-2);
}else{
for (int i = 0; i < array.length; i++) {
sum += array[i];
}
avg = sum/array.length;
}
return avg;
}
}
/**
* 工具测试类
*
* */
public class TestDemo {
public static void main(String[] args) {
//定义两组数组,分别为整型和浮点型
int[] arr = new int[]{1, 2, 4, 5, 3};
double[] score = new double[]{98.2, 99.2, 89.5, 87.5};
System.out.println("arr:" + ArraysUtils.toString(arr));
System.out.println("该选手的平均分为:" + ArraysUtils.getAverage(score));
}
}
static的注意事项总结(面试热点)
- 静态方法只能访问静态成员,不可以直接访问实例成员
- 实例方法可以访问静态成员,也可以访问实例成员。
- 静态方法中是不可以出现this关键字
2. static 应用知识:代码块
代码块概述
- 代码块是类的五大成分之一(成员变量,构造器,方法,代码块,内部类),定义在类中方法外。
- 在Java类下,使用{}括起来的代码成为代码块
代码块分为
-
静态代码块:
-
格式:static{};
-
特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发,只执行一次
-
使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
-
public class TestDemo{ //定义静态变量 public static schoolName; //定义主函数 public static void main(String[] args){ System.out.println("执行主函数") } //定义静态代码块 static{ //对静态成员变量进行初始化 schoolName = "university"; System.out.println("执行静态代码块"); System.out.println(schoolName); } } //结果 执行静态代码块 university 执行主函数
-
-
构造代码块(了解,用的少):
- 格式:{}
- 特点:每次创建对象,调用构造器执行时,都会执行改代码块中的代码,并且在构造器执行前执行
- 使用场景:初始化实例资源
public class TestDemo{
//定义无参构造函数
public TstDemo(){
System.out.println("构造器被触发执行");
}
/*
定义构造代码块
*/
{
System.out.println("构造代码块被触发执行");
}
public static void main(String[] args){
System.out.println("主函数被调用执行")
new TestDemo(); //创建匿名对象
}
//结果
主函数被调用执行
构造代码块被触发执行
构造器被触发执行
案例:斗地主游戏
案例:斗地主游戏
需求:在游戏开始阶段准备一副扑克牌
分析:
1.定义字符串集合存储扑克牌
2.利用静态代码块,在游戏开始前,输出扑克牌
import java.util.ArrayList;
/**
* 案例:斗地主游戏
* 需求:在游戏开始阶段准备一副扑克牌
* 分析:
* 1.定义字符串集合存储扑克牌
* 2.利用静态代码块,在游戏开始前,输出扑克牌
* */
public class Test1 {
//成员方法
public static ArrayList<String> array = new ArrayList<>();
//无参构造方法
public Test1(){}
//利用静态代码块将扑克牌存储到集合中
static{
String[] arr1 = new String[]{"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
String[] arr2 = new String[]{"♥", "♠", "♦", "♣"};
//产生每张牌的四种花色
for (int i = 0; i < arr1.length; i++) {
for (int i1 = 0; i1 < arr2.length; i1++) {
Test1.array.add(arr2[i1] + arr1[i]);
}
}
//在扑克牌中添加大小王
array.add("大🃏");
array.add("小🃏");
//输出扑克牌
System.out.println(array);
}
public static void main(String[] args) {
System.out.println("开始游戏");
}
}
3. 设计模式:单列
什么是设计模式
- 设计模式是一套被前人反复使用,多=多数人知晓,经过分类编目的代码设计经验的总结,后来者可以直接拿来解决问题。
- 设计模式是软件设计中的常见解决方案,好的设计模式可以进一步的提高代码的重用性。
单例模式
- 可以保证系统中,应用该模式的类永远只有一个实例,即一个类永远只能创建一个对象。
单例的场景和作用
- 例如任务管理器对象我们只需要一个就可以解决问题,这样可以节省内存空间。
单例的实现方式
- 饿汉单例
- 懒汉单例
- …
饿汉单例
- 在用类获取对象的时候,对象已经提前为你创建好了。
设计步骤
- 定义一个类,把构造器私有化
- 定义一个静态变量存储一个对象
//单例模式
public class SingleInstance {
//构造器私有化
private SingleInstance(){}
//创建一个静态的对象
public static SingleInstance instance = new SingleInstance();
}
//单例模式测试类
public class SingleInstanceDemo {
public static void main(String[] args) {
SingleInstance s1 = SingleInstance.instance;
SingleInstance s2 = SingleInstance.instance;
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
}
}
懒汉单例模式
- 在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
设计步骤:
- 定义一个类,把构造器私有化
- 定义一个存储对象的静态变量(不初始化)
- 提供一个返回单例对象的方法
//懒汉单例
public class SingleInstance2 {
//1.构造器私有化,防止用户创建多个对象
private SingleInstance2(){}
//2.定义静态变量存储对象(不初始化)
private static SingleInstance2 instance2;
//定义方法创建对象
public static SingleInstance2 getInstance(){
if(instance2 == null){
instance2 = new SingleInstance2();
}
return instance2;
}
}
//懒汉单例模式测试类
public class SingleInstance2Demo {
public static void main(String[] args) {
SingleInstance2 s1 = SingleInstance2.getInstance();
SingleInstance2 s2 = SingleInstance2.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
}
}
4.面向对象三大特征:继承
什么是继承
- 继承是类与类之间的一种关系
- 多个类继承单独的某个类,多个类就可使用单独的类的属性和行为
- 多个类称为子类(派生类),单独的这个类称为父类(基类或超类)。
继承的格式
- 在java中,继承用关键字"extends".
示例:
//人类
public class People {
//成员变量
private String name;
private int age;
//无参构造器
public People() {
}
//有参构造器
public People(String name, int age) {
this.name = name;
this.age = age;
}
//成员方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//学生类
public class Student1 extends People {
//构造方法
public Student1() {
}
public Student1(String name, int age) {
super(name, age);
}
//定义学生类自己的方法
public void study(){
System.out.println("学生正在好好学习!");
}
}
//老师类
public class Teacher extends People{
//构造器
public Teacher() {
}
public Teacher(String name, int age) {
super(name, age);
}
//定义老师类的方法
public void teach(){
System.out.println("老师正在快乐的交java");
}
}
总结:
- 什么是继承,继承的好处
- 继承是子类到父类的一种关系
- 提高代码的复用性,减少代码冗余,增强类的功能扩展性
- 继承的格式
- 子类 extends 父类
- 继承后子类的特点
- 子类继承父类,子列可以得到父类的属性与行为,子类可以使用。
- 在Java中子类更强大
继承的规范
- 子类相同特征(共同属性,共同方法)放在父类中定义,子类独有的属性和行为应定义在子类自己里面。
为什么?
- 如果子类的独有属性,行为定义在父类,会导致其他的子类也会得到这些属性和行为,这不符合面向对象逻辑。
继承的内存原理
继承的特点
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器
- Java是单继承模式:一个类只能继承一个直接父类。
- Java不支持多继承,但支持多层继承
- 在Java中所有的类都是Object类的子类
子类是否可以继承父类的静态成员
- 有争议的知识点
- 子类可以直接使用父类的静态成员(共享)
- 单个人认为:子类不能继承父类的静态成员。(共享非继承)
在子类方法中访问成员(成员变量,成员方法)满足:就近原则
- 先在子类局部范围找
- 然后子类成员范围找
- 最后父类成员范围找,如果父类范围还没有找到则报错
继承中的方法重写
什么是方法重写?
- 在继承体系中,子类出现了和父类中一摸一样的方法声明,我们就称子类这个方法是重写的方法。
方法重写的应用场景
- 当子类需要父类的功能,但父类的该功能不完全满足自己的需求时
- 子类可以重写父类中的方法
@Override重写注解
- @Override是放在重写后的方法上,作为重写是否正确的校验注解。
- 加上该注解后如果重写错误,编译阶段会出现错误提示。
- 建议重写方法都加上@Override注解,代码安全,优雅!
方法重写的注意事项和要求
- 重写方法的名称,形参列表必须与被重写方法的名称和形参列表一致。
- 私有方法不能被重写。
- 子类重写父类方法时,访问权限必须大于或等于父类(暂时了解:缺省<protected<public)
- 子类不能重写父类的静态方法
子类继承父类后构造器的特点:
- 子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己
为什么?
- 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
怎么调用父类构造器的?
- 子类构造器的第一行语句默认都是:super(),不写也存在。
this和super详情
- this:代表本类对象的引用;super:代表父类存储空间的标识。
注意事项
- 子类通过this(…)去调用本类的其他构造器,本类其他构造器会通过super去手动调用父类的构造器,最终还是会调用父类构造器
- 注意:this(…),super(…)都只能放在构造器的第一行,所以二者不能共同存在同一个构造器中。