B站课程链接:https://www.bilibili.com/video/BV1fh411y7R8?spm_id_from=333.999.0.0
用于对Java方法的注释,可据此生成参考文档。使用方式如下
/**
* @author 捉妖龙
* @version 1.0
*/
标签 | 说明 | 标签类型 |
---|---|---|
@author 作者 | 作者标识 | 包、类、接口 |
@version 版本号 | 版本号 | 包、类、接口 |
@param 参数名 描述 | 方法的入参名及描述信息,如入参有特别要求,可在此注释 | 构造函数、方法 |
@return 描述 | 对函数返回值的注释 | 方法 |
@deprecated 过期文本 | 标识随着程序版本的提升,当前API已经过期,仅为了保证兼容性依然存在,以此告之开发者不应再用这个API | 包、类、接口、值域、构造函数、方法 |
@throws 异常类名 | 构造函数或方法所会抛出的异常 | 构造函数、 方法 |
@exception 异常类名 | 同@throws | 构造函数、 方法 |
@see 引用 | 查看相关内容,如类、方法、变量等 | 包、类、接口、值域、构造函数、方法 |
@since 描述文本 | API在什么程序的什么版本后开发支持 | 包、类、接口、值域、构造函数、 方法 |
{@link包.类#成员 标签} | 链接到某个特定的成员对应的文档中 | 包、类、接口、值域、构造函数、 方法 |
{@value} | 当对常量进行注释时,如果想将其值包含在文档中,则通过该标签来引用常量的值 | 静态值域 |
相对路径:从当前目录开始定位,形成的一个路径
绝对路径:从顶级目录(磁盘)开始定位,形成的路径
..\相对路径 //使用..的形式到达当前目录的上一级
磁盘:\路径\.... //绝对路径的写法
dir 路径 //查看指定目录下有什么内容,若不填路径则查看当前定位到的目录有什么内容
cd /D 磁盘号: //切换到指定磁盘
cd 路径 //切换到指定路径(可相对可绝对)
cd .. //切换到上一级
cd \ //切换到当前目录根目录
tree 路径 //查看路径下的所有子目录
cls //清屏
exit //退出命令提示符
md 文件夹名 //在当前目录创建文件夹(可一次创建多个,文件夹名空格隔开)
rd 文件夹名 //同理,删除文件夹
echo ok > example.txt //创建example.txt并且其中内容为“ok”
type nul > example.txt //创建example.txt并且其中无内容
del 文件名 //删除文件
copy exa.txt e:\exa2.txt //将当前目录下的exa.txt拷贝到e盘下的exa2.txt(若无则创建)
move exa.txt e:\exa.txt //将当前目录下的exa.txt移动到e盘下的exa.txt(若无则创建)
java -jar 文件名.jar //运行jar文件
netstat -an //查看当前主机网络情况,包括端口监听和网络连接情况
netstat -an | more //分页显示,空格查看下一页,回车查看下一个
netstat -anb //显示各个端口是被哪些程序监听,需要管理员权限才能使用
类型 | 占用存储空间 | 范围 |
---|---|---|
byte[字节] | 1字节 | -128~127 |
short[短整型] | 2字节 | − ( 2 15 ) 到 2 15 − 1 − 32768 到 32767 -(2^{15})到2^{15}-1\\-32768到32767 −(215)到215−1−32768到32767 |
int[整型] | 4字节 | − ( 2 31 ) 到 2 31 − 1 − 2147483648 到 2147482647 -(2^{31})到2^{31}-1\\-2147483648到2147482647 −(231)到231−1−2147483648到2147482647 |
long[长整型] | 8字节 | − ( 2 63 ) 到 2 63 − 1 -(2^{63})到2^{63}-1 −(263)到263−1 |
java中的整型常量默认为int型,声明long型常量需要加l
或L
long a=2L;
byte b=10; //虽然10是int型,但是由于其在byte的范围内,所以这样没有问题
int c=10;
b=c; //这样是错的,因为c划分了空间,byte比较小,无法赋值
类型 | 占用存储空间 |
---|---|
float | 4字节 |
double | 8字节 |
java中的浮点型常量默认为double型,声明float型常量需要加f
或F
float n=6.5F;
double b=.512 //省略小数点前的0,也表示0.512
//科学计数法形式
5.12e2 //5.12*10的2次方
5.12E-2 //5.12*10的-2次方
在浮点数中,乘除法得出来的结果是一个近似数
double a=2.4/6; //结果不为0.4,而是0.39999999999999997
//因此,若要比较两个浮点数经过乘除运算后是否相等,应该采用绝对值求精度的方式
double b=0.4;
if(Math.abs(a-b)<0.01) //在此例中,人为规定当误差小于0.01就认为它们相等
占据空间少的类型可以向上兼容(boolean型不参与自动转换)
byte a=1;
int b=a; //这是对的,因为byte空间少于int
char c=b; //这也是对的,char本质是整型,可以兼容
a=c; //这是错的,因为java规定(byte,short)不能自动转换为char型
//byte,short,char三者之间可以计算,在计算时候首先自动转换为int类型
//当进行数据从大-->小,需要用到强制类型转换
int n=(int) 5.2;
//强转符号只针对最近的操作数有效
int n=(int)5.5*2.1; //先对5.5进行强转再运算,可以用括号的形式对指定数据强转
//基本类型转字符串直接【基本类型值+""】即可
boolean flag=true;
String s=flag+""; //将true转换成字符串并赋值给s
//字符串转基本类型则是调用类方法[parseXXX]
boolean flag2=Boolean.parseBoolean("true"); //将"true"转换成布尔型并赋值
在转换过程中要确保传进来的字符串可以转换成有效数据,否则会抛出异常
//短路与&&:若第一个条件为false,第二个条件不会执行判断,最终结果为false
//逻辑与&:不管第一个条件是什么,后边的都会判断,最终结果也为false
//短路或||:若第一个条件为true,第二个条件不会执行判断,最终结果为true
//逻辑或|:不管第一个条件是什么,后边的都会判断,最终结果也为true
//逻辑异或^:A^B,当A和B取值不同时结果为true,反之为false
//Scanner类实现输入
Scanner scanner = new Scanner(System.in);
//next()通过空格将输入划分成几段字符串,读取时只能读取到光标到空格前的内容
//下次next()时,光标会跳过分隔符和换行符直达下一个字符串的开头,读取到这个新字符串
//如果跳过分隔符之后没有字符串了,那就需要重新读取输入才能获取到
String s=scanner.next(); //获取输入中第一个字符串(空格为分隔符)
int a=scanner.nextInt();//获取输入流中的数字字符串并转换成整型,光标原理与next()类似
String s2=scanner.nextLine(); //此方法也是获取字符串,不过只有遇到回车符才终止,可以读取空格
//当nextLine()放在其他next方法后使用时需要格外注意
//因为其他方法会跳过空格和回车符再开始接收数据,而nextLine()方法不会跳过
//因此很有可能一调用就遇到回车符号,这时往往需要多使用一次nextLine()让其"吃掉"回车符\r
//若要一次接收大量数据,可以考虑使用BufferedReader,效率更高
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
reader.readLine(); //读取一整行数据,可配合字符串的split(" ")将其分割
// ~按位取反(单目) &按位与 |按位或 ^按位异或
// >>算术右移:低位溢出【消失】,符号位不变,用符号位补溢出的高位【符号位后的数字集体右移,多出来的空补符号位数字】
int a=1>>2; //1(转换成二进制)向右位移两位得到结果赋值给a
//<<算术左移:符号位不变,低位补0【与右移同理】
//>>>无符号右移【没有算术左移】:低位溢出,高位补0
IDEA使用
Java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)
访问级别 | 访问控制修饰符 | 本类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | 没有修饰符 | √ | √ | × | × |
私有 | private | √ | × | × | × |
public
可以修饰类(或者不写修饰符)+public和protected
的成员变量和方法子类继承父类(除私有外)所有的属性和方法【若在不同包中则只继承公有和保护属性方法】
父类的私有属性和方法在子类中无法被访问,需要通过父类提供公共的方法去访问
public class A{
private int a=10;
public void sayA(){
System.out.println(this.a);
}
}
class B extends A { //B是A的子类
public void test(){
System.out.println(a); //这样直接输出A.a属性时错误的,因为其是私有属性
sayA(); //这样输出是对的,因为sayA是一个公共方法,方法内实现输出
}
}
子类执行构造函数前会先调用父类构造器,完成父类初始化【默认执行无参构造器,当父类没有无参构造器时,子类必须使用**super
关键字(放在构造器第一行)**指定父类构造函数,否则报错】
this()
意思为调用本类的无参构造器(也可以填参数),其也需要放在构造函数第一行,因此与super()
不能共存
Java的所有类都是Object类的子类
子类最多只能直接继承一个父类【即间接继承是允许的】
super代表父类的引用,用于访问父类的属性、方法、构造器
super.属性名 //访问父类属性,但不能访问父类的private属性
super.方法名(参数) //同理不能访问父类private方法
super() //构造函数,只能放子类构造器第一句
即子类的方法名、参数与父类的一致,叫方法的重写
子类方法的返回类型必须等于父类方法返回类型或者是其子类
public class A{
public Object say(){
}
}
public class B extends A{
public String say(){ //这也是方法的重写,因为String是Object的子类
}
}
子类方法不能缩小父类的访问权限
//如上例子
public class B extends A{
protected String say(){ //这是错误的方法重写,因为protected范围小于public
}
}
父类为壳,子类为内容
一个对象的编译类型和运行类型可以不一致
//父类引用可以指向子类对象
Animal animal=new Dog(); //animal编译类型是Animal,运行类型是Dog
animal=new Cat();//animal运行类型变成了Cat,编译类型依然是Animal
编译类型在定义对象时就确定了,不能改变
Animal animal; //定义了animal的编译类型就是Animal,不能改变
运行类型是可以变化的
Animal animal=new Dog(); //animal可以指向Dog,也可以指向Cat
可以调用父类的所有成员【遵守访问权限】
因为当子类new
出来后,势必会调用父类构造器,即父类被创建了,自然可以调用父类所有成员。但是本质上还是子类,所以需要遵守访问权限
不能调用子类的特有成员
创建对象前需要进行编译,编译的类型是父类,所以父类并不认识子类的方法;而共有的方法父类是认识的,编译可以通过,编译结束后开始进入执行阶段,因此调用共有方法时会最底层开始找齐,即子类
将父类引用强制转换为子类引用
语法:子类类型 引用名=(子类类型) 父类引用
Dog dog=(Dog) animal; //成功使用的前提是,父类引用animal必须先前指向了子类对象
只能强转父类引用,不能强转父类对象
Dog dog=(Dog) new Animal; //这是错误的,不能强转父类对象
要求父类的引用必须指向的是当前目标类型的对象
当向下转型后,可以调用子类类型中所有成员
属性没有重写之说,属性值看编译类型
Animal animal=new Dog();
System.out.println(animal.age); //输出的age是Animal中的age,而不是Dog的age
instanceof
用于判断对象的运行类型是否为XX类型或XX类型的子类型
System.out.println(dog instanceof Animal); //dog是Animal的子类型,输出true
当调用对象方法时,该方法会和该对象的运行类型绑定
当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
class A{
int i=10;
public int getI(){
return i;
}
public int sum(){
return getI()+10;
}
}
class B extends A{
int i=20;
public int getI(){
return i;
}
}
A a=new B();
System.out.println(a.sum());
//首先去B里找,发现没有sum()方法,因此回到父类找,发现有sum(),进入,执行getI()。根据动态绑定机制,方法执行与运行类型有关,因此又跑到子类去找,发现有getI(),又根据动态绑定机制:属性哪里声明哪里调用,因此返回B中的i=20。回到A后+10,最后输出结果为30
即数组的编译类型为父类,执行类型为子类
Person persons=new Person[3];
persons[0]=new Student();
persons[1]=new Teacher();
利用多态数组的形式进行for
循环可以调用每个子类中重载的方法
for(int i=0;i<persons.length;i++){
persons[i].say(); //say()是共有的方法
}
多态数组利用instanceof
可以调用各子类的特有方法
for(int i=0;i<persons.length;i++){
if(persons[i] instanceof Student){
Student stu=(Student) persons[i]; //向下转型
stu.study();
}else{
Teacher tea=(Teacher) persons[i]; //向下转型
stu.teach();
}
}
参数中是一个父类,传进来的参数可以是子类(同样可以配合
instanceof
)
public void test(Person person){
person.say(); //当Teacher调用这个方法时就是Teacher的say();Student就是Student的say()
}
Object是所有类的父类,所以它的方法所有类都会有
1. ==是一个比较运算符
2. equals()是Object的一个方法
只能判断引用类型,即判断地址是否相等【其他类多重写此方法,判断内容是否相同】
String就对equals()进行了重写,源码如下
public boolean equals(Object anObject) {
if (this == anObject) { //是同一个对象,返回true
return true;
}
if (anObject instanceof String) { //是一个String对象
String anotherString = (String)anObject; //向下转型取得数据
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { //逐个比较字符,若有不同则返回false
if (v1[i] != v2[i])
return false;
i++;
}
return true; //比较字符串全部相同则返回true
}
}
return false; //不是字符串返回false
}
hashCode()
如果需要的话,也会重写默认返回:全类目(包名+类名)+@+哈希值16进制【源码如下】
public String toString() {
//getClass().getName()当前类的全类名
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
子类往往重写toString(),用于输出对象属性
class monster{
String name;
int age;
//快捷键Ctrl+Enter
@Override
public String toString() {
return "monster{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
直接输出一个对象时,toString()会被默认调用
对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法做一些释放资源的操作
什么时候被回收?当某个对象没有任何引用时,则jvm
就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁对象,在销毁该对象前,会先调用finalize方法。如果程序员没有重写finalize,那么会调用Object类的finalize(),即默认处理
但是并不是对象没有引用就马上启动垃圾回收机制,而是有自己的算法。如下↓
垃圾回收机制的调用,是由系统来决定,也可以通过System.gc()主动触发垃圾回收机制
要记得打断点!!!!
F7 //跳入方法内
F8 //逐行执行代码
shift+F8 //跳出方法
F9 //执行到下一个断点
Alt+Shift+F7 //强制进入库函数源码
由
static
修饰的变量(静态变量)是所有类成员共享的。在类加载的时候就生成了。
//类变量的定义语法
class A{
访问修饰符 static 数据类型 变量名
public static String name="1"; //推荐
static public int num=1;
}
//访问类变量
类名.类变量名 //推荐
对象名.类变量名
由
static
修饰的方法(静态方法)是所有类成员共享的。在类加载的时候就生成了。
//类方法语法格式
访问修饰符 static 数据返回类型 方法名(){} //static和访问修饰符可换位
public static int sum(){} //推荐
//访问类方法
类名.类方法名 //推荐
对象名.类方法名
super
和this
main方法是虚拟机调用的
Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public【虚拟机和文件不在同个包中】
Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
该方法接收String类型的数组参数,该数组在保存执行java命令时传递给所运行的类的参数
public class A
{
public static void main(String[] args)
{
for(int i=0;i<args.length;i++){
System.out.println("第"+(i+1)+"个参数为:"+args[i]);
}
}
}
因为main()是一个静态方法,因此在方法内可以调用静态成员,非静态成员需要创建类后调用
构造函数的补充,优先于构造函数执行。当构造函数有多个重复部分时,可以用代码块使代码变得整洁
//语法:用{}将内容包含
class A{
int a=10;
{ //可以加上static修饰符,此时是静态代码块
System.out.println("我是代码块!");
}
public A(){
System.out.println("A()被调用");
}
public A(int num){
System.out.println("A(num)被调用");
}
}
final可以修饰类、属性、方法和局部变量。final修饰的属性叫常量
//语法
访问修饰符 final class 类名
访问修饰符 final 返回类型 方法名
访问修饰符 final 参数类型 参数名
final修饰的属性一定要初始化
final修饰普通参数的初始化可以在定义的时初始化,可以在代码块初始化,也可以在构造器中初始化
final修饰静态参数的初始化可以在定义的时初始化,也可以在静态代码块中初始化,不能在构造器中初始化
如果类不是final类,但是有final方法,则该方法虽然不能重写,但是可以被继承
如果一个类已经是final类,就没有必要再将里边的方法修饰成final方法【画蛇添足】
final和static往往搭配使用,效率更高。因为底层编译器做了优化处理,不会导致类加载
父类方法具有不确定性时考虑将父类设置为抽象类
抽象方法就是没有实现的方法,抽象方法必须封装在抽象类中
用abstract
关键字修饰一个类时,这个类就叫抽象类
//语法:访问修饰符 abstract 类名{}
abstract class A{
//访问修饰符 abstract 返回类型 方法名(参数列表)
public abstract void test(); //不能有方法体
}
抽象类不能被实例化【即不能被new】
抽象类不一定要包含abstract方法
abstract只能修饰类和方法,不能修饰属性
抽象类本质还是类,因此类可以有的它都可以有,除了抽象方法不能有方法体
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,出发它自己也声明为abstract类
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时,根据具体情况把这些方法写出来
当子类继承了父类,就自动拥有了父类的功能;如果子类需要扩展功能,可以通过实现接口的方式进行扩展
//语法
interface 接口名{
//属性
//方法
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽选方法;
}
接口中的方法默认是抽象方法,没有方法体且修饰符默认为public
static
成为静态方法,此时需要自行实现default
关键字通过传入接口参数实现统一调用
public class SmallChangeSys {
public static void main(String[] args) {
method.test(new A()); //A实现了接口
}
}
interface myInterface{
public void test1();
public void test2();
}
class A implements myInterface{
@Override
public void test1() {
System.out.println("我是A的方法1");
}
@Override
public void test2() {
System.out.println("我是A的方法2");
}
}
class method {
public static void test(myInterface face) {
face.test1(); //通过接口进行统一调用
face.test2(); //参数是实现接口的哪个类就调用对应方法
}
}
接口是抽象的概念,自然不能被实例化
抽象类可以不实现接口方法
一个类可以同时继承多个接口,用逗号隔开
接口中的属性只能是final的,而且是public static final
,如int a=1
实际上是public static final int a=1
【必须初始化】
接口不能继承类,但是可以继承extends
多个别的接口
接口也支持向上转型
//如下是合法的
public class SmallChangeSys {
public static void main(String[] args) {
MyInterface myInterface = new A();
}
}
class A implements MyInterface { }
interface MyInterface { }
接口有多态传递特性
public class SmallChangeSys {
public static void main(String[] args) {
Face1 face1 = new Example();
Face2 face2 = new Example(); //由于Face1继承了Face2,所以Example也实现了Face2
face2.test();//face2只能调用接口Face2有的方法
}
}
interface Face1 extends Face2 {
public void another();
}
interface Face2 {
public void test();
}
class Example implements Face1{
@Override
public void test() {
System.out.println("god");
}
@Override
public void another() {
System.out.println("so god");
}
}
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类就称为内部类,嵌套类的类称为外部类
内部类的最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
class A{ //外部类
int age=0;
class B{ //内部类
}
}
class C{} //外部其他类
局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
class A{
int age=0;
public void m(){
//局部内部类地位是局部变量,因此不能加访问修饰符,但是可以使用final
//作用域:仅仅在定义它的方法或代码块中有用
final class B{ //本质还是类,因此也可以被继承(如果没final)
//可以直接访问外部类的所有成员
System.out.println("年龄:"+age);
}
//拥有内部类的方法若要调用内部类的非静态方法,可以创建对象调用
B b=new B();
}
}
外部其他类不能访问局部内部类,因为局部内部类的地位是方法里的一个局部变量
如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,可以使用【外部类名.this.成员】去访问
Class A{
int age=0;
public void test(){
class B{
int age=10;
public void test2(){
System.out.println("这样输出age是10"+age);
//A.this指向调用了test()方法的对象
System.out.println("这样输出age是0"+A.this.age);
}
}
}
}
没有显示给出类的名字,其本质上是一个子类,底层实际上是自动分配了类名【格式为:外部类
$
序号】。
public class Small {
public static void main(String[] args) {
new Small().method();
}
public void method(){
//匿名内部类的意思是此类在new出来后只使用一次
//后面即使再new一个同样的实现方法体,本质上也是不同的类
//本质上实现了一个类 class Small$1 implements Face2{}
Face2 face2=new Face2(){ //接口是抽象的,new出来实际是用一个类实现了这个接口并做向上转型
@Override
public void test() { //实现抽象方法
System.out.println("我是匿名内部类");
}
};
System.out.println(face2.getClass()); //此匿名内部类的外部类是Small,所以输出Small$1
}
}
interface Face2 {
public void test();
}
public class SmallChangeSys {
public static void main(String[] args) {
new SmallChangeSys().method();
}
public void method(){
//实际上创建了一个Face2的子类,然后赋值做向上转型
//class Small$1 extends Face2
Face2 face2=new Face2(){
@Override
public void test() {
System.out.println("我重写了原来的方法");
}
};
//子类重写了方法,因此执行的是子类重写的test()
face2.test();
System.out.println(face2.getClass());
}
}
class Face2 {
public void test(){
System.out.println("我是原来的方法");
}
}
定义在外部类成员位置的类。【匿名内部类和局部内部类都是定义在方法中的】
public class Small {
private int age=10;
public static void main(String[] args) {
new Small().method();
}
public void method(){
new Face2().test(); //方法中创建成员内部类并调用方法
}
private class Face2 {
public void test(){
System.out.println("访问外部类的age"+age); //访问外部类的age
}
}
}
public class Small {
public static void main(String[] args) {
//第一种方式
Face face = new Face();
Face.Face2 face2=face.new Face2();//Face2是Face的一个成员,可以.出来
//第二种方式,其实是第一种方式的合并
Face.Face2 face3=new Face().new Face2();
//第三种方式,通过外部类的方法返回
Face.Face2 face4=new Face().get();
}
}
class Face {
public void test(){
System.out.println("访问外部类的age");
}
class Face2{ }
public Face2 get(){
return new Face2();
}
}
用static修饰的成员内部类
class Face {
public void test(){
}
static private class Face2{ } //是合法的
}
public class Small {
public static void main(String[] args) {
//第一种方式,创建类后直接调用静态成员,括号内为内部类构造函数参数
Face.Face2 face2=new Face.Face2();
//第二种方式,通过方法返回一个内部类实例对象
Face.Face2 face3 = Face.get();
}
}
class Face {
static class Face2{
}
public static Face2 get(){
return new Face2();
}
}
对于一些对象,我们确认它有固定的几个结果并且不允许它出现其他情况时,就使用枚举类
//如季节只有春夏秋冬,我们不希望出现其他结果
public class Small {
public static void main(String[] args) {
System.out.println(Season.SPRING.getAttr());
}
}
class Season {
private String attr;
private static final String TIME="每个季节3个月";
public static final Season SPRING=new Season("春天"); //季节类固定死了就四个
public static final Season SUMMER=new Season("夏天"); //只能通过静态调用
public static final Season AUTUMN=new Season("秋天");
public static final Season WINTER=new Season("冬天");
private Season(String season){ //构造器私有,不允许其他外部类创建
attr=season;
};
public String getAttr(){ //外部只能通过这个方法调用参数
return attr;
}
}
setXxx
方法,枚举对象值通常为只读使用关键字enum
替代class
直接使用常量定义代替原先的static final
类;不同的枚举类间有逗号隔开
public static final Season SPRING=new Season("春天");
public static final Season SUMMER=new Season("夏天");
//变换如下↓
SPRING("春天"),SUMMER("夏天");
如果使用enum来实现枚举,要求将定义常量写在类的第一行
如果调用的是无参构造器,则创建常量对象时,可以省略括号
enum Season{
SPRING("春天"),SUMMER("夏天"),Example; //写第一行,Example调用无参构造器,等同于Example()
private String attr;
private Season(String season){ //构造器私有,不允许其他外部类创建
attr=season;
}
private Season(){}
}
使用enum关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类
由于enum
类的父类是Enum
,因此直接输出enum
类时会调用Enum
的toString()
方法,而Enum
中的toString()
方法默认返回当前常量名,因此会输出当前常量名
public class Small {
public static void main(String[] args) {
System.out.println(Season.Boy); //输出BOY,因为它是常量名
}
}
enum Season {
Boy;
}
enum
类不能再继承其他类,因为本质上它已经继承了Enum
类。但是接口可以实现
public class SmallChangeSys {
public static void main(String[] args) {
//输出当前常量枚举类名,与直接输出Season.SPRING效果相同
System.out.println(Season.SPRING.name());
//输出枚举对象的编号,按照定义从左至右顺序,从0开始
System.out.println(Season.SPRING.ordinal());
//返回枚举类的所有枚举对象并放入数组中
Season[] values = Season.values();
//将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
//如下返回的是SPRING这个常量
Season value = Season.valueOf("SPRING");
//前面的编号减去后面编号,如果小于0,说明前面的小;大于0,前面的大
Season.SPRING.compareTo(Season.SUMMER);
}
}
enum Season {
SPRING("春天"),SUMMER("夏天"),AUTUMN("秋天"),WINTER("冬天");
private String attr;
private Season(String season){ //构造器私有,不允许其他外部类创建
attr=season;
};
public String getAttr(){
return attr;
}
}
注解(Annotation)也被称为元数据,用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息
和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息
元注解就是修饰注解的注解,主要有以下几个【主要用于阅读源码,了解即可】
@Target //指定该注解在哪些地方使用,取值含义可通过源码得到
@interface //表示当前类是一个注解
@Retention //指定该注解可以保留多长时间,其包含一个RetentionPolicy类型的成员变量,使用@Retention时必须为该value成员变量指定值
RetentionPolicy.SOURCE //编译器使用后,自己丢弃这种策略的注释
RetentionPolicy.CLASS //编译器将把注解记录在class文件中,当运行Java程序时,JVM不会保留注释。这是默认值
RetentionPolicy.RUNTIME //编译器将把注解记录在class文件中,当运行Java程序时,JVM会保留注释,程序可以通过反射获取该注解
@Documented //修饰的注解类将被javadoc工具提取成文档,即生成文档时可以看到该注解
@Inherited //被修饰的注解具有继承性,即子类会有父类的注解
限定某个方法是重写的父类方法,该注解只能用于方法
class A{
public void test(){}
}
class B extends A{
@Override //表示这是一个重写的方法
public void test() { }
@Override //此处会报错,因为注释校验后发现another()实际上不是重写的方法
public void another() { }
}
用于表示某个程序元素(类、方法等)已过时【但是仍然可以使用】,使用此注释的属性或者方法被调用时会有一道横线
可以修饰方法、类、字段、包、参数等等,起到过渡作用【告诉别人这个方法已经过时了,去找找新方法】
public class SmallChangeSys {
public static void main(String[] args) {
A example = new A();
int n=example.age;
example.test();
}
}
class A{
@Deprecated
int age=0;
@Deprecated
public void test(){}
}
对于一些语句,编译器会发出警告【不影响运行】
若不想看到警告,则可以使用注释@SuppressWarning
抑制编译器警告
public class SmallChangeSys {
@SuppressWarnings({"all"}) //all的意思是一致所有警告,若有多个参数则在大括号内用逗号隔开
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
System.out.println(list.get(0));
}
}
@SuppressWarnings字符串关键字明细
@SuppressWarning
的抑制范围视其放置范围而定,通常放置在具体的方法和类上
Java中,将程序执行中发生的不正常情况称为"异常"。【语法错误和逻辑错误不是异常】
异常事件可分为两大类
Error(错误):Java虚拟机无法解决的严重问题,如JVM系统内部错误、资源耗尽等严重情况【如栈溢出和out of memory】,Error是严重错误,程序会崩溃
Exception:其他因编程错误或偶然外在因素导致的一般性问题,可以使用针对性代码进行处理。如空指针访问、试图读取不存在的文件、网络连接中断等等。
Exception
分为两大类:运行时异常和编译时异常【Exception下不在运行时异常就是编译时异常】
NullPointException //空指针异常
ArithmeticException //计算时异常,如除0
ArrayIndexOutOfBoundsException //数据越界
ClassCastException //将一个类强转成另一个毫不相干的类抛出异常
NumberFormatException //当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。使用异常可以确保输入时满足条件的数据
程序员自定义处理异常
try{
可能有异常的代码
}catch(Exception e){
//没有发生异常时,catch里的语句不会执行
//捕获到异常时,将异常封装成Exception对象e传递过来
//得到异常对象后自行处理
}finally{
//不管try代码块是否有异常发生,始终要执行finally
}
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是
JVM
若不采用try-catch,默认采用throws【只能处理运行异常,处理编译时异常要显示添加throws】
public static void main(String[] args) throws NullPointException,ArithmeticException{
//可以抛出多个异常,用逗号隔开
}
throws
过程中,如果有方法try-catch
,就相当于处理异常,可不必再往上throws
Exception
或RuntimeException
RunTimeException)
public class SmallChangeSys {
public static void main(String[] args) {
int age=10;
if(age!=20){
//抛出自定义异常,由于这是主方法,因此往上是JVM,JVM会打印异常信息
throw new AgeException("年龄不是20!");
}
System.out.println("年龄是20!");
}
}
class AgeException extends RuntimeException{
public AgeException(String message) {
//发生异常时,将传入的信息进行存储
super(message);
}
}
意义 | 位置 | 后面跟的东西 | |
---|---|---|---|
throws | 异常处理的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
针对八种基本数据类型相应的引用类型(类对象),有了类的特点,就可以调用类中的方法
Boolean
与Character
,其他包装类父类都是Number
基本数据类型 | 包装类 |
---|---|
boolean | Boolean |
char | Character |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
public class SmallChangeSys {
public static void main(String[] args) {
int n=6;
//手动装箱,即将数据手动放入构造函数中创建对象
Integer integer = new Integer(n);
//手动拆箱,即通过调用方法将存入对象的数字取出
int i = integer.intValue();
//自动装箱,底层自动调用Integer.valueOf(n)返回一个Integer对象
Integer example=n;
//自动拆箱,底层帮助自动调用了intValue()
int m=example;
}
}
public class SmallChangeSys {
public static void main(String[] args) {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j); //直接new为两个不同对象,false
Integer m=1;//自动装箱,实际上调用Integer.valueOf()方法
Integer n=1;//valueOf()方法显示:如果是-128~127之间会直接返回
System.out.println(m==n);//由于是直接返回现有数据且数值相同,返回true
Integer x=128;
Integer y=128;
System.out.println(x==y);//大于127,创建新对象
}
String对象用于保存字符串,也就是一组字符序列。字符串字符使用Unicode字符编码,一个字符占两字节
private final char value[]; //String对象实际是用这个数组存放字符串内容
//因为这是一个final类型,所以一旦赋值就不能改变,即字符串赋值后不可修改【地址】
以下语句创建了几个对象?
String s1="hello";
s1="haha";
创建了两个对象。起初常量池没有对象,因此创建一个"hello",又由于常量地址指向的内容是不可变的。所以当需要s1指向"haha"这个常量时,原先的内容并不会被抹除,而是会在常量池中寻找,发现没有找到,所以就在常量池创建"haha"并令s1指向。
创建了几个对象?
String a="hello"+"abc";
只创建1个对象,因为编译器会对其进行优化,知道我们是想组合,因此不会为"hello"与"abc"单独划分空间,而是直接划一个"helloabc"的空间
创建了几个对象?
String a="hello";
String b="abc";
String c=a+b;
a+b在底层实际是用Stringbuilder类来对a与b的内容进行append,最后调用toString()方法将其转换成String形式。所以c指向了堆的value,value指向了常量池中的"helloabc"。加之a与b,总共创建了3个对象。
equals //判断内容是否相等,区分大小写
equalsIgnoreCase //忽略大小写判断内容是否相等
indexOf //获取字符在字符串中第一次出现的索引,所以从0开始,找不到返回-1
lastIndexOf //获取字符在字符串中最后一次出现的索引,所以从0开始,找不到返回-1
trim //去除前后空格
chatAt //获取某索引处的字符
toUpperCase //转换成大写
toLowerCase //转换成小写
concat //将指定字符串连接到末尾
compareTo //按照字典顺序排序,负数说明当前字符串在字典前面
toCharArray //将字符串变字符数组
substring //截取字符串,包前不包后,填入参数为索引
//format实现字符串输出格式化
public static void main(String[] args) {
double money=12.356;
String name="卓跃龙";
String template=String.format("姓名%s,工资%.2f",name,money);
System.out.println(template);
//输出结果为:姓名卓跃龙,工资12.36
}
String保存的是字符串常量。里面的值不能更改,每次String类的更新实际上就是更改地址,效率低**【根本原因是存放存放字符串的
char[] value
是final
修饰的】**StringBuffer保存的是字符串变量,里面的值可以更改,每次
StringBuffer
的更新实际上可以更新内容,不用每次更新地址,效率高【根本原因是存放存放字符串的char[] value
没有用final
修饰的】
//String和StringBuffer构造函数可以相互填入作为参数
append //追加内容,返回的还是StringBuffer。若str=null,append后的结果为null字符串
delete //删除指定范围的内容,同样是索引,包前不包后
replace //用指定内容替换索引范围内的内容,索引范围包前不包后
indexOf //查找子串第一次出现的索引
insert //指定位置插入字符串,原来的内容若有需要自动往后移
此类提供一个与StringBuffer兼容的API,但不保证同步(即不是线程安全的)。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,优先采用该类
abs //绝对值
pow //求幂
ceil //向上取整
floor //向下取整
round //四舍五入
sqrt //求开方
random //求随机数,大于等于0.0,小于1.0
数组的封装类,没有final修饰,可以继承重写方法
toString //就数组元素变为字符串
sort //排序方式,可以传入匿名类Comparator自定义排序方式
binarySearch //二分搜索法进行查找(需要排好序),返回目标值索引,找不到返回-1
copyOf //数组元素的复制,返回复制好的数组;copy(数组,长度)是放回根据长度返回复制的数字,若长度大于原数组则填入null
fill //数组元素的填充
equals //比较两个数组元素的内容是否完全一致
asList //将一组值转换成list
exit(0) //退出程序
currentTimeMillis //获得当前时间距离1970-1-1的毫秒
gc //运行垃圾回收机制
保存比较大的数
//整数
BigInteger b = new BigInteger("1111111111111111111111111");
BigInteger b2 = new BigInteger("222222222221111111");
BigInteger add = b.add(b2); //加减后是一个BigInteger对象
System.out.println(add); //加法
System.out.println(b2.subtract(b)); //b2-b
System.out.println(b2.multiply(b)); //b2*b
System.out.println(b2.divide(b)); //b2/b
//浮点数乘除的精确值则需要借助BigDecimal,如下
MathContext math =new MathContext(5, RoundingMode.HALF_UP);//结果最多保留5位,四舍五入模式
BigDecimal decimal = new BigDecimal("2.4"); //值为2.4
decimal=decimal.divide(new BigDecimal("6"),math); //2.4除6,结果格式采用上边math的规定
BigDecimal a = new BigDecimal("12.123");
BigDecimal c = new BigDecimal("12.23");
System.out.println(a.divide(c,BigDecimal.ROUND_CEILING)); //结果保留分子的精度
Date date = new Date(); //获取当前日期
//年有四位,所以要4个y,月要输出两位,所以有2个MM
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = simpleDateFormat.format(date); //将格式化应用在日期上并返回格式后的字符串
System.out.println(format);
String s="2000年6月18日 10:20:30 星期三";
Date parse = simpleDateFormat.parse(s); //按照规定格式将字符串中的时间变回Date
System.out.println(parse);
构造器私有,因此不能new。获得对象使用getInstance方法
Calendar instance = Calendar.getInstance(); //获得当前日期对象
System.out.println(instance.get(Calendar.YEAR)+"年");
//月份+1才是真实的数据,因为这里是从0开始的
System.out.println(instance.get(Calendar.MONTH)+1+"月");
System.out.println(instance.get(Calendar.DAY_OF_MONTH)+"日");
System.out.println(instance.get(Calendar.HOUR)+"时"); //12进制,HOUR_OF_DAY是24进制
System.out.println(instance.get(Calendar.MINUTE)+"分");
System.out.println(instance.get(Calendar.SECOND)+"秒");
//没有提供格式化工具,需要自己根据需要进行组合
LocalDateTime 封装当前日期时间;LocalDate 封装当前日期;LocalTime 封装当前时间
//3个类都可以用now()获得当前时间
LocalDateTime now = LocalDateTime.now(); //获取当前时间和日期
System.out.println("年"+now.getYear());
//返回当前月份的英语
System.out.println("月"+now.getMonth());
//当前月份的数字
System.out.println("月"+now.getMonthValue());
//根据需要调用方法即可
//格式化形式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm:ss");
String format = dateTimeFormatter.format(now); //将时间放入格式化,返回格式化后的对象
System.out.println(format);
//200天后的日期
LocalDateTime localDateTime = now.plusDays(200);
//200天前的日期
System.out.println(now.minusDays(200));
//还有很多方法根据需要调用
时间戳。同样是私有的,需要调用方法来获得对象
Instant now = Instant.now();
//可以将Instant转换成Date对象
Date date = Date.from(now);
//Date也可以转换成Instant
Instant instant = date.toInstant();
动态存放任意多个对象,比较方便
单个元素构成的集合
多了个s,是工具类;没有s的是接口
//仅List可用
reverse //反转lsit中的元素
shuffle //对链表元素随机排序
sort //按照自然顺序对链表进行排序,也可以实现Compartor接口自定义排序
swap(list,i,j) //将list中的i处元素和j处元素进行交换
copy(list1,list2) //list2拷贝到list1,要确保list1有足够空间
replaceAll(list,A,B) //将list中的A替换成B
//实现了Collection接口的都可用
max/min //根据自然排序返回最大元素,也可以实现Compartor接口自定义排序
frequency //指定元素出现次数
Collection本身是一个接口,不能被实例化,但是其中有许多方法。
//参数均为对象,传入时会自动装箱
add //添加单个元素
remove //删除指定元素(可以填索引也可以填具体元素)
contains //查找元素是否存在
size //获取元素个数
isEmpty //判断是否为空
clear //清空
addAll //添加多个元素,参数为实现Collection的对象
containsAll //查找多个元素是否都存在,参数为实现Collection的对象
removeAll //删除多个元素,参数为实现Collection的对象
用于遍历Collection中的元素
执行原理
Iterator iterator=coll.iterator();//得到迭代器
//hasNext() //判断是否还有下一个元素
while(iterator.hasNext()){//若没有下一个元素返回false
//next() 1.指针下移 2.将下移后集合位置上元素返回
//返回的元素是Object
System.out.println(iterator.next());
}
//调用next()方法前必须调用hasNext(),否则当下一条记录无效时会抛出异常
//当退出while循环后,迭代光标会停在最后,如果要重新迭代需要再获得一次迭代器重置
add //默认添加到最后,可以指定位置添加,原本的元素会被往后挤
addAll //从开始索引位置起将集合内的所有元素加进来
get //获取指定位置的元素
indexOf //找到某个元素在链表首次出现中的位置
lastIndexOf //找到某个元素在链表最后一次出现的位置
remove //移除指定元素,可填索引也可具体元素
set //设置指定位置的元素
subList //返回开始索引到结束索引之间集合,包前不包后
ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData; //transient表示该属性不会被序列化
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,扩容elementData为10,如需再次扩容,则扩容elementData为1.5倍
如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
每次add的时候都会检查空间是否充裕,充裕就直接添加数据
synchronized
底层实现了双向链表和双端队列
getFirst/Last //获得首部或者尾部元素
get //获得指定索引元素
offerFirst/Last //添加元素
offer //默认添加到尾部
peekFirst/Last //检索头部或者尾部元素
peek //检索头部
pollFirst/Last //检索并删除头部或者尾部元素
poll //检索并删除头部元素
remove //删除指定位置元素,默认删除头部
first
和last
,分别指向首节点和尾结点prev(指向前一个)
、next(指向后一个)
、item(属性)
三个属性增删较多使用LinkedList;改查较多使用ArrayList
在一个项目中,根据业务灵活选择,可能一个板块用ArrayList,一个板块用LinkedList
无序且无索引,不允许重复元素,最多包含一个null
HashSet底层是HashMap
添加一个元素时,先得到hash值,通过算法转换成数组table
索引值
找到存储数据表table
,看这个索引位置是否已经存放的有元素。如果没有,直接加入;如果有,先判断当前元素hashCode()是否和数组第一个元素相同,不同则不操作;相同则调用存储类型的==equals
方法【根据程序员重写进行比较】==比较,如果相同就放弃添加,若不相同则添加到最后
//正常情况下,如果我们希望集合按照我们的意思存放相同元素,需要重新hashCode()和equals方法
//可用快捷键Ctrl+Enter生成
class Person{
int age;
String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
//当Person的名字和年龄一样就认为是equals的
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
//名字和年龄一样的Person,hashCode相同
@Override
public int hashCode() {
return Objects.hash(age, name);
}
}
如果一条链表的元素个数达到8且数组长度达到64,就会将整个结构进行红黑树化
每添加一个元素【不管加到链表还是数组】进入数组链表就会检查是否需要扩容【初始大小16】,当元素个数达到数组长度的0.75倍时就会进行扩容(扩大2倍)
LinkedHashSet中维护了一个hash表和双向链表**(LinkedHashSet有head和tail)**
每一个节点有before和after属性,这样可以形成双向链表
添加一个元素时,先求hash值,再求索引,确定该元素在hashtable的位置,然后将添加的元素加入到双向链表(如果已经存在则不添加)
next
指针依然存在,不过其指向的是当前数组链表中的后一个元素
tail.next=newElement;
newElement.pre=tail;
tail=newElement;
这样我们遍历LinkedHashSet也能确保插入顺序和遍历顺序一致
自定义排序的集合,本质上是
TreeMap
Comparator
匿名内部类中的compare
方法,根据返回值决定是将当前元素放在left
【返回值小于0】还是right
【返回值大于0】。当返回值等于0时,Key值不发生操作,但是Value值会被替换【对于集合来说不进行任何操作,因为集合存放的数据都在Key】存放的是Key-Value键值对
本质上是table
维护的一个数组链表,每一个元素是一个Node
transient Node<K,V>[] table
//节选
static class Node<K,V> implements Map.Entry<K,V> { //实现了Map.Entry接口
final int hash;
final K key;
V value;
Node<K,V> next;
}
为了方便程序员遍历,底层会创建EntrySetmap.entrySet()
获得这个集合,里面有包含有Node的key和value
map.entrySet() //存放key和value的集合
map.keySet(); //存放key的集合
map.values(); //存放values的集合(Collection)
Node实现了接口Entry
EntrygetKey()与getValue()
,其指向table
中的key
和value
值
put //添加映射关系
remove //根据key删除映射
get //根据key获取value
size //获得映射个数
isEmpty //判断map是否为空
clear //清楚map内容
containsKey/Value //是否存在key/Value值
得到keySet,利用增强for循环遍历keySet,通过键值得到value
通过得到keySet的itertor获得keySet,通过键值得到value
得到Values,遍历得到所有的Value(同样可以使用迭代器)
得到entrySet,利用Map.Entry将其中的元素进行接收,并通过getKey()和getValue()
方法来获得值
Map<String,String> map = new HashMap<String,String>();
//利用Map.Entry将其中的元素(Node)进行接收
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
}
扩容树化原理与HashSet一致,因为HashSet底层就是HashMap
使用方法与HashMap基本一致,不过HashTable是线程安全的且扩容机制是按照(2倍+1)进行扩容。效率较HashMap较低
HashTable的key和value都不能为null
专门用于读写配置文件的集合类
配置文件格式:键=值【键值对不需要有空格,值不需要用引号引起来,默认类型是String】
load //加载配置文件的键值对到Properties对象
list //将数据显示到指定设备
getProperty(key) //根据键获取值
setProperty(key,value) //设置键值对到Properties对象,没有则创建
//创建新的Properties
store //将Properties中的键值对存储到配置文件,如果有中文会存储为Unicode码
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.setProperty("user","tom"); //设置一个键值对
//将properties的东西输出到指定位置;null部分为注释信息,一般不写
properties.store(new FileOutputStream("src\\mysql2.properties"),null);
System.out.println("成功");
Properties properties2 = new Properties();
//将目标地址的文件加载下来
properties2.load(new FileInputStream("src\\mysql2.properties"));
String user = properties2.getProperty("user");//根据Key找Value
System.out.println("我是被加载的:"+user);
properties2.setProperty("pwd","123"); //新建立一个键值对
//将properties2的数据写到指定地址
properties2.store(new FileOutputStream("src\\mysql2.properties"),null);
}
自定义排序(Key)的K-V映射。原理部分与TreeSet一致【TreeSet本质是TreeMap 】
可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值类型,或者是参数类型
能够表示数据类型的数据类型【如泛型E可以代表Integer,也可以代表String】
类和接口都可以加泛型,多个泛型之间用逗号隔开
//创建对象的时候动态传入类型,所有T的部分都会变成动态传入的类型
class Person<T>{
T s;
public Person(T s) {
this.s = s;
}
public T f(){
return s;
}
}
//T具体的数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
Person<String> person = new Person<String>("j"); //这样是正确的
Person<Integer> person = new Person<Integer>("j");//构造函数要求传入的是T类型,编译类型确定了T类型是Integer,因此参数不是Integer就会报错
泛型要求指向的数据类型是引用类型,不能是基本数据类型
给泛型指定具体类型后,参数是泛型的地方可以传入该类型或者其子类型
//假设A是B的父类
Pig<A> pig=new Pig<A>(new A()); //这是对的,T为A类
Pig<A> pig=new Pig<A>(new B()); //这也是对的,T为A的子类B
class Pig<T> {
public Pig(T t) {}
}
编译器会进行自动类型推断
Person<String> person = new Person<>();//当省略执行类型的泛型时,编译器默认其泛型为编译类型的泛型,即String
class 类名<T,R,...>{ //表示可以多个泛型
成员
}
interface 接口名<T,R,...>{
//接口中属性默认都是final static ,因此都不能用泛型
}
修饰符 <T,R,...>返回类型 方法名(参数){
//返回类似可以使用泛型,参数中也可以使用泛型
}
泛型方法可以定义在普通类中,也可以定义在泛型类中
泛型方法可以使用类定义的泛型
当泛型方法被调用时,类型会确定
public class Generic {
public static void main(String[] args) {
Person person = new Person();
person.f("你好"); //传入字符串,T就被认为是String
person.f(1); //传入整型,T就被认为是Integer
}
}
class Person {
public <T> T f(T n) {
return n;
}
}
public void eat(E e)
修饰符后没有
,因此该方法不是泛型方法,而是使用了泛型
泛型不具备继承性
List<Object> list=new ArrayList<String>(); //这样是错误的
//泛型的其他形式
<?> :支持任意泛型类型
<? extends A>:支持A类及A类的子类,规定了泛型上限
<? super A>:支持A类以及A类父类,不限于直接父类,规定了泛型下限
Java的图形化设计以JFrame类为框架【可以理解为画板】,Jpanel类为面板【理解为画纸】。
X-Y坐标轴以左上角为原点,右边是X轴正向;Y轴下方是正向
JPanel上的画图由paint(Graphics g)
方法实现,其中**g可以理解为画笔**。Graphics是一个接口,在JPanel中实现了初始化,我们重写的时候收到的实际是一个上转型对象,所以可以直接调用方法使用。
以下情况会自动调用paint()方法
repaint
函数【刷新组件外观方法】被调用使用画笔进行画图时经常需要标明坐标位置以及图像的高宽,高宽是在坐标位置基础上计算的,坐标位置如箭头所示
setColor(颜色) //设置画笔颜色
drawLine(int x1, int y1, int x2, int y2) //(x1,y2)到(x2,y2)间连线【直线】
drawRect(int x, int y, int width, int height) //画矩形,(x,y)确定开始位置
drawOval(int x, int y, int width, int height) //画椭圆,高宽相等时是圆
fillOval(int x, int y, int width, int height) //根据画笔颜色填充椭圆
fillRect(int x, int y, int width, int height) //根据画笔颜色填充矩形
setFont(new Font("楷书",Font.BOLD,50)); //设置画笔字体、粗细、大小
drawString("皇后",int x,int y); //用当前画笔写字,(x,y)是字左下角位置
//添加图片,图片需要放在项目下
Image image = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/根目录下路径"));
drawImage(image,int x,int y,int width,int height,this);//在当前对象处显示图片
进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间;启动迅雷时,又启动一个进程,操作系统为迅雷分配新的内存空间
进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程。有它自身的产生、存在和消亡过程
线程是由进程创建的,是进程的一个实体
一个进程可以拥有多个线程
①继承Thread类【本质上也是实现Runnable接口】 ②实现
Runnable
接口
class A extends Thread{
public void run(){}
}
new A().start(); //创建线程并调用run方法
class B implements Runnable{
public void run(){}
}
new Thread(b).start(); //动态绑定run方法,创建线程并调用run
如何让主线程控制子线程中止?
可以通过修改子线程的变量。如子线程有一变量
private boolean loop
,当其为true
时无限循环;主线程启动子线程后,休眠一段时间,而后调用setXXX方法修改loop,使其成为false
,由此达到中止子线程的目的
setName //设置线程名称
getName //返回该线程名称
getState //获得当前状态
start //使线程开始执行,Java虚拟机底层调用该线程的start0方法
run //调用线程run方法
setPriority //更改线程优先级
getPriority //获取线程优先级
sleep //在指定毫秒数内让当前正在执行的线程休眠
interrupt //中断线程,没有真正结束线程,一般用于唤醒线程【即用于中断正在休眠线程】
public class Interrupt {
public static void main(String[] args) {
A a = new A();
a.start();
try {
System.out.println("3秒钟后我就唤醒线程,不给睡50秒");
Thread.sleep(3000);
a.interrupt(); //线程中断命令
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class A extends Thread{
public void run(){
while (true){
try {
System.out.println("我要睡50秒!");
Thread.sleep(50000);
} catch (InterruptedException e) {
System.out.println("我被中断,这次睡眠结束,但我会回来的!");
}
}
}
}
yield //线程礼让,但是礼让的时间不确定,因此不一定成功【当CPU资源足够的时不需要礼让,因此就会失败】
join //线程插队。插队一旦成功,则肯定先执行完插入线程的所有任务
守护线程是为工作线程服务的,当所有的用户线程结束,守护线程自动结束【如垃圾回收机制,当所有线程都结束时才结束,期间一直发挥作用】
setDaemon(true) //将一个线程设置为守护线程
当我们启动程序的时候,进程就被创建了,同时会开启main
线程,线程中可以创建其他线程【套娃】……多个线程之间共享内存时,轮流享受CPU服务。当进程创建的所有线程都消亡后,进程也消亡。
既然最后都是实现run()方法,为什么继承Thread的类要通过start()来调用而不直接调用run()呢?
当直接调用run()方法时,本质上就是调用了一个成员方法,与调用一个普通类的成员方法并无二异,不能达到创建新线程的效果,且由于调用了方法,因此会等到run()执行完才往下走。
start()方法底层通过start0()方法创建了一个新线程,再通过这个新线程调用run()方法。即对于原线程来说,当新线程创建成功之后,start语句就完成了,因此会继续往下执行。而新创建的线程则在资源分配过来后执行run()方法
为什么Thread构造器传入Runable接口实现类后,调用Thread的start就可以动态绑定到此类的run方法?
//Thread调用start(),实际上是调用start0()方法创建线程
//start0()调用run方法,如果此时target有值,则在线程中可以调用
private Runnable target; //Thread的一个私有属性
public void run() {
//Thread中的run方法,当构造函数传入target时,target不为空
if (target != null) {
target.run();
}
}
//我们继承Thread时往往重写run方法,因此上面的run会被我们自定义的run重载
为什么需要同步?
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
被此关键字修饰后,多个线程访问同一方法时必须等其中一个线程操作完,另一个线程才能进入操作
//同步代码块
//对象上锁,直至代码块执行完毕才解锁
synchronized (对象){ //对象没上锁才能操作同步代码
//需要被同步的代码
}
//synchronized还可以放在方法声明中,表示整个方法为同步方法
访问修饰符 synchronized void 方法名(参数){
//需要被同步的代码
}
多个线程都占用了对方的锁资源,但不肯相让,导致死锁
文件在程序中是以流的形式来操作的
用于Java与文件进行连通的类
File file = new File("f:\\text1.txt"); //直接写出所有路径
File file1 = new File("f:\\"); //将父路径放在File中
File file2 = new File(file1, "text2.txt"); //根据File保存的父路径创建文件
File file3 = new File("f:\\", "text3.txt");//指定父路径与子路径
try {
file.createNewFile(); //创建方法
file2.createNewFile();
file3.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
e.printStackTrace();
}
getName() //获取文件名
getAbosolutePath() //获取绝对路径
length //获取文件大小(字节)
exists //文件是否存在
isFile //是不是一个文件
isDirectory //是不是一个目录
mkdir //创建一级目录
mkdirs //创建多级目录
delete //删除空目录或文件
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
FileInputStream可以绑定一个File对象【或文件路径名称】,成为这个文件的输入流,通过read()方法将文件读取到内存中。
最后要调用close()方法关闭流
File file = new File("g:\\text1.txt");
FileInputStream fileInputStream=null;
try {
fileInputStream = new FileInputStream(file);//绑定输入流
int read;
while ((read=fileInputStream.read())!=-1){
//说明有字符
System.out.println((char) read);
}
} catch (Exception e) {
e.printStackTrace();
}
read参数可以填入byte数组,意思为一次读取数组大小的字节流,这样效率更高
int len=0;
byte[] bytes=new byte[8]; //接收的数组
while ((len=fileInputStream.read(bytes))!=-1){
//len为目标读取到bytes的字节长度
System.out.println(new String(bytes,0,len));
}
属于处理流,用于封装实现了InputStream抽象类的对象,提高操作效率
FileOutputStream可以绑定一个File对象【或文件路径名称】,成为这个文件的输出流,通过write()方法将内存中的数据写到文件中。
write()方法参数可以是字符,也可以是byte数组【String对象有getBytes()方法可以方便得到】,同样可以设置byte数组写入文件的长度
String path="g:\\text1.txt";
FileOutputStream out=null;
try {
out = new FileOutputStream(path,true);//为true时不会覆盖原先的内容,默认会覆盖
out.write(97); //将ASKII码97的字符写入
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
属于处理流,用于封装实现了OutputStream抽象类的对象,提高操作效率
按照字符来操作I/O,是InputStreamReader的子类
//构造函数同样是绑定File或者路径
read(char[]) //批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1。不填入参数的话则是读取单个字符,返回的为此字符ASKII码
//包装其他的节点流或处理流,内有一些封装方法,能够更高效率的对数据进行获取
BufferedReader bufferedReader = new BufferedReader(new FileReader("g:\\2.jpg"));
readLine() //读取一整行字符,返回的内容是读取到的字符串
按照字符来操作I/O,是OutputStreamWriter的子类
write(char[],off,len) //写入指定数组的指定部分
//参数也可以写入单个字符或者字符串以及指定字符串部分
//包装其他的节点流或处理流,内有一些封装方法,能够更高效率的对数据进行获取
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("g:\\1.jpg"));
write() //写入方法没有怎么变化
newLine() //插入一个换行
节点流可以从一个特定的数据源读写数据,即知道数据来源是什么【如FileReader数据来源就是文件】
处理流【包装流】是"连接"在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能【如BufferedReader可以封装实现了Reader的对象,因此使用更加灵活】
同样是处理类,配合FileXXputStream后可以**将可序列化的对象存放在文件中**。当然在需要的时候也可以取出来,按照什么顺序存入就要按照什么顺序取出
public class Test {
public static void main(String[] args) {
ObjectOutputStream out = null;
ObjectInputStream in = null;
try {
out = new ObjectOutputStream(new FileOutputStream("g:\\res.tex"));
in = new ObjectInputStream(new FileInputStream("g:\\res.tex"));
out.writeObject(new Dog());//将这个Dog对象写入文件
out.writeInt(10); //将整型Integer对象取值为10写入文件
Object dog = in.readObject(); //读出文件的Dog对象
int i = in.readInt(); //按顺序读出Integer对象
Dog dog1=(Dog) dog;
System.out.println(dog1.name);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//Dog对象实现了序列化
class Dog implements Serializable {
int age=18;
String name="Tom";
}
//编译类型是InputStream,运行类型是BufferedInputStream
System.in //标准输入流(从键盘获取)
//编译类型是PrintStream,运行类型也是PrintStream
System.out //标准输出流(输出到键盘上)
InputStreamReader和OutputStreamWriter
转换流能将字节流转换成字符流,并且规定转换时的编码方式
//接收进来的原本是文件输入流,加以包装转换为字符流并规定编码方式为gbk
//再将字符流用缓冲流封装提高效率,最后只需要对缓冲流操作即可
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("g:\\text1.txt"), "gbk"));
//输出流同理
打印流只有输出【字节输出和字符输出】,没有输入
public static void main(String[] args) throws Exception {
PrintStream out = System.out; //获得标准打印流
out.println("123");//由于是标志打印流,所以会输出在控制台
out.write("哈哈哈".getBytes()); //print方法本质是write,因此直接用write效果一直,参数是字节
System.setOut(new PrintStream("g:\\text1.txt")); //设置标准打印流的打印位置为G盘的text1.txt
System.out.println("good"); //通过设置后,此时会打印在G盘的text1.txt
}
//System.out是字节流,因此需要通过构造函数将其变为字符流
PrintWriter writer = new PrintWriter(System.out);
PrintWriter writer2=new PrintWriter("G:\\text1.txt"); //指定输出位置
//获取本机的InetAddress对象(主机名/IP地址)
InetAddress localHost1 = InetAddress.getLocalHost();
//根据主机名获取InetAddress对象(主机名/IP地址)
InetAddress name = InetAddress.getByName("捉妖龙");
//根据域名获取InetAddress对象(主机名/IP地址)
InetAddress name2 = InetAddress.getByName("www.baidu.com");
//根据InetAddress对象获取IP地址
String address = name.getHostAddress();
//根据InetAddress对象获取主机名或域名
String hostName = name.getHostName();
服务器先对端口进行监听ServerSocket.accept(),当此端口有客户来连接的时就建立起一个Socket通道。
双方通过Socket获得输入流与输出流,由此进行信息交互
注意:每次使用write()方法时要有添加结束符,否则客户机与服务器可能会死锁
对字符流添加结束符的方式有
对字节流添加结束符的方式是调用socket.shutdownOutput()方法
若用Buffered对socket.getOutputStream()得到的输出流进行封装,则使用shutdownOutput()方法前需要用flush()方法将缓冲流推上通道
public class ClientDemo {
public static void main(String[] args) throws IOException {
//客户端要往本机9999端口发送东西
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
byte []bytes= new byte[20];
int len;
out.write("Hello,World".getBytes());
socket.shutdownOutput();//结束符
while ((len=in.read(bytes))!=-1){
System.out.println("客户端收到:"+new String(bytes,0,len));
}
out.close();
in.close();
socket.close();
}
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
//对9999端口进行侦听
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("客户机侦听中……");
//等待连接,有连接后得到socket
Socket socket = serverSocket.accept();
byte []bytes= new byte[20];
int len;
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
while ((len=in.read(bytes))!=-1){
System.out.println("服务端收到:"+new String(bytes,0,len));
}
out.write("Hello".getBytes());
socket.shutdownOutput(); //加上结束符
in.close();
out.close();
socket.close();
serverSocket.close();
}
}
由于socket只能得到字节流,因此若要得到字符流需要转换流的介入。
字符流除了传统的方法加结束符外还可以用如下方法添加↓
转换流封装后需要缓冲流Buffered再进行封装,此时加入结束符的方式是BufferedWriter每次调用完write方法后就调用newLine()方法,意思为加上换行符,即结束符。同时对面接收时要使用readLine()方法
字符流需要手动调用flush()才能会将流推上Socket通道,而字节流再加上结束符后会自动推上通道
public class ClientDemo {
public static void main(String[] args) throws IOException {
//客户端要往本机9999端口发送东西
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.write("good");
out.newLine();//加结束符
out.flush(); //将数据推上通道
String s = in.readLine();
System.out.println("客户端收到的数据:" + s);
in.close();
out.close();
socket.close();
}
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
//对9999端口进行侦听
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器侦听中……");
//等待连接,有连接后得到socket
Socket socket = serverSocket.accept();
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = in.readLine();
System.out.println("服务器收到的数据:" + s);
out.write("excellent");
out.newLine();//加结束符
out.flush(); //将数据推上通道
in.close();
out.close();
socket.close();
serverSocket.close();
}
}
首先我们传入Socket通道的应当是完整的文件,即发送前应该在客户端部分将读取来的零碎字节流组装成一个字节数组bytes[]。同理,服务器将接收的图片写入src下时,也应该是完整的文件。由此,设计一个将输入流中零碎字节组装成一个完整字节组的方法很有必要
//根据输入流获得文件的完整字节数组
public static byte[] streamToByteArray(InputStream in) throws IOException {
BufferedInputStream bis = new BufferedInputStream(in);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
byte[] res = bos.toByteArray();
return res;
}
整个逻辑过程如下
整个过程中设计到Buffered的write方法都要调用flush()函数;若是将数据写入Socket通道中,则在flush后还需要调用socket.shutdownOutput()方法为其加上结束符
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(),8888);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("g:\\picture_1.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//图片的字节数组
byte[] bytes = Util.streamToByteArray(bis);
bos.write(bytes);
bos.flush();
socket.shutdownOutput();
String s = reader.readLine();
System.out.println("来自服务器消息:"+s);
bis.close();
bos.close();
reader.close();
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器侦听8888端口……");
Socket socket = serverSocket.accept();
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = Util.streamToByteArray(bis);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\copy.jpg"));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bos.write(bytes); //写进目标位置
bos.flush();
writer.write("图片已正确接收");
writer.newLine();
writer.flush();
bis.close();
bos.close();
writer.close();
socket.close();
serverSocket.close();
System.out.println("服务器结束");
}
}
public class Receive {
public static void main(String[] args) throws IOException {
//监听9998端口
DatagramSocket socket = new DatagramSocket(9998);
byte[] bytes=new byte[1024];
//用指定字节数组作为包的容器
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
System.out.println("等待9998有内容");
//将端口接收到的内容放进包中
socket.receive(packet);
//拆包得到包里收到的内容
byte[] data = packet.getData();
//得到数据长度
int length = packet.getLength();
System.out.println("这是我收到的内容:"+new String(data,0,length));
}
}
public class Send {
public static void main(String[] args) throws IOException {
//监听9999端口,当有信息来到这里会被感受到
DatagramSocket socket = new DatagramSocket(9999);
byte[] bytes="Hello".getBytes();
//包内有目的地的地址以及要发送的内容
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("捉妖龙"), 9998);
//将内容发送至指定地址
socket.send(packet);
socket.close();
System.out.println("发送完毕!");
}
}
所谓反射,即通过配置文件中的类路径还原出类对象,并根据此对象来还原类的方法、变量
//入门样例
public static void main(String[] args) {
try {
Properties properties = new Properties();
//读取目标地址的文件
properties.load(new FileInputStream("src\\re.properties"));
String path = properties.getProperty("path");//类路径
String method = properties.getProperty("method");//方法名
Class cls = Class.forName(path); //得到路径中的类
Object o = cls.newInstance(); //得到一个对象实例,但是实际上是Object
Method method1 = cls.getMethod(method);//得到这个类的指定方法
method1.invoke(o);//将方法注入到对象实例中,即对象调用hi()方法
} catch (Exception e) {
e.printStackTrace();
}
}
java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
Class cls = Class.forName(path); //得到path路径中的类加载后在堆中的对象
java.lang.reflect.Method:代表类的方法Method对象表示某个类的方法
Method method1 = cls.getMethod(method);//得到堆中类的指定方法
java.lang.reflect.Field:代表类的成员变量,Filed对象表示某个类的成员变量
//得到类中的name属性对象
Field name = cls.getField("name"); //只能得到公有属性
//若要真正拿到属性的值,还需要反向绑定类的一个实例o,调用get方法得到
String realName=(String) name.get(o);
java.lang.reflect.Constructor:代表类的构造方法,Constuctor对象表示构造器
Constructor constructor = cls.getConstructor();//得到类一个无参构造函数
Constructor constructor1 = cls.getConstructor(String.class);//得到一个参数为String的构造函数
可以动态的创建和使用对象(这是框架底层核心,使用灵活,没有反射机制的话框架技术就失去底层支撑
使用反射基本是解释执行,对执行速度有影响
ststic Class forName(String name) //返回指定类名name的Class对象
Object newInstance() //调用缺省构造函数,返回该Class对象的一个实例
ClassLoader getClassLoader()//返回该类的类加载器
Method getMethod(String name)//返回一个Method对象,方法名为name
前提:已知一个类的全类名,且该类在类路径下
通过Class类的静态方法foaName()获取【若路径错误则会抛出ClassNotFoundException异常】
应用场景:多用于配置文件,读取类全路径,加载类
前提:已知具体的类
通过类的class获取,该方式最为安全可靠,程序性能最高
Class cls=Cat.class //得到Cat类对应的Class对象
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
前提:已知某个类的实例【执行类型是被加载的Class】,调用该实例的getClass()方法获取Class对象
应用场景:通过创建好的对象,获取Class对象
通过类加载器获取
ClassLoader cl=对象.getClass().getClassLoader();
Class c=cl.loadClass("类的全类名");
基本数据类型按如下方式得到Class类
Class cls=基本数据类型.class //会被自动装箱
基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class cls=包装类.TYPE
外部类、成员内部类、静态内部类、局部内部类、匿名内部类
interface 接口;数组;enum 枚举;annotation 注解;基本数据类型;void
静态加载:编译时加载相关的类,如果这个类不存在则会报错,依赖性强
静态加载时机:①创建对象时(new);②子类被加载时,父类也加载 ③调用类中的静态成员
动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性
动态加载时机:通过反射的加载
int n=1;
if(n==100){
Class person=Class.forName("Person");
}
//这段编译时可以通过的,因为动态加载是只有运行到代码处才进行加载。此段代码不会运行到动态加载处,因此编译没有问题
JVM
在该阶段的主要目的是将字节码从不同的数据源(可能是class
文件、也可能是jar
包,甚至网络)转化为二进制字节流加载到内存中并生成一个代表该类的java.lang.Class
对象
Xverify:none
参数来关闭大部分类验证措施,缩短虚拟机类加载时间JVM虚拟机会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值,如0,null,false等)。这些变量所使用的的内存都将在方法区中进行分配
虚拟机将常量池内的符号引用替换为直接引用的过程【可以理解原先没有具体地址,放在内存中后就有了具体地址】
()
方法的过程()
方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量赋值动作和静态代码块中的语句,并进行合并【只会在首次类加载时执行一次】()
方法在多线程环境中被正确地加锁、同步【这是内存中同一对象只有一个Class的原因】,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()
方法,其他线程都需要阻塞等待,知道活动线程执行()
方法完毕Class中的成员变量取出来后存放在Field类中
Class中的方法取出来后存放在Method类中
Class中的构造器取出来后存放在Constructor类中
true
后可以利用私有构造器构造成员知识储备:韩顺平老师零基础Mysql配套笔记
JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题
//获得一个驱动类
Driver driver = new Driver();
String url="jdbc:mysql://localhost:3306/experiment";
//将数据库账号密码放入properties中
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","admin");
//驱动通过账号密码以及端口位置获得一个连接
Connection con = driver.connect(url, properties);
//利用反射加载驱动类
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
//利用反射创建一个驱动实例
Driver driver = (Driver)aClass.newInstance();
//后续步骤一致
//加载驱动类
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url="jdbc:mysql://localhost:3306/experiment";
DriverManager.registerDriver(driver); //加载驱动
//根据路径和账号得到连接
Connection con = DriverManager.getConnection(url, "root", "admin");
//加载驱动类,源码中静态代码块会调用DriverManager完成驱动的注册
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/experiment";
//驱动注册后,根据路径和账号得到连接
Connection con = DriverManager.getConnection(url, "root", "admin");
Class.forName("com.mysql.jdbc.Driver")
连接前将关键数据放入properties,这样灵活性更强
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);
//根据路径和账号得到连接
Connection con = DriverManager.getConnection(url, user, password);
Statement对象用于执行静态SQL语句并返回其生成结果的对象
con.createStatement();//获得一个实现Statement接口的对象
在连接建立后,需要对数据库进行访问,执行命名或SQL语句,可以通过以下三种方式:
Statement对象执行SQL语句存在SQL注入风险
SQL注入是利用某些系统没有对用户输入的数据进行充分检查,而在用户输入数据中注入非法SQL语句段或命令,恶意攻击数据库【因为Statement在java程序中是拼接完成的,因此若用户在输入时恶意输入一些关键字,那么就可能会造成与开发者意愿不同的结果】
要防范SQL注入,只要用PreparedStatement取代Statement就行了
PreparedStatement执行的SQL语句中的参数用问号?
来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数
setXxx()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个参数是设置的SQL语句中的参数值
调用executeQuery(),返回ResultSet()对象
调用executeUpdate(),执行更新、包括增,删,改
int id=5;
String name="kkk";
String sql="insert into a values (?,?)";
//通过连接实现PreparedStatement接口,使preparedStatement与sql语句关联
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,id); //第一个问号放个整型数据id
preparedStatement.setString(2,name);//第二个问号放字符串数据name
preparedStatement.executeUpdate();//执行这条更新语句
try {
con = JDBCUtils.getCon();
con.setAutoCommit(false);//取消事务自动提交
PreparedStatement preparedStatement = con.prepareStatement("insert into a values (5,'ppp')");
preparedStatement.executeUpdate();
preparedStatement=con.prepareStatement("insert into a values (9,'mmm')");
int i=5/0; //此处会发生错误
preparedStatement.executeUpdate();
con.commit(); //若未发生错误,在此事务提交
con.close();
System.out.println("成功");
} catch (Exception e) {
con.rollback(); //事务回滚到执行前
e.printStackTrace();
}
当需要成批插入或更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
JDBC批量处理语句包括下面方法:
addBatch():添加需要批量处理的SQL语句或参数
executeBatch():执行批量处理语句
clearBatch():情况批处理包的语句
JDBC连接MYSQL时,如果要使用批处理功能,请再url中加参数?rewriteBatchedStatement=true
批处理往往和PrepareStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高
con = JDBCUtils.getCon();
String sql="insert into a values (?,?)";
PreparedStatement preparedStatement = con.prepareStatement(sql);
for (int i=0;i<10;i++){
preparedStatement.setInt(1,i);
preparedStatement.setString(2,i+1+"");
preparedStatement.addBatch();//将这条SQL加入待处理队列
if ((i+1)%5==0){
//执行队列中的语句
preparedStatement.executeBatch();
//清空库存
preparedStatement.clearBatch();
}
}
JDBCUtils.Close(con,preparedStatement,null);
System.out.println("成功");
JDBC的数据库连接池使用javax.sql.DataSource
来表示,DataSource只是一个接口,该接口通常由第三方提供实现
C3P0:数据库连接池,速度相对较慢,稳定性不错
Druid:阿里提供的数据库连接池,集DBCP、C3P0、Proxool优点于一身的数据库连接池
第三方提供实现的方式是导入对应jar包,大部分jar包在网上都可以找到;若比较懒,希望jar包能够自动载入则需要为IDEA安插Maven,此后只需要特定语句即可在镜像服务器中将对应jar包下载到本地
IDEA导入Maven指南,如果只是普通Java项目则不需要部署服务器,也不需要创建Java Web项目
使用前要导入
c3p0-0.9.5.5.jar
和mchange-commons-java-0.2.19.jar
两个jar包
//创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//连接池驱动
comboPooledDataSource.setDriverClass(driver);
//连接的用户账号
comboPooledDataSource.setUser(user);
//连接的用户密码
comboPooledDataSource.setPassword(password);
//连接的数据库与IP地址
comboPooledDataSource.setJdbcUrl(url);
//连接池初始连接量
comboPooledDataSource.setInitialPoolSize(10);
//连接池最大连接量
comboPooledDataSource.setMaxPoolSize(20);
//从连接池中拿一个连接
Connection connection = comboPooledDataSource.getConnection();
System.out.println(connection);
首先导入
c3p0-config.xml
配置文件【一定放在src目录下】,配置文件内配置相关信息
//直接根据配置文件中的数据库池名称,创建一个数据源对象并进行连接
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("myPool");
使用前先导入
druid-1.1.22.jar
和druid.properties
【用于配置信息】
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//通过配置文件返回数据源对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//得到一个连接
Connection connection = dataSource.getConnection();
driverClassName=com.mysql.cj.jdbc.Driver
#URL连接数据库的URL,后面的参数可不改但不删
url=jdbc:mysql://localhost:3306/experiment?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
characterEncoding=utf-8
#安装mysql时候设置的用户与密码
username=root
password=admin
#初始化物理连接的个数
initialSize=5
#最大连接池数量
maxActive=10
#获取连接时最大等待时间
maxWait=3000
#用来检测连接是否有效的sql
validationQuery=SELECT 1
#保证安全性!
testWhileIdle=true
第三方用于将结果集封装成对象的工具
使用前要导入
commons-beanutils-1.8.0.jar
与commons-dbutils-1.7.jar
两个jar包
Connection con = JDBCUtils.getCon();
QueryRunner queryRunner = new QueryRunner();//得到工具
String sql="select * from team where id < ?";//SQL语句
/**
* con 连接
* new BeanListHandler<>(xxx.class):查询结果与哪个类对应
* 5:占位符【问号?】数值,有多个占位符就填多个参数
* list:将查询结果封装在链表中
*/
List<Team> list = queryRunner.query(con, sql, new BeanListHandler<>(Team.class), 5);
//如果返回结果只有一行记录,可以使用new BeanHandler<>(xxx.class)
//返回单行单列结果用new ScalarHandler<>(),返回的结果会被放在Object对象中
Connection con = JDBCUtils.getCon();
QueryRunner queryRunner = new QueryRunner();
String sql="select name from team where id = 1";
//单行单列结果
Object query = queryRunner.query(con, sql, new ScalarHandler<>());
//利用update(con,sql,占位符值)的方式完成
QueryRunner queryRunner = new QueryRunner();
String sql="update team set name =? where id=? ";
queryRunner.update(con,sql,"Worrier",1); //传入参数,完成更新,返回值为影响的行数
正则表达式就是用某种模式去匹配字符串的一个公式
String s="1988a s d a s d a strawberry2000";
String regular="(\\d\\d)(\\d\\d)";
//采取何种规则进行匹配
Pattern pattern = Pattern.compile(regular);
//将匹配结果用matcher保存
Matcher matcher = pattern.matcher(s);
//如果有符合匹配的结果就返回true,并记录匹配位置的始末下标到groups[]
while (matcher.find()){
//得到当前匹配到的字符串
System.out.println("找到:"+matcher.group(0));
//得到当前匹配到的第一个括号的字符串
System.out.println("第一个括号内找到:"+matcher.group(1));
//得到当前匹配到的第二个括号的字符串
System.out.println("第二个括号内找到:"+matcher.group(2));
}
Pattern.compile(regular)
用来得到一个正则表达式对象,确定匹配规则pattern.matcher(s)
将匹配规则与字符串进行配对,返回Matcher对象,准备阶段至此完毕matcher.find()
开始正式匹配,找到一个匹配的字符串就将其初始索引放入底层group[0],将终止索引+1放入到group[1];若正则表达式中**若有括号分截,则group[2]会放匹配到第一个括号内容字符串的起始索引,group[3]会放匹配到第一个括号内容字符串的终止索引+1**;以此类推…matcher.group(index)
用于最终截取获得匹配到的字符串,index=0
表示输出底层group[0]~
[1]记录的字符串下标对应的字符串【即整个匹配规则匹配到的字符串】;index=1
则表示输出底层group[2]~
[3]记录的字符串下标对应的字符串【即第一个括号规则匹配到的字符串】……根据需要取出即可转义符\\:在使用正则表达式检索某些特殊字符的时候,需要用到转义符号==【让特殊符号失去作用,成为一个单独字符串】==,否则检索不到结果,甚至报错【Java正则表达式中两个\
代表其他语言中一个\
】
\\s: 匹配任何空白字符
\\S:匹配任何非空白字符
.:匹配除\n外的所有字符,如果要匹配"."本身,则要用转义字符\\.
正则表达式未完待续……
一个类只允许有一个实例对象【即只能new一个对象】
static
方法返回对象//通过调用静态方法获得唯一的一个类对象
class GirlFriend{
private static GirlFriend lhc=new GirlFriend();
private GirlFriend(){}; //构造方法为静态,则无法在类之外new对象
public static GirlFriend getGirlFriend(){ //设置为类方法是为了使用静态变量lhc
return lhc;
}
}
对象在类加载时就会被创建!
//通过调用静态方法获得唯一的一个类对象
class GirlFriend{
private static GirlFriend lhc;
private GirlFriend(){}; //构造方法为静态,则无法在类之外new对象
public static GirlFriend getGirlFriend(){//设置为类方法是为了使用静态变量lhc
if(lhc==null){
lhc=new GirlFriend(); //第一次调用方法时创建对象
}
return lhc;
}
}
利用抽象类将需要完成的公共部分放在一个模板类中,而实现类只实现每个类特殊的地方
就像一个模板将大体内容固定住,只需要对其中填空【抽象方法实现】就行
public class SmallChangeSys {
public static void main(String[] args) {
new B().test();
new C().test();
}
}
abstract class Template {
public abstract void job();
public void test() {
System.out.println("我是公共部分");
job(); //我是每个类的特殊部分
}
}
class B extends Template {
@Override
public void job() {
System.out.println("我是特殊部分1");
}
}
class C extends Template {
@Override
public void job() {
System.out.println("我是特殊部分2");
}
}
设计模式未完待续……