1. 概念

JDK、JRE、JVM

参考链接:JVM、JRE、JDK三者之间的关系这一篇文章就够了!!!

  • JDK:英文全称 Java Development Kit,是Java的开发工具包, JDK 是提供给Java开发人员使用的,其中包含了 Java的开发工具JRE。其中的开发工具包括:编译工具(javac.exe)打包工具(jar.exe)等。通俗的说就是开发用的
  • JRE:英文全称 Java Runtime Environment,是Java运行环境 JRE包括 Java虚拟机 (JVM Java Virtual Machine) 和Java程序所需的 核心类库 等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。通俗的说就是运行用的
  • JVM:英文全称 Java Virtual Machine,是java虚拟机。 它只认识 .class 为后缀的文件,它能将class文件中的字节码指令进行识别并调用操作系统向上的API完成动作。JVM是java能够跨平台的核心机制。通俗的说就是跨平台用的,就是把我们写的代码,转换成class文件用的。

关系

  • JDK = JRE + 多种Java开发工具
  • JRE = JVM + 各种类库
  • 这三者的关系是一层层的嵌套关系,JDK > JRE > JVM

2. 基础语法

标识符命名规则

在Java中,标识符用作给变量、类和方法命名。

硬性要求,必须要这么做,否则代码会报错:

  • 由字母、数字、美元符号($)和下划线(_)组成,但是不能以数字开头。
  • 第一个字符必须是字母、美元符号($)或下划线(_)。
  • 不能是Java关键字和保留字。
  • 大小写敏感,长度无限制。

其他可以参考链接:java标识符的命名规则有哪些

数据类型

8种基本数据类型:

基本类型 大小(字节) 默认值 封装类
byte 1 0 Byte
short 2 0 Short
int 4 0 Integer
long 8 0L Long
float 4 0.0f Float
double 8 0.0d Double
char 2 \u0000(null) Character
boolean - false Boolean

引用数据类型:

引用类型 描述
class
接口 interface
数组 []

Java数据类型在内存中的存储:

  • 基本数据类型的存储原理:所有的简单数据类型不存在“引用”的概念,基本数据类型都是直接存储在内存中的内存栈上的,数据本身的值就是存储在栈空间里面,而Java语言里面八种数据类型是这种存储模型。
  • 引用类型的存储原理:引用类型继承于Object类(也是引用类型)都是按照Java里面存储对象的内存模型来进行数据存储的,使用Java内存堆和内存栈来进行这种类型的数据存储,简单地讲,“引用”是存储在有序的内存栈上的,而对象本身的值存储在内存堆上的。

区别:基本数据类型和引用类型的区别主要在于基本数据类型是分配在栈上的,而引用类型是分配在堆上的(需要java中的栈、堆概念)

那Java中字符串string属于什么数据类型?

Java中的字符串String属于引用数据类型,因为String是一个类。

break、continue、return

参考链接:循环里continue,break,return的作用,你知道吗?

  • break 跳出整个循环,不再执行循环(结束当前的循环体)
  • continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
  • return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

3. 关键字

this 和 super 关键字

参考链接:今天终于搞明白this和super关键字的作用了

this 关键字,主要3个作用

  • this.属性this.方法,表示当前对象的属性,当前类的方法。

  • this 表示当前对象,当前正在操作这个方法的对象就是当前对象。

  • 使用this(参数若干),可以调用当前类的构造方法,如果,构造方法之间相互调用,则有要求:

    • this() 必须放在首行。

    • 至少有一个构造方法是没有调用this(参数若干)的,就是说构造之间不能循环调用。

super关键字,主要3个作用

  • 代表对当前对象的直接父类对象的引用。
  • 可以调用父类的非 private 成员变量和方法。
  • super()可以调用父类的构造方法,只限构造方法中使用,且必须是第一条语句。

static 关键字的四种用法

参考链接:static关键字的四种用法

总结

static是java中非常重要的一个关键字,而且它的用法也很丰富,主要有四种用法:

  1. 用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享。
  2. 用来修饰成员方法,将其变为类方法,可以直接使用 类名.方法名 的方式调用,常用于工具类。
  3. 静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键。
  4. 静态导包用法,将类的方法直接导入到当前类中,从而直接使用 “方法名” 即可调用类方法,更加方便。

final 关键字的几种用法

参考链接:final关键字的几种用法

总结

final关键字是我们经常使用的关键字之一,它的用法有很多,但是并不是每一种用法都值得我们去广泛使用。它的主要用法有以下四种:

  1. 用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法被改变。对于成员变量来讲,我们必须在声明时或者构造方法中对它赋值。
  2. 用来修饰方法参数,表示在变量的生存期中它的值不能被改变。
  3. 修饰方法,表示该方法无法被重写。
  4. 修饰类,表示该类无法被继承。

上面的四种方法中,第三种和第四种方法需要谨慎使用,因为在大多数情况下,如果是仅仅为了一点设计上的考虑,我们并不需要使用final来修饰方法和类。

instanceof 关键字的作用

instanceof 严格来说是 Java 中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:

1
boolean result = obj instanceof Class

其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果 result 都返回 true,否则返回 false。

PS:编译器会检查 obj 是否能转换成右边的 Class 类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

1
2
3
int i = 0;
System.out.println(i instanceof Integer);//编译不通过 i 必须是引用类型,不能是基本类型
System.out.println(i instanceof Object);//编译不通过
1
2
3
4
Integer integer = 1;
System.out.println(integer instanceof Integer);// true
//在 JavaSE 规范中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回false
System.out.println(null instanceof Object);// false

4. 面向对象

Java只有值传递

Java 中只有值传递,没有引用传递!在这之前你需要明白:

  • 基本类型与引用类型?
  • 值传递与引用传递?

详见:Java只有值传递,没有引用传递!

Java 自动装箱与拆箱

装箱与拆箱

基本类型与对应的包装类对象之间,来回转换的过程称为 ”装箱“ 与 ”拆箱“ :

  • 装箱:从基本类型转换为对应的包装类对象。
  • 拆箱:从包装类对象转换为对应的基本类型。

以 Integer 与 int为例:

装箱:基本数值 –> 包装对象

1
2
Integer i = new Integer(4);//使用构造函数函数
Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法

拆箱:包装对象 –> 基本数值

1
int num = i.intValue();

自动装箱与自动拆箱

由于我们经常要做基本类型与包装类之间的转换,从 JDK 1.5 开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:

1
2
3
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。

以下代码会输出什么?

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}

运行结果:

1
2
true
false

为什么会出现这样的结果?输出结果表明 i1 和 i2 指向的是同一个对象,而 i3 和 i4 指向的是不同的对象。

此时只需一看源码便知究竟,下面这段代码是 Integer 的 valueof 方法的具体实现:

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

其中 IntegerCache 类的实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private static class IntegerCache {
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 Integer.IntegerCache.high >= 127;
}

private IntegerCache() {}
}

从这两段代码可以看出,在通过 valueOf 方法创建 Integer 对象的时候,如果数值在 [-128,127] 之 间, 便返回指向 IntegerCache.cache 中已经存在的对象的引用;否则创建一个新的 Integer 对象。 上面的代码中 i1 和 i2 的数值为 100,因此会直接从 cache 中取已经存在的对象,所以 i1 和 i2 指向的是同一个对象,而 i3 和 i4 则是分别指向不同的对象。

以下代码输出什么?

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}

运行结果:

1
2
false
false

原因:在某个范围内的整形数值的个数是有限的,而浮点数却不是。

重载和重写的区别

参考链接:Java—重写与重载的区别

重写(Override)

从字面上看,重写就是重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。 子类继承了父类原有的方法,但有时子类并不想原封不动地继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下,对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Father {
public static void main(String[] args) {
// TODO Auto-generated method stub
Son s = new Son();
s.sayHello();
}

public void sayHello() {
System.out.println("Hello");
}
}

class Son extends Father {
@Override
public void sayHello() {
// TODO Auto-generated method stub
System.out.println("hello by ");
}
}

重写总结:

  • 发生在父类与子类之间。

  • 方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同 。

  • 访问修饰符的限制一定要大于被重写方法的访问修饰符 (public>protected>default>private) 。

  • 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查性异常。

重载(Overload)

在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序 不 同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类 型是 否相同来判断重载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Father {
public static void main(String[] args) {
// TODO Auto-generated method stub
Father s = new Father();
s.sayHello();
s.sayHello("muyoukule");
}

public void sayHello() {
System.out.println("Hello");
}

public void sayHello(String name) {
System.out.println("Hello" + " " + name);
}
}

重载总结:

  • 重载 Overload 是一个类中多态性的一种表现。

  • 重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)。

  • 重载的时候,返回值类型可以相同也可以不相同。无法以返回类别作为重载函数的区分标准。

重载(Overload)和重写(Override)的区别?

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

怎么理解面向对象思想?

参考链接:什么是面向对象思想?面试必答题

面向对象是一种思想,是基于面向过程而言的,就是说面向对象是将功能等通过对象来实现,将功能封装进对象之中,让对象去实现具体的细节;这种思想是将数据作为第一位,而方法或者说是算法作为其次,这是对数据一种优化,操作起来更加的方便,简化了过程。

面向对象有三大特征:封装性、继承性、多态性。其中封装性指的是隐藏了对象的属性和实现细节,仅对外提供公共的访问方式,这样就隔离了具体的变化,便于使用,提高了复用性和安全性。对于继承性,就是两种事物间存在着一定的所属关系,那么继承的类就可以从被继承的类中获得一些属性和方法;这就提高了代码的复用性。继承是作为多态的前提的。多态是说父类或接口的引用指向了子类对象,这就提高了程序的扩展性,也就是说只要实现或继承了同一个接口或类,那么就可以使用父类中相应的方法,提高程序扩展性,但是多态有一点不好之处在于:父类引用不能访问子类中的成员的特有方法和属性。

举例来说:就是:比如说你要去饭店吃饭,你只需要去饭店,找到饭店的服务员,跟她说你要吃什么,然后就会给你做出来让你吃,你并不需要知道这个饭是怎么做的,你只需要面向这个服务员,告诉他你要吃什么,然后他也只需要面向你吃完收到钱就好,不需要知道你怎么对这个饭怎么吃的。

特点

  • 将复杂的事情简单化。
  • 面向对象将以前的过程中的执行者,变成了指挥者。
  • 面向对象这种思想是符合现在人们思考习惯的一种思想。

三大特征

封装、继承、多态。

权限修饰符

在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限。

  • public:公共的,所有地方都可以访问。

  • protected:本类 ,本包,其他包中的子类都可以访问。

  • 默认(没有修饰符):本类 ,本包可以访问。

    PS:默认是空着不写,不是default。

  • private:私有的,当前类可以访问。

不同权限的访问能力

public protected 默认 private
同一类中
同一包中的类
不同包的子类
不同包中的无关类

可见权限:public > protected > 默认 > private

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用 private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用 public ,方便调用方法。

PS:不加权限修饰符,就是默认权限。

null 与 “”

  • null是没有地址;
  • “”是有地址但是里面的内容是空的。

equals 和 ==

==

比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否实指相同一个对象。比较的是真正意义上的指针操作。

  • 比较的是操作符两端的操作数是否是同一个对象。

  • 两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。

  • 比较的是地址,如果是具体的阿拉伯数字的比较,值相等则 为 true,如: int a = 10long b = 10Ldouble c = 10.0 都是相同的(为 true ),因为他们都指向地址为 10 的堆。

equals

equals 是Object类中的方法,用来比较的是两个对象的内容是否相等,由于所有的类都是继承自 java.lang.Object 类的, 所以适用于所有对象,如果类没有重写 equals() 方法,那么它将默认比较对象的内存地址。但是,像String、Integer等类重写了 equals() 方法,用来比较对象的实际内容是否相同。

总结

所有比较是否相等时,都是用 equals ,并且在对常量相比较时,把常量写在前面,因为使用 Object 的 equals, Object 可能为 null ,则空指针。

hashCode 的作用

参考链接:HashCode的作用原理和实例解析

Java 的集合有两类,一类是 List,还有一类是 Set。前者有序可重复,后者无序不重复。当我们在 Set 中插入的时候怎么判断是否已经存在该元素呢,可以通过 equals 方法。但是如果元素太多, 用这样的方法就会比较慢。

于是有人发明了哈希算法来提高集合中查找元素的效率。这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的哪个区域。

hashCode 方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。

这样一来,当集合要添加新的元素时,先调用这个元素的 hashCode 方法,就一下子能定位到它应该放置的物理位置上。

  • 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
  • 如果这个位置上已经有元素了,就调用它的 equals 方法与新元素进行比较,相同的话就不存了,不相同就散列其他的地址。

这样一来实际调用 equals 方法的次数就大大降低了,几乎只需要一两次。

如何理解 hashCode 的作用?

从Object角度看,JVM每new一个Object,它都会将这个Object丢到一个Hash表中去,这样的话,下次做Object的比较或者取这个对象的时候(读取过程),它会根据对象的HashCode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。若HashCode相同再去调用equal。

为什么重写equals方法时一定要重写hashCode方法?

参考链接:为什么重写 equals方法时一定要重写hashCode方法?

重写equals方法时一定要重写hashCode方法的原因是为了保证对象在使用散列集合时能够正确地进行存储和查找,在HashMap、HashTable等散列集合中,计算存储元素的位置的时候需要调用到hashCode方法来判断插入的位置而不是调用equals方法(节约性能),而一般类中都是调用equals方法来判断两个元素是否相等,如果只重写了equals方法而不重写hashCode方法,就会导致这个类可以用equals来判断是否相等,但是添加到不可重复集合中的时候无法正常工作。

深拷贝和浅拷贝?

详见:一个工作三年的同事,居然还搞不清深拷贝、浅拷贝…

  • 浅拷贝:仅拷贝被拷贝对象的成员变量的值,也就是基本数据类型变量的值,和引用数据类型变量的地址值,而对于引用类型变量指向的堆中的对象不会拷贝。
  • 深拷贝:完全拷贝一个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝一份。