Java基础笔记整理
关键字
- 用于定义数据类型
- class interface 【引用数据类型:类、接口、数组】
- boolean(1) char(2) byte(1) short(2) int(4) long(8) float(4) double(8) 【8个内置数据类型】
类型转换:
byte→short→int→→(可能丢失精度)float→→(可能丢失精度)long→→(可能丢失精度)double
char→int
int→long
int→double
float→double - void
- 用于定义数据类型值
- true false null
- 用于定义流程控制
-
if else switch case default【选择】
【switch jdk5前:case byte\short\int\char。jdk5:case:enum。jdk7:case String】 -
while do for【循环】
-
break continue return【流程跳转】
直接for循环效率最高,其次是迭代器和 ForEach操作。作为语法糖,ForEach底层是使用迭代器实现,反编译后testForEach方法如下,可以看到,只比迭代器遍历多了生成中间变量这一步,因为性能也略微下降了一些。
public static void testForEach(List list) { for (Iterator iterator = list.iterator(); iterator.hasNext();) { Object t = iterator.next(); Object obj = t; } }
-
- 用于定义访问权限修饰符
- private public protected
- 用于定义类、函数、变量
- abstract final static synchronized
- 类与类之间的关系
- extends implements
- 建立实例、引用实例、判断实例
- new this super instanceof
- 异常处理
- try catch throw throws finally
- 包
- package import
- 其它
- native strictfp transient volatile assert
- 保留字 goto const
标识符
类、接口、方法、变量的名字
合法字符组成:英文字母、数字、$、_【不能以数字开头】
注释
- 单行 //
- 多行 /**/
- 文档 /** */
常量
- 字面值 “hello”,10.true
- 自定义 final int LINK_CODE = 0;
- final 修饰变量(基本类型值不变,引用类型地址值不变)(只能在构造方法执行之前赋值一次)
变量
类变量(静态变量) | 实例变量(成员变量) | 局部变量(本地变量) | |
---|---|---|---|
位置(代码) | 类中 | 类中方法外 | 方法定义中、方法声明上 |
位置(内存) | 方法区中静态区 | 堆 | 栈 |
生命周期 | 随类加载而加载 | 随对象 | 随方法 |
this无法存在静态中; | |||
静态方法只能访问静态变量 |
运算符
- 算术运算符
% + - * / ++ --
- 赋值运算符
= += -= *= /= %=
- 比较运算符
== != > < >= <=
- 逻辑运算符
& | ^(异或) ! && || 【&& || 有短路效果】
-
位运算符
« » »>(无符号右移) & | ^ ~(按位取反) -
三元运算符
max = a > b ? a : b
代码块
代码块加载顺序: 静态代码块(类初始化)–> 局部代码块 –> 构造代码块(对象初始化)
public class Importtant06Code {
static { System.out.println("Importtant06Code静态代码块");}
public static void main(String[] args) {
System.out.println("main");
User user = new User();
User user2 = new User();
}
}
class User{
static { System.out.println("User静态代码块");}
{System.out.println("User局部代码块");}
public User() {System.out.println("User构造代码块");}
}
运行结果:
Importtant06Code静态代码块
main
User静态代码块
User局部代码块
User构造代码块
User局部代码块
User构造代码块
Java面向对象三大特性
封装、继承、多态
类与对象
- 类:一组相关属性和行为的集合(抽象)
- 对象:改类事物的具体表现形式(具体存在的个体)
封装
把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口
好处:隐藏实现细节,提供公共访问方式,复用性,安全性
关键字:private、this、static、new
例子:
class User{
private String userName = "wm";
private Integer age = 18;
public User(){
this.name = "sg";
this.age = 25;
}
}
class Demo {
public static void main(String[] args){
User user = new User();
}
}
对象构造过程:
- 将User.class文件加载到内存
- 栈内存放置user,堆内存放置对象
- 给成员变量进行初始化
- 默认初始化 name:null age:0
- 显示初始化 name:wm age:18
- 构造初始化 name:sg age:25
- 将地址值赋给变量user
继承
从已有类得到继承信息创建新类的过程
好处:复用、维护、多态前提
关键字:extends、this、super
重载与重写
- 重写(Override) 子类重写父类方法,多态的条件之一
- 重载(Overload) 同一个类中不同参数的方法
注意
- 子类初始化之前会先进行父类的初始化
- 父类没有无参构造,编译会报错
- 子类重写父类方法,方法访问权限不能比之更低
多态
允许不同子类型的对象对同一消息作出不同的响应
好处: 维护、扩展
父类接口指向子类对象,主要体现在抽象类
Fu f = new Zi();
成员方法:编译看父类,运行看子类。
静态方法:编译看父类,运行看子类。
成员变量:编译看父类,运行看子类。
构造方法:编译看父类,运行看父子。
package com.wm.demo.learn.base.d1tod4;
class Fu {
public int num = 100;
public Fu() {
super();
System.out.println("new Fu");
}
public void show() {
System.out.println("show Fu");
}
public static void function() {
System.out.println("function Fu");
}
}
class Zi extends Fu {
public int num = 1000;
public int num2 = 200;
public Zi() {
super();
System.out.println("new Zi");
}
@Override
public void show() {
System.out.println("show Zi");
}
public void method() {
System.out.println("method zi");
}
public static void function() {
System.out.println("function Zi");
}
}
class Demo04DuoTai {
@SuppressWarnings("static-access")
public static void main(String[] args) {
//父类引用指向子类对象 父 f = new 子();
Fu f = new Zi();
System.out.println(f.num);
//找不到符号
//System.out.println(f.num2);
f.show();
//找不到符号
//f.method();
f.function();
/*new Fu
new Zi
100
show Zi
function Fu*/
}
}
java多态的实现原理
当JVM执行Java字节码时,类型信息会存储在方法区中,为了优化对象的调用方法的速度,方法区的类型信息会增加一个指针,该指针指向一个记录该类方法的方法表,方法表中的每一个项都是对应方法的指针。
方法区:方法区和JAVA堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 运行时常量池:它是方法区的一部分,Class文件中除了有类的版本、方法、字段等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分信息在类加载时进入方法区的运行时常量池中。 方法区的内存回收目标是针对常量池的回收及对类型的卸载。
方法表的构造
由于java的单继承机制,一个类只能继承一个父类,而所有的类又都继承Object类,方法表中最先存放的是Object的方法,接下来是父类的方法,最后是该类本身的方法。如果子类改写了父类的方法,那么子类和父类的那些同名的方法共享一个方法表项。
由于这样的特性,使得方法表的偏移量总是固定的,例如,对于任何类来说,其方法表的equals方法的偏移量总是一个定值,所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值。
实例
假设Class A是Class B的子类,并且A改写了B的方法的method(),那么B来说,method方法的指针指向B的method方法入口;对于A来说,A的方法表的method项指向自身的method而非父类的。
流程:调用方法时,虚拟机通过对象引用得到方法区中类型信息的方法表的指针入口,查询类的方法表 ,根据实例方法的符号引用解析出该方法在方法表的偏移量,子类对象声明为父类类型时,形式上调用的是父类的方法,此时虚拟机会从实际的方法表中找到方法地址,从而定位到实际类的方法。
注:所有引用为父类,但方法区的类型信息中存放的是子类的信息,所以调用的是子类的方法表。
抽象类与接口
抽象类 | 接口 | |
---|---|---|
特点 | 1.抽象类与方法需用abstract修饰 | 1.接口用interface修饰 |
2.抽象类不一定有抽象方法,有则为抽象类 | 2.类实现接口用implements | |
3.不能直接实例化(通过多态可实例化) | 3.不能被实例化 | |
成员特点-属性 | 变量、常量 | 常量(任何成员属性都隐含着 public static final) |
成员特点-构造方法 | 有 | 无 |
成员特点-成员方法 | 抽象、非抽象 | 抽象、【JDK8有 default、static方法】 |
关键字 | 不能与private、final、static(无意义)共存 | |
java.util.AbstractList | java.util.List |
内部类
- 成员内部类
class Outer{ static int num0 = 99; public int num = 10; class Inner{ public int num = 20; public void show(){ int num = 30; system.out.println(num);//30 system.out.println(this.num);//20 system.out.println(Outer.this.num);//10 } } } class Test{ public static void main(String[] args){ Outer.Inner oi = new Outer().new Inner(); oi.show(); } }
- 局部内部类
class Outer{ static int num0 = 99; public int num = 10; public void method(){ final int num2 = 100;//存放于堆内存 int num3 = 101;//随调用产生,随结束消失 class Inner{ Outer o = new Outer(); System.out.println(o.num0); System.out.println(o.num1); System.out.println(o.num2); //System.out.println(o.num3);//无法访问,编译报错 } } } class Test{ public static void main(String[] args){ Outer o = new Outer(); o.method(); } }
``
- 匿名内部类
interface Person{void study();} class PersonDemo{ public void method(Person p){ p.study(); } } class Test{ public static void main(String[] args){ PersonDemo pd = new PersonDemo(); pd.method(new Person(){ public void study(){ //dosomething; } }); } }
``
- 静态内部类(访问外部数据必须用static修饰)
class Outer{ static int num0 = 99; public int num = 10; static class Inner{ public int num = 20; public void show(){ int num = 30; system.out.println(num);//30 system.out.println(this.num);//20 system.out.println(Outer.this.num);//10 } } } class Test{ public static void main(String[] args){ Outer.Inner oi = new Outer().Inner(); oi.show(); Outer.Inner.show(); } }
``
异常
Java将可抛出(Throwable)的结构分为三种类型: 被检查的异常(Checked Exception),运行时异常(RuntimeException)和错误(Error)。
-
Throwable Throwable是 Java 语言中所有错误或异常的超类。 Throwable包含两个子类: Error 和 Exception 。它们通常用于指示发生了异常情况。 Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。
-
Exception Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。
-
RuntimeException RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 编译器不会检查RuntimeException异常。如NullPointerException、IndexOutOfBoundsException,ArithmeticException(除数为零时),也能通过编译。
-
非RuntimeException Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。 如IOException、SQLException,Java编译器会检查它。 此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
-
Error 和Exception一样, Error也是Throwable的子类。 它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。如StackOverflowError、OutOfMemoryError,和RuntimeException一样, 编译器也不会检查Error,。
hashCode的作用
- hashCode的存在主要是用于查找的快捷性,作用于像散列集合Hashtable,HashMap等确定对象的存储地址的;
- 如果两个对象相同,就是适⽤于equals(java.lang.Object)⽅方法,那么这两个对象的hashCode⼀定要相同;
- 如果对象的equals⽅方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals⽅方法中使用的一致,否则就会违反上面提到的第2点;
- 两个对象的hashCode相同,并不一定表示两个对象就相同(equals不一定为true),只能够说明这两个对象在散列列存储结构中,它们存放在同一个桶里面。
Collection概述
Collection 是对象集合,继承了超级接口Iterable,使用Iterator迭代器实现遍历。 Collection 有两个子接口 List 和 Set。
List 可以通过下标 (1,2..) 来取得值,值是有序重复,有自己的迭代器ListIterator,可以通过这个迭代器进行逆序的迭代。
ArrayList、Vector、LinkedList 是List的实现类。
ArrayList 是线程不安全的,底层采用数组实现(并且该数组的类型是Object类型的),默认长度为10。
扩容的步长是0.5倍原容量,扩容⽅方式是利用数组的复制,因此有一定的开销。
Vector 是线程安全的,底层采用数组实现。
LinkedList 是线程不安全的,底层是由(双向循环Deque)链表实现的。
LinkedList 有一个内部类作为存放元素的单元,⾥里里⾯面有三个属性,⽤用来存放元素本身以及前后2个单元的引用,另外LinkedList内部还有一个header属性,用来标识起始位置,第一个单元和最后一个单元都会指向header,因此形成了一个双向循环链表结构。
Stack类:继承自Vector,实现一个后进先出的栈。提供了几个基本方法,push、pop、peak、empty、search等。
Queue接口:提供了几个基本方法,offer、poll、peek等。已知实现类有LinkedList(实现双端队列Deque)、PriorityQueue等。
Set 只能通过游标来取值,并且值是不能重复的。Set中的元素类必须有一个有效的equals方法。
Set子接口有NavigableSet、SortedSet;Set子类有EnumSet、HashSet、LinkedHashSet、TreeSet、AbstractSet等。
HashSet 底层是哈希表(元素为链表的数组)实现(方法代码使用了HashMap),由hashcode()和equals()方法保证元素唯一。
TreeSet 底层是红黑树(自平衡的二叉树)实现,元素是可排序的。
LinkedHashSet 底层由链表和哈希表实现,保证元素有序且唯一。
TreeSet有两种排序实现。自然排序(内比较器),类实现Comparable;比较器排序,使用匿名内部方式实现。
//比较器排序
TreeSet<User> userSet = new TreeSet<>(
new Comparator<User>(){
public int compare(User user1, User user2){
if (user1.getAge() > user2.getAge()) return 1;
else if (user1.getAge() = user2.getAge()) return 0;
else return -1;
}
}
)
注意:
- 使用迭代器Iterator遍历集合时修改添加元素会报并发修改异常,而用ListIterator则不会。
- 使用size()获取集合长度
- toString()实现:AbstractColletion使用iterator()迭代器方法得到迭代器,之后使用StringBuilder拼接遍历的值。
ArrayList、LinkedList、Vector的底层实现和区别
- 从同步性来看,ArrayList和LinkedList是不同步的,而Vector是的。所以线程安全的话,可以使用ArrayList或LinkedList,可以节省为同步而耗费的开销。但在多线程下,有时候就不得不使用Vector了。当然,也可以通过一些办法包装ArrayList、LinkedList,使我们也达到同步,但效率可能会有所降低。
- 从内部实现机制来讲ArrayList和Vector都是使用Object的数组形式来存储的。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。如果你要在集合中保存大量的数据,那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
- ArrayList和Vector中,从指定的位置(用index)检索一个对象,或在集合的末尾插入、删除一个对象的时间是一样的,可表示为O(1)。但是,如果在集合的其他位置增加或者删除元素那么花费的时间会呈线性增长O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置,因为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行(n-i)个对象的位移操作。LinkedList底层是由双向循环链表实现的,LinkedList在插入、删除集合中任何位置的元素所花费的时间都是一样的O(1),但它在索引一个元素的时候比较慢,为O(i),其中i是索引的位置,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是对其它指定位置的插入、删除操作,最好选择LinkedList。
Map概述
Map 是键值对集合。HashTable和HashMap是Map的实现类。
HashMap不是线程安全的,键唯一(哈希表实现),可以存储null值。
HashTable是线程安全的,键唯一,不能存储null值。
LinkedHashMap键是唯一有序的(哈希表和链表实现)
TreeMap键是可排序的(红黑树实现)
常用容器初始值大小
- StringBuffer和StringBuilder初始化默认大小为16个字符
- HashMap初始化默认大小16,自增为2n.
- HashTable默认初始值为11,加载因子为0.75,自增为2n+1
- ArrayList初始化默认值为10,自增为1.5n
- Vector初始化默认值为10,自增为2n
IO流
- 字节流
- 输入
- InputSream
- FileInputStream
- BufferedInputStream
- InputSream
- 输出
- OutputStream
- FileOutputStream
- BufferedOutputStream
- OutputStream
- 输入
- 字符流
- 输入
- Reader
- InputStreamReader
- FileReader
- BufferedReader
- LineNumberReader
- InputStreamReader
- Reader
- 输出
- Writer
- OutputStreamWriter
- FileWirter
- BufferedWriter
- OutputStreamWriter
- Writer
- 输入
close()与flush():
close() 关闭流对象,但会先刷新一次流缓冲区,关闭之后不可使用。
flush() 仅仅刷新缓冲区,刷新之后,流对象还可以继续使用。
线程状态
- 新建(New):创建后尚未启动的线程处于这种状态。线程启动start()。
- 就绪/运行(Runnable):Runnable包括了操作系统状态中的 就绪 Ready和 运行 Running,也就是处于此状态的线程有可能正在执行,也有可能正在等待CPU为它分配执行时间。
- 无限期等待(Waiting):处于这种状态的进程不会被分配CPU执行时间,它们要等待被其他线程显示的唤醒。以下方法会让线程陷入无限期的等待状态:
- 没有设置Timeout参数的Object.wait()方法
- 没有设置Timeout参数的Thread.join()方法
- LockSupport.park()方法
- 限期等待(Timed Waiting):处于这种状态的进程不会被分配CPU执行时间,不过无需等待被其他线程显示的唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态:
- Thread.sleep()方法
- 设置了Timeout参数的Object.wait()方法
- 设置了Timeout参数的Thread.join()方法
- LockSupport.parkNanos()方法
- LockSupport.parkUnitil()方法
- 阻塞(Blocked):进程被阻塞了,阻塞状态和等待状态的区别是:阻塞状态在等待着获取到一个排它锁,这个事件将在另一个线程放弃这个锁的时候发生;而等待状态则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
- 结束(Terminated):已终止线程的线程状态,线程已经结束执行。
线程的控制
- 获取线程的名称
public final String getName()
- 设置线程的名称
public final void setName(String name)
- 返回当前正在执行的线程对象(名称)
public static Thread currentThread()
->Thread.currentThread().getName()
- 获取线程对象的优先级
public final int getPriority()
- 设置线程对象的优先级
public final void setPriority(int newPriority)
- 线程默认优先级是5。
- 线程优先级的范围是:1-10。
- 线程优先级高仅仅表示线程获取的 CPU时间片的几率高
- 将该线程标记为守护线程或用户线程
public final void setDaemon(boolean on)
- 当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
- 线程休眠
public static void sleep(long millis)
- 加入线程
public final void join()
:等待该线程终止。 - 礼让线程
public static void yield()
:暂停当前正在执行的线程对象,并执行其他线程。 - 后台线程
- 终止线程(掌握)
public final void stop()
:让线程停止,过时了,但是还可以使用。public void interrupt()
:中断线程。 把线程的状态终止,并抛出一个InterruptedException。
多线程相关
- 多线程实现方案:
- 继承Thread类
- 自定义线程类继承Thread类,重写run()
- 调用类 创建自定义线程类对象,启动线程
- 实现Runnable接口
- 自定义线程类实现Runable接口,重写run()
- 调用类 创建自定义线程类对象
- 调用类 以自定义线程类对象作为参数构造创建Thread对象,启动线程
- 实现Callable接口结合线程池
- 自定义线程类实现Callable接口,重写call方法
- 1
- 调用类 创建ExecutorService线程池对象,调用submit(Callable task)
- 调用类 创建Future对象,接收上一步返回值
- 2
- 调用类 创建ExecutorService线程池对象,调用 submit(Runnable task)
- 1
- pool.shutdown()结束;
- 自定义线程类实现Callable接口,重写call方法
- 继承Thread类
- 线程安全问题产生条件
- 多线程环境
- 有共享数据
- 有多条语句操作共享数据
- 同步的方式:
- 同步代码块
synchronized(对象){//doSomething}
或 同步锁 Lock- 当线程访问较多时,每个线程都会去判断同步上的锁,非常消耗资源,降低程序运行效率,而且容易产生死锁。
- 同步方法
- 同步代码块
- run()与start()
- run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
- start():启动线程,并由JVM自动调用run()方法
-
sleep()与wait()
sleep()是线程类(Thread)的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时会自动恢复(线程回到就绪(ready)状态),调用sleep 不会释放对象锁。 wait()是Object 类的方法,对象调用wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。 -
死锁:两个或两个异常的线程争夺资源过程中,发生的一种互相等待的现象。
-
锁的等级:方法锁、对象锁、类锁。
-
实现Runnable接口相比继承Thread类有如下优势:
- 可以避免由于Java的单继承特性而带来的局限
- 增强程序的健壮性,代码能够被多个程序共享,代码与数据是独立的
- 适合多个相同程序代码的线程区处理同一资源的情况
线程安全需要保证几个基本特性:
- 原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
- 可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。
- 有序性,是保证线程内串行语义,避免指令重排等。
Socket编程
网络编程三要素: IP、端口、协议
- UDP
- 发送数据
- 创建发送端口DatagramSocket对象
- 创建数据,并打开数据包
- 调用DatagramSocket对象发送方法,发送数据包
- 释放资源
- 接收数据
- 创建DatagramSocket对象
- 创建一个数据包(接收容器)
- 调用容器接收数据
- 解析数据包
- 释放资源
- 发送数据
- TCP
- 发送数据
- 创建发送端的Socket对象
- 获取输出流,写数据
- 释放资源
- 接收数据
- 创建接收端的Socket
- 监听客户端连接
- 获取输入流
- 释放资源
- 发送数据
反射机制
反射技术:动态加载一个指定的类,并获取该类中所有的内容。并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。
好处:大大增强了程序的扩展性。
反射的基本步骤:
- 获得Class对象,就是获得指定的名称的字节码文件对象
- 实例化对象,获得类的属性、方法或者构造函数
- 访问属性、调用方法、调用构造函数创建对象