java基础面试题
java特点
介绍一下java
是一门可跨平台的面向对象的编程语言,分为3个体系java se,javaee和java me
javase主要完成桌面应用程序的开发,是其它两者的基础;
java ee 开发企业环境下的应用程序,主要针对web程序开发;
java me开发电子消费产品和嵌入式设备,如手机中的程序;
人们常说的java开发,一般指的就是java Web开发
java是如何实现跨平台的
java的跨平台性是通JVM实现的,不同平台安装上该平台适用的JVM,然后JVM解释由平台无关的java编译器编译生成的字节码文件生成特定平台的机器码,然后运行
sun的jdk用的hotspot vm
java的底层实现是什么语言
Java的底层实现是使用C和C++语言。 Java的虚拟机(JVM)和部分核心库是用C和C++编写的,而Java编译器(javac)和一些工具则是用Java语言本身编写的。
面向对象的特征有哪些:
1.抽象:类是现实事物的抽象,父类是子类的抽象:将共有的属性、方法放到父类中
2.继承: 子类继承于父类,具有父类的所有属性与方法,可以重用,也可以覆盖。
3.封装: 一个类包括多个属性及方法。
- 多态性:动态:继承 静态:方法的重载
反射会破坏封装性,破坏原有的访问修饰符访问限制
面向对象和面向过程有什么区别
区别:
- 面向过程关注解决问题的步骤,它强调的是将问题分解为一系列函数或过程,然后按照顺序依次执行这些函数或过程。
- 面向对象关注问题的本质和对象之间的交互,它强调将问题域中的事物抽象为对象,并通过对象之间的协作来解决问题。
- 面向过程的代码通常按照功能或步骤来组织,模块之间的耦合度较高,不易于维护和扩展。
- 面向对象的代码按照对象来组织,每个对象都封装了自己的数据和方法,对象之间通过接口进行通信,降低了耦合度,提高了代码的可维护性和可扩展性。
- 面向过程抽象程度较低,它主要关注于如何执行一系列操作来解决问题。
- 面向对象抽象程度较高,它关注于将现实世界中的事物抽象为对象,并通过对象之间的交互来模拟现实世界的行为。
- 面向过程的代码重用性较低,因为函数或过程通常是针对特定问题设计的,不易于在其他场合使用。
- 面向对象的代码重用性较高,因为对象具有封装性、继承性和多态性等特点,可以方便地创建新的对象或扩展现有对象的功能。
-
目标:无论是面向对象还是面向过程,它们的最终目标都是解决问题并构建出高效、可维护的软件系统。
-
编程语言支持:大多数现代编程语言都支持面向对象和面向过程的编程范式,开发者可以根据具体需求选择合适的范式。
-
混合使用:在实际开发中,面向对象和面向过程并不是完全独立的,它们可以相互补充和结合使用。例如,在一个面向对象的系统中,仍然可能需要使用面向过程的方式来处理一些具体的逻辑或算法。
子类能够继承父类的private属性或方法吗
子类继承了父类的所有属性和方法或子类拥有父类的所有属性和方法,只不过父类的私有属性和方法,子类是无法直接访问到的。
java中实现多态的机制是什么?
静态的多态:方法名相同,参数个数或类型不相同。重载(overloading)
动态的多态: 子类覆盖父类的方法,子类当做父类用或子类当做父接口用,调用的是子类的方法。重写
类和对象的关系
抽象和具体的关系
类是对象的模板,描述了该类对象的共同特征,是在对象之上的抽象
对象是类的具体化,是模板的实例。
一个java文件多个类可以吗
可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。
(如果是成员内部类或静态内部类可以是public的)
java 创建对象的方式
通过构造方法
通过反射
通过clone
通过序列化机制
|
Student student = new Student(); Student student2 = (Student)Class.forName("Student类全限定名").newInstance(); //类必须先实现Cloneable接口并实现其定义的clone方法 Student stu4 = (Student) stu3.clone();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("student.bin")); Student stu5 = (Student) input.readObject(); |
clone方法说明
java对象创建的过程
类加载检查:查看要创建对象的类是否加载,未加载要先加载类(包括加载--链接--初始化)
分配内存:为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)。
初始化:分为实例变量初始化、实例代码块初始化 以及构造函数初始化。
实例变量初始化、实例代码块初始化:
我们在定义(声明)实例变量的同时,还可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。它们将在构造函数执行之前完成这些初始化操作。
构造函数初始化,先调用父类的构造函数(以保证所创建实例的完整性)再调用该类的构造函数
类加载过程
分为加载--链接--初始化
加载:通过类加载器将类的字节码文件加载到内存中。
- 验证:确保加载的类符合Java语言规范,不会破坏虚拟机的安全。
- 准备:为类的静态变量分配内存并设置初始值。
- 解析:将类中的符号引用转换为直接引用。
初始化:执行类的初始化代码,包括静态变量赋值和静态代码块的执行。
Jdk的组成
jdk是Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
- Jre是Java 运行时环境。它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。
- javac.exe:javac.exe 是 Java 编译器,用于将 Java 源代码文件(.java 文件)编译成 Java 字节码文件(.class 文件),以便在 Java 虚拟机上运行。
- java.exe:java.exe 是 Java 运行时环境的一部分,用于在 Java 虚拟机上执行 Java 字节码文件(.class 文件),实现 Java 程序的运行。
- jar.exe:jar.exe 是 Java 的归档工具,用于创建和管理 Java 归档文件(.jar 文件),可以将多个 Java 类文件、资源文件打包成一个可执行的 JAR 文件,方便分发和部署 Java 应用程序。
各种基础
机器码
用于指挥计算机应做的操作和操作数地址的一组二进制数。
为什么采用二进制:
计算机是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用“1”和“0”表示。
存储单位转换
1TB=1024GB
1GB=1024MB
1MB=1024KB
1KB=1024Byte
1Byte=8Bit
Byte:字节
Bit:比特或位
为什么要编码
计算机中存储信息的最小单元是一个字节即 8 个 bit,所以能表示的字符范围是 0~255 个 人类要表示的符号太多,
无法用一个字节来完全表示 要解决这个矛盾必须需要一个新的数据结构 char,从 char 到 byte 必须编码
计算中提拱了多种翻译方式,常见的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。
它们都可以被看作为字典,它们规定了转化的规则,按照这个规则就可以让计算机正确的表示我们的字符。
不用的应用场景使用不同的码表来应对出现的各种字符,最后都要转化为字节,码表就是字符和字节的对应关系表
PATH环境变量
系统执行用户命令时,若用户未给出绝对路径,则首先在当前目录下寻找相应的可执行文件,如果没有去注册表找,若还找不到,再依次在PATH保存的这些路径中寻找相应的可执行的程序文件。
如果我们要添加java的path环境变量就吧jdk的bin目录添加到里面
解压版的jdk需要设置path目录,安装版的不需要设置了
因为安装版安装是会把java.exe复制到了C:\Windows\System32文件夹中,而该文件地址在path环境变量中
path环境变量是为了方便执行命令行
JAVA_HOME环境变量
安装完java后我们都会配置JAVA_HOME环境变量,映射到jdk安装目录如:D:\myfile\jdk\8\jdk1.8.0_221
JAVA_HOME是给其他软件使用的,比如tomcat运行就需要,maven也需要
classpath
CLASSPATH环境变量:
设置的值为:jdk安装目录下的lib子目录中的dt.jar和tools.jar,如:
.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;;%JAVA_HOME%\lib\rt.jar
.代表当前目录就是命令行定位到的目录
dt.jar是关于运行环境的类库,主要是swing的包
tools.jar是关于一些工具的类库
rt.jar包含了jdk的基础类库
classpath环境变量的作用就是提供class文件的位置,java命令运行时会从classpath中寻找依赖的及要运行的class文件。
classpath是java应用的资源入口,一般存放:配置文件,jar包,class文件
javac命令
javac [ options ] [ sourcefiles ] [ @files ]
options命令行选项。sourcefiles一个或多个要编译的源文件(例如 MyClass.java)。@files一个或多个对源文件进行列表的文件。
如果源文件数量多,则将源文件名列在一个文件中,名称间用空格或回车行来进行分隔。然后在 javac 命令行中使用该列表文件名,文件名前冠以 @ 字符。
命令行定位到java文件所在目录,默认编译后的class文件和源文件再同一个文件夹下
javac Hello.java
javac -d d:/test1/target Hello.java 在指定目录中生产class文件
javac -classpath c:\junit3.8.1\junit.jar Xxx.java 指定依赖的jar包编译java文件
javac -classpath %CLASSPATH% Xxx.java 也可以直接使用环境变量
java命令行
执行类:
java [-options] className [args...]
执行jar文件:
java [-options] -jar jarfile [args...]
options:由空格分隔的命令选项
args 传给启动类的main()方法的参数,用空格分开
常用的命令:
java -version :查看jdk版本,和使用的虚拟机是server还是client模式
java -Dname=value 设置系统参数,系统中可以通过System.getProperty("name")获取到
java -classpath 被引用类的.class文件不在当前目录下时,可以指定加载类的路径,-cp等效于-classpath
java -Xms768m等-X开头的是用来指定jvm运行参数
实例:
java com.xgss.Hello (java命令会加载classpath中的class文件,并执行指定的class文件)
运行jar文件中的class :加上参数-cp
java -cp test.jar com.xgss.Hello
运行带有jvm参数的命令:
java -Xms256M -Xmx512M com.xgss.Hello
javap命令行
javap 是JDK自带的反汇编器,使用它可以查看字节码文件(.class文件),可以对照源代码和字节码,从而了解很多编译器内部的工作。
如
javap JavapTest
可以看到:自动添加了无参构造器
|
public class com.xgss.mybasic.JavapTest { public com.xgss.mybasic.JavapTest(); public java.lang.Integer getA(); public void setA(java.lang.Integer); public java.lang.String getB(); public void setB(java.lang.String); } |
使用javap -c JavapTest
|
Compiled from "JavapTest.java" public class com.xgss.mybasic.JavapTest { public com.xgss.mybasic.JavapTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return
public java.lang.Integer getA(); Code: 0: aload_0 1: getfield #2 // Field a:Ljava/lang/Integer; 4: areturn
public void setA(java.lang.Integer); Code: 0: aload_0 1: aload_1 2: putfield #2 // Field a:Ljava/lang/Integer; 5: return
public java.lang.String getB(); Code: 0: aload_0 1: getfield #3 // Field b:Ljava/lang/String; 4: areturn
public void setB(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #3 // Field b:Ljava/lang/String; 5: return |
使用
javap <options> <classes>
-help 输出 javap 的帮助信息。
-l 输出行及局部变量表。
-b 确保与 JDK 1.1 javap 的向后兼容性。
-public 只显示 public 类及成员。
-protected 只显示 protected 和 public 类及成员。
-package 只显示包、protected 和 public 类及成员。这是缺省设置。
-private 显示所有类和成员。
-J[flag] 直接将 flag 传给运行时系统。
-s 输出内部类型签名。
-c 输出类中各方法的未解析的代码,即构成 Java 字节码的指令。
-verbose 输出堆栈大小、各方法的 locals 及 args 数,以及class文件的编译版本
-classpath[路径] 指定 javap 用来查找类的路径。如果设置了该选项,则它将覆盖缺省值或 CLASSPATH 环境变量。目录用冒号分隔。
-bootclasspath[路径] 指定加载自举类所用的路径。缺省情况下,自举类是实现核心 Java 平台的类,位于 jrelib下面。
-extdirs[dirs] 覆盖搜索安装方式扩展的位置。扩展的缺省位置是 jrelibext。
java对象存储结构
(引入org.openjdk.jol包可以对对象大小分析查看各部分大小)
对象在内存中的存储布局分为 3 块区域:对象头、实例数据和对齐填充。
对象头(三部分组成):
- MarkWord:存储hashcode、锁状态、分代年龄等
- 类元数据指针(指向类元数据)
- 数组长度(数组对象有)
占用的大小
MarkWord: 32位操作系统32位(4字节);64位占用64位(8字节)
元数据指针:32位占用32位(4字节);64位未开指针压缩占用64位(8字节),开启指针压缩32位(4字节)
数组长度:32位占用32位;64位未开启指针压缩64位,开启指针压缩32位
(压缩指针默认是开启的)
所以在64位机器上对象头的空间占有:
非数组:12字节-16字节(默认12字节)
数组:16字节-24字节(默认16字节)
实例数据:
这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,引用类型是4个字节
引用类型:32位4个字节;64位开启指针压缩4个字节,未开启压缩指针8个字节
对齐填充:
按8个字节对齐,对象总大小凑齐8字节的倍数,要凑齐的原因是方便计算
对于对象的方法本身并不存储在对象实例中。方法是存储在类的方法区(Method Area)中的,所以不影响对象的大小。
对于类中的静态变量是存储在类的静态存储区域中也不影响对象的大小。
java语法
java中的数据类型
引用数据类型和基本数据类型
引用数据类型:
数组、类、接口。(引用类型大小统一为4个字节,记录的是引用对象的地址)
基本数据类型:
8个基本数据类型包括byte(1个字节)、short(两个字节带符号)、int(四个字节)、long(八个字节)、char(两个字节无符号unicode编码)、float(四个字节)、double(八个字节)、boolean(存储大小跟虚拟机有关,java中整型值和布尔值之间不能进相互转换)。
(1个字节=8位)(int最大值:011111共31个1,也就是2^31-1,最小值:100000共31个0,也就是2^31加个负号。)
(unicode编码特征:所有字符都占两个字节)
char其默认值是\u0000,这是一个空字符,其Unicode编码为0;取值范围为0—-2^16-1 ,不能为负数。
char的最大值为2的16次方减1,我们可以这样赋值char a=128;可以直接用数字来初始化一个char变量,只要这个数字在char类型的取值范围内
三元操作符如果遇到可以转换为数字的类型,会做自动类型提升。
Object o1 = true ? new Integer(1) : new Double(2.0); 打印出来是1.0
byte a=127;编译不会报错,byte=128;编译报错不能直接赋值超过最大值的值。
long a=0xfffL;可以编译通过
long test=012;这样定义也不错。
byte,short,char(或三者的混合) 参与运算 ,会自动提升为int。
System.out.println(5/-2);//-2
System.out.println(-5/2);//-2
System.out.println(-5/-2);//2
System.out.println(-5%2);//-1
System.out.println(5%-2);//1
取余取头
double d=5.3e12; 正确,表示5.3乘以10的12次方
常量的定义
//基本数据类型
public static final int MAX_VALUE = 100;
//字符串类型
public static final String NAME = "John Doe";
//枚举类型
public enum Color {
RED, GREEN, BLUE
}
//接口中的变量默认是public static final的
public interface MyConstants {
int MAX_VALUE = 100;
double PI = 3.141592653589793;
}
//非静态常量
public class MyClass {
private final int myConstant = 10;
}
常量也可以是private等其他访问修饰符修饰
常量必须用final修饰,枚举和接口中都是隐藏了final也都是不可修改的
java中final修饰的变量有三种:静态变量、实例变量和局部变量
常量的赋值
final修改的常量只能被赋值一次
静态常量需要在声明时或静态代码块中赋值
public class MyClass {
public static final int MY_CONSTANT = 42; // 在声明时赋值
// 或者
public static final int MY_OTHER_CONSTANT;
static {
MY_OTHER_CONSTANT = 100; // 在静态初始化块中赋值
}
}
成员常量
可以在声明时赋值,也可以在构造器中赋值
局部常量
必须在声明时或者在声明之后的某个点被赋值
public void myMethod(boolean condition) {
final int localConstant;
if (condition) {
localConstant = 10; // 在条件满足时赋值
} else {
localConstant = 20; // 在另一个条件满足时赋值
}
// 使用localConstant...
}
基本数据类型类型转换
级别从低到高为:byte,char,short(这三个平级)-->int-->long-->float-->double
自动类型转换:从低级别到高级别,系统自动转的;因为是高位扩展不存在转换风险
强制类型转换:高级别值赋值给低级别的变量就要强转;
+=这样的运算包含了一个隐含的强转转换。要舍弃高位存在风险
整数不带后缀默认是int型,小数不带后缀默认是double型的
低级别值跟没有后缀的的整数或小数运算就会自动升级为int或double,所以如果用低级别变量接收就要强转
long到float的转换是自动进行的,称为宽化转换,这种转换可能会导致精度损失,int也可以自动转换为float.
将float转换为long或int需要进行强制类型转换
Double d = 100;这样是有问题的,可以这样double a=100;这样int自动转double,之后double转Double自动装箱,不能隔着来
i++与++i区别
i++ 先运算再赋值,例如 a=i++,先运算a=i,后给i赋值i=i+1,所以结果是a==1;过程 temp=i,i=i+1,a=temp;
++i 先赋值再运算,例如 a=++i,先给i赋值i=i+1,后运算a=i,所以结果是a==2
int a=2; a=a++; 最终 a=2;
return的用法
可以返回方法的返回值
方法没有返回值void,可以使用return;用于结束方法(循环中的break只是结束循环,return是结束方法)
原码反码补码
原码:在数值前直接加一符号位的表示法。
正数的符号位是0,负数的符号位是1
数0的原码有两种形式 [+0]原=00000000 [-0]原=10000000
反码:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
补码:正数的补码与其原码相同;负数的补码是在其反码的末位加1。
位运算
and运算 &:都为1才为1
00101
11100
--------
00100
or运算 |:有一个1就是1
00101
11100
--------
11101
xor运算异或 ^:不同为1,相同为0
00101
11100
--------
11001
移位运算符
位移运算用的都是补码计算,原码转为补码再运算
右移 >>:砍掉低位,高位要看最高位符号位,符号位为1就补1,符号位为0就补0
<<左移,砍掉高位,低位补0
位移运算时,首先会将移动的位数对该类型占的位数进行取余,例如 a>>33 相当于 a>>1
(int是 32位),左移n位就相当于乘以2的n次方。
>>>无符号右移,砍掉低位,不考虑符号,高位都补0,右移n位相当于除以2的n次方。
3 << 2:3左移2位 (int共32位(4个字节,一个字节8位),正整数,高端位都是0)
0011
1100 值为12
11 >> 2:11右移2位
1011
0010 值为2
java中的main方法
main方法是程序的入口
main方法可以被重载,但重载的方法都不能作为程序的入口
main方法不管是被重载的非入口方法还是入口方法都是可以被调用的
main方法也是可以被继承的
启动一个java应用程序,JVM会查找类中的public static void main(String[] args),如果找不到该方法就抛出错误NoSuchMethodError:main 程序终止。
对于web项目是交给web服务器(如tomcat)来执行的,web服务器启动时启动服务器的main方法,应用程序的就不需要了
一个java应用可以有多个main方法,运行程序时可以指定那个作为入口方法
tomcat的main方法在:
org.apache.catalina.startup.Bootstrap类
java项目中有多个main方法
打包时指定主类:
最终会在jar包中的META-INF\MANIFEST.MF中会显示:
Main-Class: com.xgss.Mymain
打包时指定了main类,运行时就不需要指定了
方式一:
在maven-jar-plugin的参数中有指定主类的参数
方式二:
使用eclipse导出jar包时也会让指定main类
如果打包时没有指定主类,可以运行时指定主类
java -cp {xxx.jar} {主类名称(绝对路径)}。
如:
java -cp lib/*:conf/:Xgss.jar com.xgss.Mymain
java的数组
数组本质是一个java类,
数组是用于存储同一类型数据的一个容器。数组一旦定义,长度不可变
数组中可以存放基本数据类型数据也可以存放引用类型数据
数组中存放基本数据类型时,存放的是值,如果未赋初值,默认初始化为0;
数组中存放引用类型数据时,存放的是数据的引用,默认初始化为null。
不同数据类型的数组类名都不一样如:
Integer类型变量类名:java.lang.Integer
Integer类型数组变量类名:[Ljava.lang.Integer;
int类型数组变量类名:[I
String类型数组变量类名:[Ljava.lang.String;
数组的定义
定义时必须指定长度或内容
//静态定义
int[] arr = {23,4,67,198,3};
int[][] ma = { {1,2,3},{4,5,6}};
//动态定义
int arr2 [] = new int[10];
int[] arr3 = new int[]{1,3,5,7};
int[][] n = new int [2][]; 不常用,但正确
int[] array[] = new int[3][4]; 不常用,但正确
不能既定义长度又定义具体元素
对于定义了长度没有定义元素的会被赋值为默认值,如int数组,默认赋值为0
数组的赋值
定义的时候赋值
int[] arr = {23,4,67,198,3};
定义之后赋值
int arr2 [] = new int[10];
arr2[0]=1;
访问修饰符的作用域
答:区别如下:
作用域 当前类 同一package 子孙类 其他package
public √ √ √ √
protected √ √ √ ×
friendly √ √ × ×
private √ × × ×
不写时默认为friendly
private 可以修饰数据成员,构造方法,方法成员,不能修饰类(此处指外部类,不考虑内部类)
默认(default) 类,数据成员,构造方法,方法成员,都能够使用默认权限,即不写任何关键字。
protected可以修饰数据成员,构造方法,方法成员,不能修饰类(此处指外部类,不考虑内部类)。
public可以修饰类,数据成员,构造方法,方法成员。
方法中的临时变量不能用访问修饰符修饰,会编译报错。
成员内部类可以看成成员变量也是4种访问修复符都可以
局部内部类就像是方法里面的一个局部变量一样,是不能有访问修饰符的。
匿名内部类是不能有访问修饰符
外部类只能用 public和默认值
Final关键字
修饰的类:类不可以继承,方法不能被重写
修饰的变量:
变量不可被修改
属性常量,变量使用前必须附值,成员变量用final修饰使用前也要必须赋值。
如果是引用变量,引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。
修饰的方法:方法不可以被重写
final参数:当方法参数为final类型时,可以读取使用该参数,但是无法改变参数的值。
final可以修饰方法里面的临时变量;
使用final方法的原因有二:
把方法锁定,防止任何继承类修改它的意义和实现。
高效。编译器在遇到调用final方法时会转入内嵌机制,大大提高执行效率。
示例:
String类是final修饰的
Object类中的getClass(),notify(),notifyAll(),wait方法都是final的
map中为什么一般用string类型做key
string是final类型的,是不可变的,map使用它作为键为了保证可以唯一映射。不管用hash找位置,还是equel比较都需要用到唯一性,如果是可变的那一个key对应多个位置就乱套了。
&和&&的区别。
&和&&都可以用作逻辑与的运算符,表示逻辑与(and)
&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式
&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。 都为1才为1
备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入、实际经验丰富。
数值型字符串转换为数字
int value = Integer.parseInt("123");
Integer value = Integer.valueOf("123");
如何将数值转换为字符
char a=97;
System.out.println(a);
输出的就是a,输出的是对应的字符。
成员变量与局部变量区别
成员变量和局部变量
类中位置上:
成员变量定义在方法的外面
局部变量在方法或者代码块中,或者方法的声明上(即在参数列表中)
在内存中的位置不同
成员变量:在堆中(静态的放在方法区)
局部变量:在栈中
生命周期不同
成员变量:随着对象的创建而存在,随着对象的消失而消失(静态的是类加载的时候创建)
局部变量:随着方法的调用或者代码块的执行而存在,随着方法的调用完毕或者代码块的执行完毕而消失
初始值
成员变量:有默认初始值
局部变量:没有默认初始值,使用之前需要赋值,否则编译器会报错(The local variable xxx may not have been initialized)
跳出当前的多重嵌套循环?
跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如,
ok:
for(int i=0;i<10;i++)
{
for(int j=0;j<10;j++)
{
System.out.println(“i=” + i + “,j=” + j);
if(j == 5) break ok;
}
}
另外,我个人通常并不使用标号这种方式,而是让外层的循环条件表达式的结果可以受到里层循环体代码的控制,例如,要在二维数组中查找到某个数字。
int arr[][] = {{1,2,3},{4,5,6,7},{9}};
boolean found = false;
for(int i=0;i<arr.length && !found;i++)
{
for(int j=0;j<arr[i].length;j++)
{
System.out.println(“i=” + i + “,j=” + j);
if(arr[i][j] == 5)
{
found = true;
break;
}
}
}
switch语句
语法
|
switch(expression){ case value : //语句 break; //可选 case value : //语句 break; //可选 //你可以有任意数量的case语句 default : //可选 //语句 } |
支持的数据类型:
byte,short,int,char,枚举类型,String
在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量,
整数表达式可以是int基本类型或Integer包装类型,由于,byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的。
jdk1.7后switch语句开始支持String。
不支持的数据类型:long、float、double 和 boolean
char型变量中能不能存贮一个中文汉字?为什么?
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。
备注:后面一部分回答虽然不是在正面回答题目,但是,为了展现自己的学识和表现自己对问题理解的透彻深入,可以回答一些相关的知识,做到知无不言,言无不尽。
"=="和equals区别
(单独把一个东西说清楚,然后再说清楚另一个,这样,它们的区别自然就出来了,混在一起说,则很难说清楚)
==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。对象变量的内存中存放的是对象所在内存的地址,用==比较就是比较这个地址是否相同而不是对象内容。
equals方法是用于比较两个独立对象的内容是否相同,基本数据类型不能够调用该方法,常用语比较字符串内容是否相同。对比的是对象变量所指向内存中的内容。
是否可以从一个static方法内部发出对非static方法的调用?
不可以。因为非static方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而static方法调用时不需要创建对象,可以直接调用。也就是说,当一个static方法被调用时,可能还没有创建任何实例对象,如果从一个static方法中发出对非static方法的调用,那个非static方法是关联到哪个对象上的呢?这个逻辑无法成立,所以,一个static方法内部发出对非static方法的调用。
Integer与int的区别
int是java提供的8种原始(基本)数据类型之一。Integer是java为int提供的封装类。
int类型的值一般保存在栈内存中,不会在堆内存中开辟空间。Integer的创建会在堆内存中开辟一块新空间
int类型的值可以利用算术运算符进行加、减、乘、除等操作。
在进行参数传递时,int传递的是它的值,Integer传递的是所代表对象的一个引用
int的默认值为0,而Integer的默认值为null,在JSP开发中,Integer的默认为null,所以用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,所以用el表达式在文本框中显示时,结果为0,所以,int不适合作为web层的表单数据的类型。
在Hibernate中,如果将OID定义为Integer类型,那么Hibernate就可以根据其值是否为null而判断一个对象是否是临时的,如果将OID定义为了int类型,还需要在hbm映射文件中设置其unsaved-value属性为0。
另外,Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。
public void myTest(){
Long a=new Long(21L);
long b=21L;
int c=21;
System.out.println(a.equals(b));
System.out.println(a.equals(c));//Long中的equals方法先看是否是Long类型,在判断基本类型值是否相同
System.out.println(a==b);//比较的是基本类型值是否相等
System.out.println(b==c);
}
输出为:
true
false
true
true
Long类中的equals方法
源码
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
Overload和Override的区别
Override重写:如接口实现中,继承中可能用到:
两同两小一大原则:
- 方法名相同,参数类型数量相同
- 子类返回类型小于等于父类方法返回类型,(返回的类型可以是父类的子类型,)
- 子类抛出异常小于等于父类方法抛出异常,
- 子类访问权限大于等于父类方法访问权限。
被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
overload重载:
- 重载的方法名称必须相同
- 不同的参数类型或不同的参数个数或不同的参数顺序可以;
- 不能通过访问权限、返回类型、抛出的异常进行重载;
- 方法的异常类型和数目不会对重载造成影响;
- 返回的类型可以相同也可以不同,不影响重载方法的定义
- 对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。
抽象方法的作用
抽象的方法是为了让子类必须有这样的方法,子类可以有不同的实现,从而实现多态。
构造器的访问修饰符
四种访问修饰符都可以
public,protected,默认,private(写单例模式就用这个)
默认的构造器是public的无参构造器,是编译的时候默认加上的,如果定义了构造器就不加了
构造器Constructor是否可被override?
构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。
java与c++程序在编译和运行上有什么区别?
分析:
c、c++他们的编译器都是把源码直接编译成计算机可以认识的机器码,如exe、dll之类的文件,然后直接运行即可。
字节码文件指类,接口,枚举或其他类型编译后的文件,不是专指类编译后的文件。
答案:
c++源码编译以后,生成的特定机器可以直接运行的文件,而java源码经过编译后,生成的是中间的字节码文件,这些字节码文件还是需要放在JVM中运行的,而JVM是有多个平台版本的,因此java具有跨平台性,而c++没有
java的引用和c++的指针有什么区别
java的引用和c++的指针都是存放一块内存的地址,通过引用或指针来完成对内存数据的操作。
区别:
- 类型:应用其值为地址的数据元素,java封装了的地址,可以转成字符串查看,长度可以不必关心。c++指针是一个装地址的变量,长度一般是计算机字长,可以认为是个int。
- 所占内存:引用声明没有实体,不占空间;c++指针如果声明后会用到才会赋值,如果用不到不会分配内存。
- 类型转换:引用的类型转换,也可能不成功,运行时抛出异常或者编译就不能通过。c++指针只是个内存地址,指向哪里,对程序来说都是一个地址,但可能所指向的地址不是程序想要的。
- 初始值:引用初始值为java关键字null。c++指针是int,如不初始化指针,那它的值就是不固定的,这很危险。
- 计算:引用是不可以计算,c++指针是int,它可以计算,如++、--,所以经常用指针来代替数组下标。
- 控制:引用不可以计算,所以它只能在自己的程序中,可以被控制。c++指针是内存地址,可以计算,所以它可能指向一个不属于自己程序使用的内存地址,对于其他程序来说是很危险的,对自己程序来说也是不容易被控制的。
- 内存泄露:java引用不会产生内存泄露。c++指针容易产生内存泄露的,所以程序员要小心使用,及时回收。
- 作为参数:java中的方法参数只是传值,引用作为参数时,操作的是指向的对象。c++的指针作为函数参数,操作的是指针代表的地址。
总的来说,java的办法更安全和方便,但没有c++的那么灵活。
写clone()方法时,通常都有一行代码,是什么?
clone 有缺省行为,super.clone();因为首先要把父类中的成员复制到位,然后才是复制自己的成员。
abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?
抽象方法只能用public或protected修饰
abstract的method 不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系!
native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用。例如,FileOutputSteam类要硬件打交道,底层的实现用的是操作系统相关的api实现,例如,在windows用c语言实现的,所以,查看jdk 的源代码,可以发现FileOutputStream的open方法的定义如下:
private native void open(String name) throws FileNotFoundException;
如果我们要用java调用别人写的c语言函数,我们是无法直接调用的,我们需要按照java的要求写一个c语言的函数,又我们的这个c语言函数去调用别人的c语言函数。由于我们的c语言函数是按java的要求来写的,我们这个c语言函数就可以与java对接上,java那边的对接方式就是定义出与我们这个c函数相对应的方法,java中对应的方法不需要写具体的代码,但需要在前面声明native。
关于synchronized与abstract合用的问题,我觉得也不行,因为在我几年的学习和开发中,从来没见到过这种情况,并且我觉得synchronized应该是作用在一个具体的方法上才有意义。而且,方法上的synchronized同步所使用的同步锁对象是this,而抽象方法上无法确定this是什么。
介绍java中的静态成员的特点
类的静态成员是通过static关键字修饰的成员,包括:静态成员变量、静态方法、静态代码块,他们具有以下一些特点:
- 在类加载时,就进行创建和初始化或执行代码。
- 它们对于一个类来说,都只有一份。
- 类的所有实例都可访问到它们。
简述java派生类中的构造方法如何为父类传递参数
分析:在类的继承过程中,子类的创建是以父类的构造方法的调用为前提,子类的构造方法中会调用父类的构造方法。也就是说子类的创建会建立在父类的基础上。
如:
父类用的无参的构造方法,子类中就不用显式的写出构造方法,如果写出来,父类的构造方法也不用显式的写出来,默认会在构造方法调用super();
父类中:Test(){System.out.println(2);};
子类中:什么也不写就行。
父类用的有参的构造方法,子类中就要定义和父类相同参数的构造方法,并显式调用父类构造方法
父类中:Test(int i){System.out.println(2);};
子类中:Test2(int i) {super(i);}
答:使用super关键字加括号()形式来为父类的构造方法提供参数,通过参数的数目和类型决定调用哪一个构造方法,如果调用的是父类无参的构造方法,可以不显式地使用super()。
return和finally语句那个先执行
public class smallT
{
public static void main(String args[])
{
smallT t = new smallT();
int b = t.get();
System.out.println(b);
}
public int get()
{
try
{
return 1 ;
}
finally
{
return 2 ;
}
}
}
返回的结果是2。
我可以通过下面一个例子程序来帮助我解释这个答案,从下面例子的运行结果中可以发现,try中的return语句调用的函数先于finally中调用的函数执行,也就是说return语句先执行,finally语句后执行,所以,返回的结果是2。Return并不是让函数马上返回,而是return语句执行后,将把返回结果放置进函数栈中,此时函数并不是马上返回,它要执行finally语句后才真正开始返回。
在讲解答案时可以用下面的程序来帮助分析:
|
public class Test { public static void main(String[] args) { System.out.println(new Test().test());; } int test() { try { return func1(); } finally { return func2(); } }
int func1() { System.out.println("func1"); return 1; } int func2() { System.out.println("func2"); return 2; } } |
-----------执行结果-----------------
func1
func2
2
结论:finally中的代码比return 和break语句后执行
类
静态变量和实例变量的区别
在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
在程序运行时的区别:静态变量是在类加载时初始化,实例变量是在对象创建时初始化
在使用上的区别:静态变量可以由类及实例调用,实例变量只能用实例调用。
接口
用interface修饰进行定义,接口中不能有方法的实现,接口中的属性都是public static final类型的,也可以显示的修饰(也可以部分显示 pulic static,这样final还是默认的)
接口是一个特殊的类,接口可以继承多个接口、
抽象类
不能用final修饰,用于让子类覆盖方法,final了就不能被继承了
简述接口和抽象类的区别
(区别,先说概念,并说共同点再说区别,,分别从类,变量,方法,应用上联想)
(类方面)
抽象类,使用abstract class修饰的类;接口,用interface修饰。
一个类只能继承一个抽象类,可以实现多个接口。
两者都不能被实例化
(变量)
抽象类里可以有普通成员变量,接口中只能有常量。
抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static类型,并且默认为public static类型
(方法)
1抽象类里可以有构造方法,而接口内不能有构造方法。
抽象类中能有非抽象成员方法,接口中只能有抽象方法
3抽象类中的抽象方法的访问类型可以是public ,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型
4抽象类中可以包含静态方法,接口内不能包含静态方法。
(应用)
接口更多的是在系统框架设计方法发挥作用,主要定义模块之间的通信,而抽象类在代码实现方面发挥作用,可以实现代码的重用
接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)? 抽象类中是否可以有静态的main方法?
接口可以继承接口(用extends关键字不能用implements关键字)。抽象类可以实现(implements)接口,抽象类可继承具体类。抽象类中可以有静态的main方法。
只有记住抽象类与普通类的唯一区别就是不能创建实例对象和允许有abstract方法。
内部类
内部类是在public类中定义的其他类
类型:
成员内部类、静态内部类、局部内部类、匿名内部类
成员内部类:
作为类成员定义的内部类,需要使用外部类实例进行实例化
静态内部类:
在成员内部类的基础上,使用static修饰;可以使用外部类直接实例化
MyClass2 c2=new MyClass.MyClass2();
局部内部类:
在方法中定义的有名字的内部类,直对当前方法可见如:
|
public void method3() { class Inner { private String s1 = "this is s1 in Inner"; public void method1() { System.out.println(“hello”);
} } Inner inner = new Inner(); inner.method1(); } |
匿名内部类:
在方法中定义的没有名字的内部类,只对当前方法可见
|
public static void main(String[] args) { Thread t = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + "=>" + i); } }); t.start(); } |
内部类的限制:
不能有静态方法
内部类访问权限:
成员内部类和静态内部类:(对外就类似成员变量和静态成员变量一样)
public:所有其他类可以访问
protected:当前包和子类可以访问
默认省却的:当前包可以访问
private:只在当前类可以访问
局部内部类和匿名内部类:
不能使用访问权限修饰符,只有当前方法能够访问
内部类什么时候加载
跟一般的类一样都是在使用的时候才去加载
Static Nested Class 和 Inner Class的不同?
Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。
内部类可以引用它的包含类的成员吗?有没有什么限制?
完全可以。如果不是静态内部类,那没有什么限制!
如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员
jdk中哪些类是不能继承的?
不能继承的是类是那些用final关键字修饰的类。
一般比较基本的类型或防止扩展类无意间破坏原来方法的实现的类型都应该是final的,在jdk中System,String,StringBuffer等都是基本类型。
说一下枚举类型
Java中所有的枚举类型都是java.lang.Enum的子类。
其作用是将一组相关的常量定义在一个类型中,持有一组相关的常量,方便使用。
- 除了定义时指定的枚举常量,枚举类型不会有其他实例。
- 枚举类型可以直接用于switch语句,java编译器会自动在枚举成员上调用ordinal()方法
- 枚举类型定义不能使用extends短语,但隐含地扩展lang.Enum抽象类。枚举类型不能用final修饰,但也不能被扩展。
- 枚举类型中,其他语法成分必须出现在枚举常量列表的后面
- 枚举类是特殊的java类,是单例模式,枚举类的构造方法必须是私有的,因为实例已经创建好的是固定的,不能做修改
- 可以使用枚举类型的values()静态方法返回枚举类型中的所有枚举值
|
public enum AdvancedWeek{
MONDAY("星期一", "Mon."), TUESDAY("星期二", "Tues."), WEDNESDAY("星期三", "Wed."), THURSDAY("星期四", "Thurs."), FRIDAY("星期五", "Fri."), SATURDAY("星期六", "Sat."), SUNDAY("星期日", "Sun.");
private String nameCn; private String abbreviation;
//构造方法只能为private,定义其他类型会报错,不写也是private private AdvancedWeek() {}
private AdvancedWeek(String nameCn, String abbreviation) { this.setNameCn(nameCn); this.setAbbreviation(abbreviation); }
public String getNameCn() { return nameCn; }
public void setNameCn(String nameCn) { this.nameCn = nameCn; }
public String getAbbreviation() { return abbreviation; }
public void setAbbreviation(String abbreviation) { this.abbreviation = abbreviation; } } |
调用:
String nameCn=AdvancedWeek.MONDAY.getNameCn();
AdvancedWeek[] weeks=AdvancedWeek.values();
过滤器,拦截器,监听器
过滤器:
对web资源进行过滤处理,可以过滤request也可以过滤response
创建一个过滤器类需要实现Filter接口,实现三个方法:
void init(FilterConfig config): 用于完成Filter 的初始化。项目启动时调用初始化方法。
void destroy(): 用于Filter 销毁前,完成某些资源的回收。项目关闭时调用destroy方法。
void doFilter(ServletRequest request, ServletResponse response,FilterChain chain): 实现过滤功能,该方法就是对过滤的请求或响应做额外处理。chain.doFilter()来放行。
过滤器配置方法:
在web.xml进行配置或者使用@WebFilter注解来配置
匹配的url可以使用通配符/*表示匹配所有,静态内容也会被过滤
拦截器:
创建一个拦截器类需要实现Interceptor接口,
实现三个方法:
void init();
void destroy();
String intercept(ActionInvocation invocation) throws Exception;
public String intercept(ActionInvocation invocation) throws Exception{
System.out.println("Action执行前插入 代码"); //执行目标方法 (调用下一个拦截器, 或执行 Action)
final String res = invocation.invoke();
System.out.println("Action执行后插入 代码");
return res; }
struts2中的拦截器是在struts.xml文件中配置,
拦截器与过滤器的区别 :
- 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
- 拦截器不依赖于servlet容器,过滤器依赖于servlet容器。
- 拦截器可以在url对应方法执行前后做额外的处理,过滤器是在请求前后对请求和响应做额外的处理不能在方法前后。(spring中拦截器是针对url的,struts2的拦截器相当与spring中的AOP)
- 过滤器可以修改request,而拦截器不能
AOP相对于拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑。
监听器
监听器是一个事件源注册监听事件,当事件源发生对应的事件时就会执行监听其中定义的对应方法,在学习servlet时接触过ServletContextListener,HttpSessionListener,ServletRequestListener
过滤器的应用
登陆校验,统一设置编码格式,访问权限控制,敏感字符过滤等
拦截器的应用
日志记录:记录请求信息的日志
权限检查:如登录检查
性能检测:检测方法的执行时间
执行顺序依次是过滤器、拦截器、切面。
一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。
说一下泛型
利用泛型可以把运行时可能出现的问题,转变为编译时出现的问题,如(由于集合可以存储不同类型的数据,所以取元素时会导致类型转换错误)
提高代码的重用率
泛型在编译时候使用,确保数据的安全性,避免强制类型转换,一旦编译完成,所有和泛型有关的信息全被擦除。
JVM并不知道泛型,所有的泛型在编译阶段就已经被处理成了普通类和方法。
有一些方法可以间接地在运行阶段获取泛型类型的部分信息。例如,通过反射API和泛型类型的保留信息(如果可用的话)
java的反射机制
java的反射是大多数框架的基础,如struts,Hibernate和Spring等。
通过反射机制可以获得一个陌生的java类的所有信息。如属性,方法,构造器,修饰符,注解等。
也就是说反射是为了能够动态地加载一个类,动态地调用一个方法,动态地访问一个属性等
它的出发点在于JVM会为每个类创建一个java.lang.Class类的实例,通过这个对象可以获得这个类的信息。
反射的应用:
依赖注入,注解的解析
Class对象说明
通过类的Class对象可以获取类的属性,方法,构造器,修饰符等信息。
一个加载的类在JVM中只会有一个Class实例
每个类的实例都会记得自己是由哪个Class实例所生成(java中的实例对象就是通过Class对象来创建的)
生成的Class实例被存放在堆中
拥有Class对象的类型
- class:外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- Void
Class对象产生的时机
Class对象是在类加载的时候由Java虚拟机自动创建的
获取Class对象的方法:
- Class 类的 forName 静态方法:使用类的完全限定名(JDBC 开发中常用此方法加载数据库驱动)
Class c1 = Class.forName("io.github.dunwu.javacore.reflect.ReflectClassDemo01");
- 使用类的class属性:
Class c1 = MyClass.class;
- 调用 Object 的 getClass 方法,返回产生该对象的Class对象
Class c1 =MyClass. getClass()
反射与Class对象
反射基于类的Class对象来完成
反射和动态代理
动态代理可以增强委托类的方法,动态代理会生成新的类来完成方法的增加;
动态代理的实现依赖于反射来完成
jdk动态代理会通过反射生成委托类同一个接口的实现类来代替委托类(所以需要委托类必须实现了一个接口)
cglib动态代理会通过反射生成一个委托类的子类,覆盖其方法来实现增强
mybatis的mapper动态代理使用的jdk动态代理实现
注解
注解:
注解是Java中的一种特殊标记,用于为程序元素(类、方法、字段、参数等)添加元数据信息。它们提供了一种在源代码中嵌入元数据的方式,可以被编译器、工具或框架读取和处理。注解以@符号开头,可以用于描述代码的行为、约束和配置。
注解本质上是继承了 Annotation 接口的接口
注解的作用:
包括但不限于:
- 提供元数据:可以为程序元素(类、方法、字段等)添加额外信息。如:@Override注解用于标记方法覆盖父类方法,提供编译器检查。
- 编译时检查:可以在编译时对代码进行检查或生成额外的代码。如@Deprecated注解用于标记过时的方法或类,在编译时会产生警告。
- 运行时处理:可以在运行时通过反射机制获取注解信息并进行相应处理。@Autowired注解用于自动装配Bean,在运行时通过反射实现依赖注入。
- 简化配置:可以简化配置文件的编写,提高代码的可读性和维护性。@Test注解用于标记测试方法,简化测试类的配置。
- 自动生成文档:可以用于生成文档或说明注解的作用。@Override注解用于标记方法覆盖,IDE可以根据该注解生成文档。
注解可以修饰的内容:
装饰类(@Deprecated),接口(@FunctionalInterface),枚举(@Retention(RetentionPolicy.RUNTIME)),方法(@Override),字段(@NotNull),参数(@RequestParam),变量(@SuppressWarnings("unused")),构造函数(@Deprecated),包(@Native),注解(@Retention(RetentionPolicy.RUNTIME))。
元注解:
用于修饰注解的注解,通常用在注解的定义上
- @Target:注解的作用目标(类,方法,字段……)
- @Retention:注解的生命周期(编译期可见class文件不可见,class文件可见类加载后不可见,永久保存可以被反射获取到)
- @Documented:注解是否应当被包含在 JavaDoc 文档中
- @Inherited:是否允许子类继承该注解
@Target注解的属性:
- ElementType.TYPE:可以用在类、接口(包括注解类型)或枚举声明上。通常使用改值
- ElementType.FIELD: 可以用在字段上。
- ElementType.METHOD: 可以用在方法上。
- ElementType.PARAMETER: 可以用在参数上。
- ElementType.CONSTRUCTOR: 可以用在构造函数上。
- ElementType.LOCAL_VARIABLE: 可以用在局部变量上。
- ElementType.ANNOTATION_TYPE: 可以用在注解类型上。
- ElementType.PACKAGE: 可以用在包声明上。
@Retention注解用于指定注解的保留策略,它有一个属性value,可以取以下三个值:
- RetentionPolicy.SOURCE: 注解仅存在于源代码中,在编译时会被丢弃。
- RetentionPolicy.CLASS: 注解会被保留到编译后的字节码文件中,但在运行时不可获取。
- RetentionPolicy.RUNTIME: 注解会被保留到运行时,可以通过反射机制获取。
注解的解析:
编译时处理:
编译时处理需要使用到APT技术,该技术提供了一套编译期的注解处理流程。
运行时处理:
对于jvm中可见的注解,注解的解析会通过反射来生成一个实现了该注解接口(因为注解本身是接口)的实例对象,来获取注解中定义的内容
如何使用注解完成运行时处理的
java使用注解来完成运行时处理的过程通常涉及反射机制。以@Autowired注解为例,以下是简要的处理过程:
1. 在编译时,@Autowired注解会被编译器保留到字节码文件中。
2. 在运行时,当类被加载到内存中,通过反射机制可以获取类的注解信息。
3. 当Spring容器初始化时,会扫描类路径下的所有类,检查是否有使用@Autowired注解的字段或方法。
4. 对于使用了@Autowired注解的字段或方法,Spring会通过反射找到对应的类,并自动注入依赖。
5. 运行时,当需要使用被@Autowired注解标记的字段或方法时,Spring会自动完成依赖注入,将相应的实例注入到目标对象中。
通过反射和Spring容器的管理,@Autowired注解实现了依赖注入的功能,简化了代码的配置和管理。
自定义注解
使用@interface关键字定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomAnnotation {
String value() default "default value";
int count() default 0;
}
定义了一个名为CustomAnnotation的自定义注解,它可以用在方法上,并包含两个属性value和count,分别具有默认值"default value"和0。
concurrent包说明
java.util.cucurrent包,在rt.jar中
concurrent:并发,简称JUC,主要用于并发控制编程
包含的内容:
java.util.concurrent包下:提供线程池,线程安全类
ConcurrentHashMap,Callable接口,Executor线程池接口,Executors线程池工具类,ThreadPoolExecutor线程池实现类,BlockingQueue阻塞队列接口,DelayQueue延迟队列类等
java.util.concurrent.atomic包下:提供原子操作(在执行过程中不会被中断的操作,要么全部执行成功,要么全部不执行,不会出现部分执行的情况)
AtomicInteger,AtomicLong,AtomicReference等使用了volitile来修饰成员变量,并提供了很多通过cas操作保证原子性的操作,如getAndSet,getAndAdd, getAndIncrement,compareAndSet(两个参数判断参数1值和内存值相等才进行更新为参数2)
|
private volatile int value; public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; } } |
java.util.concurrent.locks包下:提供锁
Lock锁接口,ReentrantLock可重入锁类等
ConcurrentHashMap
是一个线程安全的哈希表实现,它允许多个线程同时读取和修改其中的数据,而不会发生数据不一致的情况。
继承了AbstractMap实现了ConcurrentMap接口在集合类面试题总结中有详细说明
BlockingQueue
BlockingQueue是一个接口,定义了阻塞队列中常用到的方法,下面有很多的实现类
如:ArrayBlockingQueue;DelayQueue;LinkedBlockingQueue;等
(redisson的阻塞队列RedissonBlockingQueue是RBlockingQueue的实现,而RBlockingQueue继承了BlockingQueue)
DelayQueue:延迟队列使用优先级队列实现,元素是按到期时间有序存储在队列中的(根据元素的compareTo方法比较),队头是最早到期的,多个线程去获取队头元素如果还没到时间会争抢占有权,有一个会获取占有权成功,之后这个线程会等待到达到期时间再次获取,其他线程一直处于等待状态,到期时间后有占有权的线程去消费元素,并发信号给其他线程,其他线程在去争抢.
CountDownLatch运动员赛跑问题
构造方法指定count,然后调用await方法进行挂起线程,调用一次countDown方法count就减一,当减完的时候,线程就绪获取cpu后执行
几个运动员从起跑线听到枪声起跑,到终点使用并发包中的一个类实现
在百米赛跑中,多个参赛队员在听到发令枪响之后,开始跑步,到达终点后结束计时,然后统计各个队员的成绩。
这个在线程中需要考虑两点,第一:发令枪响,这是所有跑步队员(线程)接收到的出发信号,此处涉及到裁判(主线程)如何通知跑步者(子线程)的问题;第二:如何知道所有跑步者完成了赛跑,也就是主线程如何知道子线程已经全部完成
使用CountDownLatch工具类来实现。
CountDownLatch
在java1.5被引,在java.util.cucurrent包下
countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕。这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。
构造器:
//参数count为计数值
public CountDownLatch(int count)
常用的方法:
|
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行 public void await() throws InterruptedException { }; //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //将count值减1 public void countDown() { }; |
应用场景:
场景一:
某一线程在开始运行前等待n个线程执行完毕。
将CountDownLatch的计数器初始化为n:new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1, countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
场景二:
实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
赛跑代码:
|
ublic class Test {
//比赛开始的倒计时 private static CountDownLatch countDownLatchStart = new CountDownLatch(1); //比赛结束的倒计时 private static CountDownLatch countDownLatchEnd = new CountDownLatch(3);
static class Runner implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "准备就绪"); try { countDownLatchStart.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "开始跑步"); try { //防止取出0 int time = new Random().nextInt(3) + 1; TimeUnit.SECONDS.sleep(time); System.out.println(Thread.currentThread().getName() + "经过" + time + "秒后抵达终点"); countDownLatchEnd.countDown();
} catch (InterruptedException e) { e.printStackTrace(); } } }
public static void main(String[] args) throws InterruptedException { new Thread(new Runner(), "一号运动员").start(); new Thread(new Runner(), "二号运动员").start(); new Thread(new Runner(), "三号运动员").start(); //给足时间让运动员准备 TimeUnit.SECONDS.sleep(5); System.out.println("各就各位,GO!"); //开跑 countDownLatchStart.countDown(); //等大家都到终点,就宣布比赛结束 countDownLatchEnd.await(); System.out.println("比赛结束"); } } |
日期时间
获得现在日期的年月日
Calendar now=Calendar.getInstance();
System.out.println(now.get(Calendar.YEAR));
System.out.println(now.get(Calendar.MONTH)+1);
System.out.println(now.get(Calendar.DAY_OF_MONTH));
获得现在月的天数
Calendar now=Calendar.getInstance();
now.getActualMaximum(Calendar.DAY_OF_MONTH);
异常处理
为什么要进行异常处理
为了使程序更加的健壮,能够应对出现的各种状况
对用户更加的友好
Java中的异常处理机制的简单原理和应用。
Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable
Throwable下面又派生了两个子类:Error(错误)和Exception(异常),
Error 表示应用程序本身无法克服和恢复的一种严重问题,程序只有死的份了,例如,说内存溢出和线程死锁等系统问题。
Exception表示程序还能够克服和恢复的问题,其中又分为一般异常(编译时异常)和运行时异常
error和exception有什么区别?
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。
exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。
(error错误也是可以使用try catch捕获的,但通常不推荐,因为这意味着你正在尝试处理JVM认为无法恢复的情况)
Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形,Error不需要捕捉
运行时异常与一般异常有何异同?
运行时异常:都是RuntimeException类及其子类异常,java JVM抛出的异常,我们可以不进行处理。如被0除、数组角标越界、空指针等。
一般异常:RuntimeException以外Exception,需要用户抛出或try catch处理的异常(如果不处理会编译不通过)。如IOException、SQLException、FileNotFoundException
异常处理
一种是抛出,一种是try cache处理
底层代码的话使用throw往上层抛,顶层代码直接try catch。
一般来说都是持久层和业务层try catch并且throws异常,控制层只是try catch,但不会抛出
throw和throws
throw是写在方法里面,throws是写在方法参数后面的
public void dothing(int a,int b) throws Exception1,Exception3 {
try{
//......
}catch(Exception1 e){
throw e;
}catch(Exception2 e){
System.out.println("自己打印提示,不抛出");
}
if(a!=b)
throw new Exception3("自定义异常");
}
打印堆栈信息
捕获异常后尅打印堆栈信息方便对文件进行定位,会打印出异常类型,异常位置等信息
e.printStackTrace();
try catch原理
异常表:
异常表是.class字节码文件中的类似表结构的一部分内容,当类编译为字节码后就可以看到class文件中含有异常处理的方法会有与其对应的Exception table
异常表内容:
有四个字段为try 开始位置、try 结束位置、声明捕获的异常种类
每个类
异常处理逻辑:
当程序出现异常时,java虚拟机就会查找方法对应的异常表,如果发现有声明的异常与抛出的异常类型匹配就会跳转到catch处执行相应的逻辑,如果没有匹配成功,就会回到父调用方法的异常表中继续查找,如此反复,一直到异常被处理为止,或者停止进程。
说明:
只有发生异常时才会去查询异常表
|
public static void forTry(); Code: 0: iconst_3 1: istore_0 2: iconst_0 3: istore_1 4: iload_1 5: sipush 1000 8: if_icmpge 31 11: iload_0 12: i2d 13: invokestatic #4 // Method java/lang/Math.sin:(D)D 16: pop2 17: goto 25 20: astore_2 21: aload_2 22: invokevirtual #6 // Method java/lang/Exception.printStackTrace:()V 25: iinc 1, 1 28: goto 4 31: return Exception table: from to target type 11 17 20 Class java/lang/Exception } |
捕获异常性能消耗
try块会阻止java的优化(例如重排序)。所以只要try块范围越小,对java的优化机制的影响是就越小。
如果发生异常会查询方法对应的异常表会消耗性能
回滚事务
有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。
默认被抛出的是运行时异常时,事务代码会自动回滚事务;也可以配置指定异常让事务回滚。
回滚只会回滚数据库的事务,缓存的需要手动处理
事务常用的注解@Transactional
说一下webservice
WebService就是基于Web的服务,它使用Web(HTTP)方式,接收和响应外部系统的某种请求。从而实现跨平台跨语言的远程调用。
我们可以调用天气预报服务放到我们的系统中。
学习WebService可以将你的服务(一段代码)发布到互联网上让别人去调用,也可以调用别人机器上发布的WebService,就像使用自己的代码一样.。
webservice的使用
利用cxf框架
Web Service框架, Java领域的
答:Java领域的Web Service框架很多,包括Axis2(Axis的升级版本)、Jersey(RESTful的Web Service框架)、CXF(XFire的延续版本)、Hessian、Turmeric、JBoss SOA等,其中绝大多数都是开源框架。
提示:面试被问到这类问题的时候一定选择自己用过的最熟悉的作答,如果之前没有了解过就应该在面试前花一些时间了解其中的两个,并比较其优缺点,这样才能在面试时给出一个漂亮的答案。
CXF简介
CXF是apache旗下的开源的web service框架
CXF可以与Spring进行快速无缝的整合
webservice与dubbo
在Java领域中有很多可实现远程通信的技术,例如:RMI、RPC、Web Service、http和JMS等
常用的:
Webservice:效率不高,基于soap协议。项目中不推荐使用。主要的特点是跨语言、跨平台的(以@WebService注解接口及实现类,以@WebMethod注解方法;指定发布路径及发布服务,通过jdk工具wsimport.exe来生成客户端调用代码,通过命令行进入到客户端工程的src目录,命令如下: wsimport -keep url 这里的url即服务器端发布的wsdl地址(发布路径?wsdl)执行完就有客户端代码了)
restful Web Services:它不再需要 wsdl ,也不再需要 soap 协议,而是通过最简单的 http 协议传输数据 ( 包括 xml 或 json) 。也减少了网络传输量(因为只传输代表数据的 xml 或 json ,没有额外的 xml 包装)。可以基于cxf开发 RESTful WebService。很多项目中应用。如果服务太多,服务之间调用关系混乱,需要治理服务。
使用dubbo。使用rpc协议进行远程调用,可以基于tcp传输协议进行通信。传输效率高,并且可以统计出系统之间的调用关系、调用次数。使用Java语言开发,只能用于Java语言开发的项目间的通信,不具备跨语言,跨平台的特点!
soap: 使用http协议传输xml文本的技术
rpc:(Remote Procedure Call Protocol)——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的技术思想。
webService慢效率不高的原因
基于soap协议,利用xml传输,把一个对象序列化成为一个Xml数据是一件很容易的事,但是反序列化成本似乎是很高。再加上 SOAP 协议本身是建立在 XML 形式上,这就使得 Web Service 奇慢无比了。当然因素还有很多
String
String是最基本的数据类型吗?
基本数据类型包括byte、short、int、long、char、float、double、boolean。
不是基本数据类型
String类型变量不可变原理
不可变得表现:String对象内容不可变,追加内容会重新定义新对象;
使用final修饰String类,使得String类不可被继承,方法不可被修改
数组对外不可见:String是使用char类型的数组实现,这个数组被定义成了private final类型的,这样就不能根据对象来直接调用数组成员变量修改数组。
没有提供对外修改数组元素的方法:这样这个char数组对外就是隐藏的而且是不可改变的
总结:final定义的类+private类型的成员变量char数组+没有对外提供修改数组元素的方法
StringBuffer类型变量可变原理
可变的表现:对象内容是可变的,追加内容不会重新定义新对象;
StringBuffer也是final类,不能被继承,方法不可被修改
StringBuffer也是使用char数组实现,这个数组被定义为private transient,外部无法直接通过StringBuffer对象对数组做出修改
StringBuffer提供了修改数组的方法如append方法,就是向字符串追加内容,原理是向数组中添加字符,当达到数组限制会进行扩容数组,把原数组内容放到新数组中,然后追加内容。
transient:汉译:短暂,语法含义:在类实例的序列化处理过程中会被忽略。就是StringBuffer的内容不能被序列化。
总结:对外提供了修改数组的方法
为什么要把String类设计成不可变类。
出于安全和效率的考虑
安全:
String类型经常作为参数在方法中传递,如果中途被修改就会带来安全隐患。
如:String a=”a”; A(a);B(b);
效率:
String不可变就可以放入字符串常量池中;(1.7及以后字符串常量池在堆中)
Sring的hashCode方法是根据数组中的值计算出来的,定义为不可变,hashCode就不会变,作为hash结构的key就不需要重复计算hash值,提高效率;
其他的final类如:Integer,Long,Float,Double,都是使用率比较高的类
是否可以继承String类?
String类是final类故不可以继承。
StringBuffer与StringBuilder的区别
因为StringBuilder是线程不安全的,运行效率高,如果一个字符串变量是在方法里面定义,这种情况只可能有一个线程访问它,不存在不安全的因素了,则用StringBuilder。
StringBuffer是线程安全的,在方法前都使用了synchronized进行修饰
如果要在类里面定义成员变量,并且这个类的实例对象会在多线程环境下使用,那么最好用StringBuffer。
StringBuffer和StringBuilder也是final类型的,内部都是使用char数组实现的
String 和StringBuffer的区别
String类提供了数值不可改变的字符串。(修改会创建新的对象,变量指向新的地址)
String实现了equals方法和hashCode方法
StringBuffer类提供的字符串进行可以修改。(修改不会创建新的对象)
StringBuffer没有实现equals方法和hashCode方法,不适合放入集合类
StringBuffer 适合动态的字符串应用场景
StringBuffer 是线程安全的
StringBuffer是如何实现的
String、StringBuffer、StringBuilder内部都是char数组实现
StringBuilder 和 StringBuffer能够动态改变字符串是对字符数组的扩容,如果数组需要扩容,就需要创建新的数组然后把原来的数据进行拷贝
默认容量是16,也可以指定容量长度,如果指定的字符串创建对象默认是字符串长度+16
等需要的容量超过现在的容量时进行扩容
扩容会增加为自身长度的一倍然后再加2;判断能不能放下,还是放不下,那就直接扩容到它需要的长度
数组有没有length()这个方法? String有没有length()这个方法?
数组没有length()这个方法,有length的属性。String有length()这个方法。
编码转换,实现将GB2312编码的字符串转换为ISO-8859-1编码的字符串。
如果原来是其他编码:String a=new String("中".getBytes("gb2312"),"iso-8859-1");
原来就是gb2312编码:String a=new String("中".getBytes("iso-8859-1"));
怎么反转字符串
第一种方法:利用字符串存储字符数据的原理,利用charAt(int i)方法反向逐个取出字符
第二种方法:生成StringBuffer对象,直接利用StringBuffer的reverse()方法。
String字符串常量和String对象的区别
String字符串常量和String字符串对象底层都是char型数组
字符串是Java中用的最多的一种数据类型,所以JVM专门开辟一个常量池空间针对String类型的数据作了特殊优化:即如果是字符串常量形式的声明首先会查看常量池中是否存在这个常量,如果存在就不会创建新的对象,否则在在常量池中创建该字符串并创建引用,此后不论以此种方式创建多少个相同的字符串都是指向这一个地址的引用,而不再开辟新的地址空间放入相同的数据;但是字符串对象每次new都会在堆区形成一个新的内存区域并填充相应的字符串,不论堆区是否已经存在该字符串!
常量池说明:
一种是class文件中的静态常量池,它只是java源码编译后形成的一类数据,不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间,是存在硬盘中的数据类型的命名方式;
另一种是运行时常量池,即上文中一直提到的常量池,它是内存中存储一类数据的内存区域命名方式,字符串常量池在堆内存中,其他的运行时常量池在方法区中
IO流
Io概述应用
IO流用来处理设备之间的数据传输
流的应用:
键盘输入数据
读取文件中的数据
向文件中写数据
通过网络上传下载数据
等
流的分类
按流向分为输入流和输出流
按操作对象分为字节流和字符流
字节流InputStream OutputStream
字符流Reader Writer
字节流与字符流的区别
字节流主要用于二进制数据,处理单元是一个字节(Byte,1Byte=8bit)。
字符流主要用于处理字符数据,处理单元是2个字节的Unicode字符。
字节流可用于任何类型的对象,而字符流只能处理字符文件;
字符流用到了缓存区,字节流没有用。
字节流和字符流处理相同的字符文件,字符流的效率高。
什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用。
对象数据如果要进行网络传输或存储就需要进行序列化
序列化就是将对象的状态信息转换为可以存储或传输的形式(字节序列)的过程
需要被序列化的类必须实现Serializable接口,该接口是一个mini接口,其中没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的。 序列化时需要用到对象的输出流ObjectOutputStream,调用writeObject完成序列化,反之利用对象输入流ObjectInputStream,调用readObject加载到内存,完成反序列化。
例如,在web开发中,如果对象被保存在了Session中,tomcat在重启时要把Session对象序列化到硬盘,这个对象就必须实现Serializable接口。如果对象要经过分布式系统进行网络传输或通过rmi等远程调用,这就需要在网络上传输对象,被传输的对象就必须实现Serializable接口。
序列化保存的是对象的状态,静态变量属于类的状态,序列化并不保存静态变量。class A 有静态变量i,和成员变量j,生成一个实例a设置i为1,j为1序列化后再反序列化成实例,i的值变为默认值,j为1.
为什么网络传输或者存储要序列化成字节序列形式
这是通用的规范约定,都按照规范容易管理
Unix的io模型分类
根据UNIX网络编程对I/O模型的分类,在UNIX可以归纳成5种I/O模型:
阻塞I/O
非阻塞I/O
多路复用I/O
信号驱动I/O
异步I/O
阻塞io:没有完成数据传输前进程不能处理其他事情,只能等着
非阻塞io:没有完成数据传输前进程可以做其他事,需要进程主动轮询检查情况数据到来时完成传输
多路复用io:没有完成数据传输前进程可以做其他事,到需要io(基于事件驱动)的时候会被通知来完成io
信号驱动io:没有完成数据传输前进程可以做其他事,到需要io(基于事件驱动)的时候会被通过信号回调通知来完成io
异步io:等操作完成后,进程再进行io
多路复用
多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
NIO
java的NIO使用的是多路复用I/O模型。
在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO
Nio在JDK1.4开始支持。全称New IO,java.nio.*包
nio是面向缓冲的,基于事件驱动的,非阻塞的io
一般只适用于连接数目较大但连接时间短的应用,如聊天应用等。
我们在网络中使用NIO往往是I/O模型的多路复用模型!
java非阻塞式IO
为所有的原始类型(boolean类型除外)提供缓存支持的数据容器
相比传统的bio它加入了Buffer(缓冲)、Channel(通道)、Selector(选择器)等概念,
数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中,选择器用于监听多个通道的事件(比如:连接打开,数据到达)
在NIO中,数据是放入buffer对象的,而在IO中,数据是直接写入或者读到流中
NIO使用单线程或者只使用少量的多线程,多个连接共用一个线程。
选择器:
负责监听多个通道的事件(比如:连接打开,数据到达)
通过单个线程轮询的方式实现了对多个Channel的监听。如果有数据达到会通知进行进行io处理
(
Buffer:
– 是一块连续的内存块。
– 是 NIO 数据读或写的中转地。
Channel:
– 数据的源头或者数据的目的地
– 用于向 buffer 提供数据或者读取 buffer 数据 ,buffer 对象的唯一接口。
– 异步 I/O 支持
Buffer作为IO流中数据的缓冲区,而Channel则作为socket的IO流与Buffer的传输通道。客户端socket与服务端socket之间的IO传输不直接把数据交给CPU使用,
而是先经过Channel通道把数据保存到Buffer,然后CPU直接从Buffer区读写数据,一次可以读写更多的内容。
使用Buffer提高IO效率的原因(这里与IO流里面的BufferedXXStream、BufferedReader、BufferedWriter提高性能的原理一样):IO的耗时主要花在数据传输的路上,普通的IO是一个字节一个字节地传输,
而采用了Buffer的话,通过Buffer封装的方法(比如一次读一行,则以行为单位传输而不是一个字节一次进行传输)就可以实现“一大块字节”的传输。比如:IO就是送快递,普通IO是一个快递跑一趟,采用了Buffer的IO就是一车跑一趟。很明显,buffer效率更高,花在传输路上的时间大大缩短。
)
nio的主要事件:
读就绪
写就绪
有新连接到来
NIO存在的问题
使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。
noi编程复杂,推荐使用成熟的nio框架,如Netty,MINA等
bio
JDK1.4以前的唯一选择
BIO就是指IO,即传统的Blocking IO,即同步并阻塞的IO。这也是jdk1.4之前的唯一选择,依赖于ServerSocket实现,即一个连接对应一个线程,如果线程数不够连接则会等待空余线程或者拒绝连接。所以用这种方式,在高并发情况下效率是很低的,也不可靠,一般只应用于连接数比较小且固定架构的应用,但api也比较容易使用。
BIO适用场景:
适用于连接数目比较小,并且一次发送大量数据的场景
io/bio与nio比较
IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
无 选择器
面向流与面向缓冲
标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
阻塞与非阻塞IO
传统bio是一个socket连接对应会建立一个线程,当多个socket申请连接线程不够用时,这个连接就会被阻塞或被拒绝
nio是当一个连接请求过来时会注册到选择器(selectors)上,选择器进行监听当连接发出io请求时才会启动一个线程进行处理,不断尝试获取系统的IO的使用权限,一旦成功(即:可以进行IO),则通知这个socket进行IO数据传输。
选择器(Selectors)
选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
nio的使用场景
如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据且连接比较短,例如聊天服务器,这时候用NIO处理数据可能是个很好的选择。
而如果只有少量的连接,而这些连接每次要发送大量的数据,这时候传统的IO更合适。使用哪种处理数据,需要在数据的响应等待时间和检查缓冲区数据的时间上作比较来权衡选择。
现在NIO已经大量应用在Jetty、ZooKeeper、Netty等开源框架中
AIO
JDK1.7升级了NIO类库新增了AIO
异步非阻塞IO,AIO是发出IO请求后,由操作系统自己去获取IO权限并进行IO操作之后再通知服务器应用去启动线程处理;
NIO则是发出IO请求后,由线程不断尝试获取IO权限,获取到后通知应用程序自己进行IO操作。适合连接数目多,这些连接每次只是发送少量的数据且连接比较长的场景,比如相册服务器
AIO是对nio的增强也在java.nio.*包中如:
AsynchronousFileChannel类:用于文件异步读写
AsynchronousSocketChannel类:客户端异步socket
AsynchronousServerSocketChannel类:服务器异步socket
NIO与AIO区别
NIO是同步非阻塞的,AIO是异步非阻塞的
NIO是多路复用器(选择器)轮询到连接有I/O请求时才通知服务器启动一个线程进行处理。
AIO是I/O请求由OS先完成了再通知服务器应用去启动线程进行处理
由于NIO的读写过程依然在应用线程里完成,所以对于那些读写过程时间长的,NIO就不太适合。而AIO的读写过程完成后才被通知,所以AIO能够胜任那些重量级,读写过程长的任务。
直接IO和缓存IO
缓存I/O又被称作标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,数据先从磁盘复制到内核空间的缓冲区,然后从内核空间缓冲区复制到应用程序的地址空间。即:通过高速缓存来进行io读写操作
缓存I/O的优点:
- 在一定程度上分离了内核空间和用户空间,保护系统本身的运行安全;
- 可以减少读盘的次数,从而提高性能。
缓存I/O的缺点:数据在传输过程中需要在应用程序地址空间和缓存之间进行多次数据拷贝操作,这些数据拷贝操作所带来的CPU以及内存开销是非常大的。
直接IO:
应用程序直接访问磁盘数据,而不经过内核缓冲区
直接IO的缺点:如果访问的数据不在应用程序缓存中,那么每次数据都会直接从磁盘加载
网络IO和磁盘IO
磁盘IO:
主要的延时是由(以15000rpm硬盘为例): 机械转动延时(机械磁盘的主要性能瓶颈,平均为2ms) + 寻址延时(2~3ms) + 块传输延时(一般4k每块,40m/s的传输速度,延时一般为0.1ms) 决定。(平均为5ms)
网络IO主要延时由: 服务器响应延时 + 带宽限制 + 网络延时 + 跳转路由延时 + 本地接收延时 决定。(一般为几十到几千毫秒,受环境干扰极大)
所以两者一般来说网络IO延时要大于磁盘IO的延时。
定时任务
定时任务是什么:
按指定时间规则进行执行程序
定时任务都有那些规则:
一种是定点执行,一种是每隔多长时间执行一次
定时任务的应用场景
- 缓存的定时更新
- 排行榜的数据定时计算存储
- 统计数据的定时计算存储
- 数据的定时同步
java中使用定时任务的方式
- 可以创建一个线程,通过sleep方法实现定时
使用Timer和TimerTask,优点是方便快速,缺点是每一个任务都需要占用一个线程资源,而且任务抛异常出去后,定时任务下次就不会在执行了
|
import java.util.Timer; import java.util.TimerTask; public class Task2 { public static void main(String[] args) { TimerTask task = new TimerTask() { @Override public void run() { // task to run goes here System.out.println("Hello !!!"); } }; Timer timer = new Timer(); long delay = 0; long intevalPeriod = 1 * 1000; // schedules the task to be run in an interval timer.scheduleAtFixedRate(task, delay, intevalPeriod); } // end of main }
|
3、ScheduledExecutorService,这是Java5以后提供的一个类,可以很方便的实现定时调度。
相比前面的优点:使用了线程池,灵活设置第一次延时时间,提供了良好的约定,以便设定执行的时间间隔
ScheduledExecutorService service = Executors.newScheduledThreadPool(5); // 创建调度服务,线程池数量为5
service .scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); // 开启调度,command是所要执行的任务,initialDelay是初始化延时时间,period是调度周期,unit是时间单位
- Quartz
完全由Java编写的开源作业调度框架,可以实现复杂的定时任务,需要引入quartz的jar包,可以通过xml配置要掉用的类的方法并定义时间规则
5、利用Spring提供的定时器spring task
定义好方法使用xml配置或者使用注解都可以实现定时功能,一般使用xml方便管理(在task标签里面定义)
这种方式很方便,而且也是基于线程池的方式,数量可以通过xml配置,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多
6、使用其他开源的定时任务框架,如xxl-job提供分布式调度中心进行定时任务执行
定时任务的原理
定时器的实现依赖的是CPU时钟中断,时钟中断的精度就决定定时器精度的极限
用软件来实现动态定时器常用数据结构有:时间轮、最小堆和红黑树。
cpu时钟中断:
时钟中断程序时这样的:在CPU执行指令时,计数器(定时器)同时在计时,当计时器溢出时,就向CPU申请中断,如果允许响应中断,CPU就转到中断服务程序执行相关的程序。
利用时钟中断可以协助主程序完成定时、延时等操作
计数器/定时器由寄存器组成,每来一个脉冲会加1
计数器/定时器是16位,最大计数值是65536,超过最大值会溢出
脉冲通常是指电子技术中经常运用的一种像脉搏似的短暂起伏的电冲击
时间轮算法:
一个环形队列,每个元素一个槽位,每个槽位关联一个任务列表,整个系统只需要一个定时器,每隔一定时间跳一格。
最小堆原理:维护一个最小堆,堆的元素是时间和任务,定时器隔一定时间间隔从堆中取任务,对比时间戳不超时为止,每取出任务会有个调整堆的过程,所以时间复杂度是lgn 。
最小堆,是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于其左子节点和右子节点的值。
Nginx使用红黑树实现定时器,当等待的事件不能在指定的时间内到达,则会触发 Nginx 的超时机制
序列化
序列化概述
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,序列化是把对象转为字节序列或xml格式序列或json格式序列等,反序列化是把字节序列或xml格式序列或json格式序列转为对象。
为什么要序列化
数据的存储或者服务器之间数据传输都会选择固定的协议来规范数据格式,java中的对象是指在当前的jvm是有效的,并没有普遍的适用性,所以要用通用的数据格式来进行规范,就是要进行序列化(通用,方便,高效)
序列化方式
java中序列化的方式:
原生序列化方式:(jdk自带的序列化方式)
方法一:实现Serializable接口(隐式序列化),会自动序列化所有非static和 transient关键字修饰的成员变量。static成员不属于对象实例,可能被别的对象修改没办法序列化,序列化是序列对象。
方法二:实现Externalizable接口(显式序列化)。Externalizable接口继承自Serializable, 我们在实现该接口时,必须实现writeExternal()(序列化)和readExternal()(反序列化)方法,而且只能通过手动进行序列化,可以自己选择序列化那些部分
如:
|
@Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub System.out.println("Blip.writeExternal"); out.writeObject(s); out.writeInt(i); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub System.out.println("Blip.readExternal"); s = (String)in.readObject(); i = in.readInt(); } |
原生的序列化后如:

字节
Json序列化
Json序列化一般会使用jackson或阿里的fastjson,将对象序列化为json字符串,
ProtoBuf序列化
protocol buffers 是google内部得一种传输协议,目前项目已经开源,它以二进制的方式存储,无法直接读取编辑
Java Serialization(主要是采用JDK自带的Java序列化实现,性能很不理想)
Json(目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库)
FastJson(阿里的fastjson库)
Hession(它基于HTTP协议传输,使用Hessian二进制序列化,对于数据包比较大的情况比较友好。)
Dubbo Serialization(阿里dubbo序列化)
FST(高性能、序列化速度大概是JDK的4-10倍,大小是JDK大小的1/3左右)
Serialization序列化的实现:
将需要被序列化的类实现Serializable接口
该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的
redis的序列化策略:
jdk序列化方式:被序列化的对象必须实现Serializable接口,特点存储空间大,不易读
jackson序列化和FastJson序列化方式:被序列化的对象不用实现Serializable接口,特点存储空间小,易读,速度快,推荐使用
string序列化方式:一般如果key-value都是string的话,使用String序列化方式就可以
序列化接口的id有什么用?
序列化id是一个long型的数值。private static final long serialVersionUID = 1L;
反序列化时会比较序列化id是否一致,一致才能够反序列化,否则会反序列化失败
序列化id一般用两种值:
一种是默认的IL
一种是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段(编辑器可以来生成)
第一种:希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的ID
第二种一般适用于:这个类当类做了修改,就重新生成一个版本号,不想向下兼容,不希望类的不同版本对序列化兼容
继承关系序列化
- 如果子类实现Serializable接口而父类未实现时,父类不会被序列化!
- 如果父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。
针对第一种情况会有这样的问题
子类实现了Serializable即可,父类没有,最后面,子类的数据转为字节类型数据,并存储到数据库,再从数据库出来转为子类对象,发现,父类的变量,值为空!
引用关系的序列化
当引用类也实现了序列化接口时,目标类序列化,引用类也序列化在目标类的序列化文件中。
当引用类没有实现序列化接口时,目标类序列化,JVM会抛出java.io.NotSerializableException
Transient关键字
使用Transient关键字进行修饰的变量不会被序列化
transient关键字只能修饰变量,而不能修饰方法和类。
如:
StringBuffer中的char数组就是使用Transient来修饰的(StringBuilder没有被它修饰)
StringBuffer为什么使用它来修饰:
没有详细了解过
Jdbc
jdbc介绍
Jdbc是定义了访问数据库的标准java类库,通过jdbc可以使用java来操作各种数据库
Jdbc的组成
一部分是供程序员调用的API
另一部分是需要数据库厂商实现的SPI(Service Provider Interface,数据库厂商需要实现的接口),也就是驱动程序。
这样程序员只要用jdbc通过数据库厂商实现的接口来操作各种数据库了。
jdbc编程步骤:
- 加载数据库驱动
- 创建并获取数据库链接
- 创建jdbc statement对象
- 设置sql语句
- 设置sql语句中的参数(使用preparedStatement)
- 通过statement执行sql并获取结果
- 对sql执行结果进行解析处理
- 释放资源(resultSet、preparedstatement、connection)
Class.forName(“com.mysql.jdbc.Driver”);
String url = "jdbc:mysql://localhost:3306/test";
String USER = "username";
String PASS = "password";
Connection conn = DriverManager.getConnection(url, USER, PASS);
Statement st = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = st.executeQuery(sql);
while(rs.next())
{
//Retrieve by column name
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");
//Display values
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", First: " + first);
System.out.println(", Last: " + last);
}
rs.close();
st.close();
conn.close();
为什么要关闭:
如果不关闭,jvm认为这些对象还在使用,不会进行垃圾回收。造成内存泄漏
为什么数据库连接很消耗性能
数据库连接消耗分为这几个方面:
资源开销:建立和管理数据库连接都需要消耗系统的内存和cpu
网络开销:java程序与数据库通信要建立tcp连接,还要将数据序列化、传输、反序列化,连接断开也要经历4次挥手等都会导致网络开销;
jdbc编程的优缺点
优点:
直接底层操作,
提供了很简单、便捷的访问数据库的方法,
跨平台性比较强。
灵活性比较强,可以写很复杂的SQL语句。
缺点:
操作比较繁琐,很多代码需要重复写很多次。
如果遇到批量操作,频繁与数据库进行交互,容易造成效率的下降。
数据库连接池
概述
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用数据库连接
数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。利用数据库连接池可以提高操作数据库效率
主要参数说明
最小连接数minIdle:
是连接池一直保持的数据库连接,超过minIdle的连接使用完后不需要放入池中,连接释放时直接销毁
最大连接数maxActive:
是连接池能申请的最大连接数,如果数据库连接请求超过最大连接数,后面的数据库连接请求将被加入到等待队列中
一般把maxActive设置成可能的并发量就行了
最大等待时间maxWait:当数据库连接数达到maxActive,其余的连接申请会等待,当等待时间超过maxWait,但仍然没有连接被释放时,则这些等待中的连接会被拒绝。
最大空闲时间removeAbandonedTimeout : 连接的最大空闲时间,超过此时间的连接会被释放到连接池中
autoReconnect:
当数据库连接异常中断时,是否自动重新连接
maxReconnects:
autoReconnect设置为true时,重试连接的次数
connectTimeout 和数据库服务器建立socket连接时的超时
socketTimeout
socket操作(读写)超时
数据库连接池产品
第一代数据库连接池:
c3p0:稳定性好,性能差,很久没有更新,已死
dbcp:属于Apache,后来有了dbcp2但没有什么优势,实际项目很少使用
第二代数据库连接池:
HikariCP:性能强,稳定性好,代码量少,SpringBoot2.0采用HikariCP作为默认连接池配置.,hikariCP号称java平台最快的数据库连接池。
Druid:阿里的开源项目,提供性能卓越的连接池功能外,还集成了sql监控(sql执行耗时,错误堆栈),黑名单拦截等功能,中文文档比较全面。
经验考查
说出一些常用的类,包,接口,请各举5个
要让人家感觉你对java ee开发很熟,所以,不能仅仅只列core java中的那些东西,要多列你在做ssh项目中涉及的那些东西。就写你最近写的那些程序中涉及的那些类。
常用的类:BufferedReader BufferedWriter FileReader FileWirter String Integer Class System ArrayList HashSet ThreeSet HashMap
常用的包:java.lang java.awt java.io java.util java.sql javax.sevlet java.org. java.net javax.faces javax.xml
常用的接口: List Set Map Comparable Comparator Serializable
HttpServletRequest HttpServletResponse HttpSession
给我一个你最常见到的runtime exception
所谓系统异常(运行时异常),就是…..,它们都是RuntimeException的子类
ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException, EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException, ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFormatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException
一般异常: IOException FileNotFoundException SqlException
final, finally, finalize的区别?
- final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
- finally是异常处理语句结构的一部分,表示总是执行。
- finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。
JVM加载class文件的原理机制?
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它是负责在运行时查找和装入类文件的类。
ClassLoader如何加载class 。
jvm里有多个类加载,每个类加载可以负责加载特定位置的类,例如,bootstrap类加载负责加载jre/lib/rt.jar中的类, 我们平时用的jdk中的类都位于rt.jar中。extclassloader负责加载jar/lib/ext/*.jar中的类,appclassloader负责classpath指定的目录或jar中的类。除了bootstrap之外,其他的类加载器本身也都是java类,它们的父类是ClassLoader。
分层设计的好处;
把各个功能按调用流程进行了模块化,模块化带来的好处就是可以随意组合
举例说明:如果要注册一个用户,流程为显示界面并通过界面接收用户的输入,接着进行业务逻辑处理,在处理业务逻辑又访问数据库,如果我们将这些步骤全部按流水帐的方式放在一个方法中编写,这也是可以的,但这其中的坏处就是,当界面要修改时,由于代码全在一个方法内,可能会碰坏业务逻辑和数据库访问的码,同样,当修改业务逻辑或数据库访问的代码时,也会碰坏其他部分的代码。分层就是要把界面部分、业务逻辑部分、数据库访问部分的代码放在各自独立的方法或类中编写,这样就不会出现牵一发而动全身的问题了。这样分层后,还可以方便切换各层,譬如原来的界面是Swing,现在要改成BS界面,如果最初是按分层设计的,这时候不需要涉及业务和数据访问的代码,只需编写一条web界面就可以了。
下面的仅供参考,不建议照搬照套,一定要改成自己的语言,发现内心的感受:
分层的好处:
1,实现了软件之间的解耦;
2.便于进行分工
3.便于维护
4.提高软件组件的重用
5.便于替换某种产品,比如持久层用的是hibernate,需要更换产品用toplink,就不用该其他业务代码,直接把配置一改。
6.便于产品功能的扩展。
7。便于适用用户需求的不断变化
能不能自己写个类,也叫java.lang.String?
可以,但在应用的时候,需要用自己的类加载器去加载(实现ClassLoader类,并重写findClass(String name)方法(指定从什么位置加装)和重写loadClass方法(指定加载的逻辑)),否则,系统的类加载器永远只是去加载jre.jar包中的那个java.lang.String。
由于在tomcat的web应用程序中,都是由webapp自己的类加载器先自己加载WEB-INF/classess目录中的类,然后才委托上级的类加载器加载,如果我们在tomcat的web应用程序中写一个java.lang.String,这时候Servlet程序加载的就是我们自己写的java.lang.String,但是这么干就会出很多潜在的问题,原来所有用了java.lang.String类的都将出现问题。
虽然java提供了endorsed技术,可以覆盖jdk中的某些类,具体做法是….。但是,能够被覆盖的类是有限制范围,反正不包括java.lang这样的包中的类。
(下面的例如主要是便于大家学习理解只用,不要作为答案的一部分,否则,人家怀疑是题目泄露了)例如,运行下面的程序:
public class String {
public static void main(String[] args){
System.out.println("xx");
}
}
报告的错误如下:

这是因为加载了jre自带的java.lang.String,而该类中没有main方法。
有什么代码优化经验
不要导入不使用的类
循环内不要不断创建对象引用
对不用的对象及时附null值
对重复使用的代码做抽离
当要字符串连接时应用StringBuilder或StringBuffer 代替,避免使用String创建多个对象
去掉只使用一次的临时变量
尽量减少对变量的重复计算,如for (int i = 0; i < list.size(); i++),建议替换为:
for (int i = 0, int length = list.size(); i < length; i++)
慎用异常,异常处理消耗性能,异常只能用于错误处理,不应该用来控制程序流程。
Ctrl + Shift + O – 管理import语句并移除未使用的语句
避免创建新的Boolean,Integer,String等实例。使用Boolean.valueOf(true)代替new Boolean(true)。两种写法效果差不多但却可以改善性能。
当使用流时及时关闭
如何无侵入式的计算方法执行时间
我们公司是使用Cat进行做监控的
使用AOP,编写一个切面类来做出计算,可以计算方法级别的执行时间
过滤器和拦截器可以针对url请求到结束整个的执行时间做统计
super关键字
1、子类构造函数调用父类构造函数用super
2、子类重写父类方法后,若想调用父类中被重写的方法,用super
3、未被重写的方法可以直接调用。
json
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个子集。
为什么要进行json数据交互
json数据格式在接口调用中、html页面中较常用,json格式比较简单,解析还比较方便。
Java web部分
web应用的规范
Web应用程序的目录结构和文件存放方式是有一定规定的,只有符合这些规定的应用程序才能正常运行在Web容器中
WEB-INF目录:应用的根目录下要有该文件夹,该文件夹下有:classes文件夹(存放应用程序编译后的字节码文件,一般我们把配置的properties文件和xml文件也放在这个目录),lib文件夹(存放依赖的jar包),web.xml(应用的初始化信息配置文件)
WEB-INF是Java的WEB应用的安全目录。所谓安全就是客户端无法访问,只有服务端可以访问的目录。
如果想在页面中直接访问其中的文件,必须通过在web.xml文件对要访问的文件进行相应映射才能访问。
web应用程序的启动过程
初始化web容器(如tomcat容器)--web容器去找web.xml文件来初始化应用--接收请求提供服务
详细过程:
首先我们是把我们的项目放入到tomcat的webapps目录下,或者其他目录之后在tomcat的server.xml中的Context指定项目路径
我们使用tomcat的启动脚本startup.sh进行启动tomcat,启动脚本会调用tomcat的Bootstrap类的main方法,该方法会依次调用Bootstrap的init,load,start方法,init方法设置环境变量和类加载器,load方法会解读tomcat的server.xml,并实例化组件(实例化Server、Service、Connector、Engine、Host等组件)之后初始化部分组件(Server、Service、Connector、Engine等),之后调用start方法,该方法会依次启动我们的Server、Service、Engine、Connector组件,Engine组件启动过程中会启动Context组件,Context组件过 web.xml 或者 扫描 class 字节码读取 servlet3.0 的注解配置,从而加载 webapp 定义的 Listener、Servlet、Filter 等 servlet 组件,但是并不会立即实例化对象。全部加载完毕之后,依次对 Listener、Filter、Servlet 进行实例化、并且调用其初始化方法完成应用启动
web.xml说明
web.xml的位置
Tomcat的conf目录下的context.xml指明了要加载的位置:默认位置是WEB-INF/web.xml
web.xml的内容
web.xml文件是用来初始化配置信息:比如Welcome页面、servlet、servlet-mapping、filter、listener、启动加载级别等。
是否必须
一个web中可以没有web.xml文件,当一个web项目不需要上面的内容时就可以不配置
重要的标签说明:
<context-param>
声明应用范围内(整个WEB项目)的初始化参数
如:定义参数contextConfigLocation来配置spring,contextConfigLocation参数定义了要装入的 Spring 配置文件。这个参数是为了给ContextLoaderListener使用
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/spring/applicationContext.xml
</param-value>
</context-param>
定义了contextConfigLocation参数要在web.xml中配置
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
该listener默认加载/WEB-INF/applicationContext.xml作为配置文件,指定了上面的参数就会以指定为准,该listener能够加载指定配置初始化spring容器
<load-on-startup>
如果有两个Servlet元素都含有<load-on-startup>子元素,则<load-on-startup>子元素值较小的Servlet将先被加载。如果<load-on-startup>子元素值为空或负值,负数或者没有设置,则容器会当Servlet被请求时再加载。正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet。如果两个Servlet的<load-on-startup>子元素值相同,则由Web容器决定先加载哪一个Servlet。
web.xml中内容的加载过程:
启动Web项目时,容器(比如Tomcat)会加载web.xml内容过程是context-param >> listener >> fileter >> servlet
由这个顺序可以看出listener加载Spring容器比servlet加载springmvc要早
tomcat的conf目录下的web.xml
可以理解为Tomcat的web.xml文件是每一个web应用的web.xml的父文件,Tomcat将每个应用的公共配置提取出来放在conf目录下的web.xml中
表单中 get与post提交方法的区别?
答:⑴get方法通过URL传递参数,用户在客户端地址栏可见,如果是传递密码的话,不安全;post方法通过请求的body传递参数,用户在客户端不可见,更安全。
- 默认提交方法是get方式,get方式url传递的数据大小跟浏览器有关,
- 超链接一般用get(默认),提交表单一般设置为post,
- 提交表单如果不改变服务器状态(如只查询数据)就用get,改变用post(如增加数据)。
session与cookie的区别
1、session中的内容保存在服务器端,cookie中的内容保存在浏览器端。
2、session比cookie安全性更高
3、购物车功能,显示上次访问商品记录功能,可以用cookie实现,一次性验证码可以用session实现,可以利用session完成保持用户登录状态
servlet生命周期
Servlet的生命周期分为初始化阶段、服务阶段、销毁阶段这三个阶段。
这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。
客户端第一次访问Servlet或服务器启动时(需要在web.xml中进行配置)服务器创建该Servlet的实例,然后调用init()方法完成Servlet的初始化操作
当服务器接收到请求时会调用service()方法
当服务器正常关闭(stop server,重新部署会正常关闭一次再打开)或者当前web应用被移出服务器时,Servlet会被销毁,在销毁之前,服务器会调用destroy()方法
servlet简介;
Servlet本质上是实现了Servlet接口的Java类,主要用于获取客户端请求,处理请求和响应请求。
Servlet是mvc的基础,市场上任何一个mvc的框架都是servlet发展过来的.如主流的struts和springMVC
servlet配置说明
<servlet>
<servlet-name>spring-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
编写完servlet的java类,需要在web.xml中进行配置,指定访问servlet的路径
<load-on-startup>说明:
<load-on-startup>元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。
它的值必须是一个整数,表示servlet被加载的先后顺序。
如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
省却的servlet
如果映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
web.xml文件中找不到匹配的url就会访问到省却的servlet
DispatcherServlet:
它是SpringMvc的前端控制器,主要用于流程控制(是前端控制器设计模式的实现)
比如:
拦截匹配的请求
调用处理器映射器查找Handler
调用处理器适配器执行Handler
调用视图解析器对执行结果进行解析等
DispatcherServlet初始化主要做了如下两件事情:
1、初始化SpringMVC使用的Web上下文,并指定Spring容器为父容器;
2、初始化DispatcherServlet使用的策略,如HandlerMapping、HandlerAdapter等。
Servlet的基本架构
public class ServletName extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {}
}
SERVLET API中forward() 与redirect()的区别?
{request.getRequestDispatcher(“转发资源路径”).forward(request,response)}
答:前者是请求转发,后者是重定向,
(1)请求转发涉及到一个请求,一次重定向涉及到两次请求
(2)请求转发多少次,浏览器地址栏只显示第一次请求的地址;重定向会使地址都是最新的请求地址。
(3)请求转发只能转发给本项目的其他资源,而重定向不仅可以重定向到本项目的其他资源,还可以重定向到其他项目;
(4)请求转发是服务器端的行为,转发时只需要给出转发的资源路径即可,如Servlet的访问路径;而重定向需要给出全路径,即路径要包含项目名;
(5)请求转发比重定向的效率高,因为请求转发是一个请求。
(6)请求转发是由request发起的。 request.getRequestDispatcher("").forward();
重定向 response.sendRedirect();
(7)请求转发时,它的路径 "/" 代表的是当前工程即 服务器端路径
重定向 它的路径 "/" 代表的是服务器根目录即 客户端路径.
request.getRequestDispatcher("/").forward(); 这时的/相当于当前虚拟目录名称 http://localhost:8080/day8_2
response.sendRedirect("/");它相当于是 http://localhost:8080
什么情况下调用doGet()和doPost()?
Jsp页面中的FORM标签里的method属性为get时调用doGet(),为post时调用doPost()。
Servlet中如何获取用户提交的查询参数或表单数据?
答:可以通过请求对象(HttpServletRequest)的getParameter()方法通过参数名获得参数值。如果有包含多个值的参数(例如复选框),可以通过请求对象的getParameterValues()方法获得。当然也可以通过请求对象的getParameterMap()获得一个参数名和参数值的映射(Map)。
Servlet中如何获取用户配置的初始化参数以及服务器上下文参数?
答:可以通过重写Servlet接口的init(ServletConfig)方法并通过ServletConfig对象的getInitParameter()方法来获取Servlet的初始化参数。可以通过ServletConfig对象的getServletContext()方法获取ServletContext对象,并通过该对象的getInitParameter()方法来获取服务器上下文参数。当然,ServletContext对象也在处理用户请求的方法(如doGet()方法)中通过请求对象的getServletContext()方法来获得。
怎么设置请求编码和响应编码
通过请求对象(ServletRequest)的setCharacterEncoding(String)方法可以设置请求的编码,其实要彻底解决乱码问题就应该让页面、服务器、请求和响应、Java程序都使用统一的编码,最好的选择当然是UTF-8;通过响应对象(ServletResponse)的setContentType(String)方法可以设置响应内容的类型,当然也可以通过HttpServletResponsed对象的setHeader(String, String)方法来设置。
request对象的主要方法:
(1)常用信息
- String getRemoteAddr():获取客户端ip地址;
- getLocalAddress();获取服务器ip地址.
- String getMethod():获取客户端请求方式,例如:post或get
- getQueryString();获取get请求时的参数。
- String getProtocol();获取协议;
(2)获取请求头信息
- String getHeader(String name):获取单值的请求头的值;
- int getIntHeader(String name):获取单值int类型的请求头的值;
- getDateHeader(String name):获取单值long类型的请求头的值;
- Enumeration<String> getHeaders(String name):获取多值请求头的值,返回值是Enumeration类型。
- getHeader(“referer”)它可以防止盗链。
- getHeader(“user-agent”)可以获取浏览器信息.
(3)获取请求资源路径:
- getRequestURI(); 它返回的是一个请求页面URI,如:/day01/index
- getRequestURL(); 返回一个请求页面URL,如:http://localhost:8080/day01/index
(URI包含URL,URL代表唯一的资源路径,URI统一资源标识符,可能资源路径不唯一。)
- getContextPath(); 获取虚拟目录名称.(就是工程名)如/day01
域功能:
void setAttribute(String name,Object value)
Object getAttribute(String name)
void removeAttribute(String name)
void setCharacterEncoding(String encoding),设置请求体字符编码
String getParameter(String name):获得指定参数的值
String getMethod():获取请求方式,例如GET或POST;
getHeader(String name):获得HTTP协议定义的文件头信息
request.getAttribute() 和 request.getParameter() 有何区别?
前者是获得request域中指定内容的值,
后者是根据请求参数名获得从客户端传过来对应的值。
一个是从服务器端添加进去的数据,一个是从客户端添加进去的数据。
如何在基于Java的Web项目中实现文件上传和下载?
答:在Sevlet 3.0 以前,Servlet API中没有支持上传功能的API,因此要实现上传功能需要引入第三方工具从POST请求中获得上传的附件或者通过自行处理输入流来获得上传的文件,我们推荐使用Apache的commons-fileupload。
从Servlet 3.0开始(需要Tomcat7.0~以上),可以使用servlet包中的Part类来完成上传,需要手动编写方法获得文件名,
Part part = request.getPart("file");获得指定name属性input中的附件
Part类的write(“path”)方法完成上传
Servlet3.1可以利用Part类的getSubmittedFileName()方法获得文件名
文件上传必须满足的条件:
a、页面表单的method必须是post
b、页面表单的enctype必须是multipart/form-data类型的
c、表单中提供<input type=”file”/>上传输入域
multipart,多部件的。这里指可以提交多种类型数据,也就是二进制数据传输。
post方式可以提交的数据量大,所以用post。
注意:不适合上传特别大的文件
上传页面index.jsp:
<%@ page pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Photo Upload</title>
</head>
<body>
<h1>Select your photo and upload</h1>
<hr/>
<div style="color:red;font-size:14px;">${hint}</div>
<form action="UploadServlet" method="post" enctype="multipart/form-data">
Photo file:
<input type="file" name="photo" />
<input type="submit" value="Upload" />
</form>
</body>
</html>
支持上传的Servlet:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1 L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 可以用request.getPart()方法获得名为photo的上传附件
// 也可以用request.getParts()获得所有上传附件(多文件上传)
// 然后通过循环分别处理每一个上传的文件
Part part = request.getPart("photo");
if(part != null && part.getSubmittedFileName().length() > 0) {
// 用ServletContext对象的getRealPath()方法获得上传文件夹的绝对路径
String savePath = request.getServletContext().getRealPath("/upload");
// Servlet 3.1规范中可以用Part对象的getSubmittedFileName()方法获得上传的文件名
// 更好的做法是为上传的文件进行重命名(避免同名文件的相互覆盖)
part.write(savePath + "/" + part.getSubmittedFileName());
request.setAttribute("hint", "Upload Successfully!");
} else {
request.setAttribute("hint", "Upload failed!");
}
// 跳转回到上传页面
request.getRequestDispatcher("index.jsp").forward(request, response);
}
}
我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串?
Public String translate(String str) {
String tempStr = "";
try {
tempStr = new String(str.getBytes("ISO-8859-1"), "GBK");
tempStr = tempStr.trim();
} catch (Exception e) {
System.err.println(e.getMessage());
}
return tempStr;
}
MVC的各个部分都有那些技术来实现?如何实现?
答:MVC是Model-View-Controller的简写。也就是模型、视图、控制器,
视图向用户显示相关的数据,并能接受用户的输入数据,但是它不进行任何实际的业务处理. (jsp)
模型是应用程序的主体部分.模型表示业务数据和业务逻辑.一个模型能为多个视图提供数据.由于同一个模型可以被多个视图重用,所以提高了应用的重用性. (javaBean)
控制器接受用户的输入并调用模型和视图去完成任务.(servlet)
通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。
jsp部分
JSP=HTML+Java脚本+JSP标签
修改jsp页面为什么不需要重启服务器
JSP最终要转换为class文件,为什么tomcat在修改java代码的必须先重启tomcat,反而JSP不用?
这是因为Tomcat对JSP进行了侦听,如果有修改,就会重新翻译成Servlet并最终编译成Class文件,替换掉原JSP页面对应的Class文件。Tomcat的内部机制是可以让这种Class文件立即生效的。而普通的Class文件修改后,不能立即生效。
Jsp是加载时当场编译的,而不是预先编译好。Java代码是预先编译为class文件
jsp有哪些内置对象?作用分别是什么? 分别有什么方法?
1、request对象客户端请求,此请求会包含来自GET/POST请求的参数通过它才能了解到客户的需求,然后做出响应。
2、response对象响应客户请求的有关信息
3、session对象它指的是客户端与服务器的一次会话,从客户端连到服务器的一个 WebApplication开始,直到客户端与服务 器断开连接为止。
4、out对象 它是JspWriter类的实例,是向客户端输出内容常用的对象
5、page对象 它是指向当前JSP页面本身,有点象类中的this指针,它是java.lang.Object类的实例
6、application对象 它实现了用户间数据的共享,可存放全局变量。它开始于服务器的启动,直到服务器的关闭
7、exception对象它是一个异常对象,当一个页面在运行过程中发生了异常,就产生这个对象。
8、pageContext对象它提供了对JSP页面内所有的对象及名字空间的访问
9、config对象它是在一个Servlet初始化时,JSP引擎向它传递信息用的
jsp有哪些动作?作用分别是什么?
(这个问题似乎不重要,不明白为何有此题)
答:JSP共有以下6种基本动作
jsp:forward:把请求转到一个新的页面。
jsp:include:在页面被请求的时候引入一个文件。
jsp:useBean:寻找或者实例化一个JavaBean。
jsp:setProperty:设置JavaBean的属性。
jsp:getProperty:输出某个JavaBean的属性。
jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记
JSP的常用指令
page指令:可以进行导包,设置编码,设置错误页面等
include指令:表示静态包含
taglib指令:用来导入标签库
JSP中动态INCLUDE与静态INCLUDE的区别?
答:动态INCLUDE用jsp:include动作实现
<jsp:include page=included.jsp flush=true />它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数
静态INCLUDE用include指令实现,它不会检查所含文件的变化,适用于包含静态页面 <%@ include file=included.htm %>
静态包含是运行Java文件之前将JSP文件合并,然后生成一个Java文件,最后生成一个.class文件
动态包含当前jsp和被包含的jsp都会生成各自的Servlet,然后在执行当前jsp的Servlet时完成包含另一个Servlet,因此会存在两个Java文件和两个.class文件。
页面间对象传递的方法
request,session,application,cookie等
JSP和Servlet有哪些相同点和不同点,他们之间的联系是什么?
JSP是Servlet技术的扩展,本质上是Servlet的简易方式。JSP编译后是"类servlet"。
Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是JSP=HTML+Java脚本+JSP标签。
JSP侧重于界面,Servlet主要用于控制逻辑。
JSTL简介
JSP Standard Tag Library是jsp标准标签库,是Apache对EL表达式的扩展,也就是说JSTL依赖EL表达式。
常用的是
lcore:核心标签库,这是我们学习的重点;JSP规范为核心标签库建议的前缀名为c。
lfmt:格式化标签库;
使用:
先导入标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
然后就可以使用c标签了
<c:out value="${code }" escapeXml="false"></c:out>
格式化日期
<fmt:formatDate value="${date }" pattern="yyyy-MM-dd HH:mm:ss"/>
格式化数字
<fmt:formatNumber value="1.23" pattern="0.0"> </fmt:formatNumber>
EL表达式
主要用于替换jsp页面中的脚本表达式,以从各种类型的web域中检索java对象、获取数据。
表达式语言(EL)支持哪些运算符?
答:除了.和[]运算符,EL还提供了:
- 算术运算符:+、-、*、/或div、%或mod
- 关系运算符:==或eq、!=或ne、>或gt、>=或ge、<或lt、<=或le
- 逻辑运算符:&&或and、||或or、!或not
- 条件运算符:${statement? A : B}(跟Java的条件运算符类似)
- empty运算符:检查一个值是否为null或者空(数组长度为0或集合中没有元素也返回true)
Jdk8的主要新特性
optional
Optional类是Java8为了解决null值判断问题,可以避免显式的null值判断
Optional<T>是一个容器类,代表一个值存在或不存在,原来用null表示不存在,现在用Optional可以更好地表达且可避免空指针异常。
原先:
public static String getGender(Student student) {
if(null == student) {
return "Unkown";
}
return student.getGender();
}
现在:
public static String getGender(Student student) {
return Optional.ofNullable(student).map(u - > u.getGender()).orElse("Unkown");
}
Optional类结合lambda表达式的使用能够让开发出的代码更简洁和优雅。
说明:
Optional.ofNullable参数可以为null,为null时map返回空的Optional对象,执行orElse操作,orElse判断Optional包装的值为null的就把它的参数返回;
Optional.ofNullable参数不为null时,map执行内部函数返回有值的Optional对象,执行orElse操作,orElse判断Optional的值不为null,就把该值返回
map:
如果调用方有值,则对其执行调用mapping函数得到Optional类型的返回值。没有值就返回空的Optional,空的Optional的value为null
flatMap:
如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional。
ifPresent方法:
如果Optional实例有值则为其调用consumer,否则不做处理
name.ifPresent((value) - > {
System.out.println("The length of the value is: " + value.length());
});
orElse方法:
如果有值则将其返回,否则返回指定的其它值。
System.out.println(empty.orElse("There is no value present!"));
default关键字
接口中可以定义default修饰符修饰的方法,这个方法可以有实现(以前的接口只能定义抽象方法)
目的:可以精简代码
public interface NewCharacter {
public void test1();
public default void test2() {
System.out.println("我是新特性1");
}
}
接口的实现类可以不用实现default修饰的方法
public class NewCharacterImpl implements NewCharacter {
@Override
public void test1() {}
public static void main(String[] args) {
NewCharacter nca = new NewCharacterImpl();
nca.test2();
}
}
Date Api更新
java.time包下提供了更加方便的日期处理类
LocalDate为日期处理类、LocalTime为时间处理类、LocalDateTime为日期时间处理类
DateTimeFormatter类,默认定义了很多常量格式
如:
LocalDateTime dt = LocalDateTime.now();
// String format = dt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);//2020-05-22T14:50:47.398
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
String format = dtf.format(dt); //2020-05-22 02:51:11
System.out.println(format);
Stream
如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。
Lambda表达式
也称为闭包,是匿名函数的一种表示方式,还可以将lambda表达式作为参数传递或者作为其他函数的返回值
语法是:
(参数列表) -> { 表达式或语句块 }
参数列表:与常规方法参数列表类似,可以有一个或多个参数,也可以没有参数(使用空括号())。->:Lambda操作符,用于分隔参数列表和主体。{ 表达式或语句块 }:Lambda体的实现,可以是一个表达式(如果Lambda体只有一条语句且该语句是一个表达式)或是一个语句块(用花括号{}包围)。
无参表达式:
() -> System.out.println("Hello, Lambda!")
一个参数表达式
x -> x * x
多个参数表达式
(x, y) -> x + y
Lambda表达式通常与函数式接口一起使用。函数式接口是一个只定义了一个抽象方法的接口。
Thread thread = new Thread(() -> System.out.println("This thread is running by a lambda expression."));
thread.start();
用来实例化Thread类的构造函数,该构造函数需要一个Runnable类型的参数(Runnable是一个函数式接口)。
Lambda表达式常用的函数式接口
- Predicate<T>:用于定义布尔值测试函数,通常用于集合的过滤操作。Lambda表达式作为Predicate的实现,可以方便地定义过滤条件。
List<String> filteredList = list.stream()
.filter(s -> s.startsWith("A")) // Lambda表达式实现Predicate<String>
.collect(Collectors.toList());
- Consumer<T>:表示接受一个输入参数并且没有返回的操作,常用于对集合中的每个元素执行某个操作。
list.forEach(s -> System.out.println(s)); // Lambda表达式实现Consumer<String>
- Function<T, R>:接受一个输入参数并返回一个结果,常用于集合的映射操作,即将一个集合的元素转换成另一种类型。
List<String> upperCaseList = list.stream()
.map(s -> s.toUpperCase()) // Lambda表达式实现Function<String, String>
.collect(Collectors.toList());
List<String> names = myObjects.stream() .map(MyObject::getName).collect(Collectors.toList());
// 使用Stream API将User列表转换为Student列表
List<Student> studentList = userList.stream()
.map(user -> new Student(user.getName(), user.getAge()))
.collect(Collectors.toList());
//对于没有有参构造函数,将User列表转换为Student列表
List<Student> studentList = userList.stream()
.map(user -> {
Student student = new Student(); // 假设Student有一个无参构造函数
student.setName(user.getName());
student.setAge(user.getAge());
return student;
})
.collect(Collectors.toList());
- UnaryOperator<T>:特殊类型的Function,它接受一个参数并返回相同类型的结果,常用于集合元素的转换操作。
List<Integer> squaredList = list.stream()
.map(i -> i * i) // Lambda表达式实现UnaryOperator<Integer>
.collect(Collectors.toList());
- BiFunction<T, U, R>:接受两个输入参数并返回一个结果,用于处理两个输入参数的情况。
Map<String, Integer> lengthMap = list.stream()
.collect(Collectors.toMap(s -> s, s -> s.length())); // Lambda表达式实现BiFunction
- Comparator<T>:用于定义对象比较规则,常用于集合的排序操作。
List<String> sortedList = list.stream()
.sorted(Comparator.comparing(String::length)) // Lambda表达式隐式实现Comparator<String>
.collect(Collectors.toList());
- Supplier<T>:没有输入参数,返回一个结果,常用于生成集合中的元素或提供默认值。
List<Integer> numbers = Stream.generate(() -> new Random().nextInt(100))
.limit(10) // 限制生成的元素数量
.collect(Collectors.toList()); // Lambda表达式实现Supplier<Integer>
jdk1.8提供了另外一种调用方式::,方法引用就是Lambda表达式(Apple a) -> a.getWeight()的快捷写法,形参列表允许省略形参类型。如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
|
类型 |
语法 |
对应的Lambda表达式 |
|
1、静态方法引用 |
类名::staticMethod |
(args) -> 类名.staticMethod(args) |
|
2、实例方法引用 |
inst::instMethod |
(args) -> inst.instMethod(args) |
|
3、对象方法引用 |
类名::instMethod |
(inst,args) -> 类名.instMethod(args) |
|
4、构建方法引用 |
类名::new |
(args) -> new 类名(args) |
实例:
Arrays.stream(s).forEach(System.out::println);
将数组s转为Stream流对象,并行调用forEach方法,forEach遍历每个元素,调用lambda表达式,System.out是获取到PrintStream类,out是它的方法
元空间代替永久代
PermGen space指的就是永久代,jdk8就没有了永久代,有了Metaspace(元空间)
JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。
1.7 和 1.8 将字符串常量由永久代转移到堆中
元空间是类的元数据存放到了本地内存中,永久代使用的jvm内存
元空间的存的内容跟1.7的永久代存的内容基本一样,都是类及类加载器的结构、描述等元信息
为什么取代
永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
类及方法的信息等比较难确定其大小,因此需要的永久代内存大小不好确定
元空间内存管理:
元空间的内存管理由元空间虚拟机来完成,在元空间中,类和其元数据的生命周期和其对应的类加载器是相同的。元空间有存在内存碎片的问题
对于类的元数据我们需要不同的垃圾回收器进行处理,现在只需要执行元空间虚拟机的C++代码即可完成。
方法区和永久代:
方法区是JVM 的规范,永久代(PermGen space)是jvm规范的实现
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
编程要看的书目
spring技术内幕
《Java编程思想》
《深入理解Java虚拟机:JVM高级特性与最佳实践》
《HotSpot实战》
《深入分析Java Web技术内幕》
《大型网站技术架构 核心原理与案例分析》
《大型网站系统与Java中间件实践》
《Java并发编程实战》
《从Paxos到ZooKeeper 分布式一致性原理与实践》
《Effective Java中文版》
《MySQL技术内幕:InnoDB存储引擎》

浙公网安备 33010602011771号