第一: 运行速度慢,众所周知,Java程序的运行依赖于Java虚拟机,
所以相对于其他语言(汇编,C,C++)编写的程序慢,因为它不是直接,执行机器码。
第二: 因为Java考虑到了跨平台性。所以他不能像语言
(例如:汇编,C) 那样更接近操作系统。也就不能和操作系统的底层打交道了。
所以如果是你想开发 应用程序的话 最好选择 Java以外的其他开发语言。
基础语法
1.分为基本数据类型和引用数据类型
2.基本数据类型包括8个:
数值型(byte、short、int、long、float、double),字符型(char)以及布尔型(boolean)
3.除了基本类型外,其他数据类型都属于引用类型,包括类、接口、数组等。
包装类
Integer对应 int Character 对应char
Byte 对应 byte Short 对应 short Long 对应 long
Float 对应float Double 对应double Boolean 对应boolean
● Integer是int的包装类;int是基本数据类型;
● Integer变量必须实例化后才能使用;int变量不需要;
● Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
● Integer的默认值是null;int的默认值是0。
byte:1个字节,8位,取值范围为【-128,127】
short:2个字节,16位,取值范围为【-32768,32767】
int:4个字节,32位,整数类型默认是int类型,取值范围约21亿
long:8个字节,64位,long类型表示long类型常量,要加L或者l,建议加L
float:4个字节,32位,单精度,能精确到6~7位,声明一个小数类型,要加F或者f,建议加F
double:8个字节,64位,双精度,能精确到15~16位,小数类型默认是double类型
char:2个字节,16位,字符表示Unicode(万国码)编码表中的每一个符号,每个符号使用单引号引起来,其中前128个符号和ASCII表相同
boolean:占1位,有true和false2个值,一个表示真,一个表示假,一般用于表示逻辑运算
自动装箱是指把基本数据类型,比如int,系统自动调用一个Integer.valueOf((int)10);这样一个方法,把基本数据类型变为包装类型。
自动拆箱则是指把包装类型,比如包装类型数值系统自动调用一个叫intvalue()的方法,把包装类型变为基本数据类型这样一种操作;
当相互转换赋值的时候,一定要是已经初始化有值的,空值进行赋值是会爆编译时异常的。
int a;
Integer aa=0;
a=aa;
答:类是一个抽象的概念,是具有相同特征的事物的描述,是对象的模板。对象是一个个具体的存在,是类的实例。
抽象类不能被实例化;
抽象类可以有抽象方法,只需申明,无须实现;
有抽象方法的类一定是抽象类;
抽象类的子类必须实现抽象类中的所有抽象方法,否则子类仍然是抽象类;
抽象方法不能声明为静态、不能被static、final修饰。
(1)接口是interface,抽象类是abstract
(2)接口是implement实现,可以多实现,抽象类是extends继承,只能单继承
(3)接口的方法只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有 定义与实现。
(4)接口没有构造方法,不能被实例化,抽象类有构造方法,用于子类实例化使用。接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
(5)接口成员方法,jdk1.7只能是抽象的。默认修饰符:public abstract,jdk1.8可以写以default和 static开头 的具体方法,
抽象类成员方法:可以是抽象的,也可以是非抽象的。
(6)接口成员变量:只能是常量。默认修饰符:public static final,
抽象类成员变量:可以是变量 也可以是常量。
①如果知道某个类应该成为基类,那么第一选择应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。
②在接口和抽象类的选择上,必须遵守这样一个原则:行为模型应该总是通过接口而不是抽象类定义。通过抽象类建立行为模型会出现的问题:如果有一个抽象类Moblie,有两个继承它的类Mobile1和Moblie2,分别有自己的功能1和功能2,如果要出现一个既有功能1又有功能2的新产品需求,由于Java不允许多继承就出现了问题,而如果是接口的话只需要同时实现两个接口即可。
51+2个保留字=53个关键字(java的关键字都是小写的!!
abstract、assert、boolean、break、byte、case、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、catch、void、volatile、while。
goto、const,true、false、null
①final可以修饰类,方法和变量,
被final修饰的类不可继承,String 类是 final 类,不可以被继承。
被final修饰的方法不可重写,
被final修饰的变量引用不可更改,引用的内容可以更改。
②finally:是异常处理的一部分,用来释放资源。一般来说,代码肯定会执行。特殊情况:在执行到finally之前JVM退出了
如果catch语句里面有return,请问finally里面的代码还会执行嘛。如果会,请问是return前,还是return后。
答:会执行,在return前。
③finalize:是Object类的一个方法,用于垃圾回收,在对象被垃圾回收之前将调用一次,一般用于资源的释放。
String
中的String 类中使用 final 关键字修饰,用字符数组来保存字符串,private final char value[] ,String对象是不可变的,也就可以理解为常量,线程安全。每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间
StringBuffer
StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量,对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder
可变类,速度快,没有对方法进行加同步锁,所以是非线程安全的,由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
常用方法都差不多:append,capacity,charAt,delete,indexOf,insert,length,replace
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
compareTo():比较两个字符串的字典
hashCode:返回此字符串的哈希代码
toCharArray():将此字符串转换为一个新的字符数组
toString():这个对象(这已经是一个字符串!)是自己回来了。
trim():返回一个字符串,去掉前后空格。
lastIndexOf(();返回在指定字符的最后一个发生的字符串内的索引
反向字符串
添加到StringBuilder中,然后调用reverse()。
String a=“123”;
StringBuilder aa=new StringBuilder(a);
aa.reverse();
● “==”是运算符,如果是基本数据类型,则比较存储的值;如果是引用数据类型,则比较的是所指向的对象的地址值。
● equals是Object的方法,比较的是所指向的对象的地址值,equals 方法不能用于比较基本数据类型,
● 一般情况下,类会重写equals方法用来比较两个对象的内容是否相等。比如String类中的equals()是被重写了,比较的是对象的值。
附:两个对象如果相等,那么它们的hashCode值必须相等,但两个对象的hashCode值相等时,它们不一定相同。
答:
①Array长度在定义之后就不运行改变了,而ArrayList是长度可变的,可以自动扩容。
②Array只能存储相同类型的数据,ArrayList可以存储不同类型的数据。
③ArrayList提供了更多操作数据的方法。
(1)throw
作用在方法内,表示抛出具体异常,由方法体内的语句处理;
一定抛出了异常;
(2)throws
作用在方法的声明上,表示抛出异常,由调用者来进行异常处理;
可能出现异常,不一定会发生异常;
答:①&具有按位与和逻辑与两个功能。
②&&作为逻辑与具有短路的特点,当前面的条件表达式为false时就不会进行后面条件表达式的判断,可以用来避免空指针异常。
答:
①接口中可以添加default修饰的非抽象方法,可以有方法体和内容。
②可以使用Lambda表达式,减少代码冗余。
⑤stream流,用于解决已有集合/数组类库的弊端,简化其操作,有foreach遍历、filter过滤、map映射、concat合并等功能。
⑥增加日期相关的API。
一、概念
JDK是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包。
JRE是java运行时的环境,包含了java虚拟机,java基础类库,是使用java语言编写的程序运行所需要的软件环境。
二、区别
1、面向人群不同
JDK是提供给程序员使用的。JRE是提供给想运行java程序的用户使用的。
2、重要程度不同
如果你需要编写java程序,需要安装JDK。如果你需要运行java程序,只需要安装JRE就可以了。
(1)序列化是通过实现serializable接口,将一个Java对象写入IO流中,而反序列化(Deserialize)则是指从IO流中恢复这个Java对象。最主要目的就是传递和保存对象,保存对象的完整性和可传递性。
例如,可以序列化一个对象,然后通过HTTP通过Internet在客户端和服务器之间传输该对象。在另一端,反序列化将从流中心构造成对象。
一般程序在运行时,产生对象,这些对象随着程序的停止而消失,但我们想将某些对象保存下来,这时,我们就可以通过序列化将对象保存在磁盘,需要使用的时候通过反序列化获取到。
(2)譬如通过网络传输或者把一个对象保存成本地一个文件的时候,需要使用序列化。
序列化是通过实现serializable接口,将一个Java对象写入IO流中,而反序列化(Deserialize)则是指从IO流中恢复这个Java对象。
要求类必须实现序列化接口,并且序列化和反序列化必须保持序列化的ID一致。
附:静态变量和被transient修饰的变量不会被序列化。
所谓反射,是java在运行时进行自我观察的能力,在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
附:获取Class对象有哪几种方式?
最常用的就是new
①通过类名.class
②通过对象.getClass()
③通过Class.forName(类的全限名);
(Throwable类是所有异常类的父类
NullPointerException:空指针异常;
SQLException:数据库相关的异常;
IndexOutOfBoundsException:数组下角标越界异常;
FileNotFoundException:打开文件失败时抛出;
IOException:当发生某种IO异常时抛出;
ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出此异常;
NoSuchMethodException:无法找到某一方法时,抛出;
ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常;
NumberFormatException:当试图将字符串转换成数字时,失败了,抛出;
IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
附:StackOverflowError、OutOfMemoryError异常
Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError 异常。
如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutOfMemoryError 异常。
public static void main(String[] args) {
test();
}
public static void test() {
test();
/抛出异常:Exception in thread"main"java.lang.StackoverflowError
//程序不断的进行递归调用,而且没有退出条件,就会导致不断地进行压栈。
当栈深度达到9803的时候,就出现栈内存空间不足
静态变量属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问;
非静态变量属于类的对象,只有在类的对象产生时,才会分配内存,通过类的实例去访问;
静态方法也属于类本身,但是此时没有类的实例,内存中没有非静态变量,所以无法调用。
char的长度是固定的,varchar的长度的可变的;
char的效率比varchar的效率高;
char占用空间比varchar大,char在查询时需要使用trim;
167、float 和 double 的区别是什么?
1、float 和 double 的区别是什么?
(1)内存中占有的字节数不同
单精度浮点数在内存中占有4个字节;
双精度浮点数在内存中占有8个字节;
(2)有效数字位数不同
单精度浮点数有效数字8位;
双精度浮点数有效数字16位;
(3)数值取值范围不同
单精度浮点数的表示范围:-3.40E+38~3.40E+38
双精度浮点数的表示范围:-1.79E+308~-1.79E+308
(4)在程序中处理速度不同
一般来说,CPU处理单精度浮点数的速度比双精度浮点数的速度快
如果不声明,默认小数是double类型,如果想用float,要进行强转;
2、例如
float f = 1.3;会编译报错,正确的写法是float f = (float)1.3;或者float a = 1.3f;(f或F都可以不区分大小写)
3、注意
float是八位有效数字,第七位会四舍五入;
4、面试题
(1)java中3*0.1==0.3将会返回什么?true还是false?
答:返回false,因为浮点数不能完全精确的表示出来,一般会损失精度;
(2)java中float f = 3.4;是否正确?
答:不正确。因为3.4是双精度浮点数,将双精度赋给单精度属于向下转型,会造成精度损失,因此需要强制类型转换float=(float)3.4;或者写成float f = 3.4f;
答:两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。
String a=new String(“abc”)使用了几块内存空间
内存中肯定会有两个对象, 三块内存分配: 分别是
s 这个变量在栈中的内存空间
abc 常量池区内存
new String(“abc”) 这个 s变量指向的对象内存
1.抽象
定义:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这此行为的细节是什么
1.1 研究事物的静态特征(属性)
1.2 研究事物的动态特征(方法/函数)
1.3 形成结果(类,class)
1.4 类和对象相互转变(new)
举例说明:
学生有姓名,有年龄,有身份证号,这些都是静态特征,即属性
学生会吃饭,会睡觉,会学习,这些都是动态特征,即行为。
①封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
控制在程序中属性的读和修改的访问级别,建议成员变量私有,然后提供公有的getter/setter方法来获取值/赋值,在将成员属性封装之后,我们应该提供相应的get/set方法进行访问此成员属性。
封装的核心思想是合理隐藏,合理暴露,可以提高安全性,实现代码的组件化。
可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
②继承:一种子类到父类的关系,是“is a”关系,可以提高代码的复用性,相同代码可写到父类,子类的功能更加强大,不仅得到了父类的功能,还有自己的功能。
③多态:
实现多态的两种形式:使用父类作为方法形参实现多态,使用父类为方法返回值实现多态
实现多态需要三个必备条件:继承 重写 父类引用变量指向子类对象
向上转型----子类转换为父类,自动进行类型转换
向下转型----父类转换为子类,结合强制进行转换
实现多态的两个要素:运行时,使用父类的类型,子类的对象
面向对象编程,即OOP
它的特点:抽象 封装 继承 多态
用我的话说,其实可以这么理解,假如我是上帝,我要造 人 (这是对象)。
首先,我要知道人类要有哪些最基本的东西 ?
人要有思想,人还要有肉体
这个过程呢就是(抽象)
因为人的数量很庞大,所以我需要找别人帮我一起造人,但是我不想让他知道我造人的技术(人的脸需要用多少克泥巴,需要怎么用手去捏鼻子等等),我可以把我的造人的技术封装起来,别人只需要知道他要做什么样的人类就好了。【封装】
当我自己造人的时候,我可能造着造着就累了,我可能就要用我之前造出来的某个泥人作为模板“复制”,“粘贴”了
用模板创出来的泥人具有模板的特征,就像孩子一样具有父母的性状。我们称这个过程为【继承】
但是呢 好多泥人都是我后来偷懒用模板复制出来的,为了让人类更丰富多彩一些,我对他们又进行了改变。这个过程可以叫【多态】
接下来我举个栗子来区分面向过程和面向对象
例子:是如何将大象装进冰箱
为了解决这个问题,我们采用两种方案,一种是面向过程方案,另一种是面向对象方案
面向过程:
总共分三个过程,
第一个过程:冰箱门打开(关着门的冰箱) 返回值是打开门的冰箱
第二个过程:大象装进去(打开门的冰箱) 返回值是打开着门,装着大象的冰箱
第三个过程:冰箱门关上(打开着门,装着大象的冰箱) 返回值关着门的装着大象的冰箱
面向对象:
总共分三个动作,(每个动作都有一个对象去执行)
第一个动作:冰箱.开门()
第二个动作:冰箱.装进(大象)
第三个动作:冰箱.关门()
或者是链式调用冰箱.开门().装进(大象).关门()
开闭原则、
里氏代换原则、
单一职责原则、
迪米特原则(最少知道原则)、
接口分隔原则、
依赖倒置原则、
组合/聚合复用原则。
答:
①equals(Object obj):判断其他对象是否与当前对象相等。
②toString():打印当前对象的字符串表示。
③wait():导致当前线程等待,等待其他线程唤醒,会释放锁。
④notify()/notifyAll():随机唤醒一个/全部线程。
⑤hashCode():返回当前对象的hashCode值。
⑥finalize():当垃圾回收器要回收对象前调用。
⑦clone():创建并返回对象的一个副本。
1、重写:所谓方法的重写是指子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。
2、重载:所谓方法重载是指在一个类中,多个方法的方法名相同,但是参数列表不同。参数列表不同指的是参数个数、参数类型或者参数的顺序不同。虽然每个重载方法可以有不同的返回类型,但返回类型并不足以区分所使用的是哪个方法。
附:构造器是否可被 重写?
Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多个构造函数的情况
答:①方法重载是同一个类中具有不同参数列表的同名方法(无关返回值类型)。
方法重写是子类中具有和父类相同参数列表的同名方法,会覆盖父类原有的方法。
②重载的返回值类型和权限修饰符,异常抛出类型没有要求。
重写方法的返回值类型小于等于父类被重写方法的返回值类型,修饰符权限大于等于父类被重写方法权限修饰符,抛出的异常类型小于等于父类被重写方法抛出的异常类型。
答:①内部类有更好的封装性,有更多的权限修饰符,封装性可以得到更多的控制。
②静态内部类:由static修饰,属于类本身,只加载一次。类可以定义的成分静态内部类都可以定义,可以访问外部类的静态变量和方法,通过new 外部类.静态内部类构造器来创建对象。
③成员内部类:属于外部类的每个对象,随对象一起加载。不可以定义静态成员和方法,可以访问外部类的所有内容,通过new 外部类构造器.new 成员内部类构造器来创建对象。
④局部内部类:定义在方法、构造器、代码块、循环中。只能定义实例成员变量和实例方法,作用范围仅在局部代码块中。
⑤匿名内部类:没有名字的局部内部类,可以简化代码,匿名内部类会立即创建一个匿名内部类的对象返回,对象类型相当于当前new的类的子类类型。
答:有问题,因为3.4 是双精度数,将双精度型赋值给浮点型(float)属于向下转型,可能会造成精度损失,所以必须进行强制类型转换,正确的写法是
float number =(float)3.4;/ float number =3.4F;。
答:
①使用+直接拼接,String 是final对象,不会被修改,每次使用 +进行拼接都会创建新的对象,而不是改变原来的对象,效率低,是线程安全的。
②使用StringBuffer可变字符串,效率较高,是线程安全的(StringBuffer的方法使用了synchronized关键字进行修饰)。
③使用StringBuilder可变字符串,效率最高,但是线程不安全。
我们把数据库查询出来的拷贝对象UserDO.java 拷贝到 目标对象UserVO.java。直接使用BeanUtils.copyProperties()方法。
@Test
public void commonCopy() {
UserDO userDO = new UserDO(1L, "Van", 18, 1);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userDO, userVO);
log.info("userVO:{}",userVO);
}
就是在对一个对象进行处理,又想要保留原有的数据进行接下来的操作。
克隆针对的是类的实例。
1.实现Cloneable接口,重写clone()方法;
clone方法是浅拷贝,即如果类中属性有自定义的引用类型,只拷贝引用,不拷贝引用指向的对象
2.实现Serializable接口,完成深拷贝;
基于序列化实现的克隆不仅仅是深度克隆,更重要是通过泛型限定,可以检测出要克隆的对象是否支持序列化,这项检查是编译器完成的。
● 浅拷贝只是复制了对象的引用地址,拷贝对象和被拷贝对象指向同一个内存地址,所以修改其中一个,任意一个都会变化。而对于基本类型对象改变一个并不会影响另一个。
● 深拷贝是将对象及值复制,两个对象改变其中一个另一个不会改变。
package clone;
public class people implements Cloneable{
private String name;
public people(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return name;//重写了toString方法,方便测试时观察
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
System.out.println("对象已被克隆");
return super.clone();
}
}
package clone;
/**
* 在对象属性很多时,使用clone比使用new效率高。因为new还需要去调用构造方法,而且new之后的对象属性值全是空的,使用clone方法
* clone过去的属性值与被克隆的对象一样。
* @author dell
*
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
people p = new people("小明");
people like = p; //浅拷贝对象like
Object clone = p.clone(); //深拷贝对象clone
/**
* 深拷贝(对象类实现了cloneable方法,并重写clone方法。测试类调用方法并抛出CloneNotSupportedException异常)
*/
System.out.println(clone.hashCode()+" "+p.hashCode());
System.out.println(p.toString()+" "+clone.toString());
System.out.println("--------------------------------");
/**
* 浅拷贝(like和p为同一个逻辑地址,实际上就是把值拷贝了一份)
*/
System.out.println(like.hashCode()+" "+p.hashCode());
System.out.println(like.toString()+" "+p.toString());
}
}
附:值传递和引用传递的区别
值传递不会影响到实际参数。引用传递会影响到实际参数。
bytecode(字节码文件),也就是.class文件,然后根据虚拟机系统将字节码文件转变为可执行文件,这也是java程序可跨平台的根据。
java.exe指java编译器
Java语言采用的编码Unicode
随着Java编码的发展,出现了一种统一的编码文字即Unicode
包含UTF-8,UTF-16,UTF-32所以现在的标识符可以写中文
到底什么是java标识符?用一句话来说:凡是程序员自己有权利命名的单词都是标识符。
从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制
1.强引用
本章前文介绍的引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强
引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java
虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用
的对象来解决内存不足问题。
2.软引用(SoftReference)
如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃
圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器
没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象
被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3.弱引用(WeakReference)
如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别
在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存
区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它
的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具
有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象
被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4.虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的
生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能
被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在
于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对
象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联
的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解
被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,
那么就可以在所引用的对象的内存被回收之前采取必要的行动。
public class Person {
private static User1 user1 = new User1();//1
private User2 user2 = new User2();//5
// 静态代码块:在类加载时候执行静态代码块,只会执行一次
static {
System.out.println("2");//2
}
// 实例初始化块,每次调用构造方法之前首先调用实例初始化块
{
System.out.println("6");//6
}
public Person() {
System.out.println("7");//7
}
}
public class Student extends Person{
private static User3 user3 = new User3();//3
private User4 user4 = new User4();//8
static {
System.out.println("4");//4
}
{
System.out.println("9");//9
}
public Student() {
super();
System.out.println("10");//10
}
}
先是父类静态变量,然后到父类静态代码块,自己的静态变量,自己的静态代码块,
父类变量,父类代码块,父类构造方法,自己的变量,自己的代码块,自己的方法