1、解释下什么是面向对象?面向对象和面向过程的区别?
1.1 面向过程
- **面向过程(Procedure Oriented)**就是分析出解决问题的步骤,然后用函数把这些步骤一步步的实现,使用的时候一个个的调用就可以了
1.2 面向对象
- **面向对象(Object Oriented,OO)**是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。
- 面向对象的三大特点:**封装、继承和多态。通俗点讲对象是由:属性和方法组成的;**类是对象的抽象概念。
属性:用于描述某个对象的特征,比如:身高,体重,肤色等,属性描述的是对象静态的一方面
方法:描述的则是对象动态的一方面,比如:一个人会跑,会唱歌,会说话等等。
类 :是对象的一种抽象概念,对象是类的实例化。比如:人类就是一个“类”,而具体的小明、小红等就是类中的一个对象。所以说对象就是类的实例化。
1.3 面向对象和面向过程的优缺点
-
面向过程:
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展
-
面向对象
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
- 缺点:性能比面向过程低
-
区别
- 编程思路不同:面向过程以实现功能的函数开发为主,而面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能。
- 封装性:都具有封装性,但是面向过程是封装的是功能,而面向对象封装的是数据和功能。
- 面向对象具有继承性和多态性,而面向过程没有继承性和多态性,所以面向对象优势很明显
2、面向对象的三大特性?分别解释下?
(1)封装:通常认为封装是把数据和操作数据的方法封装起来,对数据的访问只能通过已定义的接口。
(2)继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类/基类),得到继承信息的被称为子类(派生类)。
(3)多态:分为编译时多态(方法重载)和运行时多态(方法重写)。要实现多态需要做两件事:一是子类继承父类并重写父类中的方法,二是用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为。
补充:
1)子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。因为在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象;
2)子类可以拥有自己属性和方法;
3)子类可以用自己的方式实现父类的方法。(重写)
3、JDK、JRE、JVM三者之间的关系?
- JDK(Java Development Kit):是 Java 开发工具包,是整个 Java 的核心,包括了 Java 运行环境 JRE、Java 工具和 Java 基础类库。
- JRE( Java Runtime Environment):是 Java 的运行环境,包含 JVM 标准实现及 Java 核心类库。
- JVM(Java Virtual Machine):是 Java 虚拟机,是整个 Java 实现跨平台的最核心的部分,能够运行以 Java 语言写作的软件程序。所有的 Java 程序会首先被编译为 .class 的类文件,这种类文件可以在虚拟机上执行。
4、重载和重写的区别?
-
方法重载
- 是指同一个类中的多个方法具有相同的名字,但这些方法具有不同的参数列表,即参数的数量或参数类型不能完全相同
- 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。重载Overloading是一个类中多态性的一种表现。
- 重载的时候,**方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。**无法以返回类型作为重载函数的区分标准。
-
方法重写
-
父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。
但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
-
若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
-
-
联系与区别
- 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏替换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
5、Java 中是否可以重写一个 private 或者 static 方法?
- Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。
- Java 中也不可以覆盖 private 的方法,因为 private 修饰的变量和方法只能在当前类中使用, 如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能覆盖。
6、构造方法有哪些特性?
-
名字与类名相同。
-
没有返回值,但不能用 void 声明构造函数。
-
生成类的对象时自动执行,无需调用。
-
若一个类没有声明构造方法,该程序能正确执行吗
构造方法主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的无参构造方法。
7、在 Java 中定义一个不做事且没有参数的构造方法有什么作用?
-
Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。
因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是:在父类里加上一个不做事且没有参数的构造方法。
8、Java 中创建对象的几种方式?
1)使用 new 关键字;
- 这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们还可以调用任意的构造函数(无参的和有参的)。比如:Student student = new Student();
2)使用 Class 类的 newInstance 方法:
- 我们也可以使用Class类的newInstance方法创建对象,这个newInstance方法调用无参的构造器创建对象,如:Student student2 = (Student)Class.forName(“根路径.Student”).newInstance(); 或者:Student stu = Student.class.newInstance();
3)使用 clone() 方法:
无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去,用clone方法创建对象并不会调用任何构造函数。要使用clone方法,我们必须先实现Cloneable接口并实现其定义的clone方法。如:Person person1 = (Person) person.clone();这也是原型模式的应用。
@Test
public void test4() throws InstantiationException, IllegalAccessException, CloneNotSupportedException {
Person person = Person.class.newInstance();
System.out.println(person);
Person person1 = (Person) person.clone();
System.out.println(person1);
}
测试结果:
Person{name='null', age=0}
调用clone()方法复制的对象....
Person{name='null', age=0}
4)反序列化,比如调用 ObjectInputStream 类的 readObject() 方法。
当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。如:ObjectInputStream in = new ObjectInputStream (new FileInputStream(“data.obj”)); Student stu3 = (Student)in.readObject();
9、抽象类和接口有什么区别?
- 抽象类中可以定义构造函数,接口不能定义构造函数;
- 抽象类中可以有抽象方法和具体方法,而接口中只能有抽象方法(public abstract);
- 抽象类中的成员权限可以是 public、默认、protected(抽象类中抽象方法就是为了重写,所以不能被 private 修饰),而接口中的成员只可以是 public(方法默认:public abstrat、成员变量默认:public static final);
- 抽象类中可以包含静态方法,而接口中不可以包含静态方法;
JDK8中的改变:
1、在 JDK1.8中,允许在接口中包含带有具体实现的方法,使用 default 修饰,这类方法就是默认方法。
2、抽象类中可以包含静态方法,在 JDK1.8 之前接口中不能包含静态方法,JDK1.8 以后可以包含。之前不能包含是因为,接口不可以实现方法,只可以定义方法,所以不能使用静态方法(因为静态方法必须实现)。现在可以包含了,只能直接用接口调用静态方法。JDK1.8 仍然不可以包含静态代码块。
10、静态变量和实例变量的区别?
- 静态变量:是被 static 修饰的变量,也称为类变量,它属于类,因此不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;静态变量可以实现让多个对象共享内存。
- 实例变量:属于某一实例,需要先创建对象,然后通过对象才能访问到它。
11、short s1 = 1;s1 = s1 + 1;有什么错?那么 short s1 = 1; s1 += 1;呢?有没有错误?
-
对于 short s1 = 1; s1 = s1 + 1; 来说,在 s1 + 1 运算时会自动提升表达式的类型为 int ,那么将 int 型值赋值给 short 型变量,s1 会出现类型转换错误。
-
对于 short s1 = 1; s1 += 1; 来说,+= 是 Java 语言规定的运算符,Java 编译器会对它进行特殊处理,因此可以正确编译。
12、Integer 和 int 的区别?
1、int 是 Java 的八种基本数据类型之一,而 Integer 是 Java 为 int 类型提供的封装类;
2、int 型变量的默认值是 0,Integer 变量的默认值是 null,这一点说明 Integer 可以区分出未赋值和值为 0 的区分;
3、Integer 变量必须实例化后才可以使用,而 int 不需要。
Integer和int的比较延伸:
1、由于 Integer 变量实际上是对一个 Integer 对象的引用,所以两个通过 new 生成的 Integer 变量永远是不相等的,因为其内存地址是不同的;
2、Integer 变量和 int 变量比较时,只要两个变量的值是相等的,则结果为 true。因为包装类 Integer 和基本数据类型 int 类型进行比较时,Java 会自动拆包装类为 int,然后进行比较,实际上就是两个 int 型变量在进行比较;
3、非 new 生成的 Integer 变量和 new Integer() 生成的变量进行比较时,结果为 false。因为非 new 生成的 Integer 变量指向的是 Java 常量池中的对象,而 new Integer() 生成的变量指向堆中新建的对象,两者在内存中的地址不同;
4、对于两个非 new 生成的 Integer 对象进行比较时,如果两个变量的值在区间 [-128, 127] 之间,则比较结果为 true,否则为 false。Java 在编译 Integer i = 100 时,会编译成 Integer i = Integer.valueOf(100)。
public static Integer valueOf(int i) {
if (i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
从上面的代码中可以看出:Java 对于 [-128, 127] 之间的数会进行缓存,比如:Integer i = 127,会将 127 进行缓存,下次再写 Integer j = 127 的时候,就会直接从缓存中取出,而对于这个区间之外的数就需要 new 了。
-
包装类的缓存:
Boolean:全部缓存 Byte:全部缓存(Byte 的数值范围是 -128~127) Character:<= 127 缓存 Short:-128 — 127 缓存 Long:-128 — 127 缓存 Integer:-128 — 127 缓存 Float:没有缓存 Doulbe:没有缓存
13、装箱和拆箱的区别
自动装箱是 Java 编译器在基本数据类型和对应得包装类之间做的一个转化。比如:把 int 转化成 Integer,double 转化成 Double 等等。反之就是自动拆箱。
原始类型:boolean、char、byte、short、int、long、float、double
封装类型:Boolean、Character、Byte、Short、Integer、Long、Float、Double
14、switch 语句能否作用在 byte 上,能否作用在 long 上,能否作用在 String 上?
在 switch(expr 1) 中,expr1 只能是一个整数表达式或者枚举常量。而整数表达式可以是 int 基本数据类型或者 Integer 包装类型。由于,byte、short、char 都可以隐式转换为 int,所以,这些类型以及这些类型的包装类型也都是可以的。而 long 和 String 类型都不符合 switch 的语法规定,并且不能被隐式的转换为 int 类型,所以,它们不能作用于 switch 语句中。不过,需要注意的是在 JDK1.7 版本之后 switch 就可以作用在 String 上了。
15、final、finally、finzlize的区别
final:用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖、被其修饰的类不可继承;
finally:异常处理语句结构的一部分,表示总是执行;
finallize:Object类的一个方法,在垃圾回收时会调用被回收对象的finalize
16、== 和 equals 的区别?
- = =:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。
- equals 方法:用来比较两个对象的内容是否相等。注意:equals 方法不能用于比较基本数据类型的变量。如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址(很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等)
17、两个对象的 hashCode() 相同,则 equals() 也一定为 true 吗?
两个对象的 hashCode() 相同,equals() 不一定为 true。因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等【散列冲突】
18、为什么重写 equals() 就一定要重写 hashCode() 方法?
这个问题应该是有个前提,就是你需要用到 HashMap、HashSet 等 Java 集合,用不到哈希表的话,其实仅仅重写 equals() 方法也可以。而工作中的场景是常常用到 Java 集合,所以 Java 官方建议重写 equals() 就一定要重写 hashCode() 方法。
对于对象集合的判重,如果一个集合含有 10000 个对象实例,仅仅使用 equals() 方法的话,那么对于一个对象判重就需要比较 10000 次,随着集合规模的增大,时间开销是很大的。但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且在定位到大概存储位置后,后续比较过程中,如果两个对象的 hashCode 不相同,也不再需要调用 equals() 方法,从而大大减少了 equals() 比较次数。
所以从程序实现原理上来讲的话,既需要 equals() 方法,也需要 hashCode() 方法。那么既然重写了 equals(),那么也要重写 hashCode() 方法,以保证两者之间的配合关系。
hashCode()与equals()的相关规定:
1、如果两个对象相等,则 hashCode 一定也是相同的;
2、两个对象相等,对两个对象分别调用 equals 方法都返回 true;
3、两个对象有相同的 hashCode 值,它们也不一定是相等的;
4、因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖;
5、hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
19、& 和 && 的区别?
- Java 中 && 和 & 都是表示与的逻辑运算符,都表示逻辑运输符 and,当两边的表达式都为 true 的时候,整个运算结果才为 true,否则为 false。
- &&:有短路功能,当第一个表达式的值为 false 的时候,则不再计算第二个表达式;
- &:不管第一个表达式结果是否为 true,第二个都会执行。除此之外,& 还可以用作位运算符:当 & 两边的表达式不是 Boolean 类型的时候,& 表示按位操作。
20、Java中的参数传递时传值呢?还是传引用?
Java 的参数是以值传递的形式传入方法中,而不是引用传递
21、如何实现对象的克隆?
1、实现 Cloneable 接口并重写 Object 类中的 clone() 方法;
2、实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。
22、深拷贝和浅拷贝的区别?
1、浅拷贝:拷贝对象和原始对象的引用类型引用同一个对象。浅克隆只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅克隆
2、深拷贝:拷贝对象和原始对象的引用类型引用不同对象。深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝(例:JSON.parse() 和 JSON.stringify(),但是此方法无法复制函数类型)
补充:
深克隆的实现就是在引用类型所在的类实现 Cloneable 接口,并使用 public 访问修饰符重写 clone 方法。
Java 中定义的 clone 没有深浅之分,都是统一的调用 Object 的 clone 方法。为什么会有深克隆的概念?是由于我们在实现的过程中刻意的嵌套了 clone 方法的调用。也就是说深克隆就是在需要克隆的对象类型的类中重新实现克隆方法 clone()
23、什么是 Java 的序列化,如何实现 Java 的序列化?
1、什么是Java的序列化?
对象序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序。从字节流创建对象的相反的过程称为反序列化。而创建的字节流是与平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。序列化是为了解决在对象流进行读写操作时所引发的问题
2、实现Java的序列化?
序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法,只是用于标注该对象是可被序列化的,,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream 对象,接着使用 ObjectOutputStream 对象的 writeObject(Object obj) 方法可以将参数为 obj 的对象写出,要恢复的话则使用输入流
24、什么情况下需要序列化?
(1)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
(2)当你想用套接字在网络上传送对象的时候;
(3)当你想通过 RMI 传输对象的时候。
25、Java 中的动态代理是什么?有哪些应用?
1、动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新功能。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。
2、动态代理的应用:Spring 的 AOP 、加事务、加权限、加日志。
26、static关键字的作用?
(1)静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份;
(2)静态方法:静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字;
(3)静态语句块:静态语句块在类初始化时运行一次;
(4)静态内部类:非静态内部类依赖于外部类的实例,而静态内部类不需要。静态内部类不能访问外部类的非静态的变量和方法;
(5)初始化顺序:静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
27、字节和字符的区别?
字节是存储容量的基本单位;
字符是数字、字母、汉字以及其他语言的各种符号;
1 字节 = 8 个二进制单位,一个字符由一个字节或多个字节的二进制单位组成。
28、final 修饰 StringBuffer 后还可以 append 吗?
可以。final 修饰的是一个引用变量,那么这个引用始终只能指向这个对象,但是这个对象内部的属性是可以变化的。
29、Java 中的 IO 流的分类?说出几个你熟悉的实现类?
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流 和 字符流。
字节流:InputStream/OutputStream 是字节流的抽象类,这两个抽象类又派生了若干子类,不同的子类分别处理不同的操作类型。具体子类如下所示:
字符流:Reader/Writer 是字符的抽象类,这两个抽象类也派生了若干子类,不同的子类分别处理不同的操作类型。
30、字节流和字符流有什么区别?
字节流按 8 位传输,以字节为单位输入输出数据,字符流按 16 位传输,以字符为单位输入输出数据。
但是不管文件读写还是网络发送接收,信息的最小存储单元都是字节。