1、基础
1.1、面向对象编程有哪些特性
1、抽象
抽象就是对同一个目标的共有的属性、特征、方法、功能、行为等进行抽取并归纳总结,它是一种将复杂现实简单化为模型的过程,它关注的是对象行为,而不用关注具体的实现细节。
在面向对象编程中,抽象主要是通过抽象类和接口来实现的:
- 抽象类是不能被实例化的,它包含一些抽象的方法和具体的实现方法。
- 接口是一种特殊的抽象类型,一般仅包含抽象方法的声明,在java8+中可以包含默认的方法和静态方法实现
抽象可以在不知道具体实现的情况下编程,提高了代码的灵活性和拓展性。
2、封装
封装就是指隐藏对象的属性和实现细节,将对象的数据、属性、行为、方法等组合到下一个单一的单元中,并通过访问修饰符控制成员属性的访问和修改权限,再通过特定公开的方法setter和getter方法暴露给外面访问。
private String name = "ccshen";
public String getName(){
return name;
}
name属性被private封装起来,外面只能通过对象的getName才能访问。
3、继承
继承是一种实现代码重用的机制,允许一个类继承另一个类的成员和方法,使得子类也能具有父类相同的行为。
继承是通过 extends 关键字实现的:
public class Dog extends Animal{
@Override
void eat(){
System.out.printIn("狗吃饭")
}
}
java类之间只能实现单继承,接口之间可以多继承。
4、多态
多态指同一个行为在不同情况下的多种不同表现形式或形态,主要体现为同一个接口或父类的引用指向不同的实现对象,并能够在运行时动态决定调用的具体实现,这使得程序具有更好的灵活性和可扩展性。
Animal animal = new Dog();
animal.eat();
通过父类引用变量指向子类时,当调用父类的方法时,它实际上会根据实际对象的类型,去调用子类中的方法。
1.2、JDK与JRE的区别
JDK:是整个Java的核心,包含了Java的运行环境(JRE)和一系列Java开发工具完整包。
JRE:是Java程序的运行环境,包含JVM、Java核心类库等。JRE只能运行Java应用程序,不能用于编译开发,它是JDK的子集。
安装JDK后就有JRE目录,JRE属于JDK的子集。
1.3、如何编译和运行Java文件
使用javac命令来编译.java文件
javac Test.java
运行后会生成Test.class文件
1.4、Java标识符命名规则
- 标识符只能由字母、数字、下划线(_)、美元符号($)组成
- 标识符不能由数字开头
- 标识符不能使用Java关键字,比如 for
// 类名
class User(){
// 变量名
int firstName;
// 常量名
private static final int default_code = 60;
// 合法方法名
public void setFirstName(int firstName){
this.firstName = firstName;
}
}
1.5、Java定义常量
Java常量是指使用final修饰的变量,它们的值在初始化后不能被改变
final int age = 30;
加修饰范围,以及静态关键字
private static final age = 30;
常量的作用域可以是类级别(普通、静态),方法级别。
1.6、Java有哪几种基本数据类型
Java有 4类 8种 数据类型
整数型:byte、short、int、long
浮点型:float、double
字符型:char
布尔型:boolean
1.7、== 和 equals 比较有什么区别
==
如果比较的对象是基本的数据类型,则比较的是数值是否相同
如果比较的对象是引用数据类型,则比较的是对象的地址是否相同
equals
equals是Object类提供的方法,接收Object参数类型
用来比较两个对象是否相等,默认比较的是对象地址,不能用于比较基本数据类型,但可以是包装类型,所以,如果是要比较两个对象的值是否相等,一般需要重写equals与hashcode方法 。
比如常用的String、Date、Integer等类都重写了equals和hashcode方法,比较的是存储对象的内容是否相等,而不是堆内存地址。
1.8、s1 = s1 + 1 和 s1 += 1 区别
如果s1原有数据类型小于int类型,则s1 = s1 + 1发生编译异常
因为s1是short类型,1是int类型,所以计算结果为int类型,它不能自动转换为比它小的数据类型,所以发生编译异常。
解决办法使用类型强制转换:
public static void main(String[] args){
short s1 = 1;
s1 = (short)(s1 + 1);
// s1 = 2
}
使用s1 += 1不会有问题,因为 s1 += 1;支持隐式强制类型转换
public static void main(String[] args){
short s1 = 1;
s1 += 1;
// s1 = 2
}
1.9、float n = 1.8 有错吗
有错!!!
因为double是双精度型浮点型,float是单精度型浮点型。
数字1.8默认是double类型,如果将一个double类型的值赋值给一个float类型的变量时,需要进行类型转换,因为double类型的精度高于float类型。
这种情况,如果不显示进行类型转换,编译器报错,因为可能会有精度丢失
因此需要强制类型转换或后面加上F:
float n = (float)1.8;
float = 1.8F;
1.10、i++ 与 ++i 区别
i++是先赋值后加1,++1是先加1后赋值。也正是这种顺序的差异,++i能够当作左值来使用,i++却不可!
a=i++,先将i的值赋给a,然后再进行i=i+1的操,最后把加+1后的结果再赋值给i。
b=++i,先进行i=i+1操作,然后把加1后的结果赋值给i,最后把i的值赋给b。
无论是i++还是++i,i自己都是加了1。只不过运算存在顺序不同,所以把他整体赋值给变量时会不同。
public static void main(String[] args) throws Exception{
int i = 1;
int a = i++;
int j = 1;
int b = ++j;
// a = 1 , i = 2
// b = 2 , j = 2
}
1.11、while 与 do while 区别
while与do while都是循环语句
- while 是先判断条件再执行循环
- do whil e是先执行循环再判断条件
public static void main(String[] args) throws Exception{
// while
int i = 0;
while(i < 3){
i++;
System.out.printIn(i);
}
// do while
int j = 0;
do{
j++;
System.out.printIn(j);
} while(i < 3);
}
同样的条件下,如果初始条件不成立,do while会多执行一次。
1.12、如何跳出java循环
关键字 | 说明 |
---|---|
continue | 跳出当前本次循环 |
break | 跳出整个循环 |
return | 跳出整个循环及当前方法 |
public int getData(){
for(int i = 0; i < 10; i++){
if(i == 1){
// 跳出本次循环,继续下次循环
continue;
}
if(i == 5){
// 跳出整个循环,走下面代码
break;
}
}
// 结束方法,返回结果
return i;
}
1.13、如何跳出java多层嵌套循环
- 标号方式
在外面的循环语句前定义一个标号,然后在里层循环里面定义带有标号的break语句。
public static void main (String[] args){
tag:
for(int i = 0; i < 10; i++) {
for(int j = 0; j < 10; j++) {
if(j == 5) {
break tag;
}
}
}
}
- 设置标志位
public static void main (String[] args){
boolean flag = false;
for(int i = 0; i < 10; i++) {
for(int j = 0; j < 10; j++) {
if(i == 0 && j == 5) {
flag = true;
break;
}
}
if(flag){
break;
}
}
}
- 内层循环抛出异常
public static void main (String[] args){
for(int i = 0; i < 10; i++) {
for(int j = 0; j < 10; j++) {
if(i == 1 && j == 5) {
throw new Exception();
}
}
}
}
1.14、& 与 && 区别
- & 位运算符;逻辑运算符
按逻辑运算符时,&左右两边有一个条件为假就会不成立,但是2端都会运行,比如(1+2)== 4 &(1+2)== 3;即使1+2 == 4为假也会去判断1+2 == 3是否成立。
- && 逻辑运算符
&& 也叫短路运算符,因为只要左端条件为假不成立,不会去判断右端的条件。
相同点:只要有一端为假,则语句不成立
1.15、java数组初始化
- 静态初始化:创建数组时指定数组元素
int[] unmbers = [1,2,3,4,5];
String[] strs = ["a","b","c"];
- 动态初始化:先指定数组长度,然后逐个赋值
int[] numbers = new int[5];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;
- 多维数组初始化
int[][] numbers = {
{1,2,3},
{4,5,6},
{7,8,9}
};
1.16、数组有length属性,String有length()方法
public static void main(String[] args){
String[] names = {"ccshen","Li","Le"};
String str = "ccshen";
System.out.printIn(names.length);
System.out.printIn(str.length());
}
1.17、java种的值传递与引用传递区别分析
java中数据类型分为两类:基本数据类型和对象引用类型。
基本数据类型:包括 byte
, short,int
,long,float
,double
, char
, boolean
,它们都在栈中存储数据。
对象引用类型:包括类、接口和数组等,它们在栈中存储引用,在堆中存储实例。
- 值传递:方法调用时,实参是基本数据类型,形参是实参的拷贝,形参的改变不影响实参。
public void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int x = 10;
int y = 20;
swap(x, y); // 调用后,x 和 y 的值不变,因为传递的是它们的值的拷贝
- 引用传递:方法调用时,实参是对向引用类型,形参与实参指向同一个对象。
public void swap(Person p1, Person p2) {
Person temp = p1;
p1 = p2;
p2 = temp;
}
Person x = new Person("Alice");
Person y = new Person("Bob");
swap(x, y); // 调用后,x 和 y 的引用可能会交换,但它们引用的对象内容不会改变
在Java中,虽然我们通常说是传值或传引用,但实际上,参数传递统一使用的是值传递。对于对象引用类型,实际上传递的是对象的引用值,这个值是对象的内存地址。因此,方法参数传递时,基本数据类型按值传递,对象引用按值传递(传递的是引用的副本),但是传递的是引用的地址值,这个地址值是可以操作的,如果需要修改原对象,可以通过返回值或将对象作为引用传递给方法内部进行操作。
1.18、java中的构造方法是什么
构造方法是构造类的主要方法,java中每个类都必须要有构造方法,构造方法名与类名相同,没有返回类型,new 一个对象的时候就会调用指定的构造方法。
一个类至少要有一个构造方法,可以有多个构造方法,即构造方法重载,方法参数的数量不同或类型不同。
如果没有显示的创建构造方法,java编译器也会为类提供一个默认的无参构造方法。
@Data
public class User(){
private String name;
private int age;
// 构造方法
public User(String name, int age){
this.name = name;
this.age = age;
}
// 重载
public User(String name){
this.name = name;
}
}
public void Test(){
// 构造函数创建对象
User user = new User("ccshen",30);
}
1.19、static关键字
1、static关键字有什么用
static
关键字在 Java 中的作用是为类创建一个静态成员。被 static
修饰的成员属于类本身,而不是类的某个特定实例。这意味着你可以不创建类的对象就直接访问静态成员。static
关键字可以用来修饰内部类、方法、变量、代码块。
- 静态内部类:静态内部类可以不依赖外部类实例对象而被实例化,而内部类需要在外部类实例化后才能被实例化。
- 静态方法:静态方法属于类方法,不需要实例化对象就能调用
- 静态变量:静态变量属于类,不需要实例化对象就能调用
- 静态代码块:静态代码块只会在类被加载时执行且执行一次
public class Test(){
// 静态代码块
static{
}
// 静态内部类
// @Data
static class User(){
String name;
int age;
Score score;
}
// @Data
static class Score(){
String chinese;
String english;
}
// 静态变量 Test.id直接访问变量,不需要创建类
public static int id = 1;
// 静态方法 Test.getData直接调用方法,不需要创建Test对象再调用
public static void getData(){
}
}
2、static变量与普通变量的区别
- 所属目标不同:静态变量属于类的变量,普通变量属于对象的变量
- 储存区域不同:静态变量储存在方法区的静态区,普通变量储存在堆区。jdk1.7及以上,静态变量储存在其对应的class对象中,而class对象与其他普通的class对象一样,都储存在堆中。
- 加载时间不同:静态变量随着类的加载而加载,随着类的消失而消失;普通变量随着对象的加载而加载,随着对象的消失而消失。
- 调用方式不同:静态变量只能通过类名、对象调用,普通变量只能通过对象调用。
3、static不可以修饰局部变量 ,修饰的目标是:内部类、全局的成员变量、方法、代码块。
4、static 方法中不可以使用this或super,否则会导致编译错误
因为this或super是实例化对象后的操作,而static属于类级别,无法指向任何实例。
1.20、final关键字
修饰类型 | 说明 |
---|---|
修饰类 | 表示该类不能被继承 |
修饰方法 | 表示该方法不能被重写 |
修饰变量 | 表示常量,只能赋值一次,不能被修改 |
1.21、final、finally、finalize有什么区别
final:
- 如果修饰类,该类不能被继承
- 如果修饰方法,该方法不能被重写
- 如果修饰变量,该变量是常量,不能被修改
finally:
finally是try-catch-finally最后一部分,表示发生任何情况都会执行,finally部分可以省略,但如果finally部分存在,则一定会执行finally里面的代码(发生ERROR错误等非程序异常除外)
finalize
finalize是Object的一个方法,在垃圾收集器执行的时候会自动被调用被回收对象的此方法,一般不建议主动使用
1.22、java支持多继承吗
- 类与类之间不支持多继承,只支持单继承
- 接口与接口之间支持多继承
1.23、java类可以实现多个接口
JDK concurrentHashMap集合类源码,可以看到继承一个抽象类,实现2个接口。
1.24、重写与重载
- 区别
重写是子类可以根据自身的特征,重新实现父类方法的业务逻辑,但是方法名称,参数列表,返回值必须与父类保持一致;(子类通过继承实现父类方法,方法名称,参数列表,返回值不变)
重载是一个类中允许定义多个方法名相同,返回值可以相同可以不同,但是参数列表必须不同的方法;(构造方法)
重写是子类与父类间多态性的表现,重载是一个类中方法间多态性的表现。
- 构造器可以被重载,不可以被重写
一个类的构造器只属于当前类,它不能被继承,所以构造器不能被重写;
一个类里面可以有多个构造器,所以构造器可以被重载;
- 私有方法能被重载,不可以被重写
因为private修饰方法只能在当前类可见,子类都见不到,不可能被重写,重写必须在protectd以上的作用域
- 静态方法可以重载,不可以被重写
重载是一个类中可以有多个方法名相同,参数列表不同,与静态没有关系
重写是面向对象多态性的一种体现,需要有继承和子类对象的动态绑定,但是静态方法属于类的,不属于任何实例,不参与类动态绑定过程。
1.25、java异常有哪些分类
- Throwable
Throwable是java异常的顶级类,所有异常都继承这个类。
Error和Exception是异常类的两个大分类。
- Error
Error是非程序异常,即程序不能捕获的异常,一般是编译或系统性错误,如OutOfMemorry内存溢出异常等。
- Exception
Exception是程序异常类,有程序内部产生,Exception又分为运行时异常、非运行时异常。
- 运行时异常
运行时异常是程序运行才可能发生的异常,编译器编译不会检查,方法可以不用主动catch,也可以不用throws声明抛出运行时异常。
常见的运行时异常:NullPointException、IndexOutOfBoundsException、ClassCastException、ArithmeticException、SecurityException等
在程序中可以不用声明,也不用捕获。
- 非运行时异常
非运行时异常是编译器编译就会检查的异常,方法需要主动catch或throws声明受检查的异常,不然会出现编译错误。如常见的:IOException、ClassNotFoundException等。
在程序中必须声明或捕获。
1.26、java中常见的异常有哪些
- NullPointExcption
空指针异常,操作一个null对象的方法和属性抛出的异常。
- OutOfMemoryError
内存溢出异常,这不是程序能控制的,是指要分配对象的内存超出当前最大的堆内存,需要调整堆内存大小(-Xmx)或优化程序
- IOException
IO是input、output,我们在读写磁盘文件、网络内容的时候经常出现的一种异常,这种异常是受检查异常,需要进行手工捕获。
- FileNotFoundException
文件找不到或文件不存在就会抛出这样的异常,如定义文件输入输出流,文件不存在会报错。
FileNotFoundException是IOException的子类,同样是受检查的,需要进行手工捕获。
- ClassNotFoundException
类找不到异常,加载类找不到抛出异常,即类路径下不能加载指定类。它是受检查的异常,需要人工捕获。
- ClassCastException
类转换异常,将一个不是该类的实例转换成这个类就会抛出这个异常。这是运行时异常,不需要手工捕获。如数字强制转换为字符串就会抛出这样的异常。
- NoSuchMethodException
没有这个方法异常,一般发生在发射调用方法的时候。它是受检查的异常,需要人工捕获。
- IndexOutOfException
索引越界异常,当操作一个字符串或数组时抛出的异常。运行时异常,不需要手工捕获。
- ArithmetricException
算术异常,数字算术运算时异常 ,如一个数字除以0就会报这个错。
运行时异常,可以手动捕获抛出异常
- SQLException
SQL异常,发生在操作数据库时的异常,它是受检查的异常,需要人工捕获。
1.27、java中避免空指针常见的方法
字符串比较时常量放前面
String s = "ccshen";
if("shen".equals(s)){
}
初始化默认值
List<String> list = new ArrayList();
String str = "";
返回空集合
public List<User> getUsers(){
List<User> userList = userDao.queryUserInfo();
return (userList = null) ? new ArrayList() : userList;
}
断言
断言是用来检查程序的安全性的,在使用之前就进行检查条件,如果不符合条件就报异常,符合就继续。Java中自带的断言关键字为:assert
,如:
assert name == null : "名称不能为空";
不过默认是不启动断言检查的,需要带上JVM参数:-anableassertions 才能生效。不过不建议使用,建议使用 Spring 中的,更强大,更方便好用。
Spring中的用法:
Assert.notNull(name,"名字不能为空");
Optionnal
Optional是java8新特性
String newName = Optional.ofNullable(name).else("");
1.28、throw与throws区别
throw用在方法中,用来主动抛出一个异常;
throws用在方法声明中,声明方法可能会抛出异常;
2个不一定要同时用,如果方法中抛出的是RuntimeException及其子异常,则方法可以不用声明throws,否则必须要声明throws。
public void test1(){
throw new RuntimeException("运行时异常");
}
public void test1() throws Exception{
throw new Exception("发生异常");
}
1.29、try-catch-finally
try-catch-finally中catch与finally都可省略,但是不能同时省略,有try时后面必须有catch或finally
JDK7后可以一次catch多个异常
使用try-catch会影响性能吗
一般情况下,try-catch执行时间很短,不会对性能产生显著的影响,但是在极端情况下,try-catch的使用可能会对性能有影响,频繁捕获抛出异常,尤其在高并发下,try-catch性能会成为瓶颈。