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 | public abstract class TestEngine |
3.JAVA inner class
inside another class or method
成员内部类
局部内部类
匿名内部类
静态内部类
4.ragged array
java 2d array并不是真的开辟一个二维矩阵,只是指定行数,列数非必须
每行指向一个array object,这些array的长度可以不等
5.JAVA 反射
基础理论
- A类 -> A.class字节码文件 -> 加载到JVM后的A字节码文件对象(Class对象)
- A的Class对象 -> A的实例
- Class是反射的基础
- 当new一个新对象或者引用静态成员变量等时机时,JVM类加载器系统会将对应Class对象加载到JVM中,然后JVM根据Class对象创建实例对象或者提供静态变量的引用值。
- 每个类,无论创建多少个实例,在JVM中都对应同一个Class对象(类被不同的类加载器加载除外)。
主动引用
对于字节码文件的加载时机,《Java虚拟机规范》中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有
六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。
- 使用new关键字实例化对象的时候。
- 读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候。
- 调用一个类型的静态方法的时候。
- 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
- 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
- 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
被动引用
这六种场景中的行为称为对一个类型进行主动引用
。除此之外,所有引用类型的方式都不会触发初始化,称为被动引用
。
被动引用不会导致初始化,但往往也是需要加载的,我们举一些例子:
使用类加载器的loadClass()方法,不做类的初始化工作
类型.class字面量
1
2
3
4
5
6package com.myspring.service.impl;
public class A {
static {
System.out.println("初始化A类");
}
}1
2
3
4
5
6
7
8
9
10package 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("===================");
}
}子类访问父类的静态字段(不会导致子类初始化,会导致父类初始化)
通过数组定义来引用类,不会触发此类的初始化
常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
对于HotSpot虚拟机来说,可通过-XX:+TraceClassLoading参数观察到类是否会加载。而初始化时执行的是<clinit>()方法
,我们可以编写静态代码块来验证此类是否初始化了。
初始化阶段就是执行类构造器<clinit>()方法的过程
。<clinit>()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物
。<clinit>()方法
是由编译器自动收集类中的所有类变量
的赋值动作和静态语句块(static{}块)
中的语句合并
产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的
。
如何获取一个Class对象
每个.class都是Class类的实例
1 | public class MyTestBean { |
对象.getClass()
1 |
|
1 | 获得的Class对象:class com.myspring.service.impl.MyTestBean |
类型.class字面量
1 |
|
Class类的forName方法
1 |
|
类加载器的loadClass方法
1 |
|
Class类源码
简而言之,我们先获取到类型A的Class对象,通过Class对象的newInstance方法可以得到A的实例。通过Class对象可以获取到Constructor对象,进一步可以使用Constructor对象来得到A的实例。通过Class对象可以获取到Method对象,通过Method的invoke方法我们可以调用一些方法。通过Class对象可以获取到Field对象,我们可以对这个实例的一些字段进行赋值取值操作。
1 | /** |
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
14class 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
39class 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 | Map<Integer,Integer> map=new HashMap<>(); |