java class类对象在内存中的个数及使用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即可。抽象类中可以没有抽象方法。有抽象方法的必须是抽象类。

每篇一语

但它仅仅证明的是我的才华而已,它并不能改变我的生命,生活里的许多琐碎的事情,它还是得靠你一点一点去做。《朗读者》第十二期——余秀华

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值