第10章 静态,最终和单例模式
1. 静态static关键字
static关键字被称为静态关键字,可以修饰属性,方法,代码块(还有内部类)。
被static关键字修饰的内容理解为现实世界中不属于个体,而是属于整个类的。
1.1 静态属性
static属性:又被称为“类属性”或“静态属性”,有一个对象去修改了,其他对象也变化
Student类
public class Student {
public String sname;
public static int count;
}
测试类
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
s1.sname = "赵四";
s1.count = 1;
Student s2 = new Student();
s2.sname = "刘能";
s2.count = 100;
System.out.println(s1.sname);
System.out.println(s1.count);
System.out.println("====================");
System.out.println(s2.sname);
System.out.println(s2.count);
}
}
因为静态属性属于类而不属于某个对象,所以通常使用类名.去调用
s1.count = 1;
s2.count = 100;
应该更改为
Student.count = 1;
Student.count = 100;
image.png
在正常编程工作中,不应该使用对象.方式进行静态调用。但在笔试题中经常以这种面貌出现用于考察答题者对于审题细致程度和静态理解程度。
1.2 静态方法
static方法:属于类,不属于某个对象,也被称为“类方法”
通过类名.xxx()进行调用,static方法和static代码块中,只能直接调用其他的static属性和方法,不能直接调用普通的属性和方法。
Student类
package com.neuedu.test1;
public class Student {
public String sname;
public static int count;
public void say() {
System.out.println("说话");
}
public static void laugh() {
System.out.println("笑");
}
}
测试类
package com.neuedu.test2;
import com.neuedu.test1.Student;
public class Test {
public String tname;
public static int tage;
public void haha() {}
public static void xixi() {}
public static void main(String[] args) {
Student s1 = new Student();
s1.say();
Student.laugh();
System.out.println(tage);
xixi();
System.out.println(tname); //报错!!
haha(); //报错!!
}
}
- 静态方法中,可以通过对象调用其他类的普通方法,也可以通过静态方式调用其他类的静态方法
- 静态方法中,可以直接调用本类内部的静态属性和静态方法,不能直接调用本类内部的非静态属性和非静态方法
- 普通方法中,可以直接调用本类的所有内容(静态和非静态都可以)
在Java中静态内容在初始化时,不能确定非静态内容已经完成初始化,所以不能调用非静态内容。
在Java中非静态内容在初始化时,能够确定类完成加载,静态内容一定初始化完毕
1.3 静态代码块
static代码块: 在类第一次加载时,执行并在整个程序运行周期中只执行这一次。
类加载的三个时机
- 当new了某个类的对象时
- 当使用静态方式调用了某个静态属性或静态方法(类名.xxx)
- 反射加载某个类时,比如加载Object类:Class.forName("java.lang.Object");
Employee类
public class Employee {
public String ename;
public static int count;
public Employee() {
System.out.println("构造方法被执行");
}
static {
count = 1000;
System.out.println("静态代码块被执行");
}
}
测试类
public class Test {
public static void main(String[] args) {
Employee e1 = new Employee();
System.out.println("=============");
Employee e2 = new Employee();
Employee e3 = new Employee();
Employee e4 = new Employee();
Employee e5 = new Employee();
}
}
运行结果
运行结果
将
Employee e1 = new Employee();
修改为
System.out.println(Employee.count)
或
Class.forName("com.neuedu.test3.Employee");
都能运行出同样的运行结果
静态代码块主要用于对静态数组,集合等内容的初始化赋值
特点:
- 静态代码块中只能直接访问本类的静态内容(属性和方法),不能访问非静态内容
- 在类第一次加载时,执行且仅执行一次
2. 最终final关键字
最终关键字可以修饰类,属性和方法,表示最终的含义。
2.1 final类
一个类被final修饰,表示这个类不能拥有任何子类,不能被其他类继承。
Parent父类
public final class Parent {
}
Child子类,提示报错,Child不能继承final的Parent
public class Child extends Parent{
}
系统中java.lang.String和java.lang.Math类是final的
2.2 final属性和final变量
一个属性或变量被final修饰,表示它是常量,初始化后不能修改它的值
声明类
public class MyTest {
public int num;
public final int count = 0; //必须初始化
}
测试类
public class Test {
public static void main(String[] args) {
MyTest mt = new MyTest();
mt.num = 15;
mt.count = 15;//出错,final变量或属性不能重新赋值
}
}
一般来说,声明一个常量,通常使用static+final
public static final int count = 0;
2.3 final方法
一个方法被final修饰,表示这个方法不能被子类重写
父类
public class MyParent {
public void haha() {}
public final void xixi() {}
}
子类
public class MyChild extends MyParent {
@Override
public void haha() {
super.haha();
}
public void xixi() {} //报错,不能重写final方法
}
笔试面试题:
(1)final修饰类,方法,属性时作用
final类:不能被继承
final方法:不能被重写
final属性:是常量,初始化不能修改
(2)是否可以同时使用final和abstract修饰一个类
不可以
final是阻止一个类被继承
abstract是要求一个类被继承
两者有冲突
(3)系统中final类的例子
java.lang.String和java.lang.Math
(4)final,finally和finalize的区别
final是最终关键字,修饰类,属性,方法,参照题目(1)回答
finally是异常处理的最终关键字,表示其中的代码无论如何都会被执行
finalize是Object类中的一个方法,与Java的内存垃圾回收有关
Java虚拟机有一个优先级很低的线程会不断地扫描内存,当发现某块内存没有人指向它时,就将这块内存回收。finalize主要作用就是通知这个线程立即进行垃圾回收,但很多时候没有用。System.gc()也是做这件事的。
3. 设计模式 - 单例模式(重要)
单例模式:在系统中,某个类的对象最多只有一个。
核心思路:
- 不能让系统中可以随意的new这个类的对象:将构造方法私有。
- 唯一的对象:在类的内部new出唯一的对象
- 提供使用:
3.1 饿汉式单例
public class HungrySingleton {
private static final HungrySingleton s = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return s;
}
}
- 私有构造方法: 不让外界随意new出对象
- 私有属性: 创建对象,同时不让外界随意访问
- final: 设置为常量,不让通过反射去修改
- 公有静态方法:提供给外界一个访问这个对象的get方法
- 静态: 外界可以通过类名.getInstance方式访问
饿汉式单例,在声明单例对象时,很着急,立刻完成初始化操作
优点: 程序性能较高
缺点: 当系统中一次都没有使用这个对象时,浪费了对象的空间
3.2 懒汉式单例
public class LazySingleton {
private static LazySingleton s;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if(s == null) {
s = new LazySingleton();
}
return s;
}
}
- 私有构造方法: 不让外界随意new出对象
- 私有属性: 不让外界随意访问
- 公有静态方法:提供给外界一个访问这个对象的get方法,并在第一次访问这个方法时,初始化s对象
- synchronized:同步(线程安全),保证在多线程的环境中单例能够正常被创建
- 静态: 外界可以通过类名.getInstance方式访问
懒汉式单例,在声明单例对象时,不着急,直到第一次访问getInstance方法时再创建对象
优点: 不浪费空间,如果没有到这个对象就不创建
缺点: synchronized线程安全会造成程序执行效率降低
个人推荐使用饿汉式单例,执行效率高
4. 设计模式 - 工厂模式(了解)
工厂模式主要的目的是解耦合,最终为了实现开闭原则
开闭原则:对扩展开放,对修改关闭
4.1 简单工厂方式
将同类产品(对象)的生产(创建),放入到工厂类中完成。
汽车接口
public interface Car {
public void display();
}
汽车实现类
public class Benz implements Car {
@Override
public void display() {
System.out.println("我是奔驰");
}
}
public class BMW implements Car {
@Override
public void display() {
System.out.println("我是宝马");
}
}
工厂类
public class CarFactory {
public Car produce(int type) {
if(type == 1) {
return new Benz();
}else if(type == 2) {
return new BMW();
}else {
return null;
}
}
}
测试类
public class Test {
public static void main(String[] args) {
CarFactory cf = new CarFactory();
Car c1 = cf.produce(1);
Car c1 = cf.produce(2);
c1.display();
c2.display();
}
}
测试类中不会与具体的汽车产生耦合,只与工厂耦合,将所有的new的过程都放入到工厂类中,减少程序间的关联,达到解耦合的目的
优点:相对于后两种工厂模式易于理解
缺点:违反了开闭原则。当新增某个Car的实现类时,工厂需要改造生产线produce方法
4.2 工厂方法方式
工厂方法方式实现Car接口
public interface Car {
public void display();
}
CarFactory接口(工厂接口)
public interface CarFactory {
public Car produce();
}
Benz类和BMW类(Car的实现类)
public class Benz implements Car{
@Override
public void display() {
System.out.println("我是奔驰");
}
}
public class BMW implements Car{
@Override
public void display() {
System.out.println("我是宝马");
}
}
Benz和BMW的工厂实现类
public class BenzFactory implements CarFactory {
@Override
public Car produce() {
return new Benz();
}
}
public class BMWFactory implements CarFactory {
@Override
public Car produce() {
return new BMW();
}
}
测试类
public class Test {
public static void main(String[] args) {
CarFactory cf = new BMWFactory();
Car b = cf.produce();
b.display();
}
}
优点:解耦合并实现了开闭原则
缺点:使用的类和接口过多,容易造成类和接口的爆炸,造成关系不易读
4.3 抽象工厂方式
为了减少工厂方法方式造成的类过多的情况,将一部分经常在一起生产的类组成一个“产品组”的概念,一个工厂生产一种“产品组”
抽象工厂产品接口
public interface Cloth {
public void display();
}
public interface Food{
public void display();
}
public interface Weapone{
public void display();
}
工厂接口
public interface SoliderFactory {
public Weapone produceWeapone();
public Cloth produceCloth();
public Food produceFood();
}
产品的实现类
public class USCloth implements Cloth{
public void display() {
System.out.println("无敌护甲");
}
}
public class USFood implements Food{
public void display() {
System.out.println("吃了就饱的食品");
}
}
public class USWeapone implements Weapone{
public void display() {
System.out.println("超离子电磁炮");
}
}
产品的工厂
public class USArmyFactory implements SoliderFactory{
@Override
public Weapone produceWeapone() {
return new USWeapone();
}
@Override
public Cloth produceCloth() {
return new USCloth();
}
@Override
public Food produceFood() {
return new USFood();
}
}