day06_静态(static)
static关键字:
static是一个修饰符,用于修饰成员(成员变量,成员函数), 不能修饰局部变量(局部变量生命周期随着作用域结束而结束).
当成员被static修饰后,除了可以被对象调用,还可以用 类名.静态成员
/* 方法区:存放类中的方法(函数包括构造函数)和共享数据(多个对象共享) static特点: ①随着类的加载而加载,随着类的消失而消失. 当person类被使用,加载到内存 country="cn"已经在开辟好了空间 String name;还没有在内存中,因为还没有创建对象 ②根据以上 静态成员 先于 对象存在 ③被该类所有对象共享 ④可以直接被类名调用 */ class Person { String name;//成员变量/实例变量 static String country="CN";//静态成员变量/类变量 public static void showCountry() { //System.out.println(name);//根据①,此时可能没有任何对象, //而通过类名直接调用showCountry() //那么name属于哪个对象?说白了,对象 //都还没有,哪来的name. System.out.println(country); } } class StaticDemo { public static void main(String[] args) { Person p=new Person(); p.showCountry();//CN Person.showCountry();//CN } } /* (理解为主!) 类变量和实例变量的区别: 1.存放位置 类变量 随着类的加载 而存在于 方法区 中 实例变量 随着对象的建立 而存在于堆内存中 2.生命周期 类变量>实例变量(当对象消失,对象中的实例变量也跟着消失,但类依然还在 类变量也还存在) 类变量生命周期最长,随着类消失而消失 实例变量随着对象的消失而消失. */ /* 静态使用注意事项: Ⅰ.静态方法 只能访问 静态成员 非静态方法 既可以访问 静态 也可以访问 非静态 (静态先在,非静态后再) Ⅱ.静态方法中 不可以 定义 this,super 关键字 因为静态先于对象存在,所以静态方法中不可以有this Ⅲ.主函数是静态的 */
static优点与缺点:(理解)
优点:对 对象共享的数据单独进行存储,有利于节约空间(不用每个对象都存储一份)
可以通过类名直接调用.
缺点:生命周期过长,只能访问静态.
什么时候用静态呢?
/* 一.什么时候定义静态变量(类变量)? 当对象中出现共享数据时,存放该数据的变量定义成静态 而对象中特有的数据定义成非静态存放在堆内存中. 二.什么时候定义静态方法? 当该方法中没有访问到对象中特有的数据时 */ class Person { String name; //public void show() public static void show() { System.out.println("show()"); } } class StaticF { public static void main(String[] args) { //Person p=new Person(); //p.show(); Person.show();//由于show()中没有用到非静态变量(对象特有数据) //完全可以把show()用static修饰 通过类名访问 } }
静态代码块(StaticOfCode):
示例:
/* 静态代码块: 格式: static { 静态代码块中的执行语句 } 特点: ①随着类的加载而执行,只执行一次(当第二次用到该类时,将不再执行类中的静态代码块),并优先于主函数执行.(主函数被JVM调用才执行) ②用于给类进行初始化
类什么时候加载? 只有用到 类中的内容才加载 */ class StaticCode { static { System.out.println("a"); } } class StaticCodeDemo { static//①首先加载StaticCodeDemo类 该静态代码块执行 { System.out.println("b"); } public static void main(String[] args)//主函数需要JVM调用才执行 { //③JVM调用主函数 new StaticCode();//④加载StaticCode类 执行其静态代码块 new StaticCode();//StaticCode已经加载,由于静态代码块只执行一次 //因此不再执行静态代码块//StaticCode s=null;//将不再加载该类,s没有指向任何对象 //s=new StaticCode();//用到了类中的默认构造函数 System.out.println("over"); } static//②执行该静态代码块 { System.out.println("c"); } }与构造代码块示例:
class StaticCode { int num=10; StaticCode(int num) { System.out.println("StaticCode的构造函数"); this.num=num; } static { System.out.println("StatiCode的静态代码块"); } { System.out.println("StaticCode的构造代码块"+""+num); } } class StaticCodeDemo { static { System.out.println("StaticCodeDemo的静态代码块 1"); } public static void main(String[] args) { System.out.println("主函数"); StaticCode s=new StaticCode(4); } static { System.out.println("StaticCodeDemo的静态代码块 2"); } }
为了更清楚以上过程,首先来看一下对象初始化过程.
示例:
class Person { private int age; private String name; private static String country="cn"; Person(String name) { this.name=name; } Person(int age,String name) { this.age=age; this.name=name; } public void setName(String name) { this.name=name; } public void speak() { System.out.println(this.name+"..."+this.age); } public static void country() { System.out.println("country="+country); } } class Demo { public static void main(String[] args) { Person p=new Person("lisi"); } } /* Person p=new Person("lisi"); 在内存中(注意顺序): ①new Person("lisi")用到Person.class文件,JVM查找并加载Person.class ②执行静态代码块,如果有,给Person类初始化 ③在堆内存中开辟空间,分配内存地址 ④在堆内存中建立对象特有(非static)属性,进行默认初始化(null,0...) ⑤对属性进行显式初始化(private int age=10;) ⑥对 对象 进行 构造代码块 初始化 ⑦对 对象 进行对应 构造函数 初始化
⑧将 堆内存中的地址 赋给 栈内存中 的对象引用变量p ⑤和⑥的执行顺序与代码的书写顺序一致 如下流程图: */
在上面的主函数中加上:
Person p2=new Person("zhangsan",20);
p2.setName("lisi");
单例设计模式:
/* 设计模式:解决某一问题行之有效的方法 java中23中设计模式 单例设计模式:解决一个类在内存中只存在一个对象 要求:A和B程序 操作的同一对象 ①将构造函数私有化 ②在类内部创建一个本类对象 ③对外提供一个方法获取这个对象 */ //饿汉式(安全简单)-->开发采用 //Single类一进内存就创建了对象 class Single { private int num; private Single(){} //禁止类外初始化(建立)对象(根据需要的构造函数) private static Single s=new Single(); public static Single getInstance() //由于要求外部不能创建对象 { //要想访问该方法必须通过类名访问 return s; //因此加上static修饰 } public void setNum(int num) { this.num=num; } public int getNum() { return num; } }//懒汉式
//调用方法时才创建对象,叫做对象的延迟加载—>懒汉式//Single类进内存还没有创建对象,调用getInstance时才创建对象 class Single() { private static Singel s=null; private Single(){} public static Single getInstance()//在Single 前加 synchronized 同步 学到多线程技术深入理解 { if(s==null) { /*
-->A A执行到此处,cpu切换到B
-->B s==null依然满足
-->A A创建对象返回
-->B此时B又创建对象返回
*/ s=new Single(); return s } //改进写法: //if(s==null) //{ // synchronized(Single.class) // { // if(s==null) // s = new Single(); // } } } } class SingleDemo { public static void main(String[] args) { Single s1=Single.getInstance(); Single s2=Single.getInstance(); s1.setNum(30); System.out.println(s2.getNum()); } }
解析下主函数:
public static void main(String[] args){…}①为什么要加public修饰符?这是因为JVM需要调用类的main()方法,这时把main方法暴露出去,权限足够大,所以方法的权限必须是public
②为什么要加static修饰符?这是因为JVM在执行main()方法是不必创建对象,所以方法必须是staticvoid:JVM调用的main()方法要求返回值为voidmain:不是关键字,但是能够被JVM识别Sting[] args:形参为一个 字符串数组的引用变量 args为什么写args?arguments(参数)逐渐演变而来,当然可以用别的引用变量JVM在调用main()方法时传入的为new String[0];(这个可以再main()中验证)
帮助文档制作:
简单示例://ArrayTool.java /** 这是一个可以对数组进行操作的工具类,该类中提供,排序,求最值等功能 @author 张三 @version V1.1 */ public class ArrayTool { private ArrayTool(){}//不加private,javadoc.exe也不会提取,更改访问权限为public /** 这是交换数组中的两个元素 @param b 接收一个int[] 型的数组引用变量 @param i 要交换的值 @param j 要交换的值 */ //交换 private static void swap(int[] b,int i,int j) { int temp=b[i]; b[i]=b[j]; b[j]=temp; } /** 这是对数组中的元素从小到大排序 @param a 接收一个int[] 型的数组引用变量 */ //冒泡排序 public static void bubbleSort(int[] a) { for(int i=0;i<a.length-1;++i)//控制趟数 for(int j=0;j<a.length-i-1;++j)//控制每趟比较的次数 if(a[j]>a[j+1]) swap(a,j,j+1); } /** 这是对数组中的元素从小到大排序 @param b 接收一个int[] 型的数组引用变量 */ //选择排序 public static void selectSort(int[] b) { for(int i=0;i<b.length-1;++i)//控制趟数 for(int j=i+1;j<b.length;++j)//共比较(arr.length-1)-(i+1)+1 if(b[i]>b[j]) //即arr.length-i-1 swap(b,i,j); } //选择排序第二种写法: /** 这是对数组中的元素从小到大排序 @param c 接收一个int[] 型的数组引用变量 */ public static void selectSort_2(int[] c) { int k; for(int i=0;i<c.length-1;++i) { k=i; for(int j=i+1;j<c.length;++j) if(c[k]>c[j]) k=j; if(k!=i) swap(c,k,i); } } /** 打印数组中的元素. 打印格式:[element1,element2,....] @param arr 接收一个int[] 型的数组引用变量 */ //打印数组 public static void showArray(int[] arr) { System.out.print("["); for(int i=0;i<arr.length;++i) if(i!=arr.length-1) System.out.print(arr[i]+","); else System.out.println(arr[i]+"]"); } /** 获取一个整形数组中的最大值. @param arr 接收一个int[] 型的数组引用变量 @return 会返回一个数组中的最大值 */ //求最大值 public static int getMax(int[] arr) { int max; max=arr[0]; for(int i=1;i<arr.length;++i) if(arr[i]>max) max=arr[i]; return max; } //求最小值 /** 获取一个整形数组中的最小值. @param arr 接收一个int[] 型的数组引用变量 @return 会返回一个数组中的最小值 */ public static int getMin(int[] arr) { int min; min=arr[0]; for(int i=1;i<arr.length;++i) if(arr[i]<min) min=arr[i]; return min; } } /* 一个类默认会有一个空参数的构造函数 这个默认的构造函数的权限和所属类一致 即默认构造函数的权限随着类的变化而变化 注意: class Demo { Demo(){}//这个不是默认构造函数,是自定义构造函数 } */ 用javadoc.exe提取文档注释:
当两个类在不同的Java文件,并且一个类用到另一个:
例如:(用到上面的ArrayTool.java)//ArrayToolTest.java class ArrayToolShow { public static void main(String[] args) { int[] arr={3,7,1,2,5}; //ArrayTool tool=new ArrayTool(); //tool.bubbleSort(arr); //tool.showArray(arr); ArrayTool.bubbleSort(arr);//把ArrayTool中的bubbleSort定义成静态,通过类名 ArrayTool.showArray(arr); } }
用到ArrayToolTest.java中的类.注意:当使用 Javac ArrayToolTest.java(其中用到类ArrayTool) 时,JVM会在指定路径(classpath)找有没有ArrayTool.class文件,若有则编译成功没有则JVM 会在classpath路径下再找一次看有没有ArrayTool.java(名称必须和类名相同) 有则先编译ArrayTool.java,之后再编译ArrayToolTest.java.也可以set classpath=.;类所在的目录 //先找当前目录再找指定目录(当两个java文件不在同一个目录下)