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方法的类时的操作

  1. 以main方法的类作为入口
  2. 给本类static修饰的成员变量赋一个默认值,如果还被final修饰,就赋值为定义的那个值,执行本类的static代码块(先执行static代码块还是成员变量由在代码中的位置决定)
  3. 其次是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;
    }
}

构建这个泛型的对象

其中可以指定为任意类型

People people = new People<>();
        people.setName("happy");
People2 people2 = 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;
    }
}
People3 people3 = 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对象中

获取反射对象

  1. 通过路径

    Class<?> c1 = Class.forName("com.smu.User");
  2. 通过对象获取    
    Class<? extends User> aClass = user.getClass();
  3. 通过类名获取
    Class userClass = User.class;
  4. 基本内置对象的封装类都有的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循环和迭代器的区别

  1. 迭代器是用于方便集合遍历的,实现了Iterable接口的集合都可以使用迭代器来遍历。使用迭代器遍历元素时,除了查看之外,只能做remove操作。
  2. 增强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();
    }
}