Java基础总结
Java的发展历程
- 1991:因为Green项目所开发
- 1994:互联网小项目
- 1995:改名为Java
- 2009:被Oracle收购
Java的特点
- 简洁有效
- 面向对象
- 使用的用户多
变量:变量就是一块内存空间。随着程序的运行,内存空间里的数据会随时发生改变。
数据类型:数据类型可以规范不同类型所占用的内存空间,所以通过数据类型可以更加合理的利用内存空间
类型转换:在运算过程中,如果数据的类型不统一,编译器首先会将不同数据类型统一成相同的数据类型,然后再进行运算。数据类型统一的过程就称为类型转换
自动类型转换:自动类型转换也称为隐式类型转换,自动类型转换是由编译器自动完成的。自动类型转换一般都是从低数据类型向高数据类型进行转换。
强制类型转换:强制类型转换也称为显式类型转换。强制类型转换一般发生在不同数据类型之间,是高数据类型向低数据类型之间的转换。强制类型转换可能会导致数据精准度的丢失。
强制类型转换说明
- 从高类型向低类型进行转换时必须进行强转
- 强制类型转换可能会造成数据精准度丢失
- 不同应用类型之间一般无法进行强转
Object可以强转为任何类型,但可能会造成转换失败
常量:常量就是在程序运行过程中不可改变的量(数据),用来保存数据的
- 常量可以在声明时赋值,也可以先声明后赋值
- 常量一旦赋值后将无法修改其值
运算符
算术运算符
+、-、*、/、%(取余、取模)
关系运算符
>、>=、<、<=、==、!=
逻辑运算符
&&、||、!
赋值运算符
=、(扩展赋值运算符)+=、-=、*=、/=、%=
说明:扩展赋值运算符具有类型转换的能力
方法:方法是实现了特定功能的一个代码段,通过方法的名称可以对代码段进行调用。
方法的作用
- 对功能进行封装,提高代码的复用性,使得代码更加清晰
- 提高代码的可扩展性,方法可以使得功能的扩展变得更加容易
Return语句
- 返回结果
- 跳出当前方法(无论return语句在多少层循环中,遇到return语句都被跳出)
数组:数组是相同数据类型的集合。数组是一个存储多个数据的容器。数据具有长度限制。
常见异常
NullPointerException
空指针异常
对象在内存中根本不存在,在我们的代码中对对象进行操作时就会产生空指针异常。
数组的优缺点
优点
- 数组可以存储大量数据
缺点
数组的长度是固定的,当长度不足时不便于扩容
面向过程和面向对象
- 面向过程(POP)
面向过程就是适合解决相对较为简单的问题,面向过程的编程具有较高的执行效率。
- 面向对象(OOP)
对象就是现实世界中一个具体事务的抽象描述,通过程序代码的方式对现实世界中一类事务进行描述的方式就称为面向对象。
类:类是对具有相同行为和属性的抽象描述。类是一个模板,类是抽象的。
访问修饰符:访问修饰符也称为访问限制符。通过访问修饰符可以控制类和成员的被访问权限。
变量
- 全局变量:定义在类内部,方法外部的变量就称为全局变量。
- 局部变量:定义在方法内部的变量就称为局部变量。
- 类变量:定义在类内部,通过static关键字进行修饰的变量就称为类变量,也称为静态变量。
方法
- 实例方法:定义方法时,未使用static关键字修饰的方法就称为实例方法
- 静态方法:定义方法时,使用static关键字修饰的方法就称为静态方法
构造函数
- 概述
构造函数也被称为构造器或构造方法。构造函数是一种特殊的方法。
- 特点
- 构造函数名与类名相一致
- 开发人员显式提供了构造函数后,编译器将不提供任何构造函数
- 构造函数只能由new操作符进行调用
- 构造函数支持重载
“一切皆对象”
完整翻译:“一切皆为类的对象”。
对象:一类事物中的一个真实个体。对象具有具体的属性和行为。一类事物的一个对象称为类的实例。创建对象的过程称为对象实例化。
对象实例化过程
- 实例化过程
- 通过new操作符向堆内存申请空间,编译器开辟空间并分配内存地址
- 并在对象空间中,对对象中的属性进行默认初始化0或null
- 调用对应的构造函数进行初始化
- 初始化完毕后,将地址值赋值给引用变量
- 初始化过程
This的本质
This相当于当前对象。
作用
- 通过this来区分类的成员
- 在构造函数中调用其他的构造函数
Static关键字:Static关键字用于修饰类的成员。被static关键字修饰的成员称为静态成员。
静态成员字段:静态成员字段可以通过类名进行调用。静态成员字段被类的所有对象所共享。
对象间消息传递:对象之间的相互调用
重载:指对已经存在的方法在所在类中重新再次进行实现。
- 重载方法在同一个类中,方法间属于水平关系
- 重载方法要求方法名相同
优点
- 通过方法重载可以更好的对类的功能实现扩展,使得方法的功能可以满足不同用户的需求
值类型参数:方法的参数类型为基本类型的参数都称为值类型参数。
引用类型参数:方法的参数类型为引用类型的参数都称为引用类型参数。
封装:封装就是把相关联的属性和操作形成一个独立整体。对于需要被调用的操作暴露成公共的访问接口。
封装优点
- 提高了数据的安全性
- 提高了代码的复用性
- 高内聚:隐藏了内部的实现细节,提供了代码的可维护性
- 低耦合:隐藏不需要用户关注的内容,方便用户的使用
继承:Java语言中语序使用extends语句实现继承关系。
Extends:原意是扩展。表示子类对父类的扩展。
说明
- 父类:被继承的类称为父类,父类也称为基类或超类。
- 子类:继承的类称为子类,也称为派生类。子类将继承父类的非私有成员
继承的意义
提高代码的复用性,减小代码冗余
继承的要求
- 继承通过extends关键字进行实现
- 如果没有显式继承关系,则默认类继承于Object(java中的类一定具有父类)
- Java中类的继承只支持单一继承。
- 子类可以继承父类的非私有成员,但无法继承构造函数
Super:就是子类对父类的引用。可以理解为父类对象。
重写:父类中的方法无法满足子类要求时,可以在子类中对父类的同名方法重新进行定义这就称为方法的重写。
重载:在Java中,如果有功能类型,可以使用相同的名字来定义不同功能方法
- 重写的要求
- ==:方法重写要求方法名、返回值及参数列表必须一致
- ≥:子类中的重写方法的访问修饰符必须大于或等于父类中重写方法的访问修饰符
- 重载与重写
- 重载是为了使功能更加灵活;而重写是父类方法无法满足子类的要求
Object类:是所有类型的父类。Object类中提供了所有类所共有的属性或方法。
equals作用:用于比较两个引用类型的数据是否相等。
多态:同一个方法在不同对象中所展现出的不同的行为就称为多态。
要点
- 多态的三要素:
- 子类继承于父类(要有继承)
- 子类重写父类的方法
- 父类引用指向子类对象
Final关键字的使用方式
- Final:被final修饰后不可以被继承,不可以被重写,不可以被修改,不可以被重新赋值
转型
- 将子类对象赋值给父类引用的过程称为向上转型,向上转型的过程是自动完成;
- 将父类对象赋值给子类的过程称为向下转型,向下转型的过程是强制完成;
父类知道自己有什么,但是不知道子类扩展了什么。所以如果需要调用子类扩展的成员,必须将对象转换成他自己。
抽象类和抽象方法
- 概述
Abstract关键字可以用于修饰类及方法,表示抽象的。被修饰的类(方法)就称为抽象类(方法)。
抽象类和抽象方法的主要作用就是指定一套具有强制性的标准。
- 抽象方法
被abstract关键字修饰的方法就称为抽象方法。抽象方法的特点:
- 抽象方法必须定义在抽象类中
- 抽象方法不能有方法体
- 抽象方法必须在子类中进行实现
- 抽象类
被abstract关键字修饰的类就称为抽象类。抽象类的特点:
- 抽象不能被直接实例化
- 抽象类中的抽象方法必须在子类中进行实现
单一职责原则
一个类只担负一个职责。
单一职责原则的优点:
- 类的复杂性降低,实现什么职责都有明确的定义
- 代码的可维护性提高
- 变更的风险降低,因为只会在单一的类中修改。且对类的修改不会影响到其他功能
开闭原则
开闭原则要求对功能的扩展是开放的,对功能的修改是关闭的。
优点:
开闭原则可以实现对功能的扩展。
里氏替换原则
凡父类出现的地方都可以用子类进行替换,且程序功能不会发生改变。里氏替换原则是实现多态的基础。
接口:接口是比抽象类更加抽象的一个抽象类型。接口的定义使用interface关键字进行实现。
- 接口是公共规范
- 接口是契约
接口与抽象类
- 抽象类中可以定义变量、常量;接口中只能定义常量
- 抽象类只能单一继承;接口允许多继承
- 抽象类和实现类之间是继承关系;接口和实现类之间是实现关系
多态的优势
- 扩展性(extendsible):可以根据需求对功能进行任意扩展,且程序代码不需要进行太大改动
- 替换性:在程序代码中可以使用子类对象进行任意替换,且程序功能不会发生太大改动
总结
封装
隐藏内部实现,提供公共访问接口。使调用者不用关注内部实现。
继承
减少代码冗余,提高代码复用性。
多态
提高代码的扩展性和可维护性,满足不同需求的要求。
内部类
类是组织代码的最小单位,如果将一个类定义在另一个类的内部,那么这个类就称为内部类
1.内部类可以直接访问外部类的成员。
2.通过内部类的实例对象,外部类可以访问到内部类成员。
3.局部内部类只能使用final或abstract进行修饰
静态内部类的优势
- 静态内部类更好实现了封装
- 接口实现了多继承,通过内部类更好解决了多继承的问题
基本类型与包装类型
1.概述
Java是一种面向对象的语言,Java中提供8种基本数据类型,这8种基本类型不是面向对象的类型,这破坏Java的面向对象能力。为了解决这个问题Java又为每种基本类型提供对应引用类型。
2.基本类型与包装类型
基本类型 | 包装类型(引用类型) |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
作用:
- 提供了用于将引用类型转为基本类型的相关方法
拆箱与装箱
- 拆箱
将引用类型转为基本类型就称为拆箱。
- 装箱
将基本类型转为引用类型就称为装箱。
- 缺点
- 存在安全隐患
- 存在性能的损失
- 建议
编写代码过程汇总尽量避免拆箱与装箱操作。
Throw和throws
- Throw
将捕获到的异常向上进行抛出。
- Throws
Throws用在方法的后面,用于声明方法执行后可能抛出的异常的类型。
- Throw和throws的区别
- 使用的位置:throw用于try-catch语句的内部;throws用于在方法参数的后面
- 抛出的内容不同:throw抛出的是对象;throws后跟的是可能产生的异常的类型
- 行为不同:异常产生了异常时用throw;throws是声明异常,在方法内可能会产生异常
- 建议
- 尽量用合理的逻辑替代try-catch语句
- 使用try-catch语句时,try语句段中的内容尽量少
String类型
概述
存在于java.lang包中,String是一个引用数据类型。String类型是用final进行修饰的(是不可以被继承的)。String的内部使用了char[]进行实现的。且数组是用final进行修饰的。所以Sting类型是一个长度不可变的字符序列。
String的常用方法
/** * String的常用方法 * @author TerryLiu * */ public class StringDemo05 { public static void main(String[] args) { String str1 = "Hllo"; String str2 = " China"; //length():返回当前字符串对象的长度 System.out.println("length():"+str1.length()); //concat():将两个字符串拼接成一个新的字符串对象 String str3 = str1.concat(str2); System.out.println("str3=" + str3); //toLowerCase():将字符串对象全部转为小写 System.out.println(str3.toLowerCase()); //toUpperCase():将字符串对象全部转为大写 System.out.println(str3.toUpperCase()); //charAt():返回指定索引处对应的字符内容 System.out.println(str3.charAt(0)); //contains():返回当前字符串序列中是否包含指定的字符内容 System.out.println(str3.contains("in")); //endsWith():检查当前字符串序列是否以指定字符内容结尾 System.out.println(str3.endsWith("na")); //startsWith():检查当前字符串序列是否以指定字符内容开始 System.out.println(str3.startsWith("O")); //getBytes():将字符序列转为byte[] byte[] arr = str1.getBytes(); System.out.println(Arrays.toString(arr)); //indexOf():从前向后查找指定子串在当前字符串中的索引位置,如果查找失败则返回-1 System.out.println(str3.indexOf("i")); //lastIndexOf():从后向前查找指定子串在当前字符串中的索引位置,如果查找失败则返回-1 System.out.println(str3.lastIndexOf("i")); //isEmpty():判断当前字符串对象是否为空串(长度为0) System.out.println(str3.isEmpty()); //replace():使用子串替换当前字符串中的指定内容 System.out.println(str3.replace("l", "d")); String str5 = "Tom,Jerry,Lucy,,Lily"; //split():按照指定字符对当前字符串进行分割,结果为一个字符串数组 String[] names = str5.split(","); for(String n : names){ System.out.println(n); } //substring():从指定索引位置开始对字符串进行截取并生成一个新的子串(包头不包尾) System.out.println(str3.substring(1, 3)); //toCharArray():将当前字符串序列转为char数组 char[] arr2 = str3.toCharArray(); //trim():去除收尾空格 String str6 = " ha ha "; System.out.println("|"+str6.trim()+"|"); //equals():比较两个字符串是否相等 //1、比较两个字符串是否指向了同一个引用,如果是则直接返回true //2、否则判断被比较对象是否为字符串,如果不是则直接返回false //3、对两个字符串相同位置的字符逐一进行比较,如果不等则直接返回false。 //如果都相等则返回true System.out.println(str1.equals(str6)); } } |
StringBuffer和StringBuilder
1.String的缺点
- 频繁修改将导致垃圾增多
- 频繁修改将导致程序性能的下降
2.StringBuilder
概述
一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
StringBuilder的底层同样使用了char[]进行实现,默认初始化长度为16个字符。
3.String、StringBuffer及StringBuilder
- 相同点
- 都能用于保存一个字符序列。
- 都使用了final进行修饰,都不可被继承
- 不同点
- String是一个长度不可变的字符序列,当频繁操作时效率会有所下降
- StringBuilder和StringBuffer是长度可变的字符序列,操作过程中可以随内容的长度进行扩展。
- StringBuffer是线程安全的,适用于在多线程环境中进行使用。但效率相对较低
- StringBuilder是非线程安全的,适用于单线程下使用,效率相对较高
设计模式
设计模式不是语法,也不是规范,设计模式是前人解决问题的方式后被整理就称为设计模式。
- 优点
- 将创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建,明确了职责。
- 缺点
- 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
- 要新增产品类的时候,就要修改工厂类的代码,违反了开放封闭原则(对扩展的开放,对修改的关闭)。
集合框架
-
- 数组的缺点
长度不可变,对于元素的插入和删除操作比较繁琐。
-
- 概述
整个集合框分为Collection和Map两个大的接口
集合框架中的所有集合类都包含泛型集合和非泛型集合两个版本。
-
- List集合(接口)
- 概述
- List集合(接口)
List称为列表,列表是有序、可重复的集合。List是以数组为基础进行实现的。
-
-
- ArrayList
-
- 概述
ArrayList是List接口的实现类。ArrayList的底层是通过数组进行实现的。
ArrayList就是一个经过封装的数组对象,在集合对象中提供了操作数组对象的相关方法。
-
-
- Vector
-
- 概述
Vector是List接口的实现类。Vector的底层是通过数组进行实现的。Vector是线程安全的。
- 相同点
- ArrayList和Vector都实现与List接口
- ArrayList和Vector的底层实现都是基于数组进行
- 不同点
- ArrayList是非线程安全的(推荐使用);vecotr是线程安全的
- ArrayList默认初始容量为0;vecotr默认初始容量为10
-
- LinkedList
-
- 概述
LinkedList是List接口的实现类,LinkedList是基于链表进行实现的。
- 总结
- LinkedList以链表进行实现,在元素的插入和移除操作时效率较高
- 不存在扩容问题
- 元素的遍历效率相对较低
- Set集合
- 概述
- Set集合
继承于Collection接口,Set集合是一个无序不可重复的集合。Set集合中最多允许出现1次Null值
-
-
- HashSet
-
- 概述
此类实现 Set 接口,由 HashMap 支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。注意:
JDK1.7-:HashSet使用的是数组+链表方式进行实现
JDK1.8+:HashSet使用的是数组+红黑树进行实现
- HashSet的特点
- 底层以HashMap数组进行实现
- 无法通过索引下标等方式获取或遍历元素
- 简述ArrayList和HashSet的区别
- ArrayList实现于List接口;HashSet实现于Set接口
- ArrayList中元素是有序的;HashSet中的元素是无序的
- ArrayList中元素可重复;HashSet中元素不可重复
-
- TreeSet
-
- 概述
基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap会对存储的内容进行排序(而非添加顺序)。
- 总结TreeSet底层使用了红黑树进行实现
- TreeSet中的元素是按照自然排序方式进行排序的有序集合
- TreeSet中的元素是不重复的
- TreeSet中允许使用null值(只能保存1个)
- Map
- 概述
- Map
Map是集合中一种较为常用的集合类型。Map接口有HahshMap和TreeMap两种常用集合。
Map集合都是以键值对的形式存在。一个数据项中包含一个k-v组合。
-
-
- 常用方法
-
- Put():向集合对象中添加一个键值对
- size():返回当前集合中键值对的个数
- remove():按照键名称移除一个键值对
- clear():清除当前集合中所有元素
- values():获取当前值的集合
-
- HashMap
-
- 概述
HashMap是Map接口的实现类。存储时,当空间达到临界时会进行扩容。扩容执行结束后会重新对元素进行排列。
- HashMap的特点
- HashMap的值都是以键值对的形式存在的
- HashMap的key允许使用Null值(值允许1次),但value可以重复使用null值
- HashMap的key是唯一的,Value是可以重复的
-
- TreeMap
-
- 概述
TreeMap是Map接口的实现类,TreeeMap是以红黑树为基础进行实现的。TreeMap采用的是自然排序法进行排序的(数据是有序的)。
- 红黑树
- 跟节点一定为黑色节点
- 同色节点一定不能相邻
- 新添加的节点一定是红色节点
- 叶子节点一定为黑色节点且值为null
- 总结
- 集合包含Collection和Map两大接口
- Collection包含List和Set两个子接口,其中List有序可重复的集合;Set是无序不可重复(TeeSet是有序)
- List中ArrayList、Vector都是以数组进行实现的,其中Vector是线程安全的(线程安全的效率都比较低)。LinkedList是以双向链表进行实现的,是非线程安全的,效率相对较高
- Map的常用实现类有TreeMap和HashMap,其中HashMap是以数组+链表+红黑树进行实现的,HashMap中的Key是无序的。TreeMap中的Key是以自然排序方式进行排序的
- Iterator接口
- 集合的遍历
- Iterator接口
在集合中包含了很多类,这些类也称为容器类。主要包含:List、Set、Map。这些集合对象都可以通过循环等方式进行遍历。
-
-
- 为什么使用迭代器
-
直接在容器类内部实现迭代时,耦合度相对比较高。通过迭代器(迭代器模式)的方式可以更好的实现解耦。
-
- Collections
- 概述
- Collections
该类提供了用于操作Collection集合类的相关方法。该类中的方法都是以静态方式存在。
- Collections是一个工具类;Collection是一个接口
- Collections中提供了用于操作集合类的相关方法;Collection接口中规定了集合类中应该提供的方法,Collection是一个规范
- 自定义排序器
- 排序方法
- 自定义排序器
- Sort(list)
- Sort(list,comparator())
排序过程中会调用类型的comparator比较器方法进行比较。Java的数据类型中都提供了比较化器。自定义类型中可以实现自定义的比较化器。按照实现方式的不同可以分为:自然排序和定制排序两种。
-
-
- 自然排序
-
- 步骤
- 创建类并实现Comparable接口
- 实现接口的方法
- 实现比较功能
- 缺点
自然排序的灵活性相对较差。如:当前类中是按照年龄进行排序,实际使用时希望按照姓名进行排序时,就需要重新去修改比较的属性。同时,使用自然排序也增加了代码的耦合度(代码具有侵入性:)
-
-
- 订制排序
-
- 步骤
- 创建匿名类
- 实现相关方法
- 编写比较的代码
- 优点
- 降低了类的耦合性
- 比较方法可以灵活定制
简述自然排序与定制排序的区别:
- 自然排序的排序类需要实现Comparable接口;定制排序的排序类不需要继承,也不需要实现。
- 自然排序需要实现compareTo()方法;定制排序需要实现compare()方法
多线程
-
- 进程与线程
- 程序
- 进程与线程
实现了特定功能的指令的集合。程序是静态的。
-
-
- 进程
-
一个程序运行后将被加载到内存中,被加载的程序就称为进程。
-
-
- 线程
-
一个进程中的一个任务就是一个线程。一个进程可以包含多个线程,但一个线程只属于一个进程。
-
-
- 程序
-
- 单线程程序
在执行过程中,必须等待前一个任务执行结束后才能执行下一个任务。
- 多线程程序
程序开启后会同时开始多个任务的执行。如:迅雷下载。
一个进程如果有多个线程,那么这个进程一定是多线程程序。
-
- 程序的执行
- 分时执行
- 程序的执行
所谓分时执行是指将Cpu的时间进行平均划分,每一个程序的执行时间都是均等的。
-
-
- 抢占方式执行
-
所谓抢占式执行就是各程序的执行过程中会抢占Cpu的控制权,然后对程序进行执行。执行过程中优先级高的程序可能会具有更高的优先级。
多线程程序不能更程序更快执行,但可以让程序具有高更执行效率。
-
- 主线程
Java程序的执行是从main()方法开始进行执行的(Jvm去查找main()并开始进行调用)。在main()方法中遇到其他任务,Jvm会执行其他任务,其他任务执行结束后重新回到main()方法中。
程序的执行都是围绕main()方法进行执行的,所以这里的main()放就是主线程。主线程执行结束后进程的执行也就结束了。
-
- 创建线程
- 继承的方式
- 创建线程
- Thread类
通过继承Thread类(及其子类)可以创建一个线程类。
- 常用方法
- run():如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
- Start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
- 步骤
- 创建类并继承于Thread类
- 重写Thread类的run()方法
- 创建线程类
- 调用线程类的start()方法启动线程
- 问题
为什么不直接new Thread();
//为什么不直接new Thread()? //可以new Thread(),但是没有实际意义。因为没有业务代码 Thread t = new Thread(); |
可以不去调用start()方法,而是调用run()方法吗?
不可以,因为调用run()时是在调用类的方法而已,线程并没有被执行。调用start()是,start()方法会通知Jvm去启动线程并调用线程的run()方法。Run()方法需要有Jvm进行调用。
- 内存模型
在多线程环境下,系统会为每一个线程分配独立的空间,当线程执行结束后空间会被立刻进行回收。
-
-
- 实现的方式
-
- Thread构造函数
- 步骤
- 创建类并实现Runnable接口
- 实现接口的run()方法
- 创建线程类
- 创建Thread类并将线程类对象传递给Thread的构造函数
- 调用线程的start()方法启动线程
- 选择继承还是实现?
在Java中类的继承只支持单一继承,一旦继承了Thread类之后就无法在继承其他的类了,所以继承的方式具有一定的局限性;而实现的同时还可以去继承其他的类,所以使得代码更具灵活性。
实现的方式更符合面向对象的规范,实现了线程类和线程对象的分离。更好的实现了解耦。
-
-
- Callable方式
-
- 概述
继承方式和实现Runnable接口的方式实现线程时,run()方法是没有返回值的。
在Jdk5中提供了Callable接口的方式实现线程。该方式可以提供一个返回值,但需要借助于FutureTask来实现。
- 实现步骤
- 创建类并实现Callable接口
- 实现接口方法
- 实例化线程类对象
- 实例化FutureTask对象并接收线程类实例
- 实例化Thread线程对象并接收FutureTask类型对象
- 启动线程
- 通过FutureTask类型对象接收返回值
- 总结
通过Callable方式可以获取线程执行后的返回值。Call()方法内可以由异常处理。
-
-
- 匿名类方式
-
- 概述
创建线程时,如果不希望去实现一个线程类,可以直接通过匿名类的方式进行实现。
- 总结
代码最简洁。
-
- 获取线程的名称
- 常用方法
- 获取线程的名称
- CurrentThread():获取当前线程实例的引用
- getName():获取当前线程的名称
- 线程优先级
- 调度策略
- 线程优先级
- 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间
- 抢占调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
-
- 说明
-
- Main的默认优先级为5
- 子线程默认情况下优先级与主线程相同。可以通过设置优先级的方式来调整线程执行。
- 具有高优先级的线程有可能被优先执行
-
- 常用方法
-
- GetPriority():获取当前线程的优先级
- SetPriority():设置当前线程的优先级
- 线程的操作
- 线程是否活动
- 线程的操作
- 方法
IsAlive():测试线程是否处于活动状态
-
-
- 线程休眠
-
- 概述
调用sleep()方法可以让当前线程暂停执行一段时间。使当前线程处于休眠状态。
- 说明
当线程执行了sleep()方法后,线程会进入到阻塞状态(停止某个动作)。即使当前已经没有线程在执行,被阻塞的线程也不会被执行,知道阻塞状态结束后才能继续执行。
-
-
- 线程让步
-
- 概述
调用yield()方法后会暂停当前线程的执行,使当前线程重新回到就绪状态等待调度器的调度。并交出Cpu的执行权。
- 说明
- Yield()执行后暂停当前线程的执行,当前线程会重新回到就绪状态
- 当前线程暂停执行后,会将Cpu的执行权交给优先级同等或更高的线程进行执行
- Yield()与Sleep
- Sleep()暂停后线程处于阻塞状态,到达阻塞时间后才回到就绪状态;而Yield()直接回到就绪状态
- Sleep()交出Cpu的执行权时,不管其他线程的优先级;yield()只会将Cpu的执行权交给优先级同等或更高优先级的线程
- Sleep()会抛出InterruptException,线程中需要进行异常处理,而yield()不会产生异常
-
- 后台线程
-
- 概述
在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。
所谓守护线程(后台线程),是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
- 说明
后台线程会随用户线程执行结束而结束。
-
-
- 强制线程
-
- 概述
通过调用join()方法可以使指定线程被强制执行。被强制执行的线程具有最高的优先级。
- 说明
- 被强制执行的线程具有最高的优先级
- 其他线程需要等待强制执行线程执行结束后才能被执行
-
- 中断线程
-
- 概述
interrupt()方法可以中断线程的执行,线程使中断(死亡)、等待还是继续运行由中断位标志决定的。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。
- 中断线程
中断过程中首先会检测当前线程是否处于阻塞状态(如线程调用了sleep、join、wait等操作方法后可进入阻塞状态)。如果是阻塞状态则在阻塞方法调用处产生一个异常。产生异常后会将线程中断标志设置为false,抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
-
-
- 线程的等待与唤醒
-
- 概述
Object类中提供了wait()、notify()及notifyAll()等方法用来实现线程的等待与唤醒。
对象调用了wait()方法后将处于等待状态,而唤醒则需要通过notify()或notifyAll()方法来进行实现。
- 说明
- Wait可以使一个线程处于等待状态,如果需要回复线程的执行则需要调用notify进行唤醒
- Notify可以唤醒在对象监视器上等待的单个线程
- 线程的状态转换
- 创建(New):当一个线程类通过new操作符实例化后,该线程对象就被创建了。
- 就绪(Runnable):当一个线程对象的start()方法被调用后,该线程对象就处于就绪状态。对象将进入到可执行队列。等待线程调度器进行调度。就绪是一个线程对象被运行的必要条件
- 运行(Running):当一个就绪状态的线程对象获取到Cpu的执行权时,线程将处于执行状态
- 阻塞(Blocked):一个正在运行中的线程的执行被中断,线程将处于阻塞状态。直到阻塞结束或被唤醒。阻塞状态中的线程才重新回到就绪状态
- 死亡(Dead):当一个线程执行结束或因为异常而中断,这就称为死亡。
-
- 线程安全
- 竞态条件与临界区
- 线程安全
竞态条件:当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。
临界区:导致竞态条件发生的代码区称作临界区。
-
-
- 线程安全与共享资源
-
被多线程操作的共享内存、文件、系统资源等都称为共享资源。
- 线程安全的
在多线程程序中,一个资源在多个线程访问后执行结果与预期的一样就称为线程安全的。否则就称为线程不安全的。
- 局部变量
局部变量是线程安全的。
- 全局变量
他是非线程安全的。
-
-
- 同步线程
-
线程安全多是由对于成员字段、静态太字段的写访问造成。对于字段的读访问都是线程安全的。通过线程同步的方式可以解决线程不安全的问题。
- 非线程安全的
代码没有按照预期的结果进行执行,出现了重复的数据。
- 同步与异步
- 同步:当A和B同时访问某一资源时,如果资源正在被A进行访问,B不能立刻进行访问,需要等待A访问结束后才能进行访问。这种情况称为同步
- 异步:当A和B同时访问某一资源时,如果资源正在被A进行访问,B可以立刻对A进行访问。且不需要任何等待,这种情况就称为异步。
在多线程环境中,异步可能会导致数据问题。通过synchronized可以实现线程同步。
- 实现方式
- 同步代码块
- 同步方法
-
- 同步代码块
-
- 同步锁
- 实现方式
实现方式可以使用任意类型的对象作为同步锁。
-
-
- 同步方法
-
- 说明
- 静态方法加锁,监视器对象时类
- 实例方法加锁,监视器是this
- 一般不建议使用同步方法,因为其会降低代码的效率
-
- Lock锁
-
- 概述
Lock接口提供的锁可以更方便的对共享资源进行锁定。Lock允许实现对资源的并发访问,且可以实现锁链的使用。
- 常用方法
- Synchnornizied和Lock
- Synchnorinized可以自动对锁进行释放;Lock需要手动对锁进行释放
- Synchnorinized可实现代码块的同步锁,也可以实现方法同步锁;Lock只能实现代码块的同步锁
- Synchnorinized需要指定锁对象;lock不需要
- Lock比Synchnorinized更加灵活,支持锁链及对资源的并发访问
- 死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。产生死锁的原因,主要包括:
系统资源不足;
1.20.线程池
线程池的概念类似人才资源中心,原本每次要运行一个线程(公司找一个员工)都必须去创建(培养)一个员工才行,有了线程池之后就在线程池中准备了多个创建好的线程(培训好的员工)等待被调用
IO流
-
- IO
IO是Input和Output的缩写。称为输入与输出。
- 输入:程序从外部设备、文件及系统中读取数据就称为输入。
- 输出:程序向外部设备、文件及系统中写入数据就称为输出。
- File类
- 概述
- File类
File是文件和路径的抽象表现形式,File类中提供了用于操作文件和路径的相关方法。
-
-
- 路径
-
- 绝对路径:具体路径位置。如:F:\files\sys
- 相对路径:相对于当前位置的路径表现形式,相对路径是不完整的路径。如:\sys
- IO流
- 概述
- IO流
- IO流:程序以连续不断的方式对媒体进行读写操作就称为IO流
-
- IO流分类
-
- 字节流:所谓字节流指示以二进制方式进行传输的内容。一般多用于操作图片、音频、视频等文件。
- 字符流:所谓字符指示常用的英文字符、数字、符号及文字内容。字符流就是以字符的方式对媒介进行操作。多用于操作文本文件
-
- 常用类
-
读(I) | 写(o) | |
字节 | InputStream | OutputStream |
字符 | Reader | Writer |
-
-
- 字节方式写文件
-
- 类
FileOutputStream
- 作用
以字节流方式向文件中写入数据。
-
-
- 字节方式读文件
-
- 类
FileInputStream
- 作用
以流的方式从文件读取内容。
-
-
- 字符方式写文件
-
- 类
FileWriter
- 作用
以字符的方式向文件中写数据。
-
-
- 字符方式读文件
-
- 类
FileReader
- 作用
以字符的方式从文件中读取数据。
-
-
- 带有缓冲区的字节输入流
-
- 类
BufferedInputStream
- 作用
为输入流提供一个缓冲区,可以有效提高读取效率。
- 说明
- 使用结束后一定要关闭缓冲区
- 关闭一定要先关缓冲区,然后在关闭输入流对象
- 带有缓冲区的字节输出流
- 类
BufferedOutputStream
- 作用
提供一个带有缓冲区的输出流对象
-
-
- 带有缓冲区的字符输入流
-
- 类
BufferedReader
- 作用
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
-
-
- 带有缓冲区的字符输出流
-
- 类
BufferedWriter
- 作用
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入
- 总结
- 带有缓冲区的输入输出流对象的内部都包含了一个数组(char或byte类型)
- 带有缓冲区的输入输出流对象可以提高效率
- 实际使用时还是调用了基本的流对象的方法
-
- 转换流
-
- 作用
将一个字节流对象转换字符流对象进行输出。转换后可以提高效率。同时,在转换过程中,可以设置编码格式,以解决乱码问题。
转换流对象是字节流到字符流的一个桥梁。
- 字符集
- ASCII码:英语字符与二进制位之间对应。用七位二进制数进行表示
- ISO-8859-1:拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等
- GBxxxx:显示中文而设计的一套字符集(GBK、GB2312)
- Uncode:表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
-
- 输入转换流
-
- 类
InputStreamReader
可以有效解决字节流读取时中文乱码的问题。
-
-
- 输出转换流
-
- 类
OutputStreamWriter
-
-
- 序列化
-
- 持久化
程序中的数据是临时存储在内存中的,当程序退出或电脑关机数据将会丢失。数据只是被临时存储。
将数据长时间进行存储,不会收到程序或电脑关机等因素的影响(长期储存)。
常见的持久化方式:写入到数据库、写入到文件等。
- 对象输出流
- 对象输入流
- 存储对象
- 序列化与反序列化
- 序列化:所谓序列化就是将数据按照特定的格式进行保存,且保留数据的基本特征。
- 反序列化:反序列化就是对文件中保存的数据进行还原
- 说明
- 凡是实现了Serializable接口的类型对象都可以被序列化
- 类型中凡是被static、transient关键字修饰的字段都不会被序列化
- 序列化对象的serialVersionUID版本号不同时,无法进行读取
- 反射
- 类的执行
- Java代码被javac编译后将生成.class文件,执行时class文件会被加载到jvm虚拟机
- Jvm虚拟机内部 会判断代码是否被编译执行,如果是则立即编译执行,否则将被解释执行
- 解释执行过程中会记录代码的执行次数,当执行达到一定频率时将会将代码进行编译执行
在Jvm的内容同时存在即时编译器和解释器。这也是Java代码执行效率相对较高的原因。
-
- 类的加载
所谓类的加载是指将一个字节码文件(.Class)以java.lang.Class类型数据结构加载内存中。以便程序在使用时可以对类型进行实例化。
-
- 类的生命周期
一个类的加载会经历加载、连接、初始化、使用和卸载五个阶段。类的生命周期中加载不等于类的加载,加载只是类加载的一个步骤。
-
-
- 加载
-
一个class文件会先加载到方法区中,然后再加载到堆内存。只有加载到堆内存后程序才能实例化对象。
-
-
- 验证
-
验证过程包含:文件格式验证、元数据、字节码验证及引用符号验证。
文件格式验证
文件格式验证会对文件的结构进行验证,通过验证的字节码文件的静态结构会加载到方法区中,同时会在对中加载字节码文件。以便可以构建除一个Object对象。
- 元数据、字节码验证
元数据和字节码的验证主要是验证是否有影响到虚拟机安全的代码。
-
-
- 解析
-
解析可能发生在初始化之前,也可能发生在初始化之后。整个生命周期中只有解析是特殊的。
-
-
- 初始化
-
初始化过程中主要是对主动加载资源进行初始化。如:成员字段、静态代码块等。
-
- 概述
在程序运行过程中可以动态的获取到对象的相关属性、方法的信息。并对其进行动态使用的技术就称为反射。
-
- Java.lang.Class
Class一般称为类型(class)的类型,通过Class类型的对象可以对任意类型信息进行描述(元数据),并生成一个静态结构。
-
- 总结
- 在很多底层框架中都有对反射的使用
- 在很多公司的自己封装的框架中也都用到了反射技术
- 反射功能很强大,但会降低程序的执行效率
- 泛型
- 概述
泛型可以称为类型参数化。泛型可用于类、接口、方法。
-
- 实现
- 泛型类
- 实现
- 语法
class 类名 <占位符列表>{ 类成员; } |
- 说明
- 占位符列表:定义泛型类时,占位符可以是多个
- 占位符可以是任意的字符。一般常用T、K、V、E等字符
- 实现分析
- 泛型类相当于一个占位符
- 使用泛型类型时需要先传递实际的数据类型
- 代码编译过程中,将使用实际的数据类型去替换类型占位符(泛型只发生在编译阶段)
- 注意事项
- 实例化泛型对象时,只能传递引用类型,不能传递基本类型
- 泛型对象无法使用instanceof运算符判断对象是否属于某个泛型类(运行时会执行类型擦除操作)
-
- 泛型接口
-
注意
- 泛型接口可以有0或多个类型参数
- 当泛型接口的实现类有0个类型参数时,必须在继承中直接给出参数的具体类型
- 实例化0个类型参数的对象时,声明中的类型参数的类型必须与实现类所传递的类型相一致
- 总结
- 定义泛型类时,类型参数可以有多个
- 不同类型的泛型对象之间是不兼容的
- 应用场景
- 代码中同一个类型中可以使用相互之间无关联的多种类型时可以使用泛型