Java基础剖析

1、Java和C++比较

1)C++是编译型语言(首先将源代码编译生成机器语言,再由机器运行机器码),执行速度快、效率高;依赖编译器、跨平台性差些。

2)Java是解释型语言(源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。),执行速度慢、效率低;依赖解释器、跨平台性好。

 

2、面向过程和面向对象

1)面向过程把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。

2)面向对象将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。

 

3、为什么Java不支持多继承?

因为如果要实现多继承,就会像C++中一样,存在菱形继承的问题,C++为了解决菱形继承问题,又引入了虚继承。因为支持多继承,引入了菱形继承问题,又因为要解决菱形继承问题,

引入了虚继承。而经过分析,人们发现我们其实真正想要使用多继承的情况并不多。所以,在 Java 中,不允许“多继承”,即一个类不允许继承多个父类。

假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C。这时候,因为D同时继承了B和C,并且B和C又同时继承了A,那么,D中就会因为多重继承,继承到两份来自A中的属性和方法。
这时候,在使用D的时候,如果想要调用一个定义在A中的方法时,就会出现歧义。而C++为了解决菱形继承问题,又引入了虚继承。
因为支持多继承,引入了菱形继承问题,又因为要解决菱形继承问题,引入了虚继承。而经过分析,人们发现我们其实真正想要使用多继承的情况并不多。
所以,在 Java 中,不允许“声明多继承”,即一个类不允许继承多个父类。但是 Java 允许“实现多继承”,即一个类可以实现多个接口,一个接口也可以继承多个父接口。由于接口只允许有方法声明而不允许有方法实现(Java 8之前),这就避免了 C++ 中多继承的歧义问题。

 

4、接口和抽象类区别

1)接口和抽象类,最明显的区别就是接口只是定义了一些方法而已,在不考虑Java8中default方法情况下,接口中是没有实现的代码的。

2)抽象类中的抽象方法可以有public、protected和default这些修饰符,而接口中默认修饰符是public。不可以使用其它修饰符。

接口主要用于制定规范,因为我们提倡也经常使用的都是面向接口编程。而抽象类主要目的是为了复用,比较典型的就是模板方法模式。

 

5、重载和重写的区别

1)重载是就是函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。

2)重写指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法。

3)重载是一个编译期概念、重写是一个运行期间概念。

4)重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法。

5)重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法。

 

6、基本类型和包装类型的区别

1)默认值不同,基本类型的默认值为0, false或\u0000等,包装类默认为null

2)初始化方式不同,一个需要new,一个不需要

3)存储方式不同,基本类型保存在栈上,包装类对象保存在堆上(成员变量的话,在不考虑JIT优化【JIT(Just-In-Time)优化是一种在运行时对代码进行优化的技术。

它通常用于解释型语言或动态编译语言中,以提高代码的执行效率。】的栈上分配时,都是随着对象一起保存在堆上的)

 

7、自动拆装箱原理

1)自动装箱都是通过包装类的valueOf()方法来实现的

2)自动拆箱都是通过包装类对象的xxxValue()来实现的

//自动装箱
Integer i = 10;
//自动拆箱
int a = i;

 

8、自动拆装箱与缓存

Integer i1 = 10;
Integer i2 = 10;
System.out.println(i1==i2)


Integer i3 = 200;
Integer i4 = 200;
System.out.println(i3==i4)

在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。

我们只需要知道,当需要进行自动装箱时,如果数字在-128至127之间时,会直接使用缓存中的对象,而不是重新创建一个对象。

后来在Java 6中,可以通过java.lang.Integer.IntegerCache.high设置最大值。

 

9、浮点数为什么不能表示金额?

因为浮点数只是近似值,并不是精确值,所以不能用来表示金额。否则会有精度丢失。

 

10、BigDecimal等值比较,为什么要用compareTo,而不能用equals方法?

equals方法会比较两部分内容,分别是值(value)和标度(scale),而compareTo只比较值(value)

 

11、BigDecimal(double)和BigDecimal(String)

1)使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是正好等于0.1的。

而是0.1000000000000000055511151231257827021181583404541015625。这是因为double自身表示的只是一个近似值。

2)对于BigDecimal(String) ,当我们使用new BigDecimal("0.1")创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。

 

 

 

12、String的"+"是如何实现的?

String a = "hello";
String b = "girl";
String c = a+b;

反编译后的内容如下

String a = "hello";
String b = "girl";
String c = (new StringBuiler).append(a).append(b).toString

所以,不要在for循环中使用+拼接字符串,不然每次都new StringBuilder,而频繁的新建对象不仅仅会耗费时间,还会造成内存资源的浪费。

 

13、String a = "ab"; String b = "a" + "b"; a == b 吗?

1)在Java中,对于字符串使用==比较的是字符串对象的引用地址是否相同。

2)因为a和b都是由字面量组成的字符串,它们的引用地址在编译时就已经确定了,并且在编译之后,会把字面量直接合在一起。因此,a == b的结果为true,因为它们指向的是同一个字符串对象。

 

13、Unicode和UTF-8有啥关系?

Unicode是一套通用的字符集,包含世界上的大部分文字,Unicode虽然统一了全世界字符的编码,但没有规定如何存储。

因为如果Unicode统一规定,每个符号就要用三个或四个字节表示,因为字符太多,只能用这么多字节才能表示完全。一旦这么规定,那么每个英文字母前都必然有二到三个字节是0,
因为所有英文字母在ASCII中都有,都可以用一个字节表示,剩余字节位置就要补充0。如果这样,文本文件的大小会因此大出二三倍,这对于存储来说是极大的浪费。

为了解决这个问题,就出现了一些中间格式的字符集,他们被称为通用转换格式,即UTF(Unicode Transformation Format)。常见的UTF格式有:UTF-7, UTF-7.5, UTF-8,UTF-16, 以及 UTF-32。

1)UTF-8 使用一至四个字节为每个字符编码

2)UTF-16 使用二或四个字节为每个字符编码

3)UTF-32 使用四个字节为每个字符编码

 

14、有了UTF-8,为什么要出现GBK?

1)对于常用的汉字,在UTF-8中采用3字节进行编码,但是如果有一种只包含中文和ASCII的编码的话,就不需要使用3个字节,可能2个字节就够了。

2)对于大部分网站来说,基本都是只服务一个国家或者地区的,比如一个中国的网站,一般会出现简体字和繁体字以及一些英文字符,很少会出现日语或者韩文的。

3)也是出于这样的考虑,中国国家标准总局于1981年制定并实施了 GB 2312-80 编码,即中华人民共和国国家标准简体中文字符集。后来厂商微软利用GB 2312-80未使用的编码空间,收录GB 13000.1-93全部字符制定了GBK编码。

 

15、为什么会出现乱码?

1)文件里面的内容归根到底都是有0101组成的,至于0101的二进制码如何转成人们可以理解的字符串,则是需要通过规定好的字符编码标准进行转换才可以。

2)我们把一串中文字符通过UTF-8进行编码传输给别人,别人拿到这串文字之后,通过GBK进行解码,得到的内容就会是“锟届瀿锟斤拷雮傡锟斤拷直锟斤拷锟”,这就是乱码。

 

16、Lambda表达式是如何实现的?

public static void main(String... args) {
    List<String> strList = CollUtil.newArrayList("test1","test2")
    strList = strList.stream().filter(string -> string.contains("test1")).collect(Collectors.toList());
    strList.forEach( s -> System.out.println(s) );
}    

反编译代码如下:

public static void main(String... args) {
    List<String> strList = CollUtil.newArrayList("test1","test2")
    strList  = strList.stream().filter((Predicate<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, 
          lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList());
   strList.forEach((Consumer<Object>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)()); } private static /* synthetic */ void lambda$main$1(Object s) {   System.println.out(s); } private static /* synthetic */ boolean lambda$main$0(String string) { return string.contains("test1"); }

两个lambda表达式分别调用了lambda$main$1和lambda$main$0的方法,所以,lambda表达式的实现其实是依赖了一些底层的api,在编译阶段,编译器会把lambda表达式进行解糖,转换成调用内部api的方式。

 

posted @ 2023-10-18 16:46  家乐先生  阅读(23)  评论(0)    收藏  举报