java基础题库详解

目录

1 JDK和JRE有什么区别?

1.1、JRE

1.2、JDK

2、==和equals的区别是什么?

3、比较

4、装箱,拆箱

4.1、什么是装箱?什么是拆箱?

4.2、装箱和拆箱的执行过程?

4.3、常见问题

5、hashCode()相同,equals()也一定为true吗?

6、final在java中的作用

7、final finally finalize()区别

7.1、final

7.2、finally

7.3、finalize()

8、finally语句块一定执行吗?

9、final与static的区别

9.1、区别

9.2、static

9.3、final

10 、return与finally对返回值的影响

11、String的replace和replaceAll的区别?

12、 Math.round(-1.5) 等于多少?

13、String属于基础的数据类型吗?

14、java中操作字符串都有哪些类?它们之间有什么区别?

15、如何将字符串反转?

16、String类的常用方法有哪些?

17、 普通类和抽象类有哪些区别?

18、抽象类能使用final修饰吗?

19、接口和抽象类有什么区别?

20 Java访问修饰符有哪些?权限的区别?

21 Java中的 << >> >>> 是什么?

22 javap的作用是什么?

23 throw和throws的区别?

24、try-catch-finally中哪个部分可以省略?

25、常见的异常类有哪些?

26、什么是JAVA内部类?

27 nio中的Files类常用方法有哪些?

28 什么是反射?有什么作用?

29、动态代理是什么?应用场景?


1 JDK和JRE有什么区别?

1.1、JRE

Java Runtime Environment( java 运行时环境)。即java程序的运行时环境,包含了 java 虚拟机,java基础类库。

1.2、JDK

Java Development Kit( java 开发工具包)。即java语言编写的程序所需的开发工具包。

备注:JDK 包含了 JRE,同时还包括 java 源码的编译器 javac、监控工具 jconsole、分析工具 jvisualvm等。

2、==和equals的区别是什么?

==是关系运算符,equals() 是方法,结果都返回布尔值

1)Object 的比较 ==和equals() 比较的都是地址,作用相同,== 不能比较没有父子关系的两个对象

2)基本类型比较 只有==比较值是否相等

3、比较

1)值不同 

使用 == 和 equals() 比较都返回 false

2)值相同== 比较

基本类型 - 基本类型、基本类型 - 包装对象返回 true
包装对象 - 包装对象 或不同对象返回 false

Integer 缓存中取的包装对象比较返回 true(原因是 JVM 缓存部分基本类型常用的包装类对象,如 Integer -128 ~ 127 是被缓存的)
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;

System.out.println(i1==i2); //打印true
System.out.println(i3==i4); //打印false

3)值相同equals 方法

equals JDK 中的类一般已经重写了 equals(),比较的是内容
自定义类如果没有重写 equals(),将调用父类(默认 Object 类)的 equals() 方法,Object的 equals() 比较使用了 this == obj,可以按照需求逻辑,重写对象的 equals() 方法(重写 equals 方法,一般须重写 hashCode 方法)

4、装箱,拆箱

4.1、什么是装箱?什么是拆箱?

装箱:基本类型转变为包装器类型的过程。
拆箱:包装器类型转变为基本类型的过程。

 

//JDK1.5之前是不支持自动装箱和自动拆箱的,定义Integer对象,必须 Integer i = new Integer(8);
 

//JDK1.5开始,提供了自动装箱的功能,定义Integer对象可以这样

Integer i = 8; // 装箱
int n = i; //自动拆箱

4.2、装箱和拆箱的执行过程?

1)装箱是通过调用包装器类的 valueOf 方法实现的
2)拆箱是通过调用包装器类的 xxxValue 方法实现的,xxx代表对应的基本数据类型。

如int装箱的时候自动调用Integer的valueOf(int)方法;Integer拆箱的时候自动调用Integer的intValue方法。

4.3、常见问题

1)  整型的包装类 valueOf 方法返回对象时,在常用的取值范围内,会返回缓存对象。
2)  浮点型的包装类 valueOf 方法返回新的对象。
3)布尔型的包装类 valueOf 方法 Boolean类的静态常量 TRUE | FALSE, == 和 equals 相等,跟Integer 类似

4)包含算术运算会触发自动拆箱
存在大量自动装箱的过程,如果装箱返回的包装对象不是从缓存中获取,会创建很多新的对象,比较消耗内存。

5、hashCode()相同,equals()也一定为true吗?

首先,答案肯定是不一定。同时反过来 equals() 为true,hashCode() 也不一定相同。

类的 hashCode() 方法和 equals() 方法都可以重写,返回的值完全在于自己定义。
hashCode() 返回该对象的哈希码值;equals() 返回两个对象是否相等。

关于 hashCode() 和 equals() 是方法是有一些 常规协定:

1) 两个对象用 equals() 比较返回true,那么两个对象的hashCode()方法必须返回相同的结果。
2) 两个对象用 equals() 比较返回false,不要求hashCode()方法也一定返回不同的值,但是最好返回不同值,以提搞哈希表性能。
3) 重写 equals() 方法,必须重写 hashCode() 方法,以保证 equals() 方法相等时两个对象 hashcode() 返回相同的值。

6、final在java中的作用

final 语义是不可改变的。

1) 被 final 修饰的类,不能够被继承。

2) 被 final 修饰的成员变量必须要初始化,赋初值后不能再重新赋值(可以调用对象方法修改属性值)。对基本类型来说是其值不可变;对引用变量来说其引用不可变,即不能再指向其他的对象。

3) 被 final 修饰的方法代表不能重写。

7、final finally finalize()区别

7.1、final

表示最终的、不可改变的,final用于修饰类、方法和变量。

​​final 变量必须在声明时给定初值,只能读取,不可修改。

final 方法也同样只能使用,不能重写,但能够重载。

final 修饰的对象,对象的引用地址不能变,但对象的属性值可以改变

7.2、finally

finally异常处理的一部分,它只能用在 try/catch 语句中,表示希望 finally 语句块中的代码最后一定被执行(存在一些情况导致 finally 语句块不会被执行,如 jvm 结束)

7.3、finalize()

finalize是在 java.lang.Object 里定义的,Object 的 finalize() 方法什么都不做,对象被回收时 finalize() 方法会被调用。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要清理工作,在垃圾收集器删除对象之前被调用的。一般情况下,此方法由JVM调用。特殊情况下,可重写 finalize() 方法,当对象被回收的时候释放一些资源,须调用 super.finalize() 。

8、finally语句块一定执行吗?

不一定。存在很多特殊情况导致 finally 语句块不执行。如:
直接返回未执行到 finally 语句块

public static String test() { 
    String str = null; 
    int i = 0; 
    if (i == 0) { 
        return str;//直接返回未执行到finally语句块 
    } 
    try { 
        System.out.println("try..."); 
        return str; 
    } finally { 
        System.out.println("finally...");
    } 
} 

抛出异常未执行到 finally 语句块

public static String test2() { 
    String str = null; 
    int i = 0; 
    i = i / 0;
    //抛出异常未执行到finally语句块 
    try { 
        System.out.println("try..."); 
        return str;
    } finally {
        System.out.println("finally..."); 
    } 
} 

系统退出未执行到 finally 语句块

public static String test3() { 
    String str = null; 
    try { 
        System.out.println("try..."); 
        System.exit(0);//系统退出未执行到finally语句块 
        return str; 
    } finally { 
        System.out.println("finally..."); 
    } 
}

9、final与static的区别

9.1、区别

都可以修饰类、方法、成员变量。
static 可以修饰类的代码块,final 不可以。
static 不可以修饰方法内的局部变量,final 可以。

9.2、static

static 修饰表示静态或全局,被修饰的属性和方法属于类,可以用类名.静态属性 / 方法名 访问
static 修饰的代码块表示静态代码块,当 Java 虚拟机(JVM)加载类时,就会执行该代码块,只会被执行一次
static 修饰的属性,也就是类变量,是在类加载时被创建并进行初始化,只会被创建一次
static 修饰的变量可以重新赋值
static 方法中不能用 this 和 super 关键字
static 方法必须被实现,而不能是抽象的abstract
static 方法只能被 static 方法覆盖

9.3、final

final 修饰表示常量、一旦创建不可改变
final 标记的成员变量必须在声明的同时赋值,或在该类的构造方法中赋值,不可以重新赋值
final 方法不能被子类重写
final 类不能被继承,没有子类,final 类中的方法默认是 final 的
final 不能用于修饰构造方法
private 类型的方法默认是final类型的

final 和 static 修饰成员变量加载过程例子

import java.util.Random;

public class TestStaticFinal {
 
    public static void main(String[] args) {
        StaticFinal sf1 = new StaticFinal();
        StaticFinal sf2 = new StaticFinal();
        
        System.out.println(sf1.fValue == sf2.fValue);//打印false
        System.out.println(sf1.sValue == sf2.sValue);//打印true
    }
}
 
class StaticFinal {
    final int fValue = new Random().nextInt();
    static int sValue = new Random().nextInt();
    
}

10 、return与finally对返回值的影响

finally 没有 return,finally 对 return 变量的重新赋值修改无效

public static void main(String[] args) { 
    System.out.println(getString()); 
} 
public static String getString() { 
    String str = "A"; 
    try { 
        str = "B"; 
        return str; 
    } finally { 
        System.out.println("finally change return string to C"); 
        str = "C"; 
    } 
}

finally change return string to C B

2)try 和 finally 都包含return,return 值会以 finally 语句块 return 值为准
如下面的例子

    public static void main(String[] args) {
		System.out.println(getString());
	}
	
	public static String getString() {
		String str = "A";
		try {
			str = "B";
			return str;
		} finally {
			System.out.println("finally change return string to C");
			str = "C";
			return str;
		}
	}

finally change return string to C

11、String的replace和replaceAll的区别?

replace() 和 replaceAll()都是常用的替换字符串的方法

两者都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字符串。如果只想替换第一次出现的,可以使用 replaceFirst()

replaceFirst(), replaceAll()都是“优先”基于规则表达式的替换。优先判断被替换的参数 regex 是不是一个正则表达式。如果是正则,执行正则替换;如果是字符串,执行字符串替换,此时和 replace() 效果就是一样的了

执行了替换操作后,返回一个新的对象,源字符串的内容是没有发生改变的

测试代码:
String str = “Hello Java. Java is a language.”;
System.out.println(str.replace(“Java.”, “c++”));//打印 Hello c++ Java is a language.
System.out.println(str.replaceAll(“Java.”, “c++”));//打印 Hello c++ c++is a language.
打印结果:
Hello c++ Java is a language.
Hello c++ c++is a language.

12、 Math.round(-1.5) 等于多少?

round() :返回四舍五入,负 .5 小数返回较大整数,如 -1.5 返回 -1。
ceil() :返回小数所在两整数间的较大值,如 -1.5 返回 -1。
floor() :返回小数所在两整数间的较小值,如 -1.5 返回 -2。

System.out.println(“Math.round(1.4)=” + Math.round(1.4));
System.out.println(“Math.round(-1.4)=” + Math.round(-1.4));
System.out.println(“Math.round(1.5)=” + Math.round(1.5));
System.out.println(“Math.round(-1.5)=” + Math.round(-1.5));
System.out.println(“Math.round(1.6)=” + Math.round(1.6));
System.out.println(“Math.round(-1.6)=” + Math.round(-1.6));
System.out.println();

System.out.println(“Math.ceil(1.4)=” + Math.ceil(1.4));
System.out.println(“Math.ceil(-1.4)=” + Math.ceil(-1.4));
System.out.println(“Math.ceil(1.5)=” + Math.ceil(1.5));
System.out.println(“Math.ceil(-1.5)=” + Math.ceil(-1.5));
System.out.println(“Math.ceil(1.6)=” + Math.ceil(1.6));
System.out.println(“Math.ceil(-1.6)=” + Math.ceil(-1.6));
System.out.println();

System.out.println(“Math.floor(1.4)=” + Math.floor(1.4));
System.out.println(“Math.floor(-1.4)=” + Math.floor(-1.4));
System.out.println(“Math.floor(1.5)=” + Math.floor(1.5));
System.out.println(“Math.floor(-1.5)=” + Math.floor(-1.5));
System.out.println(“Math.floor(1.6)=” + Math.floor(1.6));
System.out.println(“Math.floor(-1.6)=” + Math.floor(-1.6));

打印结果:
Math.round(1.4)=1
Math.round(-1.4)=-1
Math.round(1.5)=2
Math.round(-1.5)=-1
Math.round(1.6)=2
Math.round(-1.6)=-2
Math.ceil(1.4)=2.0
Math.ceil(-1.4)=-1.0
Math.ceil(1.5)=2.0
Math.ceil(-1.5)=-1.0
Math.ceil(1.6)=2.0
Math.ceil(-1.6)=-1.0
Math.floor(1.4)=1.0
Math.floor(-1.4)=-2.0
Math.floor(1.5)=1.0
Math.floor(-1.5)=-2.0
Math.floor(1.6)=1.0
Math.floor(-1.6)=-2.0

13、String属于基础的数据类型吗?

不属于。
Java 中 8 种基础的数据类型:byte、short、char、int、long、float、double、boolean
但是 String 类型却是最常用到的引用类型。

14、java中操作字符串都有哪些类?它们之间有什么区别?

Java 中,常用的对字符串操作的类有 String、StringBuffer、StringBuilder

1)String : final 修饰,String 类的方法都是返回 new String。即对 String 对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。
2)StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。
3)StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以 new StringBuilder 对象,调用 StringBuilder 对象的 append()、replace()、delete() 等方法修改字符串。

15、如何将字符串反转?

使用 StringBuilder 或 StringBuffer 的 reverse 方法,本质都调用了它们的父类 AbstractStringBuilder 的 reverse 方法实现。(JDK1.8)

package constxiong.interview;

public class TestReverseString {

	public static void main(String[] args) {
		String str = "ABCDE";
		System.out.println(reverseString(str));
		System.out.println(reverseStringByStringBuilderApi(str));
		System.out.println(reverseStringByRecursion(str));
	}

	/**
	 * 自己实现
	 * @param str
	 * @return
	 */
	public static String reverseString(String str) {
		if (str != null && str.length() > 0) {
			int len = str.length();
			char[] chars = new char[len];
			for (int i = len - 1; i >= 0; i--) {
				chars[len - 1 - i] = str.charAt(i);
			}
			return new String(chars);
		}
		return str;
	}
	
	/**
	 * 使用 StringBuilder
	 * @param str
	 * @return
	 */
	public static String reverseStringByStringBuilderApi(String str) {
		if (str != null && str.length() > 0) {
			return new StringBuilder(str).reverse().toString();
		}
		return str;
	}


	/**
	 * 递归
	 * @param str
	 * @return
	 */
	public static String reverseStringByRecursion(String str) {
		if (str == null || str.length() <= 1) {
			return str;
		}
		return reverseStringByRecursion(str.substring(1)) + str.charAt(0);
	}
}

16、String类的常用方法有哪些?

equals:字符串是否相同
equalsIgnoreCase:忽略大小写后字符串是否相同
compareTo:根据字符串中每个字符的Unicode编码进行比较
compareToIgnoreCase:根据字符串中每个字符的Unicode编码进行忽略大小写比较
indexOf:目标字符或字符串在源字符串中位置下标
lastIndexOf:目标字符或字符串在源字符串中最后一次出现的位置下标
valueOf:其他类型转字符串
charAt:获取指定下标位置的字符
codePointAt:指定下标的字符的Unicode编码
concat:追加字符串到当前字符串
isEmpty:字符串长度是否为0
contains:是否包含目标字符串
startsWith:是否以目标字符串开头
endsWith:是否以目标字符串结束
format:格式化字符串
getBytes:获取字符串的字节数组
getChars:获取字符串的指定长度字符数组
toCharArray:获取字符串的字符数组
join:以某字符串,连接某字符串数组
length:字符串字符数
matches:字符串是否匹配正则表达式
replace:字符串替换
replaceAll:带正则字符串替换
replaceFirst:替换第一个出现的目标字符串
split:以某正则表达式分割字符串
substring:截取字符串
toLowerCase:字符串转小写
toUpperCase:字符串转大写
trim:去字符串首尾空格

17、 普通类和抽象类有哪些区别?

抽象类不能被实例化
抽象类可以有抽象方法,抽象方法只需申明,无需实现
含有抽象方法的类必须申明为抽象类
抽象类的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类
抽象方法不能被声明为静态
抽象方法不能用 private 修饰
抽象方法不能用 final 修饰

抽象类可以没有抽象方法,如

public abstract class TestAbstractClass {
 
    public static void notAbstractMethod() {
        System.out.println("I am not a abstract method.");
    }
    
}

18、抽象类能使用final修饰吗?

不能,抽象类是被用于继承的,final修饰代表不可修改、不可继承的。

19、接口和抽象类有什么区别?

抽象类可以有构造方法;接口中不能有构造方法。
抽象类中可以有普通成员变量;接口中没有普通成员变量。
抽象类中可以包含非抽象普通方法;接口中的所有方法必须都是抽象的。
抽象类中的抽象方法的访问权限可以是 public、protected 和 default;接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。

抽象类中可以包含静态方法;JDK1.8 前接口中不能包含静态方法,JDK1.8 及以后可以包含已实现的静态方法。

public interface TestInterfaceStaticMethod {
 
	static String getA() {
		return "a";
	}
	
}

抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量可以是任意访问权限;接口中变量默认且只能是 public static final 类型。
一个类可以实现多个接口,用逗号隔开,但只能继承一个抽象类。
接口不可以实现接口,但可以继承接口,并且可以继承多个接口,用逗号隔开。

20 Java访问修饰符有哪些?权限的区别?

Java 语言中有四种权限访问控制符,能够控制类中成员变量和方法的可见性。

public被 public 修饰的成员变量和方法可以在任何类中都能被访问到。被 public 修饰的类,在一个 java 源文件中只能有一个类被声明为 public ,而且一旦有一个类为 public ,那这个 java 源文件的文件名就必须要和这个被 public 所修饰的类的类名相同,否则编译不能通过。


protected被 protected 修饰的成员会被位于同一 package 中的所有类访问到,也能被该类的所有子类继承下来。


friendly默认,缺省的。在成员的前面不写访问修饰符的时候,默认就是友好的。
同一package中的所有类都能访问。被 friendly 所修饰的成员只能被该类所在同一个 package 中的子类所继承下来。


private私有的。只能在当前类中被访问到。

21 Java中的 << >> >>> 是什么?

(1)左移:<<

运算规则:在一定范围内,数据每向左移动一位,相当于原数据*2。(正数、负数都适用)

【注意】当左移的位数n超过该数据类型的总位数时,相当于左移(n-总位数)位

3<<4 类似于 3*2的4次幂 => 3*16 => 48

注意:1<<< 31 无符号数表示 1*2的31次方,符号数代表负数最大,

3的二进制          0000 0000 0000 0000 0000 0000 0000 0011

3<<4       0000  0000 0000 0000 0000 0000 0000  0011  0000

-3<<4  类似于  -3*2的4次幂 => -3*16 => -48


原码                    1000  0000 0000 0000 0000 0000 0000 0011

反码                     1111   1111   1111     1111   1111    1111   1111    1100

补码                     1111   1111   1111     1111   1111    1111   1111   1101

移动四位     1111  1111   1111     1111   1111    1111   1111  1101   0000

反码                      1111   1111     1111   1111    1111   1111  1101   0001

补码                      1111   1111     1111   1111    1111   1111 ​  1100   1111

源码                      1000  0000 0000 0000 0000 0000 0011   0000

看结果用源码,计算用补码

(2)右移:>>

运算规则:在一定范围内,数据每向右移动一位,相当于原数据/2。(正数、负数都适用)

【注意】如果不能整除,向下取整。​​​​

(3)无符号右移:>>>

运算规则:往右移动后,左边空出来的位直接补0。(正数、负数都适用)

22 javap的作用是什么?

javap 是 Java class文件分解器,可以反编译,也可以查看 java 编译器生成的字节码等。
javap 命令参数
javap -help
用法: javap
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath 指定查找用户类文件的位置
-bootclasspath 覆盖引导类文件的位置

public class TestSynchronized {
	
	public void sync() {
		synchronized (this) {
			System.out.println("sync");
		}
	}
}
 

使用命令进行反汇编
javap -c TestSynchronized
警告: 二进制文件

TestSynchronized包含constxiong.interview.TestSynchronized
Compiled from "TestSynchronized.java"public class constxiong.interview.TestSynchronized {
public constxiong.interview.TestSynchronized();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return
 
  public void sync();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter
       4: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #21                 // String sync
       9: invokevirtual #22                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: aload_1
      13: monitorexit
      14: goto          20
      17: aload_1
      18: monitorexit
      19: athrow
      20: return
    Exception table:
       from    to  target type
           4    14    17   any
          17    19    17   any
}

23 throw和throws的区别?

1) throw:

表示方法内抛出某种异常对象(只能是一个)
用于程序员自行产生并抛出异常
位于方法体内部,可以作为单独语句使用
如果异常对象是非 RuntimeException 则需要在方法申明时加上该异常的抛出,即需要加上 throws 语句 或者 在方法体内 try catch 处理该异常,否则编译报错
执行到 throw 语句则后面的语句块不再执行

2) throws:

方法的定义上使用 throws 表示这个方法可能抛出某些异常(可以有多个)
用于声明在该方法内抛出了异常
必须跟在方法参数列表的后面,不能单独使用
需要由方法的调用者进行异常处理

24、try-catch-finally中哪个部分可以省略?

catch 和 finally 语句块可以省略其中一个,否则编译会报错。

package constxiong.interview;
public class TestOmitTryCatchFinally {
 
	public static void main(String[] args) {
		omitFinally();
		omitCatch();
	}
	
	/**
	 * 省略finally 语句块
	 */
	public static void omitFinally() {
		try {
			int i = 0;
			i += 1;
			System.out.println(i);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 省略 catch 语句块
	 */
	public static void omitCatch() {
		int i = 0;
		try {
			i += 1;
		} finally {
			i = 10;
		}
		System.out.println(i);
	}
}

25、常见的异常类有哪些?

异常非常多,Throwable 是异常的根类。
Throwable 包含子类 错误-Error 和 异常-Exception 。
Exception 又分为 一般异常和运行时异常 RuntimeException。
运行时异常不需要代码显式捕获处理。

下图是常见异常类及其父子关系:

Throwable
|  ├ Error  
|  │ ├ IOError
|  │ ├ LinkageError
|  │ ├ ReflectionError
|  │ ├ ThreadDeath
|  │ └ VirtualMachineError
|  │ 
|  ├ Exception  
|  │ ├ CloneNotSupportedException
|  │ ├ DataFormatException
|  │ ├ InterruptedException
|  │ ├ IOException
|  │ ├ ReflectiveOperationException
|  │ ├ RuntimeException 
|  │    ├ ArithmeticException
|  │    ├ ClassCastException
|  │    ├ ConcurrentModificationException
|  │    ├ IllegalArgumentException
|  │    ├ IndexOutOfBoundsException
|  │    ├ NoSuchElementException
|  │    ├ NullPointerException
|  │ └ SecurityException
|  │ └  SQLException

其中 error 及相关子类 和 RuntimeException,不需要捕获异常,编辑不会报错

其他Exception 及子类需要捕获(排除RuntimeException)

26、什么是JAVA内部类?

存在于Java类的内部的Java类

1)成员内部类

class OuterClass {
    class InnerClass {

     }
    //成员内部类

}

编译之后会生成两个class文件:OuterClass.class和OuterClass$InnerClass.clas

2)方法内部类 格式

class OuterClass {
     public void doSomething(){
        class Inner{ }
   }
}

编译之后会生成两个class文件:OuterClass.class和OuterClass$1InnerClass.class
只能在定义该内部类的方法内实例化
方法内部类对象不能使用该内部类所在方法的非final局部变量
当一个方法结束,其栈结构被删除,局部变量成为历史。但该方法结束后,在方法内创建的内部类对象可能仍然存在于堆中

3)  匿名内部类 继承式

public class Fish {
	/**
	 * 游泳方法
	 */
	public void swim() {
		System.out.println("我在游泳!");
	}

	public static void main(String[] args) {
		//创建鱼对象
		Fish fish = new Fish() {
			//重写swim方法
			public void swim() {
				  System.out.println("我在游泳,突然发生海啸,我撤了!");
			}
		};
		fish.swim();
	}
}

编译后生成两个class文件:Fish.class和Fish$1.class


4)  匿名内部类 接口式

interface IFish {
	public void swim();
}

class TestIFish {
	
	public static void main(String[] args) {
		IFish fish = new IFish() {
			@Override
			public void swim() {
				System.out.println("我是一条小丑鱼,我在游泳");
			}
		};
		
		fish.swim();
	}
}

编译后生成3个class文件:IFish.class、TestIFish.class和TestIFish$1.class
接口式的匿名内部类是实现了一个接口的匿名类,感觉上实例化了一个接口。

5) 匿名内部类 参数式

public class TestCrucian {
	
	public static void main(String[] args) {
		Crucian c = new Crucian();
		c.swim(new IFish() {
			@Override
			public void swim() {
				System.out.println("鲫鱼在河水里游泳!");
			}
			
		});
	}
}
/**
 * 鲫鱼游泳
 * @author handsomX
 * 2018年8月13日上午9:41:01
 */
class Crucian {
	/**
	 * 鲫鱼的游泳方法
	 * @param fish
	 */
	void swim(IFish fish) {
		fish.swim();
	}
}

编译后生成3个class文件:Crucian.class、TestCrucian.class和TestCrucian$1.class

6) 静态嵌套类

静态嵌套类,并没有对实例的共享关系,仅仅是代码块在外部类内部
静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它
静态嵌套类仅能访问外部类的静态成员和方法
在静态方法中定义的内部类也是静态嵌套类,这时候不能在类前面加static关键字
格式

class OuterFish {
	/**
	 * 静态嵌套类
	 * @author handsomX
	 * 2018年8月13日上午10:57:52
	 */
	static class InnerFish {
	}
}

class TestStaticFish {
	
	public static void main(String[] args) {
		//创建静态内部类对象
		OuterFish.InnerFish iFish = new OuterFish.InnerFish();
	}
}

内部类的作用

内部类提供了某种进入其继承的类或实现的接口的窗口,与外部类无关,独立继承其他类或实现接口
内部类提供了Java的"多重继承"的解决方案,弥补了Java类是单继承的不足

特点

内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号
内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的
内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量

27 nio中的Files类常用方法有哪些?

isExecutable:文件是否可以执行
isSameFile:是否同一个文件或目录
isReadable:是否可读
isDirectory:是否为目录
isHidden:是否隐藏
isWritable:是否可写
isRegularFile:是否为普通文件
getPosixFilePermissions:获取POSIX文件权限,windows系统调用此方法会报错
setPosixFilePermissions:设置POSIX文件权限
getOwner:获取文件所属人
setOwner:设置文件所属人
createFile:创建文件
newInputStream:打开新的输入流
newOutputStream:打开新的输出流
createDirectory:创建目录,当父目录不存在会报错
createDirectories:创建目录,当父目录不存在会自动创建
createTempFile:创建临时文件
newBufferedReader:打开或创建一个带缓存的字符输入流
probeContentType:探测文件的内容类型
list:目录中的文件、文件夹列表
find:查找文件
size:文件字节数
copy:文件复制
lines:读出文件中的所有行
move:移动文件位置
exists:文件是否存在
walk:遍历所有目录和文件
write:向一个文件写入字节
delete:删除文件
getFileStore:返回文件存储区
newByteChannel:打开或创建文件,返回一个字节通道来访问文件
readAllLines:从一个文件读取所有行字符串
setAttribute:设置文件属性的值
getAttribute:获取文件属性的值
newBufferedWriter:打开或创建一个带缓存的字符输出流
readAllBytes:从一个文件中读取所有字节
createTempDirectory:在特殊的目录中创建临时目录
deleteIfExists:如果文件存在删除文件
notExists:判断文件不存在
getLastModifiedTime:获取文件最后修改时间属性
setLastModifiedTime:更新文件最后修改时间属性
newDirectoryStream:打开目录,返回可迭代该目录下的目录流
walkFileTree:遍历文件树,可用来递归删除文件等操作

如测试获取文件所属人

public static void testGetOwner() throws IOException {
	Path path_js = Paths.get("/Users/constxiong/Desktop/index.js");
	System.out.println(Files.getOwner(path_js));
}

28 什么是反射?有什么作用?

Java 反射,就是在运行状态中获取任意类的名称、package 信息、所有属性、方法、注解、类型、类加载器、modifiers(public、static)、父类、现实接口等
获取任意对象的属性,并且能改变对象的属性
调用任意对象的方法
判断任意一个对象所属的类
实例化任意一个类的对象
Java 的动态就体现在反射。

通过反射我们可以实现动态装配,降低代码的耦合度;动态代理等。反射的过度使用会严重消耗系统资源。
JDK 中 java.lang.Class 类,就是为了实现反射提供的核心类之一。
一个 jvm 中一种 Class 只会被加载一次。

29、动态代理是什么?应用场景?

动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法。
Java 中实现动态的方式:

JDK 中的动态代理
Java类库 CGLib

应用场景:

统计每个 api 的请求耗时
统一的日志输出
校验被调用的 api 是否已经登录和权限鉴定
Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程

29.1、静态代理的实现:


首先定义一个接口,说明业务逻辑

package net.battier.dao;  
    
    
/** 
  * 定义一个账户接口 
  * @author Administrator
*/  
    
public interface Count {  
        
    // 查询账户
    public void queryCount();  
      
    // 修改账户  
    public void updateCount();  
      
} 

然后,定义业务实现类,实现业务逻辑接口

import net.battier.dao.Count;    /** 
 * 委托类(包含业务逻辑) 
 *  
 * @author Administrator 
 *  
 */  
public class CountImpl implements Count {  
  
    @Override  
    public void queryCount() {  
        System.out.println("查看账户...");  
    }  
  
    @Override  
    public void updateCount() {  
        System.out.println("修改账户...");  
    }  
  
}  

定义业务代理类:在代理类中创建一个业务实现类对象来调用具体的业务方法;通过实现业务逻辑接口,来统一业务方法;
在代理类中实现业务逻辑接口中的方法时,进行预处理操作、通过业务实现类对象调用真正的业务方法、进行调用后操作的定义。

public class CountProxy implements Count {  
    private CountImpl countImpl;  //组合一个业务实现类对象来进行真正的业务方法的调用
  
    /** 
     * 覆盖默认构造器 
     *  
     * @param countImpl 
     */  
    public CountProxy(CountImpl countImpl) {  
        this.countImpl = countImpl;  
    }  
  
    @Override  
    public void queryCount() {  
        System.out.println("查询账户的预处理——————");  
        // 调用真正的查询账户方法        countImpl.queryCount();  
        System.out.println("查询账户之后————————");  
    }  
  
    @Override  
    public void updateCount() {  
        System.out.println("修改账户之前的预处理——————");  
        // 调用真正的修改账户操作        countImpl.updateCount();  
        System.out.println("修改账户之后——————————");  
    }  
}  

在使用时,首先创建业务实现类对象,然后把业务实现类对象作构造参数创建一个代理类对象,最后通过代理类对象进行业务方法的调用。

 public static void main(String[] args) {  
        CountImpl countImpl = new CountImpl();  
        CountProxy countProxy = new CountProxy(countImpl);  
        countProxy.updateCount();  
        countProxy.queryCount();  
  
    }  

静态代理的缺点很明显:一个代理类只能对一个业务接口的实现类进行包装,如果有多个业务接口的话就要定义很多实现类和代理类才行。而且,如果代理类对业务方法的预处理、调用后操作都是一样的(比如:调用前输出提示、调用后自动关闭连接),则多个代理类就会有很多重复代码。这时我们可以定义这样一个代理类,它能代理所有实现类的方法调用:根据传进来的业务实现类和方法名进行具体调用。——那就是动态代理。

29.2、动态代理的第一种实现——JDK动态代理


JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。
首先,定义业务逻辑接口

public interface BookFacade {  
    public void addBook();  
} 

然后,实现业务逻辑接口创建业务实现类

public class BookFacadeImpl implements BookFacade {   
    @Override  
    public void addBook() {  
        System.out.println("增加图书方法。。。");  
    }  
} 

最后,实现 调用管理接口InvocationHandler  创建动态代理类

public class BookFacadeProxy implements InvocationHandler {  
     private Object target;//这其实业务实现类对象,用来调用具体的业务方法 
     /** 
     * 绑定业务对象并返回一个代理类  
     */  
     public Object bind(Object target) {  
        this.target = target;  //接收业务实现类对象参数

       //通过反射机制,创建一个代理类对象实例并返回。用户进行方法调用时使用
       //创建代理对象时,需要传递该业务类的类加载器(用来获取业务实现类的元数据,在包装方法是调用真正的业务方法)、接口、handler实现类
       return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), this); 
}  

/** 
 * 包装调用方法:进行预处理、调用后处理 
 */  
public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable {  
        Object result=null;  

        System.out.println("预处理操作——————");  
        //调用真正的业务方法  
        result=method.invoke(target, args);  

        System.out.println("调用后处理——————");  
        return result;  
    }  
  
}  
``

在使用时,首先创建一个业务实现类对象和一个代理类对象,然后定义接口引用(这里使用向上转型)并用代理对象.bind(业务实现类对象)的返回值进行赋值。最后通过接口引用调用业务方法即可。(接口引用真正指向的是一个绑定了业务类的代理类对象,所以通过接口方法名调用的是被代理的方法们)

public static void main(String[] args) {  
        BookFacadeImpl bookFacadeImpl=new BookFacadeImpl();
        BookFacadeProxy proxy = new BookFacadeProxy();  
        BookFacade bookfacade = (BookFacade) proxy.bind(bookFacadeImpl);  
        bookfacade.addBook();  
    }  

JDK动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。

在调用代理类和方法的时候------->会调用创建类的方法------->最后执行目标方法

29.3、动态代理的第二种实现——CGlib

cglib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。
首先定义业务类,无需实现接口(当然,实现接口也可以,不影响的)

public class BookFacadeImpl1 {  
    public void addBook() {  
        System.out.println("新增图书...");  
    }  
}  

实现 MethodInterceptor方法代理接口,创建代理类

public class BookFacadeCglib implements MethodInterceptor {  
    private Object target;//业务类对象,供代理方法中进行真正的业务方法调用
    //相当于JDK动态代理中的绑定
    public Object getInstance(Object target) {  
        this.target = target;  //给业务对象赋值        Enhancer enhancer = 
        new Enhancer(); //创建加强器,用来创建动态代理类        
        enhancer.setSuperclass(this.target.getClass());  //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而
        Callback则需要实现intercept()方法进行拦        
        enhancer.setCallback(this); // 创建动态代理类对象并返回  
        return  enhancer.create(); 
    }
    // 实现回调方法  
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 
        System.out.println("预处理——————");
        proxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
        System.out.println("调用后操作——————");return null; 
    } 

创建业务类和代理类对象,然后通过  代理类对象.getInstance(业务类对象)  返回一个动态代理类对象(它是业务类的子类,可以用业务类引用指向它)。最后通过动态代理类对象进行方法调用。

AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;
JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;
CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;

30、 怎么实现动态代理?

JDK 动态代理
CGLib 动态代理
使用 Spring aop 模块完成动态代理功能

31、 什么是java序列化?什么情况下需要序列化?

序列化:将 Java 对象转换成字节流的过程。
反序列化:将字节流转换成 Java 对象的过程。
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。
序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。

注意事项:
某个类可以被序列化,则其子类也可以被序列化
对象中的某个属性是对象类型,需要序列化也必须实现 Serializable 接口
声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据
反序列化读取序列化对象的顺序要保持一致
具体使用

package constxiong.interview;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
/**
 * 测试序列化,反序列化
 * @author ConstXiong
 * @date 2019-06-17 09:31:22
 */public class TestSerializable implements Serializable {
 
	private static final long serialVersionUID = 5887391604554532906L;
	private int id;
	private String name;
 
	public TestSerializable(int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	@Override
	public String toString() {
		return "TestSerializable [id=" + id + ", name=" + name + "]";
	}
 
	@SuppressWarnings("resource")
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		//序列化
	    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("TestSerializable.obj"));
		oos.writeObject("测试序列化");
		oos.writeObject(618);
		TestSerializable test = new TestSerializable(1, "ConstXiong");
		oos.writeObject(test);
		
		//反序列化
	    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("TestSerializable.obj"));
		System.out.println((String)ois.readObject());
		System.out.println((Integer)ois.readObject());
		System.out.println((TestSerializable)ois.readObject());
	}
 
}

打印结果:
测试序列化
618 TestSerializable [id=1, name=ConstXiong]

32、 什么场景要对象克隆?

方法需要 return 引用类型,但又不希望自己持有引用类型的对象被修改。
程序之间方法的调用时参数的传递。有些场景为了保证引用类型的参数不被其他方法修改,可以使用克隆后的值作为参数传递。

33、对象克隆?

深拷贝:

复制基本类型的属性;

引用类型的属性复制

复制栈中的变量

堆内存中的对象

34、 如何实现对象克隆与深拷贝?

实现 Cloneable 接口,重写 clone() 方法。
不实现 Cloneable 接口,会报 CloneNotSupportedException 异常。

package constxiong.interview;

 /**
 * 测试克隆
 * @author ConstXiong
 * @date 2019-06-18 11:21:21
 */public class TestClone {
 
	public static void main(String[] args) throws CloneNotSupportedException {
		Person p1 = new Person(1, "ConstXiong");//创建对象 Person p1
		Person p2 = (Person)p1.clone();//克隆对象 p1
		p2.setName("其不答");//修改 p2的name属性,p1的name未变
		System.out.println(p1);
		System.out.println(p2);
	}
}

 /**
 * 人
 * @author ConstXiong
 * @date 2019-06-18 11:54:35
 */
class Person implements Cloneable {
	
	private int pid;
	
	private String name;
	
	public Person(int pid, String name) {
		this.pid = pid;
		this.name = name;
		System.out.println("Person constructor call");
	}
 
	public int getPid() {
		return pid;
	}
 
	public void setPid(int pid) {
		this.pid = pid;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
 
	@Override
	public String toString() {
		return "Person [pid:"+pid+", name:"+name+"]";
	}
}

打印结果
Person constructor callPerson [pid:1, name:ConstXiong] Person [pid:1, name:其不答]


Object 的 clone() 方法是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象,可以使用下面的两种方法,完成 Person 对象的深拷贝。


方法1、对象的属性的Class 也实现 Cloneable 接口,在克隆对象时也手动克隆属性。

@Override
public Object clone() throws CloneNotSupportedException {
		DPerson p = (DPerson)super.clone();
		p.setFood((DFood)p.getFood().clone());
		return p;
}
package constxiong.interview;
 /**
 * 测试克隆
 * @author ConstXiong
 * @date 2019-06-18 11:21:21
 */public class TestManalDeepClone {
 
public static void main(String[] args) throws Exception {
		DPerson p1 = new DPerson(1, "ConstXiong", new DFood("米饭"));//创建Person 对象 p1
		DPerson p2 = (DPerson)p1.clone();//克隆p1
		p2.setName("其不答");//修改p2的name属性
		p2.getFood().setName("面条");//修改p2的自定义引用类型 food 属性
		System.out.println(p1);
		System.out.println(p2);
	}
	
}
 
class DPerson implements Cloneable {
	
	private int pid;
	
	private String name;
	
	private DFood food;
	
	public DPerson(int pid, String name, DFood food) {
		this.pid = pid;
		this.name = name;
		this.food = food;
		System.out.println("Person constructor call");
	}
 
	public int getPid() {
		return pid;
	}
 
	public void setPid(int pid) {
		this.pid = pid;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	@Override
	public Object clone() throws CloneNotSupportedException {
		DPerson p = (DPerson)super.clone();
		p.setFood((DFood)p.getFood().clone());
		return p;
	}
 
	@Override
	public String toString() {
		return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
	}
 
	public DFood getFood() {
		return food;
	}
 
	public void setFood(DFood food) {
		this.food = food;
	}
	
}
 
class DFood implements Cloneable{
	
	private String name;
	
	public DFood(String name) {
		this.name = name;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

打印结果
Person constructor callPerson [pid:1, name:ConstXiong, food:米饭 ]Person [pid:1, name:其不答, food:面条]

方法2
结合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷贝
结合 java.io.Serializable 接口,完成深拷贝

package constxiong.interview;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class TestSeriazableClone {
 
	public static void main(String[] args) {
		SPerson p1 = new SPerson(1, "ConstXiong", new SFood("米饭"));
		SPerson p2 = (SPerson)p1.cloneBySerializable();//克隆 p1
		p2.setName("其不答");//修改 p2 的 name 属性
		p2.getFood().setName("面条");//修改 p2 的自定义引用类型 food 属性
		System.out.println(p2);
	}
	
}
 
class SPerson implements Cloneable, Serializable {
	
	private static final long serialVersionUID = -7710144514831611031L;
 
	private int pid;
	
	private String name;
	
	private SFood food;
	
	public SPerson(int pid, String name, SFood food) {
		this.pid = pid;
		this.name = name;
		this.food = food;
		System.out.println("Person constructor call");
	}
 
	public int getPid() {
		return pid;
	}
 
	public void setPid(int pid) {
		this.pid = pid;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	/**
	 * 通过序列化完成克隆
	 * @return
	 */
	public Object cloneBySerializable() {
		Object obj = null;
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(this);

			ByteArrayInputStream bais = new 
            ByteArrayInputStream(baos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bais);
			obj = ois.readObject();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return obj;
	}
 
	@Override
	public String toString() {
		return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
	}
 
	public SFood getFood() {
		return food;
	}
 
	public void setFood(SFood food) {
		this.food = food;
	}
	
}
 
class SFood implements Serializable {
	
	private static final long serialVersionUID = -3443815804346831432L;
	
	private String name;
	
	public SFood(String name) {
		this.name = name;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
	
}

打印结果
Person constructor callPerson [pid:1, name:ConstXiong, food:米饭 ]Person [pid:1, name:其不答, food:面条]

35、java跨平台运行的原理

java 源文件要先编译成与操作系统无关的 .class 字节码文件,然后字节码文件再通过 Java 虚拟机解释成机器码运行。
class 字节码文件面向虚拟机,不面向任何具体操作系统。
不同平台的虚拟机是不同的,但它们给 JDK 提供了相同的接口。
Java 的跨平台依赖于不同系统的 Java 虚拟机。

36、Java的安全性体现在哪里?

使用引用取代了指针,指针的功能强大,但是也容易造成错误,如数组越界问题。
拥有一套异常处理机制,使用关键字 throw、throws、try、catch、finally
强制类型转换需要符合一定规则
字节码传输使用了加密机制
运行环境提供保障机制:字节码校验器->类装载器->运行时内存布局->文件访问限制
不用程序员显示控制内存释放,JVM 有垃圾回收机制

37 Java针对不同的应用场景提供了哪些版本?

J2SE:Standard Edition(标准版) ,包含 Java 语言的核心类。如IO、JDBC、工具类、网络编程相关类等。从JDK 5.0开始,改名为Java SE。
J2EE:Enterprise Edition(企业版),包含 J2SE 中的类和企业级应用开发的类。如web相关的servlet类、JSP、xml生成与解析的类等。从JDK 5.0开始,改名为Java EE。
J2ME:Micro Edition(微型版),包含 J2SE 中的部分类,新添加了一些专有类。一般用设备的嵌入式开发,如手机、机顶盒等。从JDK 5.0开始,改名为Java ME。

38 什么是JVM?

Java Virtual Machine(Java虚拟机)的缩写
实现跨平台的最核心的部分
class 文件会在 JVM 上执行,JVM 会解释给操作系统执行
有自己的指令集,解释自己的指令集到 CPU 指令集和系统资源的调用
JVM 只关注被编译的 .class 文件,不关心 .java 源文件

39 什么是JDK?

Java Development Kit(Java 开发工具包)的缩写。用于 java 程序的开发,提供给程序员使用
使用 Java 语言编程都需要在计算机上安装一个 JDK
JDK 的安装目录 5 个文件夹、一个 src 类库源码压缩包和一些说明文件
bin:各种命令工具, java 源码的编译器 javac、监控工具 jconsole、分析工具 jvisualvm 等
include:与 JVM 交互C语言用的头文件
lib:类库
jre:Java 运行环境
db:安装 Java DB 的路径

src.zip:Java 所有核心类库的源代码
jdk1.8 新加了 javafx-src.zip 文件,存放 JavaFX 脚本,JavaFX 是一种声明式、静态类型编程语言

40 什么是JRE?

Java Runtime Environment(Java运行环境)的缩写
包含 JVM 标准实现及 Java 核心类库,这些是运行 Java 程序的必要组件
是 Java 程序的运行环境,并不是一个开发环境,没有包含任何开发工具(如编译器和调试器)
是运行基于 Java 语言编写的程序所不可缺少的运行环境,通过它,Java 程序才能正常运行

41 JDK、JRE、JVM之间的关系是什么样的?

JDK 是 JAVA 程序开发时用的开发工具包,包含 Java 运行环境 JRE
JDk、JRE 内部都包含 JAVA虚拟机 JVM
JVM 包含 Java 应用程序的类的解释器和类加载器等

42 Java语言有哪些注释的方式?

单行注释
多行注释,不允许嵌套
文档注释,常用于类和方法的注释

形式如下:

package constxiong.interview;
/**
 * 文档注释
 * @author ConstXiong
 * @date 2019-10-17 12:32:31
 */
public class TestComments {
	
	/**
	 * 文档注释
	 * @param args 参数
	 */
	public static void main(String[] args) {
		//单行注释
		//System.out.print(1);
		
		/* 多行注释
		System.out.print(2);
		System.out.print(3);
		*/
	}

}

43 Java中有几种基本数据类型?它们分别占多大字节?

基本数据类型
byte:1个字节,8位
short:2个字节,16位
int:4个字节,32位
long:8个字节,64位
float:4个字节,32位
double:8个字节,64位
boolean:官方文档未明确定义,依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1位,但是实际中会考虑计算机高效存储因素
char:2个字节,16位

44 i++和++i的作用和区别

作用:都是给变量 i 加 1,相当于 i = i + 1;
区别:
i++ 先运算后加 1
++i 先加 1 再运算

package constxiong.interview;

/**
 * 测试 ++i 和 i++
 * @author ConstXiong
 * @date 2019-10-17 13:44:05
 */public class TestAdd {

	public static void main(String[] args) {
		int a = 3;
		int b = a++;
		System.out.println("a=" + a);
		System.out.println("b=" + b);
		
		int x = 3;
		int y = ++x;
		System.out.println("x=" + x);
		System.out.println("y=" + y);
	}
	
}

打印
a=4
b=3
x=4
y=4

45、&和&&的作用和区别

&逻辑与,& 两边的表达式都会进行运算,整数的位运算符

&&
短路与,&& 左边的表达式结果为 false 时,&& 右边的表达式不参与计算

/**
 * 测试 & &&
 * @author ConstXiong
 */

public class TestAnd {

	public static void main(String[] args) {
		int x = 10;
		int y = 9;
		if (x == 9 & ++y > 9) {
		}
		System.out.println("x = " + x + ", y = " + y);
		
		int a = 10;
		int b = 9;
		if (a == 9 && ++b > 9) {//a == 9 为 false,所以 ++b 不会运算,b=9
		}
		System.out.println("a = " + a + ", b = " + b);
		
		//00000000000000000000000000000001
		//00000000000000000000000000000010
		//=
		//00000000000000000000000000000000
		System.out.println(1 & 2);//打印0
	}
	
}

打印
x = 10, y = 10 a = 10, b = 9
0

46、|和||的作用和区别

|逻辑或,| 两边的表达式都会进行运算,整数的或运算符

||短路或,|| 左边的表达式结果为 true 时,|| 右边的表达式不参与计算

/**
 * 测试 | ||
 * @author ConstXiong
 */
public class TestOr {

	public static void main(String[] args) {
		int x = 10;
		int y = 9;
		if (x == 10 | ++y > 9) {
		}
		System.out.println("x = " + x + ", y = " + y);
		
		int a = 10;
		int b = 9;
		if (a == 10 || ++b > 9) {//a == 9 为 false,所以 ++b 不会运算,b=9
		}
		System.out.println("a = " + a + ", b = " + b);
		
		/*
		00000000000000000000000000000001
		|
		00000000000000000000000000000010
		=
		00000000000000000000000000000011
		*/
		System.out.println(1 | 2);//打印3
	}
	
}

打印
x = 10, y = 10 a = 10, b = 9
3

47、如何让计算机最高效的算出2乘以8?

2 <<3
位运算符 <<,是将一个数左移 n 位,相当于乘以了 2 的 n 次方
一个数乘以 8 只要将其左移 3 位即可
CPU 直接支持位运算,效率最高

补充:当这个数接近Java基本整数类型的最大值时,左移位运算可能出现溢出,得出负值。

48、Java中基本类型的转换规则

等级低到高:
byte <char <short <int <long <float <double
自动转换:运算过程中,低级可以自动向高级转换
强制转换:高级需要强制转换为低级,可能会丢失精度

规则:
= 右边先自动转换成表达式中最高级的数据类型,再进行运算
= 左边数据类型级别 > 右边数据类型级别,右边会自动升级
= 左边数据类型级别 <右边数据类型级别,需要强制转换右边数据类型
整型常量赋值给 byte、 short、 char、int、long 时,超过类型最大值,超过需要强转

49 if-else-if-else与switch的区别

if-else-if-else:

适合分支较少
判断条件类型不单一
支持取 boolean 类型的所有运算
满足条件即停止对后续分支语句的执行

switch:

适合分支较多
判断条件类型单一,JDK 1.7 之前仅支持 int 和 enum,JDK 1.7 之后多支持了 String
没有 break 语句每个分支都会执行

50、while和do-while的区别

while 先判断后执行,第一次判断为 false,循环体一次都不执行
do-while 先执行后判断,最少执行1次

51、break、continue语句的作用

break

结束当前循环并退出当前循环体
结束 switch 语句

continue

结束本次循环,循环体后续的语句不执行
继续进行循环条件的判断,进行下一次循环体语句的执行

52、Java中数组有什么特征?

在内存中申请一块连续的空间
数组下标从 0 开始
每个数组元素都有默认值,基本类型的默认值为 0、0.0、false,引用类型的默认值为 null
数组的类型只能是一个,且固定,在申明时确定
数组的长度一经确定,无法改变,即定长。要改变长度,只能重新申明一个

53、可变参数的作用和特点是什么?

作用:

在不确定参数的个数时,可以使用可变参数。

语法:

参数类型…

特点:

每个方法最多只有一个可变参数
可变参数必须是方法的最后一个参数
可变参数可以设置为任意类型:引用类型,基本类型
参数的个数可以是 0 个、1 个或多个
可变参数也可以传入数组
无法仅通过改变 可变参数的类型,来重载方法
通过对 class 文件反编译可以发现,可变参数被编译器处理成了数组

54、类和对象的关系

类是对象的抽象;对象是类的具体实例
类是抽象的,不占用内存;对象是具体的,占用存储空间
类是一个定义包括在一类对象中的方法和变量的模板

55、说一说你的对面向过程和面向对象的理解

软件开发思想,先有面向过程,后有面向对象
在大型软件系统中,面向过程的做法不足,从而推出了面向对象
都是解决实际问题的思维方式
两者相辅相成,宏观上面向对象把握复杂事物的关系;微观上面向过程去处理
面向过程以实现功能的函数开发为主;面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能
面向过程是封装的是功能;面向对象封装的是数据和功能
面向对象具有继承性和多态性;面向过程则没有

56、 方法重载和重写是什么?有什么区别?

重写

在子类中将父类的成员方法的名称保留,重新编写成员方法的实现内容,更改方法的访问权限,修改返回类型的为父类返回类型的子类。

一些规则:

重写发生在子类继承父类
参数列表必须完全与被重写方法的相同
重写父类方法时,修改方法的权限只能从小范围到大范围,访问权限不能比父类中被重写的方法的访问权限更低。如:父类的方法被声明为 public,那么子类中重写该方法不能声明为 protected
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的子类(JDK1.5 及更早版本返回类型要一样,JDK1.7 及更高版本可以不同)

重写方法不能抛出新的检查异常和比被重写方法申明更宽泛的异常(即只能抛出父类方法抛出异常的子类)
声明为 final 的方法不能被重写
声明为 static 的方法不能被重写
声明为 private 的方法不能被重写。

重载

一个类中允许同时存在一个以上的同名方法,这些方法的参数个数或者类型不同
重载条件:
方法名相同
参数类型不同 或 参数个数不同 或 参数顺序不同
规则:
被重载的方法参数列表(个数或类型)不一样
被重载的方法可以修改返回类型
被重载的方法可以修改访问修饰符
被重载的方法可以修改异常抛出
方法能够在同一个类中或者在一个子类中被重载
无法以返回值类型作为重载函数的区分标准

重载和重写的区别:

作用范围:重写的作用范围是父类和子类之间;重载是发生在一个类里面
参数列表:重载必须不同;重写不能修改
返回类型:重载可修改;重写方法返回相同类型或子类
抛出异常:重载可修改;重写可减少或删除,一定不能抛出新的或者更广的异常
访问权限:重载可修改;重写一定不能做更严格的限制

57、this和super关键字的作用

this:

对象内部指代自身的引用
解决成员变量和局部变量同名问题
可以调用成员变量
不能调用局部变量
可以调用成员方法
在普通方法中可以省略 this
在静态方法当中不允许出现 this 关键

super:

代表对当前对象的直接父类对象的引用
可以调用父类的非 private 成员变量和方法,如果子类和父类不在同一个包,不能调用默认的访问权限 成员变量和方法,遵循权限访问原则
super(); 可以调用父类的构造方法,只限构造方法中使用,且必须是第一条语句

58、static关键字的作用是什么?

static 可以修饰变量、方法、代码块和内部类
static 变量是这个类所有,由该类创建的所有对象共享同一个 static 属性
可以通过创建的对象名.属性名 和 类名.属性名两种方式访问
static 变量在内存中只有一份
static 修饰的变量只能是类的成员变量

static 代码块在类被第一次加载时执行静态代码块,且只被执行一次,主要作用是实现 static 属性的初始化
static 内部类属于整个外部类,而不属于外部类的每个对象,只可以访问外部类的静态变量和方法
​​​​​​​

59、abstract关键字的作用是什么?

可以修饰类和方法
不能修饰属性和构造方法
abstract 修饰的类是抽象类,需要被继承
abstract 修饰的方法是抽象方法,需要子类被重写

60、java.lang.Object的常用方法?

public final native Class<?> getClass(); 获取类结构信息
public native int hashCode() 获取哈希码
public boolean equals(Object) 默认比较对象的地址值是否相等,子类可以重写比较规则
protected native Object clone() throws CloneNotSupportedException 用于对象克隆
public String toString() 把对象转变成字符串
public final native void notify() 多线程中唤醒功能
public final native void notifyAll() 多线程中唤醒所有等待线程的功能
public final void wait() throws InterruptedException 让持有对象锁的线程进入等待
public final native void wait(long timeout) throws InterruptedException 让持有对象锁的线程进入等待,设置超时毫秒数时间
public final void wait(long timeout, int nanos) throws InterruptedException 让持有对象锁的线程进入等待,设置超时纳秒数时间
protected void finalize() throws Throwable 垃圾回收前执行的方法

61、子类构造方法的执行过程是什么样的?

子类构造方法的调用规则:
如果子类的构造方法中没有通过 super 显式调用父类的有参构造方法,也没有通过 this 显式调用自身的其他构造方法,则系统会默认先调用父类的无参构造方法。这种情况下,写不写 super(); 语句,效果是一样的
如果子类的构造方法中通过 super 显式调用父类的有参构造方法,将执行父类相应的构造方法,不执行父类无参构造方法
如果子类的构造方法中通过 this 显式调用自身的其他构造方法,将执行类中相应的构造方法
如果存在多级继承关系,在创建一个子类对象时,以上规则会多次向更高一级父类应用,一直到执行顶级父类 Object 类的无参构造方法为止

62、什么是Java的多态?​​​​​​​

实现多态的三个条件​​​​​​​

继承的存在。继承是多态的基础,没有继承就没有多态
子类重写父类的方法,JVM 会调用子类重写后的方法
父类引用变量指向子类对象

向上转型

将一个父类的引用指向一个子类对象,自动进行类型转换。
通过父类引用变量调用的方法是子类覆盖或继承父类的方法,而不是父类的方法。
通过父类引用变量无法调用子类特有的方法。

向下转型:

将一个指向子类对象的引用赋给一个子类的引用,必须进行强制类型转换。
向下转型必须转换为父类引用指向的真实子类类型,不是任意的强制转换,否则会出现 ClassCastException
向下转型时可以结合使用 instanceof 运算符进行判断

63、instanceof关键字的作用是什么?

instanceof 运算符是用来在运行时判断对象是否是指定类及其父类的一个实例。
比较的是对象,不能比较基本类型
使用如下


```java
package constxiong.interview;

/**
 * 测试 instanceof
 * @author ConstXiong
 * @date 2019-10-23 11:05:21
 */public class TestInstanceof {

    public static void main(String[] args) {
        A a = new A();
        AA aa = new AA();
        AAA aaa = new AAA();
        System.out.println(a instanceof A);//true
        System.out.println(a instanceof AA);//false
        System.out.println(aa instanceof AAA);//false
        System.out.println(aaa instanceof A);//true
    }
    
}
class A {
}
class AA extends A {
}
class AAA extends AA {
}

​​​​​​​

64、什么是Java的垃圾回收机制?

垃圾回收机制,简称 GC
Java 语言不需要程序员直接控制内存回收,由 JVM 在后台自动回收不再使用的内存
提高编程效率
保护程序的完整性
JVM 需要跟踪程序中有用的对象,确定哪些是无用的,影响性能

特点

回收 JVM 堆内存里的对象空间,不负责回收栈内存数据
无法处理一些操作系统资源的释放,如数据库连接、输入流输出流、Socket 连接
垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行
可以将对象的引用变量设置为 null,垃圾回收机制可以在下次执行时回收该对象。
JVM 有多种垃圾回收 实现算法,表现各异
垃圾回收机制回收任何对象之前,会先调用对象的 finalize() 方法
可以通过 System.gc() 或 Runtime.getRuntime().gc() 通知系统进行垃圾回收,会有一些效果,但系统是否进行垃圾回收依然不确定
不要主动调用对象的 finalize() 方法,应该交给垃圾回收机制调用

65、什么是包装类?为什么要有包装类?基本类型与包装类如何转换?

Java 中有 8 个基本类型,分别对应的包装类如下

byte – Byte
boolean – Boolean
short – Short
char – Character
int – Integer
long – Long
float – Float
double – Double

为什么要有包装类

基本数据类型方便、简单、高效,但泛型不支持、集合元素不支持
不符合面向对象思维
包装类提供很多方法,方便使用,如 Integer 类 toHexString(int i)、parseInt(String s) 方法等等

基本数据类型和包装类之间的转换

包装类–>基本数据类型:包装类对象.xxxValue()
基本数据类型–>包装类:new 包装类(基本类型值)
JDK1.5 开始提供了自动装箱(autoboxing)和自动拆箱(autounboxing)功能, 实现了包装类和基本数据类型之间的自动转换

包装类可以实现基本类型和字符串之间的转换

字符串转基本类型:parseXXX(String s);

基本类型转字符串:String.valueOf(基本类型)

​​​​​​​

66、基本类型和包装类的区别?

基本类型只有值,而包装类型则具有与它们的值不同的同一性(即值相同但不是同一个对象)
包装类型比基本类型多了一个非功能值:null
基本类型通常比包装类型更节省时间和空间,速度更快
但有些情况包装类型的使用会更合理:
泛型不支持基本类型,作为集合中的元素、键和值直接使用包装类(否则会发生基本类型的自动装箱消耗性能)

67、java.sql.Date和java.util.Date的区别

java.sql.Date 是 java.util.Date 的子类
java.util.Date 是 JDK 中的日期类,精确到时、分、秒、毫秒
java.sql.Date 与数据库 Date 相对应的一个类型,只有日期部分,时分秒都会设置为 0,如:2019-10-23 00:00:00
要从数据库时间字段取 时、分、秒、毫秒数据,可以使用 java.sql.Timestamp

68、关于Java编译

.java 源码代码经编译后会生成 .class 字节码,通过 JVM 翻译成机器码去执行

69、关于构造方法

构造方法在一个对象被 new 时执行
Java 类中不写构造方法,编译器会默认提供一个无参构造
方法名与类名相同,但不符合命名规范,类名首字母建议大写,方法名建议首字母小写
一个类中可以定义多个构造方法,这就是构造方法的重载

70、Java中接口的修饰符

jdk 1.7之前

接口的方法默认是public abstract,接口中不可以定义变量即定义的变量前都要加上final修饰,使之成为常量(没有final修饰的是变量,加上final修饰就会变成常量)。所以接口的属性默认是public

jdk1.8及之后

可以定义静态方法及默认方法的实现

可以定义变量

71、以下代码将输出

public class AA extends A{
    public AA(){
        System.out.print("AA");
    }

    public static void main(String[] args) {
        AA aa = new AA();
    }
}

class A {
    public A(){
        System.out.print("A");
    }
} 

AAA

创建子类对象,先执行父类的构造方法,再执行子类的构造方法

72、static 方法能处理非 static 的属性?

不能
加载 class 时首先完成 static 方法装载,非 static 属性和方法还没有完成初始化,所以不能调用。
​​​​​​​

73、内存回收

内存由 JVM 负责释放
程序员无法直接释放内存
垃圾回收时间不确定

74、标识符合法

可以包含字母、数字、下划线、$
不能以数字开头
不能是 Java 关键字


                
  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吹老师个人app编程教学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值