对象:
对象是通过new关键字来创建的,通过引用来接收对象,当对象创建出来后引用就会为对象分配内存,new字是创建对象的操作符,对象的比较有两种形式:
1.“==”运算符是用来比较两个对象引用的地址是否相等,
2.“equal()”方法是来比较两对象引用的内容是否相等。对象的销毁是引用结束后就会被垃圾处理器进行回收;
Q&A 创建对象的方法:
1.使用new关键字。
2.使用Class类中的newInstance方法,newInstance方法调用无参构造方器创建对象。Class.forName.newInstance;
3.使用clone方法、
4.反序列化。
Q&A 对象的产生过程以及存储:
对象的产生:
new将对象存储在堆中,所以用new创建一个对象---特别小的,简单的变量,往往不是很有效。因此对于(基本类型)java不用new来创建这样的变量,而是创建一个并非是引用的“自动”变量。这个变量的值直接存储"值"到堆栈中。
程序创建,运行时对象是如何分配呢?内存是怎样分配呢?
对象产生的时机 类加载,然后进行对象的实例化:
Q&A 什么时候会进行类加载?
1.创建类的实例,也就是new一个对象
2.访问某个类或接口的静态变量,或者对该静态变量赋值
3.调用类的静态方法
4.反射(Class.forName("A"))
5.初始化一个类的子类(会首先初始化子类的父类)
6.JVM启动时标明的启动类,即文件名和类名相同的那个类
new ObjectInitTest()对象的产生过程1.JVM会在ObjectInitTest.class文件2.先加载类级别的成员(静态成员变量 静态块初始化)3.再加载对象级别的成员(实例成员变量 实例块初始化)
Q&A什么时候进行对象的实例化?
类加载成功(或已加载过)后,就开始进行对象的实例化了。对象的实例化依次进行了如下几个步骤:
1.对象在堆中的内存空间分配
2.初始化零值,这时会将实例变量都赋予零值
3.设置对象头,对象头保存了一些对象在运行期的状态信息,包括类信息地址(知道对象是属于哪个类的)、hashcode(用于hashmap和hashset等hash结构中)、GC分代年龄(可以依此确定对象何时该放入老年代)等
4.init方法执行,这时对变量的实例变量进行初始化
对象初始化的过程也是线程安全的动作。
StringBuffer StringBulider String 的区别:
StringBuffer线程安全,StringBulider线程不安全,底层实现StringBuffer比StringBulider多一个Synchronized.从源码中可以看得到:
StringBuffer源码分析:@Override public synchronized int length() { return count; } @Override public synchronized int capacity() { return value.length; } @Override public synchronized void ensureCapacity(int minimumCapacity) { if (minimumCapacity > value.length) { expandCapacity(minimumCapacity); } } /** * @since 1.5 */ @Override public synchronized void trimToSize() { super.trimToSize(); } /** * @throws IndexOutOfBoundsException {@inheritDoc} * @see #length() */ @Override public synchronized void setLength(int newLength) { toStringCache = null; super.setLength(newLength); } /** * @throws IndexOutOfBoundsException {@inheritDoc} * @see #length() */ @Override public synchronized char charAt(int index) { if ((index < 0) || (index >= count)) throw new StringIndexOutOfBoundsException(index); return value[index]; }
String 是否可以继承,“+”如何实现的,与StringBuffer区别?
java中通过使用“+”符号串联时实际是使用的StringBuilder实例的appdenf()方法来实现的。
构造器
考虑到在初始化期间要自动代用构造器,构造器采用与类名相同的名称,在java中“初始化”和“创建”捆绑在一起,两者不能分离。
- 是一个类创建对象的根本途径,如果一个类没有构造器,这个类通常无法创建实例。因此, Java语言提供了一个功能:如果程序员没有为一个类编写构造器,则系统会为该类提供一个默认的构造器静态变量调用:
- Java编程时不要使用对象去调用static修饰的Field、方法,而是应该使用类去调用static修饰的Field、方法!如果在其他Java代码中看到对象调用static修饰的Field、方法的情形,完全可以把这种用法当成假象,将其替换成用类来调用static修饰的Field、方法的代码
封装(Encapsulation),继承,多态
封装:
是面向对象的三大特征之一(另外两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。将对象的Field和实现细节隐藏起来,不允许外部直接访问。把方法暴露出来,让方法来控制对这些Field进行安全的访问和操作。
String的equals()方法判断两个字符串相等的标准是:只要两个字符串所包含的字符序列相同,通过equals()比较将返回true,否则将返回false。当使用==来判断两个变量是否相等时,如果两个变量是基本类型变量,且都是数值类型(不一定要求数据类型严格相同),则只要两个变量的值相等,就将返回true。
继承:
用extends关键字来指定接口继承哪个父类,
[修饰符]class 子类名 extends 父类名{类体}子类名必须选参数一般情况首字母大写,继承的父类必须选择参数,用于指定要定义的子类继承于哪个父类在继承中也有重写,就是子类中的方法名与父类的方法名相同,子类就不能继承父类的方法,子类重写了父类的方法;重写体现了子类对父类的补充和改变父类的能力;使一个方法在不同的子类中表现的行为不同;
抽象
抽象类中只有抽象方法没有方法体然后子类用继承的方法来把抽象方法重写,在抽象类中用abstract来定义类或者方法,修饰符不能为private,其中里面可包含普通方法,若子类不能实现其抽象方法则子类也必需定义为是抽象类在让他的子类来实现这些抽象类中的抽象方法;其中可以有静态的方法和变量。抽象类是对一种事物的抽象属于一类(整个类)模板式设计在类中添加方法子类不会变动,在调用时只能用子类new出一个对象来调用子类中重写的方法。
多态:
通常使用的方法是重载和重写来实现类多态的性,多态作用:消除类型之间的耦合关系。
重载 :是方法与方法之间的使用,其中方法的重载是一个类中有多个同名的方法,但是方法中的参数应不相同
// 判断时可以直接根据方法名字来判断;若调用时只需根据参数的类型,很参数的个数即可以调用
构造器就是一个很好的重载的例子:
比如有参构造器,无参构造器。
涉及基本类型的重载:基本类型能从一个”较小“的类型自动提升至一个”较大“的类型,此过程一旦牵扯到重载会造成混淆
重写:是子类与父类之间的关系中间涉及到继承与接口的知识
抽象类:
体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为方式。
抽象方法和空方法体的方法不是同一个概念。例如,public abstract void test();是一个抽象方法,它根本没有方法体,即方法定义后面没有一对花括号;但public void test(){}方法是一个普通方法,它已经定义了方法体,只是方法体为空,即它的方法体什么也不做,因此这个方法不可使用abstract来修饰。
abstract不能用于修饰Field,不能用于修饰局部变量,即没有抽象变量、没有抽象Field等说法;abstract也不能用于修饰构造器,没有抽象构造器,抽象类里定义的构造器只能是普通构造器。
抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给其子类去实现。父类中可能包含需要调用的其他系列方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。父类里提供的方法只是定义了一个通用算法,其实现也许并不完全由自身实现,而必须依赖于其子类的辅助。
接口:
对于接口里定义的常量Field而言,它们是接口相关的,而且它们只能是常量,因此系统会自动为这些Field增加static和final两个修饰符。也就是说,在接口中定义Field时,不管是否使用public static final修饰符,接口里的Field总将使用这三个修饰符来修饰。而且,接口里没有构造器和初始化块
对于接口里定义的方法而言,它们只能抽象方法,因此系统会自动为其增加abstract修饰符;由于接口里的方法全部是抽象方法,因此接口里不允许定义静态方法,即不可使用static修饰接口里定义的方法。不管定义接口里方法时是否使用public abstract修饰符,接口里的方法总是使用public abstract来修饰。
接口的实现:[修饰符]class 类名extends 父类名。接口中的变量是静态终结变量public abstract final 变量名,方法:是抽象的方法;不能有静态代码与方法;接口是对行为的抽象(局部类)辐射式设计接口中添加方法子类中都要变动;使用implements时接口列表必须选参数接口列表中存在多个接口名时各个接口名之间使用逗号在接口中:系统默认接口中的方法为抽象方法,不能在接口中有方法体同时要想实现接口的方法也要用继承然后在子类中重写接口中的抽象方法接口可以继承父接口但不能继承类,可以用子类继承接口,调用时与调用抽象类的方法是一样的,
接口与抽象类比较:
- 接口里只能包含抽象方法,不包含已经提供实现的方法;抽象类则完全可以包含普通方法。
- 接口里不能定义静态方法;抽象类里可以定义静态方法。
- 接口里只能定义静态常量Field,不能定义普通Field;抽象类里则既可以定义普通Field,也可以定义静态常量Field。
- 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
- 接口里不能包含初始化块;但抽象类则完全可以包含初始化块。
- 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。
内部类:
匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。因此不允许将匿名内部类定义成抽象类。
匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,通过实例初始化块来完成构造器需要完成的事情。
finalize()方法具有如下4个特点:
- 永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用。
- finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法。
- 当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态。
- 当JVM执行finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行。
注意:
由于finalize()方法并不一定会被执行,因此如果想清理某个类里打开的资源,则不要放在finalize()方法中进行清理,后面会介绍专门用于清理资源的方法。
java操作系统底层:
加载文件和动态链接库主要对native方法有用,对于一些特殊的功能(如访问操作系统底层硬件设备等)Java程序无法实现,必须借助C语言来完成,此时需要使用C语言为Java方法提供实现。其实现步骤如下:
- Java程序中声明native()方法,类似于abstract方法,只有方法签名,没有实现。编译该Java程序,生成一个class文件。
- 用javah编译第1步生成的class文件,将产生一个.h文件。
- 写一个.cpp文件实现native方法,其中需要包含第2步产生的.h文件(.h文件中又包含了JDK带的jni.h文件)。
- 将第3步的.cpp文件编译成动态链接库文件。
- 在Java中用System类的loadLibrary..()方法或Runtime类的loadLibrary()方法加载第4步产生的动态链接库文件,Java程序中就可以调用这个native()方法了。
java中获取当地时间:
public class LocaleList { public static void main(String[] args) { // 返回Java所支持的全部国家和语言的数组 Locale[] localeList = Locale.getAvailableLocales(); // 遍历数组的每个元素,依次获取所支持的国家和语言 for (int i = 0; i < localeList.length; i++) { // 输出所支持的国家和语言 System.out.println(localeList[i].getDisplayCountry() + "=" + localeList[i].getCountry() + " " + localeList[i].getDisplayLanguage() + "=" + localeList[i].getLanguage()); } }
}
初始化与清理:
垃圾清理的方式:
标记清除法:这是垃圾收集算法中最基础的,它的思想是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是效率不高,标记和清除的效率都很低;此外会产生大量不连续的内存碎片,从而导致以后程序在分配较大对象时由于没有充足的连续内存而提前触发一次 GC 操作。
复制算法:为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完后就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式内存的代价太高,每次基本上都要浪费一半的内存;于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为 8:1:1 三部分,较大那份内存是 Eden 区,其余是两块较小的内存区叫 Survior 区,每次都会优先使用 Eden 区,若 Eden 区满则将对象复制到第二块内存区上,然后清除 Eden 区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对象通过分配担保机制复制到老年代中。
标记整理法:这种方法主要是为了解决标记清除法产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。
分代收集法:现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记整理法或标记清除法。
构造器初始化:
当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始化,其初始化顺序是:先执行初始化块或声明属性时制定的初始值,再执行构造器里制定的初始值。在类的内部,变量定义的先后顺序决定了初始化的顺序,即时变量散布于方法定义之间,它们仍就会在任何方法(包括构造器)被调用之前得到初始化。
class Window { Window(int maker) { System.out.println("Window(" + maker + ")"); }}class House { Window window1 = new Window(1); House() { System.out.println("House()"); w3 = new Window(33); } Window window2 = new Window(2); void f() { System.out.println("f()"); } Window w3 = new Window(3);}public class OrderOfInitialization { public static void main(String[] args) { House h = new House(); h.f(); }}输出:Window(1)Window(2)Window(3)House()Window(33)f()由输出可见,w3这个引用会被初始化两次:一次在调用构造器之前,一次在调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)。
静态数据初始化
无论创建多少个对象,静态数据都只占一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。
class Bowl { Bowl(int maker) { System.out.println("Bowl(" + maker + ")"); } void f1(int maker) { System.out.println("f1(" + maker + ")"); }}class Table { static Bowl bowl1 = new Bowl(1); Table() { System.out.println("Table()"); bowl2.f1(1); } void f2(int maker) { System.out.println("f2(" + maker + ")"); } static Bowl bowl2 = new Bowl(2);}class Cupboard { Bowl bowl3 = new Bowl(3); static Bowl bowl4 = new Bowl(4); Cupboard() { System.out.println("CupBoard()"); bowl4.f1(2); } void f3(int maker) { System.out.println("f3(" + maker + ")"); } static Bowl bowl5 = new Bowl(5);}public class StaticInitialization { public static void main(String[] args) { System.out.println("created new Cupboard() in main"); new Cupboard(); System.out.println("created new Cupboard in main"); new Cupboard(); table.f2(1); cupboard.f3(1); } static Table table = new Table(); static Cupboard cupboard = new Cupboard();}输出Bowl(1)Bowl(2)Table()f1(1)Bowl(4)Bowl(5)Bowl(3)CupBoard()f1(2)created new Cupboard() in mainBowl(3)CupBoard()f1(2)created new Cupboard in mainBowl(3)CupBoard()f1(2)f2(1)f3(1)
从某种程度上来看,初始化是一段固定执行的代码,它不能接受任何参数。因此初始化块对同一个类所有对象所进行的初始化处理完全相同。基于这个原因,不难发现初始化块的基本用法,如果有一段初始化处理代码对所有对象完全相同,且无须接受任何参数,就可以把这段初始化处理代码提取到初始化块中。
初始化顺序先是静态的,然后“非静态的”
public class UUIDUtil{ Mug mug1; Mug mug2; { mug1=new Mug(); mug2=new Mug(); } static { System.out.println("UUIDUtil 静态代码块"); } UUIDUtil(){ System.out.println("UUIDUtil 构造函数"); } public static void main(String[] args) { new UUIDUtil(); }}class Mug{ Mug(){ System.out.println("MUG(构造方法)"); } static{ System.out.println("MUG static"); }}输出:UUIDUtil 静态代码块MUG staticMUG(构造方法)MUG(构造方法)UUIDUtil 构造函数// 形式二:public class UUIDUtil{ Mug mug1; Mug mug2; static { System.out.println("UUIDUtil 静态代码块"); } UUIDUtil(){ System.out.println("UUIDUtil 构造函数"); } { mug1=new Mug(); mug2=new Mug(); } public static void main(String[] args) { new UUIDUtil(); }}class Mug{ static{ System.out.println("MUG static"); } Mug(){ System.out.println("MUG(构造方法)"); }}输出UUIDUtil 静态代码块MUG staticMUG(构造方法)MUG(构造方法)UUIDUtil 构造函数上图把static{ System.out.println("MUG static");}修改为{ System.out.println("MUG static");}UUIDUtil 静态代码块MUG staticMUG(构造方法)MUG staticMUG(构造方法)UUIDUtil 构造函数
有上面可见在初始化的时候首先会调用
静态代码块--》非静态代码块-(静态代码块(只有一次),构造函数)-》构造函数,而{}包含的非静态代码块就是和构造方法一样会默认初始化。
数组初始化:
数组是一个具备相同类型的,用一个标识符名称封装到一个起的一个对象序列或基本类型数据序列。
对于数组初始化必须指定其大小,但是这样不需要比如 int[] al={1,22,3,4,5} 这种情况下存储空间的分配由编译器负责(等价于new)
数组的常用功能
数组 是一个效率高的存储和随机访问对象引用序列的方式。
常用方法:
equals():比较两个数组是否相等。
deepEquals():用于多维数组比较是否相等。
fill():填充数组。
sort():数组排序
binarySearch():用于已经排序的数组中查找元素。
toString():产生数组的toSting()表示
hashCode():产生数组的散列码。
System.arrayCopy();复制数组比用for循环速度快了许多。
数组元素的比较: 1.实现java.lang.Comparable接口,
1:所有可以 “排序” 的类都实现了java.lang.Comparable接口,Comparable接口中只有一个方法。2:public int compareTo(Object obj) ;该方法:返回 0 表示 this == obj返回整数表示 this > obj返回负数表示 this < obj3:实现了 Comparable 接口的类通过实现 comparaTo 方法从而确定该类对象的排序方式。
枚举类型:
可以参看:
Q&A对于初始化与清理常见的面试题:
Q1:java 对象加载初始化的顺序?
静态代码块--》非静态代码块-(静态代码块(只有一次),构造函数)-》构造函数,而{}包含的非静态代码块就是和构造方法一样会默认初始化
Q2:如何判断一个对象是否要回收?
这就是所谓的对象存活性判断,常用的方法有两种:1.引用计数法; 2.对象可达性分析。由于引用计数法存在互相引用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析算法。从GC Roots对象计算引用链,能链上的就是存活的。
1、引用计数法
引用计数法的逻辑非常简单,但是存在问题,java并不采用这种方式进行对象存活判断。
引用计数法的逻辑是:在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。
2、可达性分析算法
在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。
Q3:如何理解java中的垃圾回收机制?为什么需要垃圾回收机制?
java采用分代回收,分为年轻代、老年代、永久代。年轻代又分为E区、S1区、S2区。
到jdk1.8,永久代被元空间取代了。
年轻代都使用复制算法,老年代的收集算法看具体用什么收集器。默认是PS收集器,采用标记-整理算法。
System.gc()即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。如果你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐无内存使用,就会溢出。
Q4:java垃圾收集的方式有哪些?
上文已经讲述。
Q5:java垃圾回收机制的优缺点?
优点:
开发人员无须过多地关心内存管理,而是关注解决具体的业务。虽然内存泄漏在技术上仍然是可能出现的,但不常见。
GC 在管理内存上有很多智能的算法,它们自动在后台运行。与流行的想法相反,这些通常比手动回收更能确定什么时候是执行垃圾回收的最好时机。
缺点:
当垃圾回收发生时将影响程序的性能,显著地降低运行速度甚至将程序停止。所谓 “Stop the world” 就是当垃圾回收发生的时候应用程序的其他任务都将被冻结。对于应用程序的要求来说,这将是不可接收的,虽然 GC 调优可以最小化甚至消除这个影响。
虽然GC有很多方面可以调优,但你不能指定应用程序在何时怎样执行GC
Q6:java垃圾回收的时间有哪些?
CPU空闲时进行回收、堆内存满了后进行回收、调用System.gc()回收。
Q7:如果对象置为null,垃圾收集器是否会立即释放占用的内存?
不会。对象回收需要一个过程,这个过程中对象还能复活。而且垃圾回收具有不确定性,指不定什么时候开始回收。
Q8:什么是GC?为何有GC?
GC就是垃圾收集的意思(Gabage Collection)。我们在开发中会创建很多对象,这些对象一股脑的都扔进了堆里,如果这些对象只增加不减少,那么堆空间很快就会被耗尽。所以我们需要把一些没用的对象清理掉。jvm使用 jstat -gc 12538 5000 即会每5秒一次显示进程号为12538的java进成的GC情况.
Q9:方法区如何判断是否需要回收?
方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:① 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;② 加载该类的ClassLoader已经被回收;③ 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
Q10:如果频繁老年代回收怎么分析解决?