对于static关键字从第一天开始就见到了。在Java里面,static可以定义属性、方法。
下面首先来观察一个程序类,要求定义出一个表示所有清华大学学校的学生信息,那么就证明学生类里面需要提供一个学校的信息,表示所在的学校,所以按照之前学习的内容,代码开发如下。
class Student { private String name ; private int age ; String school = "清华大学" ; // 此处为了方便,暂不封装 public Student(String name,int age) { this.name = name ; this.age = age ; } public String getInfo() { return "姓名:" + this.name + ",年龄:" + this.age + ",学校:" + this.school ; } } public class TestDemo { public static void main(String args[]) { Student stuA = new Student("张三",20) ; Student stuB = new Student("李四",21) ; Student stuC = new Student("王五",22) ; System.out.println(stuA.getInfo()) ; System.out.println(stuB.getInfo()) ; System.out.println(stuC.getInfo()) ; } } |
此时每个对象之中都占有一个“school”的属性,而且这些属性只要是通过Student类产生的对象,都应该是相同的。那么如果说现在Student类产生了10W个对象。则需要同时对10W个对象的school属性内容进行修改。所以这个时候的设计就不合理了,应该将school这个属性作为一个公共属性出现。这样不仅节省了空间,而且方便数据的统一维护。
在这种情况下就可以使用static关键字来声明了,即:使用static关键字声明的属性就是公共属性。
范例:使用static修改
class Student { private String name ; private int age ; static String school = "清华大学" ; // 此处为了方便,暂不封装 public Student(String name,int age) { this.name = name ; this.age = age ; } public String getInfo() { return "姓名:" + this.name + ",年龄:" + this.age + ",学校:" + this.school ; } } public class TestDemo { public static void main(String args[]) { Student stuA = new Student("张三",20) ; Student stuB = new Student("李四",20) ; Student stuC = new Student("王五",20) ; stuA.school = "北京大学" ; System.out.println(stuA.getInfo()) ; System.out.println(stuB.getInfo()) ; System.out.println(stuC.getInfo()) ; } } |
当修改一个对象的school属性的时候所有对象的school属性都修改了,就得出一个结论:school是一个公共属性。而此属性不再保存在堆内存之中,会保存在一个称为“全局数据区”的内存区域,此时内存关系图如下。
以上的程序是通过一个对象修改了公共属性,严格来讲这样并不好,既然是公共属性,那么就必须由所有对象最高代表进行操作,那么所有对象的最高代表就是类,那么一般使用static定义的属性往往会通过类名称直接调用。
Student.school = "北京大学" ; |
正是由于static存在这样的由类名称直接调用的特点,所以static属性又被称为“类属性”,而且static属性可以在一个类没有实例化对象的时候直接进行访问。
public class TestDemo { public static void main(String args[]) { System.out.println(Student.school) ; Student.school = "北京大学" ; System.out.println(Student.school) ; } } |
那么现在就会发现在类之中的属性就分为两种:普通属性、static属性,那么在设计的时候优先考虑的是普通属性,在99%的设计之中,都会出现普通属性,而只有在1%的情况下才会出现static属性。
既然使用static定义的属性可以通过类名称直接访问,那么使用static定义的方法也一定可以通过类名称直接访问,而且使用static定义的方法其主要目的就是为了操作static属性。
范例:观察static方法的使用
class Student { private String name ; private int age ; private static String school = "清华大学" ; public Student(String name,int age) { this.name = name ; this.age = age ; } public static String getSchool() { return school ; // 没有加上this } public static void setSchool(String sch) { // 名称不统一 school = sch ; // 没有加上this } public String getInfo() { return "姓名:" + this.name + ",年龄:" + this.age + ",学校:" + this.school ; } } public class TestDemo { public static void main(String args[]) { System.out.println(Student.getSchool()) ; Student.setSchool("北京大学") ; Student stu = new Student("张三",20) ; System.out.println(stu.getInfo()) ; } } |
首先在开发之中肯定优先选用普通方法,但是对于类之中的方法现在就存在有两类:普通方法、static方法,而这两类方法调用上是有限制的:
· 使用static方法只能够调用static属性和static方法,不能够调用任何的非static操作;
· 使用非static方法可以调用任意的static属性或static方法。
思考:以上的限制只是语法层次上的限制,那么下面再通过实际的道理来分析以上问题?
首先static方法和非static方法调用的时机是不同的。Static方法可以由类名称直接调用,那么在调用的时候可以没有实例化对象产生,而非static方法,必须在有实例化对象产生之后才可以调用(对象实例化之后会开辟堆内存空间,在堆内存空间之中要保存属性的信息)。
通过以上的一系列分析可以发现,虽然static定义的属性和方法是在类之中定义的,但是却对立于类之外,不受类对象的控制。那么只有在一种情况下会选择定义static方法:如果一个类之中没有任何的属性存在,那么就可以考虑将所有的方法都定义为static。
在第一天讲解Java方法定义的时候曾经这样解释过:“如果现在一个方法定义在主类之中,并且由主方法直接调用”的话,则方法定义格式如下:
public static 返回值类型 方法名称(参数列表) { [return [返回值] ;] } |
而后来定义类的时候发现方法上并没有编写static,那么下面做一个总结说明。
范例:在主类里编写方法
public class TestDemo { public static void main(String args[]) { // static方法 // static方法只能够调用static属性或者是static方法 fun() ; // 调用static方法,直接调用 } public static void fun() { System.out.println("Hello World .") ; } } |
那么如果此时fun()方法上没有static,则表示static方法(main())要去调用非static方法(fun()),这样是无法进行调用的。那么这个时候fun()就是一个普通方法,如果是普通方法就只能通过实例化对象调用。
public class TestDemo { public static void main(String args[]) { // static方法 new TestDemo().fun() ; } public void fun() { System.out.println("Hello World .") ; } } |
而现在就可以发现一个小问题,在Java里面的主方法似乎是长了点,所以下面来分析一下主方法的组成:
· public:这是一种权限,表示公共的,都可以访问;
· static:表示此方法可以由类名称直接调用;
· void:主方法是一切的开始,只要开头了,就没回头路了;
· main():是一个系统默认定义好的方法名称,使用java解释类的时候默认找到此名称;
· String args[]:接收的参数。
范例:接收参数
public class TestDemo { public static void main(String args[]) { for (int x = 0 ; x < args.length ; x ++) { System.out.print(args[x] + "、") ; } } } |
所有的参数在解释类的时候通过空格分割:“java TestDemo 参数 参数 参数 …”,但是有一点需要注意,既然所有的参数之间必须使用“空格”分割,如果说现在参数本身就存在有空格呢?则必须使用“"”声明(java TestDemo "hello world" "hello mldn")。
在实际的工作里面,static绝对不是优先使用的,而且几乎用的很少,那么下面通过两个简单的程序,巩固一下static的使用。
功能一:作为统计记录使用
如果说现在有一个类要求可以统计出类之中产生过多少个实例化对象,则就可以利用static进行统计。由于每个新对象实例化的时候一定要调用构造方法,所以可以在构造方法里面增加统计操作。
class Person { private static int count = 0 ; // 统计个数 public Person() { System.out.println("Person对象个数:" + ++ count) ; } } public class TestDemo { public static void main(String args[]) { new Person() ; new Person() ; new Person() ; new Person() ; new Person() ; new Person() ; } } |
功能二:实现对象的自动命名
假设现在Person类里面有一个name属性,同时提供有两个构造方法(无参、一个参数),通过一个参数可以由外部设置人的名字,但是如果调用的是一个无参构造,则不希望name属性的内容是null,给他一个默认的名字“无名氏-编号”,那么这个编号不应该重复,所以就可以利用static统计。
class Person { private static int count = 0 ; // 统计个数 private String name ; public Person() { this("无名氏 - " + count ++) ; // 自动命名 } public Person(String name) { this.name = name ; } public String getName() { return this.name ; } } public class TestDemo { public static void main(String args[]) { System.out.println(new Person().getName()) ; System.out.println(new Person("张三").getName()) ; System.out.println(new Person().getName()) ; } } |
日后如果见到了有类似的操作,要能够分析出使用了static即可,其它的东西意义不大。
在程序之中使用“{}”声明的代码部分就称为代码块,而根据代码块声明的关键字以及位置的不同,代码块一共分为四种:普通代码块、构造块、静态块、同步块,其中对于同步代码块是最后再讲解的。
在观察普通代码块之前,首先来观察如下的一段程序。
public class TestDemo { public static void main(String args[]) { if(true) { int x = 10 ; // 局部 System.out.println("** x = " + x) ; } int x = 100 ; // 全局 System.out.println("## x = " + x) ; } } |
那么如果说现在将“if(true)”取消。
public class TestDemo { public static void main(String args[]) { { // 普通代码块 int x = 10 ; // 局部 System.out.println("** x = " + x) ; } int x = 100 ; // 全局 System.out.println("## x = " + x) ; } } |
一般使用普通代码块是为了方便某一个方法的编写,避免变量重复的问题。
如果把一个代码块写在了类之中,则此代码块称为构造块。
范例:观察构造代码块
class Person { public Person() { System.out.println("*** Person类的构造方法") ; } { // 构造块 System.out.println("### Person类的构造块") ; } } public class TestDemo { public static void main(String args[]) { new Person() ; new Person() ; new Person() ; } } |
现在发现构造块优先于构造方法执行,而且每当有实例化对象产生的时候都要执行构造块之中的内容,会重复调用的。在以后的编写之后不要去写构造块。
静态块是在构造块的基础之上增加了static关键字形成的。但是静态块的使用要分两种情况,一种是定义在普通类里面,另外一种是定义在主类里面。
范例:在普通类之中定义静态块
class Person { public Person() { System.out.println("*** Person类的构造方法") ; } { // 构造块 System.out.println("### Person类的构造块") ; } static { // 静态块 System.out.println("%%% Person类的静态块") ; } } public class TestDemo { public static void main(String args[]) { new Person() ; new Person() ; new Person() ; } } |
可以发现静态块将优先于构造块执行,而且不管实例化多少个对象,静态块只执行一次。一般可以利用静态块为static属性初始化,但是这样操作并不如直接为static属性在定义的时候赋值操作方便。
范例:在主类之中定义静态块
public class TestDemo { static { System.out.println("*** 静态块。") ; } public static void main(String args[]) { System.out.println("Hello World .") ; } } |
静态块优先于主类先执行,所以在JDK 1.7之前,Java一直存在一个bug。在最早的版本之后Java一直提倡,所有的程序必须通过主方法开始执行,但是由于有静态块的存在,即使类之中没有主方法,也可以利用静态块来代替主方法。但是在JDK 1.7之后,这个bug解决了。
内部类在开发之中不是首要选择,但是一定会出现,现在先学习它的语法以及使用。
所谓的内部类指的是在一个类的内部继续定义了类的结构所形成的代码形式。
范例:观察内部类的定义
class Outer { // 外部类 private String msg = "Hello World ." ; class Inner { // 内部定义的类就是内部类 public void print() { // 内部类中定义的方法 System.out.println(msg) ; // 输出数据 } } public void fun() { new Inner().print() ; } } public class TestDemo { public static void main(String args[]) { Outer out = new Outer() ; out.fun() ; } } |
内部类的概念并不是很难理解,但是在定义内部类的时候你会发现一点问题:类的组成结构应该是属性和方法两个部分,但是现在又多出了一个类。首先就应该可以发现内部类的缺点 —— 破坏了程序的结构。
但是内部类也一定有自己的优点,那么它的优点是什么呢?如果要想观察出内部类的优点,最好的做法是将内部类拿到外部来。并且实现与之前同样的功能。
范例:内部类拿出来之后的代码
class Outer { // 外部类 private String msg = "Hello World ." ; public void fun() { new Inner().print() ; } } class Inner { // 内部定义的类就是内部类 public void print() { // 内部类中定义的方法 System.out.println(msg) ; // 输出数据 } } |
如果要想实现与之前同样的功能,需要考虑如下的几个问题:
1、 在Inner类之中的print()方法里,如果要想输出Outer类的msg属性,那么应该首先在Outer类里面增加一个getMsg()方法(private String msg);
public String getMsg() { return this.msg ; } |
2、 如果要想在Inner类之中去调用getMsg()方法,那么必须存在有Outer类对象。那么现在就需要想办法把主方法里面的out对象传递给Inner类之中
· 如果要想操作Inner类,则必须有一个Outer类对象,所以在Inner类里面增加一个Outer类对象
private Outer temp ; |
· 在Inner类里面定义一个构造方法,通过此构造方法传递Outer类对象;
public Inner(Outer temp) { this.temp = temp ; } |
· 在print()方法里面使用temp这个Outer类对象调用getMsg()方法;
· 在Outer类实例化Inner类的时候传递当前对象(this);
class Outer { // 外部类 private String msg = "Hello World ." ; public void fun() { new Inner(this).print() ; } public String getMsg() { return this.msg ; } } class Inner { // 内部定义的类就是内部类 private Outer temp ; public Inner(Outer temp) { this.temp = temp ; } public void print() { // 内部类中定义的方法 System.out.println(this.temp.getMsg()) ; // 输出数据 } } public class TestDemo { public static void main(String args[]) { Outer out = new Outer() ; out.fun() ; } } |
这么麻烦实际上只是希望完成一个类访问另外一个类的私有成员。所以此时就可以发现内部类的最大优点:可以方便的访问外部类之中定义的私有成员。同理,外部类可以方便的访问内部类的私有成员。
范例:外部类访问内部类的私有成员
class Outer { // 外部类 private String msg = "Hello World ." ; class Inner { // 内部定义的类就是内部类 private int num = 100 ; // 内部类私有成员 public void print() { // 内部类中定义的方法 System.out.println(msg) ; // 输出数据 } } public void fun() { Inner in = new Inner() ; in.print() ; System.out.println(in.num) ; // 内部类私有 } } public class TestDemo { public static void main(String args[]) { Outer out = new Outer() ; out.fun() ; } } |
从访问属性的角度上讲,一直强调:只要是访问类之中的属性一定要加上“this”。在内部类访问外部类msg属性的时候认为应该加上“this”才算合理。如果直接使用“this.属性”表示的是Inner类的属性,明显是错误的,所以下面应该使用“外部类.this.属性”才表示内部类访问外部类的属性。
class Outer { // 外部类 private String msg = "Hello World ." ; class Inner { // 内部定义的类就是内部类 public void print() { // 内部类中定义的方法 System.out.println(Outer.this.msg) ; // 输出数据 } } public void fun() { Inner in = new Inner() ; in.print() ; } } public class TestDemo { public static void main(String args[]) { Outer out = new Outer() ; out.fun() ; } } |
以上的所有代码定义的内部类最终都是在Outer类之中的方法里面操作的,那么在Java里面内部类也可以通过主方法直接调用。但是这个时候所使用的语法如下:
外部类.内部类 内部类对象 = new 外部类().new 内部类() ; |
在整个操作过程之中可以发现,先实例化了外部类对象,而后再实例化内部类对象,因为内部类有可能访问外部类之中的属性,而所有的属性一定要在关键字new之后才会分配空间。
范例:由外部操作内部类
class Outer { // 外部类 private String msg = "Hello World ." ; class Inner { // 内部定义的类就是内部类 public void print() { // 内部类中定义的方法 System.out.println(Outer.this.msg) ; // 输出数据 } } } public class TestDemo { public static void main(String args[]) { Outer.Inner in = new Outer().new Inner() ; in.print() ; } } |
现在可以发现内部类的名称是“外部类.内部类”,观察生成的*.class文件,可以发现内部类的文件是:“Outer$Inner.class”,在程序之中如果文件名称是“$”,那么换到程序里面就是“.”。现在就可以发现内部类的名称的确就是“外部类.内部类”。
如果说这个时候一个内部类不希望在外部使用,那么就使用private声明内部类。
class Outer { // 外部类 private String msg = "Hello World ." ; private class Inner { // 内部定义的类就是内部类 public void print() { // 内部类中定义的方法 System.out.println(Outer.this.msg) ; // 输出数据 } } } |
这个时候由于存在有private声明,所以外部无法去使用内部类。
如果说现在一个内部类不希望受到外部类之中属性的控制,而可以直接去实例化对象,那么在定义内部类的时候就可以使用static关键字声明,而且使用static关键字声明的内部类严格来讲就是一个外部类,只能够调用所在外部类的static属性。
范例:使用static定义内部类
class Outer { // 外部类 private static String msg = "Hello World ." ; static class Inner { // 内部定义的类就是内部类 public void print() { // 内部类中定义的方法 System.out.println(msg) ; // 输出数据 } } } |
同时在定义内部类对象的时候,格式也要发生变化:外部类.内部类 内部类对象 = new 外部类.内部类() ;
public class TestDemo { public static void main(String args[]) { Outer.Inner in = new Outer.Inner() ; in.print() ; } } |
对于使用者而言,初期的开发之中很少会出现此类代码,但是在学习Java类库的时候会遇见到,如果发现某些类名称上存在有“.”应该知道使用的是内部类。
理论上内部类可以在任意的位置上定义,这就包括了:类中、代码块中、方法中,下面重点来看一下在方法中定义内部类的情况。
范例:在方法中定义内部类
class Outer { // 外部类 private static String msg = "Hello World ." ; public void fun() { // 普通方法 class Inner { // 方法里定义的内部类 public void print() { System.out.println(Outer.this.msg) ; } } new Inner().print() ; // 实例化内部类对象调用方法 } } public class TestDemo { public static void main(String args[]) { new Outer().fun() ; } } |
但是在方法之中定义的内部类如果要想访问方法的参数或者是方法定义的变量,则参数或变量前应该加上一个关键字“final”(此处的final只是一个标记,并不是其真实使用)。
class Outer { // 外部类 private static String msg = "Hello World ." ; public void fun(final int num) { // 普通方法 class Inner { // 方法里定义的内部类 public void print() { System.out.println(Outer.this.msg) ; System.out.println(num) ; } } new Inner().print() ; // 实例化内部类对象调用方法 } } public class TestDemo { public static void main(String args[]) { new Outer().fun(100) ; } } |
在日后的开发之中,此类代码会经常使用到。内部类绝对不是设计初期要考虑的,初期的开发还是要以普通类为主。