JAVA拾遗录

JAVA拾遗录

1. JAVA堆和栈的区别

堆和栈都是内存的一部分,有着不同的作用,而且一个程序需要在这片区域上分配内存。众所周知,所有的JAVA程序都运行在JVM上,这里所说的自然是JVM中的堆和栈。

最主要的区别就是栈内存用来存储局部变量和方法调用。而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。

栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的遍历只能在其所属线程中可见,即栈内存可以理解成线程的私有内存,而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError;如果堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError.

栈的内存要远远小于堆内存,如果你使用递归的话,那么你的栈很快就会充满。如果递归没有及时跳出,很可能发生StackOverFlowError问题。你可以通过-Xss选项设置栈内存的大小,-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。

2. 入口方法

一般入口方法:public static void main(String args[])。即必须有public与static修饰,返回值为void,且方法的参数为字符串数组。

public static没有先后顺序关系,这样static public void main(String args[])也是合理的,或者加入final:public static final void main(String args[])或者加入synchronized:public static synchronized void main(String args[])也可以。不管哪种定义方式,都必须保证main()方法的返回值为void,并有static和public关键字修饰。同时由于mian()方法为程序的入口方法,因此不能用abstract关键字来修饰。

3. Java程序初始化顺序

父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码块、子类构造函数。

4. 标识接口

在Java中,有些接口内部没有声明任何方法,也就是说,实现这些接口的类不需要重写任何方法,这些没有任何方法声明的接口就是标识接口。标识接口对实现它的类没有任何语义上的要求,它仅仅充当一个标识的作用,用来表明它的类属于一个特定的类型。Java中的标识接口有:RandomAccess, Cloneable, java.io.Serializable等(欢迎在留言区留言添加)。在使用时经常用instanceof来判断实例对象的类型是否实现了一个给定的标识接口。

5. 构造函数

构造函数不能被继承,因此,它不能被覆盖,但是构造函数能够被重载,可以使用不同的参数个数或参数类型来定义多个构造函数。子类可以通过super关键字来显示地调用父类的构造函数,当父类没有提供无参数的构造函数时,子类的构造函数必须显示地调用父类父类的构造函数。

当父类和子类都没有定义构造函数时,编译器会为父类生成一个默认的无参数的构造函数,给子类也生成一个默认的无参数的构造函数。此外,默认构造器的修饰符只能当前类的修饰符有关。譬如,如果一个类被定义为public,那么它的构造函数也是public。

6. Java创建对象的方法

  • 通过new语句实例化一个对象
  • 通过反射机制创建对象
  • 通过clone()方法创建一个对象
  • 通过反序列化的方式

7. 获取class对象的方法

  • Class.forName(“类的全路径”)
  • 类名.class
  • 类的实例.getClass()
  • 对于基本数据类型有:Class c1 = int.class(class只是约定标记,不是成员属性) 或者Class c2 = Integer.TYPE

8. 面向对象的特性

封装、继承、多态、抽象

9. 重载(Overload)和重写(Override)的区别

重载:重载是通过不同的方法参数来区分的,例如不同的参数个数、不同的参数类型或不同的参数顺序;不能通过方法的访问权限、返回值类型和抛出的异常类型来进行重载;对于继承来说,如果基类方法的访问权限为private,那么就不能再派生类对其重载;如果派生类也定义了一个同名的函数,这只是一个新的方法,不会达到重载的效果。

重写:派生类的覆盖方法必须要和基类中被覆盖方法有相同的函数名和参数;派生类的覆盖方法的返回值必须和基类中被覆盖方法的返回值相同;派生类中的覆盖方法所抛出的异常必须和基类(或其子类)被覆盖的方法所抛出的异常一致;基类中被覆盖的方法不能为private,否则器子类只是定义了一个方法,并没有对其覆盖。

区别:重写是子类和父类之间的关系,是垂直关系;重载是同一个类中方法之间的关系,是水平关系;重写只能由一个方法或只能由一对方法产生关系,重载是多个方法之间的关系;重写要求参数列表相同,重载要求参数列表不同;重写关系中,调用方法体是根据对象的类型来决定,而重载关系是根据调用时的实参数与形参表来选择方法体。

10. 抽象类和接口

抽象类:只要包含一个抽象方法的类就必须被声明为抽象类,抽象类可以声明方法的存在而不去实现它,被声明为抽象的方法不能包含方法体。抽象类的子类为父类中的所有抽象方法提供具体的实现,否则它们也是抽象类。抽象类的成员变量默认为default,当然也可以被定义为private, protected和public,抽象类中的抽象方法不能用private, static, synchronized, native等修饰,同时方法必须以分号结尾,并且不带花括号。

接口: 接口中定义的成员变量默认为public static final,只能够有静态的不能被修改的数据成员,而且必须给其赋值;其所有成员方法都是public, abstract的,而且只能被这两个关键字修饰。

11. this和super

1)调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
2)super()和this()类似,区别是,super从子类中调用父类的构造方法,this()在同一类内调用其它方法。
3)super()和this()均需放在构造方法内第一行。
4)尽管可以用this调用一个构造器,但却不能调用两个。
5)this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
6)this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
7)从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。

详细参考《Java中this()和super()的注意点

12. 标识符

只能由字母(a-z, A-Z)、数字(0-9)、下划线(_)和$组成。并且标识符的第一个字符必须是字母、下划线或者$ 。

13. final, finally 和finalize的区别

final:用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖和类不可继承。

finally:作为异常处理的一部分,它只能用在try-catch语句中,并且附带一个语句块表示这段语句最终一定被执行,经常被用在需要释放资源的情况下。

finalize:Object类的一个方法,在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收,例如关闭文件等,需要注意的是,一旦垃圾回收器准备好释放对象占用的空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

14. instanceof, isInstance以及isAssignableFrom

  • instanceof:用来判断对象是否是类的实例
  • isInstance:用来判断对象是否属于某个类型的实例
  • isAssignableFrom:用来判断类型间是否存在派生关系

15. 基本数据类型

在Java中,基本数据类型包括:byte(1B), int(4B), char(2B), long(8B), short(2B), float(4B), double(8B), boolean(1B)。默认声明的小数是double类型的,因此对float类型的变量进行初始化时需要进行类型转换。float类型的变量由两种初始化方法:float f=1.0f 或float f=(float)1.0。于此类似的是,直接写的整型数字是int类型的,如果在给数据类型为long的变量直接赋值时,int类型的值无法表示一个非常大的数字,因此,在复制时可以通过这种方法:long l = 2313091390L。

16. 强制类型转换

当参与运算的两个变量的数据类型不同时,就需要进行隐式的数据类型转换,转换的规则为:从低精度向高精度转换,即优先级满足byte<short<char<int<long<float<double.

char类型的数据转换为高级类型,会转换为其对应的ASCII码;byte,char,short类型的数据在参与运算时会自动转换为int型,担当使用+=运算时,就不会产生类型转换;基本数据类型与boolean类型是不能相互转换的。

在设计byte,short和char类型的运算时,首先会把这些类型的变量值强制转换为int类型,然后对int类型的值进行计算,最后得到的值也是int类型。考虑一下情况:

short s1=1;
s1=s1+1;

由于在运行时会首先将s1转换成int类型,因此s1+1的结果为int类型,这样编译器会保存,所以正确的写法应该是:shoet s1=1; s1=(short)(s1+1);。

有一种例外情况。“+=”为java规定的运算符,编译器会对其做特殊处理,因此语句:

short s1=1; s1+=1;

能编译通过。

17. 运算符的优先级

优先级 运算符
1 . () []
2 +(正) -(负) ++ – ~ !
3 * / %
4 +(加) -(减)
5 << >> >>>
6 < <= > >= instanceof
7 == !=
8 &
9 |
10 ^
11 &&
12 ||
13 ?:
14 = += -= *= /= %= &= |= ^= ~= <<= >>= >>>=

在实际使用时,如果不确定运算符的优先级,最好运用括号运算符来控制运算顺序。

例题(运算结果为0):

byte a=5;
int b=10;
int c=a>>2 + b>>2;
System.out.println(c);

18. String, StingBuilder, StringBuffer, StringTokenizer

StringBuilder非线程安全,StringBuffer是线程安全的。在执行效率方面,StringBuilder最高,StringBuffer次之,String最低。一般而言,如果要操作的数据量较小,应优先使用String类;如果是在单线程下操作大量数据,应优先使用StringBuilder类,如果是在多线程下操作大量数据,应优先考虑StringBuilder类。StringTokenizer是用来分割字符串的工具类。

19. finally块中的代码什么时候被执行。

问题描述:try{}里有一个return语句,那么紧跟在这个try后的finally{}中的代码是否会被执行?如果会,什么时候执行,在return之前还是return之后?

案例1:

static int method3()
{
try
{
return 1;
}
catch(Exception e)
{
return 0;
}
finally
{
System.out.println("method3 finally");
return 3;
}
}

如果在main()函数中运行System.out.println(method3());的结果为:method3 finally 3

由上可知,当finally块中有return语句时,将会覆盖函数中其他的return语句。

再看案例2:

static int method4()
{
int result = 1;
try{
result=2;
return result;
}
catch(Exception e){
return 0;
}
finally{
result = 3;
System.out.println("method4 finally");
}
}

static String method5()
{
StringBuffer s = new StringBuffer("Hello");
try
{
return s.toString();
}
catch(Exception e){
return null;
}
finally{
s.append(" World");
System.out.println("method5 finally");
}
}

在入口函数中运行:

System.out.println(method4());
System.out.println(method5());

输出:

method4 finally
2
method5 finally
Hello

程序在执行到return时首先将返回值存储在一个指定的位置,其次去执行finally块,最后再返回。

出现在Java程序中的finally块是不是·会被执行?不一定!来看两个例子:

案例3:

static void method6()
{
int i=5/0;
try{
System.out.println("1");
}catch(Exception e){
System.out.println("2");
}finally{
System.out.println("3");
}
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
at test.TestTest.method6(TestTest.java:80)
at test.TestTest.main(TestTest.java:96)

当程序在进入try语句块之前就出现异常时,会直接借宿,不会执行finally块中的代码。

案例4:

static void method7(){
try{
System.out.println("1");
System.exit(0);
}catch(Exception e){
System.out.println("2");
}finally{
System.out.println("3");
}
}

运行结果:1

当程序在try块中强制退出时也不会去执行finally块中的代码。

20. 检查异常(checked exception)和运行时异常(runtime exception)

检查式异常:我们经常遇到的IO异常及sql异常就属于检查式异常。对于这种异常,java编译器要求我们必须对出现的这些异常进行catch 所以 面对这种异常不管我们是否愿意,只能自己去写一堆catch来捕捉这些异常。

JDK1.7中有(部分):

  • EOFException 文件已结束异常
  • FileNotFoundException 文件未找到异常
  • SQLException 操作数据库异常
  • IOException 输入输出异常
  • NoSuchMethodException 方法未找到异常
  • ClassNotFoundException
  • NamingException
  • InterruptedException

运行时异常:我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。
详细请参考《JAVA运行时异常及检查式异常

21.序列化和反序列化

所有要实现序列化的类都必须实现Serializable接口。使用一个输出流(例如FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,紧接着,使用该对象的writeObject(Object obj)方法就可以将obj对象写出(即保存其状态),要恢复时可以使用其对应的输入流。

  • 当父类继承Serializable接口时,所有子类都可以被序列化。
  • 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,数据不会丢失),但是在子类中的属性仍能正确序列化
  • 如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错。
  • 在反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错。
  • 在反序列化时,如果serialVersionUID被序列化,则反序列化时会失败
  • 当一个对象的实例变量引用其他对象,序列化改对象时,也把引用对象进行序列化
  • static,transient后的变量不能被序列化

详细可以参考《JAVA序列化

22. System.out.println()

举个案例来引起注意:

class TestA{
@Override public String toString(){
return "I'm TestA!";
}
}
System.out.println(new TestA());
System.out.println(1+2+"");
System.out.println(""+1+2);
System.out.println(0+'0');

输出结果:

I'm TestA!
3
12
48

System.out.println()中传入的参数是一个对象则会调用这个对象的toString()方法。对于第二个输出语句,参数中的+会由左到右的顺序计算。首先计算1+2=3,接着计算3+“”则转变成字符串3。第3、4个输出语句可以类推得出结论。

23. JDBC访问数据库

String user = "zzh";
String password = "zzh";
String url ="jdbc:mysql://localhost:3306/Test";
String driver = "com.mysql.jdbc.Driver";
String sql = "select * form tableName";

Connection conn = null;
Statement stmt = null;
ResultSet rs = null;

try
{
Class.forName(driver);
conn = DriverManager.getConnection(url,user,password);
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
while(rs.next())
{
System.out.println(rs.getString(1)+" "+rs.getInt(2));
}
}
catch (ClassNotFoundException | SQLException e)
{
e.printStackTrace();
}
finally
{
try{
if(rs!=null){
rs.close();
}
if(stmt != null){
stmt.close();
}
if(conn!=null){
conn.close();
}
}catch(SQLException e){
System.out.println(e.getMessage());
}
}

24. Statement, PreparedStatement和CallableStatement

Statement用于执行不带参数的简单SQL语句,并返回它所生成结果的对象,每次执行SQL语句时,数据库都要编译该SQL语句。PreparedStatement表示预编译的SQL语句的对象,用于执行带参数的预编译SQL语句。

PreparedStatement与Statement相比具有:

  • 效率更高。在使用PreparedStatement对象执行SQL命令时,命令会被数据库进行编译解析,并放到命令缓冲去。然后,每当执行同一个PreparedStatement对象时,由于在缓冲区中可以发现预编译的命令,虽然它会被仔解析一次,但不会被再次编译,是可以重复使用的,能够有效提高系统的性能,因此,如果要执行插入,更新,删除等操作,最好使用PreparedStatement。
  • 代码可读性和可维护性好。
  • 安全性好。使用PreparedStatement能够预防SQL注入攻击。

CallableStatement则提出了用来调用数据库中存储过程的接口,如果有输出参数要注册,说明是输出参数。CallableStatement由prepareCall()方法所创建,它为所有的DBMS提供了一种标准形式调用已存储过程的方法。它从PreparedStatement中级车了用于处理输入参数的方法,而且还增加了调用数据库中的存储过程和函数以及设置输出类型参数的功能。

25. JDK命令行工具

名称 主要作用
jps JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程
jstat JVM Statistics Monitoring Tool, 用于收集HotSpot虚拟机各方面的运行数据
jinfo Configuration Info for Java, 显示虚拟机配置信息
jmap Memory Map for Java, 生成虚拟机的内存转储快照(head dump)
jhat JVM Heap Dump Browser,用于分析heapdump文件,它会简历一个HTTP/HTML服务器,让用户可以再浏览器上查看分析结果
jstack Stack Trace for Java, 显示虚拟机的线程快照(thread dump)

26. MinorGC, MajorGC以及FullGC

  • Full GC == Major GC指的是对老年代/永久代的stop the world的GC
  • Full GC的次数 = 老年代GC时 stop the world的次数
  • Full GC的时间 = 老年代GC时 stop the world的总时间
  • CMS 不等于Full GC,我们可以看到CMS分为多个阶段,只有stop the world的阶段被计算到了Full GC的次数和时间,而和业务线程并发的GC的次数和时间则不被认为是Full GC
  • Full GC本身不会先进行Minor GC,我们可以配置,让Full GC之前先进行一次Minor GC,因为老年代很多对象都会引用到新生代的对象,先进行一次Minor GC可以提高老年代GC的速度。比如老年代使用CMS时,设置CMSScavengeBeforeRemark优化,让CMS remark之前先进行一次Minor GC。

27. 如何获得ClassLoader

  • this.getClass().getClassLoader();//使用当前类的ClassLoader
  • Thread.currentThread().getContextClassLoader();//使用当前线程的ClassLoader
  • ClassLoader.getSystemClassLoader();//使用系统ClassLoader

参考资料:

  1. Java中this()和super()的注意点
  2. JAVA运行时异常及检查式异常
  3. JAVA序列化

欢迎支持笔者的作品《深入理解Kafka: 核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客(ID: hiddenkafka)。
本文作者: 朱小厮

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×