static是java中非常重要的一个关键字,而且它的用法也很丰富,主要有四种用法:
- 用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;
- 用来修饰成员方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类;
- 静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;
- 静态导包用法,将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便;
- 简单实例
public class Person {
int age;
String name;
String nation;
public void ShowIdentity() {
System.out.println("姓名:" + name + " 年龄: " + age + " 国籍:" + nation);
}
}
public class show {
public static void main(String[] args) {
Person CHN = new Person();
CHN.name = "李华";
CHN.age = 20;
CHN.nation = "中国";
Person USA = new Person();
USA.name = "Jack";
USA.age = 18;
USA.nation = "美国";
CHN.ShowIdentity();
USA.ShowIdentity();
}
}
console结果:
姓名:李华 年龄: 20 国籍:中国
姓名:Jack 年龄: 18 国籍:美国
使用Static后
//
public class Person {
int age;
String name;
static String nation;//静态成员变量
public void ShowIdentity() {
System.out.println("姓名:" + name + " 年龄: " + age + " 国籍:" + nation);
}
}
//
public class show {
public static void main(String[] args) {
Person CHN = new Person();
CHN.name = "李华";
CHN.age = 20;
Person.nation = "中国";
Person USA = new Person();
USA.name = "Jack";
USA.age = 18;
Person.nation = "美国";
CHN.ShowIdentity();
USA.ShowIdentity();
}
}
console结果:
姓名:李华 年龄: 20 国籍:美国
姓名:Jack 年龄: 18 国籍:美国
内存模型:
过程:
1.Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀);
2.JVM中的类加载器将各个类的字节码文件加载到方法区,其中类中的静态成员变量会放到“静态存储区”;
3.加载完成后,JVM执行引擎开始执行程序;
1.修饰成员变量
特点:被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享;
格式:类名.静态变量名 (不推荐使用 对象.静态变量名);
优点:1.对象之间可以共享属性;2.类名直接调用更方便;
不足:只分配一次内存,只有一个拷贝,不够灵活;
2.修饰成员方法
特点:修饰成员方法对于数据的存储上面并没有多大的变化,方法本来就是存放在类的定义当中的;
格式:类名.静态方法名;
优点:方便方法的调用,避免了先要new出对象的繁琐和资源消耗;父类中的静态方法不会被子类的重写覆盖;
不足:静态方法中不能用this和super关键字(因为调用静态方法没有调用对象,直接访问的方法区);不能直接访问所属类的非static的成员变量和成员成员方法,只能访问所属类的静态成员变量和成员方法;(因为每创建一次实例产生对象,实例变量就会产生一个拷贝,如果允许直接访问non-static filed,调用static方法时程序无法判断去使用哪个对象中的哪个变量,所以必须只可以访问static filed);
3.静态块
特点:一 般情况下,如果有些代码必须在项目启动的时候就执行的时候,这时可以使用静态代码块,这种代码是主动执行的;
格式:static {…//需要执行的代码}
示例:
//普通类
public class PuTong {
public PuTong(){
System.out.print("默认构造方法!-->");
}
//非静态代码块
{
System.out.print("非静态代码块!-->");
}
//静态代码块
static{
System.out.print("静态代码块!-->");
}
public static void test(){
System.out.println("普通方法中的代码块!");
{
}
//测试
public class TestClass {
public static void main(String[] args) {
PuTong c1 = new PuTong();
c1.test();
PuTong c2 = new PuTong();
c2.test();
}
}
/*
运行输出结果是:
静态代码块!-->非静态代码块!-->默认构造方法!-->普通方法中的代码块!
非静态代码块!-->默认构造方法!-->普通方法中的代码块!
*/
区别:
1.静态代码块:只在第一次创建对象时执行一次,而且是第一个执行;
2.非静态代码块:每创建一次对象,就执行一次;
注意:
对象的初始化顺序:首先执行父类静态的内容,父类静态的内容执行完毕后,接着去执行子类的静态的内容,当子类的静态内容执行完毕之后,再去看父类有没有非静态代码块,如果有就执行父类的非静态代码块,父类的非静态代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有非静态代码块,如果有就执行子类的非静态代码块。子类的非静态代码块执行完毕再去执行子类的构造方法。总之一句话,静态代码块内容先执行,接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。而且子类的构造方法,不管这个构造方法带不带参数,默认的它都会先去寻找父类的不带参数的构造方法。如果父类没有不带参数的构造方法,那么子类必须用supper关键子来调用父类带参数的构造方法,否则编译不能通过。
4.静态导包
5.典型应用-单例模式:
单例模式(Singleton模式) ,指的是一个类,在一个JVM里,只有一个实例存在。
- 饿汉式单例模式
示例:
public class Person {
private int age;
public int getAge() {
return age;
}
//1.创建一个私有类型的构造,禁止外部创建实例对象
private Person(int age) {
this.age = age;
}
//2.创建静态类型的引用(p),指向一个实例化对象,同时私有化禁止外部调用
private static Person p = new Person(18);
//3.创建静态类型的get方法,返回出去唯一的实例对象
public static Person getInstance() {
return p;
}
}
public class show {
public static void main(String[] args) {
Person p1 = Person.getInstance();
Person p2 = Person.getInstance();
System.out.println(p1.getAge() + ":" + p2.getAge());
System.out.println(p1+ ":" + p2);
}
}
console结果:
18:18
temp.Person@15db9742:temp.Person@15db9742
- 分析
1.内部私有化构造,外部无法创建新的实例;
2.内部静态类型的引用指向创建的唯一一个实例对象;
3.内部创建静态方法并返回上一步p指向的实例对象,方便外部通过唯一的入口进行调用;
懒汉式单例模式:
- 示例
public class Person {
private int age;
public int getAge() {
return age;
}
//创建一个私有类型的构造,禁止外部创建实例对象
private Person(int age) {
this.age = age;
}
//创建静态类型的引用(p),默认指向null
private static Person p;
//创建public静态方法,返回实例对象
public static Person getInstance(){
//第一次访问的时候,发现p没有指向任何对象,这时才会实例化一个对象
if(null==p){
p = new Person(18);
}
return p; //返回 p指向的对象
}
}
public class show {
public static void main(String[] args) {
Person p1 = Person.getInstance();
Person p2 = Person.getInstance();
System.out.println(p1.getAge() + ":" + p2.getAge());
System.out.println(p1+ ":" + p2);
}
}
console结果:
18:18
temp.Person@15db9742:temp.Person@15db9742
饿汉式单例模式:是立即加载的方式,无论是否会用到这个对象,都会加载。 如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿。
懒汉式单例模式:是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量(鉴于同学们学习的进度,暂时不对线程的章节做展开)。使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
何时使用:如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
final关键字是我们经常使用的关键字之一,它的用法有很多,但是并不是每一种用法都值得我们去广泛使用。它的主要用法有以下四种:
- 用来修饰成员变量,我们必须在声明时或者构造方法中对它赋值,该变量只能被赋值一次且它的值无法被改变;
- 用来修饰局部变量,该变量只能被赋值一次且它的值无法被改变;
- 修饰方法,表示该方法无法被重写;
- 修饰类,表示该类无法被继承;
第三种和第四种方法需要谨慎使用,因为在大多数情况下,如果是仅仅为了一点设计上的考虑,我们并不需要使用final来修饰方法和类。
1.修饰类
特点:当用final修饰一个类时,表明这个类不能被继承,final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法;
格式:final class 类名{}
使用时机:这个类在以后不会用来继承或者出于安全的考虑,可以考虑使用;
2.修饰方法
特点:当用final修饰成员方法时,该方法不能被子类重写(覆盖);
格式:例:public final void xxx(){}
使用时机:将方法锁定,以防任何继承类修改它的含义
3.修饰成员变量
特点:final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在声明时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
4.修饰局部变量
特点:对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
修饰引用类型变量分析:
内存模型:
总结:
很多时候会容易把static和final关键字混淆,简单来说static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变;
public class Test {
final double i = Math.random();
static double j = Math.random();
public void Print() {
System.out.println("hello");
}
}
public class show {
public static void main(String[] args) {
final Test tt = new Test();
System.out.println("i:" + tt.i + "---j:" + Test.j);
Test ee = new Test();
System.out.println("i:" + ee.i + "---j:" + Test.j);
}
}
console第一次运行结果:
i:0.43682430830851604---j:0.2186265152944704
i:0.12239426737840786---j:0.2186265152944704
console第二次运行结果:
i:0.040292758906855575---j:0.695015407609792
i:0.2527122814442675---j:0.695015407609792
每次运行都是j结果相同,i不相同,static和final区别显而易见;