基础篇
1、 Java语言有哪些特点
-
简单性:Java语言的语法相对简单,并且去除了一些复杂的特性,使其更易于学习和理解。
-
面向对象:Java是一种面向对象的编程语言,支持封装、继承和多态,可以更好地组织和管理代码。
-
平台无关性:Java语言的最大特点之一是它的平台无关性。Java程序可以在不同的操作系统和硬件平台上运行,只需将程序编译为字节码(bytecode),然后在Java虚拟机(JVM)上运行。
-
可靠性:Java语言有很强的异常处理机制和内存管理,可以提供相对稳定和可靠的应用程序。
-
安全性:Java提供了安全性管理机制,可以防止常见的安全漏洞,如缓冲区溢出和指针操作。
-
高性能:尽管Java是一种解释型语言,但通过即时编译(Just-In-Time Compilation)技术,可以实现接近本地代码的性能。
-
多线程:Java内置了对多线程编程的支持,可以方便地创建和管理线程,实现并发和并行处理。
-
大量的类库:Java拥有丰富的类库,包含了各种各样的功能模块和工具,开发者可以直接调用这些类库来完成常见的任务,提高开发效率。
-
开放性:Java是开源的,拥有强大的社区支持,开发者可以自由地使用和修改Java的源代码。
总的来说,Java语言具有简单、面向对象、平台无关、可靠、安全、高性能、多线程、丰富的类库和开放性等特点,因此在软件开发领域得到广泛应用。
2、面向对象和面向过程的区别
面向对象和面向过程是两种不同的编程思想。
-
面向过程是一种以过程为中心的编程思想,强调按照一系列的步骤来解决问题。在面向过程编程中,问题被分解为一系列的步骤,然后通过调用函数或方法来执行这些步骤。面向过程编程更加注重解决问题的步骤和流程,关注的是如何完成任务。
-
面向对象是一种以对象为中心的编程思想,强调将问题抽象为对象,通过对象之间的交互来解决问题。在面向对象编程中,问题被分解为一系列的对象,每个对象都有自己的属性和方法,通过对象之间的交互来实现功能。面向对象编程更加注重对象的设计和组织,关注的是如何描述和组织问题的实体和行为。
面向对象编程:
- 对象和类:面向对象编程将程序中的数据和操作数据的方法封装到一个个对象中,对象是对现实世界中事物的抽象,而类则是对象的模板。相同类型的对象可以通过实例化类来创建。例如,定义一个"汽车"类,可以创建多个汽车对象。
- 继承:通过继承,一个类可以派生出子类,继承父类的属性和方法,并且可以添加自己的特性。这样的继承层次可以提高代码的复用性和可维护性。
- 封装:封装可以将对象的内部数据和方法隐藏起来,对外提供公共的接口,保证了数据的安全性和代码的高内聚性。
- 多态性:多态性允许使用统一的接口来处理不同类型的对象,同一个操作可以具有不同的行为,通过方法的重写和方法的重载来实现。
面向过程编程:
- 过程和函数:面向过程编程以顺序执行的过程为基本单位,通过定义函数来组织和管理代码。函数是一组具有特定功能的可执行代码块。
- 数据和动作分离:在面向过程编程中,数据和对数据的操作是分离的,数据是全局的,函数对数据进行处理和操作。因此,在面向过程编程中,代码更加侧重于对数据的处理。
面向对象编程相对于面向过程编程具有以下优点:
- 代码的组织更加清晰、结构更加模块化,易于理解和维护。
- 通过封装、继承和多态等特性,实现了代码的高重用性和灵活性。
- 更适合大型复杂项目的开发,减少了开发过程中的设计和实现上的困难。
然而,并非所有情况下面向对象编程都比面向过程编程更好。在一些小型简单的项目中,面向过程编程可能更加直观和高效。选择面向对象编程还是面向过程编程取决于具体的应用场景和开发需求。
3、八种基本数据类型的大小,以及他们的封装类
注:
1、int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。
2、基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。
虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。
4、标识符的命名规则
在Java中,标识符的命名规则通常遵循以下规则:
- 标识符可以由字母(大小写)、数字和下划线组成。
- 第一个字符必须是字母或下划线,不能是数字。
- 标识符区分大小写,例如"myVar"和"myvar"是不同的标识符。
- 标识符不能是Java的保留字(关键字),例如"if"、“for”、"while"等。
- 标识符应该具有描述性,能够清晰地表达其含义。
- 避免使用特殊字符和空格。
- 一般而言,标识符应该使用驼峰命名法(CamelCase),即首字母小写,后续每个单词首字母大写,例如"myVariable"。
- 类名应该以大写字母开头,例如"Person"。
- 常量名通常全部大写,多个单词之间用下划线分隔,例如"MAX_VALUE"。
除了以上的一般规则外,还有一些Java特定的命名约定:
- 包名应该使用小写字母,多个单词之间用点号分隔,例如"com.example.mypackage"。
- 方法名应该以小写字母开头,采用驼峰命名法,例如"calculateTotal"。
- 接口名应该以大写字母开头,采用驼峰命名法,例如"Runnable"。
- 枚举类型名应该以大写字母开头,采用驼峰命名法,例如"Color"。
在命名标识符时,应该尽量遵循良好的命名规范和约定,以提高代码的可读性和可维护性。
5、instanceof 关键字的作用
instanceof
是 Java 中的一个关键字,用于判断一个对象是否属于某个特定的类或其子类的实例。
instanceof
的语法如下:
object instanceof class
其中,object
是要判断的对象,class
是要判断的类或接口。
instanceof
的作用是判断一个对象是否是某个类或其子类的实例。它返回一个布尔值,如果对象是指定类或其子类的实例,则返回 true
,否则返回 false
。
instanceof
在实际开发中常常用于类型转换和对象的动态判断。例如,可以使用 instanceof
来判断一个对象是否是某个类的实例,然后进行相应的类型转换或处理。
下面是一个示例:
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
System.out.println(animal1 instanceof Dog); // true
System.out.println(animal2 instanceof Dog); // false
}
}
在上面的示例中,animal1
是 Dog
类的实例,所以 animal1 instanceof Dog
返回 true
。而 animal2
是 Cat
类的实例,不是 Dog
类的实例,所以 animal2 instanceof Dog
返回 false
。
通过使用 instanceof
关键字,可以在程序中进行对象类型的判断和处理,提高代码的灵活性和可扩展性。
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 i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
运行结果:
true
flase
为什么会出现这样的结果?输出结果表明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() {}
}
从这2段代码可以看出,在通过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 i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
运行结果:
false
flase
原因: 在某个范围内的整型数值的个数是有限的,而浮点数却不是。
7、重载和重写的区别
重载(Overloading)和重写(Overriding)是Java中两个重要的概念,它们都涉及到方法的使用和定义,但是具有不同的特点和用途。
-
重载(Overloading):
- 定义:重载是指在同一个类中,可以定义多个具有相同名称但参数列表不同的方法。
- 特点:
- 方法名相同,但参数列表不同(参数个数、类型或顺序不同)。
- 返回值类型可以相同也可以不同。
- 重载方法可以有不同的访问修饰符。
- 作用:
- 提供了一种方便的方式来使用相似功能的方法。
- 可以根据不同的参数选择合适的方法进行调用。
- 示例:
在上面的示例中,public class Calculator { public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } }
Calculator
类定义了两个名为add
的方法,一个接受两个整数参数,另一个接受两个浮点数参数,通过重载的方式提供了不同类型的加法运算。
-
重写(Overriding):
- 定义:重写是指在子类中重新定义父类中已经存在的方法,使用相同的方法名、参数列表和返回类型。
- 特点:
- 方法名、参数列表和返回类型必须与父类中被重写的方法相同。
- 访问修饰符不能比父类中被重写的方法更严格。
- 子类方法不能抛出比父类方法更宽泛的异常。
- 作用:
- 子类可以根据自身的需要对继承自父类的方法进行定制化实现。
- 实现了多态性,通过父类引用调用子类重写的方法。
- 示例:
在上面的示例中,public class Animal { public void makeSound() { System.out.println("Animal makes sound."); } } public class Cat extends Animal { @Override public void makeSound() { System.out.println("Cat meows."); } }
Animal
类定义了一个名为makeSound
的方法,Cat
类继承了Animal
类并重写了makeSound
方法,通过重写实现了猫发出不同的声音。
总结:
- 重载是在同一个类中定义多个方法,方法名相同但参数列表不同,用于提供相似功能的方法。
- 重写是在子类中重新定义父类中已经存在的方法,方法名、参数列表和返回类型必须相同,用于定制化实现和实现多态性。
8、equals与==的区别
在Java中,equals
和==
都是用来比较两个对象是否相等的,但它们的比较方式和应用场景有所不同。
-
equals
方法比较对象的内容是否相等,而==
比较的是对象的引用是否相等。equals
方法是Object类中定义的方法,可以被所有对象调用,但需要根据具体的业务需求进行重写。==
是Java中的运算符,用于比较两个对象的引用是否相等,即它们是否指向同一个内存地址。
-
equals
方法可以被重写,而==
不能。equals
方法可以被重写,以实现自定义的比较逻辑,例如比较对象的属性值是否相等。==
不能被重写,因为它是Java中的运算符,其比较方式是固定的。
-
equals
方法比较的是对象的内容,因此需要注意对象的属性值是否相等,而==
比较的是对象的引用,不需要考虑对象的属性值。- 如果要比较两个对象的属性值是否相等,应该使用
equals
方法。 - 如果要比较两个对象是否是同一个对象,应该使用
==
运算符。
- 如果要比较两个对象的属性值是否相等,应该使用
-
equals
方法需要满足一些约定,例如具有自反性、对称性、传递性和一致性等。- 自反性:对于任何非空引用x,x.equals(x)应该返回true。
- 对称性:对于任何非空引用x和y,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。
- 传递性:对于任何非空引用x、y和z,如果x.equals(y)返回true,y.equals(z)也返回true,那么x.equals(z)也应该返回true。
- 一致性:对于任何非空引用x和y,多次调用x.equals(y)应该返回相同的结果,除非x或y的属性值发生了改变。
综上所述,equals
和==
都是用于比较两个对象是否相等的,但比较方式和应用场景有所不同。在使用时,应根据具体的需求选择合适的方法进行比较。
9、Hashcode的作用
HashCode在Java中是一个整数值,用于表示对象的哈希码。它的作用有以下几个方面:
-
在哈希表中的使用:哈希表是一种常用的数据结构,用于存储和检索大量的数据。在哈希表中,对象的哈希码被用作索引,可以快速定位到对象在内存中的位置,从而提高数据的访问速度。
-
在集合中的使用:在Java中,像HashSet、HashMap等集合类都是基于哈希表实现的。当我们向这些集合中添加对象时,集合会根据对象的哈希码来确定对象在集合中的位置,从而实现高效的查找和插入操作。
-
在对象的比较中的使用:在Java中,我们可以通过重写equals方法来比较两个对象的内容是否相等。而在重写equals方法时,通常也需要重写hashCode方法,以保证相等的对象具有相同的哈希码。这样可以确保在使用哈希表或集合类等需要比较对象的场景中,能够正确地判断对象是否相等。
需要注意的是,哈希码并不是唯一的,不同的对象可能会有相同的哈希码。因此,在比较对象时,我们仍然需要通过equals方法来进行详细的比较,以确保对象的内容是否真正相等。
为什么重写
equals
就必须重写hashCode
在Java中,重写equals
方法时,通常也需要重写hashCode
方法,以保证相等的对象具有相同的哈希码。这是因为在使用哈希表或集合类等需要比较对象的场景中,对象的哈希码被用作索引,可以快速定位到对象在内存中的位置,从而提高数据的访问速度。
如果在重写equals
方法时没有同时重写hashCode
方法,那么在使用哈希表或集合类时可能会出现以下问题:
-
相等的对象具有不同的哈希码:如果两个对象的内容相等,但它们的哈希码不相等,那么在使用哈希表或集合类时,可能会将它们存储在不同的位置,导致无法正确地检索到这些对象。
-
相等的对象无法从集合类中正确删除:当我们向集合类中添加对象时,集合会根据对象的哈希码来确定对象在集合中的位置。如果在重写
equals
方法时没有同时重写hashCode
方法,那么在从集合中删除对象时,可能无法正确地找到该对象的位置,导致无法从集合中正确删除对象。
为了避免这些问题,我们需要确保相等的对象具有相同的哈希码。因此,在重写equals
方法时,通常需要根据对象的属性值来计算哈希码,并在hashCode
方法中返回相应的哈希码。这样可以保证在使用哈希表或集合类时,能够正确地判断对象是否相等,并能够正确地定位到对象在内存中的位置。
10、String、String StringBuffer 和 StringBuilder 的区别是什么?
String、StringBuffer和StringBuilder是Java中用于处理字符串的类,它们之间的区别如下:
-
可变性:String是不可变的类,一旦创建就不能被修改。而StringBuffer和StringBuilder是可变的类,可以进行字符串的修改、添加、删除等操作。
-
线程安全性:String是线程安全的,即多个线程同时访问一个String对象时不会出现问题。而StringBuffer是线程安全的,内部的方法都进行了同步处理,可以安全地在多个线程中使用。而StringBuilder是非线程安全的,不进行同步处理,适用于单线程环境。
-
性能:由于String是不可变的,每次对String进行修改时都会创建一个新的String对象,导致内存开销较大。而StringBuffer和StringBuilder是可变的,可以直接对原始对象进行修改,避免了频繁的对象创建和销毁,性能较高。StringBuilder的性能比StringBuffer更好,因为StringBuilder不进行同步处理,省去了同步开销。
综上所述,如果需要频繁对字符串进行修改操作,并且在多线程环境中使用,应该使用StringBuffer。如果在单线程环境中使用,并且不需要线程安全性,可以使用StringBuilder。而如果不需要修改字符串,或者需要保证字符串的不可变性,应该使用String。
string为什么不可变
String是不可变的,主要有以下几个原因:
-
安全性:String作为Java中最常用的类之一,广泛用于各种场景。如果String是可变的,那么在多线程环境下,多个线程可能同时修改同一个String对象的内容,导致数据不一致或者出现竞态条件。通过将String设计为不可变的,可以保证多线程环境下的安全性。
-
缓存优化:由于String是不可变的,可以将其缓存起来以提高性能。在Java中,字符串常量池使用了缓存机制,相同的字符串常量只会在内存中存在一份,多个引用可以共享同一个字符串对象。这样可以节省内存空间,并且提高了字符串的比较效率。
-
传递性:不可变性使得String对象可以被安全地用作方法参数或返回值。因为不可变性保证了String对象在传递过程中不会被修改,从而避免了潜在的副作用。
-
效率:由于String是不可变的,所以可以使用字符串常量池来共享字符串对象,避免了频繁创建和销毁对象的开销。此外,不可变性还使得字符串对象可以被缓存、复用和优化,提高了性能。
综上所述,String的不可变性保证了多线程环境的安全性,提供了缓存优化的可能,同时也提高了传递性和效率。因此,String被设计为不可变的类。