文章目录
针对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;
}
二叉树排序——插入数据
使用了递归嗷,相对当前节点而言,小的值去左边,大的值去右边
过程:
-
67 放在根节点
-
7 比 67小,放在67的左节点
-
30 比67 小,找到67的左节点7,30比7大,就放在7的右节点
-
73 比67大, 放在67的右节点
-
10 比 67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,放在30的左节点。
…
-
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的皮肤
风格和皮肤可以自由切换的