Java基础

16次阅读

共计 9660 个字符,预计需要花费 25 分钟才能阅读完成。

1、JAVA 中的几种基本数据类型是什么,各自占用多少字节。

2、String 类能被继承吗,为什么

不能。在 Java 中,只要是被定义为 final 的类,也可以说是被 final 修饰的类,就是不能被继承的。

3、String,Stringbuffer,StringBuilder 的区别。

4、ArrayList 和 LinkedList 有什么区别。

简单的区别:
1.ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。(LinkedList 是双向链表,有 next 也有 previous)
2. 对于随机访问 get 和 set,ArrayList 觉得优于 LinkedList,因为 LinkedList 要移动指针。
3. 对于新增和删除操作 add 和 remove,LinedList 比较占优势,因为 ArrayList 要移动数据。

深度的区别:
1.对 ArrayList 和 LinkedList 而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList 而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对 LinkedList 而言,这个开销是统一的,分配一个内部 Entry 对象。

2.在 ArrayList 的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在 LinkedList 的中间插入或删除一个元素的开销是固定的。

3.LinkedList 不支持高效的随机元素访问。

4.ArrayList 的空间浪费主要体现在在 list 列表的结尾预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗相当的空间

5、讲讲类的实例化顺序。

问题:比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字
段,当 new 的时候,他们的执行顺序。

答案:
类加载器实例化时进行的操作步骤(加载–> 连接 -> 初始化)。
父类静态变量、
父类静态代码块、
子类静态变量、
子类静态代码块、
父类非静态变量(父类实例成员变量)、
父类构造函数、
子类非静态变量(子类实例成员变量)、
子类构造函数。

6、用过哪些 Map 类,都有什么区别。

问题:比如 HashMap 是线程安全的吗, 并发下使用的 Map 是什么,他们 内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
答案:
不安全,并发下使用 ConcurrentHashMap。

7、JAVA8 的 ConcurrentHashMap 为什么放弃了分段锁?

原因:通过 JDK 的源码和官方文档看来,他们认为的弃用分段锁的原因由以下几点:
1、加入多个分段锁浪费内存空间。
2、生产环境中,map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
3、为了提高 GC 的效率

既然弃用了分段锁,那么一定由新的线程安全方案,我们来看看源码是怎么解决线程安全的呢?CAS

首先通过 hash 找到对应链表过后,查看是否是第一个 object,如果是,直接用 cas 原则插入,无需加锁,然后如果不是链表第一个 object,则直接用链表第一个 object 加锁,这里加的锁是 synchronized,虽然效率不如 ReentrantLock,但节约了空间,这里会一直用第一个 object 为锁,直到重新计算 map 大小,比如扩容或者操作了第一个 object 为止。

8、ConcurrentHashMap(JDK1.8)为什么要使用 synchronized 而不是如 ReentranLock 这样的可重入锁?

可以从下面几个方面讲述:
锁的粒度
首先锁的粒度并没有变粗,甚至变得更细了。每当扩容一次,ConcurrentHashMap 的并发度就扩大一倍。
Hash 冲突
JDK1.7 中,ConcurrentHashMap 从过二次 hash 的方式(Segment -> HashEntry)能够快速的找到查找的元素。在 1.8 中通过链表加红黑树的形式弥补了 put、get 时的性能差距。
扩容
JDK1.8 中,在 ConcurrentHashmap 进行扩容时,其他线程可以通过检测数组中的节点决定是否对这条链表(红黑树)进行扩容,减小了扩容的粒度,提高了扩容的效率。

为什么是 synchronized,而不是可重入锁

  1. 减少内存开销

假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承 AQS 来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。

  1. 获得 JVM 的支持

可重入锁毕竟是 API 这个级别的,后续的性能优化空间很小。
synchronized 则是 JVM 直接支持的,JVM 能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得 synchronized 能够随着 JDK 版本的升级而不改动代码的前提下获得性能上的提升。

9、有没有有顺序的 Map 实现类,如果有,他们是怎么保证有序的。

Hashmap 和 Hashtable 都不是有序的。
TreeMap 和 LinkedHashmap 都是有序的。(TreeMap 默认是 key 升序,LinkedHashmap 默认是数据插入顺序)
TreeMap 是基于比较器 Comparator 来实现有序的。
LinkedHashmap 是基于链表来实现数据插入有序的。

https://www.iteye.com/blog/uu…

10、抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么, 类可以实现多个接口 么。

区别:
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现。abstract void abc(); 不能写成 abstract void abc(){}。
7、抽象类里可以没有抽象方法
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。

类不能继承多个类
接口可以继承多个接口
类可以实现多个接口

11、继承和聚合的区别在哪。

继承
指的是一个类继承另外的一个类的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在 Java 中此类关系通过关键字 extends 明确标识。

聚合

聚合体现的是整体与部分、拥有的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期;比如计算机与 CPU、公司与员工的关系等;

12、IO 模型有哪些,讲讲你理解的 nio,他和 bio,aio 的区别是啥,谈谈 reactor 模型。

各 IO 的区别:

reactor 是什么?

  1. 事件驱动
  2. 可以处理一个或多个输入源
  3. 通过 Service Handle 同步的将输入事件采用多路复用分发给相应的 Request Handler(一个或多个)处理

13、反射的原理,反射创建类实例的三种方式是什么。
// 创建 Class 对象的方式一:(对象.getClass()),获取类中的字节码文件
Class class1 = p1.getClass();

// 创建 Class 对象的方式二:(类.class: 需要输入一个明确的类,任意一个类型都有一个静态的 class 属性)
Class class3 = Person.class;

// 创建 Class 对象的方式三:(forName(): 传入时只需要以字符串的方式传入即可)
// 通过 Class 类的一个 forName(String className)静态方法返回一个 Class 对象,className 必须是全路径名称;//Class.forName()有异常:ClassNotFoundException
Class   class4 = Class.forName("cn.xbmchina.Person");
        

https://blog.csdn.net/LianXu3…

14、反射中,Class.forName 和 ClassLoader 区别。

Class.forName(className)方法,内部实际调用的方法是 Class.forName(className,true,classloader);
第 2 个 boolean 参数表示类是否需要初始化,Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的 static 块代码执行,static 参数也也会被再次初始化。
ClassLoader.loadClass(className)方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);
第 2 个 boolean 参数,表示目标对象是否进行链接,false 表示不进行链接,由上面介绍可以,
不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行

https://www.cnblogs.com/zabul…

15、描述动态代理的几种实现方式,分别说出相应的优缺点。

原理区别:

java 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。

而 cglib 动态代理是利用 asm 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。

1、如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP 
2、如果目标对象实现了接口,可以强制使用 CGLIB 实现 AOP 

3、如果目标对象没有实现了接口,必须采用 CGLIB 库,spring 会自动在 JDK 动态代理和 CGLIB 之间转换

如何强制使用 CGLIB 实现 AOP?
(1)添加 CGLIB 库,SPRING_HOME/cglib/*.jar
(2)在 spring 配置文件中加入 <aop:aspectj-autoproxy proxy-target-class=”true”/>

JDK 动态代理和 CGLIB 字节码生成的区别?
(1)JDK 动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
   因为是继承,所以该类或方法最好不要声明成 final 

https://blog.csdn.net/qq_2300…

16、final 的用途。

1、被 final 修饰的类不可以被继承
2、被 final 修饰的方法不可以被重写
3、被 final 修饰的变量不可以被改变(切记不可变的是变量的引用而非引用指向对象的内容。)
4、被 final 修饰的方法,JVM 会尝试为之寻求内联,这对于提升 Java 的效率是非常重要的。因此,假如能确定方法不会被继承,那么尽量将方法定义为 final 的,具体参见运行期优化技术的方法内联部分
5、被 final 修饰的常量,在编译阶段会存入调用类的常量池中,具体参见类加载机制最后部分和 Java 内存区域

https://www.cnblogs.com/swiss…

17、写出三种单例模式实现。

1 饿汉式

public class EagerSingleton {
static {System.out.println("EagerSingleton 被加载");
}

// 私有化构造方法,限制直接构造,只能调用 getInstance() 方法获取单例对象
private EagerSingleton(){}  



private static final EagerSingleton eagerSingleton=new EagerSingleton(); // 私有化静态 final 成员,类加载直接生成单例对象,比较占用内存 
public static EagerSingleton getInstance(){  // 提供对外的公共 api 获取单例对象
        return eagerSingleton;
}

}

总结:饿汉式单例的特点:饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。

2 懒汉式

public class LazySingleton {
static {System.out.println("LazySingleton 被加载");
}

private LazySingleton(){} // 私有化构造方法,限制直接构造,只能调用 getInstance() 方法获取单例对象
private static LazySingleton lazySingleton=null;// 静态域初始化为 null,为的是需要时再创建,避免像饿汉式那样占用内存
public static LazySingleton getInstance(){// 提供对外的公共 api 获取单例对象
    if(lazySingleton==null){synchronized (LazySingleton.class){ // 在 getInstance 中做了两次 null 检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
        if(lazySingleton==null){lazySingleton = new LazySingleton();
          }
      }
    }
    return lazySingleton;
  }

}

总结:有同步锁的性能消耗

3 静态内部类实现

public class IoDHSingleton {
static {System.out.println("IoDHSingleton 被加载");
}

private IoDHSingleton(){} // 私有化构造方法,限制直接构造,只能调用 getInstance() 方法获取单例对象


public static IoDHSingleton getInstance(){// 提供对外的公共 api 获取单例对象
// 当 getInstance 方法第一次被调用的时候,它第一次读取 HolderClass.ioDHSingleton,内部类 HolderClass 类得到初始化;// 而这个类在装载并被初始化的时候,会初始化它的静态域,从而创 ioDHSingleton 的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。return HolderClass.ioDHSingleton; 
}

  private static class HolderClass{
    static {System.out.println("HolderClass 被加载");
    }
    private static IoDHSingleton ioDHSingleton = new IoDHSingleton();}

 // 防止反序列化获取多个对象的漏洞  

  private Object readResolve() throws ObjectStreamException {return  HolderClass.ioDHSingleton;}  
}

这个模式的优势在于,getInstance 方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

考虑反射:
由于在调用 SingletonHolder.instance 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。
所以这种形式,很好的避免了反射入侵。
考虑多线程:
由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。
总结:
优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。

https://www.cnblogs.com/ngy02…

18、如何在父类中为子类自动完成所有的 hashcode 和 equals 实现?这么做有何优劣。

https://www.iteye.com/blog/ja…

19、请结合 OO 设计理念,谈谈访问修饰符 public、private、protected、default 在应用设计中的作用。

访问修饰符,主要标示修饰块的作用域,方便隔离防护。

public:Java 语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。

private: Java 语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问

protect: 介于 public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。

default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问。

20、深拷贝和浅拷贝区别。

浅拷贝(Shallow Copy):

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

深拷贝:

首先介绍对象图的概念。设想一下,一个类有一个对象,其成员变量中又有一个对象,该对象指向另一个对象,另一个对象又指向另一个对象,直到一个确定的实例。这就形成了对象图。那么,对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!

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

https://www.cnblogs.com/shaki…

21、数组和链表数据结构描述,各自的时间复杂度。

数组和链表的区别:
1、从逻辑结构角度来看:
数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
2、数组元素在栈区,链表元素在堆区;
3、从内存存储角度来看:
(静态)数组从栈中分配空间, 对于程序员方便快速, 但自由度小。
链表从堆中分配空间, 自由度大但申请管理比较麻烦。
数组利用下标定位,时间复杂度为 O(1),链表定位元素时间复杂度 O(n);
数组插入或删除元素的时间复杂度 O(n),链表的时间复杂度 O(1)。

22、error 和 exception 的区别,CheckedException,RuntimeException 的区别。

23、在自己的代码中,如果创建一个 java.lang.String 类,这个类是否可以被类加载器加载?为什么。


加载过程中会先检查类是否被已加载,检查顺序是自底向上,从 Custom ClassLoader 到 BootStrap ClassLoader 逐层检查,只要某个 classloader 已加载就视为已加载此类,保证此类只所有 ClassLoader 加载一次。而加载的顺序是自顶向下,也就是说当发现这个类没有的时候会先去让自己的父类去加载,父类没有再让儿子去加载,那么在这个例子中我们自己写的 String 应该是被 Bootstrap ClassLoader 加载了,所以 App ClassLoader 就不会再去加载我们写的 String 类了,导致我们写的 String 类是没有被加载的。

https://blog.csdn.net/u013206…

24、说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需要重新实现这两个方法。

对于 equals()与 hashcode(),比较通用的规则:
①两个 obj,如果 equals()相等,hashCode()一定相等
②两个 obj,如果 hashCode()相等,equals()不一定相等

25、在 jdk1.5 中,引入了泛型,泛型的存在是用来解决什么问题。

面向对象的转型只会发生在具有继承关系的父子类中(接口也是继承的一种)
向上转型:其核心目的在于参数的统一上,根本不需要强制类型转换。
向下转型:是为了操作子类定义的特殊功能,需要强制类型转换,可是现在存在的问题是:向下转型其实是一种非常不安全的操作,以为编译的时候,程序不会报错,而在运行的时候会报错,这就是传说中的—迷之报错。

不过呢,在 JDK1.5 之后,新增加了泛型的技术,这就将上述向下转型的问题消灭在了萌芽之中。
泛型的核心意义在于:类在进行定义的时候可以使用一个标记,此标记就表示类中属性或者方法以及参数的类型,标记在使用的时候,才会去动态的设置类型。

26、Java 中的 HashSet 内部是如何工作的。

HashSet 的内部采用 HashMap 来实现。由于 Map 需要 key 和 value,所以 HashSet 中所有 key 的都有一个默认 value。类似于        HashMap,HashSet 不允许重复的 key,只允许有一个 null key,意思就是 HashSet 中只允许存储一个 null 对象。

https://blog.csdn.net/qq_3257…

27、什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。

什么是序列化?
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化

什么情况下需要序列化?
当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
当你想用套接字在网络上传送对象的时候;
当你想通过 RMI 传输对象的时候;

如何实现序列化?
实现 Serializable 接口即可

注意事项:
transient 修饰的属性,是不会被序列化的
静态 static 的属性,他不序列化。
实现这个 Serializable 接口的时候,一定要给这个 serialVersionUID 赋值

关于 serialVersionUID 的描述:
序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为“serialVersionUID”的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID

https://blog.csdn.net/riemann…

28、java8 的新特性。

https://www.cnblogs.com/frank…

正文完
 0

java基础

16次阅读

共计 969 个字符,预计需要花费 3 分钟才能阅读完成。

是一系列 的合集

java.lang包中的所有类都会在编译时候自动导入,不需要代码中import 引入

final关键字表示无法更改某个变量一旦初始化它,例如: final double PI = 3.1415
默认:
(1) 变量带有 final 表示 常量
(2) 常量一般使用大写方式,单词中间使用 ’-‘ 分隔,例如: PI_MATH

java 的函数名使用驼峰命名法,

public 表示该函数可以在其他类中调用

字符串之间的比较使用equals(), 不能使用 == 或者!=

增量开发:
(1)从一个程序开始,做一些小的渐进的变动,一旦发生 error,可以迅速定位;
(2)使用变量保存中间计算值,可以使用 println or debugger;(脚手架)
(3)当程序可以正常运行,将多个小变动更新为更加合适的变动;

封装和泛化:
(1)在 main 函数或者其他函数中,写简单的代码测试;
(2)假设该函数可用,将其封装成新的函数;(封装)
(3)将新的函数中,定值替换为传参(泛化)

Arrays 数组的内置方法:

Arrays.toString(数组 a)
Arrays.copyOf(数组 a, 复制位数)

Arrays 数组的内置属性:

a.length 数组长度

java 中 不都是对象,int, double, boolean, char 称之为基本类型

String 类型的内置属性和方法:

string_a.charAt(0)
string_a.toUpperCase()
string_a.replace(“src”, “dst”)
string_a.substring()
string_a.indexOf(‘a’)
string_a.equals(string_b)
string_a.compareTo(sting_b)
string_a.format()

String 类型不可变

包装类,对于基本类型,java.lang提供了对应的包装类,其他定义了内置属性和内置方法

UML: 统一建模语言 Unified Modeling Language,定义了标准的方法设计类

private 表示该变量是一个私有实例变量,即只可以在所属类中调用;(信息隐藏)

实例方法又称为非静态方法,

static 表示变量或者函数是共享的

递归调用的常见错误:

(1)没有停止条件;
(2)停止条件永远无法到达。

自上而下开发方式:
先写伪代码 pseudocode,再写实际代码

正文完
 0