how2j学习笔记(JAVA中级)

  • Post author:
  • Post category:java


文章目录

针对how2j的JAVA基础做的个人笔记,若有帮助不胜荣幸

学习网址请点击:

https://how2j.cn?p=162023



常见问题



throw和throws的区别

  • throws 出现在方法声明上,而throw通常都出现在方法体内。
  • throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某个异常对象。



总结Lock和synchronized的区别

1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。

2、Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。

3、synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。



异常处理

异常定义:

导致程序的正常流程被中断的事件,叫做异常



常用手段

异常处理常见手段: try、catch、finally、throws

  • FileNotFoundException是Exception的子类,使用Exception也可以catch住任何异常

  • finally是最后执行的语句,不论怎样都会执行



try – catch

public static void main(String[] args) {

    File f= new File("d:/LOL.exe");

    try{
        System.out.println("试图打开 d:/LOL.exe");
        new FileInputStream(f);//尝试打开文件
        System.out.println("成功打开");
    }
    catch(FileNotFoundException e){
        System.out.println("d:/LOL.exe不存在");
        e.printStackTrace();
    }

}

多异常捕获,写多个catch即可

catch (FileNotFoundException | ParseException e)是把多个catch放在一起



throws

一个方法1调用另体外一个方法2,谁调用谁处理,即方法2throws

public class TestException {
 
    public static void main(String[] args) {
        method1();
 
    }
 
    private static void method1() {
        try {
            method2();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
 
    private static void method2() throws FileNotFoundException {
        File f = new File("d:/LOL.exe");
 
        System.out.println("试图打开 d:/LOL.exe");
        new FileInputStream(f);
        System.out.println("成功打开");
    }
}



异常的分类

  • 可查异常(CheckedException)如果不处理,编译器,就不让你通过
  • 运行时异常(RuntimeException)除数不能为0、空指针、数组越界等等,即便不进行try catch,也不会有编译错误
  • 错误(Error)系统级别的异常,通常是内存用光了,一般java程序启动的时候,最大可以使用16m的内存

在这里插入图片描述



自定义异常

通过继承Exception可以自定义异常或者跑出自定异常



IO操作

文件和文件夹都是用File代表



文件路径与File对象的创建

public static void main(String[] args) {
    // 绝对路径
    File f1 = new File("d:/LOLFolder");
    System.out.println("f1的绝对路径:" + f1.getAbsolutePath());
    // 相对路径,相对于工作目录,如果在eclipse或IDEA中,就是项目目录
    File f2 = new File("LOL.exe");
    System.out.println("f2的绝对路径:" + f2.getAbsolutePath());
    // 把f1作为父目录创建文件对象
    File f3 = new File(f1, "LOL.exe");
    System.out.println("f3的绝对路径:" + f3.getAbsolutePath());
}



常用操作

import java.io.File;
import java.util.Date;
  
public class TestFile {
  
    public static void main(String[] args) {
  
        File f = new File("d:/LOLFolder/LOL.exe");
        System.out.println("当前文件是:" +f);
        //文件是否存在
        System.out.println("判断是否存在:"+f.exists());
         
        //是否是文件夹
        System.out.println("判断是否是文件夹:"+f.isDirectory());
          
        //是否是文件(非文件夹)
        System.out.println("判断是否是文件:"+f.isFile());
          
        //文件长度
        System.out.println("获取文件的长度:"+f.length());
          
        //文件最后修改时间
        long time = f.lastModified();
        Date d = new Date(time);
        System.out.println("获取文件的最后修改时间:"+d);
        //设置文件修改时间为1970.1.1 08:00:00
        f.setLastModified(0);
          
        //文件重命名
        File f2 =new File("d:/LOLFolder/DOTA.exe");
        f.renameTo(f2);
        System.out.println("把LOL.exe改名成了DOTA.exe");
         
        System.out.println("注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息");
    }
}
import java.io.File;
import java.io.IOException;
  
public class TestFile {
  
    public static void main(String[] args) throws IOException {
  
        File f = new File("d:/LOLFolder/skin/garen.ski");
  
        // 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
        f.list();
  
        // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
        File[]fs= f.listFiles();
  
        // 以字符串形式返回获取所在文件夹
        f.getParent();
  
        // 以文件形式返回获取所在文件夹
        f.getParentFile();
        // 创建文件夹,如果父文件夹skin不存在,创建就无效
        f.mkdir();
  
        // 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹
        f.mkdirs();
  
        // 创建一个空文件,如果父文件夹skin不存在,就会抛出异常
        f.createNewFile();
        // 所以创建一个空文件之前,通常都会创建父目录
        f.getParentFile().mkdirs();
  
        // 列出所有的盘符c: d: e: 等等
        f.listRoots();
  
        // 刪除文件
        f.delete();
  
        // JVM结束的时候,刪除文件,常用于临时文件的删除
        f.deleteOnExit();
  
    }
}



什么是流

当不同的介质之间有数据交互的时候,JAVA就使用流来实现。

数据源可以是文件,还可以是数据库,网络甚至是其他的程序

比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流

输入流: InputStream

输出流:OutputStream



字节流

输入:

public class TestStream {
  
    public static void main(String[] args) {
        try {
            //准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66
            File f =new File("d:/lol.txt");
            //创建基于文件的输入流
            FileInputStream fis =new FileInputStream(f);
            //创建字节数组,其长度就是文件的长度
            byte[] all =new byte[(int) f.length()];
            //以字节流的形式读取文件所有内容
            fis.read(all);
            for (byte b : all) {
                //打印出来是65 66
                System.out.println(b);
            }
             
            //每次使用完流,都应该进行关闭
            fis.close();
              
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
          
    }
}

输出:

public class TestStream {
 
    public static void main(String[] args) {
        try {
            // 准备文件lol2.txt其中的内容是空的
            File f = new File("d:/lol2.txt");
            // 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Y
            byte data[] = { 88, 89 };
 
            // 创建基于文件的输出流
            FileOutputStream fos = new FileOutputStream(f);
            // 把数据写入到输出流
            fos.write(data);//写入的是XY
            // 关闭输出流
            fos.close();
             
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}



关闭流

所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。

如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。

  • 在try中关闭,不太好,因为前面可能会有其他报错导致无法关闭
  • 在finally中关闭,引用声明在try外面
  • 使用try()的方式==(常用)==

使用try()的方式JDK1.7开始推出:

public class TestStream {
  
    public static void main(String[] args) {
        File f = new File("d:/lol.txt");
  
        //把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
        try (FileInputStream fis = new FileInputStream(f)) {
            byte[] all = new byte[(int) f.length()];
            fis.read(all);
            for (byte b : all) {
                System.out.println(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}



字符流

Reader字符输入流

Writer字符输出流

专门用于字符的形式读取和写入数据

字符流读取:

public class TestStream {
 
    public static void main(String[] args) {
        // 准备文件lol.txt其中的内容是AB
        File f = new File("d:/lol.txt");
        // 创建基于文件的Reader
        try (FileReader fr = new FileReader(f)) {
            // 创建字符数组,其长度就是文件的长度
            char[] all = new char[(int) f.length()];
            // 以字符流的形式读取文件所有内容
            fr.read(all);
            for (char b : all) {
                // 打印出来是A B
                System.out.println(b);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
}

字符流输出:

public class TestStream {
  
    public static void main(String[] args) {
        // 准备文件lol2.txt
        File f = new File("d:/lol2.txt");
        // 创建基于文件的Writer
        try (FileWriter fr = new FileWriter(f)) {
            // 以字符流的形式把数据写入到文件中
            String data="abcdefg1234567890";
            char[] cs = data.toCharArray();
            fr.write(cs);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}



缓存流(好用)

以介质是硬盘为例,

字节流和字符流的弊端

在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。

为了解决以上弊端,采用缓存流。

缓存流在读取的时候,

会一次性读较多的数据到缓存中

,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。

写的时候也是同理



缓存流必须建立在一个存在的流的基础上


读取文件:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
  
public class TestStream {
    public static void main(String[] args) {
        // 准备文件lol.txt其中的内容是
        // garen kill teemo
        // teemo revive after 1 minutes
        // teemo try to garen, but killed again
        File f = new File("d:/lol.txt");
        // 创建文件字符流
        // 缓存流必须建立在一个存在的流的基础上
        try (
                FileReader fr = new FileReader(f);
                BufferedReader br = new BufferedReader(fr);
            )
        {
            while (true) {
                // 一次读一行
                String line = br.readLine();
                if (null == line)
                    break;
                System.out.println(line);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

写入文件:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class TestStream {
   
    public static void main(String[] args) {
        // 向文件lol2.txt中写入三行语句
        File f = new File("d:/lol2.txt");
          
        try (
                // 创建文件字符流
                FileWriter fw = new FileWriter(f);
                // 缓存流必须建立在一个存在的流的基础上              
                PrintWriter pw = new PrintWriter(fw);              
        ) {
            pw.println("garen kill teemo");
            pw.println("teemo revive after 1 minutes");
            pw.println("teemo try to garen, but killed again");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

有的时候,需要

立即把数据写入到硬盘

,而不是等缓存满了才写出去。 这时候就需要用到flush

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class TestStream {
    public static void main(String[] args) {
        //向文件lol2.txt中写入三行语句
        File f =new File("d:/lol2.txt");
        //创建文件字符流
        //缓存流必须建立在一个存在的流的基础上
        try(FileWriter fr = new FileWriter(f);PrintWriter pw = new PrintWriter(fr);) {
            pw.println("garen kill teemo");
            //强制把缓存中的数据写入硬盘,无论缓存是否已满
                pw.flush();           
            pw.println("teemo revive after 1 minutes");
                pw.flush();
            pw.println("teemo try to garen, but killed again");
                pw.flush();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}



数据流(socket通信会大量使用)

优点:会自动在数据之间添加分割符号,使得数据写和读好识别,例如11.388,可能是11.3 然后后面是整数88

DataInputStream 数据输入流

DataOutputStream 数据输出流

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
      
public class TestStream {
      
    public static void main(String[] args) {
        write();
        read();
    }
 
    private static void read() {
        File f =new File("d:/lol.txt");
        try (
                FileInputStream fis  = new FileInputStream(f);
                DataInputStream dis =new DataInputStream(fis);
        ){
            boolean b= dis.readBoolean();
            int i = dis.readInt();
            String str = dis.readUTF();
             
            System.out.println("读取到布尔值:"+b);
            System.out.println("读取到整数:"+i);
            System.out.println("读取到字符串:"+str);
 
        } catch (IOException e) {
            e.printStackTrace();
        }
         
    }
    private static void write() {
        File f =new File("d:/lol.txt");
        try (
                FileOutputStream fos  = new FileOutputStream(f);
                DataOutputStream dos =new DataOutputStream(fos);
        ){
            dos.writeBoolean(true);
            dos.writeInt(300);
            dos.writeUTF("123 this is gareen");
        } catch (IOException e) {
            e.printStackTrace();
        }
         
    }
}



对象流

对象流指的是可以直接

把一个对象以流的形式

传输给其他的介质,比如硬盘

一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口

Serializable用来标志序列化



控制台输入输出

输入:

Scanner in = new Scanner(System.in);
int a=in.nextInt();//读取浮点数、整数、字符串等等



集合框架



ArratList

1、如果要存放多个对象,可以使用数组,但是数组有局限性

比如 声明长度是10的数组,不用的数组就浪费了,超过10的个数,又放不下

2、为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是 ArrayList

3、ArrayList实现了接口List,常见的写法会把引用声明为接口List类型

//接口引用指向子类对象(多态)
List heros = new ArrayList();

4、默认取出的类型时Object

5、如果这样写List heros = new ArrayList();,那么heros既能放hero,也能放item,要指定类型,即引入了泛型

//接口引用指向子类对象(多态)
List<hero> heros = new ArrayList<hero>();
//其实写成这种的就OK了,系统会自动识别
List<hero> heros = new ArrayList<>();



常用方法

关键字 简介
size 现在大小
add 增加
contains 判断是否存在
get 获取指定位置的对象
indexOf 获取对象所处的位置
remove 删除
set 替换
size 获取大小
toArray 转换为数组
addAll 把另一个容器所有对象都加进来
clear 清空

1、add可以在指定位置加元素,list.add(3,“我草”);

2、使用get获取指定位置元素,list.get(5);就是获取第六个元素

3、删除有两种方式,heros.remove(2); heros.remove(specialHero);

4、使用set替换指定下标的元素heros.set(5, new Hero(“hero 5”));

5、把一个ArrayList添加到另外一个,heros.addAll(anotherHeros);



遍历

方法 描述
for 用for循环遍历
iterator 迭代器遍历
for: 用增强型for循环

1、for循环使用i<list.size()

2、iterator例子Iterator<Hero> it= heros.iterator();,首先问hasNext(),然后调用Hero h = it.next();

迭代器还可以往前找

3、增强for循环,for


(Hero h : heros)



其他集合——LinkedList

1、简介



ArrayList

一样,LinkedList也实现了List接口,诸如add,remove,contains等等方法。

除了实现了List接口外,LinkedList还实现了

双向链表结构



队列

Deque,可以很方便的在头尾插入删除数据,先入先出的

2、常用方法

addFirst、addLast、getFirst、getLast、removeFirst、removeLast

3、实现了队列接口,先进先出

4、队列常用方法

poll,取出第一个元素,peek查看元素,但不取出来



其他集合——二叉树

二叉树由各种

节点

组成

二叉树特点:

每个节点都可以有

左子

节点,

右子

节点,每一个节点都有一个


排序很快

package collection;

public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;
    // 值
    public Object value;
}


二叉树排序——插入数据

使用了递归嗷,相对当前节点而言,小的值去左边,大的值去右边

过程:

  1. 67 放在根节点

  2. 7 比 67小,放在67的左节点

  3. 30 比67 小,找到67的左节点7,30比7大,就放在7的右节点

  4. 73 比67大, 放在67的右节点

  5. 10 比 67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,放在30的左节点。

  6. 10比67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,找到30的左节点10,10和10一样大,放在左边

在这里插入图片描述

package collection;
  
public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;
  
    // 值
    public Object value;
  
    // 插入 数据
    public void add(Object v) {
        // 如果当前节点没有值,就把数据放在当前节点上
        if (null == value)
            value = v;
  
        // 如果当前节点有值,就进行判断,新增的值与当前值的大小关系
        else {
            // 新增的值,比当前值小或者相同
             
            if ((Integer) v -((Integer)value) <= 0) {
                if (null == leftNode)
                    leftNode = new Node();
                leftNode.add(v);
            }
            // 新增的值,比当前值大
            else {
                if (null == rightNode)
                    rightNode = new Node();
                rightNode.add(v);
            }
  
        }
  
    }
 
    public static void main(String[] args) {
        int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
        Node roots = new Node();
        for (int number : randoms) {
            roots.add(number);
        }
  
    }
}


二叉树排序-遍历

二叉树的遍历分左序,中序,右序


左序

即: 中间的数遍历后放在

左边



中序

即: 中间的数遍历后放在

中间



右序

即: 中间的数遍历后放在

右边


如图所见,我们希望遍历后的结果是从小到大的,所以应该采用

中序遍历

import java.util.ArrayList;
import java.util.List;
 
public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;
  
    // 值
    public Object value;
  
    // 插入数据
    public void add(Object v) {
        // 如果当前节点没有值,就把数据放在当前节点上
        if (null == value)
            value = v;
  
        // 如果当前节点有值,就进行判断,新增的值与当前值的大小关系
        else {
            // 新增的值,比当前值小或者相同
             
            if ((Integer) v -((Integer)value) <= 0) {
                if (null == leftNode)
                    leftNode = new Node();
                leftNode.add(v);
            }
            // 新增的值,比当前值大
            else {
                if (null == rightNode)
                    rightNode = new Node();
                rightNode.add(v);
            }
  
        }
  
    }
  
 // 中序遍历所有的节点
    public List<Object> values() {
        List<Object> values = new ArrayList<>();
  
        // 左节点的遍历结果
        if (null != leftNode)
            values.addAll(leftNode.values());
  
        // 当前节点
        values.add(value);
  
        // 右节点的遍历结果
        if (null != rightNode)
  
            values.addAll(rightNode.values());
  
        return values;
    }
  
    public static void main(String[] args) {
  
        int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
  
        Node roots = new Node();
        for (int number : randoms) {
            roots.add(number);
        }
  
        System.out.println(roots.values());
  
    }
}



其他集合——HashMap

键值对,类似字典

键不可以重复,值可以

import java.util.HashMap;
   
public class TestCollection {
    public static void main(String[] args) {
        HashMap<String,String> dictionary = new HashMap<>();
        dictionary.put("adc", "物理英雄");
        dictionary.put("apc", "魔法英雄");
        dictionary.put("t", "坦克");
         
        System.out.println(dictionary.get("t"));
        //清空数据
        dictionary.clear();
    }
}



其他集合——HashSet

Set中的元素,不能重复。Set中的元素,没有顺序。

严格的说,是没有按照元素的插入顺序排列

不保证Set的迭代顺序; 确切的说,在不同条件下,元素的顺序都有可能不一样


遍历:只能用迭代器或者增强for(增强for简单)

import java.util.HashSet;
import java.util.Iterator;
  
public class TestCollection {
    public static void main(String[] args) {
        HashSet<Integer> numbers = new HashSet<Integer>();
         
        for (int i = 0; i < 20; i++) {
            numbers.add(i);
        }
         
        //Set不提供get方法来获取指定位置的元素
        //numbers.get(0)
         
        //遍历Set可以采用迭代器iterator
        for (Iterator<Integer> iterator = numbers.iterator(); iterator.hasNext();) {
            Integer i = (Integer) iterator.next();
            System.out.println(i);
        }
         
        //或者采用增强型for循环
        for (Integer i : numbers) {
            System.out.println(i);
        }
         
    }
}

通过观察HashSet的源代码

可以发现HashSet自身并没有独立的实现,而是在里面封装了一个Map.

HashSet是作为Map的key而存在的

而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。



集合框架Collection

Collection是 Set List Queue和 Deque的接口

Queue: 先进先出队列

Deque: 双向链表

**注:**Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的

**注:**Deque 继承 Queue,间接的继承了 Collection

在这里插入图片描述



工具类——Collections

1、Collections是一个类,容器的工具类,就如同Arrays是数组的工具类

2、

synchronizedList

把非线程安全的List转换为线程安全的List。

3、常用方法

关键字 简介
reverse 反转
shuffle 混淆
sort 排序
swap 交换
rotate 滚动
synchronizedList 线程安全化

4、交换元素

Collections.swap(numbers,0,5);


5、示例

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
   
public class TestCollection {
    public static void main(String[] args) {
        //初始化集合numbers
        List<Integer> numbers = new ArrayList<>();
         
        for (int i = 0; i < 10; i++) {
            numbers.add(i);
        }
         
        System.out.println("集合中的数据:");
        System.out.println(numbers);
 
        Collections.shuffle(numbers);//别看走眼啊,是Collections不是Collection
        System.out.println("混淆后集合中的数据:");
        System.out.println(numbers);
 
        Collections.sort(numbers);
        System.out.println("排序后集合中的数据:");
        System.out.println(numbers);
    }
}



关系与区别



ArrayList与HashSet

ArrayList: 有顺序,数据可以重复

HashSet: 无顺序,数据不能够重复



ArrayList和LinkedList

ArrayList

插入,删除数据慢

LinkedList,

插入,删除数据快

ArrayList是顺序结构,所以

定位很快

,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。

LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以

定位慢

在这里插入图片描述

在最前面插入十万条数据,ArrayList 耗时4969ms,LinkedList耗时16ms

ArrayList 定位很快,几乎是0ms,但是LinkedList用36s



HashMap和Hashtable

HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式

区别1:

HashMap可以存放 null

Hashtable不能存放null

区别2:

HashMap不是

线程安全的类

Hashtable是线程安全的类



几种set

HashSet: 无序

LinkedHashSet: 按照插入顺序

TreeSet: 从小到大排序



hashcode原理

1、HashMap找数据比list快很多,HashMap几乎是0ms

就类似英汉字典,知道页数了,一翻就翻到了

每一个变量都有一个hashcode,理解可以参看下图(

空间换时间

在这里插入图片描述

2、HashSet的数据是不能重复的,相同数据不能保存在一起,到底如何判断是否是重复的呢?

根据

HashSet和HashMap的关系

,我们了解到因为HashSet没有自身的实现,而是里面封装了一个HashMap,

所以本质上就是判断HashMap的key是否重复,因此速度很快



比较器Comparator(条件排序,设置排序规则)

1、假设Hero有三个属性 name,hp,damage

一个集合中放存放10个Hero,通过Collections.sort对这10个进行排序

那么**到底是hp小的放前面?还是damage小的放前面?**Collections.sort也无法确定

所以要指定到底按照哪种属性进行排序

这里就需要提供一个Comparator给定如何进行两个对象之间的

大小

比较

2、其实是重写了compare方法

3、示例代码

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
    
import charactor.Hero;
     
public class TestCollection {
    public static void main(String[] args) {
        Random r =new Random();
        List<Hero> heros = new ArrayList<Hero>();
            
        for (int i = 0; i < 10; i++) {
            //通过随机值实例化hero的hp和damage
            heros.add(new Hero("hero "+ i, r.nextInt(100), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
            
        //直接调用sort会出现编译错误,因为Hero有各种属性
        //到底按照哪种属性进行比较,Collections也不知道,不确定,所以没法排
        //Collections.sort(heros);
            
        //引入Comparator,指定比较的算法
        Comparator<Hero> c = new Comparator<Hero>() {
            @Override
            public int compare(Hero h1, Hero h2) {
                //按照hp进行排序
                if(h1.hp>=h2.hp)
                    return 1;  //正数表示h1比h2要大
                else
                    return -1;
            }
        };
        Collections.sort(heros,c);
        System.out.println("按照血量排序后的集合:");
        System.out.println(heros);
    }
}



比较器Comparable(在类里写好,然后直接调用sort就可以了)

1、在Hero类中直接去实现这个接口,implements


Comparable<Hero>,然后写一个compareTo方法

2、Hero类代码

public class Hero implements Comparable<Hero>{
    public String name;
    public float hp;
       
    public int damage;
       
    public Hero(){
          
    }
      
    public Hero(String name) {
        this.name =name;
  
    }
      
    //初始化name,hp,damage的构造方法
    public Hero(String name,float hp, int damage) {
        this.name =name;
        this.hp = hp;
        this.damage = damage;
    }
    public int compareTo(Hero anotherHero) {
        if(damage<anotherHero.damage)
            return 1; 
        else
            return -1;
    }
    public String toString() {
        return "Hero [name=" + name + ", hp=" + hp + ", damage=" + damage + "]\r\n";
    }
}

3、Test代码

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
  
import charactor.Hero;
   
public class TestCollection {
    public static void main(String[] args) {
        Random r =new Random();
        List<Hero> heros = new ArrayList<Hero>();
          
        for (int i = 0; i < 10; i++) {
            //通过随机值实例化hero的hp和damage
            heros.add(new Hero("hero "+ i, r.nextInt(100), r.nextInt(100)));
        }
          
        System.out.println("初始化后的集合");
        System.out.println(heros);
          
        //Hero类实现了接口Comparable,即自带比较信息。
        //Collections直接进行排序,无需额外的Comparator
        Collections.sort(heros);
        System.out.println("按照伤害高低排序后的集合");
        System.out.println(heros);
          
    }
}



聚合操作

JDK8之后,引入了对集合的聚合操作,可以非常容易的遍历,筛选,比较集合中的元素。

使用Lambda表达式进行聚合操作



Lambda表达式



初探

平时需要写一个匿名类或者一个函数,调用的时候使用lambda可以简化操作

其实就是把代码逻辑写清楚了,其他冗杂的东西直接省略


源代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import charactor.Hero;
  
public class TestLambda {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 10; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("筛选出 hp>100 && damange<50的英雄");
        filter(heros);
    }
  
    private static void filter(List<Hero> heros) {
        for (Hero hero : heros) {
            if(hero.hp>100 && hero.damage<50)
                System.out.print(hero);
        }
    }
  
}




使用匿名类:

HeroChecker是一个接口

import charactor.Hero;
 
public interface HeroChecker {
    public boolean test(Hero h);
}
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
   
import charactor.Hero;
   
public class TestLambda {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("使用匿名类的方式,筛选出 hp>100 && damange<50的英雄");
        HeroChecker checker = new HeroChecker() {
            @Override
            public boolean test(Hero h) {
                return (h.hp>100 && h.damage<50);
            }
        };
        filter(heros,checker);
    }
   
    private static void filter(List<Hero> heros,HeroChecker checker) {
        for (Hero hero : heros) {
            if(checker.test(hero))
                System.out.print(hero);
        }
    }
   
}




使用lambda表达式:

HeroChecker是一个上面使用匿名类里的同一个接口

匿名类本身只是载体,他是里面方法逻辑的载体,lambda也可以实现

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
 
import charactor.Hero;
 
public class TestLamdba {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("使用Lamdba的方式,筛选出 hp>100 && damange<50的英雄");
        filter(heros,h->h.hp>100 && h.damage<50);
    }
 
    private static void filter(List<Hero> heros,HeroChecker checker) {
        for (Hero hero : heros) {
            if(checker.test(hero))
                System.out.print(hero);
        }
    }
}




演变过程

package lambda;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
 
import charactor.Hero;
 
public class TestLamdba {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("使用匿名类的方式,筛选出 hp>100 && damange<50的英雄");
        // 匿名类的正常写法
        HeroChecker c1 = new HeroChecker() {
            @Override
            public boolean test(Hero h) {
                return (h.hp > 100 && h.damage < 50);
            }
        };
        // 把new HeroChcekcer,方法名,方法返回类型信息去掉
        // 只保留方法参数和方法体
        // 参数和方法体之间加上符号 ->
        HeroChecker c2 = (Hero h) -> {
            return h.hp > 100 && h.damage < 50;
        };
 
        // 把return和{}去掉
        HeroChecker c3 = (Hero h) -> h.hp > 100 && h.damage < 50;
 
        // 把 参数类型和圆括号去掉
        HeroChecker c4 = h -> h.hp > 100 && h.damage < 50;
 
        // 把c4作为参数传递进去
        filter(heros, c4);
         
        // 直接把表达式传递进去
        filter(heros, h -> h.hp > 100 && h.damage < 50);
    }
 
    private static void filter(List<Hero> heros, HeroChecker checker) {
        for (Hero hero : heros) {
            if (checker.test(hero))
                System.out.print(hero);
        }
    }
}

Lambda 其实就是

匿名方法

,这是一种

把方法作为参数

进行传递的编程思想。

虽然代码是这么写

filter(heros, h -> h.hp > 100 && h.damage < 50);

但是,Java会在背后,悄悄的,把这些都还原成

匿名类方式

引入Lambda表达式,会使得代码更加紧凑,而不是各种接口和匿名类到处飞。



Lambda表达式弊端

Lambda表达式虽然带来了代码的简洁,但是也有其局限性。

1、可读性差,与

啰嗦的

但是

清晰的

匿名类代码结构比较起来,Lambda表达式一旦变得比较长,就难以理解

2、不便于调试,很难在Lambda表达式中增加调试信息,比如日志

3、版本支持,Lambda表达式在JDK8版本中才开始支持,如果系统使用的是以前的版本,考虑系统的稳定性等原因,而不愿意升级,那么就无法使用。

Lambda比较适合用在简短的业务代码中,并不适合用在复杂的系统中,会加大维护成本。



方法引用



聚合操作

这两个先过吧,有点不在状态



泛型



定义

泛型是泛指一切类型,当你不知道是用的数据是什么类型的时候,系统会自动判断。

E e:Element

反省可以使用任意字母代替,常用E



使用泛型的好处

1,避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型

2.把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)

弊端:只能存储一种数据类型

import java.util.ArrayList;
public class Main {
    public static void main(String[] args) {
        show2();
    }
    /*
    创建集合对象,不使用泛型
    好处:
        默认类型是Object,可以存储任意类型数据
    弊端:
        不安全,容易产生异常
    */
    public static void show1(){
        ArrayList list=new ArrayList();
        list.add(1);
        list.add("abc");
        Iterator it=list.iterator();
        while (it.hasNext()) {
            Object obj = it.next();
            System.out.println(obj);
            String s = (String) obj;//转为string类数据
            System.out.println(s + "-->" + s.length());//会报错
        }
    }

    /*
    创建集合对象,使用泛型好处:
        1,避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
        2.把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)
    弊端:
        泛型是什么类型,只能存储什么类型的数据
    */
    public static void show2(){
        ArrayList<String> list=new ArrayList();
        //list.add(1); 报错
        list.add("abc");
        Iterator it=list.iterator();
        while (it.hasNext()) {
            String obj = it.next();
            System.out.println(obj + "-->" + obj.length());//会报错
        }
    }
}



泛型的定义与使用

我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。

泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。



定义和使用含有泛型的类


定义:

E表示泛型,任何一个字母都行

public class GenericClass<E>{
	private E name;
	public E getName(){
		return name;
	}
	public E setName(E name){
		this.name=name;
	}
}


使用:

前提是在同一个包下哈

public class DemoGeneriClass{
    public static void main(String[] args){
        //不写泛型默认为Object类型
        GenericClass gc=new GenericClass();
        gc.setName("只能字符串");
        //创建对象,泛型使用Integer
        GenericClass gc2=new GenericClass();
        gc2.setName(1);
        
        Integer name=gc2.getName();
        System.out.println(name);
    }
    
}



定义和使用含有泛型的方法


定义:

public class GenericMethod{
	//定义一个含有泛型的方法
	public <M> void method(M m){
		System.out.println(m);
	}
    //定义一个静态方法
    public static <S> void method2(S s){
        System.out.println(s);
    }
}


使用:

传入什么数据就是什么类型

public class DemoGenericMethod{
    public static void main(String[] args){
        //不写泛型默认为Object类型
        GenericMethod gc=new GenericMethod();
        gc.method("我草");
        gc.method(666);
        
        gc.method2("静态方法,不建议创建对象使用");
        //静态方法,通过类名.方法名(参数)可以直接调用
        GenericMethod.method2(666);
    }
}



定义和使用含有泛型的接口

传送门:https://www.bilibili.com/video/BV1uJ411k7wy?p=250

从250开始学

定义一个接口:

public interface GenericInterface<I>{
	public abstract void method(I i)
}

定义接口的实现类:

测试类:



泛型通配符

通配符: ? 代表任意的数据类型

使用方式:

​ 不能创建对象时使用,即创建的时候不能用,见list03。只能作为方法的参数使用

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list01=new ArrayList<>();
        list01.add(1);
        list01.add(2);

        ArrayList<String> list02=new ArrayList<>();
        list02.add("a");
        list02.add("b");

        printArray(list01);
        printArray(list02);
        /*
        定义的时候不能写?
        ArrayList<String> list02=new ArrayList<>();会报错
        */
    }
    //定义一个方法,能遍历所有类型的ArrayList集合
    //可以使用通配符 ? 来接收数据类型,注意:反省没有继承概念
    public static void printArray(ArrayList<?> list){
        Iterator<?> it=list.iterator();
        while (it.hasNext()){
            //取出来的类型是Object,可以接收任意的数据类型
            Object o=it.next();
            System.out.println(o);
        }
    }
}


通配符的高级用法——受限泛型

用的不多,了解一下就好

  • 泛型的上限限定:<? extends E> 代表使用的泛型只能是E类型的子类/本身

  • 泛型的下限限定:<? super E> 代表使用的泛型只能是E类型的父类/本身



多线程



进程(Processor)和线程(Thread)的区别

首先要理解进程(Processor)和线程(Thread)的区别

**进程:**启动一个LOL.exe就叫一个进程。 接着又启动一个DOTA.exe,这叫两个进程。

**线程:**线程是在进程内部同时做的事情,比如在LOL里,有很多事情要同时做,比如”盖伦” 击杀“提莫”,

同时

“赏金猎人”又在击杀“盲僧”,这就是由多线程来实现的。



创建多线程(三种方式)

启动线程是start()方法,run()并不能启动一个新的线程



继承线程类,重写run方法

KillThread

import charactor.Hero;
 
public class KillThread extends Thread{
     
    private Hero h1;
    private Hero h2;
 
    public KillThread(Hero h1, Hero h2){
        this.h1 = h1;
        this.h2 = h2;
    }
 
    public void run(){
        while(!h2.isDead()){
            h1.attackHero(h2);
        }
    }
}

TestKillThread

两个线程同时运行

import charactor.Hero;
 
public class TestThread {
 
    public static void main(String[] args) {
         
        Hero gareen = new Hero();
        gareen.name = "盖伦";
        gareen.hp = 616;
        gareen.damage = 50;
 
        Hero teemo = new Hero();
        teemo.name = "提莫";
        teemo.hp = 300;
        teemo.damage = 30;
         
        Hero bh = new Hero();
        bh.name = "赏金猎人";
        bh.hp = 500;
        bh.damage = 65;
         
        Hero leesin = new Hero();
        leesin.name = "盲僧";
        leesin.hp = 455;
        leesin.damage = 80;
         
        KillThread killThread1 = new KillThread(gareen,teemo);
        killThread1.start();
        KillThread killThread2 = new KillThread(bh,leesin);
        killThread2.start();
    }
}



实现Runnable接口

创建类Battle,实现Runnable接口

启动的时候,首先创建一个Battle对象,然后再根据该battle对象创建一个线程对象,并启动

Battle battle1 = new Battle(gareen,teemo);
new Thread(battle1).start();



匿名类(直接在run方法中写业务代码)

使用

匿名类

,继承Thread,重写run方法,直接在run方法中写业务代码

匿名类的一个好处是

可以很方便的访问外部的局部变量

//匿名类
        Thread t1= new Thread(){
            public void run(){
                //匿名类中用到外部的局部变量teemo,必须把teemo声明为final
                //但是在JDK7以后,就不是必须加final的了
                while(!teemo.isDead()){
                    gareen.attackHero(teemo);
                }              
            }
        };
         
        t1.start();
          
        Thread t2= new Thread(){
            public void run(){
                while(!leesin.isDead()){
                    bh.attackHero(leesin);
                }              
            }
        };
        t2.start();



常用方法

关键字 简介
sleep 当前线程暂停
join 加入到当前线程中
setPriority 线程优先级
yield 临时暂停
setDaemon 守护线程

1、sleep

Thread.sleep(1000); 表示当前线程暂停1000毫秒 ,其他线程不受影响

Thread.sleep(1000); 会抛出InterruptedException 中断异常,因为当前线程sleep的时候,有可能被停止,这时就会抛出 InterruptedException

2、join

//代码执行到这里,一直是main线程在运行
        try {
            //t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走
            t1.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

3、setPriority

参数是一个整数

t2优先级小,但是也会运行,只不过几率小而已,主要供t1运行

t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);

4、守护线程

守护线程的概念是: 当一个进程里,所有的线程都是守护线程的时候,结束当前进程。

就好像一个公司有销售部,生产部这些和业务挂钩的部门。

除此之外,还有后勤,行政等这些支持部门。

如果一家公司销售部,生产部都解散了,那么只剩下后勤和行政,那么这家公司也可以解散了。

守护线程就相当于那些支持部门,如果一个进程只剩下守护线程,那么进程就会自动结束。

守护线程通常会被用来做日志,性能统计等工作。

使用方法:

在线程start之前使用==t1.setDaemon( true );==即可



同步问题(核心)

多线程的同步问题指的是多个线程同时修改一个数据的时候,可能导致的问题

多线程的问题,又叫

Concurrency

问题,会产生脏数据,示例如图

在这里插入图片描述

使用

synchronized

关键字来解决这个问题,同一时间,某一个变量只能被一个线程访问

示例代码:

import java.text.SimpleDateFormat;
import java.util.Date;
   
public class TestThread {
     
    public static String now(){
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
     
    public static void main(String[] args) {
        final Object someObject = new Object();
          
        Thread t1 = new Thread(){
            public void run(){
                try {
                    System.out.println( now()+" t1 线程已经运行");
                    System.out.println( now()+this.getName()+ " 试图占有对象:someObject");
                    synchronized (someObject) {
                          
                        System.out.println( now()+this.getName()+ " 占有对象:someObject");
                        Thread.sleep(5000);
                        System.out.println( now()+this.getName()+ " 释放对象:someObject");
                    }
                    System.out.println(now()+" t1 线程结束");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };
        t1.setName(" t1");
        t1.start();
        Thread t2 = new Thread(){
  
            public void run(){
                try {
                    System.out.println( now()+" t2 线程已经运行");
                    System.out.println( now()+this.getName()+ " 试图占有对象:someObject");
                    synchronized (someObject) {
                        System.out.println( now()+this.getName()+ " 占有对象:someObject");
                        Thread.sleep(5000);
                        System.out.println( now()+this.getName()+ " 释放对象:someObject");
                    }
                    System.out.println(now()+" t2 线程结束");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };
        t2.setName(" t2");
        t2.start();
    }   
}



把synchronized放在类的方法里

参数添加this就好啦

	//掉血
    public void hurt(){
        //使用this作为同步对象
        synchronized (this) {
            hp=hp-1;   
        }
    }
	//回血
	public synchronized void recover(){
        hp=hp-1;
    }



线程安全类

如果一个类,其

方法都是有synchronized修饰的

,那么该类就叫做

线程安全的类

同一时间,只有一个线程能够进入

这种类的一个实例

的去修改数据,进而保证了这个实例中的数据的安全(不会同时被多线程修改而变成脏数据)

比如StringBuffer和StringBuilder的区别

StringBuffer的方法都是有synchronized修饰的,StringBuffer就叫做线程安全的类

而StringBuilder就不是线程安全的类



HashMap和Hashtable的区别

HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式

区别1:

HashMap可以存放 null

Hashtable不能存放null

区别2:

HashMap不是

Hashtable是线程安全的类



StringBuffer和StringBuilder的区别

StringBuffer 是线程安全的

StringBuilder 是非线程安全的

所以当进行大量字符串拼接操作的时候,如果是单线程就用StringBuilder会更快些,如果是多线程,就需要用StringBuffer 保证数据的安全性


非线程安全的

为什么会比

线程安全的

快? 因为不需要同步嘛,省略了些时间



ArrayList和Vector的区别

两者一模一样的,区别也在于,Vector是线程安全的类,而ArrayList是非线程安全的。



把非线程安全的集合转换为线程安全,使用Collections

ArrayList是非线程安全的,换句话说,多个线程可以同时进入

一个ArrayList对象

的add方法

借助Collections.synchronizedList,可以把ArrayList转换为线程安全的List。

与此类似的,还有HashSet,LinkedList,HashMap等等非线程安全的类,都通过

工具类Collections

转换为线程安全的

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class TestThread {
    
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = Collections.synchronizedList(list1);
    }
}



死锁

使用synchronized可能会出现死锁,使用Lock就好了

当业务比较复杂,多线程应用里有可能会发生

死锁

线程1 首先占有对象1,接着试图占有对象2

线程2 首先占有对象2,接着试图占有对象1

线程1 等待线程2释放对象2

与此同时,线程2等待线程1释放对象1

就会。。。一直等待下去,直到天荒地老,海枯石烂,山无棱 ,天地合。。。

在这里插入图片描述



交互



使用wait和notify进行线程交互

写在了Hero里面

public class Hero {
    public String name;
    public float hp;
 
    public int damage;
 
    public synchronized void recover() {
        hp = hp + 1;
        System.out.printf("%s 回血1点,增加血后,%s的血量是%.0f%n", name, name, hp);
        // 通知那些等待在this对象上的线程,可以醒过来了,如第20行,等待着的减血线程,苏醒过来
        this.notify();
    }
 
    public synchronized void hurt() {
        if (hp == 1) {
            try {
                // 让占有this的减血线程,暂时释放对this的占有,并等待
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        hp = hp - 1;
        System.out.printf("%s 减血1点,减少血后,%s的血量是%.0f%n", name, name, hp);
    }
 
    public void attackHero(Hero h) {
        h.hp -= damage;
        System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n", name, h.name, h.name, h.hp);
        if (h.isDead())
            System.out.println(h.name + "死了!");
    }
 
    public boolean isDead() {
        return 0 >= hp ? true : false;
    }
}



关于wait、notify和notifyAll

这里需要强调的是,wait方法和notify方法,并

不是Thread线程上的方法

,它们是Object上的方法。

因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法。

wait()的意思是: 让占用了这个同步对象的

线程

,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。

notify() 的意思是,通知

一个

等待在这个同步对象上的线程,



可以苏醒过来了,有机会重新占用当前对象了。

notifyAll() 的意思是,通知

所有的

等待在这个同步对象上的线程,

你们

可以苏醒过来了,有机会重新占用当前对象了。



线程池(了解)

使用多线程很舒服,但是每一个线程的启动和结束都是比较消耗时间和占用资源的。

如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢。

为了解决这个问题,引入线程池这种设计思想。

线程池的模式很像

生产者消费者模式

,消费的对象是一个一个的能够运行的

任务



多线程同步(Lock对象)

1、区别于synchronized,能达到同样效果

Lock是一个接口,有很多类,需要使用使用finally释放,使用lock.unlock()释放

Lock lock = new ReentrantLock();

然后在线程使用lock.lock()方法就好啦

2、synchronized 是

不占用到手不罢休

的,会一直试图占用下去。

与 synchronized 的

钻牛角尖

不一样,Lock接口还提供了一个trylock方法。

trylock会在指定时间范围内

试图占用

,占成功了,就啪啪啪。 如果时间到了,还占用不成功,扭头就走~

注意: 因为使用trylock有可能成功,有可能失败,所以后面unlock释放锁的时候,需要判断是否占用成功了,如果没占用成功也unlock,就会抛出异常

3、lock.trylock(1,TimeUnit.SECONDS),返回布尔值



线程交互

使用synchronized方式进行线程交互,用到的是同步对象的wait,notify和notifyAll方法

Lock也提供了类似的解决办法,首先通过lock对象得到一个Condition对象,然后分别调用这个Condition对象的:

await

,

signal

,

signalAll

方法

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
  
public class TestThread {
  
    public static String now() {
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
  
    public static void log(String msg) {
        System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg);
    }
  
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
         
        Thread t1 = new Thread() {
            public void run() {
                try {
                    log("线程启动");
                    log("试图占有对象:lock");
  
                    lock.lock();
  
                    log("占有对象:lock");
                    log("进行5秒的业务操作");
                    Thread.sleep(5000);
                    log("临时释放对象 lock, 并等待");
                    condition.await();
                    log("重新占有对象 lock,并进行5秒的业务操作");
                    Thread.sleep(5000);
  
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    log("释放对象:lock");
                    lock.unlock();
                }
                log("线程结束");
            }
        };
        t1.setName("t1");
        t1.start();
        try {
            //先让t1飞2秒
            Thread.sleep(2000);
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        Thread t2 = new Thread() {
  
            public void run() {
                try {
                    log("线程启动");
                    log("试图占有对象:lock");
  
                    lock.lock();
  
                    log("占有对象:lock");
                    log("进行5秒的业务操作");
                    Thread.sleep(5000);
                    log("唤醒等待中的线程");
                    condition.signal();
  
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    log("释放对象:lock");
                    lock.unlock();
                }
                log("线程结束");
            }
        };
        t2.setName("t2");
        t2.start();
    }
}



原子访问



简介

所谓的

原子性操作

即不可中断的操作,比如赋值操作,int i=5


原子性操作本身是线程安全的

但是 i++ 这个行为,事实上是有3个原子性操作组成的。

步骤 1. 取 i 的值

步骤 2. i + 1

步骤 3. 把新的值赋予i

这三个步骤,每一步都是一个原子操作,但是合在一起,就不是原子操作。就

不是线程安全

的。



AtomicInteger

JDK6 以后,新增加了一个包

java.util.concurrent.atomic

,里面有各种原子类,比如

AtomicInteger



而AtomicInteger提供了各种自增,自减等方法,这些方法都是原子性的。 换句话说,自增方法

incrementAndGet

是线程安全的,同一个时间,只有一个线程可以调用这个方法。

import java.util.concurrent.atomic.AtomicInteger;
   
public class TestThread {
   
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicI =new AtomicInteger();
        int i = atomicI.decrementAndGet();
        int j = atomicI.incrementAndGet();
        int k = atomicI.addAndGet(3);
    }
}



同步测试

分别使用基本变量的非原子性的**++

运算符和 原子性的

AtomicInteger对象的 incrementAndGet** 来进行多线程测试。

发现直接使用++会出问题,但是使用incrementAndGet方法就不会



网络编程

百度:如何开启Windows端口,就可以一起玩了嘿嘿

TCP:打电话 C/S

  • 连接,稳定

  • 三次握手,四次挥手

  • 客户端、服务端

  • 传输完成,释放连接,效率低

  • 服务端:自定义 S

  • 客户端:自定义 C

UDP:发短信 B/S

  • 不连接,不稳定
  • 客户端、服务端,没有明确的界限
  • 不管有没有准备好,都发给你
  • 导弹
  • DDOS:洪水攻击(饱和攻击)
  • 服务端:Tomcat S
  • 客户端:浏览器 B



TCP聊天代码

记得关闭资源,否则一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出异常(Connection reset)。

服务端做一个while(true)就能一直重复操作了

客户端

1、连接服务器Socket

2、发送消息

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class TcpClientDemo1 {
    public static void main(String[] args) {
        //要知道服务器ip和端口
        InetAddress serverIP = null;
        Socket socket=null;
        OutputStream os = null;
        try {
            serverIP = InetAddress.getByName("127.0.0.1");
            int port=9999;
            //创建一个socket连接
            socket = new Socket(serverIP,port);
            //使用IO流发送消息
            os = socket.getOutputStream();
            os.write("你好啊,程序已经联通了".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服务端

1、建立服务的端口

2、等待用户的连接accept()

3、接收用户的消息

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServerDemo1 {
    public static void main(String[] args){
        ServerSocket serversocket=null;
        Socket socket=null;
        InputStream is=null;
        ByteArrayOutputStream baos=null;
        try {
            //我得有一个地址
            serversocket = new ServerSocket(9999);
            //等待客户端连接,得到socket对象
            socket = serversocket.accept();
            //读取客户端的消息
            is = socket.getInputStream();
            //管道流清洗数据,可以避免乱码,并且不会由于中文导致超出1024
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                baos.write(buffer);
            }
            System.out.println(baos.toString());
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //从后往前关
            try {
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                serversocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}



TCP实现文件上传

就是把文件变成一个流,然后输出出去

客户端

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

public class TcpClientDemo2 {
    public static void main(String[] args) throws IOException {
        //1、创建一个socket连接
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9999);
        //2、创建一个输出流
        OutputStream os = socket.getOutputStream();
        //3、文件流读取文件
        FileInputStream fis = new FileInputStream(new File("img.jpg"));
        //4、将文件写出
        byte[] buffer = new byte[1024];
        int len;
        while ((len=fis.read(buffer))!=-1){
            os.write(buffer,0,len);
        }
        //通知服务端,我传输完了
        socket.shutdownOutput();
        //确认服务器接收完了信息
        InputStream is = socket.getInputStream();
        ByteOutputStream baos = new ByteOutputStream();
        byte[] buffer2 = new byte[1024];
        int len2;
        while ((len2=is.read())!=-1){
            baos.write(buffer2,0,len);
        }
        System.out.println(baos.toString());

        //5、关闭资源
        baos.close();
        is.close();
        fis.close();
        os.close();
        socket.close();
    }
}

服务端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServerDemo2 {
    public static void main(String[] args) throws IOException {
        //1、创建服务
        ServerSocket serverSocket = new ServerSocket(9999);
        //2、监听客户端,阻塞,会一直等待客户端
        Socket socket = serverSocket.accept();
        //3、获取输入流
        InputStream is = socket.getInputStream();

        //4、把文件输出
        FileOutputStream fos = new FileOutputStream("wocao.jpg");
        byte[] buffer = new byte[1024];
        int len;
        while ((len=is.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        //通知客户端,我接收完了
        OutputStream os = socket.getOutputStream();
        os.write("我接受完啦,牛皮!".getBytes());
        //5、关闭资源
        fos.close();
        is.close();
        socket.close();
    }



Tomcat

Windows环境下,双击bin目录的startup.bat文件,Linux双击startup.sh文件,启动,默认端口号是8080

出现乱码就修改conf的logging.properties,最后一个UTF-8改为GBK编码



UDP聊天代码

不需要知道对方地址

建立连接:DatagramSocket

发包:DatagramPacket

客户端

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

public class UdpSenderDemo1 {
    public static void main(String[] args) throws Exception {

        DatagramSocket socket = new DatagramSocket();

        while (true){
            //准备数据,控制台读取
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

            String data=reader.readLine();
            byte[] datas=data.getBytes();
            DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 6666));

            socket.send(packet);
        }
        //socket.close();

    }
}

服务端

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.security.cert.TrustAnchor;

public class UdpReceiveDemo1 {
    public static void main(String[] args) throws Exception {
        //开放端口
        DatagramSocket socket = new DatagramSocket(6666);

        while (true){
            //准备接收数据
            byte[] container = new byte[1024];
            DatagramPacket packet = new DatagramPacket(container, 0, container.length);
            socket.receive(packet);//阻塞监听

            byte[] data = packet.getData();
            String ReceiveData=new String(data);
            System.out.println(ReceiveData);

            //断开链接,去掉换行符
            if(ReceiveData.trim().equals("bye")){
                break;
            }
            socket.close();
        }
    }
}

🉑



多线程聊天代码

发送的线程

package cao;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

public class TalkSend implements Runnable{
    DatagramSocket socket=null;
    DatagramPacket reader=null;

    private int fromPort;
    private String toIP;
    private int toPort;

    public TalkSend(int fromPort,String toIP,int toPort){
        this.fromPort=fromPort;
        this.toIP=toIP;
        this.toPort=toPort;
        try{
            socket=new DatagramSocket(fromPort);
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    @Override
    public void run() {
        while (true){
            try {
                //准备数据,控制台读取
                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

                String data=reader.readLine();
                byte[] datas=data.getBytes();
                DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress(this.toIP, this.toPort));
                socket.send(packet);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

接收的线程

package cao;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class TalkReceive implements Runnable {
    DatagramSocket socket=null;


    private int port;
    private String name;

    public TalkReceive(int port,String name) {
        this.port=port;
        this.name=name;
        try {
            socket = new DatagramSocket(port);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {

        while (true) {
            try {
                //准备接收数据
                byte[] container = new byte[1024];
                DatagramPacket packet = new DatagramPacket(container, 0, container.length);
                socket.receive(packet);//阻塞监听

                byte[] data = packet.getData();
                String ReceiveData = new String(data);
                System.out.println(name+"发送来:"+ReceiveData);

                //断开链接,去掉换行符
                if (ReceiveData.trim().equals("bye")) {
                    socket.close();
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

学生端

package cao;

public class TalkStudent {
    public static void main(String[] args) {
        //开启两个线程
        new Thread(new TalkSend(7777,"localhost",9999)).start();
        new Thread(new TalkReceive(8888,"学生")).start();
    }
}

老师端

package cao;

public class TalkTeacher {
    public static void main(String[] args) {
        //开启两个线程
        new Thread(new TalkSend(5555,"localhost",8888)).start();
        new Thread(new TalkReceive(9999,"老师")).start();
    }
}



URL下载网络资源(爬虫)

import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class UrlDemo{
    public static void main(String[] args) throws Exception {
        //下载地址
        URL url=new URL("http://");
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

        InputStream inputStream = urlConnection.getInputStream();
        FileOutputStream fos = new FileOutputStream("wocao.txt");
        byte[] buffer = new byte[1024];
        int len;
        while ((len=inputStream.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        fos.close();
        inputStream.close();
        urlConnection.disconnect();
    }
}

🉑



图形界面swing

JAVA的图形界面下有两组控件,一组是awt,一组是swing。

一般都是使用swing



模板

public class TestGUI {
    public static void main(String[] args) {
        // 主窗体
        JFrame f = new JFrame("LoL");

        // 主窗体设置大小
        f.setSize(400, 300);

        // 主窗体设置位置
        f.setLocation(200, 200);

        // 主窗体中的组件设置为绝对定位,否则为自动填充
        f.setLayout(null);

        // 按钮组件
        JButton b = new JButton("一键秒对方基地挂");

        // 同时设置组件的大小和位置
        b.setBounds(50, 50, 280, 30);

        // 把按钮加入到主窗体中
        f.add(b);

        // 关闭窗体的时候,退出程序
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 让窗体变得可见
        f.setVisible(true);

    }
}



监听事件



按钮绑定事件(按钮监听)

创建一个

匿名类

实现ActionListener接口,当按钮被点击时,actionPerformed方法就会被调用

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
  
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
  
public class TestGUI {
    public static void main(String[] args) {
  
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(580, 200);
        f.setLayout(null);
  
        final JLabel l = new JLabel();
        ImageIcon i = new ImageIcon("C:\Users\Administrator\Desktop\shana.png");
        l.setIcon(i);
        l.setBounds(50, 50, i.getIconWidth(), i.getIconHeight());

        JButton b = new JButton("隐藏图片");
        b.setBounds(150, 200, 100, 30);
        // 给按钮 增加 监听
        b.addActionListener(new ActionListener() {
            // 当按钮被点击时,就会触发 ActionEvent事件
            // actionPerformed 方法就会被执行
            public void actionPerformed(ActionEvent e) {
                l.setVisible(false);
            }
        });
  
        f.add(l);
        f.add(b);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
        f.setVisible(true);
    }
}



键盘监听

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class TestGUI {
    public static void main(String[] args) {

        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(580, 200);
        f.setLayout(null);

        final JLabel l = new JLabel();

        ImageIcon i = new ImageIcon("C:\\Users\\Administrator\\Desktop\\shana.png");
        l.setIcon(i);
        l.setBounds(50, 50, i.getIconWidth(), i.getIconHeight());

        // 增加键盘监听
        f.addKeyListener(new KeyListener() {

            // 键被弹起
            public void keyReleased(KeyEvent e) {

                System.out.println(e.getKeyCode)//查看按键编码
                // 39代表按下了 “右键”
                if (e.getKeyCode() == 39) {
                    // 图片向右移动 (y坐标不变,x坐标增加)
                    l.setLocation(l.getX() + 10, l.getY());
                }
            }

            //键被按下
            public void keyPressed(KeyEvent e) {
                // TODO Auto-generated method stub
            }

            // 一个按下弹起的组合动作
            public void keyTyped(KeyEvent e) {

            }
        });

        f.add(l);

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.setVisible(true);
    }
}



鼠标监听

写的代码很多,常用适配器

MouseListener 鼠标监听器

  • mouseReleased 鼠标释放

  • mousePressed 鼠标按下

  • mouseExited 鼠标退出

  • mouseEntered 鼠标进入

  • mouseClicked 鼠标点击

在本例中,使用mouseEntered,当鼠标进入图片的时候,图片就移动位置

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class TestGUI {
    public static void main(String[] args) {

        final JFrame f = new JFrame("LoL");
        f.setSize(800, 600);
        f.setLocationRelativeTo(null);
        f.setLayout(null);

        final JLabel l = new JLabel();
        ImageIcon i = new ImageIcon("C:\\Users\\Administrator\\Desktop\\shana.png");
        l.setIcon(i);
        l.setBounds(375, 275, i.getIconWidth(), i.getIconHeight());

        f.add(l);

        l.addMouseListener(new MouseListener() {

            // 释放鼠标
            public void mouseReleased(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            // 按下鼠标
            public void mousePressed(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            // 鼠标退出
            public void mouseExited(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            // 鼠标进入
            public void mouseEntered(MouseEvent e) {

                Random r = new Random();

                int x = r.nextInt(f.getWidth() - l.getWidth());
                int y = r.nextInt(f.getHeight() - l.getHeight());

                l.setLocation(x, y);

            }

            // 按下释放组合动作为点击鼠标
            public void mouseClicked(MouseEvent e) {
                // TODO Auto-generated method stub

            }
        });

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.setVisible(true);
    }
}



适配器(常用)


MouseAdapter

鼠标监听适配器

一般说来在写监听器的时候,会实现MouseListener。

但是MouseListener里面有很多方法实际上都没有用到,比如mouseReleased ,mousePressed,mouseExited等等。

这个时候就可以使用 鼠标监听适配器,MouseAdapter

只需要重写必要的方法即可

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
  
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
  
public class TestGUI {
    public static void main(String[] args) {
  
        final JFrame f = new JFrame("LoL");
        f.setSize(800, 600);
        f.setLocationRelativeTo(null);
        f.setLayout(null);
  
        final JLabel l = new JLabel("");
        ImageIcon i = new ImageIcon("C:\\Users\\Administrator\\Desktop\\shana.png");
        l.setIcon(i);
        l.setBounds(375, 275, i.getIconWidth(), i.getIconHeight());
        f.add(l);
  
        // MouseAdapter 适配器,只需要重写用到的方法,没有用到的就不用写了
        l.addMouseListener(new MouseAdapter() {
  
            // 只有mouseEntered用到了
            public void mouseEntered(MouseEvent e) {
  
                Random r = new Random();
  
                int x = r.nextInt(f.getWidth() - l.getWidth());
                int y = r.nextInt(f.getHeight() - l.getHeight());
                l.setLocation(x, y);
            }
        });
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }
}



窗口类别

1、java的图形界面中,容器是用来存放 按钮,输入框等组件的。

窗体型容器有两个,一个是JFrame,一个是JDialog

2、JFrame是最常用的窗体型容器,默认情况下,在右上角有最大化最小化按钮

JDialog也是窗体型容器,右上角没有最大和最小化按钮

3、通过调用方法 setResizable(false); 做到窗体大小不可变化

4、模态窗口:当一个对话框被设置为模态的时候,其

背后

的父窗体,是不能被激活的,除非该对话框被关闭

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
 
public class TestGUI {
    public static void main(String[] args) {
        JFrame f = new JFrame("外部窗体");
        f.setSize(800, 600);
        f.setLocation(100, 100);
 
        // 根据外部窗体实例化JDialog
        JDialog d = new JDialog(f);
        // 设置为模态
        d.setModal(true);
 
        d.setTitle("模态的对话框");
        d.setSize(400, 300);
        d.setLocation(200, 200);
        d.setLayout(null);
        JButton b = new JButton("一键秒对方基地挂");
        b.setBounds(50, 50, 280, 30);
        d.add(b);
 
        f.setVisible(true);
        d.setVisible(true);
 
    }
}



布局器



绝对定位(本人常用)

import javax.swing.JButton;
import javax.swing.JFrame;
 
public class TestGUI {
    public static void main(String[] args) {
 
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(200, 200);
        // 设置布局器为null,即进行绝对定位,容器上的组件都需要指定位置和大小
        f.setLayout(null);
        JButton b1 = new JButton("英雄1");
        // 指定位置和大小
        b1.setBounds(50, 50, 80, 30);
        JButton b2 = new JButton("英雄2");
        b2.setBounds(150, 50, 80, 30);
        JButton b3 = new JButton("英雄3");
        b3.setBounds(250, 50, 80, 30);
        // 没有指定位置和大小,不会出现在容器上
        JButton b4 = new JButton("英雄3");
 
        f.add(b1);
        f.add(b2);
        f.add(b3);
        // b4没有指定位置和大小,不会出现在容器上
        f.add(b4);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        f.setVisible(true);
    }
}



FlowLayout顺序布局器

设置布局器为FlowLayout,顺序布局器

容器上的组件水平摆放

加入到容器即可,无需单独指定大小和位置

import java.awt.FlowLayout;
 
import javax.swing.JButton;
import javax.swing.JFrame;
 
public class TestGUI {
    public static void main(String[] args) {
 
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(200, 200);
        // 设置布局器为FlowLayerout
        // 容器上的组件水平摆放
        f.setLayout(new FlowLayout());
 
        JButton b1 = new JButton("英雄1");
        JButton b2 = new JButton("英雄2");
        JButton b3 = new JButton("英雄3");
 
        // 加入到容器即可,无需单独指定大小和位置
        f.add(b1);
        f.add(b2);
        f.add(b3);
 
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        f.setVisible(true);
    }
}



BorderLayout

容器上的组件按照

上北 下南 左西 右东 中

的顺序摆放

import java.awt.BorderLayout;
import java.awt.FlowLayout;
 
import javax.swing.JButton;
import javax.swing.JFrame;
 
public class TestGUI {
    public static void main(String[] args) {
 
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(200, 200);
        // 设置布局器为BorderLayerout
        // 容器上的组件按照上北下南左西右东中的顺序摆放
        f.setLayout(new BorderLayout());
 
        JButton b1 = new JButton("洪七");
        JButton b2 = new JButton("段智兴");
        JButton b3 = new JButton("欧阳锋");
        JButton b4 = new JButton("黄药师");
        JButton b5 = new JButton("周伯通");
 
        // 加入到容器的时候,需要指定位置
        f.add(b1, BorderLayout.NORTH);
        f.add(b2, BorderLayout.SOUTH);
        f.add(b3, BorderLayout.WEST);
        f.add(b4, BorderLayout.EAST);
        f.add(b5, BorderLayout.CENTER);
 
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        f.setVisible(true);
    }
}



GridLayout(网格布局器)

import java.awt.GridLayout;
 
import javax.swing.JButton;
import javax.swing.JFrame;
 
public class TestGUI {
    public static void main(String[] args) {
 
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(200, 200);
        // 设置布局器为GridLayerout,即网格布局器
        // 该GridLayerout的构造方法表示该网格是2行3列
        f.setLayout(new GridLayout(2, 3));
 
        JButton b1 = new JButton("洪七");
        JButton b2 = new JButton("段智兴");
        JButton b3 = new JButton("欧阳锋");
        JButton b4 = new JButton("黄药师");
        JButton b5 = new JButton("周伯通");
 
        f.add(b1);
        f.add(b2);
        f.add(b3);
        f.add(b4);
        f.add(b5);
 
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        f.setVisible(true);
    }
}



setPreferredSize

即便 使用 布局器 ,也可以 通过setPreferredSize,向布局器建议该组件显示的大小.




只对部分布局器起作用,比如FlowLayout可以起作用。 比如GridLayout就不起作用,因为网格布局器必须对齐

import java.awt.Dimension;
import java.awt.FlowLayout;
 
import javax.swing.JButton;
import javax.swing.JFrame;
 
public class TestGUI {
    public static void main(String[] args) {
 
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(200, 200);
        f.setLayout(new FlowLayout());
 
        JButton b1 = new JButton("英雄1");
        JButton b2 = new JButton("英雄2");
        JButton b3 = new JButton("英雄3");
 
        // 即便 使用 布局器 ,也可以 通过setPreferredSize,向布局器建议该组件显示的大小
        b3.setPreferredSize(new Dimension(180, 40));
 
        f.add(b1);
        f.add(b2);
        f.add(b3);
 
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        f.setVisible(true);
    }
}



CardLayout

因为CardLayout需要用到

面板



JComboBox

这些内容暂时还没学的内容,所以放在后面讲:

CardLayout



常用组件

关键字 简介
JLabel 标签
setIcon 使用JLabel显示图片
JButton 按钮
JCheckBox 复选框
JRadioButton 单选框
ButtonGroup 按钮组
JComboBox 下拉框
JOptionPane 对话框
JTextField 文本框
JPasswordField 密码框
JTextArea 文本域
JProgressBar 进度条
JFileChooser 文件选择器

与python类似,可以绑定事件,代码用到了再去查



面板



基本面板(JPanel)

JPanel即为基本面板

面板和JFrame一样都是容器,不过面板一般用来充当

中间容器

,把组件放在面板上,然后再把面板放在窗体上。

一旦移动一个面板,其上面的组件,就会

全部统一跟着移动

,采用这种方式,便于进行整体界面的设计



ContentPane

JFrame上有一层面板,叫做ContentPane

平时通过f.add()向JFrame增加组件,其实是向JFrame上的 ContentPane加东西

ContentPane这个毛毯其实又放在了其他的毛毯上面



分隔条(SplitPanel)

创建一个水平JSplitPane,左边是pLeft,右边是pRight

import java.awt.Color;
import java.awt.FlowLayout;
  
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
  
public class TestGUI {
    public static void main(String[] args) {
  
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(200, 200);
  
        f.setLayout(null);
  
        JPanel pLeft = new JPanel();
        pLeft.setBounds(50, 50, 300, 60);
  
        pLeft.setBackground(Color.RED);
  
        pLeft.setLayout(new FlowLayout());
  
        JButton b1 = new JButton("盖伦");
        JButton b2 = new JButton("提莫");
        JButton b3 = new JButton("安妮");
  
        pLeft.add(b1);
        pLeft.add(b2);
        pLeft.add(b3);
  
        JPanel pRight = new JPanel();
        JButton b4 = new JButton("英雄4");
        JButton b5 = new JButton("英雄5");
        JButton b6 = new JButton("英雄6");
  
        pRight.add(b4);
        pRight.add(b5);
        pRight.add(b6);
  
        pRight.setBackground(Color.BLUE);
        pRight.setBounds(10, 150, 300, 60);
  
        // 创建一个水平JSplitPane,左边是p1,右边是p2
        JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, pLeft, pRight);
        // 设置分割条的位置
        sp.setDividerLocation(80);
  
        // 把sp当作ContentPane
        f.setContentPane(sp);
  
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
        f.setVisible(true);
    }
}



滚动条(JScrollPanel)

:



JScrollPanel

使用带滚动条的面板有两种方式

  • 在创建JScrollPane,把组件作为参数传进去

    JScrollPane sp = new JScrollPane(ta);

  • 希望带滚动条的面板显示其他组件的时候,调用setViewportView

    sp.setViewportView(ta);

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
 
public class TestGUI {
    public static void main(String[] args) {
 
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(200, 200);
 
        f.setLayout(null);
        //准备一个文本域,在里面放很多数据
        JTextArea ta = new JTextArea();
        for (int i = 0; i < 1000; i++) {
            ta.append(String.valueOf(i));
        }
        //自动换行
        ta.setLineWrap(true);
        JScrollPane sp = new JScrollPane(ta);
 
        f.setContentPane(sp);
 
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        f.setVisible(true);
    }
}



多个小面板(TabbedPanel)

在这里插入图片描述
在这里插入图片描述

import java.awt.Color;
import java.awt.FlowLayout;
 
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
  
public class TestGUI {
    public static void main(String[] args) {
  
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(200, 200);
  
        f.setLayout(null);
  
        JPanel p1 = new JPanel();
        p1.setBounds(50, 50, 300, 60);
  
        p1.setBackground(Color.RED);
  
        p1.setLayout(new FlowLayout());
  
        JButton b1 = new JButton("英雄1");
        JButton b2 = new JButton("英雄2");
        JButton b3 = new JButton("英雄3");
  
        p1.add(b1);
        p1.add(b2);
        p1.add(b3);
  
        JPanel p2 = new JPanel();
        JButton b4 = new JButton("英雄4");
        JButton b5 = new JButton("英雄5");
        JButton b6 = new JButton("英雄6");
  
        p2.add(b4);
        p2.add(b5);
        p2.add(b6);
  
        p2.setBackground(Color.BLUE);
        p2.setBounds(10, 150, 300, 60);
  
        JTabbedPane tp = new JTabbedPane();
        tp.add(p1);
        tp.add(p2);
  
        // 设置tab的标题
        tp.setTitleAt(0, "红色tab");
        tp.setTitleAt(1, "蓝色tab");
         
        ImageIcon i = new ImageIcon("e:/project/j2se/j.png");
        tp.setIconAt(0,i );
        tp.setIconAt(1,i );
  
        f.setContentPane(tp);
  
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
        f.setVisible(true);
    }
}



布局器(CardLayerout)

CardLayerout 布局器 很像

TabbedPanel

,在本例里面上面是一个下拉框,下面是一个CardLayerout 的JPanel

这个JPanel里有两个面板,可以通过CardLayerout方便的切换

在这里插入图片描述

在这里插入图片描述



使用菜单(JMenu)

先建立JmenuBar(菜单栏),然后添加菜单JMenu,把组件放到JMenu,然后把JmenuBar放上去

把菜单栏加入到frame,这里用的是set而非add f.setJMenuBar(mb);

在菜单栏放菜单项,还可以分割的

package gui;
 
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
 
public class TestGUI {
    public static void main(String[] args) {
 
        JFrame f = new JFrame("LoL");
        f.setSize(400, 400);
        f.setLocation(200, 200);
 
        JMenuBar mb = new JMenuBar();
 
        JMenu mHero = new JMenu("英雄");
        JMenu mItem = new JMenu("道具");
        JMenu mWord = new JMenu("符文");
        JMenu mSummon = new JMenu("召唤师");
        JMenu mTalent = new JMenu("天赋树");
 
        // 菜单项
        mHero.add(new JMenuItem("近战-Warriar"));
        mHero.add(new JMenuItem("远程-Range"));
        mHero.add(new JMenuItem("物理-physical"));
        mHero.add(new JMenuItem("坦克-Tank"));
        mHero.add(new JMenuItem("法系-Mage"));
        mHero.add(new JMenuItem("辅助-Support"));
        mHero.add(new JMenuItem("打野-Jungle"));
        mHero.add(new JMenuItem("突进-Charge"));
        mHero.add(new JMenuItem("男性-Boy"));
        mHero.add(new JMenuItem("女性-Girl"));
        // 分隔符
        mHero.addSeparator();
        mHero.add(new JMenuItem("所有-All"));
 
        mb.add(mHero);
        mb.add(mItem);
        mb.add(mWord);
        mb.add(mSummon);
        mb.add(mTalent);
 
        f.setJMenuBar(mb);
 
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        f.setVisible(true);
    }
}



工具栏(JToolBar)



表格控件(JTable、TableModel)



日期控件

swing没有自带的日期控件,需要第三方的类



Swing的线程

有三种线程

1、初始化线程

初始化线程用于创建各种

容器



组件

并显示他们,一旦创建并显示,初始化线程的任务就结束了。

2、事件调度线程

通过

事件监听

的学习,我们了解到Swing是一个事件驱动的模型,所有和事件相关的操作都放是放在

事件调度线程

(Event Dispatch)中进行的。比如点击一个按钮,对应的

ActionListener.actionPerformed

方法中的代码,就是在事件调度线程 Event Dispatch Thread中执行的。

3、长耗时任务线程

有时候需要进行一些长时间的操作,比如访问数据库,文件复制,连接网络,统计文件总数等等。 这些操作就不适合放在

事件调度线程

中进行,因为占用时间久了,会让使用者感觉界面响应很

卡顿

。 为了保持界面

响应的流畅性

,所有长耗时任务都应该放在专门的 长耗时任务线程中进行



Swing的皮肤

风格和皮肤可以自由切换的



版权声明:本文为m0_46521785原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。