Java面试_se基础

本文详细梳理了Java面试中常见的核心知识点,涵盖了基础语法、数据类型、异常处理、集合框架、多线程、IO流、反射机制、注解、设计模式等多个方面。通过对八大基本数据类型、装箱拆箱、final关键字、比较运算符、hashcode()与equals()的关系、以及构造方法、内部类、接口和抽象类等概念的深入解析,展示了Java语言的精髓。此外,还重点讨论了集合框架中的ArrayList、HashMap、HashSet、TreeSet的实现原理、线程安全性以及遍历方式,以及如何在面试中应对相关问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、八大基本数据类型

byte,short,int,long,float,double,char,boolean
银行不使用浮点型,float是有限的离散的,要使用BigDecimal计算
在这里插入图片描述

2、拆装箱问题(强制类型转化内存溢出问题)

装箱:基本类型包装成引用类型
拆箱:将包装类型转换为基本数据类型;int和Integer区别
Java5开始引入了自动装拆箱的机制,在-128~127之间自动装箱时不会new新的integer对象,而是直接引用常量池中的integer对象,超过范围为false

3、常量和变量

命名:以美元,下划线,子母开头,区分大小写,可包含数字(不可以以数字开头)、
Final修饰的为常量,常量名一般大写

4、final 有什么用?

  • 用于修饰类、属性和方法;
  • 被final修饰的类不可以被继承
  • 被final修饰的方法不可以被重写
  • 被final修饰的变量不可以被改变

5、==和equals

  • 断两个对象是不是同一个对象。对于基本数据类型来说比较的是值,对于引用数据类型==比较的是内存地址
  • equals():它的作用也是判断两个对象是否相等。它一般有两种使用情况①类没有覆盖equals()方法,通过equals()比较该类的两个对象时,相当于==比较这两个对象②类覆盖了equals()方法,一般,我们都覆盖equals()方法,来判断两个对象的内容是否相等

6、hashcode()与equals()

  • 都是object的方法
  • 2个对象的hashcode()相等,equals()不一定相等;
  • 2个对象的equals()相等,hashcode()必然相等。
  • 重写equals()必须重写hashcode(),因为在Java一些容器中,不允许有2个完全的相同的对象,在插入的时候,如果判断相同则会覆盖,如果没有重写hashcode(),object中hashcode()是根据对象的存储地址形成的哈希值,这时就可能因为没有重写造成相同的对象,散列到不同的位置而造成对象不能覆盖的问题。

7、值传递

Java 语言的方法调用只支持参数的值传递。

8、break,continue,return

  • break跳出总上一层循环,不再执行循环(结束当前的循环体)
  • continue跳出本次循环,继续执行下次循环(结束正在执行的循环进入下一个循环条件)
  • return程序返回,不再执行下面的代码(结束当前的方法直接返回)

跳出多重循环体,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break语句,即可跳出多重循环体。带标签的continue,相当于goto,但现在不再使用。

9、重载 重写

  • 重载是在一个类中方法名相同,参数类型,个数,顺序不同,与返回值与修饰符无关。
  • 重写是运行时的多态,他是在子类继承父类,重写父类中的方法。
  • 构造器不能被重写,但可以被重载

10、数组的定义

Int[] arr=new int[3];
Int[] arr=new int[]{1,2,3,4,5};
Int[] arr={1,2,3,4,5};

11、面向对象 面向过程

面向过程是具体化的,流程化的,解决一个问题,需要一步一步的分析,一步一步的实现。面向对象是模型化的,相当于抽象成一个盒子,你需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,使用的人不需要理会。而面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装

面向对象三大特性:封装,继承,多态

  • 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,提高复用性
  • 继承:1.子类拥有父类非private的属性和方法。2.子类可以拥有自己属性和方法,即子类可以对父类进行扩展。3.子类可以用自己的方式实现父类的方法。通过使用继承可以提高代码复用性
  • 多态:多态分为运行时多态和编译时多态。编译时多态就是我们说的一个重载,运行时多态是体现在子类继承父类。

向上转型,子类对象转换为父类类型,Animal animal=new Cat();这个时候animal这个引用调用的方法是子类方法。向上转型时,子类单独定义的方法会丢失。子类引用不能指向父类对象。Cat c=(Cat)new Animal()这样是不行的。
向下转型,把父类对象指向的是子类对象(也就是说在向下转型的时候需要先向上转型),向下转型只能转型为本类对象,就是说得存在继承关系的。比如Animal a=new Cat();Cat c=((Cat)a);

12、OOP五大基本原则:

  • 单一职责原则SRP(Single Responsibility Principle):类的功能要单一
  • 开放封闭原则OCP(Open-Close Principle):一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
  • 里式替换原则LSP(the Liskov Substitution Principle ):子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
  • 依赖倒置原则DIP(the Dependency Inversion Principle ):高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
  • 接口分离原则ISP(the Interface Segregation Principle ):设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。
  • 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话
  • 合成复用原则:尽量先使用组合或者聚合等关系来实现,其次考虑使用继承关系来实现

13、Super和this

继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)

  • this是自身的一个对象,代表对象本身;
  • super是代表超(父)类对象,可以说是指向自己超(父)类对象的一个指针。

注意
调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。this和super不能同时出现在一个构造函数里面,this()和super()都指的是对象,所以,均不可以在static环境中使用。

14、访问修饰符

使用访问修饰符来保护对类、变量、方法和构造方法的访问

  • private:在同一类内可见。使用对象:变量、方法。注意:不能修饰类(外部类)
  • default(即缺省,什么也不写,不使用任何关键字):在同一包内可见使用对象:类、变量、 方法。
  • protected:对同一包内的类和所有子类可见。使用对象:变量、方法。注意不能修饰类(外 部类)。
  • public:对所有类可见。使用对象:类、变量、方法

15、接口和抽象类

  • 接口是对行为的抽象,抽象类是对类的抽象,是一种模板设计思想。
  • 接口和抽象类都不能实例化都包含抽象方法,其子类都必须重写这些抽象方法
  • 不同的是抽象类可以有非抽象的方法,而接口中只能存在public abstract方法,在Java8中接口引入了默认方法和静态方法。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
  • 抽象类中可以有构造器,接口中不能有构造器。

16、成员变量和局部变量

  • 成员变量方法外部,类内部定义的变量。
  • 局部变量:类的方法中的变量。
  • 成员变量针对整个类有效,局部变量只在某个范围内有效(一般指的就是方法,语句体内)
  • 成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中,局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
  • 成员变量有默认初始值,局部变量没有默认初始值,使用前必须赋值。
  • 在使用变量时需要遵循的原则为就近原则首先在局部范围找,有就使用;接着在成员位置找

17、构造方法

类名与方法名相同,无返回值,但不能是void,用来实例化对象,即生成类的对象的时候自动执行,不需要调用。

18、Static

static用来修饰变量,方法,静态代码块,修饰类也就是静态内部类。被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享,而且在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只加载一次。1、静态只能访问静态。2、非静态既可以访问非静态的,也可以访问静态的。

19、内部类

成员内部类,局部内部类,匿名内部类,静态内部类。当某个类除了它的外部类,不再被其他的类使用时,我们一般使用内部类。

  • 静态内部类:定义在类内部的静态类。可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;通过外部类.内部类类名=new外部类.静态内部类()来创建。
  • 成员内部类:定义在类内部,成员位置上的非静态类,成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有,但外部类想访问内部类属性或方法时,必须要创建一个内部类对象。成员内部类依赖于外部类的实例,它的创建方式:外部类.内部类类名=外部类实例.new内部类(),如果成员内部类的属性和方法与外部类同名,将导致外部类的这些属性与方法在内部类被隐藏,也可按照外部类.this.属性方法。
  • 局部内部类:定义在方法中的内部类,可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,new内部类()。
  • 匿名内部类就是没有名字的内部类,日常开发中使用的比较多,没有构造方法,也是唯一没有构造方法的内部类,匿名内部类必须继承一个抽象类或者实现一个接口,匿名内部类不能定义任何静态成员和静态方法,只能访问外部类的的final变量,匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
new/接口{ 
//匿名内部类实现部分 
}

1.内部类可以获得外部类的私有属性和私有方法
2.静态内部类无法访问非静态的属性
3.一个java类中可以有多个class类,但只能有一个public类

20、异常

  • 所有的异常都有一个共同的祖先java.lang包中的Throwable类
  • Throwable:有两个重要的子类:Exception(异常)和Error(错误)。
  • Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM(Java虚拟机)出现的问题,比如awt相关,JVM栈溢出问题
  • Exception(异常):是程序本身可以处理的异常。Exception类有一个重要的子类
RuntimeException异常由Java虚拟机抛出
NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、
ArithmeticException(算术运算异常,一个整数除以0时,抛出该异常)和
ArrayIndexOutOfBoundsException(下标越界异常)
  • try块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块
  • catch块:用于处理try捕获到的异常
  • finally块:无论是否捕获或处理异常,finally块里的语句都会被执行,当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。

在以下4种特殊情况下,finally块不会被执行:

  • 在 finally 语句块中发生了异常。
  • 在前面的代码中用了 System.exit()退出程序。
  • 程序所在的线程死亡。
  • 关闭 CPU

Throw手动抛出,throws方法抛出,可以抛出多个异常

自定义异常,继承exception类,习惯上,定义一个异常类应包含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable的toString方法会打印这些详细信息,调试时很有用)

21、 final、finally、finalize 有什么区别?

  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java中使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

22、String,stringbuffer,stringbuilder

String的底层是char类型的数组,string是不变的,String对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回。String采用final修饰,String类不能被继承,数组没有length()方法,有length属性,String有length()方法

字符串反转:reverse(),indexOf():返回指定字符的索引,charAt():返回指定索引处的字符。replace():字符串替换,trim():去除字符串两端空白,substring():截取字符串。

StringBuilder与StringBuffer都是可变的,StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的,StringBuilder是非线程安全的。

23、泛型

用来约束的作用,避免类型之前强制转换,jdk1.5新特性

24、JDK 中常用的包有哪些

java.lang:这个是系统的基础类;
java.io:这里面是所有输入输出有关的类,比如文件操作等;
java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
java.net:这里面是与网络有关的类;
java.util:这个是系统辅助类,特别是集合类;
java.sql:这个是数据库操作的类

25、集合

  • 集合用于存储数据的容器,可变长
    Map接口和Collection接口是所有集合框架的父接口,Collection集合的子接口有Set、List、Queue三种子接口
  • List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有ArrayList、LinkedList和Vector。
  • Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set接口常用实现类是HashSet、LinkedHashSet以及TreeSet。
  • Map是一个键值对集合。Key无序,唯一;value不要求有序,允许重复。Map的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap。

底层实现

  • Arraylist:Object数组Vector:Object数组LinkedList:双向循环链表
  • HashSet(无序,唯一):基于HashMap实现的,底层采用HashMap来保存元素
  • LinkedHashSet:LinkedHashSet继承与HashSet,并且其内部是通过LinkedHashMap来实现的。
  • TreeSet(有序,唯一):红黑树(自平衡的排序二叉树。)
  • HashMap:JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
  • LinkedHashMap:LinkedHashMap继承自HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
  • HashTable:数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap:红黑树(自平衡的排序二叉树)

26、哪些集合是线程安全的?

  • vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。
  • statck:堆栈类,先进后出。
  • hashtable:就比hashmap多了个线程安全。
  • enumeration:枚举,相当于迭代器。

27、Java集合的快速失败机制 “fail-fast”

是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制

例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModificationException异常,从而产生fail-fast机制。

原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

解决办法:
1.在遍历过程中,所有涉及到改变modCount值得地方全部加synchronized。
2.使用CopyOnWriteArrayList来替换ArrayList

CopyOnWrite写入时复制,是一种com思想,是计算机程序设计领域的一种优化策略,有多个线程调用的时候,list是惟一的,在读取的时候是固定的,但是写入的时候可能后面写的会把前面的覆盖,就需要用到copyonwrite,在写入的时候先给它复制一份,复制后给调用者,调用者写完之后再抢这个东西放回去,相当于在写入的时候避免造成数据问题。

28、怎么确保一个集合不能被修改?

可以使用Collections.unmodifiableCollection(Collection c)方法来创建一个只读集合,这样改变集合的任何操作都会抛出Java.lang.UnsupportedOperationException异常。

29、迭代器 Iterator 是什么?

Iterator接口提供遍历Collection的接口。迭代器取代了Java集合框架中的Enumeration,迭代器允许调用者在迭代过程中移除元素。Iterator只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出ConcurrentModificationException异常。

30、如何边遍历边移除 Collection 中的元素?

边遍历边修改Collection的唯一正确方式是使用Iterator.remove()方法,如下:一种常见的错误代码如下:
运行以上错误代码会报ConcurrentModificationException异常。这是因为当使用foreach语句时,会自动生成一个iterator来遍历该list,但同时该list正在被Iterator.remove()修改。
Java一般不允许一个线程在遍历Collection时另一个线程修改它。

31、Iterator 和 ListIterator 有什么区别?

  • Iterator可以遍历Set和List集合,而ListIterator只能遍历List。
  • Iterator只能单向遍历,而ListIterator可以双向遍历(向前/后遍历)。
  • ListIterator实现Iterator接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

32、遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?

遍历方式有以下几种:
1.for循环遍历,基于计数器。
2.迭代器遍历,Iterator。
3.foreach循环遍历。foreach内部也是采用了Iterator的方式实现,使用时不需要显式声明Iterator或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。

最佳实践:Java Collections框架中提供了一个RandomAccess接口,用来标记List实现是否支持Random Access。如果一个数据集合实现了该接口,就意味着它支持Random Access,按位置读取元素的平均时间复杂度为O(1),如ArrayList。

如果没有实现该接口,表示不支持Random Access,如LinkedList。推荐的做法就是,支持Random Access的列表可用for循环遍历,否则建议用Iterator或foreach遍历。

33、说一下 ArrayList 的优缺点

ArrayList的优点如下
ArrayList底层以数组实现,是一种随机访问模式。ArrayList实现了RandomAccess接口,因此查找的时候非常快。ArrayList在顺序添加一个元素的时候非常方便。

ArrayList的缺点如下:删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。插入元素的时候,也需要做一次元素复制操作,缺点同上。

ArrayList比较适合顺序添加、随机访问的场景。

34、如何实现数组和 List 之间的转换? Array 和 ArrayList 有何区别?

  • Array可以存储基本数据类型和对象,ArrayList只能存储对象。
  • Array是指定固定大小的,而ArrayList大小是自动扩展的。

对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

  • 数组转List:使用Arrays.asList(array)进行转换。
  • List转数组:使用List自带的toArray()方法。

35、ArrayList 和 LinkedList 的区别是什么?

  • 数据结构实现:ArrayList是动态数组的数据结构实现,而LinkedList是双向链表的数据结构实现。
  • 随机访问效率:ArrayList比LinkedList在随机访问的时候效率要高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
  • 增加和删除效率:在非首尾的增加和删除操作,LinkedList要比ArrayList效率要高,因为ArrayList增删操作要影响数组内的其他数据的下标。
  • 内存空间占用:LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
  • 线程安全:ArrayList和LinkedList都是不同步的,也就是不保证线程安全;
  • 综合来说,在需要频繁读取集合中的元素时,更推荐使用ArrayList,而在插入和删除操作较多时,更推荐使用LinkedList。

补充:数据结构基础之双向链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

36、ArrayList 和 Vector 的区别是什么?

  • 这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合
  • ArrayList是非线程安全的。
  • 性能:ArrayList在性能方面要优于Vector。
  • 扩容:ArrayList和Vector都会根据实际的需要动态的调整容量,只不过在Vector扩容每次会增加1倍,而ArrayList只会增加50%。
  • Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。

37、CopyOnWriteArrayList和 Vector 的区别是什么?

vector的add方法是synchronized方法,效率低,而CopyOnWriteArrayList没有用synchronized方法,用的是ReentrantLock

38、多线程场景下如何使用 ArrayList?

ArrayList不是线程安全的,如果遇到多线程场景,可以通过Collections的
synchronizedList方法将其转换成线程安全的容器后再使用。

39、为什么 ArrayList 的 elementData 加上 transient 修饰?

ArrayList实现了Serializable接口,这意味着ArrayList支持序列化。transient的作用是说不希望elementData数组被序列化,重写writeObject()实现每次序列化时,先调用默认的WriteObject()方法序列化ArrayList中的非transient元素,然后遍历elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。

40、List和Set 的区别

  • List,Set都是继承自Collection接口
  • List特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素
  • Set特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。
  • 另外List支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。
  • Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
  • List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

41、说一下 HashSet 的实现原理?

HashSet是基于HashMap实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此HashSet的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,HashSet不允许重复的值。

42、HashSet如何检查重复?HashSet是如何保证数据不可重复的?

向HashSet中add()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles方法比较。HashSet中的add()方法会使用HashMap的put()方法。
HashMap的key是唯一的,由源码可以看出HashSet添加进去的值就是作为HashMap的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V。所以不会重复(HashMap比较key是否相等是先比较hashcode再比较equals)。

43、HashSet与HashMap的区别

HashMapHashSet
实现了Map接口实现了Set接口
存储键值对仅存储对象
调用 put()向 map中添加元素调用 add()方法向Set 中添加元素
HashMap 使用键(Key)计算 HashcodeHashSet 使用成员对象来计算 hashcode 值
HashMap相对于HashSet较快,因为它是使用唯一的键获取对象HashSet较HashMap来说比较慢

对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false。

44、说一下 HashMap 的实现原理?以及put元素流程

HashMap是基于哈希表的Map接口的非同步实现。并允许使用null值和null键。不保证映射的顺序,HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率。
HashMap基于Hash算法实现的

  1. 当我们往Hashmap中put元素时,利用key的hash值,这里调用了hash方法,就是key.hashCode与key.hashCode()>>>16进行异或操作,高16bit补0,因为一个数和0异或不变,所以hash函数大概的作用就是:高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞。计算出当前对象的元素在数组中的下标

  2. 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中

  3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

  4. HashMap解决hash冲突的问题的核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,总长度大于64之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

很多小伙伴看完都会问,为什么要将hashCode值右移16位并且与原来的hashCode值进行^(按位异或)操作?

  • 右移16位是为了让高16位也参与运算,可以更好的均匀散列,减少碰撞,进一步降低hash冲突的几率
  • 异或运算是为了更好保留两组32位二进制数中各自的特征

45、HashMap在JDK1.7和JDK1.8中有哪些不同? HashMap的底层实现

在Java中,保存数据有两种比较简单的数据结构:数组和链表。

  • 数组的特点是:寻址容易,插入和删除困难;
  • 链表的特点是:寻址困难,但插入和删除容易;

所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。

  • JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
  • JDK1.8之后,相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。当链表长度小于阈值6时,将红黑树转化为链表。

JDK1.8主要解决或优化了一下问题:

  1. resize扩容优化
  2. 引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考
  3. 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。
    在这里插入图片描述

46、HashMap的扩容操作是怎么实现的?

在jdk1.8中,当元素数量超过阈值(容量*负载因子)时,一般就会发生扩容,每次扩容的容量都是之前容量的2倍数。初始容量默认为16,负载因子默认为0.75。
HashMap的容量是有上限的,必须小于1<<30。如果容量超出了这个数,则不再增长,且阈值会被设置为Integer.MAX_VALUE。
resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;.每次扩展的时候,都是扩展2倍;扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。在putVal()中,我们看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值值(第一次为12),这个时候在扩容的同时也会伴随的桶上面的元素进行重新分发,这也是JDK1.8版本的一个优化的地方,在1.7中,扩容之后需要重新去计算其Hash值,根据Hash值对其进行分发,但在1.8版本中,则是根据在同一个桶的位置中进行判断(e.hash&oldCap)是否为0,重新进行hash分配后,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上。

47、什么是哈希冲突?

当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。

48、简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:

  1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
  2. 使用2次扰动函数(hash函数)就是1次位运算和1次异或运算来降低哈希冲突的概率,使得数据分布更平均;
  3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;

49、HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?

答:hashCode()方法返回的是int整数类型,其范围为-(2^31)~(2^31-1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2^30,HashMap通常情况下是取不到大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;

那怎么解决呢?

  1. HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
  2. 在保证数组长度为2的幂次方的时候,使用hash()运算之后的值与运算(&)(数组长度-1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配"的问题。

50、HashMap 的长度为什么是2的幂次方?

为了能让HashMap存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。
这个算法应该如何设计呢?我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说
hash%length==hash&(length-1)的前提是length是2的n次方;)。”并且采用二进制位操作&,相对于%能够提高运算效率,这就解释了HashMap的长度为什么是2的幂次方。

51、那为什么是两次扰动呢?

答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的。

52、HashMap 与 HashTable 有什么区别?

  • 线程安全:HashMap是非线程安全的,HashTable是线程安全的;
    HashTable内部的方法基本都经过synchronized修饰。(如果你要保证线程安全的话就使用ConcurrentHashMap吧!);
  • 效率:因为线程安全的问题,HashMap要比HashTable效率高一点。另外,HashTable基本被淘汰,不要在代码中使用它;
  • 对Null key和Null value的支持:HashMap中,null可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为null。但是在HashTable中put进的键值只要有一个null,直接抛NullPointerException。
  • 初始容量大小和每次扩充容量大小的不同:①创建时如果不指定容量初始值,Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说HashMap总是使用2的幂作为哈希表的大小
  • 底层数据结构:JDK1.8以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable没有这样的机制。
  • 推荐使用:在Hashtable的类注释可以看到,Hashtable是保留类不建议使用,推荐在单线程环境下使用HashMap替代,如果需要多线程使用则用ConcurrentHashMap替代。

关于HashMap参考:链接

53、如何决定使用 HashMap 还是TreeMap?

对于在Map中插入、删除和定位元素这类操作,HashMap是好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。

54、HashMap 和 ConcurrentHashMap 的区别

  • ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启了一种全新的方式实现,利用CAS算法。)
  • HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

55、ConcurrentHashMap 和 Hashtable 的区别?

ConcurrentHashMap和Hashtable的区别主要体现在实现线程安全的方式上不同。
底层数据结构:

  • JDK1.7的ConcurrentHashMap底层采用分段的数组+链表实现,JDK1.8采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑树。实现线程安全的方式(重要):①在JDK1.7的时候,ConcurrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。)到了JDK1.8的时候已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用synchronized和CAS来操作。虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本;
  • Hashtable(同一把锁):使用synchronized来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用put添加元素,另一个线程不能使用put添加元素,也不能使用get,竞争会越来越激烈效率越低。

答:ConcurrentHashMap结合了Hash(img)Map和HashTable二者的优势。HashMap没有考虑同步,HashTable考虑了同步的问题。但是HashTable在每次同步执行时都要锁住整个结构。ConcurrentHashMap锁的方式是稍微细粒度的。

56、ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

JDK1.7首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问,在JDK1.7中,ConcurrentHashMap采用Segment+HashEntry的方式进行实现。

  1. 该类包含两个静态内部类HashE(img)ntry和Segment;前者用来封装映射表的键值对,后者用来充当锁的角色;
  2. Segment是一种可重入的锁ReentrantLock,每个Segment守护一个HashEntry数组里得元素,当对HashEntry数组的数据进行修改时,必须首先获得对应的Segment锁。

在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node+CAS+Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

57、comparable 和 comparator的区别?

comparable接口实际上是出自java.lang包,它有一个compareTo(Object obj)方法用来比较,类需要实现Comparable
comparator接口实际上是出自java.util包,它有一个compare(Object obj1,Object obj2)方法用来比较

58、Collection 和 Collections 有什么区别?

java.util.Collection是一个集合接口(集合类的一个顶级接口)。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

59、TreeMap 和 TreeSet 在排序时如何比较元素?

Collections 工具类中的 sort()方 法如何比较元素?
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。
TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。
Collections工具类的sort方法有两种重载的形式

60、HashTable, HashMap,TreeMap区别?

  1. HashTable线程同步,HashMap非线程同步。
  2. HashTable不允许<键,值>有空值,HashMap允许<键,值>有空值。
  3. HashTable使用Enumeration,HashMap使用Iterator。
  4. HashTable中hash数组的默认大小是11,增加方式的old*2+1,HashMap中hash数组的默认大小是16,增长方式一定是2的指数倍。
  5. TreeMap能够把它保存的记录根据键排序,默认是按升序排序。

61、多线程修改HashMap

多线程同时写入,同时执行扩容操作,多线程扩容可能死锁、丢数据;可以对HashMap加入同步锁Collections.synchronizedMap(hashMap),但是效率很低,因为该锁是互斥锁,同一时刻只能有一个线程执行读写操作,这时候应该使用ConcurrentHashMap。

62、Java中的队列都有哪些,有什么区别

  1. ArrayDeque,(数组双端队列)
  2. PriorityQueue,(优先级队列)
  3. ConcurrentLinkedQueue,(基于链表的并发队列)
  4. DelayQueue,(延期阻塞队列)(阻塞队列实现了BlockingQueue接口)
  5. ArrayBlockingQueue,(基于数组的并发阻塞队列)
  6. LinkedBlockingQueue,(基于链表的FIFO阻塞队列)
  7. LinkedBlockingDeque,(基于链表的FIFO双端阻塞队列)
  8. PriorityBlockingQueue,(带优先级的无界阻塞队列)
  9. SynchronousQueue(并发同步阻塞队列)

63、java 中 IO 流分为几种?

按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。

节点流:可以从或向一个特定的地方(节点)读写数据。
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写

64、BIO,NIO,AIO 有什么区别?

  • BIO:Block
    IO同步阻塞式IO,就是我们平常使用的传统IO,它的特点是模式简单使用方便,并发处理能力低。数据的读取写入必须阻塞在一个线程内等待其完成
  • NIO:Non IO同步非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
  • AIO:Asynchronous
    IO是NIO的升级,也叫NIO2,实现了异步非堵塞IO,异步IO的操作基于事件和回调机制,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作

65、Java序列化

保存(持久化)对象及其状态到内存或者磁盘中
我们在内存中创建的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。

Serializable实现序列化,在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化

Transient关键字阻止该变量被序列化到文件中(transient只能修饰变量不能修饰方法和类)

  1. 在变量声明前加上Transient关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型的是0,对象型的是null。
  2. 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
  3. 在类中增加writeObject和readObject方法可以实现自定义序列化策略

66、Java8新特性

Java8最值得学习的特性就是Lambda表达式和Stream API。lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的内部类,匿名类
Java5:泛型,枚举,注解反射

67、链函数式接口、lambda表达式、Stream流式计算

1.链式编程
链式编程是将多个操作通过点号"."链接在一起成为一个整体,从而更加的简洁方便。链式编程的原理就是每个操作完成后都会返回一个this对象,也就是返回对象本身!

StringBuilder builder = new StringBuilder(0);
        builder.append("black")
                .append("blue")
                .append("yellow");

2.函数式接口
函数式接口指的是那些只包含一个方法的接口,如四大函数式接口,常见的Runnable接口,这种函数式接口,可以简化编程模式,在新版框架底层大量应用!
Function函数式接口:有一个输入有一个输出

    @FunctionalInterface
    public interface Function<T,R>{
        R apply(T t);
    }

Predicate断定型接口:有一个输入参数,返回值为布尔型

@FunctionalInterface
    public interface Predicate<T,R>{
       boolean test(T t);
    }

Consumer 消费型接口:只有输入,没有返回值

    @FunctionalInterface
    public interface Consumer<T>{
        void accept(T t);
    }

Supplier 供给型接口:没有输入,只有返回值

    @FunctionalInterface
    public interface Supplier<T>{
        T get();
    }

3.lambda表达式

/**
 * 题目要求:一分钟内完成此题,只能使用一行代码实现
 * 现在有5个用户!筛选
 * 1.ID必须是偶数
 * 2.年龄必须大于23岁
 * 3.用户名转为大写字母
 * 4.用户名字母倒着排序
 * 5.只输出一个用户!
 */
public class Test1 {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(6,"e",25);
        //集合用来存储
        List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
        //lambda表达式,链式编程,函数式接口,Stream流式计算
        list.stream()
                .filter(u->{return u.getID()%2==0;})
                .filter(u->{return u.getAge()>23;})
                .map(u->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

4.Stream流式计算:存储+计算

68、Tomcat

Tomcat:.bat是Windows下的执行文件,.sh是linux下的执行文件
乱码解决:conf文件下的logging.properties把utf-8改为GBK(Windows默认为GBK编码)

69、注解

Java.Annotation是java5开始引入的新技术,Annotation不是程序本身,它可以对程序作出解释(这一点和comment没有区别),可以被其他程序(比如编译器等)读取。附加在package,class,method,field上面,相当于给它们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问。格式:@注释名,还可以添加一些参数值,比如:@suppress warnings(value=”unchecked”)抑制编译时的警告信息。
内置注解
@Override重写
@FunctionalInterface函数式接口
@SuppressWarnings警告镇压
@Deprecated意思是说此方法已过时,是因为有新的API的类替代了此方法。这个被划去的方法仍然是可以正常使用的。
元注解:负责注解其他注解
@Target描述注解的使用范围
@Retention用来定义该注解在哪一个级别可用,在源代码中(SOURCE)、类文件中(CLASS)或者运行时(RUNTIME)
@Documented说明该注解将包含在javadoc中
@Inherited允许子类继承父类中的注解
自定义注解:
使用@interface自定义注解,自动继承java.lang.annotation.Annotation接口,其中的每一个方法实际是声明了一个配置参数,方法的名称就是参数名称,返回值类型是参数类型,返回值只能是基本类型,class,string,enum。
https://www.cnblogs.com/jajian/p/9695055.html

70、反射机制

动态语言:在运行时可以改变其结构的语言比如JavaScript,PHP,Python,C#
静态语言:运行时结构不可以改变的语言比如Java,C,C++
Java.Reflection:允许程序在执行期间借助于ReflectionAPI取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Class c=Class.forName(“java.lang.String”)加载完类之后,在堆内存的方法区中就产生一个Class类型对象,一个类只有一个Class对象,这个对象就包含了完整的类的结构信息,可以通过这个对象看到类的结构。正常方式是,引入需要的包的名称,通过new实例化,取得实例化对象,反射是实例化对象,getClass()方法,得到完整的包类名称。
java.lang.Class代表一个类
java.lang.reflect.Method代表类的方法
java.lang.reflect.Field代表类的成员变量
java.lang.reflect.Constructor代表类的构造器

71、获取Class类的方式

Class c=perSon.getClass();
Class.forName(“com.kuang.reflection.Student”);
Student.class;
Integer.TYPE;基本内置类型的包装类都有一个TYPE属性;
c.getSuperClass();获得父类类型

72、哪些类型可以有Class对象?

外部类,成员内部类,局部内部类,匿名内部类,接口,数组,枚举,注解,基本数据类型,void.class;Class.class;
例子:int[]a=new int[10];int[]b=new int[100];
a.getClass().hashCode();b.getClass().hashCode()//相等,只要元素类型与维度一样就是同一个Class

类的主动引用(一定会发生类的初始化)

  1. 反射
  2. new
  3. 子类继承父类,父类先被加载
  4. 调用类的静态成员(除了final常量)和静态方法
  5. 虚拟机启动,先初始化main方法所在的类

类的被动引用(一定会发生类的初始化)

  1. 访问一个静态域,只有真正声明这个域的类才会被初始化,比如当通过子类引用父类的静态变量,不会导致子类的初始化
  2. 通过数组定义引用,不会触发此类的初始化
  3. 引用常量不会触发此类的初始化

73、Invoke方法

反射解决的这个问题,运行时获取对象结构,调用方法。Method这个类就是关于反射调用方法的,大概意思就是说提供类或者接口的方法信息,就可以访问调用对应的方法。
invoke的意思上就有调用的意思,也就是说我们可以通过反射包下的Method类调用invoke方法,调用我们所提供的方法以及调用方法的参数来完成动态调用,也就是根据你给的对象实例,方法名,以及参数来调用。

invoke方法的使用:
我们经常创建一个对象A,A对象里面的方法getA()方法,然后A.getA()
我们采用invoke调用
(1)弄一个方法的“替身”(其实就是构建一个Method对象,让这个Method对象来代替你现在要用的方法)
(2)然后给替身需要的对象和参数,让替身去替你调用

public class InvokeTest {
    public void test(String[] arg){
        for (String string : arg) {
            System.out.println("zp is " + string);
        }
    }
    @Test
    public void invokeDemo() throws Exception {
        //获取字节码对象,这里要填好你对应对象的包的路径
        Class<InvokeTest> clazz = (Class<InvokeTest>) Class.forName("com.example.zp.demo.testDemo.InvokeTest");
        //形式一:获取一个对象
//        Constructor con =  clazz.getConstructor();
//        InvokeTest m = (InvokeTest) con.newInstance();
        //形式二:直接new对象,实际上不是框架的话,自己写代码直接指定某个对象创建并调用也可以
        InvokeTest m = new InvokeTest();
        String[] s = new String[]{"handsome","smart"};
        //获取Method对象
        Method method = clazz.getMethod("test", String[].class);
        //调用invoke方法来调用
        method.invoke(m, (Object) s);
    }

所以使用invoke方法要比别的方法多做一步,就是构建一个Method对象,这个对象替代的是现在程序要调用方法的替代品。
而且除了参数以外,invoke还会多要一个对象,因为方法调用需要对象,所以invoke要想调用的目标方法,就需要目标方法的需要的对象。
好处:我只需要输入参数,我可以调用替代各种方法,在未知的情况下,根据条件决定去调用什么对象,什么方法,一下子就让代码变得灵活,这不仅是invoke的妙处,也是整个反射的妙处,在程序运行时根据条件灵活使用。
参考:链接

74、Java反射面试题

1、除了使用new创建对象之外,还可以用什么方法创建对象?

使用Java反射可以创建对象!

2、Java反射创建对象效率高还是通过new创建对象的效率高?

通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,所以效率较低

3、java反射的作用

反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

4、哪里会用到反射机制?

jdbc就是典型的反射

Class.forName('com.mysql.jdbc.Driver.class');//加载MySQL的驱动类

这就是反射。如hibernate,struts等框架使用反射实现的。

5、反射的实现方式:

第一步:获取Class对象,有4中方法:

1)Class.forName(“类的路径”);

2)类名.class

3)对象名.getClass()

4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象

6、反射机制的优缺点:

优点

  • 能够运行时动态获取类的实例,提高灵活性;
  • 与动态编译结合

缺点

  • 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。

解决方案:

1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度;

2、多次创建一个类的实例时,有缓存会快很多

3、ReflflectASM工具类,通过字节码生成的方式加快反射速度

  • 相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)

7、Java 反射 API

反射 API 用来生成 JVM 中的类、接口或则对象的信息。

  • Class 类:反射的核心类,可以获取类的属性,方法等信息。

  • Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。

  • Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。

  • Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。

8、反射使用步骤(获取 Class 对象、调用对象方法)

1.获取想要操作的类的 Class 对象,他是反射的核心,通过 Class 对象我们可以任意调用类的方法。
2.调用 Class 类中的方法,既就是反射的使用阶段。
3.使用反射 API 来操作这些信息。

9、获取 Class 对象有几种方法

调用某个对象的 getClass()方法

Person p=new Person();
Class clazz=p.getClass();

调用某个类的 class 属性来获取该类对应的 Class 对象

Class clazz=Person.class;

使用 Class 类中的 **forName()**静态方法(最安全/性能最好)

Class clazz=Class.forName("类的全路径"); (最常用)

当我们获得了想要操作的类的 Class 对象后,可以通过 Class 类中的方法获取并查看该类中的方法和属性。

//获取 Person 类的 Class 对象

 Class clazz=Class.forName("reflection.Person");
//获取 Person 类的所有方法信息
 Method[] method=clazz.getDeclaredMethods();
 for(Method m:method){
    System.out.println(m.toString());
 }
 //获取 Person 类的所有成员属性信息
 Field[] field=clazz.getDeclaredFields();
 for(Field f:field){
    System.out.println(f.toString());
 }
 //获取 Person 类的所有构造方法信息
 Constructor[] constructor=clazz.getDeclaredConstructors();
 for(Constructor c:constructor){
    System.out.println(c.toString());
 }

10、利用反射动态创建对象实例

1.Class 对象的 newInstance()

使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求
该 Class 对象对应的类有默认的空构造器。

2.调用 Constructor 对象的 newInstance()

先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()
方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。

 //获取 Person 类的 Class 对象

 Class clazz=Class.forName("reflection.Person"); 

 //使用.newInstane 方法创建对象

 Person p=(Person) clazz.newInstance();

//获取构造方法并创建对象

 Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);

 //创建对象并设置属性13/04/2018 

 Person p1=(Person) c.newInstance("李四","男",20);

73.设计模式
https://www.runoob.com/design-pattern/design-pattern-intro.html
74.排序算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值