static关键字与继承的使用
笔记大纲:
重点
(1)static修饰成员变量,成员方法,代码块的特点与访问方式
(2)继承的格式与方法重写(成员方法的访问特点)
static关键字的概述与使用方式
static叫做静态关键字。
能做的事情有修饰变量,修饰方法,修饰代码块,修饰导包语句,修饰内部类。
static修饰成员变量的特点
static修饰成员变量的格式
public class 类名 {
权限修饰符 static 数据类型 变量名;
}
static修饰成员变量的访问的格式
//类变量的访问方式: 类名.变量名 √
Java412Student.classroom = "北京JavaEE就业班412期";
//类变量的访问方式: 对象名.变量名 √ (不推荐)
System.out.println(zhangergou.classroom);
特点:
static修饰的成员变量归类进行管理,可以通过类名进行访问与赋值。
所有的该类对象共享静态成员变量(只有一份)
静态变量出现的时机:在类加载的时候就存在了,和对象没有关系!
有没有那么多所有对象共享一个变量的实际情况?
很少,静态变量实际存在的意义更多的是为了静态方法服务!
static修饰成员方法的特点
static修饰成员方法的格式
权限修饰符 static 返回值类型 方法名(形式参数){
方法体;
}
public static void run() {
System.out.println("车速好快~ 已经达到10Km/h! 风驰电掣的感觉!");
}
static修饰成员方法的访问的格式
类名.方法名(); //推荐
对象名.方法名(); //不推荐
static修饰成员方法的特点
静态方法归属于类,需要被类名.方法名进行调用!
当类加载的时候就会读取类的组成部分,类的静态方法就可以被调用了,与是否有对象没有必然关系!
static修饰成员方法可以被类名直接调用,而之后的使用场景大部分情况都是基于工具类使用。
工具类就是一个类,里面都是静态方法,每个方法具有独立的功能,使用的时候直接基于工具类名.方法名就可以使用,避免创建对象。
public class ArrayUtil {
//构造私有:防止外界创建对象
private ArrayUtil() {
}
//打印数组内容
public static void printArray(int[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length; i++) {
if (i != arr.length - 1) {
System.out.print(arr[i] + ",");
} else {
System.out.print(arr[i]);
}
}
System.out.print("]");
System.out.println(); //避免和之后打印内容冲突
}
}
public static void main(String[] args) {
int[] arr = new int[]{11, 22, 33, 44};
int[] arr2 = new int[]{100, 200, 300};
//基于数组工具类调用静态方法快速打印数组内容与计算数组和
ArrayUtil.printArray(arr);
ArrayUtil.printArray(arr2);
}
static使用的注意事项
静态内容只可以访问静态内容(静态方法、静态成员)√ 不能访问非静态内容。
原因:静态内容可以使用的时候,类已经加载了,非静态内容的对象不一定存在!
非静态内容可以访问任何内容(静态方法、实例方法、静态成员、实例成员)
原因:非静态内容只能被对象调用,而对象存在表示类已经被加载(此时静态内容已经存在)
非静态内容可以使用this,静态内容不允许使用this
原因:静态内容可以被类可以直接调用,不一定是被对象调用的,所以说this不一定存在!
问题(1)调用静态方法很方便,那么为什么我们不把所有的方法都定义为静态的?
大部分时候对象的属性都是隔离的,都是不一样的!都是实例成员,都定义成静态方法是不允许访问实例成员的!
问题(2)在我们刚接触方法的时候,都会在方法中加static,之后接触类和对象的时候,就基本不加static了
因为学习方法的时候,所有使用的方法都要被main方法调用,主方法是静态的,自己的方法也得是静态的,之后是基于对象调用方法,所以不需要是静态的!
static修饰代码块的特点
static修饰代码块的格式
static {
代码;
}
static修饰代码块的执行时机
当类被加载的时候,默认执行一次(因为类就加载一次)
static修饰代码块的应用场景
加载一些热数据,方便进行访问!
//声明私有(外界不能直接访问 HotNews.newList ×) 静态(为了静态方法可以访问)
private static ArrayList<String> newsList = new ArrayList<>();
static {
newsList.add("女生高考查分××××××××××××××××!");
newsList.add("我爱中国!");
newsList.add("一只可爱的猫猫");
newsList.add("中国美丽乡村!");
newsList.add("歌曲《好运来》");
}
static的应用-单例模式-饿汉式
设计模式指的就是一种编写代码组织代码的方式,一般用于解决某一类具体的问题。
这种编写代码组织代码的方式经过了多人的验证与各种项目的洗礼,总结出最优解!
单例设计模式的思路
(1)构造私有 防止外界基于构造方法创建新的对象
(2)在单例类中维护一个当前类的成员变量并且静态私有化,在类加载的时候就完成赋值
(3)由于(2)的存在,外界不允许直接基于类名.变量名访问,对外提供一个公共的静态方法,用于获取该类对象。
饿汉式单例:当类加载的时候就已经完成唯一对象的初始化(很饥渴很饥饿,上来不管用不用都先初始化了再说)
public class HungrySingleton {
//(1)构造私有化 外界不可以访问 当前类可以访问
private HungrySingleton() {
}
//(2)维护一个静态私有成员变量,类型:当前类的类型 声明的时候完成初始化(当类加载的时候就完成创建赋值) 饿汉式
private static HungrySingleton hungrySingleton = new HungrySingleton();
//(3)对外提供一个公共获取本类唯一对象的静态方法 (静态目的:让外界直接基于静态方法进行本类唯一对象的获取 )
public static HungrySingleton getHungrySingleton(){
return hungrySingleton;
}
}
static的应用-单例模式-懒汉式
饿汉式虽然可以保证单例,但是有一定的弊端,如果单例类构成很复杂(成员变量非常多),就会导致上来就完成初始化会占用内存。
懒汉式的思路:将单例对象的创建延后(第一次使用的时候)
public class LazySingleton {
//(1)构造私有化 外界不可以访问 当前类可以访问
private LazySingleton() {
}
//(2)维护一个静态私有成员变量,类型:当前类的类型 当类加载的时候lazySingleton会开辟空间,但是不会初始化(默认:null)
private static LazySingleton lazySingleton;
//(3)对外提供一个公共获取本类唯一对象的静态方法 (静态目的:让外界直接基于静态方法进行本类唯一对象的获取 )
public static LazySingleton getLazySingleton() {
if (lazySingleton == null) //如果if判断成功说明当前是第一次访问(完成lazySingleton的初始化)
lazySingleton = new LazySingleton();
return lazySingleton;
}
}
补充:面试(请你给我讲一下单例模式是什么,怎么用,了解过吗?)
回答:单例模式是一种设计模式,当需要一个类在全局只有一个对象的时候可以基于这种模式来设计。
懒汉式和饿汉式,区别就在于饿汉式类加载就完成唯一对象的初始化,懒汉式在于什么时候用什么时候初始化。
继承的概念与好处
生活中的继承:子承父业(父亲有的儿子也有 父亲会的儿子也会)
Java中的继承:描述的是类与类之间的关系(父子关系)
好处:提高代码的复用性!
不能为了提高复用性而刻意提高复用性,当两个类满足is…a的关系的时候,可以考虑。
狗是动物的一种,教师是员工的一种,汽车是交通工具的一种!
继承的使用一般分为两种情况:
向上抽取
当已经编写了多个类,发现这多个类都是某种事物的一种,并且有重复内容,将重复内容向抽取为一个父类!
向下拓展
在项目开始的时候分析实际的需求,先定义一个父类将所有的共性内容抽取后,再基于这个父类定义子类。
继承的格式
public class 子类 extends 父类 {
}
子类继承父类,可以使用父类的所有非私有的内容!(父类有,子类就默认有一份)
public class Animal {
String name;
int age;
//吃的方法
public void eat() {
System.out.println("动物可以吃东西~");
}
}
public class Dog extends Animal {
String color; //颜色
public void lookDoor() {
System.out.println("狗可以帮助人们看家护院!");
}
}
public static void main(String[] args) {
//创建狗类对象
Dog dog = new Dog();
//可以继承父类的非私有的内容,就相当于Dog类本身也有name/age/eat()方法以及自己特有的color还有lookDoor();
dog.name = "大黄";
dog.age = 5;
dog.color = "黑色";
dog.eat();
dog.lookDoor();
}
当创建子类对象的时候,并不是由子类一个类完成的所有构建过程,子类的所有父类(继承体系)都会参与子类对象的构建。
子类对象中也都会保存所有继承的继承(私有的也会初始化,只不过不能访问)
当基于子类对象访问内容的时候,子类有访问子类的,子类没有访问父类,父类没有就报错了!
继承中的权限修饰符
权限修饰符:可以用于修饰成员变量,成员方法,构造方法的修饰关键字。
不同的权限修饰符访问范围不一样。
public最大,private最小的,其他的知道有就这个修饰符就行。
权限修饰符从大到小的关系 ppdp => public > protected > default(默认) >private
权限修饰符更多的都是Java核心类库防止使用者随便访问而推出的,实际开发中不会特别关注权限修饰符的使用,要么是public要么是private。
继承的注意事项
(1)Java中类与类是单继承,一个子类只可以有一个父类,不支持多继承,但是可以多层继承。
如果支持多继承,那么多个父类中出现相同声明的方法但逻辑不同,子类无法进行区分!
例如:两个不同的类class a 和 class b ,他们两个有一个相同的方法add(),如果class c 同时继承class a 和 class b ,那么class c在调用add()方法时就会报错,就会纠结使用谁的方法。(通俗的讲,一个孩子只能有一个亲生父亲)。
(2)Java中的任何一个类,都是Object类直接或者间接的子类。Object是根类。
Java的开发者认为,我们在编写任何一个类的时候,都应该默认一些功能,但是Java制作者认为可能开发者不会每写一个类都会将这些功能加进去,所以就定义了Object类,将它认为应该默认携带的功能定义到了Object类中。
继承中的方法重写
方法重写是之后经常会遇到一种情况。
使用场景:当子类继承了父类发现父类的方法不能满足子类要求的时候,子类可以针对于父类的方法进行逻辑的重新编写!
public class iphone4s {
//打电话
public void call(String phoneNumber) {
System.out.println("给" + phoneNumber + "拨打电话!");
}
//发短信
public void sendMessage(String message, String phoneNumber) {
System.out.println("给" + phoneNumber + "发动手机短信,短信内容:" + message);
}
}
public class iPhone14ProMax extends iphone4s {
//发现发短信方法已经无法满足当前类的需求的时候,主动进行方法重写! 在子类声明一个与父类方法声明一致的方法(参数列表,方法名称一样)
@Override //该注解用于验证标记的方法是否是有效的方法重写(没报错√ 报错×)
public void sendMessage(String message, String phoneNumber) {
System.out.println("给" + phoneNumber + "发动手机短信,短信内容:" + message + ",额外携带一个表情和一个语音消息!");
}
}
子类声明一个与父类方法名称一致,参数列表一致的方法,重新编写逻辑。
可以基于在重写的方法上添加@Override注解验证是否是有效的方法重写!
子类重写父类方法的权限修饰符应该大于等于父类方法的权限修饰符,子类重写父类方法的返回值类型要么和父类相同要么是父类返回值的子类!
可以基于在子类中使用快捷键Ctrl + O选择要重写的父类方法快速生成重写格式!
继承中成员方法的访问特点:
当子类调用一个方法的时候,到底访问谁(就近原则),子类有访问子类,子类没有访问父类。
继承中的成员变量访问特点(了解)
实际开发中不会出现子类和父类有同样的成员变量声明的情况(没有任何的意义)
继承中的成员变量访问特点:
(1)就近原则,有局部访问局部,没局部访问子类,没子类访问父类。
public class Fu {
int count = 10;
}
class Zi extends Fu {
int count = 100;
public void showCount() {
int count = 1000;
//在子类中访问成员变量(就近原则 [局部][子成员][父成员])
System.out.println(count); //局部
}
}
(2)如果想要在重名的情况下访问父类的变量,可以基于super关键字来操作。
super.父类成员变量名;
super.父类成员方法名(); //子类重写的方法中想要访问父类方法原有逻辑
继承中的构造方法访问特点(如何生成)
构造方法:基于创建对象的时机用于给对象的成员变量完成初始化的!
继承中的构造方法的目的也是如此,单独的一个类创建对象的时候,所有的成员都是当前类中的成员。
而继承的情况,类创建对象的时候,不是由一个类的成员组成的了,还有父类,爷爷类。
继承中构造访问特点:
(1)子类所有的构造方法,在没有手动声明的情况下,第一行默认会访问父类的无参构造
public Zi() {
super();
System.out.println("Zi类的无参构造执行了!");
}
因为子类对象的创建要有父类的参与(继承的内容需要基于父类的构造方法才可以初始化完毕)
(2)如果父类没有无参,那么super();默认就报错,但是第一行必须调用父类的构造,则必须手动调用父类的有参。
public Zi() {
super(10);
System.out.println("Zi类的无参构造执行了!");
}
目标:在创建子类的过程中既可以完成给子类成员变量的初始化也可以完成继承内容的初始化。
如何使用IDEA生成满足实际要求的构造方法
(1)分析想要在一次创建对象的过程中完成初始化的变量归属于哪些类!
public class Fu {
int a;
String b;
double c;
long d;
}
class Zi extends Fu {
byte e;
short f;
}
例:本次想要完成a,b,e,f这四个内容在构造过程中的赋值初始化!
a和b归Fu,e和f归Zi。
想要给Fu类的a和b初始化,那么Fu类就要有一个构造方法用于给a和b初始化!-> 在Fu类生成一个用于给a,b初始化的构造。
public Fu(int a, String b) {
this.a = a;
this.b = b;
}
想要给Zi类的e和f初始化,那么Zi类就要有一个构造方法用于给e和f初始化!-> 在生成子类构造方法的时候,弹出的选项会先询问第一行默认调用哪个父类的构造方法!
然后再选择要再初始化子类的哪个成员变量!
public Zi(int a, String b, byte e, short f) {
super(a, b);
this.e = e;
this.f = f;
}
继承的完整使用方式
之前怎么写,现在还怎么写!
(1)成员变量私有化,提供GET/SET方法。
(2)提供无参构造,以及满足实际要求的有参构造!
(3)再根据实际情况定义成员!
继承这个特性在之后开发中会使用,但使用的频率要远远低于接口。
继承是对事物的抽象表达(通过一个类描述一个事物)