string,final,方法区,常量池,编译时和运行时

string 不变性,有final修饰,不能被继承;
intern();返回string对象在常量池中的引用(即字符串对象在常量池中的地址);
==比较的是对象的地址是否相同;(即是否是同一个对象)
equals比较的是对象的内容是否一样;
对象的引用存的是对象实际位置的首地址;
final从字面上理解含义为“最后的,最终的”。在Java中也同样表示出此种含义。
final可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。

  1. final修饰类:
    final修饰的类不能被继承,即不能拥有自己的子类。
    如果试图对一个已经用final修饰的类进行继承,在编译期间会发生错误。

  2. final修饰方法:
    final修饰的方法不能被重写(可以重载多个final修饰的方法)。
    此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中有(final+private)修饰的方法,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。

  3. final 修饰变量:
    一旦定义了final变量并在首次为其显示初始化后,final修饰的变量值不可被改变。
    这里需要注意以下几个问题:

  4. final修饰的变量,无论是类属性、对象属性、形参还是局部变量,这些变量都是需要进行显示初始化(即为其显示指定初始值)。

对于final修饰的形参,由于是实参传递过来的,很好理解。

对于final修饰的局部变量,与未用final修饰的变量一样,都是需要显示初始化。即局部变量都是需要显示初始化的。

对于一般的类属性和对象属性,由类和对象的初始化过程中可以看出,首先都进行了默认初始化。然后对有显示赋值的变量才再进行显示初始化。

但对final修饰的类属性和对象属性而言,如果不显示初始化,其默认将是进行默认初始化后的值,这与final本身出发点矛盾,因此,Java语法规定:

final修饰的类属性和变量属性必须要进行显示初始化赋值。

另外,无论对于基本数据类型还是引用数据类型,final修饰的变量都是首次显示初始化后值都不能修改。对于基本数据类型,很好理解。对于引用数据类型,引用变量指向的是实际的对象,但其存储的是所指向对象的地址,因此,其值不能修改并不意味着其所指向的对象不能修改。

final修饰符修饰的变量在由于其本身的特性,在编译期就能直接确定其值,且此值不可变。在编译过程中,可以直接将其变量直接转换成其值本身去表示。

例1:
public static void main(String[] args)
{
Integer a=127; //常量池本来就有127,故引用常量池
Integer b=127; //引用常量池
//String c=new String(“5555”); //在常量池中创建,拷贝到堆中,c是堆中字符串的引用
String c=“5555”; //在常量池创建并引用常量池
String d=“5555”; //常量池直接应用
Integer e=666; //Integer不属于基本类型;所以这句相当于 Integer e=new Integer(); 下同,都是指向堆的
Integer f=666; //
Integer g=666; //
System.out.println((ab)+" "+(cd)+" "+(ef)+" "+(fg));
}
输出:
true true false false

方法区里存放着类的版本,字段,方法,接口和常量池。常量池里存储着字面量和符号引用。
静态常量池和动态常量池的关系以及区别:

  1. 静态常量池存储的是当class文件被java虚拟机加载进来后存放在方法区的一些字面量(即数值本身)和符号引用( 符号引用包括:1.类的全限定名,2.字段名和属性,3.方法名和属性):
    字面量包括字符串,基本类型的常量;
    符号引用其实引用的就是常量池里面的字符串;但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引。
  2. 动态常量池是当class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到动态常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。
    在JVM中类加载过程中,在解析阶段,Java虚拟机会把类的二级制数据中的符号引用替换为直接引用。

1.符号引用(Symbolic References):
  符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。
  符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
  各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

2.直接引用:
直接引用可以是
(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
(3)一个能间接定位到目标的句柄
直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。
动态常量池里的内容除了是静态常量池里的内容外,还将静态常量池里的符号引用转变为直接引用,而且动态常量池里的内容是能动态添加的。例如调用String的intern方法就能将string的值添加到String常量池中,这里String常量池是包含在动态常量池里的,但在jdk1.8后,将String常量池放到了堆中。

方法区与常量池的关系:
string,final,方法区,常量池,编译时和运行时方法区class文件信息包括哪些内容:
string,final,方法区,常量池,编译时和运行时
经过反编译后的class文件为:
string,final,方法区,常量池,编译时和运行时
string,final,方法区,常量池,编译时和运行时 符号引用包括:1.类的全限定名,2.字段名和属性,3.方法名和属性。

1.string对象实例化存储:
1) String str=“aaa”;//是在String池里面创建了一个"aaa"对象
2)而String str=new String(“aaa”);//创建了两个对象,一个在String池里面,还有一个是new出来的,在堆内存中,str是引用,两个对象都是"aaa";
所以关于Integer i1=6应该是先装箱,堆里和栈里都有对象,是创建了两个对象
Integer (-128~127)实例化时会从缓存中查找值;不在范围内的会new对象;

java内存应用:
常量池是介于栈和堆外的另一种独立的内存管理空间,相同内容常量池中永远只有一份;
基本数据类型、对象的引用都存在栈中,执行速度快,包装类型,对象存储,new出来的对象都是存储在堆中;
Byte,Short,Integer,Long,Character这5种整型的包装类只是在对应值小于等于127时才可使用对象池。超过了就会自动申请空间创建对象;
而String是个较为特殊的包装类型,直接用=“”创建的数据是存放在常量池,且无论数据大小都不会申请空间创建,除非使用new关键字。

字符串拼接时:
StringBuffer是线程安全的;
在无需考虑线程安全的情况下,建议使用性能相对较高的StringBuilder类;
若系统要求线程安全,就选择StringBuffer类;
一般基本数据类型转换为string时尽量使用toString()效率比valueOf()高;
======================================================

组合和继承的关系:
组合是整体与部分、拥有的关系,即has-a的关系;
继承是一种is-a关系;
继承,在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。
从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。

组合,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。
继承:父类和子类的耦合性高,父类变化,子类也要进行相应的变化;
组合:解耦,如果需要用到另一个类的方法等可以直接实例化对象进行调用;
------------------------------------------------------------------------------------------------
java编译时 和运行时
编译时顾名思义就是正在编译的时候.那啥叫编译呢?就是编译器帮你把源代码翻译成机器能识别的代码.(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言.比如Java只有JVM识别的字节码,.另外还有啥链接器.汇编器.为了了便于理解我们可以统称为编译器)

编译时就是简单的作一些翻译工作,比如检查老兄你有没有粗心写错啥关键字了啊.有啥词法分析,语法分析之类的过程.就像个老师检查学生的作文中有没有错别字和病句一样.如果发现啥错误编译器就告诉你.所以有时一些人说编译时还分配内存啥的肯定是错误的说法.

运行时

所谓运行时就是代码跑起来了.被装载到内存中去了.(你的代码保存在磁盘上没装入内存之前是个死家伙.只有跑到内存中才变成活的).而运行时类型检查就与前面讲的编译时类型检查(或者静态类型检查)不一样.不是简单的扫描代码.而是在内存中做些操作,做些判断.(这样很多编译时无法发现的错误,在运行就可以发现报错了,最好还是写的的时候就避免这个逻辑错误就好了)

java编译时,运行时,构建时:
注解:三者都有
编译时:重载,那么在编译时必须明确具体方法是哪一个
泛型<>,
继承,这是因为子类和父类的紧耦合关系是在编译期产生的.
运行时:重写(覆盖),代理或者组合 – 发生在运行时,因为它更加具有动态性和灵活性。
6、面向切面的编程(Aspect Oriented Programming-AOP)
切面可以在编译时,运行时或,加载时或者运行时织入。
5、异常(Exception)
你可以使用运行时异常或者编译时异常。
5.1、运行时异常(RuntimeException)
也称作未检测的异常(unchecked exception),这表示这种异常不需要编译器来检测。RuntimeException是所有可以在运行时抛出的异常的父类。一个方法除要捕获异常外,如果它执行的时候可能会抛出RuntimeException的子类,那么它就不需要用throw语句来声明抛出的异常。
例如:NullPointerException,ArrayIndexOutOfBoundsException,等等
5.2、受检查异常(checked exception)
都是编译器在编译时进行校验的,通过throws语句或者try{}cathch{} 语句块来处理检测异常。编译器会分析哪些异常会在执行一个方法或者构造函数的时候抛出。

有些关键字(如:final)修饰的变量进行运算会在编译时就进行了;反编译器jd-gui,Classforjava;
=================================================
Q.你能想出除了代码优化外,在什么情况下,查看编译过的代码是很有帮助的?
A.Java里的泛型是在编译时构造的,可以通过查看编译后的class文件来理解泛型,也可以通过查看它来解决泛型相关的问题。