包含的内容有:for循环、while循环、switch语句,if语句
public static void main(String[] args) {
int a=0;
for (int i = 0; i <10 ; i++) {
a+=1;
}
while (a>0){
if(a%2==0){
System.out.println(a+"是偶数");
}
else{
System.out.println(a+"是奇数");
}
a-=1;
}
switch(a){
case 0:
System.out.println("a是"+0);
break;
case 1:
System.out.println("a是"+1);
break;
}
}
primitive主数据类型+引用=变量
类型 | 位数 | 值域 |
---|---|---|
boolean | JVM决定 | true或false |
char | 16 bits | 0~65535 |
byte | 8 bits | -128~127 |
short | 16 bits | -32768~32767 |
int | 32 bits | ±2147483648 |
long | 64 bits | - 很大 ~ + 很大 |
float | 32 bits | 范围规模可变 |
double | 64 bits | 范围规模可变 |
注意,整数默认为int类型,小数默认为double类型,在创建其他类型变量以及运算时要标明。
float a=1.5f;//这个f不写则1.5默认为double,然后由double转为float
当大杯转小杯时可能会导致数据溢出,因此要注意数据的范围,以此来选择数据类型。
当编译器无法识别能否进行数据转化时,需要进行强制转换。
public class test {
public static void main(String[] args) {
long a=123;
//int b=a;会报错,编译不通过
int b=(int) a;
System.out.println(a);
}
}
自动强制转换的规则:
如果一个变量是类类型,而非基本类型,那么该变量又叫引用。
可以把实例化的对象的reference当做一个遥控器,远程遥控其他的行为。
当实例化一个对象的时候,会创建一个遥控器,遥控器在栈区,对象在堆区。
Book b=new Book();//new Book();只是创建一个对象,前面的b表示引用指向他,等号表示指向
Book c=new Book();
Book d=c;//这时候c和d两个遥控器遥控一个对象
c=b;//这时候c和b遥控同一个对象,但是d仍然遥控自己的那个对象
运算符 | 描述 |
---|---|
&& | 与 |
|| | 或 |
!= | 不等于 |
! | 非 |
短运算符
比如&&,当两端都为真才得到真,当左边为假便不会再计算右边
长运算符
比如&,强制虚拟机必须做两端的操作
符号 | 描述 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取余 |
++ | 自增 |
– | 自减 |
符号 | 描述 |
---|---|
> | 大于 |
>= | 大于或等于 |
< | 小于 |
<= | 小于或等于 |
== | 是否相等 |
!= | 不等于 |
参见ACM——位运算符整理
用的不多,用来提高运算速度,可以忽略不学。
符号 | 描述 |
---|---|
+= | a=a+x |
-= | a=a-x |
*= | a=a*x |
/= | a=a/x |
%= | a=a%x |
&= | a=a&x |
|= | a=a|x |
^= | a=a^x |
>>= | a=a>>x |
<<= | a=a< |
(判断)?A:B;
当判断的内容为真时返回A,否则返回B
int k = i < j ? 99 : 88;
数组是一个固定长度,包含相同类型数据的容器,具有默认值
public class demo extends test{
public static void main(String[] args) {
int []a=new int[5];//或者是写int a[]=new int[5];
int []b = new int[]{100,102,444,836,3236};
a[0]=15;
System.out.println(a[0]);
System.out.println(a.length);
}
}
或者使用Arrays类的方法,或者是
复制数组: System.arraycopy(src, srcPos, dest, destPos, length)
public static void main(String[] args) {
int a [] = new int[]{18,62,68,82,65,9};
int b[] = new int[3];//分配了长度是3的空间,但是没有赋值
//通过数组赋值把,a数组的前3位赋值到b数组
System.arraycopy(a, 0, b, 0, 3);
//把内容打印出来
for (int i:b) {
System.out.print(b[i] + " ");
}
}
public static void main(String[] args) {
//初始化二维数组,
int[][] a = new int[2][3]; //有两个一维数组,每个一维数组的长度是3
a[1][2] = 5; //可以直接访问一维数组,因为已经分配了空间
//只分配了二维数组
int[][] b = new int[2][]; //有两个一维数组,每个一维数组的长度暂未分配
b[0] =new int[3]; //必须事先分配长度,才可以访问
b[0][2] = 5;
//指定内容的同时,分配空间
int[][] c = new int[][]{{1,2,4}, {4,5}, {6,7,8,9}};
}
格式:可以通过values.for
快捷键实现
for (int each : values) {
System.out.println(each);//do something
}
包:import java.util.Arrays;
常用方法:
方法 | 描述 | 作用 |
---|---|---|
int[] b = Arrays.copyOfRange(a, 0, 3); | a是原数组,0取的到,3取不到 | 数组复制 |
String content = Arrays.toString(a); | a是数组,得到[18, 62, 68, 82, 65, 9] | 转换为字符串 |
Arrays.sort(a); | a是数组 | 升序排序 |
Arrays.binarySearch(a, 62 ): |
62是要搜索的元素 | 搜索,前提是升序排列了,不存在返回负数 |
Arrays.equals(a, b) | ab为两个数组 | 判断两个数组是否相同 |
Arrays.fill(a, 5 ); |
填充a数组全为5 | 填充(初始化) |
Dog[] myDogs=new Dog[3];
myDogs[0]=new Dog();//放咖啡的杯子就只能放咖啡
myDogs[0].name="Fido";
myDogs[0].bark();
在创建数据时,标明private,否则实例化的对象可以直接修改对应的值,把内裤给别人看了就挺尴尬的…
可以创建对应的方法来获取或者设置相应数据,比如
public void setname(String newname){
name=newname+getname();
}
当你仅仅只是声明一个变量,但是不初始化的时候,java会默认给你赋一个值
数据类型 | 默认值 |
---|---|
integers | 0 |
floating points | 0.0 |
boolean | false |
references | null |
实例变量是声明在类里面的,局部变量是声明在类的方法里的,局部变量在使用前必须要先赋值,否则编译器会报错。
在参数位置加三个点,例如
public void attack(Hero ... heros){
for (Hero:heros) {
//do something
}
}
//传参时参数用逗号隔开
声明为public,构造函数可以重载哦,注意别写void ,否则就成了普通方法
class demo {
public demo(String a){
System.out.println("调用了构造函数参数是"+a);
}
public demo(){
System.out.println("调用了默认构造");
}
public static void main(String[] args) {
demo test1=new demo("草");
demo test2=new demo();
}
}
指向当前类本身的元素,类似c++,即使是同名的,this.name=name;
也可以赋值,称为隐式调用。
但是只要和属性不同名,直接赋值也OK的,this还能实现构造方法
的相互调用
要访问别的class用import导入哦,import package.class
标黄了就表示不能访问到,不写默认为protected
自身 | 同包子类 | 不同包子类 | 同包类 | 其他类 | |
---|---|---|---|---|---|
private | 访问 | 继承 | 继承 | 访问 | 访问 |
protected | 访问 | 继承 | 继承 | 访问 | 访问 |
public | 访问 | 继承 | 继承 | 访问 | 访问 |
package | 访问 | 继承 | 继承 | 访问 | 访问 |
对象属性初始化有3种
"some hero"
;封装是面向对象三大特征之一(封装、继承、多态),是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的
将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作与访问,成员变量设置为private
,提供对应的getAbb
和setAbb
方法
子类继承父类可以获得父类的属性和方法,一个父类可以被多个子类继承,但是一个子类只能继承一个父类,不过一个类可以实现多个接口,Object类是所有类的父类,类的继承关键字是extents
在子类方法中寻找一个变量时如下顺序:
子类中的所有构造方法都会默认先访问父类的无参构造方法
,子类会继承父类的数据,因此在子类初始化前一定要先进行父类初始化,每一个子类第一句话默认都是super()
顺序如下:先当前再长远
实例化子类的时候,会先调用父类的构造方法,当多个构造方法时,默认调用父类无参构造方法
如果想调用父类带参构造,需要用super,例如
super(name);//在ADHero带参构造下写,这个相当于调用参数为name的父类构造方法
super.speed;//父类的属性
super.useItem();//父类的方法
this是访问或调用本类的成员变量或方法,supper是操作父类的
父类静态变量 > 父类静态初始块 > 子类静态变量 > 子类静态初始块 > 父类成员变量 > 父类非静态初始块 > 父类构造器 > 子类成员变量 > 子类非静态初始块 > 子类构造器
当子类需要父类的功能,而功能主题子类有自己特有内容时,可以重写父类的方法,这样既沿袭了父类的功能,又定义了子类独有的内容。如果在子类中定义一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,就说子类的方法覆盖了父类的方法。简单来说,子类的方法和父类一模一样
就说覆盖,注解是@Override
四个特点:
四个注意事项:(多态语法)
多态是同一对象在不同时刻表现出来的不同形态
多态的形式:
多态的前提和体现:
操作符的多态:
类的多态:
/**
LifePotion(血瓶)和MagicPotion(魔瓶)都继承于Item,当使用effect方法时只需要传入Item对象即可,
不用再写useLifePotion和useMagicPotion两个方法
*/
public class Hero {
public String name;
protected float hp;
public void useItem(Item i){
i.effect();
}
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
LifePotion lp =new LifePotion();
MagicPotion mp =new MagicPotion();
garen.useItem(lp);
garen.useItem(mp);
}
}
多态的优点:
多态的弊端:
instanceof判断引用是否指向对象
子类转父类,向上:ADHero引用转Hero引用是完全没问题的
父类转子类,向下:需要用到子类的类名进行强制转化,为什么要加呢,因为不知道子类的引用指向的对象是谁,
注:父类转子类之后再转子类可能会报错
只对方法进行声明,而不去实现,在java中,一个没有方法体的方法称为抽象方法,如果类中有抽象方法,该类必须定义为抽象类
abstruct
进行修饰该抽象类的子类对象
该抽象类的子类对象
接口就像一种公共规范,只要符合规范标准,大家就可以通用。Java中的接口更多体现在对行为的抽象
interface
修饰implements
表示成员变量只能是常量,默认修饰符:public static final
接口没有构造方法,因为接口更多的是体现在对行为的抽象
成员方法只能是抽象方法
,默认修饰符:public abstruct
JDK8之后接口都有了默认实现,可以在实现的类中直接调用
接口就像约定,接口定义某些方法,交给某个类去实现,一个类可以实现多个接口 用逗号隔开就行
接口定义关键字:interface
public interface AD{
public void physicAttack();
}
类去实现接口的关键字:implements
public class ADHero extends Hero implements AD{
public void physicAttack(){
System.out.println("发起进攻");
}
}
注意,假如类不实现接口约定的方法,这个类会报错。
注:可以继承一个类时同时实现多个接口
# 成员区别
抽象类---->常量和变量,可以定义四种访问权限,有构造方法,有抽象方法,也有非抽象方法
接口------>只有公共的静态的常量和抽象方法
# 关系区别
抽象类---->只能被单继承
接口------>可以被类多实现(被其他接口多继承)
# 设计理念区别
抽象类---->抽象类是对类抽象,是对根源的抽象,包括属性和行为,抽象类反映的是is-a关系,表示这个对象是什么
接口------>主要是对行为动作的抽象,接口反映的是like-a关系,表示这个对象能做什么,接口是更加纯粹的抽象。
注:最大的相同点是抽象类和接口都不能直接实例化
该接口的实现类对象
该接口的实现类对象
例如我们想升级接口,但是如果升级的话所有的实现类都需要实现这个新方法,可以使用一个新接口继承这个接口,然后找类去实现它,但是太麻烦了,于是有了默认实现方法
public interface Test{
public default void show() {//public可以省略,但是default不行
System.out.println("默认方法执行了,冲!");
}
}
静态方法只能被接口调用,这样做的目的是防止实现多个接口的时候,这多个接口存在同一个静态方法,导致混乱
public interface Test{
public static void show() {
System.out.println("静态方法执行了,冲!");
}
}
public class TestInterface{
public void main(String[] args) {
Test.show();
}
}
Java 9中新增了带方法体的私有方法,这其实在Java8中就埋下了伏笔:Java8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法
,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java9增加私有方法的必然性
定义的格式:
private 返回值类型 方法名(参数列表)
private static 返回值类型 方法名(参数列表)
内部类就是在类中定义一个类,例如在类A中定义了类B,这个类B就是内部类,
按照内部类在类中定义的位置不同,可以分为两种形式:
内部类的定义格式如下:
public class ClassNameA{
修饰符 class ClassNameB{
}
}
内部类:java文件的class里面的类,属性和方法平等的位置
如何访问内部类呢?
外部类名.内部类名 对象名 = 外部类名.内部类名
通常情况下,我们把类做成内部类的目的就是不让别人访问到,因此通常是将内部类修饰为private
,外部类定义一个方法去调用内部类的函数,我们只需要调用外部对象的该方法即可
如果想调用局部内部类只能是在方法中创建内部类对象,然后使用内部类对象去访问方法
public class Test {
public int num = 10;
public void method() {
class Inner {
public void show(){
System.out.println(num);
}
}
Inner inner = new Inner();
inner.show();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
通过new 外部类().new 内部类()
的形式去使用
Hero garen = new Hero();
BattleScore score = garen.new BattleScore();
static修饰,就不用再创建外部类
Hero.EnemyCrystal crystal = new Hero.EnemyCrystal();
前提是存在一个类,这个类可以是具体类也可以是抽象类,格式如下:
xxxx = new ClassNameOrInterfaceName(){
//重写方法
}
Hero是一个抽象类(abstruct),重写方法表示继承了这个类或实现了这个接口
Hero hero = new Hero(){
//实现一个attack新方法
public void attack() {
System.out.println("新的进攻手段");
}
};
hero.attack();
///--------也可以------//
new Hero(){
//实现一个attack新方法
public void attack() {
System.out.println("新的进攻手段");
}
}.attack();
匿名内部类的本质是继承了该类或实现了该接口的匿名对象
内部类与匿名类不一样的是,内部类必须声明在成员的位置,即与属性和方法平等的位置
本地类和匿名类一样,直接声明在代码块里面,可以是主方法,for循环里等等地方
public abstract class Hero {
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
public abstract void attack();
public static void main(String[] args) {
//与匿名类的区别在于,本地类有了自定义的类名
class SomeHero extends Hero{
public void attack() {
System.out.println( name+ " 新的进攻手段");
}
}
SomeHero h =new SomeHero();
h.name ="地卜师";
h.attack();
}
}
//一个int类型数据
int i = 5;
//基本类型转换成封装类型
Integer it = new Integer(i);
//一个封装类型
Integer it = new Integer(5);
//封装类型转换成基本类型
int i2 = it.intValue();
基本数据类型通过等号转换为包装类,称为装箱
int i = 5;
//自动装箱为Integer类型
Integer it2 = i;
包装类通过等号转为基本数据类型,称为拆箱
Integer it = new Integer(5);
//自动拆箱为int基本数据类型
int i3 = it;
//int的最大值
System.out.println(Integer.MAX_VALUE);
//int的最小值
System.out.println(Integer.MIN_VALUE);
数字–>字符串(第二种方式不介绍了)
String str = String.valueOf(5);
//或者是String str = 5+"";
字符串–>数字
int i= Integer.parseInt("15");
printf和format能够达到一模一样的效果,如何通过eclipse查看java源代码 可以看到,在printf中直接调用了format
String sentenceFormat ="%s 在进行了连续 %d 次击杀后,获得了 %s 的称号%n";
System.out.printf(sentenceFormat,name,kill,title);
char用来保存一个字符,char对应的封装类是Character
public class TestChar {
public static void main(String[] args) {
System.out.println(Character.isLetter('a'));//判断是否为字母
System.out.println(Character.isDigit('a')); //判断是否为数字
System.out.println(Character.isWhitespace(' ')); //是否是空白
System.out.println(Character.isUpperCase('a')); //是否是大写
System.out.println(Character.isLowerCase('a')); //是否是小写
System.out.println(Character.toUpperCase('a')); //转换为大写
System.out.println(Character.toLowerCase('A')); //转换为小写
String a = 'a'; //不能够直接把一个字符转换成字符串
String a2 = Character.toString('a'); //转换为字符串
}
}
charAt | 获取字符 |
---|---|
toCharArray | 获取对应的字符数组 |
subString | 截取子字符串 |
split | 分隔 |
trim | 去掉首尾空格 |
toLowerCase toUpperCase | 大小写 |
indexOf lastIndexOf contains | 定位 |
replaceAll replaceFirst | 替换 |
startsWith | 以…开始 |
endsWith | 以…结束 |
equals | 判断是否相等 |
equalsIgnoreCase | 忽略大小写判断是否相等 |
package character;
public class TestString {
public static void main(String[] args) {
String str1 = "let there ";
StringBuffer sb = new StringBuffer(str1); //根据str1创建一个StringBuffer对象
sb.append("be light"); //在最后追加
System.out.println(sb);
sb.delete(4, 10);//删除4-10之间的字符,都是闭区间
System.out.println(sb);
sb.insert(4, "there ");//在4这个位置插入 there
System.out.println(sb);
sb.reverse(); //反转
System.out.println(sb);
}
}
StringBuffer与StringBuilder的区别是:StringBuilder是线程安全的
方法 | 描述 |
---|---|
Random random = new Random(10); | 10是seed,可以不写 |
random.nextInt(10) | 随机在最小int~最大int取一个数 |
random.nextInt(10) | [0,x)的随机整数,x不能小于0 |
nextFloat() | 随机一个float |
nextDouble() | 随机一个Double |
nextLong() | 随机一个Long |
nextBoolean() | 随机true或false |
nextBytes(byte[] bytes) | 为一个byte类型的数组随机赋值 |
nextGaussian() | 随机生成一个高斯分布的浮点数 |
方法 | 描述 |
---|---|
Math.abs() | 取绝对值 |
Math.ceil() | 向上取整 |
Math.floor() | 向下取整 |
Math.round(float a) | 四舍五入取整 |
Math.max(int a,int b) | 去较大的值 |
Math.min(int a,int b) | 去较小的值 |
Math.pow(double a,double b) | a的b次幂 |
Math.random() | 返回一个[0.0,1.0)之间的double值 |
方法 | 描述 |
---|---|
System.currentTimeMillis() | 获取系统当前毫秒值 |
System.exit(0); | 结束正在运行的Java程序 |
System.gc(); | 垃圾回收器 |
System.getProperties() | 确定当前的系统属性 |
System.arraycopy(src, 2, dest, 0, 2); | 复制数组 |
注意:是java.util.Date;
而非 java.sql.Date,此类是给数据库访问的时候使用的
零这个数字,就代表Java中的时间原点,其对应的日期是1970年1月1日 8点0分0秒 ,
为什么对应1970年呢? 因为1969年发布了第一个 UNIX 版本:AT&T,综合考虑,当时就把1970年当做了时间原点。
所有的日期,都是以为这个0点为基准,每过一毫秒,就+1。
// 当前时间
Date d1 = new Date();
System.out.println(d1);
// 从1970年1月1日 早上8点0分0秒 开始经历的毫秒数
Date d2 = new Date(5000);
System.out.println(d2);
返回long型的时间戳,直接打印对象,会看到 “Tue Jan 05 09:51:48 CST 2016” 这样的格式,可读性比较差,可以进行日期格式化
注:System.currentTimeMillis()
也可以达到gettime()的功能,而且更精确
package date;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDate {
public static void main(String[] args) {
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS" );
Date d= new Date();
String str = sdf.format(d);
System.out.println("当前时间通过 yyyy-MM-dd HH:mm:ss SSS 格式化后的输出: "+str);
}
}
日历类可以转为日期类,还能翻日历,别鸡巴学了,感觉没啥用啊
import java.util.Calendar;
import java.util.Date;
public class TestDate {
public static void main(String[] args) {
//采用单例模式获取日历对象Calendar.getInstance();
Calendar c = Calendar.getInstance();
//通过日历对象得到日期对象
Date d = c.getTime();
Date d2 = new Date(0);
c.setTime(d2); //把这个日历,调成日期 : 1970.1.1 08:00:00
}
}
所有的饮用都能访问,用static修饰,到时候直接用 类+原点操作符
就能直接调用
例如Math库的Pi,math.pi
修饰类。这个类不能够被继承
修饰方法。这个方法不能够被重写
修饰基本类型变量。该变量只有一次赋值机会
修饰引用。表示该引用只有1次指向对象的机会
修饰常量。表示可以直接访问当不能修改的常量
public static final int itemTotalNumber = 6;
在类中声明一个方法,这个方法没有实现体,是一个“空”方法 ,即抽象方法。
一个类有抽象方法,就必须声明为抽象类。一个抽象类可以没有抽象方法。
抽象类不能直接实例化,只能通过子类来实例化
一个子类继承了抽象类就必须重写抽象方法,重写的时候就不用写abstract了
函数是有输入,有输出的额一个过程,面向对象思想强调的是一切必须通过对象的形式来做事情,
函数式思想则尽量忽略面向对象的复杂语法,强调做什么,而不是以什么形式去做,
lambda表达式就是函数式编程思想的体现
需求:启动一个线程,输出一句话
public class Test {
public static void main(String[] args) {
//实现类的方式
//MyRunnable myRunnable = new MyRunnable();
//Thread t = new Thread(myRunnable);
//t.start()
//匿名内部类的方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程启动啦!");
}
}).start();
//lambda方式改造
new Thread( ()-> {
System.out.println("多线程启动啦!");
}).start();
}
}
lambda方式分析:
格式:(形参)->{代码块}
如果有多个形参就用逗号分开,没有为空即可
有且只有一个抽象方法
Addable是一个接口,其中有add这个方法,执行完控制台输出了30,
执行lambda表达式的时候,系统偷偷的自动创建了一个对象,lambda实现了add方法
lambda表达式的结果其实就是一个实现了接口的对象,这也解释了传的参数应该是Addable的实现对象
public class Test {
public static void main(String[] args) {
useAddable( (int x,int y)-> {
return x+y;
});
}
private static void useAddable(Addable a) {//传的肯定是Addable的实现对象,所以可以调用
int sum = a.add(10,20);
System.out.println(sum);
}
}
# 所需类型不同
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda表达式:只能是接口
# 使用限制不同
如果接口中有且仅有一个抽象方法,可以使用lambda表达式,也可以使用匿名内部类
如果接口有多个抽象方法,只能使用匿名内部类,不能使用lambda表达式
# 实现原理不同
匿名内部类:编译以后产生一个单独的.class字节码文件
Lambda表达式:编译之后没有一个单独的.class字节码文件,对应的字节码会在运行时动态生成
1.8之后可用
aMap.forEach( (k,v)->{System.out.println(k+" "+v);} );
Throwable类是Java语言中所有异常和错误的超类
如果程序出现了问题,我们又没有做任何处理,最终JVM会做默认的处理:
嗐,千篇一律,无需多言
try {
//todo
} catch (Exception e) {
e.printStackTrace();//打印异常信息
} finally {
//todo
}
Message是在构造方法中进行赋值的
方法 | 描述 |
---|---|
getMessage() | 返回此异常的原因 |
toString() | 返回该异常的原因和类名 |
printStackTrace() |
将异常的类名,原因,出现异常代码的位置信息 |
Java中异常分为两大类:编译时异常与运行时异常,也被称为受检异常和非受检异常
所有的RuntimeException类以及子类被称为运行时异常,其他的异常都是编译时异常
并不是所有情况我们都有权限进行异常的处理,有时候可能出现的异常我们是处理不了的
针对这种情况,Java提供了throws解决方案,格式是跟在方法括号后面
:
public void yourMethod() throws 异常类名 {
//todo
}
异常只是被抛出当前代码块了,但是在哪里使用还是需要进行try…catch的
只要我们的类继承RuntimeException或者Exception就可以成为其中一员
编写一个异常类:
public class ScoreException extends Exception {
public ScoreException() {
}
public ScoreException(String message) {
super(message)
}
}
使用这个异常:
public class GenericDemo {
public void checkScore(int score) throws ScoreException {
if (score<0 || score>100) {
throw new ScoreException();//使用throw抛出这个异常
} else {
System.out.println("分数正常!");
}
}
}
throws:
throw:
程序( program):是为完成特定任务、用某种语言编写的一组指令的集合是一段静态的代码
。(程序是静态的)
进程( process):是程序的一次执行过程。正在运行的一个程序,进程
作为资源分配的单位
,在内存中会为每个进程分配不同的内存区域。进程是动态的)是一个动的过程,进程的生命周期:有它自身的产生、存在和消亡的过程
线程( thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的.
并行:多个CPU同时执行多个任务
并发:一个CPU“同时”执行多个任务(采用时间片切换)
注:异常线程会影响主线程的执行
继承Thread类,重写run方法:
//继承Thread类
public class MyThread extends Thread {
//重写run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
//发现仍然是单线程,因为直接调用run方法其实并没有启动线程
//myThread1.run();
//myThread2.run();
myThread1.start();
myThread2.start();
}
}
实现Runnable接口,实现run方法,将该类作为参数传入Thread对象:
public class MyRunnable implements Runnable {
//重写run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable,"猴子");
Thread thread2 = new Thread(myRunnable,"大象");
//使用匿名内部类的形式
Thread thread3 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
};
thread3.setName("老虎");
thread1.start();
thread2.start();
thread3.start();
}
}
实现Callable接口,jdk1.5之后出现:
前两种方式有两点不足:
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//Callable可以写成Callable<返回值类型>,不写则默认为Object
public class MyThread implements Callable {
@Override
public Integer call() throws Exception {
return new Random().nextInt(10);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
Thread thread = new Thread(futureTask);
thread.start();
//获取线程的返回值
Object obj = futureTask.get();
System.out.println(obj);
}
}
阻塞方式结束可以是join()
方法结束
即操作用synchronized包裹起来,就算synchronized包裹的是this,但是不影响多个线程对同一数据的同步,因为这里是实现的Runnable接口,创建线程的时候用的是同一个对象(即同一把锁),可以达到相同的目的。同时注意锁的时候不要锁多了,只需要锁一两步有安全隐患的代码就好了
synchronized (任意对象) {
多条语句操作共享数据的代码
}
public class MyRunnable implements Runnable {
private int number = 100;
//重写run方法
@Override
public void run() {
while (true) {
synchronized (this) {
if (number>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(number);
number--;
}
}
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable, "窗口1");
Thread thread2 = new Thread(myRunnable, "窗口2");
thread1.start();
thread2.start();
}
}
继承Thread类出现synchronized不起作用的问题:
如果是继承Thread,同时synchronized(this)的话会失效,因为多个线程是不同的锁,不同的锁不会相互干扰,因此,没有达到同步的目的,可以写个字符串(字符串常量池 ),或者是一个static常量(最好也加上final修饰),或者是构造的时候穿过来个东西作为锁,最好写该类的字节码信息(MyClass.class)
,因为不管对象有多少个,类的字节码信息是唯一的。同时注意,当锁必须是引用数据类型,不能是基本数据类型
如果是final修饰了引用,只是不能修改地址而已,对象的属性还是可以修改的,修改了锁还是一个锁
如果两个方法使用同一个锁,当A方法把锁锁住后,B方法同样被锁住
在方法前加上synchronized
修饰即可,但锁其实还是有问题,因为锁默认是类的this,如果是继承Thread类还是会有问题,怎么解决呢?可以加上static进行修饰,这样锁就是同一个了相当于类的字节码文件,不过就不能使用this了,可以通过Thread.currentThread().getname()获取线程名称,注意同步代码块效率更高
,同时,如果一个类中多个方法被synchronized
修饰,A方法调用锁住锁后B方法也会被锁住,即本类全锁,因为锁是this
public static synchronized void test(){
//todo
}
synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀,是虚拟机级别的。
而Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。
同时也注意是不是同一把锁的问题呀!!!,嫌麻烦就直接使用实现Runnable接口的方式就好了
Lock是接口,不能直接实例化,常采用它的实现类ReentrantLock
来实例化,代码如下:
public class MyRunnable implements Runnable {
private int number = 100;
private Lock lock = new ReentrantLock();
//重写run方法
@Override
public void run() {
while (true) {
try {
lock.lock();
if (number>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(number);
number--;
}
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable, "窗口1");
Thread thread2 = new Thread(myRunnable, "窗口2");
thread1.start();
thread2.start();
}
}
注:通常情况下使用try...finally
来包裹操作,防止中间出现问题无法释放锁
所谓的原子性操作
即不可中断
的操作,比如赋值操作,原子性操作本身是线程安全的
,但是i++
这个行为,事实上是有3个原子性操作组成的:
JDK6 以后,新增加了一个包java.util.concurrent.atomic
,里面有各种原子类,比如AtomicIntege
而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);
}
}
concurrent
包提供了一些高效的映射、有序集合、队列的实现:
ConcurrentHashMap
、ConcurrentSkipListMap
、ConcurrentSkipListSet
、ConcurrentLinkedQueue
借助Collections.synchronizedList
,可以把ArrayList转换为线程安全的List,与此类似的,还有HashSet,LinkedList,HashMap等等非线程安全的类
List<Integer> list2 = Collections.synchronizedList(list1);
方法 | 说明 |
---|---|
wait() | 让当前线程等待,直到另一个线程调用notify()或notifyall() |
notify() | 唤醒正在等待的单个线程,沿着wait之后继续执行代码 |
notifyall() | 唤醒正在等待的所有线程,沿着wait之后继续执行代码 |
注:wait和notify必须放在synchronized修饰的方法或代码块中,wait释放了锁,sleep没有释放锁
奶箱:
public class Box {
//表示剩下几瓶奶
private int milk;
//表示奶箱的状态
private boolean state = false;
//生产奶
public synchronized void put(int milk) {
if (state) {
try {
wait();//如果有牛奶应该是等待消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有牛奶再生产
this.milk = milk;
System.out.println("送奶工将第"+this.milk+"放入奶箱");
state = true;
//唤醒
notify();
}
//消费奶
public synchronized void get() {
if (!state) {//没有牛奶就等
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有牛奶就消费
System.out.println("用户拿到第"+this.milk+"奶");
state = false;
//唤醒
notify();
}
}
生产者:
public class Productor implements Runnable {
private Box b;
public Productor(Box b) {
this.b = b;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
b.put(i);
}
}
}
消费者:
public class Customer implements Runnable {
private Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true) {
b.get();
}
}
}
测试类:
public class BoxTest {
public static void main(String[] args) {
Box box = new Box();
Productor productor = new Productor(box);
Customer customer = new Customer(box);
Thread thread1 = new Thread(productor);
Thread thread2 = new Thread(customer);
thread1.start();
thread2.start();
}
}
场景:一个生产者唤醒等待池中的线程,里面既有生产者也有消费者,一下子会全唤醒,这样不好,我们可以分开,即生产者有自己的等待池,消费者有自己的等待池,一个Lock可以拥有一个同步队列和多个等待队列
class MyThread() {
//xxx
Lock lock = new ReentrantLock();
Condition produceCondition = lock.newCondition();//生产者等待队列
Condition consumeCondition = lock.newCondition();//消费者等待队列
//xxx
public void setProduct() {
lock.lock();
try {
if (xx) {
//生产者进入生产者等待队列
produceCondition.await();
}
//唤醒消费者来消费
consumeCondition.signal();
}finally{
lock.unlock();
}
}
}
Thread提供了currentThread()
静态方法获取当前线程
Thread.currentThread().setName("主线程");
super(name)
:通过构造方法往上传Java实现的是抢占式调度模型,即优先级高的线程可以获得的CPU时间片段多一点。除此之外还有分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片,默认为5,最小为1,最大为10
当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程,即将其他线程进入阻塞状态。
注意:必须先 start,再join才有效
时间以毫秒为单位
Thread.sleep(1000);//线程阻塞一秒钟
将A线程设置为B线程的伴随线程,当B线程停止的时候,A线程也不要存在了,注意,要先将A设置为守护线程再启动A线程
该方法要被废弃了,可以了解一下
Thread.currentThread().stop();
每一个任务都去执行一个新的线程,开销还是很大,因此有了线程池,将任务丢到线程池中去执行即可
目前还用不到,等有需要会补充。
File是文件和目录的路径的抽象表示,它并不是一个真正的文件,仅仅是路径名,可以存在也可以不存在
方法 | 说明 |
---|---|
File(String pathname) | 直接写路径进行构造 |
File(String parent,String child) | 目录和子目录进行拼接,都是String |
File(File file,String child) | 前面是文件,后面是子目录的文件名 |
exists() | 判断文件是否存在 |
isDirectory() | 判断是否是文件夹 |
isFile() | 判断是否是文件(非文件夹) |
length() | 获取文件长度 |
lastModified() | 获取文件最后修改时间 |
setLastModified(0 ) |
设置文件修改时间为1970.1.1 08:00:00 |
f1.renameTo(f2) | 文件重命名 |
list() | 以字符串数组的形式 ,返回当前文件夹下的所有文件 |
listFiles() | 以文件数组的形式 ,返回当前文件夹下的所有文件 |
getParent() | 以字符串形式返回文件所在文件夹 |
getParentFile() | 以文件形式返回获取所在文件夹 |
mkdir() | 创建文件夹,如果父文件夹skin不存在,创建就无效 |
mkdirs() | 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹 |
createNewFile() | 创建一个空文件,如果父文件夹skin不存在,就会抛出异常 |
getParentFile().mkdirs() | 创建一个空文件之前,通常都会创建父目录 |
listRoots() | 列出所有的盘符c: d: e: 等等 |
delete() | 刪除文件 |
deleteOnExit(); | VM结束的时候,刪除文件,常用于临时文件的删除 |
public class Test {
public static void main(String[] args) {
//给定一个路径
File srcFile = new File("E:\\IdeaProjects\\javatest");
//调用方法
getAllFilePath(srcFile);
}
public static void getAllFilePath(File srcFile) {
File[] files = srcFile.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
//递归调用
getAllFilePath(file);
} else {
System.out.println(file.getAbsolutePath());
}
}
}
}
}
IO:输入(读)/输出(写)(Input/Output),从其他位置加载到程序(内存)中称为输入,反之称为输出
流:流是一种抽象概念,是对数据传输的总称,数据在设备间的传输称为流,流的本质是数据传输
按数据的流向分:
按数据类型分:
我们说的IO流的分类一般是按数据类型
分的
InputStream是一个抽象类,是所有输入字节流类的超类
OutputStream也是一个抽象类,是所有字节输出流类的超类,输出流接收输出字符并将其发送到某个接收器
如果文件不存在会自动创建,写数据的时候有三种方式
方法 | 说明 |
---|---|
write(int b) | 一次写一个字节 |
write(byte[] b) | 直接写一个字节数组 |
write(byte[] b,int off,int len) | 从off开始写,写的长度为len |
通常情况下,close方法放在finally
中,在close的时候先判断是否为null
public class Test {
public static void main(String[] args) throws IOException {
//给定一个路径,调用系统功能创建文件
FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\javatest\\fos.txt");
//写数据,这里直接写97的时候是a
fos.write("97".getBytes());
//关闭流并释放系统资源
fos.close();
}
}
换行通过写入换行符即可,不同系统换行符不同:
追加写入的实现,在创建FileOutputStream的时候在后面添加true
,这样就是追加了
FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\javatest\\fos.txt",true);
public class Test {
public static void main(String[] args) throws IOException {
//给定一个路径,调用系统功能创建文件
FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\javatest\\fos.txt");
//读数据,一次读一个字节,如果文件达到末尾则返回-1
int read = fis.read();
System.out.println((char)read);
//一次读完所有的内容
int data;
while ((data=fis.read())!=-1) {
System.out.println((char) data);
}
//一次读取字节数组(常用)
byte[] bytes = new byte[1024];
int len;
while ((len=fis.read(bytes))!=-1) {
//这里经常写为fos.write(bytes);即写入输出流
System.out.println(new String(bytes));
}
//关闭流并释放系统资源
fis.close();
}
}
public class Test {
public static void main(String[] args) throws IOException {
//输入流读数据
FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\javatest\\fos.txt");
//输出流写数据
FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\javatest\\fos-backup.txt");
byte[] bytes = new byte[1024];
int len;
while ((len=fis.read(bytes))!=-1) {
fos.write(bytes);
}
//关闭流并释放系统资源
fos.close();
fis.close();
}
}
BufferedOutputStream
:类实现一个缓冲输出流。通过设置这样的输出流,一个应用程序可以写字节到基本的输出流,而不必导致每个字节写入的底层系统的调用。默认大小为8192字节
BufferedInputStream
:当 BufferedInputStream
创建,内部缓冲区创建数组。根据需要从流中读取字节或跳过时,内部缓冲区根据需要从包含的输入流中重新填充,一次许多字节。默认大小为8192字节
因为使用了缓冲流,大大减少了对底层的调用次数,因此可以提高效率。
注:字节缓冲流仅仅提供缓冲区
,而真正的读写数据还是要依靠基本的字节流对象
,因此构造方法中传入字节输入输出流,同时调用的方法与字节流对象相同
public class Test {
public static void main(String[] args) throws IOException {
//输入流读数据
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\IdeaProjects\\javatest\\fos.txt"));
//输出流写数据
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\IdeaProjects\\javatest\\fos-backup.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len=bis.read(bytes))!=-1) {
bos.write(bytes);
}
//关闭流并释放系统资源
bos.close();
bis.close();
}
}
字节流操作汉字或韩文、日文的时候不方便,因为读的时候是一个字节一个字节的读,而汉字在UTR-8
编码下占用3个字节
,在GBK
编码下占用2个字节
,因此直接输出会乱码,于是Java提供了字符流
字符流 = 字节流+编码表
注:汉字在存储的时候,不论使用哪种编码存储,第一个字节都是负数
public class Test {
public static void main(String[] args) throws IOException {
String s = "中国";
//默认是UTF-8
//[-28, -72, -83, -27, -101, -67]
System.out.println(Arrays.toString(s.getBytes()));
//[-28, -72, -83, -27, -101, -67]
System.out.println(Arrays.toString(s.getBytes("UTF-8")));
//[-42, -48, -71, -6]
System.out.println(Arrays.toString(s.getBytes("GBK")));
byte[] bytes = s.getBytes();
//中国
System.out.println(new String(bytes));
//中国
System.out.println(new String(bytes,"UTF-8"));
//涓浗
System.out.println(new String(bytes,"GBK"));
}
}
Reader:字符输入流的抽象类
Writer:字符输出流的抽象类
方法 | 说明 |
---|---|
write(int c) | 一次写一个字符 |
write(char[] c) | 直接写一个字节数组 |
write(char[] c,int off,int len) | 写一个字符数组的一部分,写的长度为len |
write(String str) | 写一个字符串(常用) |
writer(String str,int off,int len) | 写一个字符串的一部分(常用) |
public class Test {
public static void main(String[] args) throws IOException {
//第二个参数可以指定字符集
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\IdeaProjects\\javatest\\a.txt"));
//刷新流,将数据进行操作,即从缓冲区释放
osw.flush();
osw.write("我爱你哦哦哦哦哦");
osw.close();
}
}
注:close首先会先刷新流,然后再关闭资源
public class Test {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\IdeaProjects\\javatest\\a.txt"));
//一次读一个字符
int ch;
while ((ch=isr.read())!=-1) {
System.out.println((char)ch);
}
//使用字符数组
char[] chars = new char[1024];
int len;
while ((len = isr.read(chars))!=-1) {
System.out.print(new String(chars));
}
}
}
注:字符流与字节流读写数据的方式大同小异,只是对象不同
public class Test {
public static void main(String[] args) throws IOException {
//字符输入流
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\IdeaProjects\\javatest\\a.txt"));
//字符输出流
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\IdeaProjects\\javatest\\a-backup.txt"));
//使用字符数组读写文件
char[] chars = new char[1024];
int len;
while ((len = isr.read(chars))!=-1) {
osw.write(chars);
}
osw.close();
isr.close();
}
}
InputStreamReader与OutputStreamWriter操作起来太长了,可以使用子类FileReader
和FileWriter
,但是如果想解决编码问题还是要老老实实的使用父类
public class Test {
public static void main(String[] args) throws IOException {
//字符输入流
FileReader isr = new FileReader("E:\\IdeaProjects\\javatest\\a.txt");
//字符输出流
FileWriter osw = new FileWriter("E:\\IdeaProjects\\javatest\\a-backup.txt");
//使用字符数组
char[] chars = new char[1024];
int len;
while ((len = isr.read(chars))!=-1) {
osw.write(chars);
}
osw.close();
isr.close();
}
}
BufferedReader
与BufferedWriter
BufferedWriter
有特有的方法newLine()
,写入一个换行符,这个函数会调用系统默认的换行符
BufferedReader
有特有的方法readLine()
,读取一行文字,如果达到末尾则返回null
public class Test {
public static void main(String[] args) throws IOException {
//读
BufferedReader reader = new BufferedReader(new FileReader("E:\\IdeaProjects\\javatest\\a.txt"));
//写
BufferedWriter writer = new BufferedWriter(new FileWriter("E:\\IdeaProjects\\javatest\\a-backup.txt"));
//使用字符数组
char[] chars = new char[1024];
int len;
while ((len = reader.read(chars))!=-1) {
writer.write(chars);
}
reader.close();
writer.close();
}
}
public class Test {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("E:\\IdeaProjects\\javatest\\a.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("E:\\IdeaProjects\\javatest\\a-backup.txt"));
//使用字符数组
String line;
while ((line = reader.readLine())!=null) {
writer.write(line);
writer.newLine();
}
reader.close();
writer.close();
}
}
输入简单来说就是读
,是针对程序或者内存来说的,就是从外界读取,可以是从文件中,或者是网络请求中
输出简单来说就是写
,是针对程序或者内存来说的,将已经读取到的或者输入的信息写入外部文件中
字节流:
类名 | 功能 |
---|---|
InputStream | 所有字节输入流的超类 |
OutputStream | 所有字节输出流的超类 |
FileInputStream | 常用的字节输入流 |
FileOutputStream | 常用的字节输出流 |
BufferedInputStream | 字节输入缓冲流 |
BufferedOutputStream | 字节输出缓冲流 |
字符流:
类名 | 功能 |
---|---|
Reader | 所有字符输入流的超类 |
Writer | 所有字符输出流的超类 |
InputStreamReader | 常用的字符输入流 |
OutputStreamWriter | 常用的字符输出流 |
FileReader | 常用的字符输入流——简化版 |
FileWriter | 常用的字符输出流——简化版 |
BufferedReader | 字符输入缓冲流 |
BufferedWriter | 字符输出缓冲流 |
char[] chars = new char[1024];
int len;
while ((len = isr.read(chars))!=-1) {
osw.write(chars);
}
标准输入流:System.in
public class Test {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
//Java封装了一个类实现键盘录入,方便我们调用
//Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("请输入一个字符串:");
String line = reader.readLine();
System.out.println("你输入的是:"+line);
}
}
}
标准输出流:System.out
public class Test {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
out.println("hello,这里换行");
out.print("oh,不换行");
out.print("oh,不换行");
}
}
字节打印流:PrintStream
,继承OutputStream
,只负责输出数据,有自己独有的方法
public class Test {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("E:\\IdeaProjects\\javatest\\ps.txt");
//使用字节输出流的形式写到文件,是a
ps.write(97);
//使用特有的方法写入文件,看到的就是97
ps.println(97);
//ps.print(99);不换行
}
}
字符打印流:PrintWriter
public class Test {
public static void main(String[] args) throws FileNotFoundException {
//如果后面加了true,会自动刷新缓冲区
PrintWriter ps = new PrintWriter("E:\\IdeaProjects\\javatest\\ps.txt");
//字符流的形式写到文件,是a
ps.write(97);
ps.write("\r\n");
ps.flush();
//使用特有的方法写入文件,看到的就是97
ps.println(97);
ps.flush();
}
}
对象序列化流:ObjectOutputStream
,需要对象实现Serializable
接口,但不需要重写任何方法
public class Student implements Serializable {
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\IdeaProjects\\javatest\\a.txt"));
oos.writeObject(new Student(10,"小黑"));
oos.close();
}
}
对象反序列化流:ObjectInputStream
,可以反序列化ObjectOutputStream
序列化之后的对象,需要对象实现Serializable
接口
public class Student implements Serializable {
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\IdeaProjects\\javatest\\a.txt"));
Object object = ois.readObject();
Student student = (Student) object;
System.out.println(student.name+":"+student.age);
}
}
在网络通信协议下,实现网络互联的不同计算机上运行的程序可以进行资源交换,简而言之,用户之间可以发送消息或文件
IP地址是网络中设备的唯一标识,分为IPv4和IPv6两大类,我们使用IPv4进行网络通信,它采用点分十进制表示法,一般长这样192.168.99.1
,127.0.0.1
可以代表本机地址,一般用于测试
命令 | 说明 |
---|---|
ipconfig | 查看本机IP |
ping IP | 检查网络是否连通 |
网络的通信本质上是两个应用程序的通信,每台计算机上有许多程序,每个程序都运行在某一个端口上,端口号可以作为应用程序的唯一标识,普通应用程序要使用1024之后的端口号
在计算机网络中,连接和通信的规则称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换,常见的协议有TCP协议和UDP协议
TCP
协议是面向连接
的通信协议,可以为两台计算机提供可靠无差别
的数据传输,在TCP协议中必须明确客户端与服务器,由客户端向服务器发起连接请求,每次连接要经过三次挥手
UDP
协议是面向无连接
的通信协议,,不管对方有没有收到,反正只负责发出去,由于UDP耗费资源小,通信效率高,通常会用于传输音频、视频、和普通数据以及直播
三次握手以及四次挥手其实就是确保客户端和服务器知道彼此
都具备接收和发送
数据的能力,以及做好了分开的准备,其中,以三次挥手为例。
InetAddress是Java提供的一个类,方便我们对IP地址进行获取和操作
public class MyInetAddress {
public static void main(String[] args) throws UnknownHostException {
//通过主机名或ip地址获取对象
InetAddress address = InetAddress.getByName("192.168.99.1");
//获取主机名
String hostName = address.getHostName();
System.out.println("主机名:"+hostName);
//获取IP地址字符串
String hostAddress = address.getHostAddress();
System.out.println("IP地址:"+hostAddress);
}
}
Java提供了DatagramSocket类基于UDPP协议的Socket
发送数据的步骤:
public class TcpSendDemo {
public static void main(String[] args) throws Exception {
String hostName = "192.168.99.1";
int port = 6666;
//创建socket对象
DatagramSocket socket = new DatagramSocket();
//准备数据,控制台读取
System.out.print("请输入你要发送的内容:");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String text = reader.readLine();
byte[] data = text.getBytes();
//将数据打包
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(hostName),port);
//发送数据
socket.send(packet);
socket.close();
}
}
接收数据的步骤:
public class UdpReceiveDemo {
public static void main(String[] args) throws IOException {
//UDP接收只需要知道端口号就可以了
int port = 6666;
//创建socket对象
DatagramSocket socket = new DatagramSocket(port);
//创建一个数据包接收数据
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
//调用方法接收数据,阻塞等待
socket.receive(packet);
//解析数据包
byte[] data = packet.getData();
int length = packet.getLength();
//这里指定长度是消除后面的空字符,因为有时候不够1024
String dataString = new String(data, 0, length);
System.out.println("收到:"+dataString);
//关闭
socket.close();
}
}
客户端和服务端会建立虚拟链路,是通过这个虚拟链路进行通信的。Java对基于TCP协议的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信
发送数据的步骤:
public class TcpClientDemo {
public static void main(String[] args) throws IOException {
String hostName = "192.168.99.1";
int port = 7777;
//创建客户端对象
Socket socket = new Socket(hostName, port);
//获取输出流,写数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write("你好啊,tcp".getBytes());
//通知服务端,我传输完了
socket.shutdownOutput();
//得到服务器反馈
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
String dataString = new String(bytes, 0, len);
System.out.println("接收到服务器:"+dataString);
//释放资源
socket.close();
}
}
接收数据的步骤:
public class TcpServerDemo {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket serverSocket = new ServerSocket(7777);
//监听socket的连接
Socket accept = serverSocket.accept();
//获取输入流,读数据
InputStream inputStream = accept.getInputStream();
byte[] bytes = new byte[1024];
//一种是读字节,另一种是读字节数组,文本东西少,读一遍就好了
int len = inputStream.read(bytes);
String dataString = new String(bytes, 0, len);
System.out.println("接收客户端到:"+dataString);
//返回信息
OutputStream outputStream = accept.getOutputStream();
outputStream.write("已成功接收!".getBytes());
//关闭资源
serverSocket.close();
accept.close();
}
}
服务器可以通过accept方法获取道连接的socket,这个socket可以获取输入流(客户端传到服务器的信息),然后将输入流写入字符数组。同时这个accept得到的socket对象也可以获取输出流,用同样的套路去给客户端写东西,客户端的socket使用socket.getInputStream()
方法,使用同样的套路去获取服务器的信息
在写的时候可以使用封装了OutputStream的BufferBuilder,直接调用write方法,将字符流转为字节流,功能等同于输出流的write。将getOutputStream
字节输出流转化为了OutputStreamWriter
字符输出流,再采用BufferedWriter
写数据
public class TcpClientDemo {
public static void main(String[] args) throws IOException {
String hostName = "192.168.99.1";
int port = 7777;
//创建客户端对象
Socket socket = new Socket(hostName, port);
//字符输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String text = reader.readLine();
//获取输出流,写数据
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String line;
while ((line=reader.readLine())!=null) {
bufferedWriter.write(line);
bufferedWriter.newLine();
bufferedWriter.flush();
}
//释放资源
socket.close();
}
}
将字节流直接转为字符流
public class TcpServerDemo {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket serverSocket = new ServerSocket(7777);
//监听socket的连接
Socket accept = serverSocket.accept();
//获取输入流,读数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
String line;
while ((line=bufferedReader.readLine())!=null) {
System.out.println("接收客户端到:"+line);
}
serverSocket.close();
accept.close();
}
}
将FileWriter
封装为bufferedWriter
,客户端代码可以用上面的BufferedWriter的应用
的代码
public class TcpServerDemo {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket serverSocket = new ServerSocket(7777);
//监听socket的连接
Socket accept = serverSocket.accept();
//获取输入流,读数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
//把数据写入文本文件
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("a.txt"));
String line;
while ((line=bufferedReader.readLine())!=null) {
bufferedWriter.write(line);
bufferedWriter.newLine();
bufferedWriter.flush();
}
bufferedReader.close();
bufferedWriter.close();
serverSocket.close();
accept.close();
}
}
服务器的程序和上面TCP服务器内容写入文本文件
是一样的
public class TcpClientDemo {
public static void main(String[] args) throws IOException {
String hostName = "192.168.99.1";
int port = 7777;
//创建客户端对象
Socket socket = new Socket(hostName, port);
//字符输入流,读数据
BufferedReader bufferedReader = new BufferedReader(new FileReader("b.txt"));
//获取输出流,写数据
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String line;
while ((line=bufferedReader.readLine())!=null) {
bufferedWriter.write(line);
bufferedWriter.newLine();
bufferedWriter.flush();
}
//释放资源
bufferedReader.close();
bufferedWriter.close();
socket.close();
}
}
服务器一直开着,每来一个客户端就开一个线程,实现思路如下:
创建一个实现了Runnable
接口的类,构造方法中的参数是Socket类型,文件上传操作放在run方法中,服务器使用while死循环,accept得到的对象传入线程,然后调用start方法
泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型
它的本质是参数化类型
,也就是说所操作的数据类型被指定为一个参数
一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型
这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
泛型定义格式:
实参的类型只能是引用数据类型
先看这段代码,这样就报错了!
public class Test {
public static void main(String[] args) {
ArrayList<Object> objectArrayList = new ArrayList<>();
objectArrayList.add("hello");
objectArrayList.add("world");
objectArrayList.add(666);
Iterator<Object> iterator = objectArrayList.iterator();
while (iterator.hasNext()) {
//报错java.lang.ClassCastException:
//java.lang.Integer cannot be cast to java.lang.String
String next = (String) iterator.next();
System.out.println(next);
}
}
}
ArrayList
使用了泛型,我们可以指定具体类型的数据
public class Test {
public static void main(String[] args) {
ArrayList<String> objectArrayList = new ArrayList<>();
objectArrayList.add("hello");
objectArrayList.add("world");
//objectArrayList.add(666);不能写,将运行时期的异常提前到了编译期
Iterator<Object> iterator = objectArrayList.iterator();
while (iterator.hasNext()) {
String next = iterator.next();//不用强制转换
System.out.println(next);
}
}
}
使用泛型的好处:
泛型类的定义格式:T可以写成任意字母,一般使用T、E、K、V
public class Generic<T> {
//
}
泛型类
public class Generic<T> {
private T name;
public void setName(T name) {
this.name = name;
}
public T getName() {
return name;
}
}
泛型测试类
public class GenericTest {
public static void main(String[] args) {
Test<String> test = new Test<>();//指定T为String类型
test.setName("小黑龙");
System.out.println(test.getName());
}
}
先看看这段代码
public class Test {
public void show(String s) {
System.out.println(s);
}
public void show(Integer s) {
System.out.println(s);
}
public void show(Boolean s) {
System.out.println(s);
}
}
使用泛型类进行改善
public class Test<T> {
public void show(T t) {
System.out.println(t);
}
//但是使用的时候每次都需要去规定T的类型
public static void main(String[] args) {
Test<String> stringTest = new Test<>();
stringTest.show("11111");
Test<Integer> integerTest = new Test<>();
integerTest.show(11111);
Test<Boolean> booleanTest = new Test<>();
booleanTest.show(true);
}
}
泛型方法格式:帅到爆炸!!!酷毙了
public class Test {
public <T> void show(T t) {
System.out.println(t);
}
public static void main(String[] args) {
Test test = new Test();
test.show("1111");
test.show(1111);
test.show(true);
}
}
泛型接口:
public interface Generic<T> {
void show(T t);
}
泛型实现类:
public class GenericImpl<T> implements Generic<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
测试类:
public class Test {
public static void main(String[] args) {
GenericImpl<String> generic1 = new GenericImpl<>();
generic1.show("11111");
GenericImpl<Integer> generic2 = new GenericImpl<>();
generic2.show(11111);
}
}
List>可以表示元素类型未知的List,他的元素可以是任何类型
>
是类型通配符
类型通配符的上限: extends 类型>
,泛型的类型(等号后面那部分)只能是该类型或该类型的子类
类型通配符的上限: super 类型>
,泛型的类型只能是该类型或该类型的父类
public class GenericDemo {
public static void main(String[] args) {
//类型通配符>
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
//类型通配符上限 extends 类型>
// List extends Number> list4 = new ArrayList
List<? extends Number> list5 = new ArrayList<Number>();
List<? extends Number> list6 = new ArrayList<Integer>();
//类型通配符下限 super 类型>
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
// List super Number> list9 = new ArrayList();//报错
}
}
Java的api文档:https://www.runoob.com/manual/jdk11api/java.base/java/util/package-summary.html
数组声明
int[] arr2 = new int[5];//推荐这种
int arr[] = new int[5];
数组初始化
int arr[] = new int[]{1, 3, 5, 7, 9};
int[] arr2 = {2, 4, 6, 8, 10};
添加元素以及取出元素
int[] arr = new int[5];
arr[0] = 1;
int a = arr[0];
遍历数组
public static void main(String[] args) {
int arr[] = new int[]{1, 3, 5, 7 ,9};
int[] arr2 = {2, 4, 6, 8, 10};
for (int i = 0; i < arr.length; ++i) {
System.out.print(arr[i] + "\t"); // 1 3 5 7 9
}
for (int x: arr2) {
System.out.print(x + "\t"); // 2 4 6 8 10
}
}
Arrays工具类的常用操作
方法 | 功能 | 备注 |
---|---|---|
fill(int[] a, int val) | 填充数组 | |
fill(int[] a, int fromIndex, int toIndex, int val) | 填充指定索引区间数组 | 左闭右开 |
sort(int[] a) | 数组排序 | |
sort(int[] a, int fromIndex, int toIndex) | 排序指定索引的元素 | |
copyOf(int[] original, int newLength) | 复制数组 | 指定新数组长度 |
copyOfRange(int[] original, int from, int to) | 复制数组 | 指定所复制的原数组的索引 |
Arrays.asList(stringArray).contains(“a”); | 检查数组中是否包含某一个值 | |
Arrays.binarySearch(str) | 定位元素位置 | 前提是有序数组有序数组 |
Arrays.asList.indexOf(str); | 定位元素位置 |
ArrayUtils工具类的常用操作
方法 | 功能 | 备注 |
---|---|---|
ArrayUtils.addAll(intArray, intArray2); | 连接两个数组 | |
ArrayUtils.reverse(intArray); | 数组翻转 | |
ArrayUtils.removeElement(intArray, 3) | 从数组中移除一个元素 | 返回一个删除后的新数组 |
String是不可变类型,被final修饰,即赋值后不能被修改
字符串的格式化
String fs = String.format(
"浮点型变量的值为%f," +
"整型变量的值为%d," +
"字符串变量的值为%s"
, floatVar, intVar, stringVar);
String类常用操作
方法 | 功能 | 备注 |
---|---|---|
charAt(int index) | 返回指定索引处的 char 值。 | |
contains() | 判断是否包含指定的字符 | |
endsWith(String suffix) | 测试此字符串是否以指定的后缀结束。 | |
equals(Object anObject) | 将此字符串与指定的对象比较。 | |
equalsIgnoreCase(String anotherString) | 两个String 比较,不考虑大小写。 | |
indexOf(String str) | 返回子串在此字符串中第一次出现处的索引。 | |
lastIndexOf(int ch) | 返回字符最后一次出现处的索引。 | |
int length() | 返回此字符串的长度。 | |
replace(char oldChar, char newChar) | 替换子串 | 返回一个新的字符串 |
split(String regex) | 字符串分割 | |
substring(int beginIndex,int endIndex) | 字符串切片 | 返回一个新的字符串 |
toLowerCase() | 使用默认语言环境的规则将此 String 中的所有字符都转换为小写。 | |
toUpperCase() | 使用默认语言环境的规则将此 String 中的所有字符都转换为大写。 | |
trim() | 删除字符串左右空元素 | 返回字符串的副本 |
isEmpty() | 判断字符串是否为空。 |
Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。
Java集合框架图
Java集合框架体系图
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。ArrayList 继承了 AbstractList ,并实现了 List 接口。
初始化
import java.util.ArrayList; // 引入 ArrayList 类
ArrayList<E> objectName =new ArrayList<>(); // 初始化
ArrayList类常用操作
方法 | 功能 | 备注 |
---|---|---|
add() | 将元素插入到指定位置的 arraylist 中 |
|
addAll() | 添加集合中的所有元素到 arraylist 中 | |
clear() | 删除 arraylist 中的所有元素 | |
clone() | 复制一份 arraylist | |
contains() | 判断元素是否在 arraylist |
|
get() | 通过索引值获取 arraylist 中的元素 |
|
indexOf() | 返回 arraylist 中元素的索引值 |
|
removeAll() | 删除存在于指定集合中的 arraylist 里的所有元素 | |
remove() | 删除 arraylist 里的单个元素 |
|
size() | 返回 arraylist 里元素数量 | |
isEmpty() | 判断 arraylist 是否为空 | |
subList() | 截取部分 arraylist 的元素 | 左开右闭 |
set(index,newValue) | 更新指定索引的元素 |
|
sort() | 对 arraylist 元素进行排序 |
默认为升序排列 |
toArray() | 将 arraylist 转换为数组 | |
lastIndexOf() | 返回指定元素在 arraylist 中最后一次出现的位置 | |
retainAll() | 保留 arraylist 中在指定集合中也存在的那些元素 | |
containsAll() | 查看 arraylist 是否包含指定集合中的所有元素 | |
forEach() | 遍历 arraylist 中每一个元素并执行特定操作 |
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。
链表可分为单向链表和双向链表。
LinkedList 继承了 AbstractSequentialList 类。
LinkedList 实现了 Queue 接口,可作为队列使用。
LinkedList 实现了 List 接口,可进行列表的相关操作。
LinkedList 实现了 Deque 接口,可作为队列使用。
LinkedList 实现了 Cloneable 接口,可实现克隆。
LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。
LinkedList常用操作
方法 | 描述 | 备注 |
---|---|---|
add(E e) |
末尾添加元素 | 返回布尔值 |
add(int index, E element) | 向指定位置插入元素。 | 返回布尔值 |
addFirst(E e) |
元素添加到头部。 | |
addLast(E e) |
元素添加到尾部。 | |
get(int index) | 返回指定位置的元素。 | |
getFirst() |
返回第一个元素。 | |
getLast() |
返回最后一个元素。 | |
remove() | 删除并返回第一个元素。 | |
removeFirst() |
删除并返回第一个元素。 | |
removeLast() |
删除并返回最后一个元素。 | |
remove(Object o) | 删除某一元素 | 返回布尔值 |
remove(int index) | 删除指定位置的元素。 | |
set(int index, E element) |
设置指定位置的元素。 | |
contains(Object o) |
判断是否含有某一元素。 | |
indexOf(Object o) |
查找指定元素从前往后第一次出现的索引。 | |
lastIndexOf(Object o) | 查找指定元素最后一次出现的索引。 | |
clear() | 清空链表。 | |
size() | 返回链表元素个数。 | |
addAll(Collection c) | 将一个集合的所有元素添加到链表后面 | 返回布尔值 |
addAll(int index, Collection c) | 将一个集合的所有元素添加到链表的指定位置后面 | 返回布尔值 |
ArrayList :是顺序结构,查找和修改速度快,就像电影票
LinkedList :是链表结构,增加和删除速度快,就像佛珠
HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合
HashSet 允许有 null 值
HashSet 是无序的,即不会记录插入的顺序
HashSet 不是线程安全的
HashSet 实现了 Set 接口
HashSet类常用方法
方法 | 描述 |
---|---|
add() | 添加元素 |
remove(value) | 删除元素 |
contoins(value) | 判断是否存在元素 |
size() | 得到元素个数 |
for-each | 迭代 |
HashSet: 无序
LinkedHashSet: 按照插入顺序
TreeSet: 从小到大排序
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。
HashMap类常用操作
方法 | 功能 |
---|---|
put() | 将键/值对添加到 hashMap 中 |
get() | 获取指定 key 对应对 value |
getOrDefault() | 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值 |
remove() | 删除指定键 key 的映射关系 |
containsKey() | 是否存在指定的 key |
keySet | 返回 hashMap 中所有 key 组成的集合视图。 |
values() | 返回 hashMap 中存在的所有 value 值。 |
isEmpty() | 判断 hashMap 是否为空 |
size() | 计算 hashMap 中键/值对的数量 |
forEach() | 对 hashMap 中的每个映射执行指定的操作。 |
containsValue() | 检查 hashMap 中是否存在指定的 value 对应的映射关系。 |
HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
区别1:
区别2:
Collections是一个类,由静态方法组成,是针对集合框架,容器的工具类,就如同Arrays是数组的工具类
方法 | 功能 |
---|---|
reverse() | 反转 |
shuffle() | 随机打乱 |
sort() | 升序排序 |
swap() | 交换 |
synchronizedList() | 线程安全化 |
retuen的结果会有什么影响:
自定义排序的口诀:
升序this在前----降序this在后
方法:类去实现Comparable
接口,重写compareTo
方法
public class Student implements Comparable<Student> {
int uid;
int score;
public Student(int uid, int score) {
this.uid = uid;
this.score = score;
}
@Override
public int compareTo(Student student) {
// 升序this在前----降序this在后
return this.score-student.score;
}
public static void main(String[] args) {
TreeSet<Student> studentHashSet = new TreeSet<>();
studentHashSet.add(new Student(1,11));
studentHashSet.add(new Student(5,6));
studentHashSet.add(new Student(7,13));
studentHashSet.add(new Student(2,17));
studentHashSet.add(new Student(6,9));
for (Student student : studentHashSet) {
System.out.println(student.uid+":"+student.score);
}
}
}
使用comparator接口对ArrayList排序无效
public class Student {
int uid;
int score;
public Student(int uid, int score) {
this.uid = uid;
this.score = score;
}
public static void main(String[] args) {
TreeSet<Student> studentHashSet = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student student1,Student student2) {
//升序前减后,降序后减前
return student1.score-student2.score;
}
});
studentHashSet.add(new Student(1,11));
studentHashSet.add(new Student(5,6));
studentHashSet.add(new Student(7,13));
studentHashSet.add(new Student(2,17));
studentHashSet.add(new Student(6,9));
for (Student student : studentHashSet) {
System.out.println(student.uid+":"+student.score);
}
}
}
Comparable是通过放置的对象去实现Comparable接口的compareTo
方法,升序this在前,降序this在后
Comparator是通过创建的集合去实现匿名内部类,升序前减后,降序后减前
哈希值是JDK根据对象的地址
或者字符串
或者数字
算出来的int类型的数值,可以通过对象的hashCode()
方法获取,默认情况下不同对象的哈希值是不同的,重写hashCode()方法后可以让不同对象拥有相同哈希值
JDBC:Java Database Connectivity–>Java和数据库的连接技术
是Sun公司推出的程序访问数据库的规范(接口),各个数据库厂商实现接口,提供数据驱动jar包,我们可以通过这套接口编程,但是真正执行的是驱动jar包中的类
maven项目可以直接导入依赖:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
普通工程的步骤:
先下载MySQL的jar包点击下载
将jar包复制到项目的lib文件夹下(随便一个就行)
右键lib
文件夹,点击Add as Library
public class JDBCTest {
public static void main(String[] args) throws Exception {
//注册驱动,MySQL5版本之后这句话可以不写了
Class.forName("mysql.mysql.jdbc.Driver");
//获取连接对象
String url = "jdbc:mysql://loaclhost:3306/dbname";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);
//获取执行sql的对象,statement
Statement statement = conn.createStatement();
//定义sql,别忘了加分号
String sql = "update account set password=11111 where username=zhangsan;";
//执行sql
int count = statement.executeUpdate(sql);
//处理结果
System.out.println(count);
//释放资源
statement.close();
conn.close();
}
}
驱动管理对象
如果是本地的loaclhost还是3306端口号,这个url可以简化为jdbc:mysql:///dbname
数据库连接对象
Statement createStatement()
,执行静态sqlStatement prepareStatement(String sql)
,执行动态sqlsetAutoCommit(boolean autoCommit)
,设置参数为false即开启事务commit()
rollback()
执行sql的对象
boolean execute(String sql)
:可以执行任意sql,但是用的不多
int executeUpdate(String sql)
:执行DML(insert、update、delete)语句和DDL(库、表)语句
ResultSet executeQuery(String sql)
:指定DQL(select)语句
结果集对象,封装查询的结果
ResultSet resultSet = statement.executeQuery(sql);
//和迭代器类似
while (resultSet.next()) {
int anInt = resultSet.getInt(1);
String id = resultSet.getString("id");
}
执行sql的对象,有时候使用字符串拼接会因为字段的特殊符号导致sql注入
用?
表示,作为占位符即可
String sql = "update account set password=? where username=?;";
PreparedStatement statement = conn.prepareStatement(sql);
statement.setString(0,"111");//表示第一个占位符
statement.setObject(1,2222);
statement.executeUpdate();
import java.sql.*;
public class JDBCUtils {
static Connection conn = null;
static String url;
static String username;
static String password;
//初始化
static {
//加载类
try {
Class.forName("mysql.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//获取连接对象
try {
conn = DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
return conn;
};
public static void close() {
}
public static void close(ResultSet resultSet, Connection conn, Statement statement) throws Exception {
if (resultSet!=null) {
conn.close();
}
if (conn!=null) {
conn.close();
}
if (statement!=null) {
statement.close();
}
}
}
我这个例子不是很合适了,因为只有一条sql,但是演示了使用事务的三个步骤
Connection conn = DriverManager.getConnection(url,username,password);
PreparedStatement statement = null;
//获取执行sql的对象,statement
try {
conn.setAutoCommit(false);
statement = conn.prepareStatement("update account set password=? where username=?;");
statement.setString(1,"xiaozhng");
statement.setString(1,"zhang");
//提交
conn.commit();
}catch (Exception e) {
//有问题回滚
conn.rollback();
} finally {
//释放资源
statement.close();
conn.close();
}
如果使用循环来做效率会很低
Connection conn = DriverManager.getConnection(url,username,password);
conn.setAutoCommit(false);
PreparedStatement statement = conn.prepareStatement("update account set password=? where username=?;");
for (int i = 0; i < 50000; i++) {
statement.setString(1,"xiaozhng");
statement.setString(1,"zhang");
statement.addBatch();//添加批处理
if (i%1000==0) {
statement.executeBatch();
statement.clearBatch();
}
}
写入:
Connection conn = DriverManager.getConnection(url,username,password);
conn.setAutoCommit(false);
PreparedStatement statement = conn.prepareStatement("update user set photo=? where id=1;");
statement.setBlob(1,new FileInputStream("path"));
statement.executeUpdate();
读取:
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
InputStream inputStream = resultSet.getBinaryStream("photo");
FileOutputStream fos = new FileOutputStream("path");
int len;
byte[] bytes = new byte[1024];
while ((len=inputStream.read(bytes))!=-1) {
fos.write(bytes,0,len);
}
}
是apache的开源框架,简化了JDBC的开发步骤,使得我们可以用更少量的代码实现连接数据库的功能,最厉害的是返回值可以直接指定,单个值,列表,Bean,BeanList…,maven页
ResultSetHandler结果集处理类:
public class DBUtilsTest {
public static void main(String[] args) throws Exception {
Connection connection = JDBCUtils.getConnection();
QueryRunner qr = new QueryRunner();
//查询单个值
Object query = qr.query(connection, "select count(*) from user;", new ScalarHandler<>());
//查询一个Bean,BeanListHandler<>()是Bean列表
Map query1 = qr.query(connection,"select * from admin where id=?", new BeanHandler<>(Map.class), 1);
//修改数据
int result = qr.update(connection,"update from user set password=? where id=?","123");
}
}
用户每次来不会向系统申请,而是去连接池中使用访问对象,用完再还回来,能节约时间还提高利用率
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test_go?characterEncoding=UTF-8&useSSL=false
username: xxx
password: xxxxxx
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 30000
filters: stat
async-init: true
public ResultResponse testDruid() {
String sql = "SELECT mobile FROM user WHERE id = ?";
String mobile = jdbcTemplate.queryForObject(sql, new Object[]{1}, String.class);
return new ResultResponse(201, "hey" + mobile);
}
唉就到这吧,,,等后面直接学Spring Boot整合得了
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,顺序布局器
容器上的组件水平摆放
加入到容器即可,无需单独指定大小和位置
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);
}
}
容器上的组件按照上北 下南 左西 右东 中的顺序摆放
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);
}
}
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,向布局器建议该组件显示的大小.
注 只对部分布局器起作用,比如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需要用到面板,JComboBox这些内容暂时还没学的内容,所以放在后面讲: CardLayout
关键字 | 简介 |
---|---|
JLabel | 标签 |
setIcon | 使用JLabel显示图片 |
JButton | 按钮 |
JCheckBox | 复选框 |
JRadioButton | 单选框 |
ButtonGroup | 按钮组 |
JComboBox | 下拉框 |
JOptionPane | 对话框 |
JTextField | 文本框 |
JPasswordField | 密码框 |
JTextArea | 文本域 |
JProgressBar | 进度条 |
JFileChooser | 文件选择器 |
与python类似,可以绑定事件,代码用到了再去查
JPanel即为基本面板
面板和JFrame一样都是容器,不过面板一般用来充当中间容器,把组件放在面板上,然后再把面板放在窗体上。
一旦移动一个面板,其上面的组件,就会全部统一跟着移动,采用这种方式,便于进行整体界面的设计
JFrame上有一层面板,叫做ContentPane
平时通过f.add()向JFrame增加组件,其实是向JFrame上的 ContentPane加东西
ContentPane这个毛毯其实又放在了其他的毛毯上面
创建一个水平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);
}
}
使用带滚动条的面板有两种方式
在创建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);
}
}
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 布局器 很像TabbedPanel ,在本例里面上面是一个下拉框,下面是一个CardLayerout 的JPanel
这个JPanel里有两个面板,可以通过CardLayerout方便的切换
先建立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);
}
}
swing没有自带的日期控件,需要第三方的类
有三种线程
1、初始化线程
初始化线程用于创建各种容器,组件并显示他们,一旦创建并显示,初始化线程的任务就结束了。
2、事件调度线程
通过事件监听的学习,我们了解到Swing是一个事件驱动的模型,所有和事件相关的操作都放是放在事件调度线程 (Event Dispatch)中进行的。比如点击一个按钮,对应的ActionListener.actionPerformed 方法中的代码,就是在事件调度线程 Event Dispatch Thread中执行的。
3、长耗时任务线程
有时候需要进行一些长时间的操作,比如访问数据库,文件复制,连接网络,统计文件总数等等。 这些操作就不适合放在事件调度线程中进行,因为占用时间久了,会让使用者感觉界面响应很卡顿。 为了保持界面响应的流畅性,所有长耗时任务都应该放在专门的 长耗时任务线程中进行
风格和皮肤可以自由切换的,还可以设置成windows风格,在实例化的部分添加这段代码即可切换成windows风格
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
this.socket = new Socket(config.getServerIp(), config.getServerPort());
this.self = self;
} catch (Exception e) {
e.printStackTrace();
}
注解与反射是所有框架的底层,例如Mybatis、Spring等等,注解(Annotation)是给程序看的,注释(Comment)是给人看的,注解是Java基础中最简单的一章,不用感觉很难。
注解的作用
注解由JDK5.0引入,它不是程序本身,但是可以对程序做出解释,可以被其他程序(例如:编译器)读取,注解不是必须的,但有时候因为程序需要所以要写
注解的格式
以@注解名
的形式存在,也可以添加一些参数值,例如:@SuppressWarnings(value="unchecked")
注解在哪里使用
@Override重写
@Deprecated方法废弃
@SuppressWarnings(value=“all”)镇压警告信息
import java.lang.annotation.*;
//测试元注解
public class Test {
@MyAnnotation
public void test() {
}
}
//定义一个注解,这里METHOD是放在方法上的注解,TYPE可以放在类上
//@Target(value = ElementType.METHOD)
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//注解运行时有效
@Retention(value = RetentionPolicy.RUNTIME)
//将注解生成在javadoc中
@Documented
//子类可以继承父类的注解
@Inherited
@interface MyAnnotation{
}
使用@interface
自定义注解的时候,自动继承了java.lang.annotation.Annotation
接口
import java.lang.annotation.*;
public class Test {
//注解可以显示赋值,如果没有默认值则必须赋值
@MyAnnotation(name = "hello")
public void test() {
}
}
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解的参数:参数类型+参数名+()
String name() default "";
int age() default 0;
int id() default -1;//如果默认值为-1则表示不存在,和indexof异曲同工
}
反射让java变成了动态语言
动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言: Object-C、C#、 JavaScript、PHP、 Python等
静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection ap取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c= Class.forName(java.lang.String");
加载完类之后,在堆內存的方法区中就产生了一个Class类型的对象(一个类只有一个Cass对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。反射会引起类的主动引用,即类似new
了个对象
优点:
可以实现动态创建对象和编译,体现出很大的灵活性
缺点:
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于
直接执行相同的操作,比new出来慢了几十倍。
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("User");//类的包路径
System.out.println(c1);
//一个类在内存中只有一个class对象
//一个类被加载后,类的整个结构都会被封装在Class对象中
Class c2 = Class.forName("User");
Class c3 = Class.forName("User");
Class c4 = Class.forName("User");
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
class User{
String name;
int age;
int id;
}
在Object类中定义了以下方法:
public final Class getClass()
以上的方法返回值的类型是一个C|ass类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称
常用的方法:
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("这个人是"+person.name);
//方式一获得
Class c1 = person.getClass();
System.out.println(c1.hashCode());
//方式二
Class c2 = Class.forName("Student");
System.out.println(c2.hashCode());
//方式三
Class c3 = Student.class;
System.out.println(c3.hashCode());
//方式四,基本内置类型的包装类都有一个TYPE属性
Class c4 = Integer.TYPE;
System.out.println(c4.hashCode());
//获得父类类型
Class c5 = c1.getSuperclass();
String name = c5.getName();
System.out.println(c5);
//以后还可以用ClassLoader
}
}
class Person{
public String name;
}
class Student extends Person{
Student() {
this.name = "学生";
}
}
主动引用
java类的初始化阶段,虚拟机规范严格规定了5种情况必须立即对类进行初始化。
被动引用
除了上述5种场景,其他所有类的方式都不会触发初始化,称为被动引用。例如:
import java.lang.annotation.*;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> c1 = Class.forName("User");
//反射获取注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的value值
MyTable table = c1.getAnnotation(MyTable.class);//首先获取指定注解
System.out.println(table.value());
//获得类指定的注解
Field f = c1.getDeclaredField("name");
MyField annotation = f.getAnnotation(MyField.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@MyTable("db_User")
class User{
@MyField(columnName = "db_user",type = "varchar",length = 12)
public String name;
@MyField(columnName = "db_user",type = "int",length = 10)
public int age;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
String columnName();
String type();
int length();
}