on java 8 学习笔记 2022.2.16

本文深入探讨了Java中的垃圾回收机制,包括引用计数和对象存活链两种策略,并强调了初始化与清理的重要性。枚举类型作为常量集合的高效替代,提供了类型安全和便利性。此外,文章还介绍了Java 11引入的类型推断在for循环中的应用,以及其限制。同时,讨论了可变参数列表的使用规则,避免重载方法时的歧义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2022.2.16

问题

  1. 其实我感觉引用计数的方法不只书中提到的这种问题吧,难道不会有对象被误删的情况吗?

    答:不会,因为这种方法就是参考了只要有引用,它就是有效对象的路子,而只要引用大于0,那它就是有效对象,

  2. 这种初始化方法简单明了,但有个限制:类InitialValues每个对象都会有相同的初始值。有时候这正是你想要的,但有时你可能需要更大的灵活性。

    看不懂,啥意思?我觉得已经挺灵活的了

    答:看了6.7,感觉用构造器初始化不就是我们正常的操作吗?秀了我一脸懵逼,这有啥好灵不灵活的

    答:应该是这么写的话,就相当于直接赋初始值,而构造器灵活的地方可能在于它是方法赋值,可以多样化赋值,虽然我还是很难说服自己

  3. // housekeeping/VarargType.java
    
    public class VarargType {
    static void f(Character... args) {
     System.out.print(args.getClass());
     System.out.println(" length " + args.length);
    }
    static void g(int... args) {
     System.out.print(args.getClass());
     System.out.println(" length " + args.length);
    }
    public static void main(String[] args) {
     f('a');
     f();
     g(1);
     g();
     System.out.println("int[]: " +
       new int[0].getClass());
    }
    }
    /* 输出:
    class [Ljava.lang.Character; length 1
    class [Ljava.lang.Character; length 0
    class [I length 1
    class [I length 0
    int[]: class [I
    */
    

    getClass()方法是Object的一部分,将在第19章中进行全面探讨。它会生成一个对象的类,当打印这个类时,你会看到一个表示该类类型的编码字符串。前导的[表示这是后面紧随的类型的数组。I表示基本类型int。为了再次确认,我在最后一行创建了一个int数组并打印了它的类型。这证实了使用可变参数列表不依赖于自动装箱,这个示例实际上用的就是基本类型。

    这最后我是没看懂的,我觉得根本不会有这个误区,事实上,我都不知道为什么会有人认为他会被包装,因为你参数列表都是int,你给我装啥?

    答:这里有点绕,其实是因为作者提前输了1,0到里面去,然后程序输出的类型是I,可能有读者脑子没扭过来,因为这里可能是包装类,也可能是基本数据类型,所以作者又new了一个int数组进去,这样就可以确定了,因为数组是基本数据类型的,又是列表的一种,所以它的数据类型不应该变动

  4. 以前,你需要创建一组整型常量值,但它并没有自然地将取值范围限制在这个集合中,因此风险更大且更难使用。

    我……………这又是个啥意思,啥时候的枚举类型,我从来没用过,没经验啊wc

第六章 初始化和清理

  1. 一些更快的方案并不使用引用计数,而是基于这样一个想法:对于任何没有被废弃的对象,最终都能追溯到它存活在栈或静态存储区中的引用。这个引用链可能会穿过多个对象层次。因此,如果从栈和静态存储区开始遍历所有引用,就能找到所有存活的对象。对找到的每个引用,还要跟踪它指向的对象,然后跟踪那个对象中的所有引用,依次反复进行,直到找到了源于这个(位于栈或静态存储区的)引用的所有对象。在这个过程中遍历的每个对象都必须是“活”的。注意,这样的话,废弃的自引用对象组就不会产生问题了——它们根本不会被找到,因此自动成为垃圾。

    我看了两遍,然后这种方法其实就是一个引用指向的对象必然不可能是废弃对象,那么我只需要找到所有的有效引用,然后把它们的对象复制到一个新区里,那么剩下的在原区里的就一定是废弃对象,把他们全清楚了就完事了

    在想一个问题,按这个道理的话,是不是有引用不是在栈或者静态存储区里?如果引用都是在栈或者静态存储区的话,那么不是应该在遍历完栈中所有的引用以后,就可以确定存活的对象.

    那么按照这个逻辑考虑的话,应该是有引用可能存储在堆中的,这样比较符合实际的情况,之所以堆中可能存在引用,是因为堆中的对象里可能有引用了其他的对象

    那么按照这个方向思考下去,利用栈和静态存储区的思路,就是将原来遍历一整个堆的操作数缩小到只是遍历其中部分对象的操作数,确实很奇妙

  2. java垃圾回收是将庞大的内存空间分解成一个个更小单元的块,以此尽量减少内存空间的使用

  3. 有了块之后,垃圾收集器就可以将对象直接复制到废弃的块里。

    不知道为什么,我感觉这里有点问题,应该是把对象复制到新的块中,毕竟,这种算法标记的是存活的对象,而且,把对象复制到废弃的块中不合理.那些废弃块中的对象本身不就是要删除的,所以我个人比较认同我在其他里贴出来的博客的说法

  4. 注意下初始化对象时,对象内部的变量编译是在构造器前的,至于具体是编译时完成的还是运行时完成的,我个人感觉是运行时完成的.

  5. 初始化的顺序是从静态字段开始(如果它们还没有被先前的对象创建触发初始化的话),然后是非静态字段。

    如果有加载静态方法,或者使用静态方法的话,那么本类中的静态对象都会被全部激活并初始化,如果还有静态对象,静态对象也会被初始化

  6. 为了总结对象创建的过程,假设有一个名为Dog的类。

    1. 尽管没有显式使用static关键字,但构造器实际上也是静态方法。因此,第一次创建类型为Dog的对象时,或者第一次访问类Dog的静态方法或静态字段时,Java解释器会搜索类路径来定位Dog.class文件。
    2. Dog.class被加载后(这将创建一个Class对象,后面会介绍),它的所有静态初始化工作都会执行。因此,静态初始化只在Class对象首次加载时发生一次。
    3. 当使用new Dog()创建对象时,构建过程首先会在堆上为Dog对象分配足够的存储空间。
    4. 这块存储空间会被清空,然后自动将该Dog对象中的所有基本类型设置为其默认值(数值类型的默认值是0,booleanchar则是和0等价的对应值),而引用会被设置为null
    5. 执行所有出现在字段定义处的初始化操作。
    6. 执行构造器。正如将在第8章中看到的,这实际上可能涉及相当多的动作,尤其是在涉及继承时。
  7. 注意下创建和初始化是两码事,一定要牢记这一点,而也正是这个原因才有了在构造器前的自动初始化.而构造器初始化实际上可以理解成又一次的初始化

  8. 在执行类中的非静态方法时,代码块中的语句会先被执行

    package example;
    
    public class two {
    }
    class three extends  two{
        int i=0;
        {
            System.out.println("heool");
        }
        public void fun(){
            System.out.println(new three().i);
        }
    
        public  static void main(String[] args) {
            three temp = new three();
            temp.fun();
        }
    }
    
  9. 其实这里可以总结一下,实际上有关于方法和构造器初始化,因为二者实际上都是方法,就可以看作是类的内部变量和块总是要先进行,然后才是方法的运行

  10. Integer[] a = {1,  2, 3, };
    Integer[] b = new Integer[]{ 1,  2, 3,  };
    

    这种写法是合法的

    public class DynamicArray {
        public static void main(String[] args) {
         Other.main(new String[]{ "fiddle", "de", "dum" });
        }
    }
    
    class Other {
        public static void main(String[] args) {
         for(String s : args)
           System.out.print(s + " ");
        }
    }
    

    注意下,字符串都是引用,可以说java里除了基本数据类型就都是引用了,一定要记得这一点

    还有这里直接调用main真挺有意思的

  11. 如果没有为自己的类定义toString()方法(这将在本书后面讲解),可以看到默认行为就是打印类名和对象的地址。

  12. public class VarArg {
        static void printArray(Object[] args) {
            for(Object obj : args)
                System.out.print(obj + " ");
            System.out.println();
        }
        public static void main(String[] args) {
            printArray(new Object[]{
                    47, (float) 3.14, 11.11});
            printArray(new Object[]{"one", "two", "three" });
            printArray(new Object[]{new A(), new A(), new A()});
        }
    
    }
    class A {
        @Override
        public String toString() {
            return "hello";
        }
    }
    

    很有意思,一个什么都能塞的数组,里面东西还都不一样

    还有,把Object后的换成…也是一样的效果,我这里就不粘贴了

    然后还有如果你有数组不是Object[]这样的,你要强制转化一下

    这里还有一个,就是继承后再运行时的多态现象,即Object,表现出了A的性质

    然后,因为学艺不精,无法理解为什么这样子无法通过编译

    public class VarArg {
        static void printArray(Object[] args) {
            for(Object obj : args)
                System.out.print(obj + " ");
            System.out.println();
        }
        public static void main(String[] args) {
            printArray(new Object[]{
                    47, (float) 3.14, 11.11});
            printArray(new Object[]{"one", "two", "three" });
            printArray(new Object[]{new A(), new A(), new A()});
        }
        class A {
            @Override
            public String toString() {
                return "hello";
            }
        }
    }
    
  13. 看了很久,这个可变参数列表貌似就是传一个数组进去,不过就是从本来‘’类名[]’这样的形式换了下而已

  14. 根据经验,你应该只在其中一个重载方法上使用可变参数列表,或者压根儿就不使用它。

    我为什么要看这玩意,我吐了

  15. 好吧,可变参数列表还是有很奇妙的地方的,因为我们现在可以这么写了

    public class OptionalTrailingArguments {
      static void f(int required, String... trailing) {
        System.out.print("required: " + required + " ");
        for(String s : trailing)
          System.out.print(s + " ");
        System.out.println();
      }
      public static void main(String[] args) {
        f(1, "one");
        f(2, "two", "three");
        f(0);
      }
    }
    

    什么意思呢?就是我们输进去的参数现在理论上可以无限多了

  16. // housekeeping/OverloadingVarargs2.java
    // {WillNotCompile}
    
    public class OverloadingVarargs2 {
    static void f(float i, Character... args) {
     System.out.println("first");
    }
    static void f(Character... args) {
     System.out.print("second");
    }
    public static void main(String[] args) {
     f(1, 'a');
     f('a', 'b');
    }
    }
    

    {WillNotCompile}注释标签会把该文件排除在本书的Gradle构建之外。

    如果你手动编译它,就会看到如下所示的错误消息:

    OverloadingVarargs2.java:14: error: reference to f is ambiguous
    f('a', 'b');
    \^
    both method f(float,Character...) in OverloadingVarargs2 and method
    f(Character...) in OverloadingVarargs2 match
    1 error
    

    如果你给这两个方法添加一个非可变参数,就没有问题了:

    // housekeeping/OverloadingVarargs3.java
    
    public class OverloadingVarargs3 {
    static void f(float i, Character... args) {
     System.out.println("first");
    }
    static void f(char c, Character... args) {
     System.out.println("second");
    }
    public static void main(String[] args) {
     f(1, 'a');
     f('a', 'b');
    }
    }
    /* 输出:
    first
    second
    */
    

    根据经验,你应该只在其中一个重载方法上使用可变参数列表,或者压根儿就不使用它。

    这里作者有一点没说清楚,他这么操作实际上就是f()找不到对应的方法,实际上,改成了最后一种你也找不到对应的方法,所以我想作者的意思是,不要偷懒,没有参数的方法如果要有,你就乖乖的写去,不要乱整些没用的骚操作,我重新看了遍,确定了作者应该是写嗨了

    反正总而言之,因为全写可变参数列表会导致缺少默认方法,所以尽量少写,不过我觉得倒没什么,虽然我不用

  17. 对于枚举类型,实际上它的作用就是跳过了字符串,直接将字符串和数字画上等号,不过我现在能想到的有意思的应用就是在一些特殊情况下,将数组下标转化成枚举类型的字符串形式

    public enum Spiciness {
      NOT, MILD, MEDIUM, HOT, FLAMING
    }
    
    public class SimpleEnumUse {
      public static void main(String[] args) {
        Spiciness howHot = Spiciness.MEDIUM;
        System.out.println(howHot);
      }
    }
    /* 输出:
    MEDIUM
    */
    
  18. 然后,枚举类型的使用,你可以声明对象,然后初始化对象的方式使用枚举类型,或者你直接类似静态变量一样对枚举类型内的值进行操作,不需要new,这个应该是牵扯到了单例模式

  19. public class EnumOrder {
    public static void main(String[] args) {
     for(Spiciness s : Spiciness.values())
       System.out.println(
         s + ", ordinal " + s.ordinal());
    }
    }
    

    一个遍历枚举类型的思路,可以看看

    其实感觉枚举类型有点像接口的静态变量,不过有一点不同吧,接口的静态变量不能像这样直接遍历过去

  20. // housekeeping/TypeInference.java
    // {NewFeature} 从JDK 11开始
    
    class Plumbus {}
    
    public class TypeInference {
        void method() {
         // 显式类型:
         String hello1 = "Hello";
         // 类型推断:
         var hello = "Hello!";
         // 用户定义的类型也起作用:
         Plumbus pb1 = new Plumbus();
         var pb2 = new Plumbus();
        }
    // 静态方法里也可以启用:
        static void staticMethod() {
         var hello = "Hello!";
         var pb2 = new Plumbus();
        }
    }
    
    class NoInference {
    String field1 = "Field initialization";
    // var field2 = "Can't do this";
    // void method() {
    //   var noInitializer; // No inference data
    //   var aNull = null;  // No inference data
    // }
    // var inferReturnType() {
    //   return "Can't infer return type";
    // }
    }
    

    怎么说呢,有点鸡肋的感觉.简而言之,这个你不能在类中作为字段使用,不能作为返回值使用,不能用来声明变量(这声明本身就没什么意义,都不知道要分配多少空间)

  21. 类型推断十分适合for循环:

    // housekeeping/ForTypeInference.java
    // {NewFeature} 从JDK 11开始
    
    public class ForTypeInference {
    public static void main(String[] args) {
     for(var s : Spiciness.values())
       System.out.println(s);
    }
    }
    /* 输出:
    NOT
    MILD
    MEDIUM
    HOT
    FLAMING
    */
    

    将类型推断作为基本概念而创建的语言——如Kotlin和Scala——允许在任何可能有意义的地方进行类型推断,而Java则受到向后兼容性问题的限制。使用这个新功能的最佳方法,可能是在任何你认为可以的地方尝试它,并让编译器或你的IDE来提示是否可以这样用。

    简单说,我能不用就不用,即便他貌似还挺好用的,不过想了想算了

其他

  1. 内存分页调度https://blog.csdn.net/Bob__yuan/article/details/102584606,我看不懂,但我大受震撼
  2. 有关于停止复制和标记清理算法https://blog.csdn.net/u010841296/article/details/50945390
  3. 字段—-类变量
  4. 有关于访问修饰符,其实可以这么想,public最公开,不用说,private最封闭也不需要说,然后是protected,这个其实不给包外访问权限,但如果包外有类继承他,那么他还是会给访问权限的,而默认就是直接断绝包外的一切联系了,但包内没有影响
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cjz-lxg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值