为了更好的总结Java面试中的系统知识结构,本文根据如下资料整理学习笔记。java
在 Java 语言中,当实例化对象时,对象所在类的全部成员变量首先要进行初始化,只有当全部类成员完成初始化后,才会调用对象所在类的构造函数建立象。mysql
初始化通常遵循3个原则:c++
加载顺序git
实例程序员
class Base { // 1.父类静态代码块 static { System.out.println("Base static block!"); } // 3.父类非静态代码块 { System.out.println("Base block"); } // 4.父类构造器 public Base() { System.out.println("Base constructor!"); } } public class Derived extends Base { // 2.子类静态代码块 static{ System.out.println("Derived static block!"); } // 5.子类非静态代码块 { System.out.println("Derived block!"); } // 6.子类构造器 public Derived() { System.out.println("Derived constructor!"); } public static void main(String[] args) { new Derived(); } }
结果是:github
Base static block! Derived static block! Base block Base constructor! Derived block! Derived constructor!
参考资料:web
首先看一个在知乎上的优秀回答吧:面试
反射是什么呢?当咱们的程序在运行时,须要动态的加载一些类这些类可能以前用不到因此不用加载到 JVM,而是在运行时根据须要才加载,这样的好处对于服务器来讲不言而喻。算法
举个例子咱们的项目底层有时是用 mysql,有时用 oracle,须要动态地根据实际状况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection 这两个类咱们要用,这时候咱们的程序就写得比较动态化,经过Class tc = Class.forName("com.java.dbtest.TestConnection"); 经过类的全类名让 JVM 在服务器中找到并加载这个类,而若是是 oracle 则传入的参数就变成另外一个了。这时候就能够看到反射的好处了,这个动态性就体现出 Java 的特性了!
举多个例子,你们若是接触过spring,会发现当你配置各类各样的bean时,是以配置文件的形式配置的,你须要用到哪些bean就配哪些,spring容器就会根据你的需求去动态加载,你的程序就能健壮地运行。
反射 (Reflection) 是 Java 程序开发语言的特征之一,它容许运行中的 Java 程序获取自身的信息,而且能够操做类或对象的内部属性。经过 Class 获取 class 信息称之为反射(Reflection)
简而言之,经过反射,咱们能够在运行时得到程序或程序集中每个类型的成员和成员的信息。
程序中通常的对象的类型都是在编译期就肯定下来的,而 Java 反射机制能够动态地建立对象并调用其属性,这样的对象的类型在编译期是未知的。因此咱们能够经过反射机制直接建立对象,即便这个对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不须要事先(写代码的时候或编译期)知道运行对象是谁。
Java 反射框架主要提供如下功能:
重点:是运行时而不是编译时
不少人都认为反射在实际的Java开发应用中并不普遍,其实否则。
当咱们在使用IDE(如Eclipse,IDEA)时,当咱们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。
反射最重要的用途就是开发各类通用框架
不少框架(好比Spring)都是配置化的(好比经过XML文件配置JavaBean,Action之类的),为了保证框架的通用性,它们可能须要根据配置文件加载不一样的对象或类,调用不一样的方法,这个时候就必须用到反射——运行时动态加载须要加载的对象。
对与框架开发人员来讲,反射虽小但做用很是大,它是各类容器实现的核心。而对于通常的开发者来讲,不深刻框架开发则用反射用的就会少一点,不过了解一下框架的底层机制有助于丰富本身的编程思想,也是颇有益的。
.class
属性Class clazz1 = Person.class; System.out.println(clazz1.getName());
getClass();
Person p = new Person(); Class clazz3 = p.getClass(); System.out.println(clazz3.getName());
forName
静态方法public static Class<?> forName(String className) // 在JDBC开发中经常使用此方法加载数据库驱动: Class.forName(driver);
ClassLoader classLoader = this.getClass().getClassLoader(); Class clazz5 = classLoader.loadClass(className); System.out.println(clazz5.getName());
参考资料:
Annontation 是 Java5 开始引入的新特征,中文名称叫注解。它提供了一种安全的相似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,而且供指定的工具或框架使用。Annontation 像一种修饰符同样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的做用。包含在 java.lang.annotation
包中。
简单来讲:注解其实就是代码中的特殊标记,这些标记能够在编译、类加载、运行时被读取,并执行相对应的处理。
传统的方式,咱们是经过配置文件 .xml
来告诉类是如何运行的。
有了注解技术之后,咱们就能够经过注解告诉类如何运行
例如:咱们之前编写 Servlet 的时候,须要在 web.xml 文件配置具体的信息。咱们使用了注解之后,能够直接在 Servlet 源代码上,增长注解...Servlet 就被配置到 Tomcat 上了。也就是说,注解能够给类、方法上注入信息。
明显地能够看出,这样是很是直观的,而且 Servlet 规范是推崇这种配置方式的。
在 java.lang 包下存在着5个基本的 Annotation,重点掌握前三个。
@Deprecated public String toLocaleString() { DateFormat formatter = DateFormat.getDateTimeInstance(); return formatter.format(this); }
import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 水果名称注解 */ @Target(FIELD) @Retention(RUNTIME) @Documented public @interface FruitName { String value() default ""; }
通俗的讲,泛型就是操做类型的 占位符,即:假设占位符为 T,那么这次声明的数据结构操做的数据类型为T类型。
假定咱们有这样一个需求:写一个排序方法,可以对整型数组、字符串数组甚至其余任何类型的数组进行排序,该如何实现?答案是能够使用 Java 泛型。
使用 Java 泛型的概念,咱们能够写一个泛型方法来对一个对象数组排序。而后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。
你能够写一个泛型方法,该方法在调用时能够接收不一样类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每个方法调用。
下面是定义泛型方法的规则:
public class GenericMethodTest { // 泛型方法 printArray public static < E > void printArray( E[] inputArray ) { // 输出数组元素 for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println(); } public static void main( String args[] ) { // 建立不一样类型数组: Integer, Double 和 Character Integer[] intArray = { 1, 2, 3, 4, 5 }; Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 }; Character[] charArray = { 'H', 'E', 'L', 'L', 'O' }; System.out.println( "整型数组元素为:" ); printArray( intArray ); // 传递一个整型数组 System.out.println( "\n双精度型数组元素为:" ); printArray( doubleArray ); // 传递一个双精度型数组 System.out.println( "\n字符型数组元素为:" ); printArray( charArray ); // 传递一个字符型数组 } }
泛型类的声明和非泛型类的声明相似,除了在类名后面添加了类型参数声明部分。
和泛型方法同样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。由于他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
public class Box<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Box<Integer> integerBox = new Box<Integer>(); Box<String> stringBox = new Box<String>(); integerBox.add(new Integer(10)); stringBox.add(new String("菜鸟教程")); System.out.printf("整型值为 :%d\n\n", integerBox.get()); System.out.printf("字符串为 :%s\n", stringBox.get()); } }
?
代替具体的类型参数。例如 List<?>
在逻辑上是 List<String>
,List<Integer>
等全部 List <具体类型实参>
的父类。参考资料:
理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,咱们在此作一下区分:
类型 | 概念描述 | 举例 |
---|---|---|
字符 | 人们使用的记号,抽象意义上的一个符号。 | '1', '中', 'a', '$', '¥', …… |
字节 | 计算机中存储数据的单元,一个 8 位的二进制数,是一个很具体的存储空间。 | 0x01, 0x45, 0xFA, …… |
ANSI 字符串 | 在内存中,若是“字符”是以 ANSI 编码形式存在的,一个字符可能使用一个字节或多个字节来表示,那么咱们称这种字符串为 ANSI 字符串或者多字节字符串。 | "中文123" (占7字节) |
UNICODE 字符串 | 在内存中,若是“字符”是以在 UNICODE 中的序号存在的,那么咱们称这种字符串为 UNICODE 字符串或者宽字节字符串。 | L"中文123" (占10字节) |
字节与字符区别
它们彻底不是一个位面的概念,因此二者之间没有“区别”这个说法。不一样编码里,字符和字节的对应关系不一样:
类型 | 概念描述 |
---|---|
ASCII | 一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。一个二进制数字序列,在计算机中做为一个数字单元,通常为8位二进制数,换算为十进制。最小值0,最大值255。 |
UTF-8 | 一个英文字符等于一个字节,一个中文(含繁体)等于三个字节 |
Unicode | 一个英文等于两个字节,一个中文(含繁体)等于两个字节。符号:英文标点占一个字节,中文标点占两个字节。举例:英文句号“.”占1个字节的大小,中文句号“。”占2个字节的大小。 |
UTF-16 | 一个英文字母字符或一个汉字字符存储都须要2个字节(Unicode扩展区的一些汉字存储须要4个字节) |
UTF-32 | 世界上任何字符的存储都须要4个字节 |
参考资料:
Java面向对象的基本思想之一是封装细节而且公开接口。Java语言采用访问控制修饰符来控制类及类的方法和变量的访问权限,从而向使用者暴露接口,但隐藏实现细节。访问控制分为四种级别:
修饰符 | 当前类 | 同 包 | 子 类 | 其余包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
参考资料:
跟上Java8 - 了解lambda
https://zhuanlan.zhihu.com/p/28093333
Java 中字符串对象建立有两种形式,一种为字面量形式,如 String str = "droid";
,另外一种就是使用 new 这种标准的构造对象的方法,如 String str = new String("droid");
,这两种方式咱们在代码编写时都常用,尤为是字面量的方式。然而这两种实现其实存在着一些性能和内存占用的差异。这一切都是源于 JVM 为了减小字符串对象的重复建立,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。
工做原理
当代码中出现字面量形式建立字符串对象时,JVM首先会对这个字面量进行检查,若是字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,不然新的字符串对象被建立,而后将这个引用放入字符串常量池,并返回该引用。
public class Test { public static void main(String[] args) { String s1 = "abc"; String s2 = "abc"; // 以上两个局部变量都存在了常量池中 System.out.println(s1 == s2); // true // new出来的对象不会放到常量池中,内存地址是不一样的 String s3 = new String(); String s4 = new String(); /** * 字符串的比较不能够使用双等号,这样会比较内存地址 * 字符串比较应当用equals,可见String重写了equals */ System.out.println(s3 == s4); // false System.out.println(s3.equals(s4)); // true } }
能够将一个类的定义放在另外一个类的定义内部,这就是内部类。
在 Java 中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类
成员内部类也是最普通的内部类,它是外围类的一个成员,因此他是能够无限制的访问外围类的全部成员属性和方法,尽管是private的,可是外围类要访问内部类的成员属性和方法则须要经过内部类实例来访问。
public class OuterClass { private String str; public void outerDisplay(){ System.out.println("outerClass..."); } public class InnerClass{ public void innerDisplay(){ str = "chenssy..."; //使用外围内的属性 System.out.println(str); outerDisplay(); //使用外围内的方法 } } // 推荐使用getxxx()来获取成员内部类,尤为是该内部类的构造函数无参数时 public InnerClass getInnerClass(){ return new InnerClass(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); OuterClass.InnerClass inner = outer.getInnerClass(); inner.innerDisplay(); } } -------------------- chenssy... outerClass...
在成员内部类中要注意两点:
有这样一种内部类,它是嵌套在方法和做用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想建立一个类来辅助咱们的解决方案,到那时又不但愿这个类是公共可用的,因此就产生了局部内部类,局部内部类和成员内部类同样被编译,只是它的做用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。
//定义在方法里: public class Parcel5 { public Destionation destionation(String str){ class PDestionation implements Destionation{ private String label; private PDestionation(String whereTo){ label = whereTo; } public String readLabel(){ return label; } } return new PDestionation(str); } public static void main(String[] args) { Parcel5 parcel5 = new Parcel5(); Destionation d = parcel5.destionation("chenssy"); } } //定义在做用域内: public class Parcel6 { private void internalTracking(boolean b){ if(b){ class TrackingSlip{ private String id; TrackingSlip(String s) { id = s; } String getSlip(){ return id; } } TrackingSlip ts = new TrackingSlip("chenssy"); String string = ts.getSlip(); } } public void track(){ internalTracking(true); } public static void main(String[] args) { Parcel6 parcel6 = new Parcel6(); parcel6.track(); } }
匿名内部类也就是没有名字的内部类。正由于没有名字,因此匿名内部类只能使用一次,它一般用来简化代码编写。但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
实例1:不使用匿名内部类来实现抽象方法
abstract class Person { public abstract void eat(); } class Child extends Person { public void eat() { System.out.println("eat something"); } } public class Demo { public static void main(String[] args) { Person p = new Child(); p.eat(); } }
运行结果:eat something
能够看到,咱们用 Child 继承了 Person 类,而后实现了 Child 的一个实例,将其向上转型为 Person 类的引用
可是,若是此处的 Child 类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?
这个时候就引入了匿名内部类
实例2:匿名内部类的基本实现
abstract class Person { public abstract void eat(); } public class Demo { public static void main(String[] args) { Person p = new Person() { public void eat() { System.out.println("eat something"); } }; p.eat(); } }
运行结果:eat something
能够看到,咱们直接将抽象类 Person 中的方法在大括号中实现了,这样即可以省略一个类的书写,而且,匿名内部类还能用于接口上。
实例3:在接口上使用匿名内部类
interface Person { public void eat(); } public class Demo { public static void main(String[] args) { Person p = new Person() { public void eat() { System.out.println("eat something"); } }; p.eat(); } }
运行结果:eat something
由上面的例子能够看出,只要一个类是抽象的或是一个接口,那么其子类中的方法均可以使用匿名内部类来实现
最经常使用的状况就是在多线程的实现上,由于要实现多线程必须继承 Thread 类或是继承 Runnable 接口
实例4:Thread类的匿名内部类实现
public class Demo { public static void main(String[] args) { Thread t = new Thread() { public void run() { for (int i = 1; i <= 5; i++) { System.out.print(i + " "); } } }; t.start(); } }
运行结果:1 2 3 4 5
实例5:Runnable接口的匿名内部类实现
public class Demo { public static void main(String[] args) { Runnable r = new Runnable() { public void run() { for (int i = 1; i <= 5; i++) { System.out.print(i + " "); } } }; Thread t = new Thread(r); t.start(); } }
运行结果:1 2 3 4 5
关键字 static 中提到 static 能够修饰成员变量、方法、代码块,其余它还能够修饰内部类,使用 static 修饰的内部类咱们称之为静态内部类,不过咱们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,咱们知道非静态内部类在编译完成以后会隐含地保存着一个引用,该引用是指向建立它的外围内,可是静态内部类却没有。
public class OuterClass { private String sex; public static String name = "chenssy"; // 静态内部类 static class InnerClass1{ // 在静态内部类中能够存在静态成员 public static String _name1 = "chenssy_static"; public void display(){ // 静态内部类只能访问外围类的静态成员变量和方法 // 不能访问外围类的非静态成员变量和方法 System.out.println("OutClass name :" + name); } } // 非静态内部类 class InnerClass2{ // 非静态内部类中不能存在静态成员 public String _name2 = "chenssy_inner"; // 非静态内部类中能够调用外围类的任何成员,无论是静态的仍是非静态的 public void display(){ System.out.println("OuterClass name:" + name); } } // 外围类方法 public void display(){ // 外围类访问静态内部类:内部类 System.out.println(InnerClass1._name1); // 静态内部类 能够直接建立实例不须要依赖于外围类 new InnerClass1().display(); // 非静态内部的建立须要依赖于外围类 OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2(); // 方位非静态内部类的成员须要使用非静态内部类的实例 System.out.println(inner2._name2); inner2.display(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); outer.display(); } } ---------------- Output: chenssy_static OutClass name :chenssy chenssy_inner OuterClass name:chenssy
组合:各部件之间没什么关系,只须要组合便可。例如组装电脑,须要 new CPU(),new RAM(),new Disk()
public class Computer { public Computer() { CPU cpu=new CPU(); RAM ram=new RAM(); Disk disk=new Disk(); } } class CPU{ } class RAM{ } class Disk{ }
继承:子类须要具备父类的功能,各子类之间有所差别。例如 Shape 类做为父类,子类有 Rectangle,CirCle,Triangle……代码不写了,你们都常常用。
代理:飞机控制类,我不想暴露太多飞机控制的功能,只需部分前进左右转的控制(而不须要暴露发射导弹功能)。经过在代理类中 new 一个飞机控制对象,而后在方法中添加飞机控制类的各个须要暴露的功能。
public class PlaneDelegation{ private PlaneControl planeControl; //private外部不可访问 // 飞行员权限代理类,普通飞行员不能够开火 PlaneDelegation(){ planeControl = new PlaneControl(); } public void speed(){ planeControl.speed(); } public void left(){ planeControl.left(); } public void right(){ planeControl.right(); } } final class PlaneControl {// final表示不可继承,控制器都能继承那还得了 protected void speed() {} protected void fire() {} protected void left() {} protected void right() {} }
说明:
构造函数是函数的一种特殊形式。特殊在哪里?构造函数中不须要定义返回类型(void 是无需返回值的意思,请注意区分二者),且构造函数的名称与所在的类名彻底一致,其他的与函数的特性相同,能够带有参数列表,能够存在函数的重载现象。
通常用来初始化一些成员变量,当要生成一个类的对象(实例)的时候就会调用类的构造函数。若是不显示声明类的构造方法,会自动生成一个默认的不带参数的空的构造函数。
public class Demo{ private int num=0; //无参构造函数 Demo() { System.out.println("constractor_run"); } //有参构造函数 Demo(int num) { System.out.println("constractor_args_run"); } //普通成员函数 public void demoFunction() { System.out.println("function_run"); } }
在这里要说明一点,若是在类中咱们不声明构造函数,JVM 会帮咱们默认生成一个空参数的构造函数;若是在类中咱们声明了带参数列表的构造函数,JVM 就不会帮咱们默认生成一个空参数的构造函数,咱们想要使用空参数的构造函数就必须本身去显式的声明一个空参的构造函数。
构造函数的做用
经过开头的介绍,构造函数的轮廓已经渐渐清晰,那么为何会有构造函数呢?构造函数有什么做用?构造函数是面向对象编程思想所需求的,它的主要做用有如下两个:
父类引用能指向子类对象,子类引用不能指向父类对象;
向上造型
父类引用指向子类对象,例如:
Father f1 = new Son();
向下造型
把指向子类对象的父类引用赋给子类引用,须要强制转换,例如:
Father f1 = new Son(); Son s1 = (Son)f1;
但有运行出错的状况:
Father f2 = new Father(); Son s2 = (Son)f2; //编译无错但运行会出现错误
在不肯定父类引用是否指向子类对象时,能够用 instanceof 来判断:
if(f3 instanceof Son){ Son s3 = (Son)f3; }
final int x = 1; // x = 2; // cannot assign value to final variable 'x' final A y = new A(); y.a = 1;
1. 静态变量
静态变量在内存中只存在一份,只在类初始化时赋值一次。
public class A { private int x; // 实例变量 public static int y; // 静态变量 }
注意:不能再成员函数内部定义static变量。
2. 静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例,因此静态方法必须有实现,也就是说它不能是抽象方法(abstract)。
3. 静态语句块
静态语句块在类初始化时运行一次。
4. 静态内部类
内部类的一种,静态内部类不依赖外部类,且不能访问外部类的非静态的变量和方法。
5. 静态导包
import static com.xxx.ClassName.*
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大下降。
6. 变量赋值顺序
静态变量的赋值和静态语句块的运行优先于实例变量的赋值和普通语句块的运行,静态变量的赋值和静态语句块的运行哪一个先执行取决于它们在代码中的顺序。
public static String staticField = "静态变量";
static { System.out.println("静态语句块"); }
public String field = "实例变量";
{ System.out.println("普通语句块"); }
最后才运行构造函数
public InitialOrderTest() { System.out.println("构造函数"); }
存在继承的状况下,初始化顺序为:
跳出当前循环;可是若是是嵌套循环,则只能跳出当前的这一层循环,只有逐层 break 才能跳出全部循环。
for (int i = 0; i < 10; i++) { // 在执行i==6时强制终止循环,i==6不会被执行 if (i == 6) break; System.out.println(i); } 输出结果为0 1 2 3 4 5 ;6之后的都不会输出
终止当前循环,可是不跳出循环(在循环中 continue 后面的语句是不会执行了),继续往下根据循环条件执行循环。
for (int i = 0; i < 10; i++) { // i==6不会被执行,而是被中断了 if (i == 6) continue; System.out.println(i); } 输出结果为0 1 2 3 4 5 7 8 9; 只有6没有输出
特别注意:返回值为 void 的方法,从某个判断中跳出,必须用 return。
final 用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖和类不可被继承。
在异常处理的时候,提供 finally 块来执行任何的清除操做。若是抛出一个异常,那么相匹配的 catch 字句就会执行,而后控制就会进入 finally 块,前提是有 finally 块。例如:数据库链接关闭操做上
finally 做为异常处理的一部分,它只能用在 try/catch 语句中,而且附带一个语句块,表示这段语句最终必定会被执行(无论有没有抛出异常),常常被用在须要释放资源的状况下。(×)(这句话其实存在必定的问题,尚未深刻了解,欢迎你们在 issue 中提出本身的看法)
finalize() 是 Object 中的方法,当垃圾回收器将要回收对象所占内存以前被调用,即当一个对象被虚拟机宣告死亡时会先调用它 finalize() 方法,让此对象处理它生前的最后事情(这个对象能够趁这个时机挣脱死亡的命运)。要明白这个问题,先看一下虚拟机是如何判断一个对象该死的。
能够覆盖此方法来实现对其余资源的回收,例如关闭文件。
Java 采用可达性分析算法来断定一个对象是否死期已到。Java中以一系列 "GC Roots" 对象做为起点,若是一个对象的引用链能够最终追溯到 "GC Roots" 对象,那就天下太平。
不然若是只是A对象引用B,B对象又引用A,A B引用链均未能达到 "GC Roots" 的话,那它俩将会被虚拟机宣判符合死亡条件,具备被垃圾回收器回收的资格。
上面提到了判断死亡的依据,但被判断死亡后,还有生还的机会。
如何自我救赎:
须要注意:
finalize() 只会在对象内存回收前被调用一次 (The finalize method is never invoked more than once by a Java virtual machine for any given object. )
finalize() 的调用具备不肯定性,只保证方法会调用,但不保证方法里的任务会被执行完(好比一个对象手脚不够利索,磨磨叽叽,还在自救的过程当中,被杀死回收了)。
虽然以上以对象救赎举例,但 finalize() 的做用每每被认为是用来作最后的资源回收。
基于在自我救赎中的表现来看,此方法有很大的不肯定性(不保证方法中的任务执行完)并且运行代价较高。因此用来回收资源也不会有什么好的表现。
综上:finalize() 方法并无什么鸟用。
至于为何会存在一个鸡肋的方法:书中说 “它不是 C/C++ 中的析构函数,而是 Java 刚诞生时为了使 C/C++ 程序员更容易接受它所作出的一个妥协”。
参考资料:
断言(assert)做为一种软件调试的方法,提供了一种在代码中进行正确性检查的机制,目前不少开发语言都支持这种机制。
在实现中,assertion 就是在程序中的一条语句,它对一个 boolean 表达式进行检查,一个正确程序必须保证这个 boolean 表达式的值为 true;若是该值为 false,说明程序已经处于不正确的状态下,系统将给出警告而且退出。通常来讲,assertion 用于保证程序最基本、关键的正确性。assertion 检查一般在开发和测试时开启。为了提升性能,在软件发布后,assertion 检查一般是关闭的。下面简单介绍一下 Java 中 assertion 的实现。
在语法上,为了支持 assertion,Java 增长了一个关键字 assert。它包括两种表达式,分别以下:
assert
若是
若是为 false,则程序抛出 AssertionError,并终止执行。
assert
若是
若是为 false,则程序抛出 java.lang.AssertionError,并输入 <错误信息表达式> 。
public static void main(String[] args) { System.out.println("123"); int a = 0; int b = 1; assert a == b; //需显示开启,默认为不开启状态 assert a == b : "执行失败!"; System.out.println("1234"); }
assert 的应用范围不少,主要包括:
每次都读错,美式发音:volatile /'vɑlətl/ adj. [化学] 挥发性的;不稳定的;爆炸性的;反复无常的
volatile 是一个类型修饰符(type specifier),它是被设计用来修饰被不一样线程访问和修改的变量。在使用 volatile 修饰成员变量后,全部线程在任什么时候间所看到变量的值都是相同的。此外,使用 volatile 会组织编译器对代码的优化,所以会下降程序的执行效率。因此,除非无可奈何,不然,能不使用 volatile 就尽可能不要使用 volatile。
参考资料:
instanceof 是 Java 的一个二元操做符,相似于 ==,>,< 等操做符。
instanceof 是 Java 的保留关键字。它的做用是测试它左边的对象是不是它右边的类的实例,返回 boolean 的数据类型。
public class Main { public static void main(String[] args) { Object testObject = new ArrayList(); displayObjectClass(testObject); } public static void displayObjectClass(Object o) { if (o instanceof Vector) System.out.println("对象是 java.util.Vector 类的实例"); else if (o instanceof ArrayList) System.out.println("对象是 java.util.ArrayList 类的实例"); else System.out.println("对象是 " + o.getClass() + " 类的实例"); } }
strictfp,即 strict float point (精确浮点)。
strictfp 关键字可应用于类、接口或方法。使用 strictfp 关键字声明一个方法时,该方法中全部的 float 和 double 表达式都严格遵照 FP-strict 的限制,符合 IEEE-754 规范。当对一个类或接口使用 strictfp 关键字时,该类中的全部代码,包括嵌套类型中的初始设定值和代码,都将严格地进行计算。严格约束意味着全部表达式的结果都必须是 IEEE 754 算法对操做数预期的结果,以单精度和双精度格式表示。
若是你想让你的浮点运算更加精确,并且不会由于不一样的硬件平台所执行的结果不一致的话,能够用关键字strictfp.
transient 英 /'trænzɪənt/ adj. 短暂的;路过的 n. 瞬变现象;过往旅客;候鸟
咱们都知道一个对象只要实现了 Serilizable 接口,这个对象就能够被序列化,Java 的这种序列化模式为开发者提供了不少便利,咱们能够没必要关系具体序列化的过程,只要这个类实现了 Serilizable 接口,这个类的全部属性和方法都会自动序列化。
然而在实际开发过程当中,咱们经常会遇到这样的问题,这个类的有些属性须要序列化,而其余属性不须要被序列化,打个比方,若是一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不但愿在网络操做(主要涉及到序列化操做,本地序列化缓存也适用)中被传输,这些信息对应的变量就能够加上 transient 关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
总之,Java 的 transient 关键字为咱们提供了便利,你只须要实现 Serilizable 接口,将不须要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
参考资料:
native(即 JNI,Java Native Interface),凡是一种语言,都但愿是纯。好比解决某一个方案都喜欢就单单这个语言来写便可。Java 平台有个用户和本地 C 代码进行互操做的 API,称为 Java Native Interface (Java本地接口)。
参考资料:
类型 | 存储 | 取值范围 | 默认值 | 包装类 |
---|---|---|---|---|
整数型 | ||||
byte | 8 | 最大存储数据量是 255,最小 -27,最大 27-1, [-128~127] |
(byte) 0 | Byte |
short | 16 | 最大数据存储量是 65536,[-215,215-1], [-32768,32767],±3万 |
(short) 0 | Short |
int | 32 | 最大数据存储容量是 231-1, [-231,231-1],±21亿,[ -2147483648, 2147483647] |
0 | Integer |
long | 64 | 最大数据存储容量是 264-1, [-263,263-1], ±922亿亿(±(922+16个零)) |
0L | Long |
浮点型 | ||||
float | 32 | 数据范围在 3.4e-45~1.4e38,直接赋值时必须在数字后加上 f 或 F | 0.0f | Float |
double | 64 | 数据范围在 4.9e-324~1.8e308,赋值时能够加 d 或 D 也能够不加 | 0.0d | Double |
布尔型 | ||||
boolean | 1 | true / flase | false | Boolean |
字符型 | ||||
char | 16 | 存储 Unicode 码,用单引号赋值 | '\u0000' (null) | Character |
jdk5.0
提供的新特特性,它能够自动实现类型的转换// jdk 1.5 public class TestDemo { public static void main(String[] args) { Integer m =10; int i = m; } }
上面的代码在 jdk1.4 之后的版本都不会报错,它实现了自动拆装箱的功能,若是是 jdk1.4,就得这样写了
// jdk 1.4 public class TestDemo { public static void main(String[] args) { Integer b = new Integer(210); int c = b.intValue(); } }
new Integer(123) 与 Integer.valueOf(123) 的区别在于,new Integer(123) 每次都会新建一个对象,而 Integer.valueOf(123) 可能会使用缓存对象,所以屡次使用 Integer.valueOf(123) 会取得同一个对象的引用。
Integer x = new Integer(123); Integer y = new Integer(123); System.out.println(x == y); // false Integer z = Integer.valueOf(123); Integer k = Integer.valueOf(123); System.out.println(z == k); // true
编译器会在自动装箱过程调用 valueOf() 方法,所以多个 Integer 实例使用自动装箱来建立而且值相同,那么就会引用相同的对象。
Integer m = 123; Integer n = 123; System.out.println(m == n); // true
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,若是在的话就直接使用缓存池的内容。
// valueOf 源码实现 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
在 Java 8 中,Integer 缓存池的大小默认为 -128~127。
static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; }
Java 还将一些其它基本类型的值放在缓冲池中,包含如下这些:
所以在使用这些基本类型对应的包装类型时,就能够直接使用缓冲池中的对象。
参考资料:
i++ 是在程序执行完毕后进行自增,而 ++i 是在程序开始执行前进行自增。
i++ 的操做分三步
三个阶段:内存到寄存器,寄存器自增,写回内存(这三个阶段中间均可以被中断分离开)
因此 i++ 不是原子操做,上面的三个步骤中任何一个步骤同时操做,均可能致使 i 的值不正确自增
在多核的机器上,CPU 在读取内存 i 时也会可能发生同时读取到同一值,这就致使两次自增,实际只增长了一次。
i++ 和 ++i 都不是原子操做
原子性:指的是一个操做是不可中断的。即便是在多个线程一块儿执行的时候,一个操做一旦开始,就不会被其余线程打断。
JMM 三大特性:原子性,可见性,有序性。详情请阅读 Github 仓库:Java 并发编程 一文。
Java 定义了位运算符,应用于整数类型 (int),长整型 (long),短整型 (short),字符型 (char),和字节型 (byte)等类型。
下表列出了位运算符的基本运算,假设整数变量A的值为60和变量B的值为13
A(60):0011 1100
B(13):0000 1101
操做符 | 名称 | 描述 | 例子 |
---|---|---|---|
& | 与 | 若是相对应位都是 1,则结果为 1,不然为 0 | (A&B)获得 12,即 0000 1100 |
| | 或 | 若是相对应位都是 0,则结果为 0,不然为 1 | (A|B)获得 61,即 0011 1101 |
^ | 异或 | 若是相对应位值相同,则结果为 0,不然为 1 | (A^B)获得49,即 0011 0001 |
〜 | 非 | 按位取反运算符翻转操做数的每一位,即 0 变成 1,1 变成 0 | (〜A)获得-61,即1100 0011 |
<< | 左移 | (左移一位乘2)按位左移运算符。左操做数按位左移右操做数指定的位数。左移 n 位表示原来的值乘 2n | A << 2获得240,即 1111 0000 |
>> | (右移一位除2)有符号右移,按位右移运算符。左操做数按位右移右操做数指定的位数 | A >> 2获得15即 1111 | |
>>> | 无符号右移 | 无符号右移,按位右移补零操做符。左操做数的值按右操做数指定的位数右移,移动获得的空位以零填充 | A>>>2获得15即0000 1111 |
一个数在计算机中的二进制表示形式,叫作这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号,正数为 0,负数为 1。
好比,十进制中的数 +3 ,计算机字长为 8 位,转换成二进制就是 00000011。若是是 -3 ,就是 10000011 。那么,这里的 00000011 和 10000011 就是机器数。
由于第一位是符号位,因此机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位 1 表明负,其真正数值是 -3 而不是形式值 131(10000011 转换成十进制等于 131)。因此,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。
例:0000 0001 的真值 = +000 0001 = +1,1000 0001 的真值 = –000 0001 = –1
原码就是符号位加上真值的绝对值,即用第一位表示符号,其他位表示值。好比若是是 8 位二进制:
[+1]原 = 0000 0001
[-1]原 = 1000 0001
第一位是符号位。由于第一位是符号位,因此 8 位二进制数的取值范围就是:[1111 1111 , 0111 1111],即:[-127 , 127]
原码是人脑最容易理解和计算的表示方式
反码的表示方法是:
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原= [11111110]反
可见若是一个反码表示的是负数, 人脑没法直观的看出来它的数值. 一般要将其转换成原码再计算。
补码的表示方法是:
[+1] = [0000 0001]原 = [0000 0001]反 = [0000 0001]补
[-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]补
对于负数,补码表示方式也是人脑没法直观看出其数值的。 一般也须要转换成原码在计算其数值。
参考资料:
若是给定整数 a 和 b,用如下三行代码便可交换 a 和b 的值
a = a ^ b; b = a ^ b; a = a ^ b;
说明:位运算的题目基本上都带有靠经验积累才会作的特征,也就是准备阶段须要作足够多的题,面试时才会有良好的感受。
#include <stdio.h> int add(int a, int b) { int c = a & b; int r = a ^ b; if(c == 0){ return r; } else{ return add(r, c << 1); } } int main(int argn, char *argv[]) { printf("sum = %d\n", add(-10000, 56789)); return 0; }
(1)&& 和 & 都是表示与,区别是 && 只要第一个条件不知足,后面条件就再也不判断。而 & 要对全部的条件都进行判断。
// 例如: public static void main(String[] args) { if((23!=23) && (100/0==0)){ System.out.println("运算没有问题。"); }else{ System.out.println("没有报错"); } } // 输出的是“没有报错”。而将 && 改成 & 就会以下错误: // Exception in thread "main" java.lang.ArithmeticException: / by zero
public static void main(String[] args) { if((23==23)||(100/0==0)){ System.out.println("运算没有问题。"); }else{ System.out.println("没有报错"); } } // 此时输出“运算没有问题”。若将||改成|则会报错。
在编程语言中,字面量(literal)指的是在源代码中直接表示的一个固定的值。
八进制是用在整数字面量以前添加 “0” 来表示的。
十六进制用在整数字面量以前添加 “0x” 或者 “0X” 来表示的
Java 7 中新增了二进制:用在整数字面量以前添加 “0b” 或者 “0B” 来表示的。
在数值字面量中使用下划线
在 Java7 中,数值字面量,无论是整数仍是浮点数都容许在数字之间插入任意多个下划线。而且不会对数值产生影响,目的是方便阅读,规则只能在数字之间使用。
public class BinaryIntegralLiteral { public static void main(String[] args) { System.out.println(0b010101); System.out.println(0B010101); System.out.println(0x15A); System.out.println(0X15A); System.out.println(077); System.out.println(5_000); /** * 输出结果 * 21 * 21 * 346 * 346 * 63 * 5000 */ } }
如下为 Object 中的通用方法
public final native Class<?> getClass() public native int hashCode() public boolean equals(Object obj) protected native Object clone() throws CloneNotSupportedException public String toString() public final native void notify() public final native void notifyAll() public final native void wait(long timeout) throws InterruptedException public final void wait(long timeout, int nanos) throws InterruptedException public final void wait() throws InterruptedException protected void finalize() throws Throwable {} // JVM内存回收之finalize()方法
1. equals() 与 == 的区别
Integer x = new Integer(1); Integer y = new Integer(1); System.out.println(x.equals(y)); // true System.out.println(x == y); // false
2. 等价关系
(一)自反性
x.equals(x); // true
(二)对称性
x.equals(y) == y.equals(x); // true
(三)传递性
if (x.equals(y) && y.equals(z)) x.equals(z); // true;
(四)一致性
屡次调用 equals() 方法结果不变
x.equals(y) == x.equals(y); // true
(五)与 null 的比较
对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
x.euqals(null); // false;
3. 实现
public class EqualExample { private int x; private int y; private int z; public EqualExample(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EqualExample that = (EqualExample) o; if (x != that.x) return false; if (y != that.y) return false; return z == that.z; } }
hasCode() 返回散列值,而 equals() 是用来判断两个实例是否等价。等价的两个实例散列值必定要相同,可是散列值相同的两个实例不必定等价。
在覆盖 equals() 方法时应当老是覆盖 hashCode() 方法,保证等价的两个实例散列值也相等。
下面的代码中,新建了两个等价的实例,并将它们添加到 HashSet 中。咱们但愿将这两个实例当成同样的,只在集合中添加一个实例,可是由于 EqualExample 没有实现 hasCode() 方法,所以这两个实例的散列值是不一样的,最终致使集合添加了两个等价的实例。
EqualExample e1 = new EqualExample(1, 1, 1); EqualExample e2 = new EqualExample(1, 1, 1); System.out.println(e1.equals(e2)); // true HashSet<EqualExample> set = new HashSet<>(); set.add(e1); set.add(e2); System.out.println(set.size()); // 2
理想的散列函数应当具备均匀性,即不相等的实例应当均匀分布到全部可能的散列值上。这就要求了散列函数要把全部域的值都考虑进来,能够将每一个域都当成 R 进制的某一位,而后组成一个 R 进制的整数。R 通常取 31,由于它是一个奇素数,若是是偶数的话,当出现乘法溢出,信息就会丢失,由于与 2 相乘至关于向左移一位。
一个数与 31 相乘能够转换成移位和减法:31\*x == (x<<5)-x
,编译器会自动进行这个优化。
@Override public int hashCode() { int result = 17; result = 31 * result + x; result = 31 * result + y; result = 31 * result + z; return result; }
默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
public class ToStringExample { private int number; public ToStringExample(int number) { this.number = number; } }
ToStringExample example = new ToStringExample(123); System.out.println(example.toString());
ToStringExample@4554617c
1. cloneable
clone() 是 Object 的 protect 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
public class CloneExample { private int a; private int b; }
CloneExample e1 = new CloneExample(); // CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'
重写 clone() 获得如下实现:
public class CloneExample { private int a; private int b; @Override protected CloneExample clone() throws CloneNotSupportedException { return (CloneExample)super.clone(); } }
CloneExample e1 = new CloneExample(); try { CloneExample e2 = e1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
java.lang.CloneNotSupportedException: CloneTest
以上抛出了 CloneNotSupportedException,这是由于 CloneTest 没有实现 Cloneable 接口。
public class CloneExample implements Cloneable { private int a; private int b; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
应该注意的是,clone() 方法并非 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,若是一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
参考资料: