Java se
Lamda表达式
对于functional interface(函数式接口)可以使用lamda表达式
- 任何接口,如果只包含一个抽象方法,就是一个函数式接口
Hello hello = new Hello() { @Override public void sayHello() { System.out.println("hello,happy"); } }; hello.sayHello(); Hello helloLamda = ()-> { System.out.println("hello,happy3"); }; helloLamda.sayHello();
简单了解java类加载的运行顺序
进入main方法的类时的操作
- 以main方法的类作为入口
- 给本类static修饰的成员变量赋一个默认值,如果还被final修饰,就赋值为定义的那个值,执行本类的static代码块(先执行static代码块还是成员变量由在代码中的位置决定)
- 其次是static方法
当使用 类的static属性 或者static方法 或者new一个对象时会触发类加载
当new一个时,如果父类还没有被初始化,会先对其父类先初始化
如果已经初始化了,会先执行父类的构造器
泛型
泛型只存在于编码阶段,在运行阶段会被擦除,目的是为了兼容老版本的jdk
泛型的通配符为“?”,泛型的通配符使用的上边界以及下边界问题
extends 上边界 当需要读取,但不需要写入
super 下边界 当需要写入,但不需要读写
自定义一个泛型类
public class People{ private T name; public T getName() { return name; } public void setName(T name) { this.name = name; } }
public class People2{ private K name; private V age; public K getName() { return name; } public void setName(K name) { this.name = name; } public V getAge() { return age; } public void setAge(V age) { this.age = age; } }
构建这个泛型的对象
其中可以指定为任意类型
Peoplepeople = new People<>(); people.setName("happy");
People2people2 = new People2<>(); people2.setName("happy"); people2.setAge(12);
定义一个泛型方法以及应用
静态的泛型方法需要自己声名占位符,与类的占位符无关,同理,普通方法也可以自定义
public class People3{ public void show(T name){ System.out.println(name); } public T show2(T name){ return name; } public M show3(M name){ return name; } public static void showStatic(E name){ System.out.println(name); } public static V showStatic2(V name){ return name; } }
People3people3 = new People3<>(); people3.show("11111"); System.out.println(people3.show2("返回值的普通泛型方法")); People3.showStatic("happyStatic"); System.out.println(People3.showStatic2(111));
定义泛型接口
1:其实现类给接口指定类型
2:实现类也声明一个泛型
注解与反射
注解
@Target({ElementType.TYPE,ElementType.METHOD})//表示注解的作用域
@Retention(RetentionPolicy.RUNTIME)//什么时候生效
@Documented//表示方法已经过时,但是还能用
@Inherited//表示子类可以继承
public @interface MyAnnotation {
String value() default "";
}
反射
一个类只有一个class对象
类被加载后,类的整个结构都被封装在class对象中
获取反射对象
-
通过路径
Class<?> c1 = Class.forName("com.smu.User"); - 通过对象获取
Class<? extends User> aClass = user.getClass();
- 通过类名获取
Class
userClass = User.class; - 基本内置对象的封装类都有的TYPE属性
Class
type = Integer.TYPE;
通过反射获取类的信息
获取类名
- String name = c1.getName();
- String simpleName = c1.getSimpleName();
获取字段(带有Declared的是获取包括private的字段)
- Field[] fields = c1.getFields();
- Field username = c1.getField("username");
- Field[] declaredFields = c1.getDeclaredFields();
- Field username1 = c1.getDeclaredField("username");
获取方法(不带Declared是获取本类以及父类的public方法,带有Declared的是获得本类的所有包括private的方法)
- Method[] methods = c1.getMethods();
- Method getAge = c1.getMethod("getAge");
- Method[] declaredMethods = c1.getDeclaredMethods();
- Method setPassword = c1.getDeclaredMethod("setPassword", String.class);
获取构造器
- Constructor<?>[] constructors = c1.getConstructors();
- Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors();
- Constructor<?> declaredConstructor1 = c1.getDeclaredConstructor(String.class, String.class, Integer.class);
获取对象
通过class对象获取(需要有无参构造器,需要是public修饰的),通过构造器获取,如果是private修饰的需要setAccessible(true);
- User o = (User) c1.newInstance();
- o.setAge(12);
- System.out.println("class对象获取"+o);
- Constructor<?> declaredConstructor = c1.getDeclaredConstructor(String.class, String.class, Integer.class);
- declaredConstructor.setAccessible(true);
- User happy = (User) declaredConstructor.newInstance("happy", "889886", 21);
- System.out.println("通过构造器获取"+happy);
执行方法
可以通过类对象调用私有方法,也可以直接通过class对象获取方法
- Constructor<?> declaredConstructor = c1.getDeclaredConstructor(String.class, String.class, Integer.class);
- declaredConstructor.setAccessible(true);
- Object happy1 = declaredConstructor.newInstance("happy", "889886", 15);
- Method setUsername = c1.getDeclaredMethod("setUsername", String.class);
- setUsername.invoke(happy1, "sjjshj");
多线程
三种创建方式
- 继承Thread(Thread实现了Runnable接口)
- 实现Runnable(这种方式需要将该实现类作为参数调用Thread对象)
- 实现Callable
线程的状态
线程方法
yield()方法会重新让线程回到就绪状态,所以不一定会礼让成功
join()一定会礼让成功
线程状态synchronized默认锁的对象是this
CopyOnWriteArrayList<?>()是线程安全的List
死锁的产生条件
什么时候会释放锁
由于等待一个锁定线程只有在获得这把锁之后,才能恢复运行,所以让持有锁的线程在不需要锁的时候及时释放锁是很重要的。在以下情况下,持有锁的线程会释放锁:
1、当前线程的同步方法、代码块执行结束的时候释放
2、当前线程在同步方法、同步代码块中遇到break 、 return 终于该代码块或者方法的时候释放。
3、当前线程出现未处理的error或者exception导致异常结束的时候释放
4、调用obj.wait()会立即释放锁,当前线程暂停,释放锁,以便其他线程可以执行obj.notify(),但是notify()不会立刻立刻释放sycronized(obj)中的obj锁,必须要等notify()所在线程执行完synchronized(obj)块中的所有代码才会释放这把锁。而 yield(),sleep()不会释放锁。
除了以上情况外,只要持有锁的此案吃还没有执行完同步代码块,就不会释放锁。因此在以下情况下,线程不会释放锁:
1. 在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。
2. 在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。
3. 在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。但Thread类的suspend()方法已经被废弃
关于Lock锁
每个对象都有锁,关于Object提供的几个方法
线程池
使用方式
具体实现方式1
具体实现方式2
volatile 与 synchronized 的比较:
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
线程安全性包括两个方面,①可见性。②原子性。
仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。
集合
for循环、增强for循环和迭代器的区别
- 迭代器是用于方便集合遍历的,实现了Iterable接口的集合都可以使用迭代器来遍历。使用迭代器遍历元素时,除了查看之外,只能做remove操作。
- 增强for循环,内部使用的是迭代器,所以它的操作对象是数组和可以使用迭代器的集合。遍历时只能查看,无法修改、删除、增加。所以如果需要对遍历的对象做增删修改的操作,使用普通的for循环来操作。
常用集合的分类:
Collection 接口的接口 对象的集合(单列集合)
├——-List 接口:元素按进入先后有序保存,可重复│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
│—————-└ Vector 接口实现类 数组, 同步, 线程安全
│ ———————-└ Stack 是Vector类的实现类
└——-Set 接口: 仅接收一次,不可重复,并做内部排序
├—————-└HashSet 使用hash表(数组)存储元素
│————————└ LinkedHashSet 链表维护元素的插入次序
└ —————-TreeSet 底层实现为二叉树,元素排好序
Map 接口 键值对的集合 (双列集合)
├———Hashtable 接口实现类, 同步, 线程安全
├———HashMap 接口实现类 ,没有同步, 线程不安全-
│—————–├ LinkedHashMap 双向链表和哈希表实现
│—————–└ WeakHashMap
├ ——–TreeMap 红黑树对所有的key进行排序
└———IdentifyHashMap
list和set的区别
list集合的特点
- ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
- LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
- Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素
set集合的特点
- HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。
- LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高。
- TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造),自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;比较器排需要在TreeSet初始化是时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法;
map集合
HashMap 非线程安全
- HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
- TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
适用场景分析:
HashMap和HashTable:HashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。HashMap允许空键值,而HashTable不允许。
HashMap:适用于Map中插入、删除和定位元素。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
线程安全集合类与非线程安全集合类
LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
HashMap是非线程安全的,HashTable是线程安全的;
StringBuilder是非线程安全的,StringBuffer是线程安全的。
数据结构
ArrayXxx:底层数据结构是数组,查询快,增删慢
LinkedXxx:底层数据结构是链表,查询慢,增删快
HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序
文件操作
构造方法
public File(String pathname);//主要在Java EE的开发之中public File(File parent, String child);//主要在Android开发之中
获取文件的基本信息
除了以上的常用的方法之外,在File类之中还可以通过以下的方法取得一些文件的基本信息:
(1)取得文件的名称:
public String getName();
(2)给定的路径是否是文件夹:
public boolean isDirectory();
(3)给定的路径是否是文件:
public boolean isFile();
(4)是否是隐藏文件:
public boolean isHidden();
(5)文件的最后一次修改日期:
public long lastModified();
(6)取得文件大小:
public long length();
字节流与字符流
(1)字节操作流:OutputStream、InputStream;
(2)字符操作流:Writer、Reader。
字节流
不管是写入还是读取,都是对byte()进行操作
OutputStream和InputStream是字节流的两个顶层父类。让他们提供了输出流类和输入流类通用API,字节流一般用于读写二进制数据
OutputStream output = new FileOutputStream(file, true);对原有数据不覆盖
OutputStream output = new FileOutputStream(file);会对原有数据覆盖
- 写入单个字节数据:public abstract void write(int b) throws IOException;
- 写入一组字节数据:public void write(byte[] b) throws IOException;
- 写入部分字节数据:public void write(byte[] b, int off, int len) throws IOException;
import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; public class TestDemo { public static void main(String[] args) throws Exception { //第一步:定义文件路径 File file = new File("D:"+File.separator + "demo"+ File.separator + "test.txt"); if(!file.getParentFile().exists()){ file.getParentFile().mkdirs(); } //第二步:实例化输出流 OutputStream output = new FileOutputStream(file); String data = "hello world !\r\nhello world !\r\nhello world !\r\nhello world !"; // 第三步:输出数据,要将数据变为字节数组输出 output.write(data.getBytes()); //第四步:关闭资源 output.close(); } }
InputStream input = new FileInputStream(file);
(1)读取单个字节:
public abstract int read() throws IOException;
注意:每次执行read()方法都会读取一个数据源的指定数据,如果现在发现已经读取到了结尾返回-1;
(2)将读取的数据保存到字节数组中:
public int read(byte[] b) throws IOException;
注意:如果现在要读取的数据小于byte的数据,这个时候read()方法的返回值int返回的是数据个数,如果数据已经读完了,则这个时候的int返回的是-1;
(3)将读取的数据保存在部分字节数组中:
public int read(byte[] b, int off, int len) throws IOException;
import java.io.File; import java.io.FileInputStream; import java.io.InputStream; public class TestDemo { public static void main(String[] args) throws Exception { //第一步:定义文件路径 File file = new File("D:" + File.separator + "demo" + File.separator + "test.txt"); // 定义文件路径 if (file.exists()) { // 文件存在则可以读取 //第二步:实例化输入流 InputStream input = new FileInputStream(file); //第三步:读取数据到字节数组 byte data[] = new byte[1024]; // 假设要读的长度是1024 int len = input.read(data); // 读取数据,返回读取个数 //第四步:关闭资源 input.close(); System.out.println("读取的数据是:【" + new String(data, 0, len) + "】"); } } }
字符流
字符输出流:Writer
写入字符串不需要将字符串转换为字节形式
import java.io.File; import java.io.FileWriter; import java.io.Writer; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "demo" + File.separator + "test.txt"); // 定义文件路径 if (!file.getParentFile().exists()) { file.getParentFile().mkdirs();// 创建父目录 } Writer out = new FileWriter(file); String data = "Hello World ."; out.write(data); // 直接写入字符串 out.close(); // 关闭 } }
字符输入流:Reader
读取方法:public int read (char[] cbuf) throws IOException;
import java.io.File; import java.io.FileReader; import java.io.Reader; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "demo" + File.separator + "test.txt"); // 定义文件路径 if (file.exists()) { Reader in = new FileReader(file); // 字符输入流 char data[] = new char[1024]; // 开辟数组 int len = in.read(data); // 读取数据 in.close(); System.out.println("读取数据内容:【" + new String(data, 0, len) + "】"); } } }
完成一次文件复制
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; public class TestDemo { public static void main(String[] args) throws Exception { File inFile = new File("D:" + File.separator + "demo" + File.separator + "test.zip"); // 定义文件路径 File outFile = new File("D:" + File.separator + "demo" + File.separator + "test2.zip"); // 定义文件路径 long start = System.currentTimeMillis(); if (!inFile.exists()) { // 源文件不存在 System.out.println("源文件不存在!"); System.exit(1); // 程序退出 } if(!outFile.getParentFile().exists()){ outFile.getParentFile().mkdirs(); } InputStream input = new FileInputStream(inFile); OutputStream output = new FileOutputStream(outFile); int temp = 0;//保存每次读取的个数 byte data[] = new byte[4096]; // 每次读取4096字节 while ((temp = input.read(data)) != -1) { // 将每次读取进来的数据保存在字节数组里,并返回读取的个数 output.write(data, 0, temp); // 输出数组 } long end = System.currentTimeMillis(); System.out.println("拷贝完成,所花费的时间:" + (end - start) + "毫秒"); input.close(); output.close(); } }