Java基础知识
一、Java基本功
(一) Java入门(基础概念与常识)
1.1.1. Java语言的主要特征
Java语言是易学的。Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用Java。
Java语言是强制面向对象的。Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为implements)。
Java语言是分布式的。Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。
Java语言是健壮的。Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证。对指针的丢弃是Java的明智选择。
Java语言是安全的。Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。如:安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查。
Java语言是体系结构中立的。Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。
Java语言是解释型的。如前所述,Java程序在Java平台上被编译为字节码格式,然后可以在实现这个Java平台的任何系统的解释器中运行。
Java是性能略高的。与那些解释型的高级脚本语言相比,Java的性能还是较优的。
Java语言是原生支持多线程的。在Java语言中,线程是一种特殊的对象,它必须由Thread类或其子(孙)类来创建。
1.1.2. JDK 和 JRE有什么区别
JDK:Java Development Kit 的简称,Java开发工具包,提供了Java的开发环境和运行环境
JRE:Java Runtime Environment 的简称,Java运行环境,为Java的运行环境提供了所需环境
结论:JDK包含了JRE,同时包含了编译Java源码的编译器Javac,还包含了很多Java程序调试和分析的工具。
如需运行Java程序,安装JRE; 如需编写Java程序,安装JDK。
(二) Java语法
1.2.1. 标识符与关键字的区别
在我们编写程序的时候,需要大量地为程序、类、变量、方法等取名字,于是就有了标识符,简单来说,标识符就是一个名字。但是有一些标识符,Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这种特殊的标识符就是关键字。因此,关键字是被赋予特殊含义的标识符。比如,在我们的日常生活中 ,“警察局”这个名字已经被赋予了特殊的含义,所以如果你开一家店,店的名字不能叫“警察局”,“警察局”就是我们日常生活中的关键字。
1.2.2. Java中常见的关键字
访问控制 | private | protected | public | ||||
---|---|---|---|---|---|---|---|
类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
new | static | strictfp | synchronized | transient | volatile | ||
程序控制 | break | continue | return | do | while | if | else |
for | instanceof | switch | case | default | |||
错误处理 | try | catch | throw | throws | finally | ||
包相关 | import | package | |||||
基本类型 | boolean | byte | char | double | float | int | long |
short | null | true | false | ||||
变量引用 | super | this | void | ||||
保留字 | goto | const |
1.2.3. 自增自减运算符
自增自减运算符:n++将变量n的当前值加1,n--则将n的值减1。
例如,以下代码:
int n = 12; n++;
将n的值改为13。由于这些运算符改变的是变量的值,所以它们不能应用于数值本身。
实际上运算符有两种形式:
运算符放在操作数后面的'后缀'形式;
'前缀'形式:++n。后缀和前缀形式都会使变量值加1或减1。但用在表达式中时,二者就有区别了。
前缀形式会先完成加1;而后缀形式会使用变量原来的值。
int m = 7; int n = 7; int a = 2 * ++m;//now a is 16,m is 8 int b = 2 * n++;//now b is 14, n is 8
1.2.4. i++和++i的区别
int i = 0;
int a = i++;
int b = i;
输出:a = 0 ; b = 1;
int i = 0;
int a = ++i;
int b = i;
输出:a = 1 ; b = 1;
结论:
1. i++,将现在的值赋给变量,将加1的赋值给下一个变量
2. ++i,将加1的值给变量,同时将加1的值更新为自身当前值
1.2.5. ==和 equals 的区别
==比较两个对象在内存里是不是同一个对象,就是说在内存里的存储位置一致。两个String对象存储的值是一样的,但有可能在内存里存储在不同的地方。
==比较的是引用而equals方法比较的是内容。 public boolean equals(Object obj)这个方法是由 Object对象提供的,可以由子类进行重写。默认的实现只有当对象和自身进行比较时才会返回true,这个
时候和==是等价的。 String, BitSet, Date,和File都对equals方法进行了重写,对两个String对象而言,值相等意味着它们包含同样的字符序列。对于基本类型的包装类来说,值相等意味着对应的基本类型
的值一样。
1.2.6. hashCode()与 equals()
(三) 基本数据类型
1.3.1. Java中的基本数据类型
Java中有 8 种基本数据类型,分别为:
- 6 种数字类型 :byte、short、int、long、float、double
- 1 种字符类型:char
- 1 种布尔型:boolean
这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean
基本类型 | 基本类型名称 | 大小(字节) | 默认值 | 包装类 |
---|---|---|---|---|
byte | 字节型 | 1 | 0 | Byte |
short | 短整型 | 2 | 0 | Short |
int | 整型 | 4 | 0 | Integer |
long | 长整型 | 8 | 0L | Long |
float | 单精度类型 | 4 | 0f | Float |
double | 双精度类型 | 8 | 0d | Double |
boolean | 布尔类型 | - | false | Boolean |
char | 字符类型 | 2 | 'u0000' | Character |
注意:
- Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析:
- char a = 'h'char :单引号,String a = 'hello' :双引号
Tips: boolean类型占了单独使用是4个字节,在数组中又是1个字节
基本类型所占的存储空间是不变的。这种不变性也是Java具有可移植性的原因之一。
基本类型放在栈中,直接存存储。
所有数值类型都有正负号,没有无符号的数值类型。
为什么需要封装类?
因为泛型类包括预定义的集合,使用的参数都是对象类型,无法直接使用基本数据类型,所以Java又提供了这些基本类型的封装类。
基本类型和对应的封装类由于本质的不同。具有一些区别:
1.基本类型只能按值传递,而封装类按引用传递
2.基本类型会在栈中创建,而对于对象类型,对象在堆中创建,对象的引用在栈中创建,基本类型由于在栈中,效率会比较高,但是可能存在内存泄漏的问题。
1.3.2. 自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
更多内容见:深入剖析 Java 中的装箱和拆箱
1.3.3. 8种基本类型的包装类和常量池
(四)方法(函数)
1.4.1.重载与重写的区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者 二者都不同)则视为重载;
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
- 方法的重载要遵循“两同一不同”:
“两同”即同一个类、方法名相同;
“一不同”即参数不同;
- 方法的重写要遵循“两同两小一大”:
“两同”即方法名相同、形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
1.4.2.深拷贝vs浅拷贝
- 浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
- 深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
1.4.3.方法的四种类型
- 无参数无返回值的方法
// 无参数无返回值的方法(如果方法没有返回值,不能不写,必须写void,表示没有返回值) public void f1() { System.out.println('无参数无返回值的方法'); } Copy to clipboardErrorCopied
- 有参数无返回值的方法
/**
* 有参数无返回值的方法
* 参数列表由零组到多组“参数类型+形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间以英文空格隔开
*/
public void f2(int a, String b, int c) {
System.out.println(a + '-->' + b + '-->' + c);
}
Copy to clipboardErrorCopied
- 有返回值无参数的方法
// 有返回值无参数的方法(返回值可以是任意的类型,在函数里面必须有return关键字返回对应的类型) public int f3() { System.out.println('有返回值无参数的方法'); return 2; } Copy to clipboardErrorCopied
- 有返回值有参数的方法
// 有返回值有参数的方法
public int f4(int a, int b) {
return a * b;
}
Copy to clipboardErrorCopied
- return 在无返回值方法的特殊使用
// return在无返回值方法的特殊使用 public void f5(int a) { if (a > 10) { return;//表示结束所在方法 (f5方法)的执行,下方的输出语句不会执行 } System.out.println(a); }
1.4.4.Java的四种引用
强引用
最普遍的一种引用方式,如 String s='abc',变量s就是字符串'abc'的强引用,只要强引用存在,则垃圾回收器就不会回收这个对象。软引用( Softreference)
用于描述还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。一般用于实现内存敏感的高速缓存,软引用可以和引用队列 Reference Queue联合使用,如果软引用的对象被垃圾回收JVM就会把这个软引用加入到与之关联的引用队列中。弱引用( Weakreference)
弱引用和软引用大致相同,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。虚引用( Phantom Reference)
就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:
虚引用必须和引用队列( Reference Queue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
二、Java面向对象
(一)类和对象
2.1.1.面向对象和面向过程的区别
2.1.2.构造方法
(二)面向对象的三大特征
2.2.1.封装
2.2.2.继承
2.2.3.多态
(三)修饰符
###2.3.1.访问权限
作用域 | 当前类 | 同一package | 子孙类 | 其他package |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | x |
default | √ | √ | x | x |
private | √ | x | x | x |
Tips:不写默认default
2.3.2.Java中final、finally、finalize的区别与用法
- final
final是一个修饰符也是一个关键字。
被final修饰的类无法被继承
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改
如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是它指向的对象的内容是可变的。被fnal修饰的方法将无法被重写,但允许重载
注意:类的 private方法会隐式地被指定为fna方法。
- finally
finally,是一个关键字。
finally在异常处理时提供 finally块来执行任何清除操作。不管有没有异常被抛出或者捕获, finally块都会执行,通常用于释放资源。
finally块正常情况下一定会被执行。但是有至少两个极端情况:
如果对应的try块没有执行,则这个try块的finally块并不会被执行
如果在try块中Jvm关机,例如 system.exit(n),则 finally块也不会执行(都拔电源了,怎么执行)finally块中如果有reun语句,则会覆盖try或者catch中的 return语句,导致二者无法 return,所以强烈建议finally块中不要存在reurn关键字。
- finalize
finalize()是 Object类的 protected方法,子类可以覆盖该方法以实现资源清理工作。
GC在回收对象之前都会调用该方法。
finalize方法是存在很多问题的:
java语言规范并不保证 finalize方法会被及时地执行,更根本不会保证它们一定会被执行
finalize()方法可能带来性能问题,因为JVM通常在单独的低优先级线程中完成 finalize的执行
finalize()方法中,可将待回收对象赋值给 GC Roots可达的对象引用,从而达到对象再生的目的
finalize方法最多由GC执行一次(但可以手动调用对象的 finalize方法)
(四)其他重要知识点
2.4.1.堆和栈的概念和区别
在说堆和栈之前,先说一下JVM(虚拟机)内存的划分:
Java程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java虚拟机运行时也是要开辟空间的。JVM运行时在内存中开辟一片内存区域,启动时在自己的内存区域中进行更细致的划分,因为虚拟机中每一片内存处理的方法都不同,所以要单独进行管理。
JVM内存的划分有五片:
1.寄存器
2.本地方法区
3.方法区
4.栈内存
5.堆内存
我们来重点说一下堆和栈:
栈内存:栈内存首先时一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量,for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用于,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的声明周期都很短。
堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封存数据,而且是封存多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,也就没有了。堆里的实体虽然不会被释放,但是会被当做垃圾,Java有垃圾回收机制不定时的收取。
下面我们通过一个图例详细讲一下堆和栈:
比如主函数里的语句
int[] arr = new int[3];
在内存中是怎么被定义的:
主函数先进栈,在栈中定义一个变量arr,接下来为arr赋值,但是右边不是一个具体值,是一个实体。实体创建在堆里,在堆里首先通过new关键字开辟一个空间,内存在存储数据的时候都是通过地址来实现的,地址是一块连续的二进制,然后给这个实体分配一个内存地址。数组都有一个索引,数组这个实体在堆内存中产生之后每一个空间都会默认的初始化(这是堆内存的特点,未初始化的数据是不能用的,但是在堆里是可以用的,因为初始化过了,但是栈里没有),不同的类型初始化的值不一样,所以堆和栈里就创建了变量和实体:
那么堆和栈怎么联系起来的呢?
我们刚刚说郭给堆分配一个地址,把堆的地址赋给arr,arr就通过地址只想了数组。所以arr想操作数组时,就通过地址,而不是直接把四蹄都赋给它。这种我们不再叫它基本数据类型,而叫引用数据类型。称为arr引用了堆内存当中的实体
如果当int[] arr = null;
arr不做任何指向,null的作用就是取消引用数据类型的指向。
当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当作一个垃圾,在不定时的时间内自动回收,因为Java有一个自动回收机制。自动回收机制自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候回收不一定。
所以堆和栈的区别很明显:
1.栈内存存储的是局部变量而堆内存存储的是实体;
2.栈内存的更新速度要快于堆内存,因为局部变量的声明周期很短;
3.栈内存存放的变量声明周期一旦结束就会被释放,而堆内存存放的实体会被垃圾啊回收机制不定时的回收。
2.4.2.java中继承,子类是否继承父类的构造函数(转载链接)
java继承中子类是不会继承父类的构造函数的,只是必须调用(隐式或者显式)
public class TestExtends { public static void main(String[] args) { SonClass s = new SonClass(66); } } class FooClass{ public FooClass() { System.out.println(100); } public FooClass(int count) { System.out.println(count); } } class SonClass extends FooClass{ public SonClass() { } public SonClass(int c) { System.out.println(1234); } }
运行结果:100 1234
接下来分析为什么会产生这样的结果:
程序在执行SonClass s = new SonClass(66);
这行时,调用
public SonClass(int c) {
System.out.println(1234); //在执行这行时系统会优先调用父类的无参构造函数super();
}
因此子类在执行上面的构造方法时,等价于执行了下面的代码:
public SonClass(int c) { super(); // 必须在第一行调用,否则不能编译 System.out.println(1234); //在执行这行时系统会优先调用父类的无参构造函数super(); }
所以结果为:100 1234
接下来介绍另外一种情况(显式调用),如果子类构造函数是这样写的:
public SonClass(int c) {
super(2); // 必须写在第一行,否则不能编译,显式调用父类构造函数后,系统就不在默认调用无参构造函数了
System.out.println(1234);
}
执行结果:2 1234
结论:
构造函数是不能继承的,只是用来在子类调用,(如果父类没有无参构造函数,创建子类时,必须在子类构造函数代码体的第一行显式调用父类的有参数构造函数,否则不能编译);
如果父类没有有参构造函数,那么在创建子类时可以不显式调用父类构造函数,系统会默认调用父类的无参构造函数super();
如果父类没有无参构造函数,那系统就调不了默认的无参构造函数了,所以不显示调用编译也就无法通过了;
注意:
- 在java中,创建有参构造函数后,系统就不在有默认的无参构造函数
- 如果父类中没有任何构造函数,系统会默认有一个无参的构造函数