Java基础

1.Java基本类型

类型占用存储空间默认值包装类
byte8位0Byte
short16位0Boolean
int32位0Short
long64位0LLong
float32位0.0fFloat
double64位0.0dDouble
char16位nullCharacter
boolean一位falseBoolean

类型字节数位数存储范围
byte1字节8位元组,即8位bit可存储-2^8 ~ 2^7 (-128 ~ 127)
short2字节2*8 = 16 bit-2^16 ~ 2^15 (-32768 ~ 32767)
int4字节4*8 = 32 bit-2^32 ~ 2^31 (-2147483648 ~ 2147483647)
long8字节8*8 = 64 bit-2^64 ~ 2^63 (-18446744073709551616 ~ 18446744073709551615)

Java自动类型转换

自动类型转换指的是容量小的数据类型可以自动转换为容量大的数据类型
实线表示无数据丢失的自动类型转换,而虚线表示在转换时可能会有精度的损失。
说明:容量大小指的是表示数的范围的大和小;

Java中boolean类型占用多少个字节

1个字节,理由是虽然编译后1和0只需占用1位空间,但计算机处理数据的最小单位是1个字节,1个字节等于8位,实际存储的空间是:用1个字节的最低位存储,其他7位用0填补,如果值是true的话则存储的二进制为:0000 0001,如果是false的话则存储的二进制为:0000 0000。

2.Java基本类型的包装类

注意:Java中的包装类不允许被继承,没有子类。

包装类的作用主要有以下两方面:

  • 编码过程中只接收对象的情况,比如List中只能存入对象,不能存入基本数据类型;比如一个方法的参数是Object时,不能传入基本数据类型,但可以传入对应的包装类
  • 方便类型之间的转换,比如String和int之间的转换可以通过int的包装类Integer来实现,具体如下。
    int a = new Integer(“123”);
    或者
    int a = Integer.parseInt(“123”);

String转为int
int i=Integer.parseInt(string);
int i=Integer.valueOf(s).intValue();

int转为String
String s = String.valueOf(i);
String s = Integer.toString(i);
String s = “” + i;

String和数组都是引用数据类型
一个字节由8位二进制数组成。在计算机内部,一个字节可以表示一个数据或者一个英文字母,但是一个汉字需要两个字节表示。
1B=8bit
1Byte=8bit
1KB=1024Byte(字节)=8*1024bit
1MB=1024KB
1GB=1024MB
1TB=1024GB

3.Java权限修饰符:


修饰符一般在类,方法,构造器等的开头
修饰符:修饰符可以省略,也可以是public、protected、private、static、final、abstract,其中public、protected、private三个最多只能出现其中一个;abstract和final最多只能出现其中之一,它门可以与static组合起来修饰方法
类里面的绝大部分成员变量都应该使用private修饰,只有一些static修饰的,类似于全局变量的成员变量,才可能考虑使用public修饰
如果某个类主要用做其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不想外界直接调用,则应该使用protected修饰这些方法
希望暴露出来给其他类自由使用的方法应该使用public修饰

4.static关键字

可以修饰方法或者成员变量,但是不能修饰类,没有静态类这东西
static修饰的成员不能访问没有static修饰的成员
有static修饰的成员属于类本身,没有static修饰的成员属于该类的实例
被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个对象来
被static修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要new出一个对象来
被static修饰的变量、被static修饰的方法统一属于类的静态资源,是类实例之间共享的,换言之,一处变、处处变
静态变量是在定义时就分配内存,而动态变量则是在程序执行过程中才会分配内存
没有使用static修饰的成员变量和方法都必须使用对象来调用

接口

接口中只能包含抽象方法和常量
一个类可以实现多个接口
接口可以被继承
只能是 public, abstract 、final才能实现接口
接口的修饰符:public,abstract,static,final

5.构造器

构造器是一种特殊的方法,定义构造器的语法格式与定义方法的语法格式很像
构造器没有返回值,也不能声明返回值类型
构造器最大的用处就是在创建对象时执行初始化,当创建一个对象时,系统会为这个对象的实例进行默认的初始化
如果想改变这种默认的初始化,就可以通过自定义构造器来实现。
构造器是为了创建一个类的实例化对象的时候用到:InstanceObject IO = new InstanceObject(); 构造器可以用来在初始化对象时,初始化数据成员,即包括初始化属性和方法。
一个类可以有多个构造器。一个类的构造器的名称必须与该类的名称一致。要退出构造器,可以使用返回语句“return;”。 相反,方法的作用是为了执行java代码。
构造器重载:一个类里面具有多个构造器,多个构造器的形参列表不同,即被称为构造器重载,构造器不能被重写
构造器不能重写但是可以重载,因为构造方法不能被继承

局部内部类

在某些情况下,某些业务逻辑需要临时处理,这些业务逻辑只在这里使用又可以封装成一个类的话,而又没必要重新建个文件,所以可以这写一个局部内部类来处理。
然后,在我的记忆中,java代理模式中有用到局部内部类,在方法中直接实现接口,返回代理对象,简单而又方便。
感觉内部类一般在设计模式中会用的比较多,我们平时开发不会用到太多。学习了就好!

匿名内部类

从重构的角度来看,匿名内部类可以使你少些一些重复代码
java8中可以用lambda替换匿名内部类
不过请注意:lambda表达只能替换
只有一个抽象方法的接口

内部类的作用

1、内部类可以很好的实现隐藏。
非内部类是不可以使用 private和 protected修饰的,但是内部类却可以,从而达到隐藏的作用。同时也可以将一定逻辑关系的类组织在一起,增强可读性

2、间接的实现多继承。
每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。所以说内部类间接的实现了多继承

6.this关键字

this.属性名称
指的是访问类中的成员变量,用来区分成员变量和局部变量(重名问题)
用来区分本类的成员与外部传进来的参数

class Person{
	private String name;
	private int age;
	private String gender;
	Person(){}
	Person(String name,int age,String gender){
		this.name = name;
		this.age = age;
		this.gender = gender;
	}

this.方法名称
用来访问本类的成员方法
this();
访问本类的构造方法
()中可以有参数的 如果有参数 就是调用指定的有参构造
注意事项:
1.this() 不能使用在普通方法中 只能写在构造方法中
2.必须是构造方法中的第一条语句

7.super关键字

super就是用于访问父类中的同名变量
super用于限定该对象调用它从符类继承得到的实例变量或者方法
super不能 出现在static修饰的方法中,static修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,因而super限定也就失去了意义

8.Final关键字:

final可以用来修饰类、变量(包括成员变量和局部变量)和方法
修饰类
final修饰一个类时表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
修饰方法
第一个原因是把方法锁定,以防任何继承类修改它的含义
第二个原因是效率  
父类的final方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。
final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写(可以重载多个final修饰的方法)final方法可以被重载
修饰变量
修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变

string、stringBuffer和stringbuilder的区别


String类是不可变类,任何对String的改变都会引发新的String对象的生成,所以String一般被认为是线程安全的,因为不可变;StringBuffer则是可变类,任何对它所指代的字符串的改变都不会产生新的对象。

为什么string是不可变的

什么是不可变对象?
如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变

JAVA语言为什么把String类型设计成不可变
第一:在Java程序中String类型是使用最多的,这就牵扯到大量的增删改查,每次增删改差之前其实jvm需要检查一下这个String对象的安全性,就是通过hashcode,当设计成不可变对象时候,就保证了每次增删改查的hashcode的唯一性,也就可以放心的操作。

第二:网络连接地址URL,文件路径path通常情况下都是以String类型保存, 假若String不是固定不变的,将会引起各种安全隐患。就好比我们的密码不能以String的类型保存,,如果你将密码以明文的形式保存成字符串,那么它将一直留在内存中,直到垃圾收集器把它清除。而由于字符串被放在字符串缓冲池中以方便重复使用,所以它就可能在内存中被保留很长时间,而这将导致安全隐患

第三:字符串值是被保留在常量池中的,也就是说假若字符串对象允许改变,那么将会导致各种逻辑错误

9.项目一般的打包方式是什么?jar包和war包的区别是什么?

SSM项目一般都是打成war包,SpringBoot项目因为自带tomcat所以可以直接打成jar包
jar包和war包的区别:
1、war是一个web模块,其中需要包括WEB-INF,是可以直接运行的WEB模块;jar一般只是包括一些class文件,在声明了Main_class之后是可以用java命令运行的。
2、war包是做好一个web应用后,通常是网站,打成包部署到容器中;jar包通常是开发时要引用通用类,打成包便于存放管理。
3、war是Sun提出的一种Web应用程序格式,也是许多文件的一个压缩包。这个包中的文件按一定目录结构来组织;classes目录下则包含编译好的Servlet类和Jsp或Servlet所依赖的其它类(如JavaBean)可以打包成jar放到WEB-INF下的lib目录下。
JAR文件格式以流行的ZIP文件格式为基础。与ZIP文件不同的是,JAR 文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和 JVM 这样的工具直接使用
war包和项目的文件结构保持一致,jar包则不一样。
jar包里没有静态资源的文件(index.jsp)
部署普通的spring项目用war包就可以,部署springboot项目用jar包就可以,因为springboot内置tomcat。

10.java 的 instanceof 关键字

java中,instanceof运算符的前一个操作符是一个引用变量,后一个操作数通常是一个类(可以是接口),用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是返回true,否则返回false。
也就是说:使用instanceof关键字做判断时, instanceof 操作符的左右操作数必须有继承或实现关系

11.Java程序初始化顺序

1、父类的静态变量
2、父类的静态代码块
3、子类的静态变量
4、子类的静态代码块
5、父类的非静态变量
6、父类的非静态代码块
7、父类的构造方法

8、子类的非静态变量
9、子类的非静态代码块
10、子类的构造方法
总的来说就是:父类的静态(变量,代码块)–子类的静态–父类的非静态–父类构造方法–子类非静态–子类的构造方法

12.Java运算符:

参考的原文
与运算符(&)
如果 4&7 那么这个应该怎么运算呢?
首先我们需要把两个十进制的数转换成二进制
4 : 0000 0100
7 : 0000 0111

在这里要提到一点,1表示true,0表示false
而与运算的时候相同位之间其实就是两个Boolean的运算
全true(1),即为true(1)
全false(0),即为false(0)
一false(0)一true(1),还是false(0)

4)或运算符(|)
以 5|9 为例

5 : 0000 0101
9 : 0000 1001

在做与运算的时候
遇true(1)就是true(1),
无true(1)就是false(0)

5) 异或运算符(^)
以 7^15 为例
7: 0000 0111
15: 0000 1111

在异或的时候
只要相同都是false(0)
只有不同才是true(1)

6) 取反运算符(~)
例: ~15
同样的先变成二进制:15:0000 1111

  这个其实挺简单的,就是把1变0,0变1

注意:二进制中,最高位是符号位 1表示负数,0表示正数

7) 左移运算(<<)
左移就是把所有位向左移动几位
如: 12 << 2 意思就是12向左移动两位
12的二进制是: 0000 1100

通过这个图我们可以看出来,所有的位全都向左移动两位,然后把右边空的两个位用0补上,最左边多出的两个位去掉,最后得到的结果就是00110000 结果就是48
我们用同样的办法算 12<<3 结果是 96
8<<4 结果是 128
由此我们得出一个快速的算法 M << n 其实可以这么算 M << n = M * 2n

8) 右移运算符(>>)
这个跟左移运算大体是一样的
例: 12 >> 2

我们可以看出来右移和左移其实是一样的,但是还是有点不同的,不同点在于对于正数和负数补位的时候补的不一样,负数补1,正数补0
如我们再做一个 –8 的 -8>>2

这里总结一下,关于负数或者正数来说,移位的时候是一样的,但是在补位的时候,如果最高位是0就补0,如果最高位是1就补1
由此我们得出一个快速的算法 M >> n 其实可以这么算 M >> n = M / 2^n
9无符号右移(>>>)
无符号右移(>与运算符(&)
无符号右移。无论是正数还是负数,高位通通补0。

12.转发和重定向:

在Servlet中实现页面的跳转有两种方式:转发和重定向

什么是转发
概念
由服务器端进行的页面跳转

原理图

转发的方法

1.OneServlet向请求域中添加了一个键和值,转发给TwoServlet
2.TwoServlet就从请求域中取出键和值,打印到浏览器上。
效果

转发的特点
1.地址栏不发生变化,显示的是上一个页面的地址
2.请求次数:只有1次请求
3.根目录:http://localhost:8080/项目地址/,包含了项目的访问地址
4.请求域中数据不会丢失

什么是重定向
概念
由浏览器端进行的页面跳转
原理图

重定向方法

步骤
1.在OneServlet中向请求域中添加键和值
2.使用重定向到TwoServlet,在TwoServlet中看能否取出请求域的值
效果

重定向的特点
1.地址栏:显示新的地址
2.请求次数:2次
3.根目录:http://localhost:8080/ 没有项目的名字
4.请求域中的数据会丢失,因为是2次请求

疑问
问:什么时候使用转发,什么时候使用重定向?
如果要保留请求域中的数据,使用转发,否则使用重定向。
以后访问数据库,增删改使用重定向,查询使用转发。
问:转发或重定向后续的代码是否还会运行?
无论转发或重定向后续的代码都会执行
小结:重定向和转发的区别

区别转发forward()重定向sendRedirect()
根目录包含项目访问地址没有项目访问地址
地址栏不会发生变化会发生变化
哪里跳转服务器端进行的跳转浏览器端进行的跳转
请求域中数据不会丢失会丢失

13.静态方法能调用非静态方法吗

在一个类的静态成员中去访问非静态成员之所以会出错是因为在类的非静态成员不存在的时候静态成员就已经存在了,访问一个内存中不存在的东西当然会出错

14.Session和Cookie的区别:

所以个人建议:
一般登录信息都放在cookie中
一般购物车实现使用的是session
保存会话Cookie和Session的区别:

区别CookieSession
存在Cookie是客户端技术,通常保存在客户端,不是十分安全Session是服务器端技术,在服务端,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象
存储数据只能存储 String 类型的对象能够存储任意的 java 对象
性能Cookie存在客户端对服务器没影响Session过多时会消耗服务器资源,大型网站会有专门Session服务器
作用域Cookie通过设置指定作用域只能在指定作用域有效Session在整个网页都有效
作用时间Cookie可以通过 setMaxAge设置有效时间,即使浏览器关闭了仍然存在关闭网页Session就结束了

Session和Cookie的保存时间:
session保存在服务器端,会一直存在,默认存在时间30分钟;
cookie保存sessionid,服务器会根据cookie中sessionid获取session;
两种类型的Cookie:
临时Cookie(会话Cookie)
永久Cookie
不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。
设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。
存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。
为何关闭浏览器后,再次访问会觉得session失效了呢,这里的失效意思是session的数据丢失了?
其实这里session数据并没有丢失,只是关闭浏览器后,因为默认的cookie生命周期为浏览器的缓存,即关掉浏览器之后cookie就失效了,此时sessionid也就没有了。再次访问后,服务器又生成一个新的sessionid,此时request.getSession()通过sessionid获取到的session就不是之前的session了。

16.异常:

异常和错误:
Java把所有的非正常情况分为两种:异常(Exception)和错误(Error)它们都继承Throwable父类

Exception:所有的RuntimeException类以及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为非Rumtime异常

Exception :受检查的异常,这种异常是强制我们catch或throw的异常你遇到这种异常必须进行catch或throw,如果不处理,编译器会报错。比如:IOException

RuntimeException:运行时异常,这种异常我们不需要处理,完全由虚拟机接管比如我们常见的NullPointerException,我们在写程序时不会进行catch或throw

异常处理机制:
对于Checked异常的处理方式有如下两种: 当前方法明确知道如何处理该异常,程序应该使用try…catch块来捕获该异常,然后在对应的catch块中修复该异常
当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常
Runtime异常则更加灵活,Runtime异常无需显示声明抛出,如果程序需要捕获Runtime异常,也可以使用try…catch块来实现

抛出异常:
如果执行try块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行环境,这个过程被称为抛出(throw)异常
使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理。如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理 JVM对异常的处理方法是,打印异常的跟踪栈信息,并终止程序运行,这就是前面程序在遇到异常后自动结束的原因。
一旦使用throws语句声明 抛出该异常,程序就无须使用try…catch块来捕获异常了。
调用一个方法时,要么放在try块中显式捕获该异常,要么放在另一个带throws声明抛出的方法中 子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。

捕获异常:
当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常。

如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出 不管程序代码块是否处于try中,甚至包括catch块中的代码,只要在执行该代码块出现了异常,系统总会自动生成一个异常对象。
try块后可以有多个catch块,这是为了针对不同的异常类提供不同的异常处理方式。
进行异常捕获时不仅应该把Exception类对应的catch块放在在最后,而且所有父类异常的catch块都应该排在子类异常catch块的后面(先处理小异常,在处理大异常),否则处理了exception,后面的就不会再执行了,这样也会出现编译错误。
多异常捕获:
catch(…|…|…){
}
捕获多种类型的异常时,多种异常类型之间用竖线(|)隔开 捕获多种类型的异常时,异常遍历有隐式的final修饰,因此程序不能对异常变量重新赋值。
使用finall回收资源 用于回收try块中打开的物理资源 不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或者catch块中执行了return语句,finally块总会被执行。

异常处理语法结构中只有try块是必须的,也就是说,如果没有try块,则不能有后面的catch块和finally块 catch块和finally块都是可选的,但是catch块和finally块至少出现其中之一,也可以同时出现,但是不能只有try块,既没有catch块,也没有finally块
可以有多个catch块,捕获父类一些的catch块必须位于捕获子类异常的后面
多个catch块必须位于try块之后,finally块必须位于所有的catch块之后
不要在finally块中使用return或者throw等导致方法终止的语句,一旦在finally块中使用了return或者throw语句,将会导致try块、catch块中的return、throw语句失效
五种常见的异常:ClassCastException(类转换异常)、IndexOutOfBoundsException(数组越界异常)、NullPointerException(空指针异常)、ArrayStoreException(数据存储异常,操作数组时类型不一致)、BufferOverflowException(还有IO操作的,缓冲溢出异常)。

重写和重载的区别

重写是发生在父类和子类,重写是方法名字和参数的列表是要完全一致的,重写的意义在于父类的方法已经不能满足时,子类重写为自己需要的。
重载是在同一个类当中有多个名称相同方法,但各个相同方法的参数列表不同(无关返回值类型)也就是返回值的类型可以被改变。

17.项目部署流程:

项目部署流程

18.抽象类和接口的区别

1.抽象类可以有构造方法,接口中不能有构造方法
2.抽象类中方法可以有方法体,接口中的方法没有方法体
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法
4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然
eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 一个类可以实现多个接口,但只能继承一个抽象类

19.JAVA的四大基础特征(封装 继承 多态 抽象)

Java实现多态的方式
三种:普通类,抽象类和接口
1.普通类
AnimalFu a = new AnimalZi new();
2.抽象类 animal 是父类抽象类
Animal a = new Cat();
3.接口 // 学生是类 smoking 是接口
Smoking sk = new Student()
多态的特点:
变量:编译看父类
方法:运行看子类

封装
封装的本质就是通过定义类,并且给类的属性和方法加上访问权限,用于控制在程序中属性的读和修改的访问级别。
口头理解就是:通过访问权限控制,隐藏重要数据,避免随便修改数据。只对外暴露不会影响类的数据安全的公共方法。
访问权限修饰符的作用域范围从大到小:public --> proetected --> default --> private

继承
从已存在的类中定义新的类,实现代码复用,这称为继承。继承在软件重用方面是一个重要且功能强大的特征。
通过继承可以定义一个通用的类(父类),之后在将其扩展为一个更加特定的类(子类)。
继承使用场景
类与对象的关系:一个类具有同一类型的对象的属性和行为。
类与类的关系:有的时候,不同类之间也有一些共同的特性和行为,将这些共同的特性和行为提取出来,统一放在一个类中,作为一个通用类。这时可以定义特定的类继承该通用类, 这些特定的类继承通用类的可访问数据域和方法。
继承语法:使用extends关键字。 public subClass extends SuperClass{}
用下面例子来体现继承的作用。(也可以自行去设计类来体现继承)

多态
多态就是同一事物的多种不同形态。简单的说就是,做同样的事,但根据实际对象来决定具体的实现。例如,学生去上课,有的学生上高数,有的学生上计算机课,有的上英语课;叫每个学生都看书,有的学生看历史书,有的看文学,有的看技术类书籍等等。
实现多态的前提:类与类之间建立继承关系;子类对父类的方法进行重写;父类变量指向子类对象
多态的作用:消除了类型之间的耦合关系,允许将多种类型(同一基类)视为同一类型来处理,同一份代码也就可以毫无差别地运行在这些不同类型之上,使得父类型的引用变量可以引用子类型的对象。

最终多态体现为父类引用变量可以指向子类对象:父类类型 变量名 = new 子类类型();
多态的三种形式:
1、 重写
2、 重载
3、接口定义方法,方法的实现在继承接口的具体类中定义,也是对同一行为的不同表现形式

多态的转型分为向上转型与向下转型两种
向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的!
一、向上转型
好处:隐藏了子类型,提高了代码的扩展性。
坏处:只能使用父类的功能,不能使用子类特有功能,功能被限定。
使用场景:不需要面对子类型,通过提高扩展性,或者使用父类的功能即可完成操作,就是使用向上转型。
二、向下转型
好处:可以使用子类型的特有功能
坏处:面对具体的子类型,向下转型具有风险。即容易发生ClassCastException,只要转换类型和对象不匹配就会发生。解决方法:使用关键字instanceof。

向上转型和向下转型的用途可看:
https://blog.csdn/qq_34159161/article/details/105065738

20.字符流和字节流:

字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类
InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流
OutputStream/Writer:所有输出流的基类,前者更是字节输出流,后者是字符输出流

字符流和字节流的区别:
每次读写的字节数不同;
字符流是块读写,字节流是字节读写;
字符流带有缓存,字节流没有。

java流在处理上分为字符流和字节流。字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。

字符流和字节流,一个属性范围小,一个属性范围大,字符流只能是字符这一种类型,但是字节流中可以是字符,可以是二进制文件,可以是音频,可以是各种各样的类型,只要符合字节形式存储的都可以接字节流,而字符流只能接字符。

21.Java泛型

增加泛型是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象“丢进”Java集合中,集合就会忘记对象的类型,把所有对象当Object类型处理 增加了泛型支持后的集合,完全可以记住集合中元素的类型,并且可以在编译时检查集合中元素的类型,如果试图向集合中添加不满足类型要求的对象,编译器就会提示错误 其实就是指定集合的数据类型

22.equals()和“ == ”的区别:

1、“ == “如果作用于基本数据类型,则比较的是值是否相等;
   如果作用于引用类型,则比较的是变量所指向的对象的地址
2、对于没有重写equals方法的类,比较的是引用类型的变量所指向的对象的地址
但对于在其类中重写了equals()的类,例如String类,比较的是两个对象的值是否一样
" == "是判断两个变量或实例是不是指向同一个内存空间。"equals"是判断两个变量或实例所指向的内存空间的值是不是相同。
” == “是指对内存地址进行比较 , equals()是对字符串的内容进行比较
“ == ”指引用是否相同, equals()指的是值是否相同

String重写了Object的equals()方法,源码如下:

public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
执行逻辑为:

首先判断是否为同一个对象,若是直接返回true,若不是则继续执行下述逻辑;

判断入参是否为String对象,若不是,直接返回false,若相等继续执行下述逻辑;

判断入参长度是否与this的长度相等,若不相等直接返回false,若相等继续执行下述逻辑;

从尾部开始遍历每个字符,比较是否相等,在此过程中若有字符不相等,直接返回false;

若全部字符均相等,则返回true。

String创建时产生了多少个对象?

String a = “abc”;
该语句创建对象的过程:先在常量池中查找是否有内容为"abc"的字符串对象,若有,直接将该对象的引用赋给a;若没有,则在常量池中创建"abc"对象,再将其引用赋给a。

String a1 = new String(“abc”);
String a2 = new String(“abc”);
在常量池没有"abc"对象的前提下,这两条语句产生了3个对象,两个处于堆中的string对象,一个处于字符串常量池string对象。

String a1 = “abc”;
String a2 = “abc”;
在常量池没有"abc"对象的前提下,这两条语句产生了一个对象,位于字符串常量池中。当String a1 = "abc"执行完毕后,JVM会在字符串常量池中创建一个"abc"对象;然后执行String a2 = “abc"时会先在常量池中查询是否有"abc”,如果有,将"abc"的引用赋给a2;如果没有,在在字符串常量池中创建一个"abc"对象,再赋给a2。

在我们的实际开发中,通常会认为两个对象的内容相等时,则两个对象相等,equals返回true。对象内容不同,则返回false。

23.为什么重写equals方法还要重写hashcode方法?**

1.为什么要重写equals()方法?
因为object中的equals()方法比较的是对象的引用地址是否相等,如何你需要判断对象里的内容是否相等,则需要重写equals()方法。
2.Java中有哪些类重写了equals()方法?
java中的大部分类都重写了equals()方法,没有重写的类大部分都是自己定义的类,比如说自己定义了一个person类,这个就没有重写equals()方法。
3.hashCode的规则
两个对象相等,hashcode一定相等
两个对象不相等,hashcode可能相等
hashcode相等,两个对象不一定相等
hashcode不相等,两个对象一定不相等

4.hashcode()方法的作用?
Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。提到hashcode,通常我们会理解为对象的地址,这里存在一个误区,对象的物理地址跟这个hashcode地址不一样,hashcode代表对象的地址说的是对象在hash表中的位置,物理地址说的对象存放在内存中的地址。
主要是针对HashSet和Map集合类型,比如我们在向HashSet集合里边添加新元素的时候,由于set集合里边不允许元素重复,所以我们在插入新元素之前需要先判断插入元素是否存在,首先根据hashCode()方法得到该对象的hashCode值,如果集合里边不存在该值,可以直接插入进去。如果已经存在,则需要再次通过equals()来比较,这样的话可以提升效率。
4.重写equals()方法为什么要同时重写hashcode()方法?
String重写了hashcode()方法,String类型的hash值是根据字符串的内容来决定的,并不是内存地址,只要两个String类型的字符串内容一致,那么两者的hashcode就相同
重写equals()方法同时重写hashcode()方法,就是为了在比较两个对象是否相等时,保证当两个对象通过equals()方法比较相等时,那么他们的hashCode值也一定要保证相等。
从执行逻辑可以看出String通过改写equals方法实现了引用数据类型所指向的对象的值的比较,而不是默认的比较两个对象是否为同一对象。实际上由于java规定若equals()判断相等的对象,其hashcode必须相等,因此重写了equals(),就必须要重写hashcode(),否则会出现equals判断相等,但其hahs值反而不相等的情况而且重写hashcode可以加快速率,采取重写hashcode方法,先进行hashcode比较,如果不同,那么就没必要在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的

如果重写了equals就必须重写hashCode,如果不重写将引起与散列集合(HashMap、HashSet、HashTable、ConcurrentHashMap)的冲突。例如定义两个对象,值相等,equals重写了,但是hashcode没有重写,那么此刻,equals得出相等,但是hashcode得出不相同。如果我们把这两个值存进hashMap时,那么都可以存进,但是hashMap规定了key是不能重复的,所以这就产生了问题。所以我们必须重写hashcode,以让当equals相同时,hashcode也必须相同,这时候当两个值都存进hashMap时,会自动去重。根据业务状况重写equals后,一定要将hashCode用一定相同的规则做hash,防止在一些需要用到对象hashCode的地方造成误会,引发问题

24.HashCode()和equals()的区别:

通过调用hashCode()方法获取对象的hash值。
equals它的作用也是判断两个对象是否相等,如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。
equals()相等的两个对象,hashcode()一定相等;
反过来:hashcode()不等,一定能推出equals()也不等;
hashcode()相等,equals()可能相等,也可能不等

25.Git和SVN的区别:

1.SVN是中央版本控制系统,Git是分布式版本控制系统
2.GIT把内容按元数据方式存储,而SVN是按文件
3.GIT分支和SVN的分支不同
4.GIT没有一个全局的版本号,而SVN有
5.GIT的内容完整性要优于SVN

26.编译的整个过程:

预编译、编译、汇编、链接

27.Java和C语言有什么区别?Java好在哪里?

1.C语言面向过程,Java是面向对象
2.C语言可移植差,java可移植强
3.C语言的垃圾回收要程序员手动进行,Java自动垃圾回收。
4.Java跨平台,C语言不跨平台

从概念上看,C语言是一门面向过程、抽象化的通用程序设计语言;Java是一门面向对象编程语言,而Java语言是从C语言衍生而来,它吸收了C++语言的各种优点,并且摒弃了C++里难以理解的多继承、指针等概念。从概念可以看出C语言相当于Java的基础语言,学完C再学Java会更容易。

从应用领域上,C语言和Java的应用领域几乎没有重合。因为随着这几年Java、web、PHP等语言的出现,它们凭借比C更简洁的、更安全的性能快速崛起,抢占了大量的市场。目前因为Java有完善的框架,在多线程、高并发方面有着较好的稳定性,所以Java主要应用于企业级应用开发、大型系统开发等领域。而C语言由于编程的复杂性,退守到了系统级别语言,而且因为想要系统级软件可以发挥出机器的功能,它的底层核心就需要用C/C++来编写,所以C语言主要应用于软件工业。Java还有JVM虚拟机,可主动进行垃圾回收和通过反射机制加载类的成员变量的属性和方法对象

28.怎么理解Java的跨平台性,一次编译到处运行?

简单地说Java的跨平台性就是指,编译后的Java程序可直接在不同的平台上运行而不用重新编译,这一特性使得Java随着Web应用的普及而迅速普及起来。而Java的跨平台性是如何实现的呢?这就要理解Java虚拟机和字节码的概念。
实际上,编译后的Java代码并不是传统的二进制代码(如Windows下的.exe文件),而是Java字节码,这种字节码文件是不能直接在操作系统上执行的。要想在一个操作系统上运行一个Java程序必须有一个中间环节来负责将Java字节码解释成二进制码,这个中间环节就是Java虚拟机(简称JVM)。由于目前大多数操作系统已经实现了JVM,所以Java轻松实现跨平台性。
Java编译产生的不是针对特定平台的机器码,而是一种与平台无关的字节码文件(即*.class文件)。相同的字节码在不同平台上直接运行原本是不可能的,但通过中间的转换器实现了“一次编译,到处运行”的效果,JVM就是这个转换器。不同平台上的JVM是不同的,但他们提供给Java字节码程序的接口是完全相同的。因此,这些字节码不面向任何平台,只面向JVM,也就是说,JVM充当了中介或者叫做翻译的角色

29.面向对象和面向过程的区别?

一、编程思想不同
1、面向过程:是一种以过程为中心的编程思想。都是以什么正在发生为主要目标进行编程。
2、面向对象语言:是一类以对象作为基本程序结构单位的程序设计语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。
二、特点不同
1、面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
2、面向对象语言:识认性,系统中的基本构件可识认为一组可识别的离散对象,对象具有唯一的静态类型和多个可能的动态类型,在基本层次关系的不同类中共享数据和操作。
三、优势不同
1、面向过程:不支持丰富的“面向对象”特性(比如继承、多态),并且不允许混合持久化状态和域逻辑。
2、面向对象语言:在内部被表示为一个指向一组属性的指针。任何对这个对象的操作都会经过这个指针操作对象的属性和方法。

30.深克隆和浅克隆

克隆(复制)在Java中是一种常见的操作,目的是快速获取一个对象副本。克隆分为深克隆和浅克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
总之深浅克隆都会在堆中新分配一块区域,区别在于对象属性引用的对象是否需要进行克隆(递归性的)

31.注解的实现原理

一、什么是注解?
对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
  Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。
  注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。
二、注解的用处:
1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2 依赖注入,未来java 开发,将大量注解配置,具有很大用处;
3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
三、注解的原理:
  注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。
四、元注解:
java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented – 注解是否将包含在JavaDoc中
@Retention – 什么时候使用该注解
@Target – 注解用于什么地方
@Inherited – 是否允许子类继承该注解

xml文件的解析过程

1.获取解析工厂
SAXParserFactory factory=SAXParserFactory.newlnstance();
2.从解析工厂获取解析器
SAXParser parse = factory.newSAXParser();
3.编写处理器
4.加载文档Document注册处理器
5.解析

Lambda循环

// Lambda 表达式遍历(JDK 1.8)
        System.out.println("\n第三种遍历方式:Lambda 表达式遍历 List 集合");
        items.forEach(item->{
            System.out.println(item);
        });
   // Lambda 表达式遍历(JDK 1.8)
        System.out.println("\n第四种遍历方式:Lambda 表达式遍历 List 集合");
        items.forEach(System.out::println);

32.Lambda表达式的优缺点?

作用
1.替代匿名内部类
2.使用lambda表达式对集合进行迭代
3.Stream操作
4.与函数式接口Predicate配合

优点

  1. 代码简洁,开发迅速
  2. 方便函数式编程
  3. 非常容易进行并行计算
  4. java引入lambda,改善了集合操作(引入Stream API)

缺点

  1. 代码可读性变差
  2. 使得语言学习曲线陡峭,学习难度提升
  3. 性能方面,在非并行计算中,很多计算未必有传统的for性能要高
  4. 不容易进行调试

33.Stream流式编程的好处?终止方法和延迟方法的区别是?终止方法存在的意义是什么?

Stream其实就是一个高级的迭代器,它不是一个数据结构,不是一个集合,不会存放数据。它只关注怎么高效处理数据,把数据放入一个流水线中处理。

Stream是Java 8提供的新功能,是对集合(Collection)对象功能的增强,能对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。 与Lambda 表达式结合,也可以提高编程效率、简洁性和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。

Stream流式编程:

stream() / parallelStream():
最常用到的方法,将集合转换为流

List list = new ArrayList();
// return Stream
list.stream();

而 parallelStream() 是并行流方法,能够让数据集执行并行操作

map():
将流中的每一个元素 T 映射为 R(类似类型转换)

例1:

List newlist = list.stream().map(Person::getName).collect(Collectors.toList());

newlist 里面的元素为 list 中每一个 Person 对象的 name 变量。

例2:

List<Integer> num = Arrays.asList(1,2,3,4,5);
List<Integer> collect1 = num.stream().map(n -> n * 2).collect(Collectors.toList());
System.out.println(collect1); //[2, 4, 6, 8, 10]

flatMap(T -> Stream)
将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流。

List list = new ArrayList<>();
list.add(“aaa bbb ccc”);
list.add(“ddd eee fff”);
list.add(“ggg hhh iii”);
list = list.stream().map(s -> s.split(" ")).flatMap(Arrays::stream).collect(toList());

上面例子中,我们的目的是把 List 中每个字符串元素以” “分割开,变成一个新的 List。
首先 map 方法分割每个字符串元素,但此时流的类型为 Stream。

Collectors.toList() :
将流中的所有元素导出到一个列表( List )中

filter(T -> boolean):
保留 boolean 为 true 的元素

//保留年龄为 20 的 person 元素
list = list.stream()
.filter(person -> person.getAge() == 20)
.collect(Collectors.toList());
//打印输出 [Person{name=‘jack’, age=20}]

distinct():
去除重复元素,这个方法是通过类的 equals 方法来判断两个元素是否相等的

如例子中的 Person 类,需要先定义好 equals 方法,不然类似[Person{name=‘jack’, age=20}, Person{name=‘jack’, age=20}] 这样的情况是不会处理的。

sorted() / sorted((T, T) -> int):
如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream
反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口
根据年龄大小来比较:

list = list.stream()
.sorted((p1, p2) -> p1.getAge() - p2.getAge())
.collect(Collectors.toList());
当然这个可以简化为
list = list.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.collect(Collectors.toList());

limit(long n):
返回前 n 个元素

list = list.stream()
.limit(2)
.collect(Collectors.toList());
//打印输出 [Person{name=‘jack’, age=20}, Person{name=‘mike’, age=25}]

skip(long n):
去除前 n 个元素

list = list.stream()
.skip(2)
.collect(Collectors.toList());
//打印输出 [Person{name=‘tom’, age=30}]
tips:
skip(m)用在 limit(n) 前面时,先去除前 m 个元素再返回剩余元素的前 n 个元素。
limit(n) 用在 skip(m) 前面时,先返回前 n 个元素再在剩余的 n 个元素中去除 m 个元素。
list = list.stream()
.limit(2)
.skip(1)
.collect(Collectors.toList());
//打印输出 [Person{name=‘mike’, age=25}]

anyMatch(T -> boolean):
流中是否有一个元素匹配给定的 T -> boolean 条件
是否存在一个 person 对象的 age 等于 20:

boolean b = list.stream().anyMatch(person -> person.getAge() == 20);

allMatch(T -> boolean):
流中是否所有元素都匹配给定的 T -> boolean 条件

boolean result = list.stream().allMatch(Person::isStudent);

noneMatch(T -> boolean):
流中是否没有元素匹配给定的 T -> boolean 条件

boolean result = list.stream().noneMatch(Person::isStudent);

findAny() 和 findFirst():
findAny():找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream()并行时找到的是其中一个元素)
findFirst():找到第一个元素
值得注意的是,这两个方法返回的是一个 Optional 对象,它是一个容器类,能代表一个值存在或不存在,这个后面会讲到。

reduce((T, T) -> T)reduce(T, (T, T) -> T)

归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。

在流中,reduce函数能实现归约。
reduce函数接收两个参数:

初始值
进行归约操作的Lambda表达式
用于组合流中的元素,如求和,求积,求最大值等

int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge());

//计算年龄总和:

int sum = list.stream().map(Person::getAge).reduce(0, (a, b) -> a + b);

//与之相同:

int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);

其中,reduce 第一个参数 0 代表起始值为 0,lambda (a, b) -> a + b 即将两值相加产生一个新值
同样地:

//计算年龄总乘积:

int sum = list.stream().map(Person::getAge).reduce(1, (a, b) -> a * b);

当然也可以

Optional sum = list.stream().map(Person::getAge).reduce(Integer::sum);

即不接受任何起始值,但因为没有初始值,需要考虑结果可能不存在的情况,因此返回的是 Optional 类型

count():
返回流中元素个数,结果为 long 类型

forEach()
返回结果为 void,很明显我们可以通过它来干什么了,比方说:

//打印各个元素:
list.stream().forEach(System.out::println);

再比如说 MyBatis 里面访问数据库的 mapper 方法:

//向数据库插入新元素:
list.stream().forEach(PersonMapper::insertPerson);

34.sleep和wait的作用与区别

sleep的作用
sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。
wait的作用
调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。
sleep和wait的区别在于这两个方法来自不同的类分别是Thread和Object,sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。wait只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。

35.对象创建的五种方法

1.使用new关键字
2.使用Class类的newInstance方法
3.使用Constructor类的newInstance方法
4.使用clone方法
5.使用反序列化

36.J2EE的13个标准(规范):

常见的有servlet,xmljdbc,jsp,jms,jta,jts

J2EE体系结构:

37.生产上有千个接口,但客户说太卡了,怎么排查哪个接口有问题?

我们开发过程中每个接口都有AOP记录执行时间的,如果速度过慢,可以通过F12查看是否是因为后端接口导致的,如果是后端接口导致需要重新分析接口逻辑处理。

38.Java Web有什么组件?

Servlet:作为一个中转处理的容器,他连接了客户端和服务器端的信息交互和处理。简单来说,客户端发送请求,传递到servlet容器,而servlet将数据转换成服务器端可以处理的数据再发送给服务器端,再数据处理之后,再传递到servlet容器,servlet再转译到客户端,完成了一次客户端和服务器端的信息交互
Filter:filter用于拦截用户请求,在服务器作出响应前,可以在拦截后修改request和response,这样实现很多开发者想得到的功能。filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Filter不像Servlet,它不能产生一个请求或者响应,它只是修改对某一资源的请求,或者修改从某一的响应。
Listener:事件监听机制
事件 :一件事情
事件源 :事件发生的地方
监听器 :一个对象
注册监听 :将事件,事件源,监听器绑定在一起。当事件源上发生某个事件后,执行监听器代码

39.java中的四大引用、说说虚引用

强引用
是指创建一个对象并把这个对象赋给一个引用变量
比如:
Object object =new Object();
String str =“hello”;
强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象

软引用(SoftReference)
如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它
如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

弱引用(WeakReference)
  弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象

虚引用
是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收

40.jdk1.8流式编程有什么优缺点?

Stream流是Java 8新加入的最常用的流接口,位于java.util.stream包下(java.util.stream.Stream ),这并不是一个函数式接口,接口里面包含了很多抽象方法,这些方法对于Stream流的操作都是至关重要的,最常见的方法有filter、map、foreach、count、limit、skip、concat

获取Stream流的两种方法

  1. 所有的 Collection 集合都可以通过 stream
    默认方法获取流,stream()方法是Collection集合接口的一个默认方法,需要注意的是Map集合接口并不是Collection接口的子接口,所以不能直接调用stream方法获取Stream流,但是却可以分别对key和value获取Stream流;
  2. Stream 接口的静态方法 of 可以获取数组对应的流,of 方法的参数其实是一个可变参数,所以支持数组。

Stream流常用方法
对于Stream流的常用方法,可以分成延迟方法终结方法

延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,支持链式调用(除了终结方法外,其余方法均为延迟方法)
终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持链式调用。常见的终结方法有 count 和forEach 方法
1.foreach:遍历(对流中的元素进行遍历)
2. filter:过滤(对流进行一个过滤操作,只保留满足条件的元素)
3. map:映射(将流中的元素映射到另一个流中)
4. count:统计(计算流中元素个数)
5. limit:(截取流中前几个元素)
6. skip:(跳过流中前几个元素)
7. concat:组合(组合两个流)
8. collect:(将stream流转换成集合)

好处:使用stream流式编程不仅可以简化代码开发,增强可读性,还可以在不修改原来集合的基础上对数据进行过滤、映射等操作
详情可参考文章:https://blog.csdn/can_chen/article/details/106886484

41.反射可以获取类的私有变量吗?

可以
从设计的原则来说,类的成员变量如果设计成private,那么我们就不能在类外部去获取到此private变量的值。通常的做法是提供此private变量的get 和set方法。但是如果这个类没有提供get和set方法,我们如何去获取到这个类的private成员变量呢?比如有这么一个类 Person:

class Person{
	private String name = "cross";
}

我们如何不在这个类中来获取到name的值呢?通过java的反射机制可以获取的,注意这里会对name这个field设置下field.setAccessible(true), 我们才能访问private变量

package com.withiter.test;
 
import java.lang.reflect.Field;
 
public class ReflectTest {
 
	public static void main(String[] args) {
		try {
			**Class<?> obj = Class.forName("com.withiter.test.Person");
			Field[] f = obj.getDeclaredFields();
			for(Field field : f){
				field.setAccessible(true);
				System.out.println(field.getName()+":"+field.get(obj.newInstance()));**
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
 
class Person{
	private String name = "cross";
}

42.反射怎么破坏类的单例模式?

单例模式类Singleton,是使用静态内部类实现的单例模式。
因为在单例模式中的构造器是私有的,在Singleton类外部是不能随便调用的,但是通过反射就不一定了,通过这段代码constructor.setAccessible(true)便可以得到构造器的访问权。由于该构造函数是private的,通过setAccessible(true)指示反射的对象在使用时应该取消 Java 语言访问检查,使得私有的构造函数能够被访问,这样使得单例模式失效

如果要抵御这种攻击要防止构造函数被成功调用两次。需要在构造函数中对实例化次数进行统计,大于一次就抛出异常
饿汉式实现的单例模式都可以这样来防止单例模式被反射破坏。
懒汉式实现的单例模式是不可以防止被反射破坏的。
用双重检查锁式实现的单例模式来进行测试,其他的懒汉式实现的单例模式同理。

43.有用过哪些JDK自带的命令?(提了jps 和 jstack,面试官还问了jmap)

jps(JVM Process Status Tool):查看正在运行的java虚拟机进程
jps命令相当于Linux下的ps命令,只不过它只列出Java进程

jstat(JVM Statistics Mornitoring Tool):查看虚拟机运行时信息
jstat可以查看Java虚拟机各种运行状态信息,可以通过它查看JAVA虚拟机进程中的类装载、堆内存、垃圾收集、JIT编译等运行数据

jmap(Memory Map For Java):查看或导出堆快照信息
jmap可以生成Java程序的堆的dump文件,也可以查看堆内存对象的统计信息,查看ClassLoader的信息以及finalizer队列
例如可以查看查看JVM堆内存使用情况、查看JVM永久代使用情况、查看JVM内存中存活的对象等

44.switch case语句后面如果没有break会一直执行下去吗

如果case后面没有break;就会一直执行下去,直到有break为止;如果还没有就会执行到default语句,结束switch语句

如果不在 case 的语句段最后写上 break 的话,代码将继续执行下一个 case 中的语句段,因此,break语句的添加需要根据程序的逻辑来选用,如果有需要程序执行连续执行符合条件的case中的代码,则不应该添加break而使得程序结束跳出。

为什么会出现4.0-3.6=0.40000001这种现象?

这种舍入误差的主要原因是:
浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10
这 就好像十进制无法精确地表示分数 1/3—样。
如果在数值计算中不允许有任何舍入误差, 就应该使用 BigDecimal类。

JDK和JRE的区别

JRE(Java Runtime Enviroment)是Java的运行环境。面向Java程序的使用者,而不是开发者。如果你仅下载并安装了JRE,那么你的系统只能运行Java程序。JRE是运行Java程序所必须环境的集合,包含JVM标准实现及 Java核心类库。它包括Java虚拟机、Java平台核心类和支持文件。它不包含开发工具(编译器、调试器等)。

JDK(Java Development Kit)又称J2SDK(Java2 Software Development Kit),是Java开发工具包,它提供了Java的开发环境(提供了编译器javac等工具,用于将java文件编译为class文件)和运行环境(提 供了JVM和Runtime辅助包,用于解析class文件使其得到运行)。如果你下载并安装了JDK,那么你不仅可以开发Java程序,也同时拥有了运 行Java程序的平台。JDK是整个Java的核心,包括了Java运行环境(JRE),一堆Java工具tools.jar和Java标准类库 (rt.jar)。

区别
JRE主要包含:java类库的class文件(都在lib目录下打包成了jar)和虚拟机(jvm.dll);JDK主要包含:java类库的 class文件(都在lib目录下打包成了jar)并自带一个JRE。那么为什么JDK要自带一个JRE呢?而且jdk/jre/bin下的client 和server两个文件夹下都包含jvm.dll(说明JDK自带的JRE有两个虚拟机)。

记得在环境变量path中设置jdk/bin路径麽?老师会告诉大家不设置的话javac和java是用不了的。确实jdk/bin目录下包含了所有的命令。可是有没有人想过我们用的java命令并不是jdk/bin目录下的而是jre/bin目录下的呢?不信可以做一个实验,大家可以把jdk /bin目录下的java.exe剪切到别的地方再运行java程序,发现了什么?一切OK!(JRE中没有javac命令,原因很简单,它不是开发环境)那么有人会问了?我明明没有设置jre/bin目录到环境变量中啊?试想一下如果java为了提供给大多数人使用,他们是不需要jdk做开发的,只需 要jre能让java程序跑起来就可以了,那么每个客户还需要手动去设置环境变量多麻烦啊?所以安装jre的时候安装程序自动帮你把jre的 java.exe添加到了系统变量中,验证的方法很简单,去Windows/system32下面去看看吧,发现了什么?有一个java.exe。

JDK和JRE的区别

JRE(Java Runtime Enviroment)是Java的运行环境。面向Java程序的使用者,而不是开发者。如果你仅下载并安装了JRE,那么你的系统只能运行Java程序。JRE是运行Java程序所必须环境的集合,包含JVM标准实现及Java核心类库。它包括Java虚拟机、Java平台核心类和支持文件。它不包含开发工具(编译器、调试器等)。

JDK(Java Development Kit)又称J2SDK(Java2 Software Development Kit),是Java开发工具包,它提供了Java的开发环境(提供了编译器javac等工具,用于将java文件编译为class文件)和运行环境(提供了JVM和Runtime辅助包,用于解析class文件使其得到运行)。如果你下载并安装了JDK,那么你不仅可以开发Java程序,也同时拥有了运行Java程序的平台。JDK是整个Java的核心,包括了Java运行环境(JRE),一堆Java工具tools.jar和Java标准类库(rt.jar)。

命令Java和Javac的区别

java命令是执行程序的命令,它将javac转化的class文件运行
javac从命令是编译器(编译命令),作用是将我们的java文件转化为class文件,再供java使用

xml和json的区别

区别:

1、读取的方式不一样。XML是使用XML DOM来循环遍历文档,JSON可以通过JSON.parse方法将json字符串转化为JavaScript可解析的格式。
2、格式不一样,XML的格式是对便签类似于HTML便签一样,而JSON是key/value的格式。
3、xml和json都用在项目交互下,xml多用于做配置文件,json用于数据交互。
4、xml相对json来说是重量级的,json是轻量级的
5、xml在编码上的可读性更强
6、JSON对数据的描述性比XML较差。
7、JSON的速度要远远快于XML

XML的优缺点
<1>.XML的优点
  A.格式统一,符合标准;
  B.容易与其他系统进行远程交互,数据共享比较方便。
<2>.XML的缺点
  A.XML文件庞大,文件格式复杂,传输占带宽;
  B.服务器端和客户端都需要花费大量代码来解析XML,导致服务器端和客户端代码变得异常复杂且不易维护;
  C.客户端不同浏览器之间解析XML的方式不一致,需要重复编写很多代码;
  D.服务器端和客户端解析XML花费较多的资源和时间。

JSON的优缺点
JSON的优点
  A.数据格式比较简单,易于读写,格式都是压缩的,占用带宽小;
  B.易于解析,客户端JavaScript可以简单的通过eval()进行JSON数据的读取;
  C.支持多种语言,包括ActionScript, C, C#, ColdFusion, Java, JavaScript, Perl, PHP, Python, Ruby等服务器端语言,便于服务器端的解析;
  D.在PHP世界,已经有PHP-JSON和JSON-PHP出现了,偏于PHP序列化后的程序直接调用,PHP服务器端的对象、数组等能直接生成JSON格式,便于客户端的访问提取;
  E.因为JSON格式能直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量,且完成任务不变,并且易于维护。
JSON的缺点
  A.没有XML格式这么推广的深入人心和喜用广泛,没有XML那么通用性;
  B.JSON格式目前在Web Service中推广还属于初级阶段。

什么是权限管理?

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源

权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问

认证
身份认证,就是判断一个用户是否为合法用户的处理过程.最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确(登录).对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡.

上边的流程图中需要理解以下关键对象:
Subject:主体 (user)(抽象的概念:保存登录后的相关信息)
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

Principal:身份信息 (username)
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)(类似于数据库表中的id,保持唯一)。

credential:凭证信息 (password)
是只有主体自己知道的安全信息,如密码、证书等。

匿名访问:类似于淘宝首页
是否认证通过:淘宝下单,检查是否登录,没有登录请登录后下单

授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。(前提:必须认证通过)
例如:假如你刚好看中了一个高清无码的种子,而你准备要下载了,这个时候会弹出一个非常恶心的框,让你登录才行,好,你辛辛苦苦注册登录后,点击下载,这时有弹出一个框,告诉你必须是VIP用户才能下载,这时候就是分配权限启最用,这时你就会去充值一个VIP,然后可以点击下载,这时他们就会检查你这个用户是否有权限,有权限下载,没有权限不允许下载。

什么是Apache Shiro?

Apache Shiro是Java的一个安全框架。目前,使用的人越来越多,因为它相当简单,对比Spri9ng Security,功能就没那么强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。这里我们就不纠结它俩哪个好哪个坏,能更简单的解决项目问题就好了。
Apache Shiro是Java的一个安全框架。帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。

shiro底层实现原理

authenticator:认证器,主体进行认证最终通过authenticator进行的。

authorizer:授权器,主体进行授权最终通过authorizer进行的。

认证是利用authenticator认证器和subject、SecurityManager的login方法来实现
授权是利用authorizer授权器还有subject、SecurityManager的isPermitted方法来实现

三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即"当前操作用户"。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着"当前跟软件交互的东西"。但考虑到大多数目的和用途,你可以把它认为是Shiro的"用户"概念。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

Realm: Realm充当了Shiro与应用安全数据间的"桥梁"或者"连接器"。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

认证过程

认证执行流程

1、通过ini配置文件创建securityManager

2、调用subject.login方法主体提交认证,提交的token

3、securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。

4、ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息

5、IniRealm根据输入的token(UsernamePasswordToken)从 shiro.ini查询用户信息,根据账号查询用户信息(账号和密码)

如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)

如果查询不到,就给ModularRealmAuthenticator返回null

6、ModularRealmAuthenticator接收IniRealm返回Authentication认证信息

如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException)

如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在)
和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)

授权流程

授权流程

1、对subject进行授权,调用方法isPermitted(“permission串”)

2、SecurityManager执行授权,通过ModularRealmAuthorizer执行授权

3、ModularRealmAuthorizer执行realm(自定义的Realm)从数据库查询权限数据

调用realm的授权方法:doGetAuthorizationInfo

4、realm从数据库查询权限数据,返回ModularRealmAuthorizer

5、ModularRealmAuthorizer调用PermissionResolver进行权限串比对

6、如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。

递归的优缺点

优点:
1、代码简洁
2、易于理解
如在树的前/中/后序遍历中,递归的实现明显比循环简单。

缺点:
1、时间和空间的消耗比较大
递归由于是函数调用自身,而函数的调用时消耗时间和空间的,每一次函数调用,都需要在内存栈中分配空间以保存参数,返回值和临时变量,而往栈中压入和弹出数据也都需要时间,所以降低了效率。

2、重复计算
递归中又很多计算都是重复的,递归的本质时把一个问题分解成两个或多个小 问题,多个小问题存在重叠的部分,即存在重复计算,如斐波那契数列的递归实现。

3、调用栈溢出
递归可能时调用栈溢出,每次调用时都会在内存栈中分配空间,而栈空间的容量是有限的,当调用的次数太多,就可能会超出栈的容量,进而造成调用栈溢出。

Java数组去重

//第一种方式:最开始想到的是利用Set集合的不可重复性进行元素过滤

 public static Object[] oneClear(Object[] arr){
  Set set = new HashSet();
  for(int i=0;i<arr.length;i++){
    set.add(arr[i]);
   }
  return set.toArray();
 }

//第二种方式:要想保持原数组的顺序就使用有顺序、不重复特点的链表的哈希集合

 public static Object[] twoClear(Object[] arr){
  LinkedHashSet<Object> temp = new LinkedHashSet<>();
  for(int i=0;i<arr.length;i++){
     temp.add(arr[i]);
   }
  return temp.toArray();
 }

//第三种方式:创建一个list集合,然后遍历数组将元素放入集合,再用contains()方法判断一个集合中是否已存在该元素即可

 public static Object[] threeClear(Object[] arr){
  List list = new ArrayList();
  for(int i=0;i<arr.length;i++){
   if(!list.contains(arr[i])){
    list.add(arr[i]);
   }
  }
  return list.toArray();
 }

更多推荐

Java基础知识总结