1. Java语言有哪些特点
- 简单易学、有丰富的类库
- 面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高)
- 与平台无关性(JVM是Java跨平台使用的根本)
- 可靠安全
- 支持多线程
- 分布式计算支持、网络编程支持
2. 面向过程和面向对象的区别
面向过程:
- 是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,
- 然后在使用时候一 一调用即可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发。
面向对象:
- 是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。
- 面向对象有封装、继承、多态的特性,所以下列优势:
- 易维护
- 易复用
- 易扩展
- 可以设计出低耦合的系统
- 性能上来说,面向对象比面向过程要低。
3.八种基本数据类型的大小,以及它们的封装类
基本类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
byte | 1 | (byte)0 | Byte |
short | 2 | (short)0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0L | Long |
float | 4 | 0.0f | Float |
double | 8 | 0.0d | Double |
boolean | - | false | Boolean |
char | 2 | \u0000(null) | Character |
注:
-
int是
基本数据类型
,Integer是int的封装类,是引用类型
。int默认值是0,而Integer默认值是null
,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。 -
基本数据类型在声明时系统会
自动分配空间
,而引用类型声明时只是分配了引用空间
, 必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另 一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。 -
虽然Java定义了
boolean
类型,但对其支持有限。在Java虚拟机中,boolean
没有专用的字节码指令,编译后会使用int
类型代替,且boolean
数组被编码为byte
数组,每个元素占8位。因此,单独使用时boolean
类型占4字节,在数组中每个元素占1字节。使用int
类型的原因是现代32位处理器一次处理32位数据,能提高存取效率。
4.标识符的命名规则
- 标识符的含义: 是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。
- 命名规则:(硬性要求) 标识符可以包含英文字母,0-9的数字,$以及 _ 标识符不能以数字开头,标识符不能是关键字
- 命名规范:(非硬性要求) 类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。
- 变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。
- 方法名规范:同变量名。
5. instanceof 关键字的作用
- instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例
boolean result = obj instanceof Class
- 其中 obj 是一个对象,Class 表示一个类或接口。当 obj 是 Class 的对象,或者是其直接或间接子类,或者是实现了其接口的类的对象,instanceof 结果返回 true,否则返回 false。
- 注意:编译器会检查 obj 是否能够被赋值为右侧 Class 类型(即是否满足类型安全),如果不符合,编译时会报错。如果无法在编译时确定类型,则通过编译,但会在运行时进行类型检查。
int i = 0;
System.out.println(i instanceof Integer);//编译不通过 i必须是引用类型,不能是基本类型
System.out.println(i instanceof Object);//编译不通过
Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);//true
//在JavaSE规范中对instanceof 运算符的规定就是:如果obj为null,那么将返回false。
System.out.println(null instanceof Object);//false
6.Java自动装箱与拆箱
- 装箱就是自动将基本数据类型转换为包装器类型(int–>Integer);调用方法:Integer的 valueOf(int) 方法
- 拆箱就是自动将包装器类型转换为基本数据类型(Integer–>int)。调用方法:Integer的 intValue方法
在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:
Integer i = new Integer(10);
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:
Integer i = 10;
面试题1: 以下代码会输出什么?
public class Main {
public static void main(String[] args) {
Integer Integer Integer Integer i1 = 100;
i2 = 100;
i3 = 200;
i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
运行结果:
true
false
为什么会出现这样的结果?输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
其中IntegerCache类的实现为:
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that // require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue(); i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
-
从这两段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
-
上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。
面试题2:以下代码输出什么
public class Main {
public static void main(String[] args) {
Double Double Double
Double i1 = 100.0;
i2 = 100.0;
i3 = 200.0;
i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
运行结果:
false
false
原因:在某个范围内的整型数值的个数是有限的,而浮点数却不是。
7.重载和重写的区别
重写(Override)
- 重写是指在子类中重新定义父类的某个方法,并且方法名、参数列表和返回类型都必须与父类中的方法相同。
- 重写是为了改变或扩展父类方法的实现。
class Animal {
// 父类方法
public void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
// 子类重写父类方法
@Override
public void sound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.sound(); // 输出:Animal makes a sound
Dog dog = new Dog();
dog.sound(); // 输出:Dog barks
// 多态:父类引用指向子类对象
Animal polyAnimal = new Dog();
polyAnimal.sound(); // 输出:Dog barks(动态绑定)
}
}
特点:
- 发生在父类与子类之间
- 方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
- 访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
- 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载(Overload )
- 重载是指在同一个类中,方法的名称相同,但参数的类型、数量或顺序不同。
- 通过重载,可以让同一个方法名称根据不同的输入参数执行不同的操作。
- 重载与方法的返回类型无关。
class Calculator {
// 重载方法:加法操作,参数为两个整数
public int add(int a, int b) {
return a + b;
}
// 重载方法:加法操作,参数为三个整数
public int add(int a, int b, int c) {
return a + b + c;
}
// 重载方法:加法操作,参数为两个浮点数
public double add(double a, double b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // 调用两个整数的加法
System.out.println(calc.add(5, 10, 15)); // 调用三个整数的加法
System.out.println(calc.add(5.5, 10.5)); // 调用两个浮点数的加法
}
}
特点:
- 重载Overload是一个类中多态性的一种表现
- 重载要求同名方法的参数列表不同(参 数类型,参数个数甚至是参数顺序)
- 重载的时候,返回值类型可以相同也可以不相同。无法以返回类型别作为重载函数的区分标准
8.String、StringBuffer 和 StringBuilder 的区别是什么?
特征 | String | StringBuffer | StringBuilder |
---|---|---|---|
可变性 | 不可变 | 可变 | 可变 |
线程安全 | 不设计线程安全 | 线程安全 | 线程不安全 |
性能 | 性能较低(频繁修改时) | 性能较低(因线程同步开销) | 性能较高(无线程同步开销) |
适用场景 | 不需要修改的字符串常量 | 多线程环境中修改字符串 | 单线程环境中修改字符串 |
String
String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的String对象。
private final char value[];
- 每次 + 操作 :隐式在堆上new了一个跟原字符串相同的StringBuilder对象,再调用append方法拼接 + 后面的字符。
StringBuffer和StringBuilder
- StringBuffer和StringBuilder它们俩都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到
/**
* The value is used for character storage.
*/
char[] value;
- 它们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。
- StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
9.equals与==的区别
== 运算符
== 是一个比较运算符,用于比较两个对象或变量的引用是否相等。也就是说,它比较的是对象在内存中的地址,或者是原始数据类型的值是否相同。
- 基本数据类型(如 int、float 等):比较的是值是否相等。
- 对象引用类型:比较的是两个引用是否指向同一个对象(即内存地址是否相同)。
示例:
public class Main {
public static void main(String[] args) {
int x = 10;
int y = 10;
System.out.println(x == y); // 输出 true,因为基本类型比较的是值
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // 输出 false,因为它们指向不同的内存地址
String str3 = str1;
System.out.println(str1 == str3); // 输出 true,因为它们指向同一个对象
}
}
equals 方法
-
equals 是一个方法,用于比较对象的内容是否相等。
-
它是定义在 Object 类中的方法,所有类都继承自 Object 类,因此每个类都有 equals 方法。
-
默认的 equals 方法比较的是对象的引用(即 ==),但许多类会重写equals 方法来比较对象的内容。
-
对于自定义的类(如 String、List 等),通常会根据类的内容来重写 equals 方法。
示例:
public class Main {
public static void main(String[] args) {
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2)); // 输出 true,因为 String 类重写了 equals 方法,比较的是字符串内容
Person person1 = new Person("Alice");
Person person2 = new Person("Alice");
System.out.println(person1.equals(person2)); // 输出 true(假设 Person 类正确重写了 equals 方法)
Person person3 = person1;
System.out.println(person1.equals(person3)); // 输出 true,因为它们内容相同,引用也是相同的
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 引用相同
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return name.equals(person.name); // 比较 name 字段的内容
}
}
== 和 equals 的比较:
特性 | == | equals |
---|---|---|
比较对象 | 比较两个引用是否指向同一个对象(内存地址) | 比较对象的内容(通常通过类重写 equals) |
默认行为 | 比较对象引用(默认情况下,引用类型的 == 与 equals 相同) | 比较对象内容(如果没有重写,则比较引用) |
使用场景 | 用于比较基本数据类型的值,或判断两个引用是否指向同一对象 | 用于判断两个对象的实际内容是否相等 |
10.Hashcode
HashCode 是 Java 中 Object 类的一个方法,作用主要体现在集合类中,特别是 HashMap、HashSet、Hashtable 等基于哈希表的集合类中。HashCode 用于确定对象的存储位置、加速查找过程以及保证对象的唯一性等。
Hashcode的作用
- 加速查找:hashCode 的主要作用是为对象提供一个整数值,用于决定该对象在哈希表中的存储位置。通过 hashCode 值,哈希表可以快速定位对象的位置,从而提高查找效率。例如,HashMap 根据键的 hashCode值来确定其存储位置,并通过链表或红黑树来解决哈希冲突。
- 优化集合操作:在集合类(如 HashSet、HashMap)中,hashCode 提供了哈希算法的基础,允许集合进行高效的插入、删除和查找操作。通常,哈希表在理想情况下可以提供 O(1) 的时间复杂度来进行操作。
- 对象唯一性判断:结合 equals() 方法,hashCode 能够帮助判断两个对象是否相等。根据 Java 的规定,若两个对象的 equals() 返回 true,那么它们的 hashCode() 值也必须相同。
- 优化存储:在一些集合(如 HashMap)中,元素会根据 hashCode 值被分配到不同的桶(bucket)中,这样可以减少冲突,提高存储的效率。
hashCode 的规范
- 一致性:在程序的同一执行过程中,如果两个对象的字段没有发生改变,则多次调用 hashCode() 应返回相同的值。
- 相等的对象必须有相同的 hashCode:如果两个对象通过 equals() 方法比较返回 true,则它们的 hashCode() 方法必须返回相同的值。
- 不等的对象 hashCode 不必不同:如果两个对象不相等(equals() 返回 false),它们的 hashCode 值不一定要不同,但不同的 hashCode 可以提高哈希表的性能,减少碰撞。
hashCode 和 equals 的关系
-
hashCode 和 equals 之间的关系非常重要。在 HashMap 等基于哈希表的集合中,查找一个元素时,首先会根据 hashCode 查找对应的桶(bucket),然后再通过 equals() 方法确认是否真正找到了目标元素。
-
如果重写了 equals() 方法,也应当重写 hashCode() 方法,确保相等的对象具有相同的哈希码。如果 equals() 返回 true,那么 hashCode() 必须返回相同的值。
public class Person {
private String name;
private int age;
// 构造方法、getter、setter
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // 使用 name 和 age 计算 hashCode
}
}
在这个示例中,Person 类重写了equals() 和 hashCode() 方法,确保相等的 Person 对象有相同的 hashCode 值。