一、Java 创建对象的几种方式
1. 使用new关键字
这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们还可以调用任意的构造函数(无参的和有参的)。
User user = new User();
2. 使用反射机制
运用反射手段,调用Java.lang.Class
或者java.lang.reflect.Constructor
类的newInstance()
实例方法。
-
使用Class类的newInstance方法
可以使用Class
类的newInstance
方法创建对象。这个newInstance
方法调用无参的构造函数创建对象。
//创建方法1
User user = (User)Class.forName("根路径.User").newInstance();
//创建方法2(用这个最好)
User user = User.class.newInstance();
-
使用Constructor类的newInstance方法
和Class类的newInstance方法很像,java.lang.reflect.Constructor
类里也有一个newInstance
方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。
Constructor<User> constructor = User.class.getConstructor();
User user = constructor.newInstance();
这两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。
3. 使用clone方法
无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
要使用clone方法,我们需要先实现Object接口并实现其定义的clone方法。
public class CloneTest {// implements Object
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public CloneTest(String name, int age) {
super();
this.name = name;
this.age = age;
}
public static void main(String[] args) {
try {
CloneTest cloneTest = new CloneTest("wangql",18);
CloneTest copyClone = (CloneTest) cloneTest.clone();
System.out.println("newclone:"+cloneTest.getName());
System.out.println("copyClone:"+copyClone.getName());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
执行
newclone:wangql
copyClone:wangql
4. 使用反序列化
当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。
为了反序列化一个对象,我们需要让我们的类实现Serializable
接口。
二、Java类的实例化顺序?
父类静态成员和静态代码块 -> 子类静态成员和静态代码块 -> 父类非静态成员和非静态代码块 -> 父类构造方法 -> 子类非静态成员和非静态代码块 -> 子类构造方法
三、基本数据类型
1. 基本类型概念
java的基本数据类型可以简称为“四类八种
”:
- 整型:(
byte
、short
、int
、long
) 整数数据类型默认是int - 浮点型:(
float
、double
) - 字符型:(
char
) - 布尔类型:(
boolean
)
byte -> short -> char -> int -> long -> float ->double(小到大)
2. 数据类型转换:
自动类型转换(自动
),较小的类型转换为一个更大的类型
byte -> short -> char -> int -> long -> float ->double
强制类型转换(手动
),更大的类型转换到一个较小的类型
double -> float -> long -> int -> char -> short ->byte
3. 什么是包装类
因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int
、double
等类型放进去的。因为集合的容器要求元素是Object
类型。
为了让基本类型也具有对象的特征
,就出现了包装类型,它相当于将基本类型“包装起来
”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
4. 什么是自动拆箱和自动装箱
在Java SE5
中,为了减少开发人员的工作,Java提供了自动拆箱与自动装箱功能。
- 自动装箱: 就是将基本数据类型自动转换成对应的包装类。
- 自动拆箱:就是将包装类自动转换成对应的基本数据类型。
那些场景会发生装箱与拆箱
- 将基本数据类型放入集合类
- 包装类型和基本类型的大小比较
- 包装类型的运算
- 三目运算符的使用
- 函数参数与返回值
四、 final相关
1. 关于final修饰符
根据程序上下文环境,Java关键字final有“这是无法改变的
”或者“终态的
”含义,它可以修饰非抽象类
、非抽象类成员方法
和变量
。
你可能出于两种理解而需要阻止改变
:设计或效率。
- final类不能被继承,没有子类,final类中的方法默认是final的。
- final方法不能被子类的方法覆盖,但可以被继承。
- final成员变量
表示常量
,只能被赋值一次
,赋值后值不再改变。 - final不能用于修饰构造方法。
注意:父类的private成员方法是不能被子类方法覆盖的,因此private
类型的方法默认是final类型
的。 - 如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
使用final方法的原因有二:
- 把方法锁定,防止任何继承类修改它的意义和实现。
- 高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。(这点有待商榷,
《Java编程思想》中对于这点存疑
)
2. final和static的区别:
很多时候会容易把static
和final
关键字混淆,static
作用于成员变量用来表示只保存一份副本,而final
的作用是用来保证变量不可变。
3. 是否可以继承String类,为什么
不能
被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。
Java对String类的定义:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
// 省略...
}
三、String和StringBuilder、StringBuffer的区别?
StringBuilder:适用于单线程
下在字符缓冲区
进行大量操作
的情况(是线程不安全
的)
StringBuffer:适用多线程
下在字符缓冲区进行大量操作的情况(一般很少)(是线程安全
的)
运行速度:StringBuilder
> StringBuffer
> String
四、值传递和引用传递的理解
1. 基本类型和引用类型的理解
Java中的数据类型分为两种为基本类型
和引用类型
。
- 基本类型的变量保存原始值,所以变量就是
数据本身
。
常见的基本类型:byte,short,int,long,char,float,double,Boolean,returnAddress
。 - 引用类型的变量保存引用值,所谓的引用值就是对象所在内存空间的“
首地址值
”,通过对这个引用值来操作对象。
常见的引用类型:类类型
,接口类型
和数组
。
int num = 10;
String str = "hello";
2. 值传递和引用传递的理解
实参和形参
- 形参:方法被调用时需要传递进来的参数,如:func(int a)中的a,它只有在func被调用期间a才有意义,也就是会被分配内存空间,在方法func执行完成后,a就会被销毁释放空间,也就是不存在了
- 实参:方法被调用时是传入的实际值,它在方法被调用前就已经被初始化并且在方法被调用时传入。
值传递
指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递
是指在调用函数时将实际参数的地址值的复制份传递到函数中(的形参),那么在函数中对参数所进行的修改,将影响到实际参数。
总结:
java中只有值传递
,所谓的引用传递
也是地址值的复制份,不是真的引用传递。
五、异常与错误
- 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
- 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
从图中可以看出所有异常类型都是内置类Throwable的子类
,因而Throwable在异常类的层次结构的顶层。
1、try{} 里有一个 return 语句,那么紧跟在这个 try 后的 finally{} 里的 code 会不会被执行,什么时候被执行,在 return 前还是后?
答案:会执行,在方法返回调用者前执行。
2、Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?
答:一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally为确保一段代码不管发生什么异常状况都要被执行;try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。
3、运行时异常与受检异常有何异同?
答:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在Effective Java中对异常的使用给出了以下指导原则:
- 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
- 对可以恢复的情况使用受检异常,对编程错误使用运行时异常
- 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
- 优先使用标准的异常
- 每个方法抛出的异常都要有文档
- 保持异常的原子性
- 不要在catch中忽略掉捕获到的异常
4、throw和throws的区别
throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
throws语句用在方法声明后面,表示抛出异常,由该方法的调用者来处理。
throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。
throw是当程序出现某种逻辑错误时由程序员主动抛出某种特定类型的异常是,具体向外抛异常的动作,所以它是抛出一个异常实例。
六、java中垃圾收集的方法有哪些?
1. 引用计数算法(Reference Counting)
介绍:给对象添加一个引用计数器,每当一个地方引用它时,数据器加1;当引用失效时,计数器减1;计数器为0的即可被回收。
优点:实现简单,判断效率高
缺点:很难解决对象之间的相互循环引用(objA.instance = objB; objB.instance = objA)的问题,所以java语言并没有选用引用计数法管理内存
2. 根搜索算法(GC Root Tracing)
Java和C#都是使用根搜索算法来判断对象是否存活。通过一系列的名为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时(用图论来说就是GC Root到这个对象不可达时),证明该对象是可以被回收的。
在Java中哪些对象可以成为GC Root?
虚拟机栈(栈帧中的本地变量表)中的引用对象
方法区中的类静态属性引用的对象
方法区中的常量引用对象
本地方法栈中JNI(即Native方法)的引用对象
3. 标记-清除算法(Mark-Sweep)
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记那些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:
1.效率不高,标记和清除的效率都很低;
2.会产生大量不连续的内存碎片,导致以后程序在分配交大的对象时,由于没有充足的连续内存而提前触发一次GC动作。
4. 复制算法(Copying)
为了解决效率问题,复制算法将可用内存按容量划分相等的两部分,然后每次只使用其中的一块,当第一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清除完第一块内存,在将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一块内存。
于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大的那份内存叫Eden区,其余两块较小的内存叫Survior区。每次都会先使用Eden区,若Eden区满,就将对象赋值到第二块内存上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制赋值到老年代中。(java堆又分为新生代和老年代)。
5. 标记-整理算法(Mark-Compact)
该算法是为了解决标记-清楚,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收的对象移动一端,然后清除掉端边界以为的对象,这样就不会产生内存碎片。
6. 分代收集算法(Generational Collection)
根据对象的存活周期的不同将内存划分为几块,一般就分为新生代和老年代,根据各个年代的特点采用不同的收集算法。新生代(少量存活)用复制算法,老年代(对象存活率高)“标记-清理”算法
补充:分代划分内存介绍
整个JVM内存总共划分为三代:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)
1、年轻代:所有新生成的对象首先都放在年轻代内存中。年轻代的目标就是尽可能快速的手机掉那些生命周期短的对象。年轻代内存分为一块较大的Eden空间和两块较小的Survior空间,每次使用Eden和其中的一块Survior.当回收时,将Eden和Survior中还存活的对象一次性拷贝到另外一块Survior空间上,最后清理Eden和刚才用过的Survior空间。
2、年老代:在年轻代经历了N次GC后,仍然存活的对象,就会被放在老年代中。因此可以认为老年代存放的都是一些生命周期较长的对象。
3、持久代:基本固定不变,用于存放静态文件,例如Java类和方法。持久代对GC没有显著的影响。持久代可以通过-XX:MaxPermSize=进行设置。
七、类加载机制及双亲委派模型
类加载机制
虚拟机将描述类的数据从class文件加载到内存中,对加载的数据进行验证,解析,初始化,最后得到虚拟机认可后转化为直接可以使用的java类型的过程
双亲委派模型
类加载器有是三个:启动类加载器、扩展类加载器、应用程序加载器(系统加载器)
工作过程是:如果一个类加载器收到了一个类加载的请求,它首先不会去加载类,而是去把这个请求委派给父加载器去加载,直到顶层启动类加载器,如果父类加载不了(不在父类加载的搜索范围内),才会自己去加载。
1. 启动类加载器:加载的是lib目录中的类加载出来,包名是java.xxx(如:java.lang.Object)
2. 扩展类加载器:加载的是lib/ext目录下的类,包名是javax.xxx(如:javax.swing.xxx)
3. 应用程序扩展器:这个加载器就是ClassLoader的getSystemClassLoader的返回值,这个也是默认的类加载器。
双亲委派模型的意义在于不同的类之间分别负责所搜索范围内的类的加载工作,这样能保证同一个类在使用中才不会出现不相等的类,举例:如果出现了两个不同的Object,明明是该相等的业务逻辑就会不相等,应用程序也会变得混乱。
八、反射机制有哪些优点和缺点
1. 优点:
- 静态编译:
在编译时确定类型,绑定对象,即通过。 - 动态编译:
运行时确定类型,绑定对象。动态编译最大限度的发挥了java的灵活性,体现了多态的应用,有利于降低类之间的耦合性。
一句话,反射机制的优点就是可以实现动态创建对象和编译
,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
2. 缺点:
是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。
九、 final, finally, finalize有什么区别
final
修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载 。finally
在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。finalize
方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。
十、简单介绍原生jdbc执行sql过程
- class.forName()加载数据驱动
- DriverManager.getConnection()获取数据库连接对象。
- 根据SQL或sql会话对象,有两种方式Statement、PreparedStatement。
- 执行sql处理结果集,如果有参数就设置参数。
- 关闭结果集,关闭会话,关闭资源。
十一、char 型变量中能不能存贮一个中文汉字,为什么?
char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode,一个char类型占2个字节(16比特),所以放一个中文是没问题的。
十二、两个对象值相同(x.equals(y) == true),但却可以有不同的hashcode?
这个得看情况,如果该对象重写了equals方法,那么可能会出现equals相同,但hashcode不同的情况,但假如没有重写equals方法,那么它默认继承是Object的equals方法,根据源码可知,此时equals相同,hashcode一定相同。
如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
如果两个对象的hashCode相同,它们并不一定相同。
十三、构造器(constructor)是否可被重写(override)?
Constructor(构造器)不能被继承,所以不能被override(重写),但是可以被overloading(重载)。