文章目录
- 静态非静态区别
- class类对象在内存中有几个
- 执行static代码块、加载static方法、属性初始化的几种方式
- 使用默认的类加载器加载的class对象只有一个
- 自定义类加载器加载的class对象可以有多个
- static的注意事项
- 定义的static变量值不能使用运算符的方式
- 定义的static变量只有一份
类的定义:它是一个模板,它描述一类对象的行为和状态。
java中static修饰的属性、方法属于类。它跟非static的属性和方法有什么区别?class类对象在内存中唯一吗?
静态非静态区别
①static属性初始化先于非static属性初始化。且只会初始化一次(前提是没有使用自定义的ClassLoder)。
②static方法内不能使用this或super原因会在new对象之前先初始化static方法此时还没有对象故而不能使用。
③static修饰的属性跟方法可使用类名点及对象点的方式调用,而非static修饰的属性跟方法只能对象点的方式调用。
④static方法与非static方法在内存中只有一份,为了节约内存。
class类对象在内存中有几个
执行static代码块、加载static方法、属性初始化的几种方式
①class.forName。若class.forName没有执行则用clazz对象new对象的时候执行。(没有自定义类加载器)
②调用静态方法、静态属性时。
③new 对象时
注意:
以上三种任意一种方式执行了static代码块、加载static方法、属性初始化则其余方式不再进行初始化。
使用默认的类加载器加载的class对象只有一个
class.forName的作用是把class对象加载到内存中。没有指定加载器用默认的应用程序类加载器(Application ClassLoader)。若有static代码块只会在第一次调用Class.forName的时候执行,第二次不执行static代码块。对于静态的方法加载跟静态的属性初始化,也只会在第一次调用时执行。
//执行static代码块且执行一次。
Class<?> mathTeacherClazz1 = Class.forName("textJava.MathTeacher");
//不再执行static代码块
Class<?> mathTeacherClazz2 = Class.forName("textJava.MathTeacher");
//不会再执行static代码块,因为第一次调用 Class.forName时已执行。
MathTeacher mathTeacher = new MathTeacher();
System.out.println(mathTeacherClazz1 == mathTeacherClazz2 );//为true说明class类对象只有一个。
说明:
- 可以在执行Class.forName时指定执行或不执行静态代码块。默认是执行静态代码块。
//第二个参数为false则不执行static代码块
Class<?> mathTeacherClazz1 = Class.forName("textJava.MathTeacher",false,MathTeacherTest.class.getClassLoader());
- Class.forName只是类加载到内存的一种方式(反射)。理解成主动加载。比如Spring就是使用反射把类加载到内存中的。因为程序的入口不能一次性显示使用所有的类,显示调用形如new Student或者StaticValue.PORT都是显示调用。
- 加载类到内存中的顺序,若有父类父类不在内存中则先加载父类,然后加载此类到内存中。
自定义类加载器加载的class对象可以有多个
继承ClassLoader即可实现自定义加载器。重写findClass方法即可。
注意:
①只有使用findClass加载类才会与Class.forName加载的类不同。且findClass对于同一个类不能多次加载。
②若使用loadClass方法加载类则它会委托父类加载,父类加载不到才会调用子类的findClass方法。
③一个自定义加载器只能加载一个相同路径下的类即只能调用一次findClass。
④findClass没有执行静态代码块。只有在new实例的时候才会即执行newInstance方法时执行静态代码块。
自定义classLoader
public class MyClassLoader extends ClassLoader {
private static final String BASE_DIR = System.getProperty("user.dir");
//继承URLClassLoader 去加载url路径下的class文件
//没有重写loadClass。重写的是findClass。重写loadClass就是双亲委派模型,默认父类加载加载到了就不执行重写的findClass方法了
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = name.replaceAll("\\.", "/");
//加载的必须是.class文件,它的作用是把.class文件变成class对象。.class本身也是一个文件
fileName = BASE_DIR +"//textJava//src//"+ fileName + ".class";
try {
byte[] bytes = BinaryFileUtils.readFileToByteArray(fileName);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException ex) {
throw new ClassNotFoundException("failed to load class " + name, ex);
}
}
}
BinaryFileUtils工具类
public static byte[] readFileToByteArray(String fileName) throws IOException {
InputStream input = new FileInputStream(fileName);
ByteArrayOutputStream output = new ByteArrayOutputStream();
try{
copy(input, output);
return output.toByteArray();
}finally{
input.close();
}
}
public static void copy(InputStream input,
OutputStream output) throws IOException{
byte[] buf = new byte[4096];
int bytesRead = 0;
while((bytesRead = input.read(buf))!=-1){
output.write(buf, 0, bytesRead);
}
}
}
HelloService类
public class HelloService {
static {
System.out.println("动态代码块");
}
public void helloClassLoader(){
System.out.println("你好classloader");
}
}
测试classLoader
public class MyClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException{
//负责把其它类加载到内存
laoma.learnclassloder.MyClassLoader myClassLoader1 = new laoma.learnclassloder.MyClassLoader();
String className = "laoma.learnclassloder.HelloService";
//findClass没有执行静态代码块。只有在new实例的时候才会即执行newInstance方法时执行静态代码块
Class<?> myClassLoaderClass1 = myClassLoader1.findClass(className);
// myClassLoaderClass1.newInstance();
laoma.learnclassloder.MyClassLoader myClassLoader2 = new laoma.learnclassloder.MyClassLoader();
//loadClass也不会执行动态代码块,它执行的是classLoader的方法
Class<?> myClassLoaderClass2 = myClassLoader2.loadClass(className);
//只会在第一次Class.forName的时候执行static代码块,给参数false不执行
Class<?> class3 = Class.forName(className);
Class<?> class4 = Class.forName(className);
System.out.println(myClassLoaderClass1 == myClassLoaderClass2); //false
System.out.println(myClassLoaderClass1 == class3);//false
System.out.println(myClassLoaderClass2 == class3);//true
System.out.println(class3 == class4);//true
}
}
参考老马说编程
static的注意事项
自定义classloder不常用,以下的注意事项针对默认的加载器加载类到内存。
定义的static变量值不能使用运算符的方式
项目在启动时若没有输入-Dport参数则使用默认的,若输入了则使用输入的-Dport参数值。
public class StaticValue {
public static String IP = "127.0.0.1";
public static String PORT = "1990";
public static String IP_PORT = IP + ":"+PORT;
public static void changePort(String port) {
PORT = port;
//IP_PORT已在调用PORT时进行了初始化必须重新赋值
IP_PORT = IP + ":"+PORT;
}
}
测试代码 比如输入的-Dport值为3000。idea在Run/Debug Configurations界面vm options写上-Dport=3000。多个参数以空格分隔。可以配置VM参数。这里不做展开。-D是开头固定写法,获取时去掉-D。
public static void main(String[] args) {
//定义的static变量值不能依赖于另一个会改变的static值。相加之后会得到原来的默认值。
String dPort = System.getProperty("port");
if(!"1990".equals(dPort))
StaticValue.PORT=System.getProperty("port");
System.out.println(StaticValue.PORT);//3000
System.out.println(StaticValue.IP_PORT);//127.0.0.1:1990
}
输出的结果是不是很惊讶,IP_PORT的值为什么不是127.0.0.1:3000。在StaticValue.PORT的时候就已经初始化IP_PORT,而此时StaticValue.PORT还没有被赋值。如何解决?在StaticValue 加上changePort方法。
StaticValue.PORT=System.getProperty("port");
//把上面的改成下面的写法即可
StaticValue.changePort(System.getProperty("port"));
定义的static变量只有一份
我们在StaticValue类 String PORT加上
public static HelloService HELLO_SERVICE= new HelloService();
HelloService类代码已在上面给出。当StaticValue.HELLO_SERVICE的时候,每次获取的是相同对象吗?new StaticValue的时候.HELLO_SERVICE获取是同一个对象吗?
public static void main(String[] args) {
//结果均为true
System.out.println(StaticValue.HELLO_SERVICE==StaticValue.HELLO_SERVICE);
StaticValue staticValue1 = new StaticValue();
StaticValue staticValue2 = new StaticValue();
System.out.println(staticValue1.HELLO_SERVICE==staticValue2.HELLO_SERVICE);
System.out.println(staticValue1.HELLO_SERVICE==StaticValue.HELLO_SERVICE);
}
结果都是true。不是new 的StaticValue为什么都为true。相信你已经知道原因,static属于类只会初始化一次。所以地址是同一个地址。
小技巧:StaticValue是一个常量类不应该new对象,上面只是为了演示效果。如何从源头杜绝?把StaticValue变成抽象类在class前abstract即可。抽象类中可以没有抽象方法。有抽象方法的必须是抽象类。