如果问你了解static吗?我觉得每一个接触过java的人都会说当然。可是你真的会用并且知道为什么
用吗?反正我在学习之前是真的不知道为甚么要用static,只知道被static修饰的方法只能调用同样被
static修饰的方法,可是你要问我为甚么?我真的就不知道惹
static是静态的意思,这是句废话。但是只说它是静态,我想也很难顾名思义吧。
实际上static不仅有静态方法,它一共有五种用法。(令人孩怕也太多种了吧)
静态导入/静态变量/静态方法/静态代码块/静态内部类
静态导入
我真的是第一次听说这个,但是第一次听说却不代表是第一次接触哦
那么什么是静态导入呢?
老规矩,上代码
public class StaticDemo {
public static void main(String[] args){
double a = Math.cos(Math.PI/2);
double b = Math.pow(2.4, 1.2);
double r = Math.max(a, b);
System.out.println(r);
}
}
第一感觉是不是没啥想说的,那你的水平可能和我差不多了。事实上,一个不爱偷懒的程序员不是一个好程序员。仔细想想,这段代码里也写了太多次MATH了吧。那怎么办呢?
public class StaticDemo {
public static void main(String[] args){
double a = cos(Math.PI/2);
double b = pow(2.4, 1.2);
double r = max(a, b);
System.out.println(r);
}
}
这就是静态导入啦,我们平时使用一个静态方法的时候,就是类名.方法名, 使用静态变量的时候就是
类名.变量名。如果一段代码中频繁的使用到了某个静态方法或变量,我们简便的方法是将静态类一次性倒入。这样再次使用该方法或变量时,就不再需要写对象名了。但是这样也是有弊端的。
import static java.lang.Double.*;
import static java.lang.Integer.*;
import static java.lang.Math.*;
import static java.text.NumberFormat.*;
public class ErrorStaticImport {
// 输入半径和精度要求,计算面积
public static void main(String[] args) {
double s = PI * parseDouble(args[0]);
NumberFormat nf = getInstance();
nf.setMaximumFractionDigits(parseInt(args[1]));
formatMessage(nf.format(s));
}
// 格式化消息输出
public static void formatMessage(String s){
System.out.println(" 圆面积是:"+s);
}
}
你在看这一段程序的时候是否也和我一样摸不着头脑,PI我知道,parseDouble猜一猜也能猜到可能是Double类的一个转换方法。但是问题来了,这个getInstance是从哪来的啊?是ErrorStaticImport的本地方法吗?看了下也米有这么个方法鸭。其实它是NumberFormate类的方法,你接手一个这样的代码你不烦吗?
所以要说的是,不要滥用静态导入!!!!!!
我们正确的做法是将上面所有带通配符的引用通通写清楚,就像下面:
import java.text.NumberFormat;
import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Math.PI;
import static java.text.NumberFormat.getInstance;
这样友好多了吧。还有就是平时在写代码的时候尽可能将方法名起的能代表这个方法的作用,不然你写method1()啦,demo2()啦,时间久了你自己也不知道这个方法是干啥的了吧。也不能凡事都写注释吧,又不是写小说。
小Tip:
IDEA会自动修改你引入的通配符哦。IDEA很强大但是小贵
静态变量
java中有两种变量,静态变量和成员变量。
静态变量归属类,在内存中只有一个实例。当静态变量所在的类被加载的时候,就会为其分配内存空间。
静态变量有两种被使用的方式:类名.变量名 和 对象.变量名
我们一开始就讲说java的内存分为四个模块,堆 栈 静态区 常量区,理所当然静态变量的类被加载时,虚拟机就会在静态区为其开辟一块空间。所有使用该静态变量的对象都访问这一个空间。
通过一个代码例子来学习一下静态变量和实例变量吧:
public class StaticDemo {
public static int staticInt = 10;
public static int staticIntNo;
public int nonStatic = 5;
public static void main(String[] args){
StaticDemo s = new StaticDemo();
System.out.println("s.staticInt = " + s.staticInt);
System.out.println("StaticDemo.staticInt = " + StaticDemo.staticInt);
System.out.println("s.staticIntNo = " + s.staticIntNo);
System.out.println("StaticDemo.staticIntNo = " + StaticDemo.staticIntNo);
System.out.println("s.nonStatic = " + s.nonStatic);
s.staticInt++;
s.staticIntNo++;
s.nonStatic++;
System.out.println("s.nonStatic = " + s.nonStatic);
StaticDemo s2 = new StaticDemo();
System.out.println("s2.staticInt = " + s2.staticInt);
System.out.println("StaticDemo.staticInt = " + StaticDemo.staticInt);
System.out.println("s2.staticIntNo = " + s2.staticIntNo);
System.out.println("StaticDemo.staticIntNo = " + StaticDemo.staticIntNo);
System.out.println("s2.nonStatic =" + s2.nonStatic);
}
}
结果是:
s.staticInt = 10
StaticDemo.staticInt = 10
s.staticIntNo = 0
StaticDemo.staticIntNo = 0
s.nonStatic = 5
s.nonStatic = 6
s2.staticInt = 11
StaticDemo.staticInt = 11
s2.staticIntNo = 1
StaticDemo.staticIntNo = 1
s2.nonStatic =5
从上例中我们发现,静态变量只有一个,被类所拥有,也就是说所有实现这个类的对象都共享这个静态变量。而实例变量是与具体对象相关的。
java中,不能在方法中体中定义static变量,我们前面讲的都是类变量,不包含方法内的变量。当一个变量在程序任何地方都有可能被访问到的时候,我们应当考虑将它设计为静态的。否则每次使用这个变量的时候都要创建一个这个变量所在的对象,浪费了内存空间。
静态方法
静态方法即是在方法外加一个static修饰符。与静态变量一样,java也提供了静态方法和非静态方法。
static方法是类的方法,无需创建对象就可以使用,比如Math里的一些数学运算方法。使用类名.方法名或对象名.方法名 即可调用。
相对的非static方法是对象的方法,只有对象被创建之后才可以被使用,使用方法是对象.方法名
PS:这里要非常注意的是,static方法中不能使用this或super关键字,不能调用非static方法,很多时候我们在main方法里调用其它非static的方法时IDE都会报错提示我们。静态方法只能访问所在类的静态变量和静态方法。
因为当static方法被调用的时候,这个类的对象可能还没有创建。
那么静态方法有什么用呢?
static方法很常见的一个用途就是实现单例模式。单例模式的特点就是一个类只有一个实例,为了实现这一功能,必须隐藏该类的构造函数。也就是把构造函数声明成private,并提供一个创建对象的方法。
public class TestDL {
private static TestDL dl;
private TestDL(){
}
public static TestDL getInstance(){
if(dl == null){
dl = new TestDL();
}
return dl;
}
}
构造函数私有化,将实例变量静态化,每次调用的时候都去静态区找到这个静态实例。只有第一次创建的时候开辟一个新区域存放。(当然这里开辟的区域在静态区中)
这个类只会有一个对象。
要注意的是其他用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。
static 变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用(当然也可以在非静态成员方法中使用--废话),但是不能在其他类中通过类名来直接引用,这一点很重要。
实际上你需要搞明白,private是访问权限限定,static表示不要实例化就可以使用,这样就容易理解多了。static前面加上其它访问权限关键字的效果也以此类推。
静态方法的用途
静态变量可以被非静态方法调用,也可以被静态方法调用。但是静态方法只能被静态方法调用。
一般工具方法会设计为静态方法,比如Math类中的所有方法都是静态的,因为我们不需要Math类的实例,我们只是想要用一下里面的方法。所以,你可以写一个通用的 工具类,然后里面的方法都写成静态的。
静态代码块
既然提到了静态代码块,那就不妨学习了解一下代码块到底是个什么。
代码块除了这里要学到的静态代码块之外,还有另外三种:普通代码块,同步代码块和构造代码块。
普通代码块:
普通代码块就是我们在码代码的每天都会用到的,就是在方法名后面用{}括起来的代码段。普通代码块是不能够单独存在的,它必须要紧跟在方法名后面。同时也必须要使用方法名调用它。
public void common(){
System.out.println("普通代码块执行");
}
静态代码块:
使用static修饰的用{}括起来的代码段,主要目的是为了对静态属性进行初始化。
静态代码块不存在任何方法体内,可以随便放,也可以随便写多少个。JVM加载类时会执行这些代码块,如果有多个的话,就按照在类中的出现顺序依次执行,当然只会被执行一次。
又到了用代码说话的时候惹:
public class Person {
private Date birthDate;
private static Date sDate,eDate;
public Person(Date birthDate){
this.birthDate = birthDate;
}
/**判断一个人的生日是不是90后
* 但是每一次调用这个方法的时候都要生成startDate和endDate,很浪费资源
* @return
*/
boolean isBornBoomer(){
Date startDate = Date.valueOf("1990");
Date endDate = Date.valueOf("1999");
return birthDate.compareTo(startDate)>0 && birthDate.compareTo(endDate)<0;
}
/*
* 改进一个方法
*/
static{
sDate = Date.valueOf("1990");
eDate = Date.valueOf("1999");
}
boolean isBornBoomer2(){
return birthDate.compareTo(sDate)>0 && birthDate.compareTo(eDate)<0;
}
}
因此,将很多只需要进行一次初始化的操作都放到static代码块中会大大提升效率,节省内存
同步代码块:
使用Synchronized关键字修饰,并使用{}括起来的代码片段。这个表示同一时间内只有一个线程可以进入到该方法中,是一种多线程保护机制。
这个我们先大概有个印象,之后在讨论多线程的时候,再详细进行讨论。
构造代码块:
在类中直接定义没有任何修饰符、前缀、后缀的代码块即为构造代码块。
我们明白一个类必须至少有一个构造函数,构造函数在生成对象时被调用。构造代码块和构造函数一样同样是在生成一个对象时被调用,那么构造代码在什么时候被调用?
如何调用的呢?
继续看代码吧
public class CodeDemo {
private int a = 1;
private int b;
private int c;
//静态代码块
static {
int a =4;
System.out.println("我是静态代码块1");
}
//构造代码块
{
int a = 0;
b = 2;
System.out.println("我是构造代码块1");
}
public CodeDemo(){
this.c = 3;
System.out.println("构造函数");
}
public int add(){
System.out.println("count a + b + c");
return a+b+c;
}
//静态代码块
static {
System.out.println("我是静态代码块2,I do nothing");
}
//构造代码块
{
System.out.println("我是构造代码块2");
}
}
public static void main(String[] args){
CodeDemo c = new CodeDemo();
System.out.println(c.add());
System.out.println();
System.out.println("*******再来一次*********");
System.out.println();
CodeDemo c1 = new CodeDemo();
System.out.println(c1.add());
}
结果是:
我是静态代码块1
我是静态代码块2,I do nothing
我是构造代码块1
我是构造代码块2
构造函数
count a + b + c
6
*******再来一次*********
我是构造代码块1
我是构造代码块2
构造函数
count a + b + c
6
总结一下这段代码
静态代码块只会执行一次,有多个时依次执行。构造代码块每次创建新对象时都会执行,有多个时依次执行。执行顺序:静态代码块>构造代码块>构造函数
构造代码块和静态代码块都有自己的作用域,作用域内部的变量不影响作用域外部。这也是为什么count出来的数是6,而没有随着代码块中的数值变化。
构造代码块的应用场景:
1.初始化实例变量
如果一个类中存在若干个构造函数,这些构造函数都需要对实例变量进行初始化,如果直接在构造函数中实例化,必定会产生很多重复代码,繁琐且可读性差,这里我们可以利用构造代码块来实现。这是利用了编译器会将构造代码块添加到每个构造函数中的特性。
2.初始化实例环境
一个对象必须在适当的场景下才能存在,如果没有适当的场景,则就需要在创建对象时创建此场景。我们可以利用构造代码块来创建此场景,尤其是该场景的创建过程较为复杂。构造代码会在构造函数之前执行。
静态内部类
被static修饰的内部类,它可以不依赖于外部类实例对象而被实例化,而通常的内部类需要在外部类实例化后才能实例化。
静态内部类不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能访问内部类中的静态成员和静态方法(包括私有类型)。
由于还没有详细讲解过内部类,这里先一笔带过,在讲解内部类的时候会详细分析静态内部类。
只有内部类才能被static修饰,普通的类不可以。
如果既有继承,又有代码块,执行的顺序是怎样的呢?
public class Parent {
static{
System.out.println("父类静态代码块");
}
{
System.out.println("父类构造代码块");
}
public Parent(){
System.out.println("父类构造函数");
}
}
class Children extends Parent{
static {
System.out.println("子类静态代码块");
}
{
System.out.println("子类构造代码块");
}
public Children(){
System.out.println("子类构造函数");
}
}
public static void main(String[] args){
new Children();
}
结果是:
//父类静态代码块
//子类静态代码块
//父类构造代码块
//父类构造函数
//子类构造代码块
//子类构造函数
顺序一目了然:
首先执行静态,由父到子。其它非静态也是由父到子。(非静态中有构造代码块和构造函数)
总结
static关键字的五种用法:
静态导入/静态变量/静态方法/静态代码块/静态内部类
代码块:
普通代码块/构造代码块/静态代码块/同步代码块
网友评论