java 小记
好记性不如烂笔头。
Java语言中的数据类型:基本数据类型(8种)+ 引用类型(对象)
基本数据类型:byte、short、int、long、float、double
引用类型:基本类型的包装类、其他各种对象类型
1、获取web项目根目录的绝对路径
request.getContextPath() 获取项目名称,如 /BiYeSheJi
getServletContext().getRealPath("/") 获取项目根目录,如 C:\install files\programing software\eclipse\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\BiYeSheJi\
或
request.getRealPath(""); //D:\eclipse\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\zhx-web\
strDirectory="files" , request.getRealPath("//WEB-INF//" + strDirectory + "//"); //D:\eclipse\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\zhx-web\WEB-INF\files\
获取放.class文件的目录的绝对路径:
(即Maven工程的classes绝对路径 或 非Maven工程的bin绝对路径,当打包成Jar运行时获取到的是Jar的绝对路径 /.../xxx.jar)
// 获取类所在绝对路径
java.net.URL url = Main_Other.class.getProtectionDomain().getCodeSource().getLocation();
String filePath = null;
try {
filePath = url.getPath();//被用UTF-8编码了
filePath = java.net.URLDecoder.decode(filePath, "utf-8");
System.out.println(filePath);
} catch (Exception e) {
e.printStackTrace();
}
由上,可以获取程序的父目录名字:
(在Eclipse中运行时得到放置.class文件的目录的绝对路径[bin或Maven的classes],打包成jar时获得jar程序(无论是Maven还是普通程序)的所在目录的绝对路径):
String rootPath = Test.class.getProtectionDomain().getCodeSource().getLocation().getPath();
rootPath = rootPath.substring(0, rootPath.lastIndexOf("/") + 1);
System.out.println(rootPath);
其应用是可以以 rootPath+"/"+config.properties 指定路径来读取配置文件,这样开发时配置文件放在工程里,打包时放在程序所在目录,而不用更改配置文件路径。
注:在Spring [Boot]中则可以通过 ResourceUtils.getURL("classpath:").getPath() 获取到该路径。
2、前台URL中含有中文参数时,传给Java后台获取到的会是乱码,因为ISO-8859-1是Java中网络传输使用的标准字符集,String city=request.getParameter("city");得到的还是ISO-8859-1字符集,所以要转换一下,方法不止一种,如:city = new String(city.getBytes("ISO-8859-1"), "UTF-8");
获取到的中文参数存到后台数据库后,在数据库里都变成?的解决方法:在数据库连接的URL上加 ?useUnicode=true&characterEncoding=UTF-8 ,如 jdbc:mysql://172.20.4.183:3306/jpatest?useUnicode=true&characterEncoding=UTF-8
3、20150315
Spring框架中提供一种模式,定义类的成员变量后不用写get或set方法就可以直接使用get或set,只要在变量前声明@Getter @Setter;
此外,对于进行@Autowired声明的类变量,不需要去new它,在Spring框架中会自动维护一个类对象。如果去new它也不会保存,但会产生一些影响,如如果该类在一个web接口中会被用到,则当接口请求频繁时会造成产生大量对象的恶果
获取类所在目录的绝对路径:
java.net.URL url = TestMain.class.getProtectionDomain().getCodeSource().getLocation();
String filePath = null;
try {
filePath = java.net.URLDecoder.decode(url.getPath(), "utf-8");
System.out.println(filePath);
} catch (Exception e) {
e.printStackTrace();
}
4、20150329
Java多线程实现方法:
(1)写一个实现Runnable接口(及里面的run方法)的类,然后启动一个线程,此类作为线程的参数,再start
class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
new Thread(new MyRunnable()).start();
(2)写一个类,继承Thread类(实现run方法),然后new此类,再start
class MyThread extends Thread {
public void run() {
System.out.println("fuck");
}
}
(new MyThread()).start();
5、20150702 参考:http://www.cnblogs.com/qinqinmeiren/archive/2011/07/19/2151683.html
Java 编译器默认为所有的 Java 程序导入了 JDK 的 java.lang 包中所有的类(import java.lang.*;),其中定义了一些常用类,如 System、String、Object、Math 等,因此我们可以直接使用这些类而不必显式导入。但是使用其他类必须先导入。
不像 C/C++,Java 不支持无符号类型(unsigned)。
float 类型有效数字最长为 8 位,有效数字长度包括了整数部分和小数部分;double 类型有效数字最长为 15 位。如2.123456789f+1f 结果为3.1234567f。
Java也有格式化输出函数,例:System.out.printf("%d*%d=%2d ", i, j, i * j)。
String变量被初始化后,长度和内容都不可变,每次对String的操作都会生成新的String对象,不仅效率低,而且耗费大量内存空间;对于String str = new String(“abc”),实际上创建了两个String对象,一个是”abc”对象,存储在常量空间中(常量池),一个是使用new关键字为对象str申请的空间。
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
// i1,i2分别位于堆中不同的内存空间
System.out.println(i1 == i2);// 输出false Integer i3 = 1;
Integer i4 = 1;
// i3,i4指向常量池中同一个内存空间
System.out.println(i3 == i4);// 输出true // 很显然,i1,i3位于不同的内存空间
System.out.println(i1 == i3);// 输出false // 值大于127时,不会从常量池中取对象
Integer i5 = 128;
Integer i6 = 128;
System.out.println(i5 == i6);// 输出false
6、String、StringBuffer、StringBuilder:前者效率最低,初始化后长度、内容不可变;后两者效率更高,StringBuilder类和StringBuffer类功能基本相似,方法也差不多,主要区别在于StringBuffer类的方法是多线程安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。
线程安全:
- StringBuffer:线程安全
- StringBuilder:线程不安全
速度:
一般情况下,速度从快到慢为 StringBuilder > StringBuffer > String,当然这是相对的,不是绝对的。
使用环境:
- 操作少量的数据使用 String;
- 单线程操作大量数据使用 StringBuilder;
- 多线程操作大量数据使用 StringBuffer。
7、Java修饰符
Java 通过修饰符来控制类、属性和方法的访问权限和其他功能,通常放在语句的最前端,分为访问修饰符和非访问修饰符。访问修饰符也叫访问控制符,是指能够控制类、成员变量、方法的使用权限的关键字
修饰符 | 说明 |
---|---|
public | 共有的,对所有类可见。 |
protected | 受保护的,对同一包内的类和所有子类可见。 |
默认的 | 在同一包内可见。默认不使用任何修饰符。 |
private | 私有的,在同一类内可见。 |
java的访问控制是停留在编译层的,也就是它不会在.class文件中留下任何的痕迹,只在编译的时候进行访问控制的检查。其实,通过反射的手段,是可以访问任何包下任何类中的成员,例如,访问类的私有成员也是可能的。
8、Java变量类型及作用域
变量类型:前两者会自动初始化,第三个不会,须显式初始化。
类变量(或称静态变量、全局变量),属于类,通过类或实例访问;声明周期:随类的加载和卸载而产生和销毁
实例变量(或称成员变量),属于实例,通过实例访问;声明周期:随对象的创建和回收而产生和销毁
局部变量,在方法(类方法、实例方法、构造方法等)、代码块(静态块、实例块)内定义的变量;声明周期:随方法、构造器、代码块的执行而产生和销毁
变量作用域:在Java中,变量的作用域分为四个级别:
类级(全局级变量或静态变量,需要使用static关键字修饰)
对象实例级(成员变量,实例化后才会分配内存空间,才能访问)
方法级(包括类方法、实例方法、构造方法,在方法内部定义的变量,就是局部变量)
块级(定义在一个块内部的变量,也是局部变量,变量的生存周期就是这个块)。
- 块可以是静态块或非静态块,还可以是在方法内的块。
- 块内部能够访问类级、实例级变量,如果块被包含在方法内部,它还可以访问方法级的变量。
- 方法内部除了能访问方法级的变量,还可以访问类级和实例级的变量。
- 方法级和块级的变量必须被显示地初始化,否则不能访问。
9、this关键字
this 关键字用来表示当前对象本身,或当前类的一个实例,通过 this 可以调用本对象的所有方法和属性。
- 构造方法中区分形参和类的成员变量:
public String name;
public int age;
public Demo(String name, int age){
this.name = name;
this.age = age;
} - 构造方法重载时作为方法名初始化对象:
public class Demo{
public Demo(){
this("博客园", 10);
}
public Demo(String name, int age){
this.name = name;
this.age = age;
}
} - 作为对象参数传递
public class Demo{
public static void main(String[] args){
B b = new B(new A());
}
}
class A{
public A(){
new B(this).print(); // 匿名对象
}
public void print(){
System.out.println("Hello from A!");
}
}
class B{
A a;
public B(A a){
this.a = a;
}
public void print() {
a.print();
System.out.println("Hello from B!");
}
}
- 构造方法中区分形参和类的成员变量:
10、构造方法不能被继承,要使用父类的构造方法,可以通过super关键字
11、重写(覆盖)和重载:
前者子类继承父类,覆盖父类的方法,参数类型、个数、顺序、返回类型必须与父类一致,可通过super调用父类被覆盖的方法(父类被覆盖的方法还能用);
后者在一个类中有多个同名方法,只要参数类型、个数、顺序至少一者不同即可;
父类的一个方法只能被子类覆盖一次(否则一个类中有参数列表和返回类型都完全相同的方法会报错),而一个方法可以在所有的类中可以被重载多次。
重写的方法不能比原方法抛出更多的异常;被重写的方法不能是final类型、不能为private、不能为static
12、面向对象的三大特性:封装、继承、多态。
多态存在的三个必要条件:要有继承、要有重写、父类变量引用子类对象。
多态可以理解为父类既可以指向父类实例,也可以指向其子类的实例,对于一个方法,既可以表现父类的行为,如果被子类继承则表现为子类的行为,从而有不同的表现形式。
当使用多态方式调用方法时:
- 首先检查父类中是否有该方法,如果没有,则编译错误;如果有,则检查子类是否覆盖了该方法。(可见,具有不同表现形式即多态是相对于被继承的方法来说的)
- 如果子类覆盖了该方法,就调用子类的方法,否则调用父类方法。
13、多态性相关问题:
(1)如何判断一个变量所实际引用的对象的类型 ? C++使用runtime-type information(RTTI),Java 使用 instanceof 操作符:如果变量引用的是当前类或它的子类的实例,instanceof 返回 true,否则返回 false。如下:
public final class Demo{
public static void main(String[] args) {
// 引用 People 类的实例
People obj = new People();
if(obj instanceof Object){
System.out.println("我是一个对象");
}
if(obj instanceof People){
System.out.println("我是人类");
}
if(obj instanceof Teacher){
System.out.println("我是一名教师");
}
if(obj instanceof President){
System.out.println("我是校长");
}
System.out.println("-----------"); // 分界线 // 引用 Teacher 类的实例
obj = new Teacher();
if(obj instanceof Object){
System.out.println("我是一个对象");
}
if(obj instanceof People){
System.out.println("我是人类");
}
if(obj instanceof Teacher){
System.out.println("我是一名教师");
}
if(obj instanceof President){
System.out.println("我是校长");
}
}
}
class People{ }
class Teacher extends People{ }
class President extends Teacher{ }
(2)对象类型转换
这里所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象。当对不存在继承关系的对象进行强制类型转换时,java 运行时将抛出 java.lang.ClassCastException 异常。子类向父类转换称为“向上转型”(如父类变量引用子类对象),将父类向子类转换称为“向下转型”(有些时候为了完成某些父类没有的功能,我们需要将向上转型后的子类对象再转成子类,调用子类的方法,这就是向下转型),向上转型会自动进行,向下转型的对象必须是当前引用类型的子类。
不能直接将父类的对象强制转换为子类类型,只能将向上转型后的子类对象再次转换为子类类型。也就是说,子类对象必须向上转型后,才能再向下转型。因为向下转型存在风险,所以在接收到父类的一个引用时,请务必使用 instanceof 运算符来判断该对象是否是你所要的子类。示例如下:
public class Demo {
public static void main(String args[]) {
SuperClass superObj = new SuperClass();
SonClass sonObj = new SonClass();
// superObj 不是 SonClass 类的实例
if(superObj instanceof SonClass){
SonClass sonObj1 = (SonClass)superObj;
}else{
System.out.println("①不能转换");
}
superObj = sonObj;
// superObj 是 SonClass 类的实例
if(superObj instanceof SonClass){
SonClass sonObj2 = (SonClass)superObj;
}else{
System.out.println("②不能转换");
}
}
}
class SuperClass{ }
class SonClass extends SuperClass{ }
14、final
在 Java 中,声明类、变量和方法时,可使用关键字 final 来修饰。final 所修饰的数据具有“终态”的特征,表示“最终的”意思。具体规定如下:
- final 修饰的类不能被继承。
- final 修饰的方法不能被子类重写。
- final 修饰的变量(成员变量或局部变量)即成为常量,只能赋值一次。
- final 修饰的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有 一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用。
- final 修饰的局部变量可以只声明不赋值,然后再进行一次性的赋值。
- 如果将引用类型(任何类的类型)的变量标记为 final,那么该变量不能指向任何其它对象。但可以改变对象的内容,因为只有引用本身是 final 的。
15、内部类
在 Java 中,允许在一个类(或方法、语句块)的内部定义另一个类,称为内部类(Inner Class),有时也称为嵌套类(Nested Class)。内部类和外层封装它的类之间存在逻辑上的所属关系,一般只用在定义它的类或语句块之内,实现一些没有通用意义的功能逻辑,在外部引用它时必须给出完整的名称。参见 这 里
内部类是 Java 1.1 的新增特性,看似增加了—些优美有趣,实属没必要的特性;内部类语法很复杂,严重破坏了良好的代码结构, 违背了Java要比C++更加简单的设计理念。
创建内部类的方法:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); //Inner是实例的内部类
Outer.StaticInner staInner = new Outer.StaticInner(); //StaticInner是静态内部类
16、抽象类和接口
抽象类:
抽象类是指包含零或多个抽象方法的类,类名和抽象方法均由abstract修饰,如 abstract class A{int a,b; abstract void getA(); int getB(){return b;}};
抽象类可以有实现了的方法、可以有初始化或未初始化的成员变量,当没有抽象方法时,它和普通类类似,只是多了abstract修饰;
抽象类不能用来创建实例(故不能有构造方法),只能用继承它的子类创建实例,但可以用抽象类定义引用变量并指向子类对象。
接口:
接口是特殊的抽象类,是若干常量和抽象方法的集合,接口名由interface修饰;
接口不能用来创建实例(故不能有构造方法),只能用实现了该接口的类来创建实例,但可以用接口定义变量指向其实现类的对象;
接口中声明的成员变量默认都是 public static final 的,由于是final,必须显示地初始化且实现类不能修改其值。在常量声明时可以省略这些修饰符;
接口中只能定义抽象方法,这些方法默认为 public abstract 的,因而在声明方法时可以省略这些修饰符。试图在接口中定义实例变量、非抽象的实例方法及静态方法,都是非法的。
// 接口成员变量必须是public static void,可省略修饰符
// 接口方法必须是public abstract,可省略修饰符
// 上述两者修饰符可省略,在显示指定修饰符时,与上述一个修饰符相悖的都是非法的
interface SataHdd {
// 连接线的数量
public int connectLine; // 编译出错,connectLine被看做静态常量,必须显式初始化 // 写数据
protected void writeData(String data); // 编译出错,必须是public类型
// 读数据 public static String readData(){ //编译出错,接口中不能包含静态方法
return "数据"; //编译出错,接口中只能包含抽象方法,
}
} class BBB implements SataHdd {
public BBB() {
this.connectLine = 33;// 错误,connectLine为public static final,值不能更改
} public int getData() {
return 0;
}
}
17、JDK1.2后,Java对引用概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Soft Reference)、虚引用(Phantom Reference)。未被任一种引用引用的对象会被回收(这就是回收算法要干的活:如何确定没有被引用的对象、什么时候回收、被如何回收),被后三种引用引用的对象在一定条件下会被回收。
- 强引用:只要对象被强引用引用,垃圾回收器就不会回收该对象。这类引用最常用,为类似“Object obj=new Object();”这样的引用。
- 软引用:用来描述一些还有用但并非必需的对象。在系统将发生内存溢出异常前会把这类引用的对象进行回收。JDK1.2开始提供了SoftReference类实现软引用。
- 弱引用:也用来描述非必需对象,但强度比软引用更弱一些。被弱引用引用的对象只能生产到下一次垃圾回收发生之前。JDK1.2开始提供了WeakReference类实现弱引用。
- 虚引用:最弱,亦称为幽灵引用或幻影引用。一个对象是否有虚引用完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为对象设置虚引用的唯一目的是能在此对象呗垃圾收集器回收时收到一个系统通知。JDK1.2开始提供了PhantomReference类实现此引用。
18、Java Runtime.getRuntime.exec()的执行过程:首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用此新进程去执行外部命令,最后再退出这个新进程。若频繁执行此操作,系统CPU、内存消耗会很大。
19、
OpenJDK是Sun在2006年末把Java开源而形成的项目,其源码与Sun/Oracle JDK源码除了文件头的版权注释之外,其余代码基本相同。其基于正在研发的JDK7上产生,剥离JDK7新功能的代码产生第一个版本OpenJDK6
64位虚拟机可以管理更多的内存,但Java程序运行在64位虚拟机上需要付出比较大的额外代价:
1、由于指针膨胀和各种数据类型对其补白的原因,运行于64位系统上的Java应用需要消耗更多的内存,通常比32位增耗10%-30%
2、64位虚拟机的运行速度比32位有15%左右的性能差异
目前大多数的企业还是选择虚拟集群等方式继续在32位虚拟机中进行部署。
随着硬件技术的发展,计算机终究会过渡到64位的时代,主流的虚拟机应用终究也会从32位过渡到64位,而虚拟机对64位的支持也将会进一步改善。
20、Java获取指定范围内的随机数
public static int getRandom(int min, int max, boolean isMaxInclude) {
return new Random().nextInt(max - min + (isMaxInclude ? 1 : 0)) + min;
}
21、每个对象都有一个锁和一个等待队列,类对象也不例外。synchronized保护的是对象,对实例方法,保护的是当前实例对象this,对静态方法,保护的是类对象。synchronized静态方法和synchronized实例方法保护的是不同的对象,不同的两个线程,可以同时,一个执行synchronized静态方法,另一个执行synchronized实例方法。
22、
获取本地IP地址: InetAddress.getLocalHost().getHostAddress()
23、Java.nio.channel.FileChannel的 transferTo transferFrom 是零拷贝的两个函数
24、jdk http请求
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map; public class HttpRequest {
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.addRequestProperty("content-Type",
"text/html;charset=utf-8");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
} /**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
} public static String doRequest(String url, String param, boolean isGetMethod) {
if (isGetMethod) {// 发生GET请求
return HttpRequest.sendGet(url, param);
} else {// 发生POST请求
return HttpRequest.sendPost(url, param);
}
} public static void main(String[] args) throws UnsupportedEncodingException {
String url = "http://localhost:8080/zhx-web/test1/name="
+ java.net.URLEncoder.encode("小明", "utf-8");
System.out.println(url);
// url =
// "http://localhost:8080/zhx-web/test1/name=小明";//程序中按默认编码对中文进行url
// encode,这里是GBK;浏览器一般是utf-8
String param = ""; url = "http://mu:8080/odp-web/route/add/";
param = "arraydata=255234"; url = "http://restapi.amap.com/v3/direction/driving";
param = "origin=116.45925,39.910031&destination=116.587922,40.081577&output=json&key=7b00cf949d5bee7805f96527129b7c29"; String res = HttpRequest.doRequest(url, param, false);
System.out.println(new String(res.getBytes("gbk"),"utf-8"));
}
}
25、通过代码获取程序当前占用内存的大小
//法1,通过Runtime.getRuntime()获取
Runtime myRuntime = Runtime.getRuntime();
myRuntime.maxMemory();//进程(JVM)可从OS申请的最大内存大小,也即程序运行时候的堆的最大可能的值。可通过-Xmx指定。
myRuntime.totalMemory();//进程(JVM)当前已从OS申请的内存大小,也即程序当前堆的大小。可通过-Xms设置初始大小,运行过程中可能增加或减少,但不会小于-Xms指定的值。
myRuntime.freeMemory();//进程(JVM)已从OS申请的内存中,还有未被使用。
myRuntime.totalMemory()-myRuntime.freeMemory()即为进程真正使用的内存大小。 //法2,通过ManagementFactory.getMemoryMXBean()获取
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
MemoryUsage usage = memorymbean.getHeapMemoryUsage();//每次获取下面的新值前都要调用此代码,而通过Runtime则不用
usage.getInit();//-Xms指定的大小
usage.getUsed();//即myRuntime.totalMemory()-myRuntime.freeMemory()
usage.getCommitted();//即myRuntime.totalMemory()
usage.getMax();//即myRuntime.maxMemory()
26、Java类变量(静态变量)的初始化方式:定义时初始化、静态代码块初始化
27、Java创建类的实例的方式:通过new、通过反射( Class.forName("类的完全限定名"); )、通过反序列化
28、Java泛型:http://blog.csdn.net/seu_calvin/article/details/52230032、https://blog.csdn.net/s10461/article/details/53941091
Java泛型只在编译阶段有效,在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,成功编译过后的class文件中不包含任何泛型信息的。
总结:
定义。泛型中,类型参数的位置:
对于泛型类和泛型接口:在类或接口名后,如 public class Generator<T>
对于泛型方法:在方法返回值前面,如public <T> T next()
静态方法中的泛型。静态方法无法使用类上定义的泛型参数(why?因为泛型参数只在编译期有效);从而,静态方法要使用泛型的话自然自身得是个泛型方法。
泛型转换:泛型参数的具体类型实例不能互相转换,即使泛型的具体类型有继承关系,如List<Number> numbers=new List<Integer>() 会报编译错误。
泛型边界:
通配符:List<?> numbers=new List<Integer>() 则可以,这里问号表示通配符。
上下界:上下界的声明须与泛型类型声明放一起,如 public <T extends Number> T showKeyName(Generic<T> obj)正确,public <T> T showKeyName(Generic<T extends Number> obj)错误。public void showKeyValue1(Generic<? extends Number> obj)则可以。
泛型数组。不允许创建泛型数组:List<String>[] ls = new ArrayList<String>[10]; 会编译错误:"Cannot create a generic array of ArrayList<String>"
更多示例可参阅:https://www.cnblogs.com/z-sm/p/6259859.html
29、Java char
在Java内部进行字符处理时,采用的都是Unicode,具体编码格式是UTF-16BE。
char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应于Unicode编号,用于表示那个Unicode编号对应的字符。由于固定占用两个字节,char只能表示Unicode编号在65536以内的字符,而不能表示超出范围的字符。那超出范围的字符怎么表示呢?使用两个char。
char的位运算可以看做就是对应整数的位运算,只是它是无符号数,也就是说,有符号右移>>和无符号右移>>>的结果是一样的。
30、Java可变长度参数
可变长参数的语法是在数据类型后面加三个点...,在函数内,可变长度参数可以看做就是数组来使用,可变长度参数必须是参数列表中的最后一个参数,一个函数也只能有一个可变长度的参数。
可变长度参数实际上会转换为数组参数,也就是说,函数声明max(int min, int... a)实际上会转换为 max(int min, int[] a),在main函数调用 max(0,2,4,5)的时候,实际上会转换为调用 max(0, new int[]{2,4,5}),使用可变长度参数主要是简化了代码书写。
31、创建数组时:
要么显式指定初始值,如 int[] arr = {,,}; 、 int[] arr = new int[]{,,};
要么显式指定长度(此时按默认值自动初始化),如 int[] arr = new int[];
两者不能同时指定,如 int[] arr = new int[3]{1,2,3} 是编译报错。这样可以防止初始值的个数与指定的长度不一致。
32、数组是引用类型,所以与类、接口的对象一样,可以变量间直接赋值(实际上是改变变量的引用,被引用的对象的内容并没变)。
int[] aa = new int[] { 1, 2, 3 };
int[] ab = new int[] { 4, 5, 6, 7 };
int[] atmp;
System.out.println(aa.length + " " + ab.length);// 3 4
atmp = aa;
aa = ab;
ab = atmp;
System.out.println(aa.length + " " + ab.length);// 4 3
33、Java switch里参数须为表达式、case的值须为常量表达式。表达式的类型可为:int(及比它小的类型,byte、short、char,不能为long、float、double等)、enum、String(Java1.7后)。其实最终都是通过整数,char本质上是整数、enum有对应的整数、String通过hashCode转为整数。
分支语句有 switch、if else、用于条件赋值的三元运算符等。
后两者编译后转换为 条件跳转/无条件跳转 语句;
前者如果分支数多采用条件跳转/无条件跳转需要进行很多词比较运算效率较低,可能会采用更高效的方式——跳转表,表的键为分支整数且有序、值为分支地址,因此能二叉查找,从而跳转效率更高。这里的键就是case 后的表达式值。
因此,如果分支数较多,采用 switch 比 if else 效率高。
34、用于String的“+”与“+=”是Java中仅有的两个重载过的操作符,除这两个外没有任何重载操作符,也不允许程序员重载任何操作符。
35、Java语言中的转义字符及正则语法中的转义字符在Java字符串中的表示。
ASCLL中的转义字符共有14个:
\o |
空字符(NULL) |
00H/0 |
\n |
换行符(LF) |
0AH/10 |
\r |
回车符(CR) |
0DH/13 |
\t |
水平制表符(HT) |
09H/9 |
\v |
垂直制表(VT) |
0B/11 |
\a |
响铃(BEL) |
07/7 |
\b |
退格符(BS) |
08H/8 |
\f |
换页符(FF) |
0CH/12 |
\’ |
单引号 |
27H/39 |
\” |
双引号 |
22H/34 |
\\ |
反斜杠 |
5CH/92 |
\? |
问号字符 |
3F/63 |
\ddd |
任意字符 |
三位八进制 |
\xhh |
任意字符 |
二位十六进制 |
在字符串或字符中只要出现反斜杠就 连同其后的一或多个字符 被认为是一个转义字符并尝试解析之,若不是合法的转义字符就会出错。在大多数语言中均如是,只不过可能不支持所有的转义字符。
语言对转义字符的处理:
在Java中支持的只有11个: \0 \n \r \t \b \f \' \" \\ \ddd \xhh ,即在Java中与反斜杠搭配的只能有这11种情况,除此之外的都被认为是有误的。如 "\c" 或 '\c' 都会报错,即使我们本意不是想把前者中的 \c 当做转义字符而是想表示 \ 和 c 这两个字符,但由于语言看到了斜杠就会按转义字符去解析,所以对于前后者都会因解析不成转义字符而报错。故若想表示这两个字符而不报错,就要加以处理以让语言不把它们当做转义字符解析,方法是对斜杠转义(让语言把斜杠当做普通字符而非转义字符的开始)——即 “\\c" 。
36、关于传值、传址
所谓传值、传址,是指调用函数时传入的实参是 变量的内容(或称变量的值) 还是 变量的位置(或称变量的地址)。对于前者,在被调用函数里是没法改变实参变量的值的,而后者则可以。
对Java来说,只有传值这一方式,因此无论如何都没有办法在被调用的函数里改变实参变量的值。
根据参数类型的不同,传的“值”的含义也不同,具体来说:
1、若参数是基本数据类型,则传的为变量的值。如int、long等
2、若参数是对象类型,则传的为引用(即对象的地址)。此时在被调用函数里虽然没法改变实参变量的值(即让该实参变量指向其他对象),但却可通过该引用改变实参变量指向的对象的内容。
对于String、基本数据类型包装类如Integer 等immutable类型,虽然也为对象类型,但因为其没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待。可以认为是传值,因此没办法在函数里改变引用对象的内容。
示例:
class Person {
public String name = "LiMing";
}
class TestCase {
public static void test(Person p, Integer count, String address) {
p.name = "XiaoLi";
p = null;
count = 2;
address = "Beijing";
}
public static void main(String[] args) {
Person person = new Person();
Integer count = 1;
String address = "Shanghai";
System.out.println(person.name + " " + (person == null) + " " + count + " " + address);// LiMing false 1 Shanghai
test(person, count, address);
System.out.println(person.name + " " + (person == null) + " " + count + " " + address);// XiaoLi false 1 Shanghai
}
}
Java对变量值的解析方式:
若变量是基本数据类型,则直接使用该值进行加减等操作;
若变量是对象类型,则把该值当做地址,需要一次据之进行一次寻址才能拿到要进行加减等操作的值。
对C来说,除了传值外,还有传址方式,因此能在被调用函数里改变实参变量的值。
1、若参数是基本数据类型,则为传值,如int、long等
2、若参数是指针类型,且实参是一个指针变量,则在被调用函数里无法改变实参即该变量的内容但可以改变该变量指向的对象的内容。这相当于上述Java里的引用情形。
特别地,若实参是形如 &a 这样取值运算符加变量的形式,则实参的值会被改变,此即传址。实际上,对变量取址就隐含得到了指向该变量的指针,所以函数里能够改变该指针指向的对象的内容即变量的值。
示例:如下所示,传指针变量a时调用前后指针变量a的内容不变但其指向的对象的内容变了;传&d2前后d2的内容变了。
void test(int * p,int *q)
{
*p=;
*q=;
}
int main()
{
int d1=,d2=;
int *a=&d1; printf("%p\n",a);//000000000062FE1C
test(a,&d2);
printf("%p\n",a);//000000000062FE1C
printf("%d %d %d\n",*a,d1,d2);//10 10 20 return ;
}
37、String.intern()
String.intern()是个Native方法,用于返回字符串在常量池中的“位置”。其作用在不同JDK上不同:
- 在JDK1.7以前,其返回常量池中字符串与此字符串相同的String对象,若不存在则先将此字符串添加到常量池,然后返回常量池中的该字符串对象。(即将首次出现的字符串复制到常量池并返回常量池中的该字符串对象)
- 从JDK1.7开始,不再将首次出现的字符串复制到常量池,而是在常量池中记录首次出现的字符串的引用。
示例:
String s1 = new StringBuilder().append("hello ").append("there").toString();
System.out.println(s1 == s1.intern()); System.out.println();
String s2 = new StringBuilder().append("hello ").append("there").toString();
System.out.println(s2 == s2.intern()); System.out.println();
System.out.println(s1 == s2);
System.out.println(s1.intern() == s2.intern());
System.out.println(s1 == s2.intern());
System.out.println(s1.intern() == s2); System.out.println();
String s3 = new StringBuilder().append("ja").append("va").toString();
System.out.println(s3==s3.intern());//false
若在JDK1.7之前的虚拟机上执行,则结果为 false, false, false, true, false, false, false。 此时s1为堆对象,s1.intern()为常量池中的对象,故不等,其他同理。
若在JDK1.7及之后的虚拟机上执行,则结果为 true, false, false, true, true, false, false。 此时s1为堆对象,s1.intern()亦为堆对象(因为常量池中保存的是s1对象的引用),故相等,即s1.intern()和s2.intern()实际上都返回s1;而 “java”是保留关键字在s3之前出现过了,所以s3.intern()返回的不是s3。
38、32位虚拟机 VS 64位虚拟机
由于指针膨胀和各种数据类型对齐补白的原因,运行于64位系统上的Java应用需要消耗更多的内存(通常比32位的增加10%~30%的内存开销) ;此外,64位虚拟机的运行速度比32位的大约有15%左右的性能差距。
不过,64位虚拟机也有它的优势:首先能管理更多的内存,32位最多4GB,实际上还受OS允许进程最大内存的现在(Windows下2GB);其次,随着硬件技术的发展,计算机终究会完全过渡到64位,虚拟机也将过渡到64位。
39、Java Runtime.getRuntime.exec()
用来执行外部命令,JVM执行此命令的过程如下:
首先克隆一个和当前虚拟机有一样环境变量的进程,再用此新进程去执行外部命令,最后再退出该进程。
若频繁执行该命令,系统的CPU和内存消耗很大。
40、关于Java自动拆箱/装箱的注意点
1、对于Integer、Long,在自动装箱时会进行缓存:位于[-128,127]的每个数只有唯一一个对应的包装类实例,不在此范围的数则可以对应任意多个包装类实例。(Float、Double则不会缓存;Integer缓存范围的最大值可以配置,默认为127;自动装箱实际上是调用了类的valueOf()来返回实例,在该方法里进行判断是否直接取缓存对象,因此如果直接new则不会缓存)。
2、==运算符的作用:操作数是基本类型时对应值一样为true;操作数为引用类型时两操作数须是同种类型编译才能通,若它们指向同一对象则为true。包装类的==运算符在不遇到算术运算时不会自动拆箱。
3、equals()方法的作用:对比的两者是同种类型且值一样时为true。包装类的equals()方法不处理数据转型关系。
示例:
Integer a=1;
Integer b=1;
Integer c=128;
Integer d=128;
Integer e=new Integer(1);
Integer f=new Integer(1);
Long g=1;
Long h=1;
Double i=1;
Double j=1; System.out.println(a==b);//true
System.out.println(c==d);//false,不在缓存范围
System.out.println(e==f);//false,不是自动装箱
System.out.println(g==h);//true,Long与Integer一样有缓存处理
System.out.println(i==j);//false,Double、Float没有缓存处理
Integer a = 1;
Integer b = 2;
Integer c = 3;
Long d = 3L; System.out.println(c == (a + b));// true,在缓存范围内
System.out.println(c.equals(a + b));// true,是同种类型且值一样 System.out.println(d == (a + b));// true,有运算符,自动装箱
// System.out.println(d == c);//类型不同,编译不通过
System.out.println(d.equals(a + b));// false,不是同种类型
System.out.println(c.equals(d));// false,不是同种类型
41、
Java中整数对象的Cache:
- 只有整数才会被Cache(Character、Byte、Short、Integer、Long),且只有在自动装箱时才会Cache
- Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行,Integer的默认范围为-128到127。
Java整数的比较(涉及到自动拆箱/装箱):
1、若两数有一个是基本类型,则都按基本类型比较。如:
int i = 1300;
Integer i1 = 1300;
Integer i2 = new Integer(1300);
System.out.println(i == i1);//true
System.out.println(i == i2);//true
//只要两数含基本类型,就按基本类型比,不存在Cache问题。
2、否则,都是包装类对象,此时需要考虑自动装箱时的Cache问题:
Integer i1 = 0;
Integer i2 = new Integer(0);
Integer i3 = 0;
System.out.println(i1 == i2);//false,始终为false
System.out.println(i1==i3);//true,若值都从0变为大于Cache范围的数如129,这次为false
42、同步容器与并发容器
容器(Collection)有List、Set、Map的实现类等,它们不是线程安全的。为了线程安全,有:
同步容器:简单粗暴地加排它锁,如Vector、HashTable、Stack等
并发容器:同步容器直接加排它锁导致效率低, JDK5中添加了新的concurrent包,其中包含了很多并发容器,这些容器针对多线程环境进行了优化,大大提高了容器类在并发环境下的执行效率。如CopyOnWriteArrayList、ConcurrentLikedQueue、ArrayListBlockingQueue、LinkedBlockingQueue、ConcurrentHashMap等。
43、类型提升
byte x = 64;
int i = x << 2;
byte j = (byte) (x << 2);
System.out.println(i + " " + j);// 256 0
45、关于除以0:
System.out.println(100 / 0.0);// Infinity
System.out.println(100.0 / 0);// Infinity
System.out.println(100.0 / 0.0);// Infinity
System.out.println(100 / 0);//java.lang.ArithmeticException: / by zero
System.out.println(3 / 2 + 1.0);// 2.0
原因:若操作符的左右操作数中最高类型是浮点数,则由于类型提升两操作数都按浮点数运算,再由于计算机无法精确表示浮点数所以此时除数的0在计算机内部实际上不是0,故前三者不会出现除以0的异常。
46、Java中获取类对象的方法
1、通过全限定类名字符串获取: Class.forName("ucar.buaa.act.Imtg.Grid");
2、通过类.class获取: ucar.buaa.act.Imtg.Grid.class
3、通过实例对象.getClass获取: Main myMain = new Main(); myMain.getClass(); ,若有继承关系,还可以 myMain.getClass().getSuperclass();
47、Java反射:(java的访问控制是停留在编译层的,它不会在.class文件中留下任何的痕迹,只在编译的时候进行访问控制的检查。因此,通过反射的手段,是可以访问任何包下任何类中的成员,例如,访问类的私有成员也是可能的)
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java反射机制主要提供了以下功能: 在运行时判断任意一个类所具有的成员变量和方法;在运行时构造任意一个类的对象;在运行时判断任意一个对象所属的类;在运行时调用任意一个对象的方法;生成动态代理。
更多详见:https://www.cnblogs.com/z-sm/p/6746210.html
48、接口和抽象类的区别:(抽象类是is a的关系,接口是like a的关系)
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){}。
7、抽象类里可以没有抽象方法
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。
特别是对于公用的实现代码,抽象类有它的优点。抽象类能够保证实现的层次关系,避免代码重复。然而,即使在使用抽 象类的场合,也不要忽视通过接口定义行为模型的原则。从实践的角度来看,如果依赖于抽象类来定义行为,往往导致过于复杂的继承关系,而通过接口定义行为能 够更有效地分离行为与实现,为代码的维护和修改带来方便。
49、Java静态类只能作为内部类,即静态类一定是静态内部类。静态内部类最大的好处在于可以隐藏自己(只让自己被所在外层的类用到),同时又可以访问到所在外层类的属性。和“非静态”的内部类相比,它可以放置一些静态的成员变量和方法定义,而非静态类不可以;而且,静态内部类不能访问外层类非静态的属性。
50、Java异常:
Java异常是Java提供的一种识别及响应错误的一致性机制。
Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出。
Java异常机制用到的几个关键字:try、catch、finally、throw、throws。
• try -- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
• catch -- 用于捕获异常。catch用来捕获try语句块中发生的异常。
• finally -- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
• throw -- 用于抛出异常。
• throws -- 用在方法签名中,用于声明该方法可能抛出的异常。
1、分类:
java.lang.Throwable是所有异常的根
java.lang.Error是错误,编译器不会检查Error。
当程序发生不可控的错误时,通常做法是通知用户并中止程序的执行。Error是throwable的子类,代表编译时间和系统错误,用于指示合理的应用程序不应该试图捕获的严重问题。Error由Java虚拟机生成并抛出,包括动态链接失败,虚拟机错误等。程序对其不做处理。
java.lang.Exception是异常,分为:Checked异常和RuntimeException。Exception类本身、以及Exception的子类中除了"RuntimeException及其子类"之外的其它子类都属于Checked异常。
Checked异常:代码里必须捕获或抛出到方法声明,编译器会检查此异常。Java认为Checked异常都是可被处理的异常,Java程序必须显示处理Checked异常。若程序没有处理,该程序在编译时就会发生错误无法编译。这体现了Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种:当前方法指导如何处理该异常则用try...catch处理;当前方法不知道如何处理则再定义该方法时抛出异常。常见的有Java.lang.ClassNotFoundException、Java.lang.NoSuchMetodException、java.io.IOException等。
RuntimeException异常:代码里不需要捕获或抛出到方法声明,编译器不会检查此异常。如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。常见的有Java.lang.ArithmeticException、Java.lang.ArrayStoreExcetpion、Java.lang.ClassCastException、Java.lang.IndexOutOfBoundsException、Java.lang.NullPointerException等。
示例:
public static void main(String[] args) {
// throw new RuntimeException();//无错误,正常编译
throw new FileNotFoundException();//编译不过,需要要么tray...catch处理、要么抛出到方法声明
}
更多可参阅:Java异常简介及其架构、《Effective Java》中关于异常处理的建议
51、公平锁、非公平锁
对于非公平锁:“同步队列”中的每个Node始终自旋地CAS竞争同步状态;对于公平锁:“同步队列”中的每个Node,当检测到它的pre不是队列头节点时就await()了,当队列头结点任务完成后,会执行signal()唤醒它的next节点。
52、Java HashMap的key可以为byte[]类型吗?(假设我们希望内容一样的数组当成一样的key)
不行,因为HashMap的key必须为Object类型或其子类。改为Byte[]在语法上没问题,但逻辑上会有问题,因为HashMap需要确保同一个对象的hashCode一样,而HashMap的hashCode()方法的默认实现为 (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) ,可见,当传进数组时候会以数组的地址为hashCode而不是根据数组内容作为key。
同理,以数组类型作为key均存在此问题。示例:
HashMap<Byte[], Integer> map = new HashMap<>();
Byte[] bts1 = new Byte[] { 1, 2, 3 };
Byte[] bts2 = new Byte[] { 1, 2, 3 };
System.out.println(Integer.toHexString(bts1.hashCode()) + "," + bts1.toString());// 659e0bfd,[Ljava.lang.Byte;@659e0bfd
System.out.println(Integer.toHexString(bts2.hashCode()) + "," + bts2.toString());// 2a139a55,[Ljava.lang.Byte;@2a139a55 map.put(bts1, 1);
map.put(bts2, 2);
System.out.println(map.get(bts1));//
System.out.println(map.get(bts2));//
53、在不同语言中,字符串的表示及字符串引号中包含转义字符时的打印结果:(设字符串为"hel\nlo")
Java:(只能双引号表示,会换行) "hel\nlo"会换行。
JavaScript:(单、双引号表示,功能一样,均会换行)“hel\nlo”、'hel\nlo'均会换行。引号里可以嵌套一层引号,只要内层引号与外层不同是大引号或同是小引号。
Ruby:(双引号换行,单引号不换) “hel\nlo”会换行、'hel\nlo'则不会换行而是原原本本打印字面内容。
Go:(双引号换行、反引号不换) “hel\nlo”会换行、`hel\nlo`不会换行而是原原本本打印字面内容。
54、请求重定向和请求转发的区别:
- 一个web资源收到客户端请求后,通知服务器去调用另外一个web资源进行处理,称之为请求转发/307。
- 一个web资源收到客户端请求后,通知浏览器去访问另外一个web资源进行处理,称之为请求重定向/302
55、Java8链式语法风格:
基本操作:
List<Integer> nums = Arrays.asList(1, 1, null, 2, 3, 4, null, 5, 6, 7, 8, 9, 10);
System.out.println("求和:" + nums.stream()// 转成Stream
.filter(team -> team != null)// 过滤
.distinct()// 去重
.mapToInt(num -> num * 2)// map操作
.skip(2)// 跳过前2个元素
.limit(4)// 限制取前4个元素
.peek(System.out::println)// 流式处理对象函数
.sum());//
将两个列表合并成一个:
List<Integer> lista = Arrays.asList(1, 2, 3);
List<Integer> listb = Arrays.asList(3, 4, 5, 6);
List<List> lists = Arrays.asList(lista, listb);
System.out.println(lists.stream().flatMap(e -> e.stream())// 将两个list合并成一个
.collect(Collectors.toList()));
56、枚举(参考资料:深入理解java enum)
1、原理:对编译后的class文件javap反编译可以看出,定义的枚举类继承自java.lang.Enum抽象类且通过public static final定义了几个常量作为枚举常量。示例:
//定义枚举类型
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
} //对应的完整内容
//反编译Day.class
final class Day extends Enum
{
//编译器为我们添加的静态的values()方法
public static Day[] values()
{
return (Day[])$VALUES.clone();
}
//编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
public static Day valueOf(String s)
{
return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
}
//私有构造函数
private Day(String s, int i)
{
super(s, i);
}
//前面定义的7种枚举实例
public static final Day MONDAY;
public static final Day TUESDAY;
public static final Day WEDNESDAY;
public static final Day THURSDAY;
public static final Day FRIDAY;
public static final Day SATURDAY;
public static final Day SUNDAY;
private static final Day $VALUES[]; static
{
//实例化枚举实例
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
WEDNESDAY = new Day("WEDNESDAY", 2);
THURSDAY = new Day("THURSDAY", 3);
FRIDAY = new Day("FRIDAY", 4);
SATURDAY = new Day("SATURDAY", 5);
SUNDAY = new Day("SUNDAY", 6);
$VALUES = (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
枚举类反编译后的源码
java.lang.Enum抽象类定义了一些方法:
返回类型 | 方法名称 | 方法说明 |
---|---|---|
int |
compareTo(E o) |
比较此枚举与指定对象的顺序 |
boolean |
equals(Object other) |
当指定对象等于此枚举常量时,返回 true。 |
Class<?> |
getDeclaringClass() |
返回与此枚举常量的枚举类型相对应的 Class 对象 |
String |
name() |
返回此枚举常量的名称,在其枚举声明中对其进行声明 |
int |
ordinal() |
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零) |
String |
toString() |
返回枚举常量的名称,它包含在声明中 |
static<T extends Enum<T>> T |
static valueOf(Class<T> enumType, String name) |
返回带指定名称的指定枚举类型的枚举常量。
|
主要源码:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable { private final String name; //枚举字符串名称 public final String name() {
return name;
} private final int ordinal;//枚举顺序值 public final int ordinal() {
return ordinal;
} //枚举的构造方法,只能由编译器调用
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
} public String toString() {
return name;
} public final boolean equals(Object other) {
return this==other;
} //比较的是ordinal值
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;//根据ordinal值比较大小
} @SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
//获取class对象引用,getClass()是Object的方法
Class<?> clazz = getClass();
//获取父类Class对象引用
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
} public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
//enumType.enumConstantDirectory()获取到的是一个map集合,key值就是name值,value则是枚举变量值
//enumConstantDirectory是class对象内部的方法,根据class对象获取一个map集合的值
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
} //.....省略其他没用的方法
}
java.lang.Enum
2、可以把枚举类型当成常规类,即我们可以向枚举类中添加方法和变量。但是枚举常量定义必须在方法定义前面,否则编译报错。示例:
public enum Day2 {
MONDAY("星期一"),
TUESDAY("星期二"),
WEDNESDAY("星期三"),
THURSDAY("星期四"),
FRIDAY("星期五"),
SATURDAY("星期六"),
SUNDAY("星期日");//记住要用分号结束 private String desc;//中文描述 /**
* 私有构造,防止被外部调用
* @param desc
*/
private Day2(String desc){
this.desc=desc;
} /**
* 定义方法,返回描述,跟常规类的定义没区别
* @return
*/
public String getDesc(){
return desc;
} public static void main(String[] args){
for (Day2 day:Day2.values()) {
System.out.println("name:"+day.name()+
",desc:"+day.getDesc());
}
} /**
输出结果:
name:MONDAY,desc:星期一
name:TUESDAY,desc:星期二
name:WEDNESDAY,desc:星期三
name:THURSDAY,desc:星期四
name:FRIDAY,desc:星期五
name:SATURDAY,desc:星期六
name:SUNDAY,desc:星期日
*/
}
枚举类型自定义方法
public enum EnumDemo3 { FIRST{
@Override
public String getInfo() {
return "FIRST TIME";
}
},
SECOND{
@Override
public String getInfo() {
return "SECOND TIME";
}
} ; /**
* 定义抽象方法
* @return
*/
public abstract String getInfo(); //测试
public static void main(String[] args){
System.out.println("F:"+EnumDemo3.FIRST.getInfo());
System.out.println("S:"+EnumDemo3.SECOND.getInfo());
/**
输出结果:
F:FIRST TIME
S:SECOND TIME
*/
}
}
枚举类型中定义抽象方法
3、定义的枚举类型无法被继承(看反编译后的源码可知类被final修饰了)也无法继承其他类(因其已默认继承了Enum类,而Java只允许单继承),但可以实现接口。一个很好的示例:
public enum Meal{
APPETIZER(Food.Appetizer.class),
MAINCOURSE(Food.MainCourse.class),
DESSERT(Food.Dessert.class),
COFFEE(Food.Coffee.class);
private Food[] values;
private Meal(Class<? extends Food> kind) {
//通过class对象获取枚举实例
values = kind.getEnumConstants();
}
public interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
}
}
枚举类实现接口
4、枚举与单例:使用枚举单例的写法,我们完全不用考虑序列化和反射的问题。枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性(也说明了只有Java中只有编译器能创建枚举实例)。
如何确保反序列化时不会破坏单例:根据valueOf(name)得到反序列化后对象,valueOf根据枚举常量名获取对应枚举常量
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
} Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
//getEnumConstantsShared最终通过反射调用枚举类的values方法
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map<String, T> m = new HashMap<>(2 * universe.length);
//map存放了当前enum类的所有枚举实例变量,以name为key值
for (T constant : universe)
m.put(((Enum<?>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}
private volatile transient Map<String, T> enumConstantDirectory = null;
valueOf
如何确保反射不会破坏单例:反射源码里对于枚举类型反射直接抛异常所以反射生成不了枚举类型实例
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
//获取枚举类的构造函数(前面的源码已分析过)
Constructor<SingletonEnum> constructor=SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
//创建枚举
SingletonEnum singleton=constructor.newInstance("otherInstance",9);
} //运行结果
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at zejian.SingletonEnum.main(SingletonEnum.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) //newInstance源码
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
//这里判断Modifier.ENUM是不是枚举修饰符,如果是就抛异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
在单例中,枚举也不是万能的。在android开发中,内存优化是个大块头,而使用枚举时占用的内存常常是静态变量的两倍还多,因此android官方在内存优化方面给出的建议是尽量避免在android中使用enum。
5、EnumMap与EnumSet:见上述参考资料。
前者与HashMap类似,只不过key是Enum类型且不能为null。
后者则采用位向量实现,对于枚举值个数少于64的用一个long来标记(RegularEnumSet)否则用long[ ]来标记(JumboEnumSet)。
57、获取本机的所有IP地址:
public static void getIPs() { Enumeration<NetworkInterface> netInterfaces = null;
try {
netInterfaces = NetworkInterface.getNetworkInterfaces();
while (netInterfaces.hasMoreElements()) {
NetworkInterface ni = netInterfaces.nextElement();
System.out.println("DisplayName:" + ni.getDisplayName());
System.out.println("Name:" + ni.getName());
Enumeration<InetAddress> ips = ni.getInetAddresses();
while (ips.hasMoreElements()) {
System.out.println("IP:" + ips.nextElement().getHostAddress());
}
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
}
}
获取作为服务端的本机的IP,以便其他机子能连:
/** 获取本服务端的非环回IPv4 IP,在机子有很多IP时有可能出错 */
public static String getIPv4() {
String localip = null;// 本地IP,如果没有配置外网IP则返回它
String netip = null;// 外网IP
try {
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
InetAddress ip = null;
boolean finded = false;// 是否找到外网IP
while (netInterfaces.hasMoreElements() && !finded) {
NetworkInterface ni = netInterfaces.nextElement();
Enumeration<InetAddress> addressSet = ni.getInetAddresses();
// System.out.println(String.format("isLoopback:%s, isUp:%s, isVirtual:%s, isPointToPoint:%s",
// ni.isLoopback(), ni.isUp(), ni.isVirtual(), ni.isPointToPoint()));
if (ni.isUp() && !ni.isLoopback()) {
while (addressSet.hasMoreElements()) {
ip = addressSet.nextElement();
// System.out.println(ni.getName() + "; " + ip.getHostAddress() + "; ip.isSiteLocalAddress()="
// + ip.isSiteLocalAddress() + "; ip.isLoopbackAddress()=" + ip.isLoopbackAddress());
if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress()
&& ip.getHostAddress().indexOf(":") == -1) {// 外网IP
netip = ip.getHostAddress();
finded = true;
break;
} else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress()
&& ip.getHostAddress().indexOf(":") == -1) {// 内网IP
localip = ip.getHostAddress();
}
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
if (netip != null && !"".equals(netip)) {
return netip;
} else {
return localip;
}
}
58、通过反射获取对象各字段值:
public static Map<String, Object> copyFildsToMap(Object obj, Field[] fields) {
Map<String, Object> desMap = new HashMap<>();
for (Field field : fields) {
field.setAccessible(true);
try {
desMap.put(field.getName(), field.get(obj));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
return desMap;
} //使用
Student s=new Student();
Map<String,Object> map=copyFieldsToMap(s,s.getClass.getDeclaredFields);
59、Map和Bean间相互转换
/**
* Map --> Bean:利用Introspector、PropertyDescriptor实现。Bean中有public set方法的属性才会被赋值。
*
* @param <T>
*
* @param map
* 待转换者
* @param obj
* Bean实例
*/
public static void transMap2Bean(Map<String, Object> map, Object obj) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();// 有public get或public set方法的属性才会被获取到,此外还一定额外会有个只带有public
// get方法的class属性
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (!key.equals("class") && map.containsKey(key)) {// 会自动有个只有public get方法的class属性
Method setter = property.getWriteMethod();// 获取属性的public set方法,故属性有public set方法才能获取到,否则为null
if (null == setter) {
continue;
}
Object value = map.get(key);
setter.setAccessible(true);
setter.invoke(obj, value);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} /**
* Bean --> Map:利用Introspector、PropertyDescriptor实现。Bean中有public get方法的属性才会被包装到Map。
*
* @param obj
* 指定的对象实例
* @return
*/
public static Map<String, Object> transBean2Map(Object obj) {
if (obj == null) {
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();// 有public get或public set方法的属性才会被获取到,此外还一定额外会有个只带有public
// get方法的class属性 for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (!key.equals("class")) {// 会自动有个只有public get方法的class属性
Method getter = property.getReadMethod();// 获取属性的public get方法,故属性有public get方法才能获取到,否则为null
if (null == getter) {
continue;
}
getter.setAccessible(true);
Object value = getter.invoke(obj);
map.put(key, value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
} /**
* Bean --> Map:利用反射将指定对象的指定属性及值包装到Map。不要求属性一定得有get方法。
*
* @param obj
* 指定的对象实例
* @param fields
* 该对象的属性集合
* @return
*/
public static Map<String, Object> copyFieldsToMap(Object obj, Field[] fields) {
if (null == obj) {
return null;
}
Map<String, Object> desMap = new HashMap<>();
for (Field field : fields) {
field.setAccessible(true);
try {
desMap.put(field.getName(), field.get(obj));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
return desMap;
}
60、zip文件contentType
windows: application/x-zip-compressed
Linux,Mac: application/zip
61、IdentityHashMap与HashMap区别:前者用==比较key是否一样(即是否是同一个对象)、而后者在两个key均为null或equals成立时认为两个key一样。
62、Java数据类型:8中数据类型 、引用类型、数组
String[]names= {"liming"};
System.out.println(names.getClass().getTypeName());//java.lang.String[]
System.out.println(names.getClass().getDeclaredFields().length);//0
System.out.println(names instanceof Object);//true
System.out.println(names instanceof String[]);//true
System.out.println(names instanceof Object[]);//true
数组也是Object的实例,但其是一种特殊的引用类型:之所以说特殊是因为其没有Field,如对于String[] names={"liming"},names.getClass.getDeclaredFields的结果数组中大小为0。
63、JDK的SPI(Service Provider Interface)机制
SPI是JDK自身提供的一种能力,用于发现(实现了某接口的)服务提供者。
我们已经知道,Java多态使得我们可以面向接口编程,这样我们可以以改变很少的业务代码的代价来切换接口的具体实现。SPI更进一步,我们的业务代码可以完全面向接口编程,在程序执行过程动态发现接口的实现类。通常是调用者定义接口(如java.sql.Driver),而服务提供商实现该接口(如com.mysql.jdbc.Driver)
在JDBC4.0之前,通常会用Class.forName("com.mysql.jdbc.Driver")先加载数据库相关的驱动,然后再进行获取连接等的操作;而JDBC4.0之后不需要Class.forName来加载驱动,直接获取连接即可,其就是使用了Java的SPI扩展机制来实现
详情参阅:https://juejin.im/post/5af952fdf265da0b9e652de3
示例:
SPI文件:在META-INF/services/下创建文件,文件名与接口全限定名一样,如: com.mysqi.IMyServiceProvider ;文件中写该接口的具体实现类的全限定名,若有多个则每行一个。
代码:
//IMyServiceProvider interface
package com.myspi;
public interface IMyServiceProvider {
public String getName();
} //Implementation of IMyServiceProvider interface
package com.marchon.learn.spidemo;
import com.myspi.IMyServiceProvider;
public class MyServiceProviderImpl1 implements IMyServiceProvider {
public String getName() {
return "provider1";
}
} //main
package com.marchon.learn.spidemo; import java.util.Iterator;
import java.util.ServiceLoader; import com.myspi.IMyServiceProvider; public class Main {
public static void main(String[] args) {
ServiceLoader<IMyServiceProvider> serviceLoader = ServiceLoader.load(IMyServiceProvider.class);
Iterator<IMyServiceProvider> iterator = serviceLoader.iterator(); while (iterator.hasNext()) {
IMyServiceProvider service = iterator.next();
System.out.println(service.getName() + " " + service.hashCode());
}
}
}
进阶:Spring自动配置的原理
SpringBoot中很多库只要引入Maven依赖库的默认配置就会自动生效(自动成为Bean被注入到IOC容器),即使main方法的componentscan basepath中未包含该库的配置文件路径。其原理与SPI很像。原理:
在库的META-INF/spring.factories里指定此库要被自动生效的配置类的全限定名,Spring的SpringFactoriesLoader会扫描classpath上各库里的spring.factories文件、自动创建文件里指定的配置类对应的实例、将实例注册到IOC容器,从而库的配置会自动生效。
“@EnableAutoConfiguration自动配置的魔法骑士就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。”
实例:
64、关于类型转换
在语法上:
List<Object>对象可以赋给Collection<Object>变量,而Collection<String>不能赋给Collection<Object>变量。即 同类型的子泛型 不能转为同类型的父泛型
Integer[]对象可赋给Object[]变量,而int[]对象不可赋给Object[]对象。即引用类型的数组对象可以赋给Object[]变量,而基本类型的数组对象不能赋给Object[]变量
对于语法上允许的转换,虽然编译器不会报错,但使用者需要确保赋值时类型准确否则运行时会报错。如Number[] numbers=new Integer[3]; numbers[0]=1.2f; 编译正确但运行时会报错。
65、未知类型的数组遍历
对于一个数组ary,其中元素的类型未知,可能是基本类型也可能是对象类型,如何遍历?
容易想到的一个方法是将之视为Object[] objs=(Object[])ary; 然后对objs遍历即可。但此法存在问题:若ary中元素是基本类型,则该赋值会报类型转换错误。
可行的方法:通过Array.getLength(ary)获取数组ary的长度,然后通过Array.get(ary, i)遍历每个元素。
20191125
66、接口的default method
java 8为接口引入了default,一大用途是通过default method为Collection类增加Stream API及Lambda表达式支持。
interface default method模糊了interface和abstrat class之间的区别
interface default method的一个问题:一个类A同时实现两个接口,若两接口都有同样签名的default method,则编译器不能确定A是要继承哪个接口的方法。解决:编译器会报错,要求A一定得重写同名方法
interface default method不能override a method from java.lang.Object,因为Object是一切类的基类,若允许override则会造成混淆。发生这种情况时编译器会报错。
可参阅:https://www.journaldev.com/2752/java-8-interface-changes-static-method-default-method
67、接口的static method
java 8接口也支持static method。与普通的static method类似,其不可被继承。区别在于:接口的实现类的实例无法访问到该接口的静态方法,只能通过接口来访问。而普通的中的静态方法对于子类对象来说是可见的。
此外,与default method类似,接口的default method也不能覆盖Object中的方法。
接口的default方法和static方法的区别?类似于类的实例方法与静态方法的区别:
异:default方法可被继承和覆盖(多default method继承问题:强制重写),而static方法不可(与类的静态方法不同,接口的静态方法对于子类来说是不可见的,只能通过接口去访问,故遑论覆盖了)
同:都不能与Object中的方法的声明一样。
68、为什么说反射慢?
无法被编译器进行代码优化
被创建的类实例或调用的方法需要去查找
参数需都被当成Object或Object[]
需要进行各种检查等
- The compiler can do no optimization whatsoever as it can have no real idea about what you are doing. This probably goes for the
JIT
as well- Everything being invoked/created has to be discovered (i.e. classes looked up by name, methods looked at for matches etc)
- Arguments need to be dressed up via boxing/unboxing, packing into arrays,
Exceptions
wrapped inInvocationTargetException
s and re-thrown etc.- Check that there's a parameterless constructor
- Check the accessibility of the parameterless constructor
- Check that the caller has access to use reflection at all
- Work out (at execution time) how much space needs to be allocated
- Call into the constructor code (because it won't know beforehand that the constructor is empty)
- etc.
可参阅:https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow
69、如何以简单的方式让程序出现OutOfMemory/StackOverFlow?
产生OutOfMemory:创建大量线程
StackOverFlow:方法递归调用自己。如重写toString()方法,在方法内部调用this.toString()
70、关于代码兼容旧版本
通常理解的兼容是语法上的兼容,若要做到向旧代码兼容,则新代码里只能增加新的API而不能改或删掉旧API的语法。然而这其实只是表面,由于代码最终都是转化为二进制代码的,所以其本质是需做到binary compatible (BC) 。
对于动态类型语言,由于是解释执行的,故做到语法兼容确实就能做到binary compatible。
对于强类型语言,则做到语法兼容并不能保证binary compatible。相关可参阅:https://airbrake.io/blog/java-exception-handling/incompatibleclasschangeerror、https://stackoverflow.com/questions/1980452/what-causes-java-lang-incompatibleclasschangeerror
以Java为例:假设一个library里某个类的某方法本来是非static的,且有一个client引用了该方法,现将该方法改为static的且更新client的引用库但client未重新编译,则会出现binary incompatible。
真实场景:项目Expserver直接依赖Auth项目、I18项目,Auth项目也直接依赖I18n项目;将I18n项目中的某个非静态方法改为静态方法;重新编译I18n项目和Expserver项目,则Auth项目中用到I18n项目该方法时会报 java.lang.IncompatibleClassChangeError: Expecting non-static method
71、程序运行时获取主类的权限定名
法1:
//代码
System.err.println(System.getProperty("sun.java.command"));
//结果
com.marchon.test.expserver2c.web.Test
法2:
//代码
public static String getMainClassName() {
StackTraceElement trace[] = Thread.currentThread().getStackTrace();// 背后的原理:return (new Exception()).getStackTrace();
if (trace.length > 0) {
return trace[trace.length - 1].getClassName();
}
return "Unknown";
} //结果
com.marchon.test.expserver2c.web.Test
71、List踩坑记:(更多可参阅:https://mp.weixin.qq.com/s/6gKyb_9cBGNs_GMQcw-VIQ)
代码:
String[] arrays = { "1", "2", "3" };
ArrayList<String> list = new ArrayList<>(Arrays.asList(arrays)); // 不可行方案:foreach迭代时通过list删除。foreach迭代语法糖底层会转为Iterator迭代
for (String str : list) {
if (str.equals("1")) {
list.remove(str);//抛java.util.ConcurrentModificationException in java.util.ArrayList$Itr.checkForComodification
}
} // 可行方案:下标迭代时通过list删除
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("1")) {
list.remove(list.get(i)); // or list.remove(i);
}
}
// 可行方案:Iterator迭代时通过iterator删除
for (Iterator<String> it = list.iterator(); it.hasNext();) {
String val = it.next();
if (val.equals("1")) {
it.remove();
}
}
迭代及删除方案
List迭代:Iterator迭代、foreach迭代(类似 for String str:strList 者)、下标迭代(类似 for i=0;i<list.size();i++ 者)。foreach迭代是个语法糖,会被编译为Iterator迭代。
List迭代时删除:
可行方案:
1. 下标迭代时通过list删除,有删除指定元素和删除指定下标两种。注意如果是list.remove(int i)方法则记得将当前index减1,否则会漏掉一个元素
2. Iterator迭代时通过iterator删除:Iterator#remove
3. JDK1.8起 Collection#removeIf ,内部其实就是上种迭代方式。虽是个语法糖,但代码更简洁故推荐此法。
不可行方案:Iterator迭代时通过list删除、foreach迭代时通过list删除。对ArrayLisst、LinkedList适用,对CopyOnWriteArrayList不适用。
原因:与List的实现有关。有些List的实现在内部会记录插入、删除等操作的次数(modCount变量),且这些List实现的Iterator#next方法内部会先checkForComodification(),即modCount != expectedModCount(iterator初始化时执行会expectedModCount = modCount ),check失败就会抛错。显然,Iterator迭代时通过list删除就会修改modCount从而抛错。
另外,这里的“有些”是指ArrayLisst、LinkedList(还有哪些?),CopyOnWriteArrayList的实现并没有modCount、expectedModCount、Iterator check等约束故不会报错。
java 小记的更多相关文章
- RxAndroid/java小记
Rxandroid 作为一个在设计模式中能把MVP发挥的淋漓尽致的框架不去学习感觉真的对不起自己,然后也学点新东西吧,响应式编程,MVP观察者模式,然后使用RxAndroid使我们自己的代码更加简洁 ...
- Intel项目Java小记
cannot be cast to javax.servlet.Filter添加provided即可 install -X是什么意思? Unsupported major.minor version ...
- Java 小记 — Spring Boot 的实践与思考
前言 本篇随笔用于记录我在学习 Java 和构建 Spring Boot 项目过程中的一些思考,包含架构.组件和部署方式等.下文仅为概要,待闲时逐一整理为详细文档. 1. 组件 开源社区如火如荼,若在 ...
- JAVA小记 (1)
JVM: Java虚拟机 JVM个数取决于同时执行的程序个数 JDK:JAVA 开发工具包 Java利用JVM实行跨平台 JRE:Java运行环境 JavaSE:企业版 GC:垃圾回收机制 命名规范 ...
- Java 小记 - 时间的处理与探究
前言 时间的处理与日期的格式转换几乎是所有应用的基础职能之一,几乎所有的语言都会为其提供基础类库.作为曾经 .NET 的重度使用者,赖其优雅的语法,特别是可扩展方法这个神级特性的存在,我几乎没有特意关 ...
- JAVA小记(一)
java中向上转型.向下转型.内部类中所需注意的问题: 向上转型与向下转型: 举个例子:有2个类,Father是父类,Son类继承自Father. Father f1 = new Son(); / ...
- JAVA小记
关于重写equals()方法和重写toString()方法,一般来说,Objects的默认子类都重写了这两个方法,直接利用就行了: 对于用户自定义的类,如果要用到这两方法,就必须在程序中重写.
- Java 小记 — Spring Boot 注解
前言 本篇随笔将对 Spring Boot 中的常用注解做一个简单的整理归档,写作顺序将从启动类开始并逐步向内外扩展,目的即为了分享也为了方便自己日后的回顾与查阅. 1. Application 启动 ...
- Java 小记 — RabbitMQ 的实践与思考
前言 本篇随笔将汇总一些我对消息队列 RabbitMQ 的认识,顺便谈谈其在高并发和秒杀系统中的具体应用. 1. 预备示例 想了下,还是先抛出一个简单示例,随后再根据其具体应用场景进行扩展,我觉得这样 ...
随机推荐
- CHAP认证原理
整个过程就是PPP协商过程,分三步:LCP.认证.NCP. 一 协议概述 PPP包含以下两个层次的协议: ·链路控制协议(LCP):负责建立.配置和测试数据链路连接 ·网络控制协议(NCP):负责建立 ...
- MSSQL 分组后取每组第一条(group by order by)
查询中经常遇到这种查询,分组后取每组第一条.分享下一个SQL语句: --根据 x 分组后.根据 y 排序后取第一条 select * from ( select ROW_NUMBER() over(p ...
- Arduino智能小车实践学习报告
Arduino智能小车实践学习报告 参与人员: 20135316 王剑桥 20135312 吴汉彦 20135319 朱锂 一. 背景了解: 单片机:将中央处理单元CPU(进行运算.控制).随机存储器 ...
- linux用户管理命令
关键字 useradd passwd who w uptime 1.useradd添加用户命令 useradd 用户名 passwd 用户名 (设置密码) 2.userdel 删除用户 userdel ...
- DOM(八)使用DOM控制表单
1.表单简介 表单<form>是网页中交互最多的形式之一,它通过各种形式接收用户的数据,包括下拉列表框,单选按钮,复选框和文本框,本篇主要介绍表单中常用的属性和方法 javascript中 ...
- 每天一个linux命令(32):wc命令
Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数.字数.行数,并将统计结果显示输出. 1.命令格式: wc [选项]文件... 2.命令功能: 统计指定文件中的字节数. ...
- JSP中<base href="<%=basePath%>">的作用
来源于:http://fanshuyao.iteye.com/blog/2097229 首先了解是什么是<base href=""> <base href=&qu ...
- u1-nav-js
'use strict';define([ 'jquery'], function($) { var nav = { init : function() { $("#burger-menu& ...
- xml_MathML的基本知识点__这东西要自己实践最好
1 : <mi> 一般的字符串 2: <mo> 操作字符串 <mo> ( </mo> <mo>∑</mo> 3:<mn&g ...
- Spring 使用中的设计模式
1. 简单工厂 又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一. 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类. ...