Java基础
Java学习!
第一天
Hello World
1 | // 第一行的第三个单词必和文件名一样 |
基本数据类型
整数型 byte short int long
浮点型 float double
字符型 char
布尔型 boolean
P.S float的数据范围比long大
自动类型转换:可以从小到大转(数据范围)
强制类型转换:可以大到小,也可以小到大;(一般不推荐使用,可能会发生数据精度缺失)
byte /shot/char运算时首先提升为int型;
1 | public class Main { |
boolean型不能发生强制类型转换!
int + double —> double + double —>double
第二天
四则运算
+号
对于字符串来说“+”号代表着字符串连接操作!任何数据类型和字符串进行连接时,结果都会变成字符串;
自增 自减
前置:变量先自增(减),后使用;
后置:变量先使用,后自增(减);
方法
定义方法
1 | /* |
第三天
方法重载
多个方法的名称一样,但是参数列表不同;
与下列因素有关:
1.参数个数不同;
2.参数类型不同;
3.参数的多类型顺序不同;
与下列因素无关:
1.参数名称无关;
2.返回值类型无关;
数组
初始化数组:
动态(指定长度): 数据类型[] 数组名称 = new 数据类型[数组长度];
元素自动初始化为0(整型)/0.0(浮点型)/‘\u0000’(字符型)/false(布尔型)/null(引用类型 );
静态(指定内容): 数据类型[] 数组名称 = new 数据类型[] { 元素1, 元素2,};
静态省略格式: 数据类型[] 数组名称 = { 元素1, 元素2,};.
面向对象
“静态创建对象指的是在栈上创建对象,比如A a;它会在对象的作用域结束后自动销毁. 动态创建对象指的是在堆上创建对象,然后栈上的指针指向创建的对象.比如A *pa = new A();它需要程序员手动delete掉.如果不进行delete操作,只能等到程序结束后,由OS来回收掉.
类
1 | public class Student { |
通常情况下,一个类不能直接使用,需要根据类创建一个对象,才能使用;
1.导包
1 | import 包名称.类名称 |
2.创建
1 | 类名称 对象名 = new 类名称(); |
3.使用
使用成员变量: 对象名.成员变量
使用成员方法: 对象名.成员方法
第四天
Scanner类
可以实现键盘输入数据到程序中;
1 | //导包 |
匿名对象
1 | public class Person { |
使用建议:如果确定有一个对象只需要使用唯一一次,就可以使用匿名对象;
注意事项:匿名对象只能使用唯一的一次,下次使用不得不再创建一个对象;
Random类
1 | import java.util.Random |
ArrayList类
ArrayList类集合不同于数组,数组长度不可变,ArrayList长度可变;
< E > 泛型 代表着装在集合中的所有元素都是同一类型;注意 只能是引用类型,不能是基本类型;
对于ArrayList集合来说,直接打印得到的不是地址值,而是内容;若内容为空,则得到空的中括号;
1 | public class demoArray { |
如果希望向集合ArrayList当中存储基本类型数据,必须使用基本类型对于的包装类
1 | /* |
第五天
String类
String类代表字符串;字符串是常量;
字符串的效果相当于是char[]字符数组,但底层原理是byte[]字节数组;
1 | //三种构造方法 |
第六天
static静态
一旦使用static关键字,那么这样的内容不再属于对象自己,而是属于类的,所以凡是本类的对象,都共享同一份;
一旦使用static修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,属于类;
如果没有static关键字,那么必须先创建对象,然后通过对象才能使用成员方法;
对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称来调用;
P.s 静态不能直接访问非静态;原因:在内存当中先有的静态内容,后有的非静态内容;
静态方法当中不使用this关键字;
1 | public class Student { |
Arrays类
java.util.Arrays是一个与数组相关的工具类,里面提供了大量静态方法,用来实现数组常见操作;
1 | public static String toString(数组):将参数数组变成字符串(按照默认格式:[元素1,元素2,元素3]); |
Math类
java.util.Math是一个与数学相关的工具类,里面提供了大量静态方法,用来实现数学常见操作;
1 | public static double abs(double num):获取绝对值 |
第七天
面向对象
三大特征:封装性,继承性,多态性;
继承是多态的前提;如果没有继承,就没有多态;
继承主要解决的问题就是:共性抽取
共性存放于父类,也叫基类,超类;
特殊的存放于子类,也叫派生类;
继承关系中的特点:
1.子类可以拥有父类的“内容”;
2.子类还可以拥有自己的专属内容;
1 | /* |
第八天
抽象类
1 | /* |
接口
接口就是一种公共的标准规范;
1 | /* |
第九天
多态
一个对象拥有多种形态,这也就是对象的多态
1 | /* |
第十天
final关键字
1 | /* |
权限修饰符
1 | /* |
第十一天
Object类
java.lang.Object类是Java语言中的根类,即所有类的父类;
1 | /* |
Objects类
1 | /* |
第十一天
内部类
1 | /* |
第十二天 日期时间类
Date类
1 | /* |
DateFormat类(抽象类)
1 | /* |
第十三天
枚举类
1 | /* |
topview面试问题总结
1.面向接口编程
疑问:为什么有的地方必须使用接口而不是抽象类,而在另一些地方,又必须使用抽象类而不是接口呢?
原则:行为模型应该总是通过接口而不是抽象类定义;
根本的原因在于使用抽象类不仅意味着定义特定的行为,而且意味着定义实现的模式。也就是说,应该定义一个事物如何获得行为的模型,而不仅仅是声明事物具有某一个行为。
interface关键字用来声明一个接口,它可以产生一个完全抽象的类,并且不提供任何具体实现。interface的特性整理如下:
接口中的方法可以有参数列表和返回类型,但不能有任何方法体。
接口中可以包含字段,但是会被隐式的声明为static和final。
接口中的字段只是被存储在该接口的静态存储区域内,而不属于该接口。
接口中的方法可以被声明为public或不声明,但结果都会按照public类型处理。
当实现一个接口时,需要将被定义的方法声明为public类型的,否则为默认访问类型,Java编译器不允许这种情况。
如果没有实现接口中所有方法,那么创建的仍然是一个接口。
- 扩展一个接口来生成新的接口应使用关键字extends,实现一个接口使用implements。
interface在某些地方和abstract有相似的地方,但是采用哪种方式来声明类主要参照以下两点:
如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。
如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。
接口应有两类:
第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
Linkedlist的底层实现逻辑
ArrayList和LinkedList的区别如下:
- ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。
- 对于随机访问,ArrayList优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问。而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)
- 对于插入和删除操作,LinkedList优于ArrayList,因为当元素被添加到LinkedList任意位置的时候,不需要像ArrayList那样重新计算大小或者是更新索引。
- LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
第十五天
容器
数组
类型确定,长度固定,不适合进行增删操作
集合
大小不固定,类型也不固定,适合进行元素的增删操作
不支持基本类型
1 | /* |
数据结构
精心选择的数据结构可以带来更高的运行或者存储效率
抽象数据类型(ADT):一个数学模型以及定义在这模型上的一系列操作
1 | /* |
集合工具类
1 | Collections类 |
第十六天
不可变集合
集合的数据项在创建的时候提供,并且在整个生命周期都不可改变;
当集合对象被不可信的库调用时,不可变形式是安全的;
在List,Set,Map接口当中,都存在of方法,可以创建一个不可变的集合;
Lambda表达式
作用:简化匿名内部类的代码写法(只能简化函数式接口的匿名内部类的写法形式)
函数式接口:首先必须是接口,其次接口中有且仅有一个抽象方法的形式;通常会在接口上加上@FunctionalInterface注解,标记该接口必须式满足函数式接口;
1 | 省略写法: |
Stream流
得益于Lambda所带来的函数式编程,引入的全新概念 =》流水线
目的:用于简化集合和数组操作的API
使用步骤:
1.先得到集合或数组的Stream流;
1 | //集合获取当前对象的Stream流 |
2.把元素放上去;
3.用stream流的简化API操作元素;
1 | //用于对流中的数据进行过滤。 |
异常
程序在“编译”或者“执行”的过程中可能出现的问题,注意:语法错误不算在异常体系中;
异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止;
Throwable | ||||
---|---|---|---|---|
Error | Exception | |||
RuntimeException | 除RuntimeException之外的所有异常 | |||
Error:系统级别问题,JVM退出等,代码无法控制
Exception:称为异常类,它表示程序本身可以处理的问题
RuntimeException及其子类:运行时异常,编译阶段不会报错;
除RuntimeException之外的所有异常 :编译时异常,编译期必须处理的,否则程序不能通过编译;
默认处理流程:
1.默认会在出现异常的代码那里自动的创建一个异常对象: ArithmeticException 。
2.异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM 虚拟机。
3.虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
4.直接从当前执行的异常点干掉当前程序。
5.后续代码没有机会执行了, 因为程序已经死亡。
异常处理机制:
处理方式1:throws 异常1,异常2…
用在方法上,可以将方法内部出现的异 常抛出去给本方法的调用者处理;规范做法:直接抛出Exception;
处理方式2:try…catch…
监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行;建议直接catch(Exception)异常,然后打印异常栈信息;
处理方式3 前两者结合(推荐)
方法直接将异常通过throws抛出给调用者,调用者收到异常后直接捕获处理;
自定义异常
1.自定义编译时异常
· 定义一个异帛类继承Exception.
· 重写构造器。
· 在出现异常的地方用throw new 自定义对象抛出。
作用:编译时异常是编译阶段就报错,提醒更加强烈!
throw :在方法内部直接创建一个异常对象,并从此点抛出
throws:用在方法声明上的,抛出方法内部的异常;
2.自定义运行时异常
· 定义一个异帛类继承RuntimeException.
· 重写构造器。
· 在出现异常的地方用throw new 自定义对象抛出。
作用:编译阶段不报错,提醒不强烈,运行时才可能出现!
第十七天
日志
程序中的日志可以用来记录程序运行过程中的信息,并可以进行永久存储。
输出语句的弊端:
· 信息只能展示在控制台;
· 不能将其记录到其他的位置(文件,数据库);
· 想要取消记录的信息需要修改代码才可以完成;
· 多线程性能较差;
日志技术的优势:
· 可以将系统执行的信息选择性的记录到指定的位置( 控制台、文件中、数据库中) 。
· 可以随时以开关的形式控制是否记录日志, 无需修改源代码。
· 多线程性能较好;
1 | /*日志规范接口 - 提供给日志的实现框架设计的标准 |
Logback
概述:
logback 分为三个模块,logback-core、logback-classic 和 logback-access。
· logback-core 模块为其他两个模块奠定了基础。
· logback-classic 模块可以同化为 log4j 1.x 的显着改进版本。此外,logback-classic 原生实现了SLF4J API,因此您可以轻松地在 logback 和其他日志框架(例如 log4j 1.x 或 java.util.logging (JUL))之间来回切换。
· logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能。
!!需第三方jar包
入门:
1.在项目下新建文件夹lib,导入Logback的相关jar包到该目录下,并添加到项目依赖库中去;
2.将logback的核心配置文件logback.xml直接拷贝到src目录下(必须是src下);
3.在代码中获取日志的对象
1 | public static final Logger LOGGER = LoggerFactory.getLogger("类对象"); |
第十八天
File类
可以定位文件:进行删除,获取文本信息等操作;
但不能读写文件内容
File对象可以定位文件和文件夹
File封装的对象仅仅是一个路径名,这个路径可以存在也可以不存在;
1 | //在操作文件时一定要使用 File.separator 表示分隔符 |
方法名称 | 说明 |
---|---|
boolean canRead() | 测试应用程序是否能从指定的文件中进行读取 |
boolean canWrite() | 测试应用程序是否能写当前文件 |
boolean delete() | 删除当前对象指定的文件(占用可删),非空文件夹不能删除;(不走回收站!) |
boolean exists() | 测试当前 File 是否存在 |
String getAbsolutePath() | 返回由该对象表示的文件的绝对路径名 |
String getName() | 返回表示当前对象的文件名或路径名(如果是路径,则返回最后一级子路径名) |
String getParent() | 返回当前 File 对象所对应目录(最后一级子目录)的父目录名 |
boolean isAbsolute() | 测试当前 File 对象表示的文件是否为一个绝对路径名。该方法消除了不同平台的差异,可以直接判断 file 对象是否为绝对路径。在 UNIX/Linux/BSD 等系统上,如果路径名开头是一条斜线/ ,则表明该 File 对象对应一个绝对路径;在 Windows 等系统上,如果路径开头是盘符,则说明它是一个绝对路径。 |
boolean isDirectory() | 测试当前 File 对象表示的文件是否为一个路径 |
boolean isFile() | 测试当前 File 对象表示的文件是否为一个“普通”文件 |
long lastModified() | 返回当前 File 对象表示的文件最后修改的时间 |
long length() | 返回当前 File 对象表示的文件长度 |
String[] list() | 返回当前 File 对象指定的路径文件列表 |
String[] list(FilenameFilter) | 返回当前 File 对象指定的目录中满足指定过滤器的文件列表 |
boolean mkdir() | 创建一个目录,它的路径名由当前 File 对象指定 |
boolean mkdirs() | 创建一个多级目录,它的路径名由当前 File 对象指定 |
boolean renameTo(File) | 将当前 File 对象指定的文件更名为给定参数 File 指定的路径名 |
boolean creatNewFile() | 创建新文件,创建成功返回true,若失败或文件已经存在则返回false(几乎不用) |
File[] listFiles() | 返回当前目录下所有的”一级文件对象“的一个文件对象数组(常用);调用者不存在,返回null;调用者为一个空文件夹时,返回长度为0的数组;(常用) |
方法递归
方法直接调用自己,或者间接调用自己的形式称为方法递归(recursion);
递归死循环:递归方法无限调用自己,无法终止,出现栈内存溢出;
递归问题解决的思路:
~ 把一个复杂的问题层层转化为一个与原问题相似规模较小的问题来解决;
三要素: 递归公式 递归终点 递归方向
文件搜索(方法递归)
1 | /** |
字符集
ASCII字符集:包括了数字,英文,符号,使用一个字节存储一个字符,可以表示128个字符信息;
GBK字符集:Windows默认码表,兼容ASCII码表,也包括了几万个汉字,并支持繁体汉字以及部分日韩文字;两个字节表示一个中文;
Unicode码表:又称万国码,容纳世界上大多数国家的所有常见位置和符号;三个字节表示一个中文;
1 | //编码 |
IO流
输入输出流,用来读写数据的;
按流中数据最小单位划分:字节流,字符流;
字符流和字节流
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。字节流和字符流的区别:
(1)读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
(2)处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
(3)字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件,我们将在下面验证这一点。
结论:优先选用字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。但是字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。
输入字节流InputStream
1 | /** |
输出字节流OutputStream
1 | /** |
资源释放
try-catch-finally
finally 关键字用来创建在 try 代码块后面执行的代码块。
无论是否发生异常,finally 代码块中的代码总会被执行。
在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
在关闭流的时候,要对流进行校验是否为空,为空会抛出空指针异常;
try-with-resource
1 | private static void printFileJava7() throws IOException { |
输入字符流Read
1 | /** |
输出字符流Writer
1 | /** |
缓冲流
作用:缓冲流自带缓冲区,可以提高原始字节流,字符流读写数据的性能;
字节缓冲流
自带8KB的缓冲池
BufferInputStream
1 | //构造器 |
BufferOutputStream
1 | //构造器 |
字符缓冲流
BufferReader
1 | //构造器 |
BufferWriter
1 | //构造器 |
转换流
InputStreamReader
1 | //构造器 |
OutputStreamWriter
1 | //构造器 |
对象序列化
作用:以内存为基准,把内存中的对象存储到磁盘中去,称为对象序列化
对象字节输出流ObjectOutputStream
1 | //构造器 |
对象的反序列化
作用:以内存为基准,把磁盘中的对象数据恢复成内存中的对象,称为对象反序列化
对象字节输入流ObjectInputStream
1 | //构造器 |
关键字transient
修饰的成员不参与序列化;
序列化版本号
1 | //序列化的版本号要与反序列化的版本一致 |
打印流
作用:打印流可以实现方便、高效的打印数据到文件中去;
PrintStream
1 | //底层包装了缓冲流 |
PrintWrite
1 | //打印功能与PrintStream无区别,只不过支持字符数据输出,而PrintStream只能写字节 |
若打印流要进行追加,只能用低级管道构造打印流;
输出语句重定向
1 | System.setOut(PrintSream ps) |
Properties属性集对象
是Map集合的实现类
作用:代表一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。
属性文件:后缀是.properties结尾的文件,内容为key=value,用来做系统配置信息。
1 | //保存 |
IO框架
commons-io是apache开源基金组织提供的一组有关IO操作的类库,
可以挺提高IO功能开发的效率。commons-io工具包提供了很多有关io操作的类,
1 | //读取文件中的数据, 返回字符串 |
第十九天
多线程
线程是一个程序内部的一条执行路径
main方法的执行就是一条单独的路径
多线程是指软硬件上实现多条执行流程的技术
多线程的创建
方法一:继承Thread类
1 | /** |
方法二 实现Runnable接口
1 | /** |
方法三 Callable,FutureTask接口
方法一和方法二都存在一个问题:无返回结果!
解决方法:Callable和FutureTask来实现。
1 | /**: |
Thread类常用API
序号 | 方法描述 |
---|---|
1 | public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) 改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) 更改线程的优先级。 |
5 | public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() 中断线程。 |
8 | public final boolean isAlive() 测试线程是否处于活动状态。 |
静态方法
序号 | 方法描述 |
---|---|
1 | public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
2 | public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
3 | public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
4 | public static Thread currentThread() 返回对当前正在执行的线程对象的引用。 |
5 | public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。 |
线程安全
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题。
原因:存在多线程并发,同时访问共享资源,存在修改共享资源;
线程同步
· 为了解决线程安全问题
· 让多个线程实现先后依次访问共享资源,这样就解决了安全问题;
核心思想
1.加锁 把共享资源进行上锁,每次只能 一个线程进入访问,访问完毕以后解锁,然后其他线程才能进来;
方法一:同步代码块
作用:把出现线程安全问题的核心代码给上锁
原理:每次只能 一个线程进入访问,访问完毕以后解锁,然后其他线程才能进来;
1 |
|
方法二:同步方法
作用:把出现线程安全问题的核心方法给上锁
原理:每次只能 一个线程进入访问,访问完毕以后解锁,然后其他线程才能进来;
1 | /** |
同步代码块锁的范围更小,同步方法锁的范围更大
方法三:Lock锁
· 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。
· Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
· Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。
1 | //获取实现类对象(通常用final修饰) |
线程通信
线程间相互发送数据;
常见形式
1.通过共享一个数据的方式实现
2.根据共享数据的情况决定自己该怎么做,已经通知其他线程怎么做
线程通信实际应用场景
1.生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据。
要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己。
Object类等待和唤醒方法
1 | void wait() //让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法 |
线程池
可以复用线程的技术
不使用线程池的问题
如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。
线程池实现的API,参数说明
接口:ExecutorService
如何得到线程池对象
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象(推荐使用,参数可控)
1 | public ThreadPoolExecutor(int corePoolSize, //指定线程池的线程数量(核心线程) 不能小于零 |
新任务拒绝策略
策略 | 详解 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。是默认的策略 |
ThreadPoolExecutor.DiscardPolicy: | 丢弃任务,但是不抛出异常 这是不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 |
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象(不推荐使用)
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
public static ExecutorService newSingleThreadExecutor () | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
public static ExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的
Executor使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
方法名称 | 存在问题 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError ) |
public static ExecutorService newSingleThreadExecutor() | |
public static ExecutorService newCachedThreadPool() | 创建的线程数量最大上限是Integer.MAX_VALUE, 线程数可能会随着任务1:1增长,也可能出现OOM错误( java.lang.OutOfMemoryError ) |
public static ExecutorService newScheduledThreadPool(int corePoolSize) |
常见问题:
1.临时线程什么时候创建啊?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
2.什么时候会开始拒绝任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
ExecutorService常用API
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行 Runnable 任务 |
Future |
执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List |
立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
定时器
定时器是一种控制任务延时调用,或周期调用技术。
作用:闹钟、定时邮件发送。
实现方式
1.Timer
1 | //构造器 |
2.ScheduledExecutorService
ScheduledExecutorService是 jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池。
Executors的方法 | 说明 |
---|---|
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 得到线程池对象 |
ScheduledExecutorService的方法 | 说明 |
---|---|
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) | 周期调度方法 |
ScheduledExecutorService的优点
1、基于线程池,某个任务的执行情况不会影响其他定时任务的执行。
并发 并行
正在运行的程序(软件)就是一个独立的进程, 线程是属于进程的,多个线程其实是并发与并行同时进行的。
并发的理解
1.CPU同时处理线程的数量有限。
2.CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
线程的生命周期
状态
1 | //6种状态都定义在Thread类的内部枚举类中。 |
第二十天
Junit单元测试:
* 测试分类:
1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
2. 白盒测试:需要写代码的。关注程序具体的执行流程。
* Junit使用:白盒测试
* 步骤:
1. 定义一个测试类(测试用例)
* 建议:
* 测试类名:被测试的类名Test CalculatorTest
* 包名:xxx.xxx.xx.test cn.itcast.test
2. 定义测试方法:可以独立运行
* 建议:
* 方法名:test测试的方法名 testAdd()
* 返回值:void
* 参数列表:空参
3. 给方法加@Test
4. 导入junit依赖环境
* 判定结果:
* 红色:失败
* 绿色:成功
* 一般我们会使用断言操作来处理结果
* Assert.assertEquals(期望的结果,运算的结果);
* 补充:
* @Before:
* 修饰的方法会在测试方法之前被自动执行
* @After:
* 修饰的方法会在测试方法执行之后自动被执行
反射:框架设计的灵魂
* 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
* 反射:将类的各个组成部分封装为其他对象,这就是反射机制
* 好处:
1. 可以在程序运行过程中,操作这些对象。
2. 可以解耦,提高程序的可扩展性。
* 获取Class对象的方式:
1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
* 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
2. 类名.class:通过类名的属性class获取
* 多用于参数的传递
3. 对象.getClass():getClass()方法在Object类中定义着。
* 多用于对象的获取字节码的方式
* 结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
* Class对象功能:
* 获取功能:
1. 获取成员变量们
* Field[] getFields() :获取所有public修饰的成员变量
* Field getField(String name) 获取指定名称的 public修饰的成员变量
* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name)
2. 获取构造方法们
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?>[] getDeclaredConstructors()
3. 获取成员方法们:
* Method[] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4. 获取全类名
* String getName()
* Field:成员变量
* 操作:
1. 设置值
* void set(Object obj, Object value)
2. 获取值
* get(Object obj)
3. 忽略访问权限修饰符的安全检查
* setAccessible(true):暴力反射
* Constructor:构造方法
* 创建对象:
* T newInstance(Object... initargs)
* 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
* Method:方法对象
* 执行方法:
* Object invoke(Object obj, Object... args)
* 获取方法名称:
* String getName:获取方法名
* 案例:
* 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
* 实现:
1. 配置文件
2. 反射
* 步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法
注解:
* 概念:说明程序的。给计算机看的
* 注释:用文字描述程序的。给程序员看的
* 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
* 概念描述:
* JDK1.5之后的新特性
* 说明程序的
* 使用注解:@注解名称
* 作用分类:
①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
* JDK中预定义的一些注解
* @Override :检测被该注解标注的方法是否是继承自父类(接口)的
* @Deprecated:该注解标注的内容,表示已过时
* @SuppressWarnings:压制警告
* 一般传递参数all @SuppressWarnings("all")
* 自定义注解
* 格式:
元注解
public @interface 注解名称{
属性列表;
}
* 本质:注解本质上就是一个接口,该接口默认继承Annotation接口
* public interface MyAnno extends java.lang.annotation.Annotation {}
* 属性:接口中的抽象方法
* 要求:
1. 属性的返回值类型有下列取值
* 基本数据类型
* String
* 枚举
* 注解
* 以上类型的数组
2. 定义了属性,在使用时需要给属性赋值
1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
* 元注解:用于描述注解的注解
* @Target:描述注解能够作用的位置
* ElementType取值:
* TYPE:可以作用于类上
* METHOD:可以作用于方法上
* FIELD:可以作用于成员变量上
* @Retention:描述注解被保留的阶段
* @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
* @Documented:描述注解是否被抽取到api文档中
* @Inherited:描述注解是否被子类继承
* 在程序使用(解析)注解:获取注解中定义的属性值
1. 获取注解定义的位置的对象 (Class,Method,Field)
2. 获取指定的注解
* getAnnotation(Class)
//其实就是在内存中生成了一个该注解接口的子类实现对象
public class ProImpl implements Pro{
public String className(){
return "cn.itcast.annotation.Demo1";
}
public String methodName(){
return "show";
}
}
3. 调用注解中的抽象方法获取配置的属性值
* 案例:简单的测试框架
* 小结:
1. 以后大多数时候,我们会使用注解,而不是自定义注解
2. 注解给谁用?
1. 编译器
2. 给解析程序用
3. 注解不是程序的一部分,可以理解为注解就是一个标签