static表示静态,是java中的一个修饰符,可以修饰成员方法,成员变量。
一、静态变量(类变量)和非静态变量(实例变量)
1.类变量
定义:有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享
特点:在类加载的时候就直接被加载到静态区中了,优于对象而存在
public class Student {
//类变量一般定义为public,完全对外部进行共享,方便外部谁都可以对其进行访问和修改
//如果定义成private,就只能在本类中共享
public static String name;
//实例变量
public int age;
}
类变量的访问方式:
①类名.类变量(访问本类中的类变量,可以省略类名不写;访问其他类中的类变量,必须带类名)
public class Test {
public static void main(String[] args) {
//1.类名.类变量(推荐)
Student.name="zhangsan";
}
}
类的字节码文件(student.class)加载进来后,会在堆中额外开辟一个空间(暂称为"静态区")存放类变量。
根据类名去方法区里面找到该类,再根据该类找到类变量name,修改为"zhangsan"。
注:在jdk8之前,静态变量存在方法区;jdk8及以后,存放在堆中反射的class对象的尾部。
②对象.类变量
public class Test {
public static void main(String[] args) {
//2.对象.类变量(不推荐)
Student s1=new Student();
s1.name="lisi";
Student s2=new Student();
s2.name="wangwu";
System.out.println(s1.name);//wangwu
Student s3=null;
//虽然s3指向空,但会变成Student.name去访问静态区中的name。
System.out.println(s3.name);//wangwu
}
}
当对象访问静态变量时,会直接通过类名来访问,而不是通过对象引用。
s1、s2、s3都会变成Student.name去访问静态区中的name,尽管s3指向空。
由于类变量是所有对象共享的,所以最后静态区中的name被修改为"wangwu"。
应用场景:在开发中,如果某个数据只需要一份,且希望能被共享(访问、修改),则该数据可以被定义成类变量。
例如:系统启动后,要求用户类可以记住自己创建了多少个用户对象。
public class User {
//类变量
public static int number;
//构造器
public User(){
//User.number++;
//在同一个类中,访问自己的类变量,才可以省略类名不写
number++;
}
}
public class Test {
public static void main(String[] args) {
User u1 = new User();
User u2 = new User();
User u3 = new User();
User u4 = new User();
System.out.println(User.number);//4
}
}
2.实例变量
定义:对象的变量 -->无static修饰,属于每个对象,即每个对象都有一份。
特点:在对象创建的时候才会产生
public class Student {
//类变量
public static String name;
//实例变量
public int age;
}
实例变量的访问方式:对象.实例变量
public class Test {
public static void main(String[] args) {
Student s1=new Student();
Student s2=new Student();
s1.age=18;
s2.age=23;
System.out.println(s1.age);//18
//System.out.println(Student.age);//报错
}
}
实例变量是每个对象都有一份,数据各不同并不共享。所以最后s1.age仍是18。
二、静态方法(类方法)和非静态方法(实例方法)
1.类方法
定义:有static修饰的成员方法,属于类。
应用场景:做工具类。
public class Student {
//类方法
public static void printHelloWorld() {
System.out.println("helloWorld");
}
}
调用方式:
①类名.类方法(访问本类中的类方法,可以省略类名不写;访问其他类中的类方法,必须带类名)
public class Test {
public static void main(String[] args) {
//1.类名.类方法(推荐)
Student.printHelloWorld();
}
}
根据类名去方法区里面找该类,再调用其中的printHelloWorld方法
② 对象.类方法
public class Test {
public static void main(String[] args) {
//2.对象.类方法(不推荐)
Student s1=new Student();
s1.printHelloWorld();
Student s2=null;
//虽然s2指向空,但会变成Student.printHelloWorld()去调用该方法。
s2.printHelloWorld();//成功执行
}
}
当对象访问静态方法时,会直接通过类名来访问,而不是通过对象引用。
s1、s2都会变成变成Student.printHelloWorld()去调用该方法,尽管s2指向空。
2.实例方法
定义:对象的方法 --> 无static修饰的成员方法,属于对象
public class Stuedent {
public static double score = 60;
//实例方法(非静态方法/对象的方法)
public void printPass() {
//访问本类中的类变量,可以省略类名不写;访问其他类中的类变量,必须带类名
System.out.println("成绩" + (score >= 60 ? "及格" : "不及格"));
}
}
调用方式:对象.实例方法
public class Test {
public static void main(String[] args) {
Student s1=new Student();
s1.printPass();//及格
//Student.printPss(); //报错
}
}
3.扩展(main方法的含义)
main方法是测试类的类方法。
当用java命令执行Test程序,即java Test时,虚拟机事实上会直接调用Test.main(...)来触发main方法执行。
String[] args是以前由于接收键盘数据的,现在没用,但是java为了兼容低版本,继续保留了。
例如:
4.工具类
定义:帮助我们做一些事情的类,但是不描述任何事物的类。
工具类中的方法都是一些类方法,每个方法都是用来完成一个功能的,工具类是给开发人员使用的。
好处:提高了代码复用;调用方便,提高了开发效率
首先定义一个简单的Student类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(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;
}
}
再定义一个StudentUtil工具类,实现求学生最大年龄的方法。
public class StudentUtil {
//私有化构造方法
private StudentUtil(){};
//方法需要定义成类方法(静态的),
public static int getMaxAge(ArrayList<Student> list){
int max=list.get(0).getAge();
for (int i = 1; i < list.size(); i++) {
int tempAge=list.get(i).getAge();
if(max<tempAge){
max=tempAge;
}
}
return max;
}
}
注:
①私有化构造方法-->不让外界创建他的对象(外界需要使用该类的方法,没必要创建该类的对象)
②方法需要定义成类方法(静态的),直接用类名调用即可,调用方便,也能节省内存。
原因:如果定义成实例方法,想调用就必须要创建对象,对象占内存,会浪费内存;并且工具类也不能创建对象。
测试类:
public class Test {
public static void main(String[] args) {
//1.创建集合
ArrayList<Student> list=new ArrayList<>();
//2.创建对象
Student s1=new Student("张三",23);
Student s2=new Student("李四",24);
Student s3=new Student("王五",25);
//3.将学生添加到对象中
list.add(s1);
list.add(s2);
list.add(s3);
//调用工具类中的方法
int maxAge=StudentUtil.getMaxAge(list);
System.out.println(maxAge);//25
}
}
三、static的注意事项
1.静态方法只能访问静态
类方法中可以直接访问类的成员(类变量和类方法),不可以直接访问实例成员(实例变量和实例方法)。
public class Student {
static String schoolName;//类变量
double score;//实例变量
//类方法
public static void print1(){
//注意:同一个类中,访问类成员,可以省略类名不写
//类成员
schoolName="heima";
show1();
//实例成员
//System.out.println(score); 报错
//print2(); 报错
}
//类方法
public static void show1(){
}
//实例方法
public void print2(){
}
}
2.非静态方法可以访问所有
实例方法中既可以直接访问类成员,也可与直接访问实例成员。
public class Student {
static String schoolName;//类变量
double score;//实例变量
//实例方法
public void print2(Student this){
//类成员
schoolName="heima";
show1();
//实例成员
System.out.println(score);
show2();
}
//实例方法
public void show2(){
}
//类方法
public static void show1(){
}
}
3.静态方法中没有this关键字
实例方法中可以出现this关键字,类方法中不可以出现this关键字。
this:表示当前方法调用者的地址值
public class Student {
static String schoolName;//类变量
double score;//实例变量
//类方法
public static void print1(){
//System.out.println(this); 报错
}
//实例方法
public void print2(Student this){
System.out.println(this);
//以下this均可省略不写,都时自动隐含的
System.out.println(this.schoolName);
System.out.println(this.score);
this.print1();
this.show2();
}
//实例方法
public void show2(){
}
}
注:
实例方法的形参当中实际上隐含了一个Student this,这个this不能手动赋值,是由jvm自动赋值的。类方法形参中并没有隐含Student this。
所以实例方法中调用的变量和其他方法前面都相当于有个隐含的this.,但是由于没有和形参中其他变量名字重复,可以省略不写。
简单理解:this代表当前对象,实例方法是对象的方法,由于是对象调用,this就指代这个对象。类方法无论是类名还是对象调用,最终都会转变成类名调用,没有对象,也就没有this。
四、代码块
1.局部代码块
定义:写在方法中的单独的一对{}
作用:提前结束变量的生命周期,节约内存
public class Test {
public static void main(String[] args) {
{
int a = 10;
}
//当代码执行到这里时,变量a就已经结束生命周期,从内存中消失了
//System.out.println(a); 报错
}
}
2.实例代码块/构造代码块
定义:每次创建对象时,执行实例代码块,并在构造器前执行
作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值(不推荐)
public class Student {
static String schoolName;//类变量
double score;//实例变量
{
System.out.println("实例代码块执行了~~");
}
public Student() {
System.out.println("无参构造器执行了~~");
}
public Student(double score) {
System.out.println("有参构造器执行了~~");
}
public class Test {
public static void main(String[] args) {
Student s1=new Student();
Student s2=new Student(23);
}
}
运行结果:
可以看出,每次创建对象,都在构造器之前执行了一次实例代码块
应用场景:例如记录对象创建的日志,原本需要在所有构造器中都要写的重复代码,只需要在实例代码块中写一次。可以把多个构造方法中重复的代码抽取出来,提高代码复用性。
初始代码:
public class Student {
double score;//实例变量
public Student() {
System.out.println("有人创建了对象:"+this);
}
public Student(double score) {
System.out.println("有人创建了对象:"+this);
}
}
优化后代码:
public class Student {
double score;//实例变量
{
System.out.println("有人创建了对象:"+this);
}
public Student() {
}
public Student(double score) {
}
由于采用构造代码块的话,每个构造方法执行时都必须会执行构造代码块中的内容,不够灵活。
改进方式:
方法一:将重复代码抽取到一个构造方法中,在其他构造方法中用this关键字调用该构造方法
方法二 :将重复的代码抽取成一个方法,在构造方法中需要使用时直接调用
3.静态代码块
定义:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
作用:完成类的初始化,例如:对类变量的初始化赋值
public class Student {
static String schoolName;//类变量
double score;//实例变量
static {
schoolName="zhangsan";
System.out.println("静态代码块执行了~~");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Student.schoolName);
System.out.println(Student.schoolName);
System.out.println(Student.schoolName);
}
}
运行结果:
可以看出静态代码块只在类加载的时候执行了一次。
注:虽然也可以通过声明时直接初始化赋值的方式,但如果所定义的类变量是一个复杂的对象,就不方便直接初始化了,此时就需要在静态代码块中初始化
举个🌰🌰🌰,初始化用户信息
public class App {
static ArrayList<User> list = new ArrayList<>();
static {
list.add(new User("张三","123456","100101200101011234","13112344321"));
list.add(new User("李四","123123","100101200101014321","13112341234"));
list.add(new User("王五","123321","100101200101011212","13112345678"));
}
public static void main(String[] args) {
//....
}
}
对于集合这种复杂对象信息,就没法在声明的时候直接初始化赋值。虽然可以在main函数中实现该操作,但只要是函数,都有可能被多次调用。
如果创建对象连接的是数据库,那么每次启动该系统执行main函数时,都会重复执行创建对象的代码。
为了只让这些代码执行一次,最好的办法就是放在静态代码块中执行。
五、单例设计模式(确保一个类只有一个对象)
1.饿汉式单例
定义:拿对象时,对象早就创建好了
应用场景:如果一个对象需要频繁调用,就采用饿汉式单例
public class User {
//2,定义一个类变量记住类的一个对象
private static User u=new User();
//1.私有化构造器
private User(){
}
//3.定义一个方法返回类的对象
public static User getObject(){
return u;
}
}
public class Test {
public static void main(String[] args) {
User u1=User.getObject();
User u2=User.getObject();
System.out.println(u1==u2);//true
}
}
2.懒汉式单例
定义:拿对象时,才开始创建对象
应用场景:如果一个对象不需要经常调用,就采用懒汉式单例,节省内存开销
public class User {
//2,定义一个类变量记住类的一个对象
private static User u;
//1.私有化构造器
private User(){
}
//3.定义一个方法返回类的对象
public static User getInstance(){
if(u==null){
u=new User();
}
return u;
}
}
public class Test {
public static void main(String[] args) {
User u1=User.getInstance();
User u2=User.getInstance();
System.out.println(u1==u2);//true
}
}