黑马程序员--面向对象(二)static关键字、静态代码块、对象的初始化过程
static (静态)关键字
静态:static
用法:是一个修饰符,用于修饰成员(成员变量,成员函数)
当成员被静态修饰后,就多了一个调用的方式,除了可以被对象调用,还可以直接被类名调用就
语法:类名.静态成员
例:
1 class Person 2 { 3 String name;//成员变量,也叫实例变量 4 /*静态的成员变量,也叫类变量 5 使用static关键字,使之成员变量被共享*/ 6 static String country="CN"; 7 //定义静态方法,但是要注意静态只能访问静态 8 public static void show(){ 9 System.out.println("::"+country); 10 } 11 } 12 class StaticDemo 13 { 14 public static void main(String[] args) 15 { 16 Person p=new Person(); 17 p.name="zhangsan"; 18 //p.show(); 19 //直接被类名调用、 20 Person.show(); 21 System.out.println(Person.country);//使用静态调用,类名.成员 22 } 23 }
static内存特点:
- 随着类的加载而加载
也就是说:静态会随着类的消失而消失,说明它的生命周期最长
- 优先于对象存在
明确一点:静态是先存在,对象是后存在的
- 被所有对象所共享
- 可以直接被类名所调用
实例变量和类变量的区别
- 存放位置
类变量随着类的加载而存放在于方法区中
实例变量随着对象的建立存在于堆内存中
- 生命周期
类变量生命周期最长:随着类的消失而消失
实例变量生命周期随着对象的消失而消失
静态的使用注意事项
- 静态方法只能访问静态成员
非静态方法既可以访问静态也可以访问非静态
类优先于对象出现在内存中,先出现的不可以访问后出现的的,(静态出现时示例的还没有出现,所以无法访问)后出现的可以访问先出现的
- 静态方法中不可以定义this ,super关键字
因为静态优先于对象存在,所以静态方法中不可以出现this,super
- 主函数是静态的
静态有利有弊
利处:1.对对象的共享数据进行单独空间的存储,节省空间,没有必要每一个对象中都存储一份
可以直接被类名调用
弊处:生命周期长,
访问出现局限性(只能访问静态)
main主函数
public static void main(){String []args}
主函数:是一个特殊的函数,作为程序的入口,可以被JVM调用
主函数的定义:
Public: 代表着该函数访问权限是最大的
Static: 代表主函数随着类的加载就已经存在了
Void : 主函数没有具体的返回值
Main: 不是关键字,但是一个特使的单词,可以被JVM识别
(String []args):函数的参数,参数类型是一个数组,该数组中的元素是字符串,字符串类型的数组,数组名称args是可以改变的
主函数是固定的格式,JVM识别
JVM在调用主函数时,传入的是new String[0];长度为0的数组
可以看一下JVM在调用主函数时,往里传入的是什么
代码:
1 class MainDemo 2 { 3 public static void main(String[] args) 4 { 5 //看看JVM在调用主函数时传的什么参数 6 System.out.println(args); 7 //数组args的长度 8 System.out.println(args.length); 9 } 10 }
结果:
往主函数中传值:
编译后启动虚拟机,在执行一个类的同时传入参数
向参数中传了3个字符串:hahe hehe heihei
这时数组的长度为3了
主函数也能调用主函数:
代码:
1 class MainDemo 2 { 3 public static void main(String[] args) 4 { 5 String[] arr={"haha","hehe","heihei","xixi"}; 6 MainText.main(arr); 7 } 8 } 9 //不同类的两个主函数 10 class MainText 11 { 12 public static void main(String []args){ 13 for(int i=0;i<args.length;i++){ 14 System.out.println(args[i]); 15 } 16 } 17 }
结果:
当什么时候使用static
要从两个方面下手
因为静态修饰的内容有成员变量和函数
什么时候定义静态变量(类变量)?
当对象中出现共享数据时,该数据被静态所修饰,对象中的特有数据要定义称非静态存在于堆内存中
什么时候定义静态函数
当功能内部没有访问到静态数据或对象特有的数据,那么该功能可以定义成静态的
例:
1 class Person 2 { 3 String name; 4 public static void show(){//此方法没有使用到对象中的特有数据 5 System.out.println("haha"); 6 } 7 8 } 9 class PersonDemo6 10 { 11 public static void main(String[] args) 12 { 13 //Person p=new Person(); 14 /*把数据封装成类,使用对象来调用,是为了更 15 方便的调用它的属性与方法,而这个show()方法并没有使用 16 到类特有的数据,这样创建对象并没有意义,所以这种情况 17 可以将show()写成static的*/ 18 //p.show(); 19 Person.show(); 20 } 21 }
静态的应用:
每一个应用程序中都有共性的功能
可以将这些功能进行抽取,独立封装,以便复用
虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作
发现问题:
- 对象是用来封装数据的,可是ArrayTool对象并未封装特有数据
- 操作数组的每一个方法都没有用到ArrayTool对象中的特有数据
这时就考虑,让程序更严谨,是不需要对象的
可以将ArrayTool中的方法都定义成静态的,直接通过类名调用即可
将方法都静态后可以方便于使用,但是该类还是可以被其他程序建立对象的,为了更为严禁,强制让该类不能建立对像,可以通过将构造函数私有化完成
工具类代码:
1 //封装工具类 2 class ArrayTool 3 { 4 5 //将构造函数私有化,为了防止实例化对象 6 private ArrayTool(){} 7 //获取数组中最小的值 8 public static int getMax(int[] arr){ 9 int max=0; 10 for(int x=0;x<arr.length;x++){ 11 if(arr[x]>arr[max]){ 12 max=x; 13 } 14 } 15 return arr[max]; 16 } 17 //获取数组中最小的值 18 public static int getMin(int[] arr){ 19 int min=0; 20 for(int x=0;x<arr.length;x++){ 21 if(arr[x]<arr[min]){ 22 min=x; 23 } 24 } 25 return arr[min]; 26 } 27 //冒泡排序 28 public static void selectSort(int[] arr){ 29 30 for(int x=0;x<arr.length-1;x++){ 31 for(int y=0;y<arr.length-x-1;y++){ 32 if(arr[y]>arr[y+1]){ 33 swap(arr,y,y+1); 34 } 35 } 36 } 37 } 38 //选择排序 39 public static void bubbleSort(int[] arr){ 40 for(int x=0;x<arr.length-1;x++){ 41 for(int y=x+1;y<arr.length;y++){ 42 if(arr[x]>arr[y]){ 43 swap(arr,x,y); 44 } 45 } 46 } 47 } 48 //两个数交换,这个方法内部使用,不需要提供出去,所以私有化 49 private static void swap(int[]arr,int a,int b){ 50 int temp=arr[a]; 51 arr[a]=arr[b]; 52 arr[b]=temp; 53 } 54 //遍历数组 55 public static void showArray(int[] arr){ 56 for(int x=0;x<arr.length;x++){ 57 System.out.print(arr[x]+" "); 58 } 59 System.out.println(); 60 } 61 } 62 63 调用工具类的方法 64 class ArrayToolDemo 65 { 66 public static void main(String[] args) 67 { 68 int[] arr={3,4,1,8,5,3}; 69 //获取最大值 70 int max=ArrayTool.getMax(arr); 71 System.out.println("max="+max); 72 //获取最小值 73 int min=ArrayTool.getMin(arr); 74 System.out.println("min="+min); 75 //排序 76 ArrayTool.showArray(arr); 77 ArrayTool.selectSort(arr); 78 ArrayTool.showArray(arr); 79 80 } 81 }
制作程序说明书
将ArrayTool.class文件发送给其他人,其他人只要将该文件设置到classpath路径下,就可以使用该工具类
但是,很遗憾,该类中到底定义了多少个方法,对方却不清楚,因为该类并没有使用说明书
制作程序的说明书
1.给方法加注释使用文档注释符/** */
在dos命令行输入 javadoc –d myhelp –author –versinon ArrayTool.java
Myhelp:要保存的目录
-autor -version:(选择属性)
ArrayTool.java:文件名
静态代码块
格式:
static{
静态代码块中的执行语句
}
静态代码块的特点:
随着类的加载而执行,只执行一次,用于给类进行初始化,并优先于主函数
练习:
1 class StaticCode 2 { 3 static{ 4 System.out.println("a"); 5 } 6 } 7 class StaticCodeDemo 8 { 9 static{ 10 System.out.println("b"); 11 } 12 public static void main(String []args){ 13 new StaticCode();//当用到StaticCode类时才会加载 14 new StaticCode(); 15 System.out.println("over"); 16 } 17 static{ 18 System.out.println("c"); 19 } 20 }
结果:
b c a over
结论:静态代码块优先于主函数,并且静态代码块只有第一次new的之后只执行一次
练习2:
1 class StaticCode 2 { 3 int num=9; 4 StaticCode(){ 5 System.out.println("b"); 6 } 7 //静态代码块,给类初始化的 8 static{ 9 System.out.println("a"); 10 } 11 //构造代码块,给对象初始化的 12 { 13 System.out.println("c"+this.num); 14 } 15 //构造函数,给对应对象初始化的 16 StaticCode(int x){ 17 System.out.println("d"); 18 } 19 public static void show(){ 20 System.out.println("show run"); 21 } 22 } 23 class StaticCodeDemo 24 { 25 26 public static void main(String []args){ 27 new StaticCode(4); 28 29 } 30 }
结果:a c9 d
结论:静态代码块优先于类,构造代码块优先于构造函数
对象的初始化过程
1 class Person 2 { 3 private String name; 4 private int age; 5 private static String country="cn"; 6 Person(String name,int age){ 7 this.name=name; 8 this.age=age; 9 } 10 public void setName(String name){ 11 this.name=name; 12 } 13 public void speak(){ 14 System.out.println(this.name+"..."+this.age); 15 } 16 public static void showCountry(){ 17 System.out.println("country="+country); 18 } 19 20 } 21 class PersonDemo7 22 { 23 public static void main(String[] args) 24 { 25 Person p=new Person("zhangsan",20); 26 } 27 }
Person p=new Person(“zhangsan”,20);
该句话都做了什么事情?
- 1. 因为new用到了Person.class所以会先找到Person.class文件并加载到内存中
- 2. 执行该类中的static代码块,如果有的话,给Person.class类进行初始化,
- 3. 在堆内存中开辟空间,分配内存地址
- 4. 在堆内存中建立对象的特有属性,并进行默认初始化
- 5. 对属性进行显示初始化
- 6. 对对象进行构造代码块初始化
- 7. 对对象进行对应的构造函数初始化
- 8. 将内存地址赋给栈内存中的p变量