第一天 类的加载与反射
使用一个还没被加载到内存中的类,jvm会通过加载、连接、初始化来对该类初始化。
什么是类的加载呢?类的加载就是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的连接:连接阶段负责把类的二进制数据合并到JRE中,分为三个步骤:
第一,验证,检验被加载的类是否有正确的内部结构;第二,准备,负责为类的类变量分配内存,并设置默认初始值。第三,解析将类的二进制数据中的符号引用替换成直接引用。
每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类
- 当你知道该类的全路径名时,使用Class类的forName(String clazzName)静态方法,如forname(“java.util.Random”)
- 调用某个类的class属性来获取该类对应的Class对象,如person.class,这种方法只适合在编译前就知道操作的 Class。
- 调用某个对象的getClass()方法,如p1.getClass()
反射测试代码:
class test {
private int age;
private String name;
private int testint;
public test(int age){
this.age=age;
}
public test(int age, String name){
this.age=age;
this.name=name;
}
public test(String name){
this.name=name;
}
public test(){
}
public static void main(String[] args) {
test test1=new test();
String s="java.util.Random";
try {
Class c1=Class.forName(s);
System.out.println(c1.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class c4=test1.getClass();
System.out.println(c4.getPackage());//获取包名
Constructor[] constructors;
//getDeclaredConstructors获取类的所有构造方法
constructors=c4.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
for (int i = 0; i < constructors.length; i++) {
//getModifiers获取构造方法类型
System.out.println(Modifier.toString(constructors[i].getModifiers()) + "参数:");
//getParameterTypes获取构造方法的参数类型
Class[]parametertypes=constructors[i].getParameterTypes();
for (int j = 0; j < parametertypes.length; j++) {
System.out.print(parametertypes[j].getName() + " ");
}
System.out.println("");
}
}
}
第二天 集合与泛型
集合
Collection集合的方法:
List:
(1)ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
(2)LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
(3)Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素
Set:
(1)HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。
(2)LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高。
(3)TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。
Map:
Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。
注意,Map 没有继承 Collection 接口!
(1)HashMap
使用位桶和链表实现(最近的jdk1.8改用红黑树存储而非链表),它是线程不安全的Map,方法上都没有synchronize关键字修饰
(2)HashTable
hashTable是线程安全的一个map实现类,它实现线程安全的方法是在各个方法上添加了synchronize关键字。
(3)TreeMap
TreeMap也是一个很常用的map实现类,因为他具有一个很大的特点就是会对Key进行排序,使用了TreeMap存储键值对
(4)LinkedHashMap
LinkedHashMap它的特点主要在于linked,带有这个字眼的就表示底层用的是链表来进行的存储。相对于其他的无序的map实现类,还有像TreeMap这样的排序类,linkedHashMap最大的特点在于有序,但是它的有序主要体现在先进先出FIFIO上。
泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
第三天 异常
一、异常
定义:Java 中的异常(Exception)又称为例外,是一个在程序执行期间发生的事件,它中断正在执行的程序的正常指令流。
在Java中,所有的异常都有一个共同的父类Throwable,该类有两个重要的子类:Exception和Error,二者都是Java异常处理的重要子类,各自都包含大量子类。它们都是java.lang下的类。
Error与Exception
Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
运行时异常和非运行时异常
运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,要么使用try-catch捕获,要么使用throws语句抛出,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
异常的处理
声明异常:throws
捕获异常:try,catch,finally
抛出异常throw
throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结 束当前方法的执行。
throw new 异常类名(参数);
声明抛出异常throws
运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常。
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2 ... { }
第四天 注解
Java 注解(Annotation)又称 Java 标注,是JDK5.0引入的一种注释机制。 注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
java内置注解
-
@Deprecated
意思是说此方法已过时,是因为有新的API的类替代了此方法。这个被划去的方法仍然是可以正常使用的。 -
@Override
用于标明此方法覆盖了父类的方法。 -
@SuppressWarnings
用于通知java编译器忽略特定的编译警告。 -
@FunctionalInterface
指定接口必须为函数式接口,如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。函数式接口”是指仅仅只包含一个抽象方法的接口。 -
@SafeVarargs
这个注解用来抑制堆污染警告
元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :@Target和@Retention。
java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Target – 注解用于什么地方
@Retention – 什么时候使用该注解
@Documented – 注解是否将包含在JavaDoc中
@Inherited – 是否允许子类继承该注解
注解语法
注解的定义:注解通过@interface 关键字进行定义。注解的定义格式是 :public @interface 注解名 {定义体}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
String value() default "";
}
第五天 IO
IO流的分类
· 根据数据处理的不同类型分为:字节流和字符流
· 根据数据流向不同分为:输入流和输出流
字节流和字符流的区别:
· 读写单位的不同:字节流以字节(8bit)为单位。字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
· 处理对象不同:字节流可以处理任何类型的数据,如图片、avi等,而字符流只能处理字符类型的数据。
输入流和输出流
对于输入流只能进行读操作。
对于输出流只能进行鞋操作。
程序中需要对于传输数据的不同特性而使用不用的流。
File类
File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。File类保存文件或目录的各种数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名、判断文件是否存在、获取当前目录中的文件列表、创建、删除文件和目录等方法。
对象序列化
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
用途
把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;(持久化对象)
在网络上传送对象的字节序列。(网络传输对象)
第六天 多线程
一些概念:
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
并行与并发:
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
多线程的创建方式
方式1:继承于Thread类
1.创建一个集成于Thread类的子类
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法
public class j6 {
public static void main(String[] args) {
new ThreadTest().start();
new ThreadTest().start();
System.out.println("main thread is running");
}
}
class ThreadTest extends Thread{
@Override
public void run() {
super.run();
System.out.println(Thread.currentThread().getName()+" is running");
}
}
输出的结果次序是随机的,跟每次程序的进程调度有关。
main thread is running
Thread-0 is running
Thread-1 is running
方式2:实现Runable接口方式
1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
//通过实现Runnable接口创建线程
class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}
class TestRunnable{
public static void main(String[] args) {
RunnableTest t=new RunnableTest();
Thread oneThread=new Thread(t);
oneThread.start();
}
}
结果
Thread-0 is running
比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式
原因1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中
线程的死锁问题:
线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)
出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续
死锁的解决办法:
1.减少同步共享变量
2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
3.减少锁的嵌套。