Java初级基础面试题

Java基础

1、java的八大基本数据类型

基本数据类型:四类八种;

数值型,byte,int,short,long

浮点型,float,double

字符型,char

布尔类型 boolean

引用数据类型:类、接口、数组、 枚举

String是特殊的引用数据类型

(1)八种基本数据类型和封装类
(2)自动装箱和自动拆箱

什么是自动装箱拆箱

基本数据类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。

一般我们要创建一个类的对象实例的时候,我们会这样:

Class a = new Class(parameter);

当我们创建一个Integer对象时,却可以这样:

Integer i = 100; (注意:不是 int i = 100; )

实际上,执行上面那句代码的时候,系统为我们执行了:Integer i = Integer.valueOf(100);

此即基本数据类型的自动装箱功能。

基本数据类型与对象的差别

基本数据类型不是对象,也就是使用int、double、boolean等定义的变量、常量。

基本数据类型没有可调用的方法。

eg: int t = 1; t. 后面是没有方法滴。

Integer t =1; t. 后面就有很多方法可让你调用了。

什么时候自动装箱

例如:Integer i = 100;

相当于编译器自动为您作以下的语法编译:Integer i = Integer.valueOf(100);

什么时候自动拆箱

自动拆箱(unboxing),也就是将对象中的基本数据从对象中自动取出。如下可实现自动拆箱:

 Integer i = 10; //装箱 

 int t = i; //拆箱,实际上执行了 int t = i.intValue();

在进行运算时,也可以进行拆箱。

 Integer i = 10; 

 System.out.println(i++);

Integer // 的自动装箱

 //在-128~127 之外的数

  Integer i1 =200; 

  Integer i2 =200;     

 System.out.println("i1==i2: "+(i1==i2));          

  // 在-128~127 之内的数

 Integer i3 =100; 

  Integer i4 =100; 
  System.out.println("i3==i4: "+(i3==i4));

// 输出的结果是:

//  i1==i2: false
//  i3==i4: true

说明:

equals() 比较的是两个对象的值(内容)是否相同。

“==” 比较的是两个对象的引用(内存地址)是否相同,也用来比较两个基本数据类型的变量值是否相等。

前面说过,int 的自动装箱,是系统执行了 Integer.valueOf(int i),先看看Integer.java的源码:

 public static Integer valueOf(int i) {

   if(i >= -128 && i <= IntegerCache.high) // 没有设置的话,IngegerCache.high 默认是127
     return IntegerCache.cache[i + 128];
   else
   return new Integer(i);
 }

对于–128到127(默认是127)之间的值,Integer.valueOf(int i) 返回的是缓存的 Integer 对象!!!(并不是新建对象)

所以范例中, i3 i4 实际上是指向同一个对象。

而其他值,执行Integer.valueOf(int i) 返回的是一个新建的 Integer对象,所以范例中,i1与i2 指向的是不同的对象。

当然,当不使用自动装箱功能的时候,情况与普通类对象一样,请看下例:

 Integer i3 =new Integer(100); 

 Integer i4 =new Integer(100); 

 System.out.println("i3==i4: "+(i3==i4));//显示false

2、Java的基本数据类型都有哪些各占几个字节

如下表所示:

Java初级基础面试题_第1张图片在这里插入图片描述
《Java虚拟机规范》给出了4个字节,和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是运算效率和存储空间之间的博弈,两者都非常的重要。

3、数据类型之间的转换

(1)、字符串如何转基本数据类型?

调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型。

(2)、基本数据类型如何转字符串?

一种方法是将基本数据类型与空字符串(“”)连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf()方法返回相应字符串。

4、运算符

算术运算符:+ - * / %(取模|求余) ++ 自增 --自减

关系运算符:< <= >= > !=不等于 ==等于

逻辑运算符:&& 且、|| 或 、!非

运算符的优先级:()->算术运符符>关系运算符>逻辑运算符

5、Integer与int区别

①int 是 java 提供的 8 种原始数据类型之一.Java 为每个原始类型提供了封装类,Integer 是 java 为 int 提供的封装类.

②int 的默认值为 0,而 Integer 的默认值为 null,

③Integer 可以区分出未赋值和值为 0 的区别,int 则无法表达出未赋值的情况.

④Integer提供多个与整数相关的操作方法,例如:字符串转整数Integer.parseInt(“数字字符串”),integer最小值和最大值Integer.MAX_VALUE

6、弄懂 JRE、JDK、JVM 之间的区别与联系

这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM

jdk 是我们的开发工具包,它集成了 jre

而仅仅是用来部署和运行 Java 程序的,那么完全可以不用安装 jdk,只需要安装 jre 即可

Java 能够跨平台运行的核心在于 JVM

Java 引入了字节码的概念,jvm 只能认识字节码,并将它们解释到系统的 API 调用。针对不同的系统有不同的 jvm 实现,有 Linux 版本的 jvm 实现,也有 Windows 版本的 jvm 实现,但是同一段代码在编译后的字节码是一样的。引用上面的例子,在 Java API 层面,我们调用系统声音设备的代码是唯一的,和系统无关,编译生成的字节码也是唯一的。但是同一段字节码,在不同的 jvm 实现上会映射到不同系统的 API 调用,从而实现代码的不加修改即可跨平台运行。

java程序的运行过程:

编写源码文件(xxx.java)-编译–>字节码文件(xxx.class)-运行->java运行平台(JVM)。

7、描述一下 JVM(JAVA 虚拟机)加载 class 文件的原理机制?

Java中的所有类,都需要由类加载器装载到JVM中才能运行。

Java的类加载器有三个,对应Java的三种类:

java中的类大致分为三种:1.系统类 2.扩展类 3.由程序员自定义的类

Bootstrap Loader // 负责加载系类

ExtClassLoader // 负责加载扩展类(就是继承类和实现类)
AppClassLoadr // 负责加载应用类(程序员自定义的类)

原理机制就是下面几个步骤:

1.装载:查找和导入class文件;

2.连接:

(1)检查:检查载入的class文件数据的正确性;

(2)准备:为类的静态变量分配存储空间;

(3)解析:将符号引用转换成直接引用(这一步是可选的)

3.初始化:初始化静态变量,静态代码块。

这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。

8、说说你对Java中堆和栈的理解

1、栈和堆的特点

栈:

函数中定义的基本类型变量,对象的引用变量都在函数的栈内存中分配。
栈内存特点,数据一执行完毕,变量会立即释放,节约内存空间。
栈内存中的数据,没有默认初始化值,需要手动设置。

堆:

堆内存用来存放new创建的对象和数组。
堆内存中所有的实体都有内存地址值。
堆内存中的实体是用来封装数据的,这些数据都有默认初始化值。
堆内存中的实体不再被指向时,JVM启动垃圾回收机制,自动清除,这也是JAVA优于C++的表现之一(C++中需要程序员手动清除)。

2.堆和栈的区别:

1.线程问题:

堆:线程共享,栈:线程私有;

2.作用方面

堆:存放对象实例,栈:存储方法执行的时候所用到的信息

(局部变量表,方法出口等);

3.异常方面

堆:内存溢出,栈:内存溢出、栈溢出;

  1. 存取方面

堆:唯一、无序、共享(容器),栈:先进后出(栈底、栈顶);

9、&与&&操作符区别

&,即如果第一个表达式为false,则还是会计算第二个表达式,

&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,

10、java 中实现多态的机制是什么?

方法的重写Overriding和重载Overloading是Java多态性的不同表现。

重写Overriding:

是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。

方法的重写Overriding:
子类可以重写父类的方法。方法的返回值类型、参数类型、参数个数都不能改变,只能重写方法体。

类内的多态方式:方法的重载(Overload) ,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。

11、多态的实现(了解)

Java实现多态有三个必要条件:继承、重写、向上转型。

继承:在多态中必须存在有继承关系的子类和父类。

重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

12、==与equals方法区别

操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用操作符。

equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。

13、final,finally,finalize区别

final用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。

14、String原理

1)String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。
2)String类其实是通过char数组来保存字符串的。
3)String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象

4)String 字面量在编译期被确定,引用对象在运行期才被确定

5)使用final修饰的引用对象在编译期被确定

14.1、String 常用方法详解

2018.08.29 00:11:15字数 928阅读 590

String类在java.lang包中,java使用String类创建一个字符串变量,字符串变量属于对象。java把String类声明为final类。String类对象创建后不能修改,由0或多个字符组成,包含在一对双引号之间。重新赋值后将会生成新的对像来存放新的内容,原先的对象依旧在内存中,原来的对象就会成为垃圾内存,在某一个特定的时刻Java虚拟机回收。

具体方法:

声明一个字符串变量:

String name = "abc";

1 、int length();

语法:返回值为 int 类型。得到一个字符串的字符个数(中、英、空格、转义字符皆为字符,计入长度)
字符串变量名.length();

 String str = "abc \t\n";
 int length = str.length();
 System.out.println(length );

运行结果:6

2 、char charAt(值);

语法 : 返回值为 char 类型。从字符串中取出指定位置的字符
字符串名.charAt(值);

String str= "abc";
char c = str.charAt(2); 
System.out.println("指定字符为:" + c); 

运行结果:指定字符为:c

3 、char toCharArray();

语法:返回值为 char 数组类型。将字符串变成一个字符数组
字符串名.toCharArray();

String str= "abc"char c[] = str.toCharArray(); 
for (int i = 0; i < c.length; i++) 
{
   System.out.println("转为数组输出:" + c[i]);

}

运行结果:
转为数组输出:a
转为数组输出:b
转为数组输出:c

4 、int indexOf(“字符”); int lastIndexOf(“字符”) ;

语法 :查找一个指定的字符串是否存在,返回的是字符串的位置,如果不存在,则返回-1 。
字符串名.indexOf(“字符”);
字符串名.indexOf(“字符”,值);
字符串名.lastIndexOf(“字符”);

String str="axcdefgabc";
int a1 = str.indexOf("a"); 
int a2 = str.indexOf("x", 2);// 查找x的位置,从第3个开始查找
int a3 = str.lastIndexOf("c"); 
System.out.println("你的位置为:" + a1); 
System.out.println("为的位置为:" + a2);
System.out.println("点最后出现的位置为:" + a3);

运行结果:
a的位置为: 0
x的位置为: -1
c最后出现的位置为: 9

5 、字符串大小写的转换

 toUpperCase(); 转大写
 toLowerCase();转换成小写
String str = "hello world";
String str1 = "HELLO WORD";
System.out.println("将字符串转大写为:" + str.toUpperCase());
System.out.println("将字符串转换成小写为:" + str1.toLowerCase());

运算结果:
将字符串转大写为:HELLO WORLD
将字符串转换成小写为:hello world

6 、根据给定的正则表达式的匹配来拆分此字符串。形成一个新的String数组。

String[] split("字符");
String str = "abc,123,def";
String[] arr1 = str.split(",");

//运行结果:
  // arr1  //{ "boo", "and", "foo" }

7 、字符串变量名.wquals(字符串变量名); 返回值为布尔类型。所以这里用 if 演示。比较两个字符串是否相等,返回布尔值

boolean equals(Object anObject)
 String str = "hello";
 String str1= "world"; 
 if(str.equals(str1)) 
 { 
​    System.out.println("相等"); 
 } 
 else
 {
​    System.out.println("不相等"); 
 }

运行结果:
    不相等

8 、去掉字符串左右空格

String trim();

String str = "    abc     "; 

System.out.println("去掉左右空格后:" + str.trim());

运行结果:
 去掉左右空格后:abc

9**、新字符替换旧字符,也可以达到去空格的效果一种**

String replace(char oldChar,char newChar);
 String replaceAll(String,String);//将某个内容全部替换成指定内容
 String repalceFirst(String,String);//将第一次出现的某个内容替换成指定的内容
String str = "abcdefgabdc"; 
System.out.println("替换:" + str.replace("abc", "123")); 
System.out.println("替换全部:" + str.replaceAll("ab", "12")); 
System.out.println("替换第一次出现:" + str.repalceFirst("a", "a")); 

运行结果:
 替换:123defg
替换全部:12cdefg12dc
替换第一次出现:abcdefgabdc

10 、截取字符串

String substring(int beginIndex,int endIndex)
 String str = "abcdefg"; 
 // 截取0-3个位置的内容, 不含3 
 System.out.println("截取后的字符为:" + str.substring(0, 3));
 // 从第3个位置开始截取, 含2
 System.out.println("截取后字符为:" + str.substring(2));

运行结果:
   截取后的字符为:abc
   截取后字符为:cdefg

11 、忽略大小写的比较两个字符串的值是否一模一样,返回一个布尔值

boolean equalsIgnoreCase(String);
String str = "HELLO WORLd";
String str1 = "hello world";
if(str.equalsIgnoreCase(str1))
{
​    System.out.println("相等");
}
else
{
​    System.out.println("不相等");
}

运行结果:
   相等

12 、判断一个字符串里面是否包含指定的内容,返回一个布尔值

boolean contains(String);
String str = "HELLO WORLD";
String str1 = "WORLD";
if(str.contains(str1))

{
​    System.out.println("str内容中包含WORLD");

}
else

{

​    System.out.println("str内容中不包含WORLD");

}

运行结果:
 str内容中包含WORLD

13 、测试此字符串是否以指定的前缀开始。返回一个布尔值boolean startsWith(String);

String str = "HELLO WORLd";

String str1 = "HE";

if(str.startsWith(str1))

{
   System.out.println("str内容中包含HE前缀开头");
}

else

{

   System.out.println("str内容中不包含HE前缀开头");

}

运行结果:
 str内容中傲寒HE前缀开头

14 、测试此字符串是否以指定的后缀结束。返回一个布尔值

boolean endsWith(String);

String str = "HELLO WORLD";

String str1 = "LD";

if(str.endsWith(str1))

{

   System.out.println("str内容中包含 LD 后缀");

}

else

{

​    System.out.println("str内容中不包含 LD 后缀");

}

// 运行结果:
  // System.out.println("str内容中包含 LD 后缀");

15、String 与 StringBuffer与 StringBuilder区别

String:是对象不是原始类型.

  1. 不可变对象,一旦被创建,就不能修改它的值.
    对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.

  2. String 是final类, 不能被继承.

  3. 重写了equals方法

StringBuffer ,StringBuilder是一个可变对象,可变字符串,没有重写了equals方法

StringBuffer 线程安全,运行效率低

StringBuilder线程不安全,运行效率高

使用场景

(1)如果要操作少量的数据用 String
(2)单线程操作字符串缓冲区 下操作大量数据 StringBuilder
(3)多线程操作字符串缓冲区 下操作大量数据 StringBuffer

16、abstract class与 interface区别及应用场景

  1. 抽象类可以有构造方法,接口中不能有构造方法.

  2. 抽象类中可以有普通成员变量,接口中没有普通成员变量

  3. 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法.

  4. 抽象类中的抽象方法的访问类型可以是public,protected 和默认作用域(不建议使用, 因为默认作用域不能被其它包中的子类集成),但接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型.

  5. 抽象类中可以包含静态方法,接口中不能包含静态方法

  6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final 类型,并且默认即为public static

final 类型.

  1. 一个类可以实现多个接口,但只能继承一个抽象类.

interface的应用场合
A. 类与类之前需要特定的接口进行协调,而不在乎其如何实现。
B. 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
C. 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
D. 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。

abstract class的应用场合
一句话,在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它。最常见的有:
A. 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖。
B. 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
C. 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能。

17、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)? 抽象类中是否可以有静态的 main 方法?(重点)

接口可以继承接口.抽象类可以实现(implements)接口,抽象类可继承实体类,但前提是实体类必须有明确的构造函数. 抽象类中可以有静态的 main 方法.

18、数组去重

方法一:

思路:先计算旧数组中重复元素的个数,再用旧数组的长度减去重复元素个数得到新数组长度,再把旧数组中的元素复制到新数组

public static int[] clearRepeat(int[] arr){//先计算出重复元素的个数:int count = 0; //记录重复元素的个数for(int i = 0 ; i < arr.length-1 ; i++){for(int j = i+1 ; j<arr.length ; j++){if(arr[i]==arr[j]){

​                  count++;break;}}}

​       

​       //新数组 的长度int newLength = arr.length - count;//创建一个新的数组int[] newArr = new int[newLength];

int index = 0 ;    //新数组的索引值//遍历旧数组,把旧数组的元素存储到新数组中,存入之前要先判断该元素是否存在新元素中for(int i = 0 ; i< arr.length ; i++){int temp = arr[i]; //取出旧数组中的元素 boolean flag = false; //定义变量记录是否是重复元素,默认不是重复元素//拿着旧数组 的元素 与新数组的每个元素比较一次。for(int j = 0 ; j< newArr.length ; j++){if(temp==newArr[j]){

​                  flag = true;  //已经存在break;}}if(flag == false){

​              newArr[index++] = temp;}}return newArr;}

 

方法二:(利用集合)

int[] arr= {10,20,60,40,20,40};  //定义数组

​       Set set=new HashSet(); //创建set集合

   //遍历,把数组中元素存到set集合,利用set自动去重for (int i = 0; i < arr.length; i++) {

​           s.add(arr[i]);}

   Integer[] newarr = s.toArray(new Integer[s.size()]);

19、throws 和 throw 的区别(重点)

声明异常——throws:Java 语言中通过关键字 throws 声明某个方法可能抛出的各种异常.throws 可以同时声明多个异常,之间有逗号隔开.

抛出异常——throw:Java 语言中,可以通过使用throw 关键字来自行抛出异常. 同时它的区别还包括以下三方面:

(1) 作用不同:throw 用于程序员自行产生并抛出异常,throws 用于声明在该方法内抛出的异常.

(2)使用的位置不同:throw 位于方法体内部,可以作为单独语句使用.throws 必须跟在方法参数列表后面,不能单独使用.

(3)内容不同:throw 抛出一个异常对象,而且只能是一个.throws 后面跟异常类,而且可以跟多个异常类.

20、异常处理

1.捕获处理

格式:

​ try {

​ 可能发生异常的代码; //遇到异常时,jvm会创建异常对象,需要捕获

​ }catch(捕获的异常类型 变量名){

​ 处理异常的代码… //可以输出异常信息,可以把异常信息发送服务端,也可以跳转页面:系统为维护中。。。

​ }

细节

a.如果try块中代码出了异常经过了处理之后,那么try-catch块外面的代码可以正常执行

b.如果try块中出现了异常,那么try块中异常代码后面的代码不会执行

c.一个try块后面是可以有多个catch块的,也就是一个try块可以捕获多种类型的异常

d.一个try块可以捕获多种类型的异常,但是捕获的异常类型必须从小到大进行捕获,不然就是废话

e.异常的捕获可以根据不同类型的异常做出不同的处理方式,不一定要用Exception代替所有异常

2.throws抛出处理

格式:

  public void 方法名(形参)throws Exception{if(条件){throw new Exception(); //抛出异常}

​    Syetem.out.println(); //如果以上代码抛出异常,这句就不会执行

  }

细节:

a.如果一个方法的内部抛出(throw)了一个异常对象,那么必须要在方法上声明抛出(throws)

b.如果调用了一个声明抛出异常的方法,那么调用者必须要处理异常(声明抛出或捕获)

c.如果一个方法内部抛出了一个异常对象,那么throw语句后面的代码都不会执行了

​ (一个方法遇到throw关键字该方法也会马上停止执行)

d.在一种情况下只能抛出一种类型异常对象,可以用if(抛出一个)else if(再抛出一个)

e.子类重写方法后,该方法抛出的异常必须小于等于父类方法抛出的异常

21、运行时异常(RuntimeException)与一般异常有何异同? (重点)

常见的 RuntimeException**(运行时异常):**

 IndexOutOfBoundsException //(下标越界异常) 
 NullPointerException //(空指针异常) 
 NumberFormatException //(String转换为指定的数字类型异常) 
 ArithmeticException   //-(算术运算异常 如除数为0) 
 ArrayStoreException   //- (向数组中存放与声明类型不兼容对象异常) 
 SecurityException  // -(安全异常) 

// 一般异常
 FileNotFoundException // (文件未找到异常。) 
 IOException  // (操作输入流和输出流时可能出现的异常。) 
 EOFException // (文件已结束异常)

包括运行时异常(RuntimeException)对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。
一般异常必须要处理的异常,java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。

22、冒泡排序实现(重点)

口诀:n 个数字来比较,外层循环 N-1,内层循环 N-1-i,两两相比小靠前.

代码示例:

public static void main(String[] args) {

  int[] number=new int[]{22,12,33,45,66};
  int temp;
 
//冒泡排序

for(int i=0;i<number.length-1;i++){ 

  for(int j=0;j<number.length-1-i;j++){

      if(number[j]>number[j+1]){
          temp=number[j+1];
          number[j+1]=number[j]; 
          number[j]=temp;
         }
      }
   }
}

22.1、七大排序算法(java代码)

本文链接:https://blog.csdn.net/qq_41706670/article/details/79967692

用java实现冒泡,插入,选择等排序
冒泡排序(优化)
1.思想

从零号下标开始遍历,相邻的两个数相互比较,将最大的数沉底。

2.代码
public void bubble(int[] a){
        for (int i = 0; i < a.length - 1; i++) {
            for (int j = 0; j < a.length-1-i; j++) {
                if(a[j]>a[j+1]) swap(a,j,j+1);//把第j和第j+1下标的值交换
            }
        }
}

优化

 public void bubble(int[] a){
        for (int i = 0; i < a.length - 1; i++) {
            boolean s=false;//设置标记
            for (int j = 0; j < a.length-1-i; j++) {
                if(a[j]>a[j+1]){
                    swap(a,j,j+1);
                    s=true;
                }
            }
            if(!s)break;
        }
    }
选择排序
1.思想

假设零号位为最小值,然后拿零号位与后面的数依次比较,如果大于则交换。将数组遍历一遍以后,第一个值即为最小值。然后再将一号位设为最小值与后面比较。

2.代码
   public void select(int[] a) {
        for (int i = 0; i < a.length - 1; i++) {
            for (int j = i + 1; j < a.length; j++) {
                if (a[i] > a[j]) swap(a, i, j);
            }
        }
    }
插入排序
1.思想

以零号位开始为一个有序数组,依次加入数组下一位数,加入的这位数从前往后与前面的数比较,使它成为有序数组。如a[0]本身有序;加入下一位啊a[1],对a[0:1]进行排序,假如a[1]< a[0]则交换,交换后a[0:1]有序;加入下一位a[2],将a[0:2]有序,依次加入到数组最后一位,排序后数组有序。

2.代码
 public void insert(int[] a) {
        for (int i = 1; i < a.length; i++) {
            for (int j = i - 1; j >= 0; j--) {
                if (a[j + 1] < a[j]) swap(a, j + 1, j);
            }
        }
    }
快速排序(递归)
1.思想

任取待排序对象中的某个对象作为基准,按照该对象的关键码大小,将整个对象序列划分为左右两个子序列。其中,左侧子序列中所有的关键码都小于或等于基准对象的关键码;右侧子序列中所有对象的关键码都大于基准对象的关键码。此时基准对象所在的位置也就是该对象在该序列中最终应该安放的位置。然后递归的在左右子序列中重复实施上述的方法,直至所有的对象都放到相应的位置上为止。

2.代码
    public int portion(int[] a, int left, int right) {
 
        int tmp = a[left];
        while (left < right) {
            while (left < right && a[right] >= tmp) --right;
            a[left] = a[right];
            while (left < right && a[left] <= tmp) ++left;
            a[right] = a[left];
        }
        a[left] = tmp;
        return left;
    }
 
    public void quickSort(int[] a, int start, int end) {
        int par = portion(a, start, end);
        if (par > start + 1) quickSort(a, start, par - 1);
        if (par < end - 1) quickSort(a, par + 1, end);
    }
快速排序(非递归)
1.思想

递归的快排算法是编译器自动用栈来实现的,当递归层次比较深的时候,需要占用比较大的进程栈空间,会造成进程栈溢出的危险。因此我们可以自己用栈模拟递归过程,即每次从栈顶取出一部分做划分之后,都把新的两部分的起始位置分别入栈。

2.代码
public static int partion(int[] a, int low,int high) { 
       int tmp = a[low];
       while(low < high) {
              while(low < high && a[high] >= tmp ) {
                     --high;
              } 
              if(low >= high) {
                     break;
              }else {
                     a[low] = a[high];
              }
              while(low < high && a[low] <=tmp) {
                     ++low;
              }
              if(low >= high) {
                     break;
              }else {
                     a[high] = a[low];
              }             
       }
       a[low] =tmp;
       return low;
       }
       public static void QSort(int[] a) {
              int[] stack = new int[a.length];
              int top = 0;
              int low =0;
              int high = a.length-1;
              int par =partion(a, low, high);
              if(par > low + 1) {
                     stack[top++] = low;
                     stack[top++] = par - 1;
              }
              if(par < high - 1) {
                     stack[top++] = par + 1;
                     stack[top++] = high;
              }
              //出栈
              while(top > 0) {
                     high = stack[--top];//最后一次入栈top++了,所以这里用--top而不是top--
                     low = stack[--top];
                     
                     
                     par = partion(a,low,high);
                     
                     
                     if(par > low+1) {
                            stack[top++] = low;
                            stack[top++] = par - 1;
                     }
                     if(par  < high-1) {
                            stack[top++] = par+1;
                            stack[top++] = high;
                     }
              }
       }
shell排序(放大版的插入排序)
1.思想

(shell()方法的思想)先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的的记录放在同一个组中。先在各组内进行直接插入排序;
(shellSort()的思想)然后,取第二个增量d2< d1重复上述的分组和排序,直至所取的增量dt=1(dt< dt-l< …< d2< d1),即所有记录放在同一组中进行直接插入排序为止。

2.代码
    public void shell(int[] a, int gap) {
        for (int i = gap; i < a.length; i++) {
            for (int j = i - gap; j >= 0; j = j - gap) {
                if (a[j + gap] < a[j]) swap(a, j + gap, j);
            }
        }
    }
 
    public void shellSort(int[] a) {
        int[] d = {7, 3, 1};
        for (int i = 0; i < d.length; i++) {
            shell(a, d[i]);
        }
    }
堆排序
1.思想

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

2.代码
 public void adjust(int[] a, int start, int end) {
        int tmp = a[start];
        for (int i = start * 2 + 1; i <= end; i = i * 2 + 1) {
            if (i < end && a[i] < a[i + 1]) i++;
            if (a[i] > tmp) {
                a[start] = a[i];
                start = i;
            } else break;
        }
        a[start] = tmp;
    }
 
    public void heapSort(int[] a) {
        for (int i = a.length / 2 - 1; i >= 0; i--) {
            adjust(a, i, a.length - 1);
        }
        for (int i = 0; i < a.length - 1; i++) {
            swap(a, 0, a.length - 1 - i);
            adjust(a, 0, a.length - 2 - i);
        }
    }
归并排序
1.思想

基本思路就是将数组分成按a.length/2n个组2n个数,如下面左边第三行是第二次分组,分了5个组,每组21个数。然后建立一个临时数组,将相邻的两个组合并成一个有序的组,放入临时数组里。直到2n>=a.length。

2.代码
       public static void mergeSort(int[] a) {
              for (int i = 1; i < a.length; i = i * 2) {
                     merge(a, i);
              }
       }
 
       public static void merge(int[] a, int gap) {
              int start1 = 0;//第一组开头
              int end1 = start1 + gap - 1;//第一组结尾
              int start2 = end1 + 1;//第二组开头
              int end2 = start2 + gap - 1 < a.length - 1 ? start2 + gap - 1 : a.length - 1;//第二组结尾
              int[] tmpa = new int[a.length];//用来合并的临时数组
              int i = 0;
              while (start2 < a.length) {//将相邻为gap的组,利用临时数组,两两合并
                     while (start1 <= end1 && start2 <= end2) {
                            if (a[start1] < a[start2]) tmpa[i++] = a[start1++];
                             else tmpa[i++] = a[start2++];
                     }
                     while (start1 <= end1)       tmpa[i++] = a[start1++];//如果第二组先遍历完,将第一组补到临时数组后面
                     while (start2 <= end2)       tmpa[i++] = a[start2++];//如果第一组先遍历完,将第二组补到临时数组后面
                     start1 = end2 + 1;
                     end1 = start1 + gap - 1;
                     start2 = end1 + 1;
                     end2 = start2 + gap - 1 < a.length - 1 ? start2 + gap - 1 : a.length - 1;//如果最后一次只有第一组,不够第二组,退出循环
              }
              while (i < a.length) tmpa[i++] = a[start1++];//将最后一次第一组补上
              for(int j = 0;j<a.length;j++) a[j] = tmpa[j];//将临时数组复制到原数组
       }

23、谈谈你对 static 关键词的理解(重点)

声明为 static 的方法有以下几条限制:

  1. 它们仅能调用其他的 static 方法.

  2. 它们只能访问 static 数据.

  3. 它们不能以任何方式引用 this 或 super.

static的特点:

1.static是一个修饰符,用于修饰成员。

2.static修饰的成员被所有的对象所共享。

3.static优先于对象存在,因为static的成员随着类的加载已经存在了。

4.static修饰的成员多了一种调用方式,就可以直接被类名所调用,类名.静态成员。

5.static修饰的数据是共享数据,对象中的存储的是特有数据

6.static代码块优先加载

static关键字,像一个父类,一个子类,父类子类都有静态代码块有构造代码块有构造方法,调用子类的时候它们的执行顺序?

(1.父静态代码块2.子静态代码块3.父构造代码块4.父构造方法5.子构造代码块6.子构造方法)

24、static为什么一般与final一起用?

static和final的意义是不同的,
static修饰的时候代表对象是静态的,而final修饰的时候代表对象只能赋值一次,
他们连用的时候是因为定义的那个对象既要它是静态的,也要求它的值不能再被修改。
举例说明:
static int a=1;
static final b=1;
这里a和b的区别在于,a在程序里可以被重新赋值为2或3或等等的整数,而b在程序里不能被重新赋值,b永远都为1,也就是说b是一个常量。
final int c=1;
static final b=1;
这里c和b的区别在于,b存放在静态空间,不会在程序运行时被释放,它永远占着内存直到程序终止,而c在程序用完它而不会再用到它的时候就会被自动释放,不再占用内存。

当一个常数或字符串我们需要在程序里反复反复使用的时候,我们就可以把它定义为static final,这样内存就不用重复的申请和释放空间。

final,常量,变量的命名是全小写,常量是全大写,final可以修饰类,属性,方法

25、Collection 和 Collections 的区别(重点)

Collection 是集合类的上级接口,继承与他的接口主要有 Set 和 List.

Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作.

collection与collections的区别,前者是提供集合的接口(map除外),后者是提供操作集合的方法,比如排序sort,反转Reverse,替换Fill等等。

26、char 型变量中能不能存贮一个中文汉字?为什么? (重点)

char 型变量是用来存储 Unicode 编码的字符的,unicode 编码字符集中包含了汉字, 所以,char 型变量中当然可以存储汉字啦.不过,如果某个特殊的汉字没有被包含在unicode 编码字符集中,那么,这个 char 型变量中就不能存储这个特殊汉字.

补充说明:unicode 编码占用两个字节,所以,char 类型的变量也是占用两个字节.

27、 什么是java 序列化,如何实现java 序列化?或者请解释Serializable 接口的作用.

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化(将对象转换成二进制)。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间,序列化是为了解决在对对象流进行读写操作时所引发的问题。把对象转换为字节序列的过程称为对象的序列化把字节序列恢复为对象的过程称为对象的反序列化

序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

在java中如何实现序列化: 首先我们要把准备要序列化类,实现 Serializabel接口

对象的序列化主要有两种用途:

1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

2) 在网络上传送对象的字节序列。

28、 拦截器和过滤器的区别(重点)

1,拦截器是基于 java 的反射机制的,而过滤器是基于函数回调.

2,拦截器不依赖与servlet 容器,过滤器依赖与 servlet 容器.

3,拦截器只能对 action 请求起作用,而过滤器则可以对几乎所有的请求起作用.

4,拦截器可以访问 action 上下文、值栈里的对象,而过滤器不能访问.

5,在 action 的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次

6,拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

7,拦截器需要在Spring配置文件中配置,过滤器只需要在web.xml中配置

29、 悲观锁乐观锁的区别? (重点)

悲观锁(Pessimistic Lock), 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁.传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁.

乐观锁(Optimistic Lock),每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制.乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition 机制的其实都是提供的乐观锁.

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量.但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适.

乐观锁 事例

30、类变量和实例变量区别

类变量是所有对象共享的,所有的实例对象都共用一个类变量,内存中只有一处空间存放类变量的值。如果一个对象改变了类变量的值,着其他对象得到的类变量的值都是改变后的结果

类变量由static修饰,随着类一起加载,且只加载一次。

实例变量(成员变量):仅初始值一样,每个对象的成员变量的值改变互不影响

31、类方法和实例方法区别

类方法(静态方法):类方法只能由类调用,类方法只能调用其他类方法,不能调用实例方法;

类方法由static修饰,随着类一起加载,且只加载一次。

实例方法(非静态方法):没有static修饰的方法,实例方法只能由实例对象调用,在对象被创建后才会分配内存空间;

32、Java的访问修饰符及作用区域

(1)public:可以被所有其他类所访问。

(2)private:只能被自己访问和修改。

(3)protected:自身,子类及同一个包中类可以访问。

(4) default(默认):同一包中的类可以访问,声明时没有加修饰符,认为是friendly。

33、字节流和字符流的区别

· 字节流中输出数据主要是使用OutputStream完成,输入使的是InputStream;

字符流中输出主要是使用Writer类完成,输入流主要使用Reader类完成(四个类都是抽象类)

· 字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元(两个字节)。

· 字节流默认不使用缓冲区;字符流使用缓冲区。

· 字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。

34、面向对象都有哪些特性以及你对这些特性的理解

1)继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

2) 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供简单的编程接口。

3) 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对 A 系统来说都是透明的。

方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象精髓的东西,要实现多态需要做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

4)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

注意:默认情况下面向对象有3大特性,封装、继承、多态,如果面试官问让说出4大特性,那么我们就把抽象加上去。

35、JDK1.8 新特性(全)

本文链接:https://blog.csdn.net/qq_29411737/article/details/80835658

JDK1.8 新特性

本文主要介绍了JDK1.8版本中的一些新特性,乃作者视频观后笔记,仅供参考。

jdk1.8新特性知识点:

1、Lambda表达式

2、函数式接口

3、方法引用和构造器调用

4、Stream API

5、接口中的默认方法和静态方法

6、新时间日期API

7、在jdk1.8中对hashMap等map集合的数据结构优化。hashMap数据结构的优化
原来的hashMap采用的数据结构是哈希表(数组+链表),hashMap默认大小是16,一个0-15索引的数组,如何往里面存储元素,首先调用元素的hashcode
方法,计算出哈希码值,经过哈希算法算成数组的索引值,如果对应的索引处没有元素,直接存放,如果有对象在,那么比较它们的equals方法比较内容
如果内容一样,后一个value会将前一个value的值覆盖,如果不一样,在1.7的时候,后加的放在前面,形成一个链表,形成了碰撞,在某些情况下如果链表
无限下去,那么效率极低,碰撞是避免不了的
加载因子:0.75,数组扩容,达到总容量的75%,就进行扩容,但是无法避免碰撞的情况发生
在1.8之后,在数组+链表+红黑树来实现hashmap,当碰撞的元素个数大于8时 & 总容量大于64,会有红黑树的引入
除了添加之后,效率都比链表高,1.8之后链表新进元素加到末尾
ConcurrentHashMap (锁分段机制),concurrentLevel,jdk1.8采用CAS算法(无锁算法,不再使用锁分段),数组+链表中也引入了红黑树的使用

Lambda表达式

lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码

先来体验一下lambda最直观的优点:简洁代码

//匿名内部类

 Comparator<Integer> cpt = new Comparator<Integer>() {

   @Override

   public int compare(Integer o1, Integer o2) {return Integer.compare(o1,o2);

   }

 };

 

 TreeSet<Integer> set = new TreeSet<>(cpt);

 

 System.out.println("=========================");

 

 //使用lambda表达式

 Comparator<Integer> cpt2 = (x,y) -> Integer.compare(x,y);

 TreeSet<Integer> set2 = new TreeSet<>(cpt2);

只需要一行代码,极大减少代码量!!

这样一个场景,在商城浏览商品信息时,经常会有条件的进行筛选浏览,例如要选颜色为红色的、价格小于8000千的….

// 筛选颜色为红色

public List<Product> filterProductByColor(List<Product> list){

  List<Product> prods = new ArrayList<>();

  for (Product product : list){if ("红色".equals(product.getColor())){

​      prods.add(product);}

  }

  return prods;

 }

 

// 筛选价格小于8千的

public List<Product> filterProductByPrice(List<Product> list){

  List<Product> prods = new ArrayList<>();

  for (Product product : list){if (product.getPrice() < 8000){

​      prods.add(product);}

  }

  return prods;

 }

我们发现实际上这些过滤方法的核心就只有if语句中的条件判断,其他均为模版代码,每次变更一下需求,都需要新增一个方法,然后复制黏贴,假设这个过滤方法有几百行,那么这样的做法难免笨拙了一点。如何进行优化呢?

优化一:使用设计模式

定义一个MyPredicate接口

public interface MyPredicate <T> {

  boolean test(T t);

}

如果想要筛选颜色为红色的商品,定义一个颜色过滤类

public class ColorPredicate implements MyPredicate <Product> {

 

   private static final String RED = "红色";

 

   @Override

   public boolean test(Product product) {return RED.equals(product.getColor());

   }

定义过滤方法,将过滤接口当做参数传入,这样这个过滤方法就不用修改,在实际调用的时候将具体的实现类传入即可。

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){

​    List<Product> prods = new ArrayList<>();for (Product prod : list){if (mp.test(prod)){

​        prods.add(prod);}}return prods;

  }

例如,如果想要筛选价格小于8000的商品,那么新建一个价格过滤类既可

public class PricePredicate implements MyPredicate<Product> {

  @Override

  public boolean test(Product product) {return product.getPrice() < 8000;

  }

}

这样实现的话可能有人会说,每次变更需求都需要新建一个实现类,感觉还是有点繁琐呀,那么再来优化一下

优化二:使用匿名内部类

定义过滤方法:

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){

​    List<Product> prods = new ArrayList<>();for (Product prod : list){if (mp.test(prod)){

​        prods.add(prod);}}return prods;

  }

调用过滤方法的时候:

// 按价格过滤

public void test2(){

  filterProductByPredicate(proList, new MyPredicate<Product>() {@Overridepublic boolean test(Product product) {return product.getPrice() < 8000;}

  });

}

 

 // 按颜色过滤

 public void test3(){

   filterProductByPredicate(proList, new MyPredicate<Product>() {@Overridepublic boolean test(Product product) {return "红色".equals(product.getColor());}

   });

 }

使用匿名内部类,就不需要每次都新建一个实现类,直接在方法内部实现。看到匿名内部类,不禁想起了Lambda表达式。

优化三:使用lambda表达式

定义过滤方法:

public List<Product> filterProductByPredicate(List<Product> list,MyPredicate<Product> mp){

​    List<Product> prods = new ArrayList<>();for (Product prod : list){if (mp.test(prod)){

​        prods.add(prod);}}return prods;

  }

使用lambda表达式进行过滤

@Test

public void test4(){

   List<Product> products = filterProductByPredicate(proList, (p) -> p.getPrice() < 8000);

   for (Product pro : products){

​     System.out.println(pro);

   }

 }

在jdk1.8中还有更加简便的操作 Stream API

优化四:使用Stream API

甚至不用定义过滤方法,直接在集合上进行操作

// 使用jdk1.8中的Stream API进行集合的操作

@Test

public void test(){

  // 根据价格过滤

  proList.stream().fliter((p) -> p.getPrice() <8000).limit(2).forEach(System.out::println);

 

  // 根据颜色过滤

  proList.stream().fliter((p) -> "红色".equals(p.getColor())).forEach(System.out::println);

 

  // 遍历输出商品名称

  proList.stream().map(Product::getName).forEach(System.out::println);

}

Lmabda表达式的语法总结: () -> ();

前置 语法
无参数无返回值 () -> System.out.println(“Hello WOrld”)
有一个参数无返回值 (x) -> System.out.println(x)
有且只有一个参数无返回值 x -> System.out.println(x)
有多个参数,有返回值,有多条lambda体语句 (x,y) -> {System.out.println(“xxx”);return xxxx;};
有多个参数,有返回值,只有一条lambda体语句 (x,y) -> xxxx

口诀:左右遇一省括号,左侧推断类型省

注:当一个接口中存在多个抽象方法时,如果使用lambda表达式,并不能智能匹配对应的抽象方法,因此引入了函数式接口的概念

函数式接口

函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。

什么是函数式接口?
简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解:@FunctionalInterface

常见的四大函数式接口

· Consumer < T > :消费型接口,有参无返回值

  @Test

  public void test(){changeStr("hello",(str) -> System.out.println(str));

  }

 

  /**

   \* Consumer 消费型接口

   \* @param str

   \* @param con

   */

  public void changeStr(String str, Consumer<String> con){

​    con.accept(str);

  }

· Supplier < T > :供给型接口,无参有返回值

  @Test

  public void test2(){

​    String value = getValue(() -> "hello");

​    System.out.println(value);

  }

 

  /**

   \* Supplier 供给型接口

   \* @param sup

   \* @return

   */

  public String getValue(Supplier<String> sup){return sup.get();

  }

· Function ::函数式接口,有参有返回值

  @Test

  public void test3(){

​    Long result = changeNum(100L, (x) -> x + 200L);

​    System.out.println(result);

  }

 

  /**

   \* Function 函数式接口

   \* @param num

   \* @param fun

   \* @return

   */

  public Long changeNum(Long num, Function<Long, Long> fun){return fun.apply(num);

  }

· Predicate《T》: 断言型接口,有参有返回值,返回值是boolean类型

public void test4(){boolean result = changeBoolean("hello", (str) -> str.length() > 5);

​    System.out.println(result);

  }

 

  /**

   \* Predicate 断言型接口

   \* @param str

   \* @param pre

   \* @return

   */

  public boolean changeBoolean(String str, Predicate<String> pre){return pre.test(str);

  }

在四大核心函数式接口基础上,还提供了诸如BiFunction、BinaryOperation、toIntFunction等扩展的函数式接口,都是在这四种函数式接口上扩展而来的,不做赘述。

总结:函数式接口的提出是为了让我们更加方便的使用lambda表达式,不需要自己再手动创建一个函数式接口,直接拿来用就好了,贴

方法引用

若lambda体中的内容有方法已经实现了,那么可以使用“方法引用”
也可以理解为方法引用是lambda表达式的另外一种表现形式并且其语法比lambda表达式更加简单

(a) 方法引用
三种表现形式:

  1. 对象::实例方法名
  2. 类::静态方法名
  3. 类::实例方法名 (lambda参数列表中第一个参数是实例方法的调用 者,第二个参数是实例方法的参数时可用)
 public void test() {/**

​    *注意:

​    \*  1.lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!

​    \*  2.若lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method

​    *

​    */

​    Consumer<Integer> con = (x) -> System.out.println(x);

​    con.accept(100);// 方法引用-对象::实例方法

​    Consumer<Integer> con2 = System.out::println;

​    con2.accept(200);// 方法引用-类名::静态方法名

​    BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y);

​    BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;

​    Integer result = biFun2.apply(100, 200);// 方法引用-类名::实例方法名

​    BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);

​    BiFunction<String, String, Boolean> fun2 = String::equals;

​    Boolean result2 = fun2.apply("hello", "world");

​    System.out.println(result2);

  }

(b)构造器引用
格式:ClassName::new

public void test2() {// 构造方法引用 类名::new

​    Supplier<Employee> sup = () -> new Employee();

​    System.out.println(sup.get());

​    Supplier<Employee> sup2 = Employee::new;

​    System.out.println(sup2.get());// 构造方法引用 类名::new (带一个参数)

​    Function<Integer, Employee> fun = (x) -> new Employee(x);

​    Function<Integer, Employee> fun2 = Employee::new;

​    System.out.println(fun2.apply(100));

 }

©数组引用

格式:Type[]::new

public void test(){// 数组引用

​    Function<Integer, String[]> fun = (x) -> new String[x];

​    Function<Integer, String[]> fun2 = String[]::new;

​    String[] strArray = fun2.apply(10);

​    Arrays.stream(strArray).forEach(System.out::println);

}

Stream API

Stream操作的三个步骤

· 创建stream

· 中间操作(过滤、map)

· 终止操作

stream的创建:

// 1,校验通过Collection 系列集合提供的stream()或者paralleStream()

  List<String> list = new ArrayList<>();

  Strean<String> stream1 = list.stream();

 

  // 2.通过Arrays的静态方法stream()获取数组流

  String[] str = new String[10];

  Stream<String> stream2 = Arrays.stream(str);

 

  // 3.通过Stream类中的静态方法of

  Stream<String> stream3 = Stream.of("aa","bb","cc");

 

  // 4.创建无限流

  // 迭代

  Stream<Integer> stream4 = Stream.iterate(0,(x) -> x+2);

 

  //生成

  Stream.generate(() ->Math.random());

Stream的中间操作:

/**

  \* 筛选 过滤 去重

  */

 emps.stream().filter(e -> e.getAge() > 10).limit(4).skip(4)// 需要流中的元素重写hashCode和equals方法.distinct().forEach(System.out::println);

 

 

 /**

  \* 生成新的流 通过map映射

  */

 emps.stream().map((e) -> e.getAge()).forEach(System.out::println);

 

 

 /**

  \* 自然排序 定制排序

  */

 emps.stream().sorted((e1 ,e2) -> {if (e1.getAge().equals(e2.getAge())){return e1.getName().compareTo(e2.getName());} else{return e1.getAge().compareTo(e2.getAge());}}).forEach(System.out::println);

Stream的终止操作:

 /**

​     \*   查找和匹配

​     \*     allMatch-检查是否匹配所有元素

​     \*     anyMatch-检查是否至少匹配一个元素

​     \*     noneMatch-检查是否没有匹配所有元素

​     \*     findFirst-返回第一个元素

​     \*     findAny-返回当前流中的任意元素

​     \*     count-返回流中元素的总个数

​     \*     max-返回流中最大值

​     \*     min-返回流中最小值

​     *//**

​     \* 检查是否匹配元素

​     */boolean b1 = emps.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));

​    System.out.println(b1);boolean b2 = emps.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));

​    System.out.println(b2);boolean b3 = emps.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));

​    System.out.println(b3);

 

​    Optional<Employee> opt = emps.stream().findFirst();

​    System.out.println(opt.get());// 并行流

​    Optional<Employee> opt2 = emps.parallelStream().findAny();

​    System.out.println(opt2.get());long count = emps.stream().count();

​    System.out.println(count);

 

​    Optional<Employee> max = emps.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));

​    System.out.println(max.get());

 

​    Optional<Employee> min = emps.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));

​     System.out.println(min.get());

还有功能比较强大的两个终止操作 reduce和collect
reduce操作: reduce:(T identity,BinaryOperator)/reduce(BinaryOperator)-可以将流中元素反复结合起来,得到一个值

/**

​     \* reduce :规约操作

​     */

​    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

​    Integer count2 = list.stream().reduce(0, (x, y) -> x + y);

​    System.out.println(count2);

 

​    Optional<Double> sum = emps.stream().map(Employee::getSalary).reduce(Double::sum);

​    System.out.println(sum);

collect操作:Collect-将流转换为其他形式,接收一个Collection接口的实现,用于给Stream中元素做汇总的方法

/**

​     \* collect:收集操作

​     */

 

​    List<Integer> ageList = emps.stream().map(Employee::getAge).collect(Collectors.toList());

​    ageList.stream().forEach(System.out::println);

并行流和串行流

在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api中声明可以通过parallel()与sequential()方法在并行流和串行流之间进行切换。
jdk1.8并行流使用的是fork/join框架进行并行操作

ForkJoin框架

Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
关键字:递归分合、分而治之。
采用 “工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线
程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的
处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因
无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果
某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子
问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程
的等待时间,提高了性能.。

/**

 \* 要想使用Fark—Join,类必须继承

 \* RecursiveAction(无返回值)

 \* Or

 \* RecursiveTask(有返回值)

*

*/

public class ForkJoin extends RecursiveTask<Long> {

 

  /**

   \* 要想使用Fark—Join,类必须继承RecursiveAction(无返回值) 或者

   \* RecursiveTask(有返回值)

   *

   \* @author Wuyouxin

   */

  private static final long serialVersionUID = 23423422L;

 

  private long start;

  private long end;

 

  public ForkJoin() {

  }

 

  public ForkJoin(long start, long end) {this.start = start;this.end = end;

  }

 

  // 定义阙值

  private static final long THRESHOLD = 10000L;

 

  @Override

  protected Long compute() {if (end - start <= THRESHOLD) {long sum = 0;for (long i = start; i < end; i++) {

​        sum += i;}return sum;} else {long middle = (end - start) / 2;

​      ForkJoin left = new ForkJoin(start, middle);//拆分子任务,压入线程队列

​      left.fork();

​      ForkJoin right = new ForkJoin(middle + 1, end);

​      right.fork();//合并并返回return left.join() + right.join();}

  }

 

  /**

   \* 实现数的累加

   */

  @Test

  public void test1() {//开始时间

​    Instant start = Instant.now();//这里需要一个线程池的支持

​    ForkJoinPool pool = new ForkJoinPool();

 

​    ForkJoinTask<Long> task = new ForkJoin(0L, 10000000000L);// 没有返回值   pool.execute();// 有返回值long sum = pool.invoke(task);//结束时间

​    Instant end = Instant.now();

​    System.out.println(Duration.between(start, end).getSeconds());

  }

 

  /**

   \* java8 并行流 parallel()

   */

  @Test

  public void test2() {//开始时间

​    Instant start = Instant.now();// 并行流计算  累加求和

​    LongStream.rangeClosed(0, 10000000000L).parallel().reduce(0, Long :: sum);//结束时间

​    Instant end = Instant.now();

​    System.out.println(Duration.between(start, end).getSeconds());

  }

 

  @Test

  public void test3(){

​    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

​    list.stream().forEach(System.out::print);

 

​    list.parallelStream().forEach(System.out::print);

  }

展示多线程的效果:

@Test

  public void test(){// 并行流 多个线程执行

​    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

​    numbers.parallelStream().forEach(System.out::print);//

​    System.out.println("=========================");

​    numbers.stream().sequential().forEach(System.out::print);

  }

Optional容器

使用Optional容器可以快速的定位NPE,并且在一定程度上可以减少对参数非空检验的代码量。

· 1

/**

   \*   Optional.of(T t); // 创建一个Optional实例

   \*   Optional.empty(); // 创建一个空的Optional实例

   \*   Optional.ofNullable(T t); // 若T不为null,创建一个Optional实例,否则创建一个空实例

   \*   isPresent();  // 判断是够包含值

   \*   orElse(T t);  //如果调用对象包含值,返回该值,否则返回T

   \*   orElseGet(Supplier s); // 如果调用对象包含值,返回该值,否则返回s中获取的值

   \*   map(Function f): // 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty();

   \*   flatMap(Function mapper);// 与map类似。返回值是Optional

   *

   \*   总结:Optional.of(null) 会直接报NPE

   */

 

Optional<Employee> op = Optional.of(new Employee("zhansan", 11, 12.32, Employee.Status.BUSY));

​    System.out.println(op.get());// NPE

​    Optional<Employee> op2 = Optional.of(null);

​    System.out.println(op2);

@Test

  public void test2(){

​    Optional<Object> op = Optional.empty();

​    System.out.println(op);// No value present

​    System.out.println(op.get());

  }

@Test

  public void test3(){

​    Optional<Employee> op = Optional.ofNullable(new Employee("lisi", 33, 131.42, Employee.Status.FREE));

​    System.out.println(op.get());

 

​    Optional<Object> op2 = Optional.ofNullable(null);

​    System.out.println(op2);// System.out.println(op2.get());

  }

  @Test

  public void test5(){

​    Optional<Employee> op1 = Optional.ofNullable(new Employee("张三", 11, 11.33, Employee.Status.VOCATION));

​    System.out.println(op1.orElse(new Employee()));

​    System.out.println(op1.orElse(null));

  }

 

  @Test

  public void test6(){

​    Optional<Employee> op1 = Optional.of(new Employee("田七", 11, 12.31, Employee.Status.BUSY));

​    op1 = Optional.empty();

​    Employee employee = op1.orElseGet(() -> new Employee());

​    System.out.println(employee);

  }

 

  @Test

  public void test7(){

​    Optional<Employee> op1 = Optional.of(new Employee("田七", 11, 12.31, Employee.Status.BUSY));

​    System.out.println(op1.map( (e) -> e.getSalary()).get());

  }

接口中可以定义默认实现方法和静态方法

在接口中可以使用default和static关键字来修饰接口中定义的普通方法

public interface Interface {

  default String getName(){return "zhangsan";

  }

 

  static String getName2(){return "zhangsan";

  }

}

在JDK1.8中很多接口会新增方法,为了保证1.8向下兼容,1.7版本中的接口实现类不用每个都重新实现新添加的接口方法,引入了default默认实现,static的用法是直接用接口名去调方法即可。当一个类继承父类又实现接口时,若后两者方法名相同,则优先继承父类中的同名方法,即“类优先”,如果实现两个同名方法的接口,则要求实现类必须手动声明默认实现哪个接口中的方法。

新的日期API LocalDate | LocalTime | LocalDateTime

新的日期API都是不可变的,更使用于多线程的使用环境中

  @Test

  public void test(){// 从默认时区的系统时钟获取当前的日期时间。不用考虑时区差

​    LocalDateTime date = LocalDateTime.now();//2018-07-15T14:22:39.759

​    System.out.println(date);

 

​    System.out.println(date.getYear());

​    System.out.println(date.getMonthValue());

​    System.out.println(date.getDayOfMonth());

​    System.out.println(date.getHour());

​    System.out.println(date.getMinute());

​    System.out.println(date.getSecond());

​    System.out.println(date.getNano());// 手动创建一个LocalDateTime实例

​    LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31);

​    System.out.println(date2);// 进行加操作,得到新的日期实例

​    LocalDateTime date3 = date2.plusDays(12);

​    System.out.println(date3);// 进行减操作,得到新的日期实例

​    LocalDateTime date4 = date3.minusYears(2);

​    System.out.println(date4);

  }

  @Test

  public void test2(){// 时间戳 1970年1月1日00:00:00 到某一个时间点的毫秒值// 默认获取UTC时区

​    Instant ins = Instant.now();

​    System.out.println(ins);

 

​    System.out.println(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli());

​    System.out.println(System.currentTimeMillis());

 

​    System.out.println(Instant.now().toEpochMilli());

​    System.out.println(Instant.now().atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli());

  }

  @Test

  public void test3(){// Duration:计算两个时间之间的间隔// Period:计算两个日期之间的间隔

 

​    Instant ins1 = Instant.now();try {

​      Thread.sleep(1000);} catch (InterruptedException e) {

​      e.printStackTrace();}

​    Instant ins2 = Instant.now();

​    Duration dura = Duration.between(ins1, ins2);

​    System.out.println(dura);

​    System.out.println(dura.toMillis());

 

​    System.out.println("======================");

​    LocalTime localTime = LocalTime.now();try {

​      Thread.sleep(1000);} catch (InterruptedException e) {

​      e.printStackTrace();}

​    LocalTime localTime2 = LocalTime.now();

​    Duration du2 = Duration.between(localTime, localTime2);

​    System.out.println(du2);

​    System.out.println(du2.toMillis());

  }

@Test

  public void test4(){

​    LocalDate localDate =LocalDate.now();try {

​      Thread.sleep(1000);} catch (InterruptedException e) {

​      e.printStackTrace();}

 

​    LocalDate localDate2 = LocalDate.of(2016,12,12);

​    Period pe = Period.between(localDate, localDate2);

​    System.out.println(pe);

  }

  @Test

  public void test5(){// temperalAdjust 时间校验器// 例如获取下周日 下一个工作日

​    LocalDateTime ldt1 = LocalDateTime.now();

​    System.out.println(ldt1);// 获取一年中的第一天

​    LocalDateTime ldt2 = ldt1.withDayOfYear(1);

​    System.out.println(ldt2);// 获取一个月中的第一天

​    LocalDateTime ldt3 = ldt1.withDayOfMonth(1);

​    System.out.println(ldt3);

 

​    LocalDateTime ldt4 = ldt1.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));

​    System.out.println(ldt4);// 获取下一个工作日

​    LocalDateTime ldt5 = ldt1.with((t) -> {

​      LocalDateTime ldt6 = (LocalDateTime)t;

​      DayOfWeek dayOfWeek = ldt6.getDayOfWeek();if (DayOfWeek.FRIDAY.equals(dayOfWeek)){return ldt6.plusDays(3);}else if (DayOfWeek.SATURDAY.equals(dayOfWeek)){return ldt6.plusDays(2);}else {return ldt6.plusDays(1);}});

​    System.out.println(ldt5);

}

@Test

  public void test6(){// DateTimeFormatter: 格式化时间/日期// 自定义格式

​    LocalDateTime ldt = LocalDateTime.now();

​    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");

​    String strDate1 = ldt.format(formatter);

​    String strDate = formatter.format(ldt);

​    System.out.println(strDate);

​    System.out.println(strDate1);// 使用api提供的格式

​    DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE;

​    LocalDateTime ldt2 = LocalDateTime.now();

​    String strDate3 = dtf.format(ldt2);

​    System.out.println(strDate3);// 解析字符串to时间

​    DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

​    LocalDateTime time = LocalDateTime.now();

​    String localTime = df.format(time);

​    LocalDateTime ldt4 = LocalDateTime.parse("2017-09-28 17:07:05",df);

​    System.out.println("LocalDateTime转成String类型的时间:"+localTime);

​    System.out.println("String类型的时间转成LocalDateTime:"+ldt4);

  }

·    1  // ZoneTime ZoneDate    ZoneDateTime

  @Test

  public void test7(){

​    LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));

​    System.out.println(now);

 

​    LocalDateTime now2 = LocalDateTime.now();

​    ZonedDateTime zdt = now2.atZone(ZoneId.of("Asia/Shanghai"));

​    System.out.println(zdt);

 

​    Set<String> set = ZoneId.getAvailableZoneIds();

​    set.stream().forEach(System.out::println);

  }

· 1补充:

表示日期的LocalDate
表示时间的LocalTime
表示日期时间的LocalDateTime

新的日期API的几个优点:

* 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum

* java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。

* java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍

* 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。

· LocalDate

public static void localDateTest() {//获取当前日期,只含年月日 固定格式 yyyy-MM-dd  2018-05-04

​    LocalDate today = LocalDate.now();// 根据年月日取日期,5月就是5,

​    LocalDate oldDate = LocalDate.of(2018, 5, 1);// 根据字符串取:默认格式yyyy-MM-dd,02不能写成2

​    LocalDate yesteday = LocalDate.parse("2018-05-03");// 如果不是闰年 传入29号也会报错

​    LocalDate.parse("2018-02-29");

  }

·    LocalDate常用转化

  /**

   \* 日期转换常用,第一天或者最后一天...

   */

  public static void localDateTransferTest(){//2018-05-04

​    LocalDate today = LocalDate.now();// 取本月第1天: 2018-05-01

​    LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());// 取本月第2天:2018-05-02

​    LocalDate secondDayOfThisMonth = today.withDayOfMonth(2);// 取本月最后一天,再也不用计算是28,29,30还是31: 2018-05-31

​    LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());// 取下一天:2018-06-01

​    LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1);// 取2018年10月第一个周三 so easy?: 2018-10-03

​    LocalDate thirdMondayOf2018 = LocalDate.parse("2018-10-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY));

  }

·    LocalTime

 public static void localTimeTest(){//16:25:46.448(纳秒值)

​    LocalTime todayTimeWithMillisTime = LocalTime.now();//16:28:48 不带纳秒值

​    LocalTime todayTimeWithNoMillisTime = LocalTime.now().withNano(0);

​    LocalTime time1 = LocalTime.parse("23:59:59");

  }

·    LocalDateTime

public static void localDateTimeTest(){//转化为时间戳 毫秒值long time1 = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();long time2 = System.currentTimeMillis();//时间戳转化为localdatetime

​    DateTimeFormatter df= DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS");

 

​    System.out.println(df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time1),ZoneId.of("Asia/Shanghai"))));

  }

36、你对反射的理解

内容目录

一、反射相关概念

二、应用场景

三、反射中Class的获取方式

四、创建类对象的方式

五、用代码说话

六、反射优缺点

一、反射相关概念

1 、编译期 & 运行期

编译期: 编译器把源代码翻译成机器能识别的代码,如Java代码编译成jvm识别的字节码文件。

运行期: 将可执行文件交给操作系统去执行。

2 、静态加载类 & 动态加载类

静态加载类: 编译时刻加载类

动态加载类: 运行时刻加载类

3 、反射:

【说法一】

在运行状态中,

对于任意一个类, 都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性;

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

【说法二】

允许程序在运行时取得任何一个已知名称的class的内部信息,包括修饰符,属性,方法等,并可于运行时改变属性内容或调用方法

【说法三】

反射是运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

【说法四】

通过字节码文件,使用相应的类,成员变量,方法,注解等。

【说法五】

在运行时判断任意一个对象所属的类;

在运行时构造任意一个类的对象;

在运行时判断任意一个类所具有的成员变量和方法;

在运行时调用任意一个对象的方法;

生成动态代理。

注:5种说法皆可

4 、相关类

Class 类: 代表类的实体,在运行的Java应用程序中表示类和接口

Field 类: 代表类的成员变量(即类的属性)

Method 类: 代表类的方法

Constructor 类: 代表类的构造方法

二、应用场景

1 、JDBC原生代码注册驱动

2 、Hibernate的实体类

3 、Mybatis框架

4 、Spring的AOP等

原理:

在配置文件(xml或properties)里配置好相关信息,java代码解析配置文件信息,运用反射机制,根据获取的配置信息(值)获取Class实例,进行动态配置。

如:Mybatis框架

通过读取sql,得到字段名称(与属性名相同)并用反射的方式将对象创建出来,调用其set方法进行参数注入,最后放到List中。

三、反射中Class的获取方式

1、Class personClazz = new Person().getClass();

2、Class personClazz =Person.class;

3、Class personClazz = Class.forName(“com.demo.Person”);

四、创建类对象的方式

1 、通过Class对象的newInstance()——>只能使用默认的无参数构造方法

Class clz = Person.class;

Person person = (Person)clz.newInstance();

2 、通过Constructor对象的newInstance()

Class clz = Person.class;

Constructor constructor = clz.getConstructor();

Person person = (Person)constructor.newInstance(“张三”,18);

五、用代码说话

1 、基础类

接口类:MyInterface.java​
接口类:MyInterface2.java​
对象类:Person.java​
对象类:Student.java​
工具类:PropertyUtil.java​
配置类:class.txt​

2 、测试内容

demo1 :获取反射对象(反射入口)

demo2 :获取方法:1、所有的公共方法 2、当前类的所有方法

demo3 :获取所有的接口

demo4 :获取所有的父类

demo5 :获取所有的构造方法

demo6 :获取所有的公共属性

demo7 :获取当前反射所代表类(接口)的对象(实例)

demo8 :获取对象实例,并操作对象

demo9 :操作方法

demo10 :获取指定的构造方法

demo11 :动态加载类名和方法

demo12 :越过泛型检查

demo13 :给任意对象、任意属性 赋值

(1) 获取反射对象的三种方式

(2) 获取方法:

1、所有的公共方法 :A、本类以及父类、接口中的所有方法 B、符合访问修饰符(公有) (无private方法)

2、当前类的所有方法:A、只能是当前类,B、忽略访问修饰符限制

(3) 获取所有的接口

(4) 获取所有的父类

(5) 获取所有的构造方法

(6)获取所有的公共属性​

(7) 获取当前反射所代表类(接口)的对象(实例)

(8) 获取对象实例,并操作对象

(9) 操作方法

(10) 获取指定的构造方法

(11) 动态加载类名和方法

(12) 越过泛型检查

Java 集合中的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了

(13) 给任意对象、任意属性 赋值

六、反射优缺点

优点:

1、在运行时检测或修改程序行为,提高程序的灵活性、扩展性

2、降低耦合性,

3、提高自适应能力

缺点:

1、性能较低:包含动态类型,JVM无法对此进行优化;同时解析操作的效率远低于直接代码

2、安全限制:程序必须在一个没有安全限制的环境中运行

3、内部暴露:可以执行在正常情况下不被允许的操作(比如访问私有属性、方法、去泛型化等),可能会导致不可预见的副作用,出现代码功能性错误,降低可移植性,破坏抽象性。

你可能感兴趣的:(Java初级基础面试题)