0%

JAVA杂记

JAVA Q&A

1. JAVA一个文件写多个类 ( 同级类 ) 规则和注意点

  • 在一个.java文件中可以有多个同级类, 其修饰符只可以public/abstract/final/和无修饰符
  • public修饰的只能有一个,且必须要与文件名相同;
1
//因为jvm虚拟机为了提高查找类的速度,使用import语句导入的时候,只会导入对应空间的文件名所对应的class文件,而public文件是大家都要使用的,因此直接导入这个类名对应的class文件即可。
  • 没有public的则可与文件名不同

    1
    //Java编译器在编译的时候,如果整个Java文件(编译单元)都没有public类(对外的公开接口类),类加载器子就无需从这方面直接去加载该编译单元产生的所有的字节码文件(.class文件),那么也就是无需去寻找编译后字节码文件存放位置。而类名和文件名一致是为了方便虚拟机在相应的路径中找到相应的类所对应的字节码文件。所以在没有public类的Java文件中,文件名和类名都没什么联系。
  • 该文件同级的类之间可以互相调用,但是除了public的类,其他不能够在其他文件调用

  • 在一个.java文件中由类/Enum/接口/Annotation其中至少一个类型组成。单独一个方法/变量不能独自存在与文件中,所以公用方法的封装也是做成类方法。原因是java是类加载机制,需要编译一个java文件成多个class文件,当类来使用。

  • 用javac 编译这个.java文件的时候,它会给每一个类生成一个.class文件

2. 抽象类可以有constructor吗?

Yes ! *是的* Abstract classes can have constructors ! *抽象类可以有构造函数*

Yes, when we define a class to be an Abstract Class it cannot be instantiated but that does not mean an Abstract class cannot have a constructor. 是的,当我们将类定义为抽象类时,它无法实例化,但这并不意味着抽象类不能具有构造函数。 Each abstract class must have a concrete subclass which will implement the abstract methods of that abstract class. 每个抽象类必须有一个具体的子类,它将实现该抽象类的抽象方法

When we create an object of any subclass all the constructors in the corresponding inheritance tree are invoked in the top to bottom approach. 当我们创建任何子类的对象时,相应的继承树中的所有构造函数都是从上到下的方法调用的。 The same case applies to abstract classes. 同样的情况适用于抽象类。 Though we cannot create an object of an abstract class, when we create an object of a class which is concrete and subclass of the abstract class, the constructor of the abstract class is automatically invoked. 虽然我们不能创建抽象类的对象,但是当我们创建一个类的对象时,它是抽象类的具体和子类,抽象类的构造函数会被自动调用。 Hence we can have a constructor in abstract classes. 因此,我们可以在抽象类中使用构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public abstract class TestEngine
{
private String engineId;
private String engineName;

public TestEngine(String engineId , String engineName)
{
this.engineId = engineId;
this.engineName = engineName;
}
//public gettors and settors
public abstract void scheduleTest();
}


public class JavaTestEngine extends TestEngine
{

private String typeName;

public JavaTestEngine(String engineId , String engineName , String typeName)
{
super(engineId , engineName);
this.typeName = typeName;
}

public void scheduleTest()
{
//do Stuff
}
}

3.JAVA inner class

inside another class or method

  • 成员内部类

  • 局部内部类

  • 匿名内部类

  • 静态内部类

4.ragged array

java 2d array并不是真的开辟一个二维矩阵,只是指定行数,列数非必须

每行指向一个array object,这些array的长度可以不等

5.JAVA 反射

基础理论

  1. A类 -> A.class字节码文件 -> 加载到JVM后的A字节码文件对象(Class对象)
  2. A的Class对象 -> A的实例
  3. Class是反射的基础
  4. 当new一个新对象或者引用静态成员变量等时机时,JVM类加载器系统会将对应Class对象加载到JVM中,然后JVM根据Class对象创建实例对象或者提供静态变量的引用值。
  5. 每个类,无论创建多少个实例,在JVM中都对应同一个Class对象(类被不同的类加载器加载除外)。

主动引用

对于字节码文件的加载时机,《Java虚拟机规范》中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。
    • 使用new关键字实例化对象的时候。
    • 读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候。
    • 调用一个类型的静态方法的时候。
  2. 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
  3. 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  5. 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
  6. 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

被动引用

这六种场景中的行为称为对一个类型进行主动引用。除此之外,所有引用类型的方式都不会触发初始化,称为被动引用

被动引用不会导致初始化,但往往也是需要加载的,我们举一些例子:

  1. 使用类加载器的loadClass()方法,不做类的初始化工作

  2. 类型.class字面量

    1
    2
    3
    4
    5
    6
    package com.myspring.service.impl;
    public class A {
    static {
    System.out.println("初始化A类");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.myspring.service.impl;
    public class ClassLoadTest {
    public static void main(String args[]){
    System.out.println("===================");
    System.out.println("===================");
    Class c1 = A.class;
    System.out.println("===================");
    System.out.println("===================");
    }
    }
  3. 子类访问父类的静态字段(不会导致子类初始化,会导致父类初始化)

  4. 通过数组定义来引用类,不会触发此类的初始化

  5. 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

对于HotSpot虚拟机来说,可通过-XX:+TraceClassLoading参数观察到类是否会加载。而初始化时执行的是<clinit>()方法,我们可以编写静态代码块来验证此类是否初始化了。

初始化阶段就是执行类构造器<clinit>()方法的过程<clinit>()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的

如何获取一个Class对象

每个.class都是Class类的实例

1
2
3
4
5
6
7
8
9
public class MyTestBean {
public String testStr = "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}

对象.getClass()

1
2
3
4
5
6
7
8
@Test
public void test(){
MyTestBean myTestBean1 = new MyTestBean();
Class c1 = myTestBean1.getClass();
System.out.print("获得的Class对象:");
System.out.println(c1);

}
1
获得的Class对象:class com.myspring.service.impl.MyTestBean

类型.class字面量

1
2
3
4
5
6
7
@Test
public void test(){
Class c1 = MyTestBean.class;
System.out.print("获得的Class对象:");
System.out.println(c1);

}

Class类的forName方法

1
2
3
4
5
6
7
@Test
public void test() throws ClassNotFoundException {
Class c1 = Class.forName("com.myspring.service.impl.MyTestBean");
System.out.print("获得的Class对象:");
System.out.println(c1);

}

类加载器的loadClass方法

1
2
3
4
5
6
@Test
public void test() throws ClassNotFoundException {
Class c1 = this.getClass().getClassLoader().loadClass("com.myspring.service.impl.MyTestBean");
System.out.print("获得的Class对象:");
System.out.println(c1);
}

Class类源码

简而言之,我们先获取到类型A的Class对象,通过Class对象的newInstance方法可以得到A的实例。通过Class对象可以获取到Constructor对象,进一步可以使用Constructor对象来得到A的实例。通过Class对象可以获取到Method对象,通过Method的invoke方法我们可以调用一些方法。通过Class对象可以获取到Field对象,我们可以对这个实例的一些字段进行赋值取值操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* 使用给定的字符串名称返回与类或接口关联的Class对象。 调用此方法等效于:Class.forName(className, true, currentLoader)currentLoader表示当前类的定义类加载器。
* 调用forName“X”)会导致初始化名为X的类。
* @param className 所需类的完全限定名称。
* @return 具有指定名称的类的Class对象。
* ......
*/
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
/**
* 使用给定的类加载器返回与具有给定字符串名称的类或接口关联的Class对象。
* 给定类或接口的完全限定名称(采用getName返回的相同格式),此方法尝试查找,加载和链接该类或接口。指定的类加载器用于加载类或接口。
* 如果参数loader为null,则通过引导类加载器加载该类。仅当initialize参数为true且之前尚未初始化时,才初始化该类。
* 如果name表示原始类型或void,则将尝试在名称为name的未命名包中定位用户定义的类。 因此,该方法不能用于获取表示原始类型或void的任何Class对象。
* 如果name表示数组类,则该数组类的组件类型已加载但未初始化。
* 请注意,此方法会引发与加载,链接或初始化有关的错误
* 请注意,此方法不会检查其调用者是否可以访问所请求的类。
* 如果loader为null,并且存在安全管理器,并且调用方的类加载器不为null,则此方法使用RuntimePermission(“getClassLoader”)权限,以确保可以访问引导程序类加载器。
* @param initialize 如果true,则将初始化该类。
* @param loader 类加载器
* @return 代表所需类的类对象
* @exception LinkageError ExceptionInInitializerError ClassNotFoundException
*/
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException{
......
return forName0(name, initialize, loader, caller);
}
/**
* 创建此Class对象表示的类的新实例。就像通过带有空参数列表的new表达式实例化该类一样。如果尚未初始化该类,则将其初始化。
* 请注意,此方法传播由null构造函数引发的任何异常,包括已检查的异常。使用此方法有效地绕过了编译时异常检查,否则该检查将由编译器执行。
* (java.lang.reflect.Constructor)Constructor.newInstance方法通过将构造函数抛出的所有异常包装在java.lang.reflect.InvocationTargetException中从而避免了此问题。
* @return 该对象表示的类的新分配实例。
* @throws IllegalAccessException 如果该类或其无效构造函数不可访问。
* @throws InstantiationException 如果此Class表示抽象类,接口,数组类,原始类型或void;或如果类没有空构造函数;或者或实例化由于其他原因而失败。
* @throws ExceptionInInitializerError 如果此方法引发的初始化失败。
* @throws SecurityException 如果存在安全管理器,并且调用者的类加载器与当前类的调用者的类加载器不同,并且调用SecurityManager#checkPackageAccess拒绝访问此类的程序包。
*/
public T newInstance() throws InstantiationException, IllegalAccessException{
......
if (cachedConstructor == null) {
if (this == Class.class) {
throw new IllegalAccessException("Can not call newInstance() on the Class for java.lang.Class");
}
try {
Class<?>[] empty = {};
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
.......
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
// 安全性检查(与java.lang.reflect.Constructor中的相同)
......
// 运行构造函数
try {
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
......
}
}
/**
* 返回一个包含Class对象的数组,这些对象表示此Class对象表示的类的所有public类和接口成员。这包括从超类继承的public类和接口成员,以及由该类声明的公共类和接口成员。如果此Class对象没有public成员类或接口,则此方法返回长度为0的数组。如果此Class对象表示原始类型,数组类或void,则此方法还返回长度为0的数组。
*/
public Class<?>[] getClasses() {......}
/**
* 返回一组Class对象的数组,这些对象反映了声明为该Class对象表示的类的成员的所有类和接口。这包括public, protected, default (package) access,private类和接口,但不包括继承的类和接口。如果类未声明任何类或接口作为成员,或者此Class对象表示原始类型,数组类或void,则此方法返回长度为0的数组。
*/
public Class<?>[] getDeclaredClasses() throws SecurityException {......}
/**
* 返回一个包含Field对象的数组,这些对象反映了此Class对象表示的类或接口的所有可访问public字段。
* 如果此Class对象表示没有可访问的public字段的类或接口,则此方法返回长度为0的数组。
* 如果此Class对象表示一个类,则此方法返回该类及其所有超类的public字段。
* 如果此对象表示一个接口,则此方法返回该接口及其所有超级接口的字段。
* 如果此Class对象表示数组类型,原始类型或void,则此方法返回长度为0的数组。
* 返回数组中的元素未排序,并且没有任何特定顺序。
*/
public Field[] getFields() throws SecurityException {......}
/**
* 返回Field对象的数组,该数组反映由这个class对象表示的类或接口声明的所有字段。这包括public、protected、default(package)access和private字段,但不包括继承的字段。
* 如果这个Class对象表示没有声明字段的类或接口,则此方法返回长度为0的数组。
* 如果这个Class对象表示数组类型、基元类型或void,则此方法返回长度为0的数组。
* 返回数组中的元素没有排序,并且没有任何特定的顺序。
*/
public Field[] getDeclaredFields() throws SecurityException {......}
/**
* 返回一个Field对象,该对象反映由这个class对象表示的类或接口的指定public成员字段。name参数是一个String,指定所需字段的简单名称。
* 要反射的场由下面的算法确定。设C为该对象表示的类或接口:
* 1.如果C用指定的名称声明了一个public字段,则该字段就是要反映的字段。
* 2.如果在上面的步骤1中找不到任何字段,则此算法递归地应用于C的每个直接上接口。直接上接口按声明的顺序进行搜索。
* 3.如果在上面的步骤1和2中找不到字段,并且C有超类S,则此算法将在S上递归调用。如果C没有超类,则抛出NoSuchFieldException。
* 如果这个Class对象表示数组类型,则此方法找不到数组类型的length字段。
*/
public Field getField(String name) throws NoSuchFieldException, SecurityException {......}
/**
* 返回一个Field对象,该对象反映此Class对象表示的类或接口的指定声明字段。name参数是一个String,它指定所需字段的简单名称。
* 如果此Class对象表示数组类型,则此方法找不到数组类型的length字段。
*/
public Field getDeclaredField(String name)throws NoSuchFieldException, SecurityException {......}
/**
* 返回一个数组,该数组包含Method对象,这些对象反映由该class对象表示的类或接口的所有public方法,包括由类或接口声明的方法以及从超类和超接口继承的那些方法。
* 如果这个Class对象表示一个类型,该类型具有多个具有相同名称和参数类型但返回类型不同的public方法,则返回的数组对这些每个方法都有一个Method对象。
* 如果这个Class对象表示一个具有类初始化方法<clinit>的类型,则返回的数组没有与之(<clint>)相应的method对象。
* 如果这个Class对象表示数组类型,那么返回的数组对于数组类型从object继承的每个public方法都有一个Method对象。它不包含clone()的Method对象。
* 如果这个Class对象表示接口,则返回的数组不包含object隐式声明的任何方法。因此,如果此接口或其任何一个超接口中没有显式声明方法,则返回的数组的长度为0。(请注意,表示类的Class对象始终具有从object继承的public方法。)
* 如果这个Class对象表示基元类型或void,则返回的数组的长度为0。
* 在此class对象表示的类或接口的上层接口中声明的静态方法不被视为类或接口的成员。
* 返回数组中的元素没有排序,并且没有任何特定的顺序。
*/
public Method[] getMethods() throws SecurityException {......}
/**
* 返回一个数组,该数组包含Method对象,这些对象反映由该class对象表示的类或接口的所有已声明方法,包括public, protected, default (package) access, and private方法,但不包括继承的方法。
* 如果这个Class对象表示一个类型,该类型具有多个声明的方法,这些方法具有相同的名称和参数类型,但返回类型不同,则返回的数组对这些方法每个都有一个Method对象。
* 如果这个Class对象表示一个具有类初始化方法<clinit>的类型,则返回的数组没有与之(<clint>)相应的method对象。
* 如果这个Class对象表示没有声明方法的类或接口,则返回的数组的长度为0。
* 如果此Class对象表示数组类型,原始类型或void,则返回的数组长度为0。
* 返回数组中的元素没有排序,并且没有任何特定的顺序。
*/
public Method[] getDeclaredMethods() throws SecurityException {......}
/**
* 返回一个Method对象,该对象反映由该class对象表示的类或接口的指定public成员方法。name参数是一个String,指定所需方法的简单名称。parameterTypes参数是一个Class对象的数组,这些对象按声明的顺序标识方法的形式参数类型。如果parameterTypes是null,则将其视为空数组。
* 如果name是“<init>}”或“<clinit>}”,则引发NoSuchMethodException。否则,要反映的方法由下面的算法确定。设C为该对象表示的类或接口:
* 1. C搜索匹配方法,如下所述。如果找到匹配的方法,它将被反映出来。
* 2. 如果在步骤1中未找到匹配方法,则:
* 2.1如果C是Object以外的类,那么这个算法将在C的超类上递归调用.
* 2.2如果C是类Object,或者C是接口,那么将搜索C的上层接口(如果有)以查找匹配的方法。如果找到任何这样的方法,它就会被反映出来。
*
* 要在类或接口C中查找匹配的方法,请执行以下操作:如果C声明了一个具有指定名称和完全相同形式参数类型的public方法,则该方法就是所反映的方法。如果在C中发现了不止一个这样的方法,并且其中一个方法的返回类型比其他任何方法都更具体,则会反映该方法;否则任意选择其中一个方法。
* 请注意,一个类中可能有多个匹配的方法,因为尽管Java语言禁止一个类声明具有相同签名但返回类型不同的多个方法,而Java虚拟机是不禁止的,这增加了虚拟机的灵活性,可用于实现各种语言功能。
* 如果这个Class对象表示数组类型,则此方法找不到clone方法。
* 在此class对象表示的类或接口的上层接口中声明的静态方法不被视为类或接口的成员。
*/
public Method getMethod(String name, Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {......}
/**
* 返回一个Method对象,该对象反映此Class对象表示的类或接口的指定声明方法。name参数是一个String,用于指定所需方法的简单名称,而parameterTypes参数是一个Class对象的数组,这些对象标识该方法的形式参数类型,按声明的顺序。如果在一个类中声明了一个以上具有相同参数类型的方法,并且其中一个方法的返回类型比其他方法更具体,则返回该方法。否则,可以选择其中一种方法。如果名称是“ <init>”或“ <clinit>”,则会引发{@code NoSuchMethodException}。
* 如果此{@code Class}对象表示数组类型,则此方法找不到{@code clone()}方法。
*/
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {......}
/**
* 返回一个数组,其中包含Constructor对象,这些对象反映由这个class对象表示的类的所有public构造函数。如果类没有public构造函数,或者类是数组类,或者类反映基元类型或void,则返回长度为0的数组。
* 请注意,虽然此方法返回Constructor<T>对象的数组(即该类中的构造函数数组),但该方法的返回类型是Constructor<?>[]而不是Constructor<T>[]。这种信息较少的返回类型是必需的,因为从该方法返回后,可以修改数组以保存不同类的Constructor对象,这将违反Constructor<T>[]的类型保证。
*/
public Constructor<?>[] getConstructors() throws SecurityException {......}
/**
* 返回Constructor对象的数组,该数组反映由这个class对象表示的类声明的所有构造函数。它们是public、protected、default(package)access和private构造函数。
* 返回的数组中的元素没有排序,并且没有任何特定的顺序。
* 如果类具有默认构造函数,则它将包含在返回的数组中。
* 如果class对象表示接口、基元类型、数组类或void,则此方法返回长度为0的数组。
*/
public Constructor<?>[] getDeclaredConstructors() throws SecurityException {......}
/**
* 返回一个Constructor对象,该对象反映由这个class对象表示的类的指定public构造函数。parameterTypes参数是一个Class对象的数组,这些对象按声明的顺序标识构造函数的形式参数类型。
* 如果这个Class对象表示在非静态上下文中声明的内部类,则形式参数类型包括显式封闭实例作为第一个参数。
* 要反映的构造函数是由这个class对象表示的类的public构造函数,该对象的形式参数类型与parameterTypes指定的参数类型匹配。
*/
public Constructor<T> getConstructor(Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {......}
/**
* 返回一个Constructor对象,该对象反映此Class对象表示的类或接口的指定构造函数。parameterTypes参数是Class对象的数组,这些对象按声明的顺序标识构造函数的形式参数类型。
* 如果此Class对象表示在非静态上下文中声明的内部类,则形式参数类型包括显式的封闭实例作为第一个参数。
*/
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException {......}

6.JAVA常量池

  • Class文件常量池

    class文件是一组以字节为单位的二进制数据流,在java代码的编译期间,我们编写的java文件就被编译为.class文件格式的二进制数据存放在磁盘中,其中就包括class文件常量池。 class文件中存在常量池(非运行时常量池),其在编译阶段就已经确定,jvm规范对class文件结构有着严格的规范,必须符合此规范的class文件才能被jvm任何和装载。为了方便说明,我们写个简单的类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class JavaBean{
    private int value = 1;
    public String s = "abc";
    public final static int f = 0x101;

    public void setValue(int v){
    final int temp = 3;
    this.value = temp + v;
    }

    public int getValue(){
    return value;
    }
    }

    通过javac命令编译之后,用javap -v 命令查看编译后的文件:

    这个命令之后我们得到了该class文件的版本号、常量池、已经编译后的字节码(这里未列出)。既然是常量池,那么其中存放的肯定是常量,那么什么是“常量”呢? class文件常量池主要存放两大常量:字面量和符号引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class JavaBasicKnowledge.JavaBean
    minor version: 0
    major version: 52
    flags: ACC_SUPER
    Constant pool:
    #1 = Methodref #6.#29 // java/lang/Object."<init>":()V
    #2 = Fieldref #5.#30 // JavaBasicKnowledge/JavaBean.value:I
    #3 = String #31 // abc
    #4 = Fieldref #5.#32 // JavaBasicKnowledge/JavaBean.s:Ljava/lang/String;
    #5 = Class #33 // JavaBasicKnowledge/JavaBean
    #6 = Class #34 // java/lang/Object
    #7 = Utf8 value
    #8 = Utf8 I
    #9 = Utf8 s
    #10 = Utf8 Ljava/lang/String;
    #11 = Utf8 f
    #12 = Utf8 ConstantValue
    #13 = Integer 257
    #14 = Utf8 <init>
    #15 = Utf8 ()V
    #16 = Utf8 Code
    #17 = Utf8 LineNumberTable
    #18 = Utf8 LocalVariableTable
    #19 = Utf8 this
    #20 = Utf8 LJavaBasicKnowledge/JavaBean;
    #21 = Utf8 setValue
    #22 = Utf8 (I)V
    #23 = Utf8 v
    #24 = Utf8 temp
    #25 = Utf8 getValue
    #26 = Utf8 ()I
    #27 = Utf8 SourceFile
    #28 = Utf8 StringConstantPool.java
    #29 = NameAndType #14:#15 // "<init>":()V
    #30 = NameAndType #7:#8 // value:I
    #31 = Utf8 abc
    #32 = NameAndType #9:#10 // s:Ljava/lang/String;
    #33 = Utf8 JavaBasicKnowledge/JavaBean
    #34 = Utf8 java/lang/Object
  • 运行时常量池

  • 全局字符串常量池

  • 基本类型包装类对象常量池

7.JAVA map.keySet() map.Values()

一言以蔽之,shallow copy而非deep copy

map变了,这些set, collection也跟着变

尝试变这些set? 不行!

1
2
3
4
5
6
7
8
9
10
11
Map<Integer,Integer> map=new HashMap<>();
Set<Integer> keys = map.keySet();
map.put(1,1);
System.out.println(map.keySet());//[1]
System.out.println(keys);//[1]
System.out.println(map.keySet()==keys);//true
Collection<Integer> values = map.values();
map.put(2,3);
System.out.println(values);//[1, 3]
keys.add(5);//UnsupportedOperationException extends RuntimeException
System.out.println(map.keySet());