Java基础

583

==Java基础==


Java语法糖有哪些?

switch支持String与枚举、泛型、自动装箱与拆箱、方法变长参数、枚举、内部类、条件编译、断言、增强for循环、数字字面量、优雅关闭try-with-resource、Lambda表达式。


Java8新特性

  • Lambda表达式
  • 函数式接口
  • Optionals
  • Stream流
  • Date API

Java和C++的区别?

  • java是跨平台的,每个操作系统安装对应的JVM,java在JVM上运行,感觉不到底层的差异,所以可以一次编译,到处运行。而C++做不到这一点。
  • java没有指针,由JVM自动为我们管理内存以及垃圾回收工作,而C++需要手动释放内存,忘记释放容易出现内存泄漏问题。
  • java的类是单继承的,而C++可以类可以多继承。

java有了基本数据类型还需要包装类?

在 Java 中,new一个对象是存储在里的,然后通过栈中的引用来使用这些对象;所以,对于对象本身来说是比较消耗资源的。对于经常用到的类型,如 int 等,如果我们每次使用这种变量的时候都需要 new 一个 Java 对象的话,就会比较笨重。所以,和 C++ 一样,Java 提供了基本数据类型,这种数据的变量不需要使用 new 创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。

Java是一个面向对象语言,基本数据类型并不具有对象的性质,为了让基本数据类型也具有对象的特征,就出现了包装类型,它相当于将基本数据类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

另外,当需要往ArrayList,HashMap等集合中放元素时,像int,double这种基本数据类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了。


抽象类和接口的区别?

先简单介绍下抽象类, 含有抽象方法的类叫做抽象类(当然,没有抽象类中也可以没有抽象方法,但是这完全没有意义,不定义抽象方法,为啥还要定义成抽象类)。

  • 抽象类可以有非抽象方法,而接口中只能有抽象方法,所以接口比抽象类更加抽象;
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
  • 抽象类可以有构造方法,接口中不能有构造方法。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
  • 接口的设计目的,是对类的行为进行约束;而抽象类的设计目的,是为了代码复用。

简述 == 与 equals()的区别?

== : 它的作用是判断两个对象是否相等。(基本数据类型比较的是,引用数据类型比较的是内存地址)。 equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

  • case 1:没有重写 equals() 方法,等价于通过“==”比较这两个对象。
  • case 2:重写了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 (:equals()在使用的时候容易出现空指针错误,所以当一个对象与一个常量比较时,建议将常量写在前面。)

简述hashcode()方法的理解?

hashCode()是Object类的一个方法,也就是说每个对象都有这个方法,它的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()在结合散列表中才有用,在其它情况下没用。归根结底就是为了提高程序的效率才实现了 hashcode() 方法。程序先进行 hashcode 的比较,如果不同,那没就不必在进行 equals 的比较了,这样就大大减少了 equals 比较的次数,这对比需要比较的数量很大的效率提高是很明显的。


为什么重写equals()的时候必须重写hashcode()?

equals() 方法和 hashcode() 方法间的关系是这样的:

  1. 如果两个对象equals相等,则 hashcode 一定也是相等的
  2. 两个对象有相同的 hashcode 值,它们equals不一定是相等的

判断对象是否相等的时候,先根据hashcode进行判断,相等的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。 在Java中的一些容器中,不允许存在两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals(),而不重写hashcode(),Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。

例子:一个student类,如果重写equals方法使得属性全部一样的对象是同一个对象的话,那么此时如果不重写hashcode()的话,那他们的hashcode值是不一样的,根据 hashcode 的规则,两个对象相等其 hashnode值一定要相等,矛盾就这样产生了。所以我们需要重写hashcode()方法,使得他们的hash值也一样。


简述对clone()方法的理解?

clone()在堆上分配内存,分配的内存和源对象的内存大小相同,然后再使用源对象中对应的各个域,填充新对象的域, 填充完成之后,clone()方法返回一个新的相同的对象,同样可以把这个新对象的引用发布到外部。如果,想要对该对象进行处理,又想保留原来数据进行接下来的操作,clone()就很方便。


简述浅拷贝与深拷贝的区别?

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用

浅拷贝:①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

深拷贝:不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!

简单地说,深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。


简述深拷贝的实现方式?

  1. 实现Cloneable接口,并且重写Object类中的clone()方法(对于类中所有的引用类型的成员变量都需要实现Cloneable接口)。
  2. 实现Serializable接口序列化。(对于类中所有的引用类型的成员变量都需要实现Serializable接口)

Arrays.copyOf与System.arraycopy的区别?

从两种拷贝方式的定义来看:System.arraycopy()使用时必须有原数组和目标数组,Arrays.copyOf()使用时只需要有原数组即可。 从两种拷贝方式的底层实现来看: System.arraycopy()是用c或c++实现的,Arrays.copyOf()是在方法中重新创建了一个数组,并调用System.arraycopy()进行拷贝。 两种拷贝方式的效率分析:由于Arrays.copyOf()不但创建了新的数组而且最终还是调用System.arraycopy(),所以System.arraycopy()的效率高于Arrays.copyOf()。


Java多态

Java多态表示一个对象具有多种状态,具体表现为父类的引用指向子类的实例。简单的说就是一个类实例的相同方法在不同情形有不同表现形式。 满足三个条件:继承、重写、父类引用指向子类对象。 多态特点:

  • 对象类型和引用类型之间具有继承(类)/ 实现(接口)的关系;
  • 方法具有多态性,属性不具有多态性;
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定(动态绑定);
  • 多态不能调用“只在子类存在但在父类不存在”的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

弊端:不能使用子类特有的成员属性和子类特有的成员方法。


重载VS重写

  1. 重载是一个编译期概念,遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法;重写是一个运行期概念,遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法。

  2. 因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。(注:严格来说,重载是编译时多态,即静态多态。但是,Java中提到的多态,在不特别说明的情况下都指动态多态)


Java泛型类型擦除

Java的泛型是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会被擦除,这个过程成为类型擦除。大白话就是:泛型信息在java代码里面有,而在生成的字节码里面就没有了。之所以这么做主要是为了向下兼容老的java版本。因为 JVM 中没有泛型概念的,因此在编译期间所有的泛型信息都会被擦除,类型被擦除后,在字节码中会用原始类型代替,一般无限定的泛型用Object来代替,如果泛型有限定,原始类型就用第一个边界的类型变量类替换。例如 List<String> 类型,运行时它只是 List,并不体现 String 类型。

问题:

  • 先检查,再编译以及编译的对象和引用传递问题
  • 自动类型转换
  • 类型擦除与多态的冲突和解决方法——桥方法
  • 泛型类型变量不能是基本数据类型
  • 编译时集合的instanceof
  • 泛型在静态方法和静态类中的问题

www.cnblogs.com/wuqinglong/…


java中synchronized修饰静态方法和非静态方法有什么区别

synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,俗称“对象锁”。 synchronized修饰静态方法,实际上是对该类对象加锁,俗称“类锁”。


String,StringBuffer,StringBuilder的对比?

String:字符串常量,final修饰不可被继承,是不可变对象,一旦创建,就不能修改它的值。 StringBuffer:字符串变量,final修饰不可被继承,对象内容可以被修改,是线程安全的。效率较低。 StringBuilder:字符串变量,final修饰不可被继承,对象内容可以被修改,不是线程安全的。效率高。 三者在字符串连接操作的执行速度方面的比较:StringBuilder > StringBuffer > String ①Stringbuilder的append()的方式:自始至终只创建过一个StringBuilder对象。 ②使用String的字符串拼接方式:内存中会创建较多的StringBuilder对象和String对象,内存占用更大。 **注:**在实际开发中,如果基本确定要前前后后添加的字符串长度不高于某个限定值的情况下,建议使用带参构造器。


API和SPI

在“调用方”和“实现方”之间需要引入“接口”。

API:当接口属于实现方,即实现方提供了接口,并且完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现,这就是API。

  • 概念上更接近实现方
  • 组织上位于实现方所在的包中
  • 实现和接口在一个包中

SPI:当接口属于调用方,即调用方来制定接口,实现方来针对接口来做不同的实现,调用方来选择自己需要的实现方的实现,这就是SPI。

  • 概念上更依赖调用方
  • 组织上位于调用方所在的包中
  • 实现位于独立的包中(也可认为在提供方中)

SPI 本质:将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。


SPI实现原理

  1. 应用程序调用ServiceLoader.load(),ServiceLoader.load()内先创建一个新的ServiceLoader,并实例化ServiceLoader中的五个成员变量,包括:

    • service(表示正在加载的服务的类或接口)
    • loader(ClassLoader类型,用于定位,加载和实例化providers的类加载器)
    • acc(AccessControlContext类型,创建ServiceLoader时采用的访问控制上下文)
    • providers(LinkedHashMap类型,用于缓存加载成功的类,按实例化的顺序排列)
    • lookupIterator(实现迭代器功能,懒查找迭代器)
  2. 应用程序通过迭代器接口获取对象实例,ServiceLoader先判断providers中是否有缓存实例对象,如果有缓存,直接返回。 如果没有缓存,执行类的装载:

    • 读取jar包下**META-INF/services/**下的配置文件,获得所有能被实例化的类的全限定名
    • 通过反射方法Class.forName()加载类对象,并用instance()方法将类实例化
    • 把实例化后的类缓存到providers中
    • 然后返回实例对象。

Arrays.asList()

Arrays.asList()将数组转换为集合后,底层其实还是数组。并且Arrays.asList()是泛型方法,传入的对象必须是对象数组

**注:**使用Arrays.asList()把数组转换为集合时,不能使用修改集合的相关方法,会抛出异常UnsupportedOperationExecption异常。因为asList()只是返回一个内部类,而这个内部类并没有实现集合的修改方法。


Exception和Error


常见的Exception

  • ClassCastException(类转换异常)
  • IndexOutOfBoundsException(索引越界异常)
  • ArithmeticException(算术条件异常)
  • ArrayIndexOutOfBoundsException(数组索引越界异常)
  • NullPointerException(空指针)
  • NoSuchMethodException(方法不存在异常)
  • UnsupportedOperationExecption(不支持的操作)
  • NoSuchFieldException(属性不存在异常)
  • NumberFormatException(数字格式异常)
  • ArrayStoreException(数据存储异常,操作数组时类型不一致)

常见的Error

  • OutOfMemoryError:内存不足错误
  • StackOverflowError:堆栈溢出错误
  • NoClassDefFoundError:类定义错误
  • InstantiationError:实例化错误

NoClassDefFoundError 和 ClassNotFoundException 有什么区别

在类的加载过程中, JVM 或者 ClassLoader 无法找到对应的类时,都可能会引起这两种异常/错误,由于不同的 ClassLoader 会从不同的地方加载类,有时是错误的 CLASSPATH 类路径导致的这类错误,有时是某个库的 jar 包缺失引发这类错误。

  • NoClassDefFoundError:表示这个类在编译时期存在,但是 JVM 认为应用程序运行时找不到相应的引用,就会抛出该错误,所以是是 JVM 引起的错误,是 unchecked。

  • ClassNotFoundException:当尝试在运行时使用反射加载类时,找不到相应的类的时候,就会抛出ClassNotFoundException,是checked,需要 try-catch 语句块或者 try-finally 语句块包围,否则会导致编译错误。 。

简而言之,ClassNotFoundException 和 NoClassDefFoundError 都是由 CLASSPATH 中缺少类引起的