一、基础DOS操作
dir:directory 列出当前目录内容
md:make directory 创建目录
rd:remove directory 删除目录
cd:change directory 进入指定目录
cd..:退回到上一级目录
↑↓:遍历历史操作
二、Java语言概述
1.编写:将编写的代码保存在以 .java结尾的源文件中,如源文件HelloJava.java。
2.编译:使用javac.exe命令对源文件进行编译,在WINDOWS运行,WINDOWS不区分大小写,编译时,可写成javac hellojava.java,编译完成为字节码文件HelloJava.class(名字为类名),每定义一个class就编译为一个字节码文件,所以编译完成可能会有多个字节码文件。
3.运行:使用javac.exe命令解释运行字节码文件,可以写成java HelloJava,字节码文件名字区必须严格按照大小写,不需要写.class后缀。
三、Java基本语法
//单行注释 /*多行注释*/ /**文档注释*/
public类的类名必须与源文件名相同,一个源文件只有一个public
每个执行语句末尾都以 ; 结束。代码从右往左看除了 ; 就是{ }。
//源文件 HelloJava.java
public class HelloJava{
public static void main(String[] args){ //public static void main(String a[])
System.out.println("HelloJava"); //只有[]和args可以改,args=arguments参数
}
}
class person{
}
class animal{
}
1.命名规范
包名:多单词组成所有字母都小写:aaabbbccc
类名,接口名:所有首字母大写:AaaBbbCcc
变量名、方法名:第一个单词小写,第二个开始的单词首字母大写:aaaBbbCcc
常量名:所有单词字母都大写,每个单词之间用下划线连接:AAA_BBB_CCC
2.基本数据类型
2.1基本数据类型
Java定义变量的格式:数据类型 变量名=变量值
整型:byte(1字节=8bit) short(2字节) int(4字节) long(8字节);byte范围:-128~127;long型变量必须以 L 结尾,不写L会转换为int型。
浮点型:float(4字节) double(8字节) 表示的数值范围比long还大;定义float类型变量时必须以 F 结尾。
字符型:char(2字节=1字符);单引号' '表示,只能写一个字符,运算结果为int型。
自动类型提升:byte、 short、 char做运算时,结果都会自动提升到 int 型。
Java中的数值,整型默认为 int 类型、浮点型默认为double 类型。
public class VariableTest{
public static void main(String[] args) {
long l=12345;
System.out.println(l);
long l1=2233334445556L; //数值超过int型,必须加L
float f1=12.3; //报错,默认12.3为double型,应写为float f1=12.3F;
byte b=1;
byte b1=b+1; //报错,默认1为int型
float f1=f+9.8; //报错,默认9.8为double型
}
}
字符串数据类型:String;不属于基本数据类型,属于引用数据类型。双引号" "表示,可以和8种基本数据类型做运算,运算结果为连接运算。
2.2进制
二进制:以0b或0B开头; 八进制:以0开头; 十六进制:0-9及A-F,以0x或0X开头。
二进制的负数表示:最高位为符号位,0为正数,1为负数。有三种形式:原码;反码:对原码取反,最高位为1;补码:反码加1。正数的原码、反码、补码相同,计算机底层都用补码来储存数据。
3.运算符
算数运算符:(前)++:先自增1 (后)++:后自增1
赋值运算符:+=:S+=2相当于S=S+2
short s1 = 10;
s1+=2; //不会改变本身的数据类型
//s1=s1+2 编译失败
//自增1
byte b1 = 8;
byte++; //不会改变本身的数据类型
比较运算符:==判断相等
boolean b = false;
if(b = true) //注意区分好=与==,写错了很难看出来
System.out.println("True");
else
System.out.println("False");
//输出结果为True
逻辑运算符:&逻辑与 &&短路与 |逻辑或 ||短路或
boolean b1=true;
int num1=10;
if(b1 | (num1++>10))
System.out.println(num1);
//num1=11
boolean b2=true;
int num2=10;
if(b2 || (num2++>10))
System.out.println(num2);
//num2=10
位运算符:^异或 &与 |或(和逻辑运算符符号相同,位运算符只用于数值类型的运算)
三元运算符:(条件表达式)?表达式1:表达式2
True则执行表达式1,False则执行表达式2
int m = 12;
int n = 12;
String maxStr = (m > n)? "m大" : ((m = n)? "m和n一样大" : "n大");
System.out.println(maxStr)
//m和n一样大
4.流程控制
4.1分支结构①:If-else
int num1 = 10,num2 = 20,num3 = 30;
int max;
if (num1 >= num2){
max = num1;
}
else{
max = num2;
}
if(max >= num3){
max = max;
}
else{
max = num3;
}
System.out.println(max);
Scanner类:
import java.util.Scanner; //调用包
public class ScannerTest {
public static void main(String[] args) {
Scanner scan = new Scanner(system.in);
System.out.println("请输入姓名:");
String name = scan.next();
System.out.println("姓名:"+name);
System.out.println("请输入性别(男/女):");
String gender = scan.next();
char genderChar = gender.charat(0);//String转char,不转也能运行
//charat(index) 表示String里的第几个字符,index从0开始
System.out.println("性别:"+genderChar);
System.out.println("请输入年龄:");
int age = scan.nextInt();
System.out.println("年龄:"+age);
}
}
//生成[10,100)的随机整数
int value = (int)(Math.random() * 90 + 10); //Math.random() 是生成[0.0,1.0)的随机数
//要想生成[m,n)的随机整数,只需 (int)(Math.random()*(n - m)+ n)
字符串的比较:
String s = "优秀";
if(s.equals("优秀")){ //用于字符串,char可以直接用==判断
System.out.println("优秀");
}
4.2分支结构②:switch-case
int num = 1;
switch(num){
case 1:
System.out.println("one");
break; //如果不加break,就会继续往下运行,且会跳过判断直接运行case 2的内容
case 2:
System.out.println("two");
break;
default:
System.out.println("three");
break; //可不写
}
int month = 5;
switch(month) {
case 1: //case的内容相同可以合并
case 2:
case 3:
System.out.println("spring");
break;
case 4:
case 5:
case 6:
System.out.println("summer");
break;
4.3循环结构①:for
int num = 1;
for(System.out.print('a');num <= 3;System.out.print('c'),num++){
System.out.print('b');
}
//输出结果为abcbcbc
4.4循环结构②:while
int i = 1;
while(i <= 100){
if(i % 2 == 0){
System.out.println(i);
}
i++
}
while循环和for循环都是可以相互转换的,如:while(true){} 可以写成 for(;;){}
4.5循环结构③:do while
int i = 10;
do{
System.out.println(i);
i--;
}while(i > 10);
4.6嵌套循环
理解:外层的循环可以理解为次数,里层的循环是需求的意义
for(int j = 1;j <= 5;j++){ //结果为:
for(int i = 1;i <= 5 - j;i++){ // *
System.out.print(" "); // * *
} // * * *
for(int k = 1;k <= j;k++){ // * * * *
System.out.print("* "); // * * * * *
}
System.out.println();
}
for(int i = 1;i <= 100000;i++) {
for(int j = 2;j <= Math.sqrt(i);j++) { //将被除数 j<i 优化为 j<=√i
if(i % j ==0) { //运算速度为一个量级的提升
break; //有break运算速度为一个量级的提升
}
else if(j == (int)Math.sqrt(i)) {
System.out.print(i + ",");
}
}
}
特殊关键字的使用:break continue
for(int i = 1;i <= 10;i++){
if(i % 4 == 0){
continue;
}
System.out.print(i);
} //输出123567910
跳出更外层循环:标签label的使用
label:for(int i = 2;i <= 100;i++) {
for(int j = 2;j <= Math.sqrt(i);j++) {
if(i % j == 0) {
continue label; 跳到label标签那层
}
}
System.out.println(i);
}
5.数组
5.1一维二维数组的基本写法
数组特点:属于引用数据类型;数组的长度一旦确定就不可以修改。数组一旦初始化长度就是确定的,反过来说,必须要知道数组的长度才能创建数组。
int[] array = new int[] {1,2,3,4,5}; //数组的定义方式一
int[] arr = new int[5]; //数组的的定义方式二,默认赋值为0;浮点类型为0.0
arr[0] = 1; //char类型的也是0,而不是'0';boolean类型的也是0,即false。
System.out.println(arr); //输出为该数组的地址
for(int i = 0;i < array.length;i++) { //.lenght求数组长度
System.out.println(array[i]);
}
一维数组的内存解析:
二维数组:是一维数组的嵌套
int[][] arr1 = new int[][] {{1,2,3},{2,3},{3,4,5,6}};
int[][] arr2 = {{1,2,3},{2,3},{3,4,5,6}}; //也是可以这样写的
int[][] arr3 = new int[4][2];
int[][] arr4 = new int[4][]; //也可以这样写
//int[][] arr5 = new int[][]; 错误写法
arr4[0] = new int[2];
System.out.println(arr2.length); //输出3
System.out.println(arr3[0]); //输出[I@123a439b
System.out.println(arr4); //输出[[I@7de26db8
System.out.println(arr4[1]); //输出null
二维数组内存解析:
int[] x,y[]; //表示的是x[],y[][],y是二维数组
x = new int [] {5,1,4};
y = new int [][] {{1,2,3},{2,3},{23,4,5,6}};
y[0] = x; //将 x 赋给 y[0] ,实际上 是把 y[0]的地址指向 x 的地址,并不是数组的复制
y[0][0] = x[1]; //此时x[] 和y[][]指的是同一个地址,使用的是同一个数据
for(int i = 0;i < y[0].length;i++) {
System.out.print(y[0][i]); //输出结果为114
}
System.out.println();
for(int i = 0;i < x.length;i++) {
System.out.print(x[i]); //输出结果为114
}
引用数据类型的赋值都是赋的地址,基本数据类型赋值才是赋的真正的数值
5.2数组的复制
String[] arr = new String[] {"AA","BB","CC","DD","EE","FF"};
String[] arr2 = new String[arr.length];
//复制arr
for(int i = 0;i < arr2.length;i++) {
arr2[i] = arr[i];
}
//反转元素
for(int i = 0;i <= arr.length/2;i++) {
String temp = arr[i];
arr[i] = arr[arr.length - i -1];
arr[arr.length - i - 1] = temp;
}
for(int i = 0;i < arr.length;i++) {
System.out.print(arr[i] + " ");
}
5.3二分法查找
int[] arr = new int[] {-42,-23,-4,0,2,14,34,45,56,88,99};
int head = 0;
int end = arr.length - 1;
boolean isFlag = true;
int dest = 56;
while(head <= end) {
int middle = (head + end)/2;
if(dest == arr[middle]) {
System.out.println("在位置" + middle + "找到了" + dest);
isFlag = false;
break;
}
else if(dest > arr[middle]) {
head = middle + 1;
}
else {
end = middle - 1;
}
}
if(isFlag == true){
System.out.println("没有找到");
}
5.4Arrays工具类
int[] arr1 = new int[] {1,2,3,4,5,6,7,8};
int[] arr2 = new int[] {1,5,3,4,2,8,7,6};
boolean isEqual = Arrays.equals(arr1, arr2);
System.out.println(isEqual); //判断数组是否相等
System.out.println(Arrays.toString(arr2)); //输出数组的元素
int index = Arrays.binarySearch(arr1, 3); //二分查找
System.out.println(index); //若没有找到,返回的index为负数
Arrays.fill(arr1, 10); //将一个数填充到数组中
System.out.println(Arrays.toString(arr1)); //输出为[10, 10, 10, 10, 10, 10, 10, 10]
Arrays.sort(arr2); //数组排序
System.out.println(Arrays.toString(arr2));
5.5数组中常见的异常
Arrayindexoutofboundsexception:数组角标越界异常
合理范围:[0 , arr.length - 1] 越界:arr[-1],arr[arr.length]
NullPointerException:空指针异常
举例:int[ ] arr = null; arr[0]
四、面向对象(Object)
1.面向对象的属性
面向对象的三大特征:封装,继承,多态。
类和对象的关系:对象是类的实例化
属性和局部变量:
1.1属性(成员变量)
直接定义在类内;局部变量:定义在方法内、方法形参、构造器内、代码块内。属性有初始默认值,局部变量没有初始默认值,所以使用前一定要赋值。
1.2常用的权限修饰符
public、private、缺省、protected。①写在成员变量前面,不能写在局部变量前,可以理解为局部变量的权限修饰符已经在方法上体现了。②权限大小:private只能在类内部;缺省只能在同一个包内;protected在不同包,只要是子类(继承)就能调用;public在同一个工程都可以调用。
public class PersonTest { //测试类,正常情况下是新建一个文件来写的,写在同一个文件也不会报错
public static void main(String[] args) {
Person p1 = new Person(); //创建对象
p1.name = "Tom"; //调用属性
System.out.println(p1.name);
//调用方法
p1.eat();
p1.talk("中文");
}
}
class Person{ //创建类
//属性(或成员变量)
public String name;
int age = 1;
boolean isMale;
public void eat() { //方法
String food = "烙饼"; //局部变量
System.out.println("eat" + food);
}
public void talk(String language) { //language:形参,也是局部变量
System.out.println("我们使用" + language + "交流");
}
}
2.面向对象中方法的使用
2.1方法的声明和使用
class Customer{
String name;
public void eat(){
System.out.println("吃饭");
//void不需要返回值;但也可以写return;表示结束方法
}
public String getName() {
return name; //返回值类型为String,所以一定要返回一个String
}
public void sleep(int hours) { //hours叫做形参
System.out.println("睡眠时间为" + hours);
eat(); //方法里面可以调用方法
}
public String getNation(String nation) {
String info = "我的国籍是" + nation;
return info;
}
}
2.2内存的结构
虚拟机栈:存储局部变量
堆:new出来的对象;对象的属性(非static)
方法区:类的加载信息,常量池,静态域
2.3匿名对象
public class NoNameInstance {
public static void main(String[] args) {
//匿名对象
new Phone().price = 1999;
new Phone().showPrice(); //输出0.0
//匿名对象的使用
PhoneMall mall = new PhoneMall();
mall.show(new Phone());
}
}
class Phone{
double price;
public void sendEmail() {
System.out.println("发送邮件");
}
public void playGames() {
System.out.println("玩游戏");
}
public void showPrice() {
System.out.println(price);
}
}
class PhoneMall{
public void show(Phone phone) {
phone.sendEmail();
phone.playGames();
}
}
2.4方法的重载(overload)
public class ArraysUtil{
//数组反转的方法
public void reverse(int[] arr) {
for(int i =0;i < arr.length / 2;i++) {
int temp = arr[i];
arr[i] = arr[arr.length - i - 1];
arr[arr.length - i - 1] = temp;
}
}
public void reverse(String[] arr) { //同名,这就叫重载
for(int i =0;i < arr.length / 2;i++) {
String temp = arr[i];
arr[i] = arr[arr.length - i - 1];
arr[arr.length - i - 1] = temp;
}
}
}
2.5可变个数的形参
可变个数形参的书写格式:变量类型 ... 变量名
在一个方法的形参里面,可变形参只能放在末尾,所以一个方法只能有一个可变形参。例如:public void show(String ... strs,int i)这样写是错的。
public class MethodArgs {
public static void main(String[] args) {
MethodArgs meth = new MethodArgs();
meth.show("ss");
meth.show("ss","aa","bb"); //调用的是方法show(String .. strs)
meth.show(new String[] {"ss","aa","bb"});//调用的也是方法show(String .. strs)
}
public void show(String s) { //与下面方法同名,但形参不一样,这样写就是方法的重构
System.out.println(s);
}
public void show(String ...strs) { //String ... 是可变形参的写法
for(int i = 0;i < strs.length;i++) {
System.out.print(strs[i]);
}
System.out.println();
}
// public void show(String[] str) { //上个方法的可变形参已经包含了String[]
// for(int i = 0;i < strs.length;i++) { //这样写就不是重构,与上个方法同名同参数
// System.out.print(str[i]);
// }
// System.out.println();
// }
}
2.6方法参数的值传递机制
如果参数是基本数据类型,此时实参赋值给形参是实参的真实数据值。
如果参数是引用数据类型,此时实参赋值给形参是实参存储数据的地址值。
public class ValueTransferTest {
public static void main(String[] args) {
int m = 10;
int n = 20;
ValueTransferTest test = new ValueTransfertest();
test.swap(m,n); //基本数据类型赋值的是真实数值,调用方法结束后就出栈了
System.out.println(m + " " + n); //结果还是10 20
}
public void swap(int i, int j) {
int temp = i;
i = j;
j = temp;
}
}
2.7方法的递归(recursion)
public class RecursionTest {
public static void main(String[] args) {
RecursionTest test = new Recursiontest();
System.out.println(test.getSum(6));
}
public int getSum(int n) {
if(n == 1) {
return 1;
}
else {
return n + getSum(n - 1);
}
}
}
3.封装性
3.1封装与隐藏
体现为:将属性的值私有化private,同时提供公有化public的方法来设置set和获取get属性。
public class AnimalTest {
public static void main(String[] args) {
Animal test = new Animal();
// test.legs = 4; 这样就无法调用legs了
test.setlegs(4);
}
}
class Animal{
//封装体现为 将属性私有化,再提供属性的设置加获取。
private int legs; //将legs私有化,这就是隐藏
//属性的设置
public void setlegs(int l) {
if(l >=0 && l % 2 == 0) {
legs = l;
}
else {
legs = 0;
}
}
//属性的获取
public int getlegs() {
return legs;
}
}
3.2构造器(constructor)
构造器的作用:与方法并列的一种结构,无返回值。创建对象,初始化对象的信息;一旦显示了自己定义的构造器后,就不再提供默认的空参构造器。
public class ConstructorTest {
public static void main(String[] args) {
//Person p1 = new Person(); 报错,没有定义无参的构造器
Person p2 = new Person(12);
Person p3 = new Person(12,"Tom");
System.out.println(p2.age); //输出12
System.out.println(p3.name); //输出Tom
}
}
class Person{
int age;
String name;
public Person(int i) { //构造器
age = i;
}
public Person(int i,String n) { //构造器,构造器重载
age = i;
name = n;
}
}
属性赋值的顺序:①系统默认初始化;②显示初始化;③构造器中初始化;④通过"对象.属性"和"对象.方法"进行赋值。(不包含代码块)
3.3 this关键字
this可以用来修饰属性、方法、构造器;理解为当前对象;this可以用来调用构造器,调用构造器只能声明在首行。
public class ThisTest {
public static void main(String[] args) {
Human p = new Human(12,"Allen");
System.out.println(p.getAge() + p.getName());
}
}
class Human{
int age;
String name;
public void setAge(int age) {
this.age = age; //此处,如果写age = age,那么系统就会认为两个age都是形参age
}
public int getAge() {
return age; //这里隐藏了this. 也可以写this.age
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name; //这里也可以写name
}
public Human() {
}
public Human(int age) {
this(); //调用构造器,只能放在首行,这里调用的是Human()
this.age = age;
}
public Human(int age,String name) {
this(age); //这里调用的是Human(int age),构造器也可以嵌套调用
this.name = name;
}
}
3.4 package、import关键字
package包:用来管理区分类,声明在源文件首行,每"."一次就代表一层目录。
import导入:使用指定包下的类、接口。import java.util.* 表示导入java.util包下的所有结构,但如果使用的是java.util下的子包,仍需要显示导入子包。如果使用的是java.lang包下的类、接口,则可以省略import。如果使用了两个不同包下同名的类,则其中一个只能以全类名(包名.类名)的方式来表示。
4.继承性(inheritance)
4.1继承的格式
格式写法:class A extends B{}
①A为子类(subclass),B为父类(superclass),一旦子类A继承了父类B后,就获取了父类B中所有的属性和方法(private属性也能继承);②一个父类可以被多个子类继承,一个子类只能继承一个父类;③如果一个类没有显示声明继承一个父类,那么它默认继承java.lang.Object类,所以所有的java类都直接或间接的继承java.lang.Object类。
4.2方法的重写(overwrite)
重写:①子类继承父类后,可以对父类中同名的方法进行重写,重写后覆盖父类的方法。②方法名和形参列表与父类的方法名和形参列表相同;③子类中的权限修饰符不小于父类的权限修饰符(权限大的才能覆盖权限小的),当父类中的权限修饰符为private时,无法进行重写;④子类的返回值类型不大于父类的返回值类型(int类型不是double类型的子类,父类写double,子类不能写int)⑤static不能被重写
子类对象实例化的过程:①从结果上来看:就是继承性;②从过程上来看:因为默认super()继承父类,可以一直继承到java.lang.Object的空参构造器上。
4.3 super关键字
①super可以用来调用属性、方法、构造器;理解为父类的;②super调用构造器只能放在首行③子类的空参构造器默认调用了父类的空参构造器,即默认有super();④构造器中只能this()和super()二选一
public class Circle {
private double radius;
public Circle() { //空参构造器
radius = 1.0;
}
public Circle(double radius) { //有参构造器
this.radius = radius;
}
public double findArea() {
return Math.PI * radius * radius;
}
public void seTradius(double r) {
radius = r;
}
public double geTradius() {
return radius;
}
}
public class Cylinder extends Circle{
private double height;
public Cylinder() {
height = 1.0;
}
public Cylinder(double radius,double height) {
super(radius); //super调用构造器
this.height = height;
}
public double findVolume() {//求圆柱体积
return super.findArea() * height;
}
@Override
public double findArea() {//求圆柱表面积
return super.findArea() * 2 +
2 * Math.PI * geTradius() * 2;
}
public void setHeight(double height) {
this.height = height;
}
public double getHeight() {
return height;
}
}
5.多态性(polymorphism)
5.1多态的基本概念
Java的多态性为对象的多态性:父类的引用指向子类的对象;
虚拟方法调用:在编译期,只能调用父类声明的方法,但在运行期,执行的是子类重写的方法;总结:编译看左边,运行看右边;
多态性只适用于方法,不适用于属性(属性的编译和运行都只看左边)。
向下转型:使用强制类型转换符;使用强转时可能会报错。
public class PersonTest {
public static void main(String[] args) {
// Person为父类,Man为子类
Person p1 = new Man(); //多态
p1.eat();//子类重写的方法,编译期,调用的是父类的方法
p1.walk();//子类重写的方法,运行结果显示为子类的内容
// p1.earnMoney(); 子类特有的方法,无法调用
Man m1 = (Man)p1; //强制类型转换
m1.earnMoney(); //转换后就可以调用子类的方法
// Woman w1 = (Woman)p1; //报错
// w1.goShopping();
if(p1 instanceof Woman) { //判断p1是否为Woman的实例化
Woman w1 = (Woman)p1; //是返回true,不是返回false
w1.goShopping();
}
}
}
5.2 instanceof关键字
为了保证强转时不出现异常,使用instanceof进行判断。判定对象a是否为类A的实例化,如果是,返回true;如果不是,返回false。
6.Object类的使用
6.1Object类的基本概念
object里面的主要方法:①equals() 定义为 ==运算符,但String、Date、File、包装类等都重写了equals(),使得equals()比较的是属性的相等;②toString()定义为输出哈希地址值,但String、Date、File、包装类等都重写了toString(),使得toString()输出的是“实体内容”;
6.2进行单元测试代码
导入JUnit包,测试要求:①此类为public;②此类提供无参构造器;③对需要测试的方法上方注解@Test
7.包装类的使用
7.1使基本数据类型具有类的特征
基本数据类型对应了包装类;其中包装类Byte、Short、Integer、Long、Float、Double继承于Number类;还有boolean和char对应了包装类Boolean、Character。
7.2基本数据类型、包装类、String类型的相互转换
public void method(Object obj) {
System.out.println(obj);
}
public void test1() {
//基本数据类型与对应的包装类的自动装箱拆箱
int num1 = 10;
method(num1);//不会报错,因为会自动装箱,即自动将int转换为Integer
// 自动装箱
Integer in1 = num1; // 只能将对应的基本数据类型转换成对应的包装类;将int转Integer
// Short sh1 = num1; 报错,无法将int转换成Short包装类
boolean isTrue1 = true;
Boolean b1 = isTrue1;
//自动拆箱
int num2 = in1;
boolean isTrue2 = b1;
}
public void test2() {
int num = 10;
// 基本数据类型和包装类 与 String类 互转;使用valueOf转换
Integer in1 = Integer.valueOf(num); // 将int转Integer也可以
String s1 = String.valueOf(num); //将int转String
int i1 = Integer.valueOf(s1); //也可将String转int
//本质是String转为Integer,然后Integer自动转int
Integer in2 = Integer.valueOf("123"); // 将String转换成Integer类
String s2 = String.valueOf(in1); // 也可将Integer转化为String
Boolean b1 = Boolean.valueOf("TrUe"); // true
Boolean b2 = Boolean.valueOf("true1"); // false
// 包装类转化为基本数据类型
int i2 = in1.intValue();
}
public void test3() {
//parse强转
String str1 = "123";
//与valueOf的区别为:parse是直接由String转int;
//valueOf是先将String转Integer再由Integer自动转int
int num = Integer.parseInt(str1);//可能会报NumberFormatException
}
}
包装类总结:①基本数据类型 与 包装类 互转:使用自动装箱与自动拆箱。 ②包装类或基本数据类型(可以看成一个整体,因为自动装箱和拆箱)与 String类 互转:使用valueOf。③String类 转化为 基本数据类型:使用parse强转,可能会报NumberFormatException错误。
valueOf和parse都能将String类转化为基本数据类型,区别为:parse是直接由String转int;
valueOf是先将String转Integer,由于基本数据类型的自动拆箱,Integer能够自动转为int。
8.static关键字
8.1static关键字的基本概念
成员变量(属性)分为:实例变量(不用static修饰)和静态变量(用static修饰)。
静态变量随着类的加载而加载,即静态变量的加载要早于对象的创建。
静态的方法中,只能调用静态的方法;在静态的方法中不能使用this、super关键字。
非静态的方法中,既能调用非静态的方法,也能调用静态的按方法。
8.2类变量 vs 实例化变量的内存解析
9.单例设计模式
饿汉式和懒汉式;采用一定方法保证整个软件系统中,对某个类只能存在一个对象实例。饿汉式:创建太早,一直处于加载状态,线程是安全的;懒汉式:可以延迟对象的创建,需要考虑线程安全。
//饿汉式实现单例模式
class Bank{
private Bank() {
}
private static Bank instance = new Bank();
public static Bank getInstance() {
return instance;
}
}
//懒汉式实现单例模式
class Order{
private Order() {
}
private static Order instance = null;
public static Order getInstance() {
if (instance == null) { //双重判断,使效率提高
synchronized (Bank.class) { //synchronized线程同步
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
main()方法
①作为程序入口;②是一个普通的静态方法;③作为与控制台交互的方式。
10.代码块
10.1代码块的作用
①用来初始化类、对象;②分为静态代码块和非静态代码块;③静态代码块:随着类的加载而执行,而且只执行一次;④非静态代码块:随着对象的创建而执行,每创建一个对象就执行一次;
//static代码块
static{
System.out.println("static block");
desc = "爱学习的人"; //可以重新赋值属性,仅限static属性或方法
}
//非static的代码块
{
System.out.println("non-static block");
age = 1; //可以重新赋值属性,都可以
}
对属性赋值的先后顺序:①系统默认初始化;②显示初始化或代码块;③构造器中初始化;④通过"对象.属性"和"对象.方法"进行赋值。
10.2 final关键字
用来修饰一个类,使其不能被其他类继承;比如:String类、System类、StringBuffer类。
用来修饰一个方法,使其不能被重写;比如:Object类中的getClass()方法。
用来修饰变量,使其变为常量;①用来修饰属性,考虑可以赋值的位置有:显示初始化;代码块中初始化;构造器中初始化。②用来修饰局部变量,如:形参,给形参赋值以后,不能重新赋值。③用static final修饰的为全局常量。
11.抽象类和抽象方法
11.1 abstract关键字的使用
abstract类是抽象的,可以用来修饰类和方法;
修饰类:此类不能实例化,通常都会提供子类,让子类实例化。
修饰方法:抽象方法只有方法的声明,没有方法体;包含抽象方法,那么此类一是抽象类;子类需要重写父类中的抽象方法或者将子类也变成抽象类。
11.2抽象类的匿名子对象类
public class PersonTest {
public static void main(String[] args) {
//创建了一个匿名子类的对象p
Person p = new Person() {
@Override
public void eat() {
System.out.println("吃饭饭");
}
};
}
method(p);
public static void method(Person p) {
p.eat();
}
}
11.3模板方法的设计模式
abstract class Template{
public void spendTime(){
long start = System.currentTimeMillis();
code(); //不确定的部分
long end = System.currentTimeMillis();
System.out.println("花费时间为:"+(end-start));
}
public abstract void code();
}
class SubTemplate extends Template {
@Override
public void code() {
for (int i = 2; i <= 1000; i++) {
boolean isFlag = true;
for (int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0) {
isFlag = false;
break;
}
}
if (isFlag) { System.out.println(i); }
}
}
}
12.接口(interface)
12.1接口的使用
接口中不能定义构造器,因此不能够实例化;接口是通过让类去实现(implements)的方法来使用的;
可以实现多个接口,两个接口用逗号隔开;接口与接口之间可以继承并且可以多继承。
接口中可以定义静态(static)方法和默认(default)方法;静态方法只能通过接口来调用,默认方法即可以由实现类的对象调用,也可以由接口来调用。
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900;//第一宇宙速度
//省略了 public static final
int MIN_SPEED = 1;
public abstract void fly();
//省略了 public abstract
void stop();
}
12.2接口的匿名实现类的对象创建
public class USBTest {
public static void main(String[] args) {
Computer computer = new Computer();
USB phone = new USB() {
@Override
public void start() {System.out.println("手机开始工作");}
@Override
public void stop() {System.out.println("手机结束工作");}
};
computer.transferDate(phone);
}
}
class Computer {
public void transferDate(USB usb) {
usb.start();
System.out.println("传输中");
usb.stop();
}
}
interface USB{
void start();
void stop();
}
12.3代理模式(Proxy)
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork {
public void browse();
}
class Server implements NetWork {
@Override
public void browse() {System.out.println("真实的服务器在访问网络");}
}
class ProxyServer implements NetWork {
private NetWork work;
public ProxyServer(NetWork work) {this.work = work;}
public void check(){System.out.println("联网之前的工作");}
@Override
public void browse() {
check();
work.browse();
}
}
13.内部类
成员内部类:内类可以定义属性、方法、构造器;可以被static、final修饰、4种权限修饰符修饰。
13.1内部类的实例化
public static void main(String[] args) {
//静态内部类的实例化
Person.Dog dog = new Person.Dog();
//普通内部类的实例化
Person person = new Person();
Person.Bird bird = person.new Bird();
}
13.2内部类的属性调用
class Bird {
String name = "杜鹃";
public Bird(){
}
public void sing(){
System.out.println("鸟");
Person.this.eat(); //可以省略写成 eat();
}
public void display(String name) {
System.out.println(name); //方法的形参
System.out.println(this.name); //内部类的属性
System.out.println(Person.this.name); //外部类的属性
}
}
五、异常处理
1.异常体系结构
分为Error和Exception,Exception是可以处理的异常
1.1 Exception异常类型
编译时异常:IOException、ClassNotFoundException等等
运行时异常(RuntimeException):NullPointerException、NumberFormatException、ClassCastException、Arrayindexoutofboundsexception 等等
运行时异常可以不处理,编译时异常一定要处理(即把编译时可能会出现的异常,延时到运行时展示出来)
2.异常处理方式
2.1异常处理机制
异常处理:抓抛模型
过程1:"抛":程序在执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,抛出对象后,后面的代码就不再执行。
异常对象的生成有两个方式:①系统自动生成;②手动生成(throw)一个异常对象。
过程2:"抓":异常处理的方式;如try-catxh-finally、throws
2.2 try-catch-finally
一旦try中的异常对象匹配到catch时,就进入catch中进行异常处理,处理完之后就跳出catch结构了,继续执行后面的代码。
public void test(){
FileInputStream fis = null;
try {
File file = new File("hello.txt");
fis = new FileInputStream(file);
int data = fis.read();
while (data != -1) {
System.out.print((char)data);
data = fis.read();
}
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printstacktrace();
}
}
}
2.3 throws
throws的方式只是将异常抛出给方法的调用者,并没有将异常处理掉。
public class Throws {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
e.printstacktrace();
}
}
public static void method2() throws Exception{
method1();
}
public static void method1() throws Exception{
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while (data != -1) {
System.out.print((char)data);
data = fis.read();
}
fis.close();
}
}
2.4手动抛出异常
class Student{
int id;
public void register(int id) {
if (id > 0) {
this.id = id;
}else{
// System.out.println("非法");
throw new RuntimeException("输入的用户ID非法");
}
}
}
六、多线程
1.程序、进程、线程的基本概念
程序(program):一段静态的代码,静态对象。
进程(process):正在运行的一个程序;进程作为资源分配的单位,系统在运行时为每个进程分配不同的内存区域。
线程(thread):进程进一步细化为线程,是一个程序内部的一条执行路径;若一个进程同一时间并行执行多个线程,就是支持多线程的;每个线程拥有独立的运行栈和程序计数器,线程切换的开销小。
2.线程的创建和使用
2.1多线程的创建
方法一:
创建一个继承于Thread类的子类,重写run()方法,创建该类的对象,通过此对象调用start()。
public class ThreadTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
//直接调thread.run()就相当于调用一个普通的方法,不构成多线程
thread.start();
//以下是在main()线程中执行的
for (int i = 0; i < 10000; i++) {
if (i % 2 == 0) {
System.out.println(i+"-----main()------");
}
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
方法二:
创建一个实现Runnable接口的类,实现run()方法,创建该类的对象,将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象,通过此对象调用start()。
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
2.2 Thread中常用的方法
start():启动当前线程,并调用run()
currentThread():静态方法,返回当前线程
getName():获取当前线程的名字
setName():设置当前线程的名字
yield():释放当前cpu的执行权
join():调用此方法的线程加入进来优先执行,待执行完才会执行其他线程
sleep():让当前线程睡眠指定的毫秒
isAlive():判断当前线程是否存活
2.3线程的调度
线程的优先级
MAX_PRIORITY=10 高优先级并不一定就先执行完
norMANL_PRIORITY=5
MIN_PRIORITY=1
设置线程的优先级
getPriority():获取线程的优先级
setPriority():设置线程的优先级
3.线程的同步
3.1线程安全的方法
方法一:同步代码块; 代码格式:synchronized(同步监视器){ }
同步监视器,俗称锁,任何对象都能充当锁,只需要是唯一的;多个线程必须要共用一把锁
class Window implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {//此时的this即window对象,是唯一的
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printstacktrace();
}
System.out.println(Thread.currentThread().getName()+":买票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
非静态方法的同步监视器为:this;静态方法的同步监视器为:当前类本身(当前类.class)
class Window3 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
if (ticket == 0) {
break;
}
}
}
private synchronized void show(){//同步监视器为this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printstacktrace();
}
System.out.println(Thread.currentThread().getName()+":买票,票号为:"+ticket);
ticket--;
}
}
}
3.2死锁
public class DeadLockTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1) {
s1.append("a");s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printstacktrace();
}
synchronized (s2) {
s1.append("b");s2.append("2");
System.out.println(s1);System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printstacktrace();
}
synchronized (s1) {
s1.append("d");s2.append("4");
System.out.println(s1);System.out.println(s2);
}
}
}
}).start();
}
}
3.3线程安全的方法三:Lock
class Window implements Runnable{
private int ticket = 100;
private reentrantlock lock = new reentrantlock(true);
@Override
public void run() {
while (true) {
try {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printstacktrace();
}
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}else{
break;
}
} finally { //每执行一次lock就再执行一次unlock
lock.unlock(); //必须执行
}
}
}
}
4.线程的通信
4.1线程通信的方法
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait()的一个线程;如果有多个线程被wait()就唤醒优先级高的线程。
notifyAll():唤醒所有被wait()的线程。
class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printstacktrace();
}
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
//使得调用wait()方法的线程阻塞
try {
wait();
} catch (InterruptedException e) {
e.printstacktrace();
}
}else{
break;
}
}
}
}
}
4.2 sleep()和wait()的异同
①sleep()是Thread类中的,wait()是Object类中的。
②sleep()可以在任何场景下使用,wait()必须使用在同步代码块或同步方法中。
③在同步代码块或同步方法中,wait()会释放同步监视器,sleep()则不会。
5.新增线程创建方式
5.1实现Callable接口
优点:①call()可以有返回值;②call()可以抛异常;③Callable支持使用泛型
public class ThreadSafeNew{
public static void main(String[] args) {
NewThread newThread = new NewThread();
FutureTask futureTask = new FutureTask(newThread);
new Thread(futureTask).start();
try {
//get()返回值即为FutureTask构造器参数Callable实现类重谢的Call()返回值
Object sum = futureTask.get();
System.out.println("总和为:"+sum);
} catch (Exception e) {
e.printstacktrace();
}
}
}
class NewThread implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {System.out.println(i);sum+=i;}
}
return sum;
}
}
5.2使用线程池
优势:①提高响应速度,减少创建新线程的时间;②降低资源消耗,重复利用线程池中线程,不需要每次都创建;③便于线程管理
corePoolSize():核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
class OddThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {System.out.println(Thread.currentThread().getName()+":"+i);}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
threadPoolExecutor.setCorePoolSize(15);
// threadPoolExecutor.setKeepAliveTime();
executorService.execute(new OddThread());//适合使用Runnable
// executorService.submit();//适合使用于Callable
//关闭连接池
executorService.shutdown();
}
}
七、常用类
1.String类
1.1 String类的特性
String 类:代表字符串。 Java程序中的所有字符串面值(如 "abc")都作为此类的实例现。
String是一个 final类,代表不可变的字符序列 。
String实现了Serializable接口:表示字符串是支持序列化的
实现了Comparable接口:表示String可以比较大小
字符串是常量,用双引号起来表示。 它们的值在创建之后不能更改 。
String对象的字符内容是存储在一个数组 value[] 中。
1.2字符串对象的存储
字符串常量存储在字符串常量池,目的是共享;字符串非常量对象存储在堆中 。
public void test2(){
Person p1 = new Person("Tom",12);
Person p2 = new Person("Tom",12);
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.name == p2.name);//true
System.out.println(p1.age == p2.age);//true
}
1.3 String常用方法
charat(int index):返回某索引处的字符return value[index]
toLowerCase():使用默认语言环境,将String中的所有字符转换为小写
trim():返回字符串的副本,忽略前导空白和尾部空白
equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
compareto(String anotherString):比较两个字符串的大小
substring(int beginIndex,int endindex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
endsWith(String suffix):测试此字符串是否以指定的后缀结束
contains(CharSequence s):当且仅当此字符串包含指定的char 值序列时,返回true
indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引
replace(char oldChar,char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。
replaceAll(String regex,String replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。
matches(String regex):告知此字符串是否匹配给定的正则表达式
split(String regex):根据给定正则表达式的匹配拆分此字符串。
public void test(){
String str1 = "abc123";
char[] chars = str1.tochararray(); //String 转换为char[]
for (char aChar : chars) {
System.out.println(aChar);
}
String str2 = new String(chars); //char[]转换为String
byte[] bytes = str1.getBytes(); //String转换为byte[]
String str3 = new String(bytes); //byte[]转换为String
}
2.StringBuffer和StringBuilder类
2.1StringBuffer类的特点
StringBuffer代表可变的字符序列
其对象必须使用构造器生成。有三个构造器:
StringBuffer():初始容量为16的字符串缓冲区
StringBuffer(int size):构造指定容量的字符串缓冲区
StringBuffer(String str):将内容初始化为指定字符串内容
StringBuffer底层是创建长度为16的byte[],如果长度不够会自动扩容
2.2StringBuilder类的特点
StringBuilder和StringBuffer是一样的,均代表可变的字符序列 ;StringBuffer是线程安全的,效率相对低;StringBuilder是线程不安全的,效率高。
2.3StringBuffer和StringBuilder的常用方法
append(xxx):用于进行字符串拼接,()里面可以是很多类型
delete(int start,int end):删除指定位置的内容
replace(int start,int end,String str):把[start,end)位置替换为str
insert(int ffset,xxx):在指定位置插入
reverse():把当前字符序列逆转
3.日期时间的API
3.1 System类
System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
此方法适于计算时间差。
3.2 java.util.Date类和java.sql.Date类
java.util.Date类表示特定的瞬间,精确到毫秒;其很多方法都过时了。
两者的转换,可以先求得毫秒数再转换
public class DataTimeTest {
@Test
public void test1(){
//返回当前时间与1970年1月1日0时0分0秒之间的以毫秒为单位的时间差
long timeMillis = System.currentTimeMillis();
System.out.println(timeMillis);
}
@Test
public void test2(){
//java.util.Date
Date date1 = new Date();
System.out.println(date1); //Thu Feb 24 21:45:08 CST 2022
System.out.println(date1.getTime()); //1645710308233
java.sql.Date date2 = new java.sql.Date(1645710308233L);
System.out.println(date2); //2022-02-24
java.sql.Date date3 = new java.sql.Date(date1.getTime());
System.out.println(date3); //2022-02-24
}
}
3.3 java.text.SimpleDateFormat类
SimpleDateFormat对日期Date类的格式化和解析
格式化:日期 --->字符串
解析:格式化的逆过程,字符串 ---> 日期
@Test
public void testSimpleDateFormat(){
Date date = new Date();
//格式化为指定的格式
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String format1 = sdf1.format(date);
System.out.println(format1);//2022-02-25 03:27:24
try {
Date date1 = sdf1.parse("2022-02-25 03:27:24"); //解析为util.Date
System.out.println(date1); //Fri Feb 25 03:27:24 CST 2022
} catch (ParseException e) {
e.printstacktrace();
}
}
3.4 Calendar类:日历类、抽象类
@Test
public void testCalendar(){
Calendar calendar = Calendar.getInstance();
//常用方法//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTime() 转为util.Date类
Date date = calendar.getTime();
System.out.println(date);
//setTime() 转换为日历类
Date date1 = new Date();
calendar.setTime(date1);
int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
System.out.println(dayOfYear);
}
存在的问题:可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从0开始。
格式化:格式化只对Date用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。
3.5 LocalDateTime类、LocalDate类、LocalTime类
类似于Calendar,是JDK8以后新加入的取代Calendar的类,解决了上述存在的问题。
@Test
public void test1(){
LocalDate localDate = LocalDate.Now();
LocalTime localTime = LocalTime.Now();
LocalDateTime localDateTime = LocalDateTime.Now();
//of():设置指定的年月日时分秒
LocalDateTime localDateTime1 = LocalDateTime.of(2022, 8, 22, 23, 56, 0);
//getXXX()获取
System.out.println(localDateTime.getDayOfMonth());
//withXXX() 修改
LocalDate localDate1 = localDate.withDayOfMonth(12);
LocalDateTime localDateTime2 = localDateTime.withHour(4);
//plusXXX()和minusXXX()增加和减少
LocalDate localDate2 = localDate.plusDays(4);
}
3.6 Instant类
时间线上的一个瞬时点。 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC开始的秒数。)类似于 java.util.Date类
@Test
public void test(){
//有时区的问题/直接获取的时本初子午线对应的时间
Instant instant = Instant.Now();
//设置一个(小时)偏移
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
//得到毫秒数
long milli = instant.toEpochMilli();
//将毫秒数转为Instant对象
Instant instant1 = Instant.ofEpochMilli(1655775544444L);
}
3.7 DateTimeFormatter类
格式化或解析日期、时间;类似于SimpleDateFormat
@Test
public void test(){
//预定义的标准格式
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.Now();
String formatDate = formatter.format(localDateTime);
//本地化相关的格式
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
String formatDate2 = formatter1.format(localDateTime);
//自定义格式
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
String formatDate3 = formatter2.format(localDateTime);
//将指定格式的字符序列转换为日期时间
TemporalAccessor accessor = formatter2.parse("2022-02-25 08:15:18");
}
4.Java比较器
4.1 Comparable接口
像String、包装类等实现了Comparable接口,重写了compareto(obj)方法,给出了比较两个对象大小的方式。
重写compareto(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareto (obj) 方法。在compareto (obj) 方法中指明如何排序
public class Goods implements Comparable{
@Override
public int compareto(Object o) {
if (o instanceof Goods) {
Goods goods = (Goods)o;
if (this.price > goods.price) {
return 1;
} else if (this.price < goods.price) {
return -1;
}else{
return this.name.compareto(goods.name);
}
}
throw new RuntimeException("传入的数据不一样");
}
}
4.2 Comparator接口
当元素的类型没实现 Comparable 接口而又不方便修改代码,或者实现了Comparable 接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序。
@Test
public void test3(){
String[] arr = new String[]{"AP","AA", "BB", "KA", "MM", "GG"};
Arrays.sort(arr, new Comparator<String>() {
//按照字符串从大到小排序
@Override
public int compare(String o1, String o2) {
if (o1 instanceof String && o2 instanceof String) {
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareto(s2);
}
throw new RuntimeException("输入的类型不正确");
}
});
System.out.println(Arrays.toString(arr));
}
两种排序方式对比
Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
Comparator接口属于临时性的比较。
5.其他类
5.1 System类
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
5.2 Math类
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。
5.3 BigInteger类、BigDecimal类
@Test
public void test3(){
BigInteger bigInteger = new BigInteger("1233444444444422333");
BigDecimal bigDecimal = new BigDecimal(123444444.333222221);
BigDecimal bigDecimal2 = new BigDecimal(12.22221);
//scale:精度,RoundingMode:四舍五入
System.out.println(bigDecimal.divide(bigDecimal2, RoundingMode.HALF_DOWN));
System.out.println(bigDecimal.divide(bigDecimal2, 7,RoundingMode.HALF_DOWN));
}
八、枚举类和注解
1.枚举类的创建
1.1自定义枚举类
class Season{
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName,String seasonDesc){
this.seasonDesc=seasonDesc;
this.seasonName=seasonName;
}
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("冬天","冰天雪地");
}
1.2使用关键字enum创建枚举类
enum继承于Enum类,不是Object类
enum Season1{
SPRING ("春天","春暖花开"),
SUMMER ("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
private final String seasonName;
private final String seasonDesc;
private Season1(String seasonName,String seasonDesc){
this.seasonDesc=seasonDesc;
this.seasonName=seasonName;
}
}
2.注解(Annotation)
2.1注解的概述
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。
框架 = 注解 + 反射机制 + 设计模式
2.2注解的使用
生成文档相关的注解
在编译时进行格式检查(JDK内置的个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的择。
2.3自定义注解
注解声明为:@interface
内部定义成员,通常使用value表示
可以指定成员的默认值,使用default定义
如果自定义注解没成员,表明是一个标识作用。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义
public @interface MyAnnotation {
String value();
}
2.4元注解
对现有的注解进行解释说明的注解。
@Retention:指定所修饰的 Annotation 的生命周期,有三种类型:SOURCE(仅保留在源文件层面)、CLASS(默认行为,保留在class文件中)、RUNTIME(运行时保留)。(只有声明为RUNTIME 的注解,才能通过反射获取)
@Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素(类、方法、构造器等)可以指定为ElementType.TYPE、ElementType.METHOD等等
@Documented:表示所修饰的注解在被javadoc解析时,保留下来。(频率低)
@Inherited:被它修饰的 Annotation 将具继承性。(使用频率低)
JDK8中注解的新特性:可重复注解、类型注解
@Repeatable:表示注解可以重复修饰,成员值为MyAnnotations.class
MyAnnotation的Target和Retention等元注解与MyAnnotations相同。
ElementType.TYPE_ParaMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, ParaMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_ParaMETER,TYPE_USE})
public @interface MyAnnotation {
String value();
}
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, ParaMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_ParaMETER,TYPE_USE})
public @interface MyAnnotations {
MyAnnotation[] value();
}
九、Java集合
1.Java集合框架概述
1.1集合与数组存储数据概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储
1.2数组的特点
一旦初始化以后,其长度就确定了。
数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
缺点:
一旦初始化以后,其长度就不可修改。
数组中提供的方法非常限,对于添加、删除、插入数据等操作,非常不便、效率不高。
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
2.Collection接口
2.1单列集合框架结构
Collection接口:单列集合,用来存储一个一个的对象
List接口:存储序的、可重复的数据。 -->“动态”数组
ArrayList、LinkedList、Vector
Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
HashSet、LinkedHashSet、TreeSet
2.2 Collection接口的常用方法
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry", 20));
System.out.println(coll); //输出集合
boolean contains = coll.contains(123);
System.out.println(contains); //true
System.out.println(coll.contains(new String("Tom")));//true
//contains()调用的是equals方法,因为String的equals方法已经重写,所以返回true
System.out.println(coll.contains(new Person("Jerry", 20)));//false
//remove()方法,在删除之前也是需要判断是否contains的
System.out.println(coll.remove(123));
coll.remove(new Person("Jerry", 20));//没有重写equals(),contains()判断为false,删除失败
}
2.3 Iterator迭代器接口
用于遍历Collection的方式
@Test
public void test2(){
//省略 Collection coll = new ArrayList(); ....
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
//remove()需写在iterator.next()后面
if ("Tom".equals(o)) {
iterator.remove();
}
}
//再次遍历要重新调用iterator()
iterator = coll.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
2.4增强For循环(forEach)
内部也是调用的迭代器
@Test
public void test3(){
String[] arr = new String[]{"AA","BB","CC"};
//for(数组元素的类型 局部变量:数组对象
for (String s : arr) {
s = "GG"; //这里的更改并不会改变原本的arr
System.out.println(s);
}
for (String s : arr) {
System.out.println(s);
}
}
3. Collection子接口:List接口
3.1 List接口的框架结构
List接口:存储序的、可重复的数据。“动态”数组,替换原的数组
ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
3.2 ArrayList源码分析
jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组。如果此次的添加导致底层elementData数组容量不够,则扩容。默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的数组。第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]。后续的添加和扩容操作与jdk 7 无异。
小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
3.3 LinkedList源码分析
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev; Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
3.4 Vector
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
3.5存储的元素的要求
3.6 List常用方法
@Test
public void test2(){
ArrayList<Object> list = new ArrayList<>();
list.add(123);
list.add("sjfdj");
list.add(new Person("sjj", 23));
int index = list.indexOf(1253); //返回要查找内容的index
//从后往前找,index返回的还是从前往后的index
System.out.println(list.lastIndexOf(345));
System.out.println(list.remove(1)); //1是index
System.out.println(list.remove(Integer.valueOf(123)));//转成Integer才是整型数
list.set(1, "CC"); //设置index为1的值
//不改变本身的List
System.out.println(list.subList(1, 3));
}
4. Collection子接口:Set接口
4.1 Set接口的框架结构
Set接口:存储无序的、不可重复的数据
HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
LinkedHashSet:HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
TreeSet:可以照添加对象的指定属性,进行排序。
4.2 Set接口的特点
Set接口:存储无序的、不可重复的数据。以HashSet为例说明:
无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。
不可重复性:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。
4.3常用方法
Set接口中没额外定义新的方法,使用的都是Collection中声明过的方法。
4.4 HashSet的底层原理
底层用的是HashMap,只将元素放到key,value不使用;value都指向同一个Object()对象。
4.5 HashSet 元素添加过程
向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置,判断
数组此位置上是否已经元素:
如果此位置上没其他元素,则元素a添加成功。 --->情况1
如果此位置上其他元素b(或以链表形式存在的多个元素,则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。--->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况3
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a。
4.6 LinkedHashSet
LinkedHashSet在HashSet的基础上,每个数据还维护了两个引用(双向链表),记录此数据的前后数据。对于频繁的遍历操作,LinkedHashSet效率高于HashSet。
4.7存储对象所在类的要求
Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode ()和equals()
重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
4.8 TreeSet
①向TreeSet中添加的数据,要求是相同类的对象。
②TreeSet会将属性进行排序,所以对于无法排序的对象,需要实现排序方法 。
③两种排序方式:自然排序(实现Comparable接口 和 定制排序(Comparator))。
④自然排序中,比较两个对象是否相同的标准为:compareto()返回0.不再是equals()。
⑤定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals()。
5. Map接口
5.1 Map接口的框架结构
Map:双列数据,存储key-value对的数据(键值对)
HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。 底层使用红黑树
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
5.2 Map结构
Map中的key:无序的、不可重复的,使用Set存储所的key → key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所的value →value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所的entry
5.3 HashMap的底层实现原理
HashMap在jdk7中实现原理:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
map.put(key1,value1)
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。 ----情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
如果equals()返回false:此时key1-value1添加成功。----情况3
如果equals()返回true:使用value1替换value2。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
5.4 HashMap在jdk8中相较于jdk7的改变
①new HashMap():底层没创建一个长度为16的数组
② jdk 8底层的数组是:Node[],而非Entry[]
③首次调用put()方法时,底层创建长度为16的数组
④jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
⑤当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
5.5 HashMap底层属性说明
DEFAULT_INITIAL_CAPACITY:HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
5.6 LinkedHashMap的底层实现原理
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap。
区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node。
5.7 HashMap常用方法
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet() / values() / entrySet()
5.8 TreeSet
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要照key进行排序:自然排序 、定制排序
@Test
public void test(){
TreeMap<Object, Object> treeMap = new TreeMap<>(new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User) {
User u1 = (User) o1;
User u2 = (User) o2;
return Integer.compare(u1.getAge(),u2.getAge());
}
throw new RuntimeException("错误的");
}
});
}
6. Collections工具类
6.1 Collections工具类的介绍
Collections是一个操作Set、List和Map等集合的工具类
6.2 Collections工具类的常用方法
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的旧值
@Test
public void test1(){
ArrayList<Integer> list = new ArrayList<>();
list.add(1323);list.add(21);
list.add(32);list.add(0);
// ArrayList<Integer> dest = new ArrayList<>();
//copy底层要求 dest.size()要大于等于list.size()
// Collections.copy(dest,list); //这样copy是不成功的,因为dest.size()为0
// 方式一:
List<Object> dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest,list);
System.out.println("dest"+dest);
// 方式二:
// ArrayList<Integer> dest = new ArrayList<>();
// dest.addAll(list); //使用addAll()达到copy的效果
// System.out.println("dest:"+dest);
}
十、泛型(Generic)
1.泛型的使用
1.1泛型的概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参)。
1.2泛型在集合中的使用
@Test
public void test3(){
HashMap<String, Integer> map = new HashMap<>();
map.put("Tom",100);
map.put("Jom",90);
map.put("Oom",67);
map.put("Mom",88);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
①集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) --->实例化以后:add(Integer e)
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。
⑤ 如果实例化时,没指明泛型的类型。默认类型为java.lang.Object类型。
2.自定义泛型
2.1自定泛型的方式
public class Order<T> {
String orderName;
int orderId;
T orderT;
public T getorderT() {
return orderT;
}
}
@Test
public void test1(){
Order<String> order = new Order<>();
order.setorderT("AA:hello");
}
public class SubOrder extends Order<Integer>{
}
@Test
public void test2(){
//由于子类在继承时已经明确了泛型的类型,所以实例化时,不需要再指明泛型
SubOrder subOrder = new SubOrder();
subOrder.setorderT(123);
}
2.2泛型方法
public <E> List<E> copyFromArrayToList(E[] arr) {
ArrayList<E> list = new ArrayList<>();
Collections.addAll(list, arr);
return list;
}
泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没任何关系。
泛型方法可以声明为静态的。原因:泛型参数是在调用方法时确定的,而并非在实例化类时
3.注意点
3.1泛型的注意点
①泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
②泛型类的构造器如下:public Genericclass(){}。而下面是错误的:public Genericclass<E>(){}
③泛型不同的引用(ArrayList<String>和ArrayList<lnteger>)不能相互赋值。
④泛型如果不指定,将被擦除,泛型按照object处理,但不等价于Object。
⑤如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
⑥泛型的指定中不能使用基本数据类型,可以使用包装类替换。
⑦在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
异常类不能是泛型的
⑧不能使用new E[ ]。但是可以:E[ ] elements =(E[ ])new Object[capacity]
⑨父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。
3.2 泛型在继承上的体现
@Test
public void test1(){
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
List<Object> list1 = null;
List<String> list2 = null;
//此时list1和list2不具有子父类的关系
// list1 = list2; 编译无法通过
}
@Test
public void test2(){
List<String> list1 = null;
ArrayList<String> list2 = null; //间接父类List
AbstractList<String> list3 = null; //父类List
list1 = list2;
list1 = list3;
}
3.3通配符
@Test
public void test3(){
List<Object>list1 = null;
List<String>list2 = null;
List<?> list = null;
list = list1;
list = list2;
List<String>list3 = new ArrayList<>();
list3.add("AA");list3.add("BB");list3.add("CC");
//写入
list = list3;
// list.add("AA"); 不允许
list.add(null); //只能add(null)
//读取
Object o = list.get(0); //可以用Object类型表示
}
限制条件的通配符:
@Test
public void test4(){
//通配符下限:extends类似于小于等于
List<? extends Person> list1 = null;
//通配符上限:super类似于大于等于
List<? super Person> list2 = null;
List<Student> list3 = null;
List<Person> list4 = null;
List<Object> list5 = null;
list1 = list3;
list1 = list4;
// list1 = list5; //Object类是Person类父类,所以列表类型不符合
// list2 =list3; //Student类是Person类子类
list2 = list4;
list2 = list5;
//读取数据
list1 = list3;
Person p = list1.get(0); //也可以由Object接收数据
list2 = list4;
Object object = list2.get(0); //只能Object接收
//写入数据
// list1.add(new Person()); //不行
list1.add(null); //同样只能add(null)
// list2.add(new Object()); //不行
list2.add(new Student()); //也可以写入Person()
}
十一、IO流
1.File类的使用
1.1路径的分类
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径
说明:在IDEA中:
如果大家开发使用JUnit中的单元测试方法测试,相对路径即为当前Module下。
如果大家使用main()测试,相对路径即为当前的Project下。
1.2 File类的常用方法
File类的获取功能
.getAbsoluteFile() 获取绝对路径
.getPath() 获取路径
.getName() 获取名称getParent()获取上层文件目录路径,若无,返回null
.list() 返回指定目录下所有文件或文件目录的String[]
.listFiles() 返回指定目录下所有文件或文件目录(包含路径名)的String[]
File类的判断功能
.isDirectory() 判断是否是文件目录
.isFile() 判断是否是文件
.exists() 判断文件是否存在
File类的创建功能
.createNewFile() 创建文件,若文件存在,则不创建
.mkdir() 创建单层文件目录mkdirs()一次性可以创建多层级目录
.delete() 删除文件或文件夹
@Test
public void tes2(){
//只是在内存层面创建一个对象,并不是真正的创建
File file = new File("D:/IdeaProjects");
String[] list = file.list();
for (String s : list) {
System.out.println(s);
}
}
2. IO流的概述
2.1流的分类
①操作数据单位:字节流、字符流
②数据的流向:输入流、输出流(对于程序来说)
③流的角色:节点流(直接作用于文件上)、处理流(作用在流上的流)
2.2流的体系结构
3.节点流(或文件流)
3.1 FileReader 输入过程
① 创建File类的对象,指明读取的数据的来源。(要求此文件一定要存在否则就会报FileNotFoundException。)
② 创建相应的输入流,将File类的对象作为参数,传入流的构造器中
③ 具体的读入过程:创建相应的char[]。(read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1)
④ 关闭流资源。(说明:程序中出现的异常需要使用try-catch-finally处理。)
3.2 FileReader的使用
@Test //读入的方式一 每次只读一个字符,效率太差了
public void testFileReader1() {
FileReader fr = null;
try {
File file = new File("hello.txt");
fr = new FileReader(file);
int data;
while ((data = fr.read()) != -1) { //返回读入的一个字符,如果达到文件末尾了返回-1
System.out.print((char)data); //每次读入一个字符
}
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if(fr != null) //要注意fr == null时,不用关闭流
fr.close();
} catch (Exception e) {
e.printstacktrace();
}
}
}
@Test //读入的方式二 创建char[],提高读入效率
public void testFileReader2(){
FileReader fr = null;
try {
File file = new File("hello.txt");
fr = new FileReader(file);
//每次读入cbuf数组中的字符个数,如果达到末尾,返回-1
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1) {
//写法1:
// for (int i = 0; i < len; i++) {
// System.out.println(cbuf[i]);
// }
//写法2:
String str = new String(cbuf,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if(fr != null)
fr.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
3.3 FileWriter的输出过程
① 创建File类的对象,指明写出的数据的位置。(不要求此文件一定要存在)
②File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
如果构造器是:FileWriter(file) / FileWriter(file,false) :对原文件的覆盖
如果流是:FileWriter(file,true):不会对原文件覆盖,而是在原文件基础上追加内容)
③ 创建相应的输出流,将File类的对象作为参数,传入流的构造器中
④ 具体的写出过程:write(char[ ] buffer,0,len)
⑤ 关闭流资源。(说明:程序中出现的异常需要使用try-catch-finally处理。)
3.4 FileWriter的使用
@Test
public void testFileWriter1(){
FileWriter fw = null;
try {
File file = new File("hello1.txt");
fw = new FileWriter(file);
fw.write("I have a dream\n");
fw.write("you also have a dream");
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if(fw != null)
fw.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
3.5使用FileReader和FileWriter实现复制
@Test
public void testFileReaderWriter(){
FileReader fr = null;
FileWriter fw = null;
try {
File file = new File("hello.txt");
File destFile = new File("copyHello.txt");
fr = new FileReader(file);
fw = new FileWriter(destFile);
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1) {
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if(fw != null)
fw.close();
} catch (IOException e) {
e.printstacktrace();
}
try {
if(fr != null)
fr.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
3.6使用FileInputStream和FileOutputStream实现复制
@Test //字节流 实现复制
public void testFileInputOutputStream(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File file = new File("1.jfif");
File destFile = new File("1copy.jfif");
fis = new FileInputStream(file);
fos = new FileOutputStream(destFile);
byte[] buffer = new byte[5];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if(fos != null)
fos.close();
} catch (IOException e) {
e.printstacktrace();
}
try {
if(fis != null)
fis.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
3.7字节流和字符流的使用
对于文本文件(.txt,.java,.c,.cpp),使用字符流处理。
对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理 。
对于复制操作而言,字节流既可以实现非文本文件也可以实现文本文件。
4.缓冲流
4.1缓冲流的作用
缓冲流是一种处理流,作用在节点流上
作用:提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区。默认情况下是8kb。
4.2使用BufferedInputStream 和 bufferedoutputstream 实现复制
@Test
public void testBufferedInputOutputStream(){
BufferedInputStream bis = null;
bufferedoutputstream bos = null;
try {
File file = new File("hello.txt"); //1.造文件
File destFile = new File("hellocopy.txt");
FileInputStream fis = new FileInputStream(file); //2.造流
FileOutputStream fos = new FileOutputStream(destFile);
bis = new BufferedInputStream(fis);
bos = new bufferedoutputstream(fos);
byte[] buffer = new byte[10]; //3.复制的操作:读取和写入
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer,0,len);
}
} catch (IOException e) {
e.printstacktrace();
} finally {
//4.流关闭 要求先关闭外层再关闭内层;关闭外层时,内层也会自动关闭,所以一般只关闭外层
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printstacktrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printstacktrace();
}
}
// fos.close();fis.close();
}
}
4.3使用BufferedReader和BufferedWriter实现复制
注意:BufferReader多了一种读取的方法 .readLine();每次读取一行,返回的是String类型,当返回null时,读取结束。
@Test
public void testBufferedReaderWriter(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
// File file = new File("hello.txt");
// File destFile = new File("hellocopy.txt"); 可以省略
FileReader fr = new FileReader("hello.txt");
FileWriter fw = new FileWriter("hellocopy.txt");
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
String data; //在BufferedReader里,可以直接使用.readLine()方法,每次读取一行,返回的是String类型
while ((data = br.readLine()) != null) { //当返回null时,读取结束
bw.write(data +"\n");
}
} catch (IOException e) {
e.printstacktrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printstacktrace();
}
}
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
}
5.转换流
5.1转换流的作用
转换流属于字符流 InputStreamReader:将一个字节的输入流转换为字符的输入流
解码:字节、字节数组 → 字符数组、字符串 OutputStreamWriter:将一个字符的输出流转换为字节的输出流
编码:字符数组、字符串 → 字节、字节数组
说明:编码决定了解码的方式
5.2 InputStreamReader的使用
@Test //将字节流转换为字符流
public void test1(){
InputStreamReader isr = null;
try {
FileInputStream fis = new FileInputStream("hello111.txt");
isr = new InputStreamReader(fis);
char[] cbuffer = new char[5];
int len;
while ((len = isr.read(cbuffer)) != -1) {
String str = new String(cbuffer, 0, len);
System.out.println(str);
}
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if (isr != null)
isr.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
5.3综合使用InputStreamReader和OutputStreamWriter
@Test //字节流转字符流之后,再将字符流转为 指定编码 的字节流
public void test2(){
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
FileInputStream fis = new FileInputStream("hello.txt");
FileOutputStream fos = new FileOutputStream("hellotransfer.txt");
isr = new InputStreamReader(fis,"UTF-8");
osw = new OutputStreamWriter(fos,"GBK");
char[] cbuffer = new char[20];
int len;
while ((len = isr.read(cbuffer)) != -1) {
osw.write(cbuffer,0,len);
}
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if (osw != null)
osw.close();
} catch (IOException e) {
e.printstacktrace();
}
try {
if (isr != null)
isr.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
6.其他流的使用
6.1标准的输入输出流 system.in 和 System.out
system.in:标准的输入流,默认从键盘输入 (是字节流,所以通常需要转换为字符流)
System.out:标准的输出流,默认从控制台输出
修改默认的输入和输出行为:
System类的setIn(InputStream is) / setout(PrintStream ps)方式重新指定输入和输出的流。
@Test //读取键盘输入的字符,当输入e或exit时停止读取
public void test1() throws IOException { //异常处理应使用try-catch-finally
InputStreamReader isr = new InputStreamReader(system.in);
BufferedReader br = new BufferedReader(isr);
while (true) {
System.out.println("请输入字符串:");
String data = br.readLine();
if (data.equalsIgnoreCase("e") || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束");
break;
}
String upp = data.toupperCase();
System.out.println(upp);
}
br.close();
}
6.2打印流 PrintStream 和PrintWriter
提供了一系列重载的print()和println()方法,用于多种数据类型的输出
System.out返回的是PrintStream的实例
@Test //打印到指定文件
public void test2() throws IOException { //异常处理应使用try-catch-finally
FileOutputStream fos = new FileOutputStream(new File("printStream.txt"));
PrintStream ps = new PrintStream(fos,true);
if (ps != null) {
System.setout(ps); //setout()设置一个打印流,不使用默认输出在控制台
}
for (int i = 0; i < 256; i++) {
System.out.print((char)i); //输出ASCII码 字符
}
fos.close();
}
6.3数据流 DataInputStream 和 DataOutputStream
用于读取或写出基本数据类型的变量或字符串
@Test //将内存中的字符串、基本数据类型的变量写出到文件中
public void test3() throws IOException { //异常处理应使用try-catch-finally
DataOutputStream dos = new DataOutputStream(new FileOutputStream("test3_1.txt"));
dos.writeUTF("名字");
dos.writeInt(23);
dos.writeBoolean(true);
dos.flush(); //清空缓冲区的数据流
dos.close();
}
@Test
public void test4() throws IOException { //异常处理应使用try-catch-finally
DataInputStream dis = new DataInputStream(new FileInputStream("test3.txt"));
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
System.out.println(name + age + isMale);
}
7.对象流 ObjectInputStream 和 ObjectOutputStream
7.1对象流的作用
ObjectOutputStream:内存中的对象 → 存储中的文件、通过网络传输出去(序列化过程)
ObjectInputStream:存储中的文件、通过网络接收过来 → 内存中的对象(反序列化过程)
对象的序列化机制:
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
7.2 ObjectOutputStream序列化的实现
@Test
public void test1() throws IOException {//异常处理应使用try-catch-finally
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("我爱北京"));
oos.writeObject(new Person("阿狸",12));
oos.writeObject(new Person("阿狸",12,new Account(100)));
oos.flush();
oos.close();
}
7.3 ObjectInputStream反序列化的实现
@Test
public void test2() throws Exception {//异常处理应使用try-catch-finally
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object o = ois.readobject(); //读取的顺序要按照写入的顺序读取
String str = (String) o;//先读取String
System.out.println(str);
Object o1 = ois.readobject();
Person p = (Person) o1; //再读取Person
System.out.println(p);
Object o2 = ois.readobject();
Person p1 = (Person) o2; //最后读取Person带Account
System.out.println(p1);
ois.close();
}
7.4序列化的要求
①需要实现接口:Serializable
②当前类提供一个全局常量:serialVersionUID (long型的唯一的数)
③除了当前Person类需要实现Serializable接口之外,还必须保证其内部所属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
④不能序列化static和transient修饰的成员变量(如果想要某个属性不被序列化,可以用transient关键字来修饰)
8. RandomAccessFile流
8.1使用说明
①RandomAccessFile既可以作为一个输入流,也可以作为一个输出流。
②在模式里:"r" 以只读的方式打开;"rw"可以读取和写入
③RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
④RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
⑤如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原文件内容进行覆盖。(默认情况下,从头覆盖)
⑥可以通过相关的操作,实现RandomAccessFile“插入”数据的效果。seek(int pos)
8.2复制图片的示例
@Test
public void test1() throws Exception {//异常处理应使用try-catch-finally
RandomAccessFile raf1 = new RandomAccessFile("1.jfif", "r");//以只读的方式打开
RandomAccessFile raf2 = new RandomAccessFile("1Random.jfif", "rw");//读取和写入
byte[] buffer = new byte[1024];
int len;
while ((len = raf1.read(buffer))!= -1) {
raf2.write(buffer,0,len);
}
raf1.close();
raf2.close();
}
8.3 RandomAccessFile实现插入本文的效果
@Test //插入文本到指针3中间
public void test2() throws IOException { //异常处理应使用try-catch-finally
RandomAccessFile raf1 = new RandomAccessFile("Random.txt", "rw");
//调指针到3
raf1.seek(3);
//保存指针3后面的所有数据到StringBuffer[]
StringBuffer builder = new StringBuffer((int) new File("Random.txt").length());
byte[] buffer = new byte[20];
int len;
while ((len = raf1.read(buffer)) != -1) {
builder.append(new String(buffer, 0, len));
}
//调回指针到3,写入"xyz"
raf1.seek(3);
raf1.write("xyz".getBytes());
//将StringBuilder[]再写在后面
raf1.write(builder.toString().getBytes());
raf1.close();
}
9. NIO
9.1 NIO的介绍
Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。
NIO与原来的IO同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。
NIO将以更加高效的方式进行文件的读写操作。
随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。
十二、网络编程
1. 通信要素一:IP和端口号
1.1 IP的理解
①IP:唯一的标识 Internet 上的计算机(通信实体)
②在Java中使用InetAddress类代表IP
③IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
④域名: www.baidu.com www.mi.com www.sina.com www.jd.com
⑤域名解析:域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。 -------域名解析
⑥本地回路地址:127.0.0.1 对应着:localhost
1.2 InetAddress类
实例化
getByName(String host) 、 getLocalHost()
常用方法
getHostName() / getHostAddress()
public static void main(String[] args) {
try {
InetAddress inet1 = InetAddress.getByName("192.168.10.01");
System.out.println(inet1);
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
System.out.println(inet2); //www.baidu.com/163.177.151.109
System.out.println(inet2.getHostName()); //www.baidu.com
System.out.println(inet2.getHostAddress()); //163.177.151.109
//获取本机IP
InetAddress localHost = InetAddress.getLocalHost();
} catch (UnkNownHostException e) {
e.printstacktrace();
}
}
1.3端口号
端口号指正在计算机上运行的进程。
要求:不同的进程不同的端口号
范围:被规定为一个 16 位的整数 0~65535。
2.通信要素二:网络通信协议
2.1分型模型
2.2 TCP和UDP的区别
2.3 TCP三次握手和四次挥手
3. TCP网络编程
3.1客户端发送信息给服务端
客户端发送信息给服务端,服务端将数据显示在控制台上
@Test//客户端
public void client() throws IOException{ //异常用try-catch-finally处理
InetAddress inet = InetAddress.getByName("127.0.0.1"); //服务端的ip
Socket socket = new Socket(inet,8899); //服务器的端口号
OutputStream os = socket.getoutputStream();
os.write("你好,我是客户端".getBytes());
socket.close();
os.close();
}
@Test //服务端 测试需要先打开服务端,再打开客户端
public void server() throws IOException { //异常用try-catch-finally处理
ServerSocket ss = new ServerSocket(8899);
Socket accept = ss.accept();
InputStream is = accept.getInputStream();
//字节流的读取,转换为字符时可能会出错
// byte[] buffer = new byte[20];
// int len;
// while ((len = is.read(buffer)) != -1) {
// String str = new String(buffer, 0, len);
// System.out.println(str);
// }
//方法二:使用ByteArrayOutputStream,是把每段字节拼一起,最后再转成字符
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
System.out.println("收到地址为:"+accept.getInetAddress().getHostAddress()+"的数据");
baos.close();
is.close();
accept.close();
ss.close();
}
3.2客户端发送文件给服务端
从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。
@Test //客户端
public void client() throws IOException { //异常用try-catch-finally处理
Socket socket = new Socket(InetAddress.getByName("localhost"), 9090);
OutputStream os = socket.getoutputStream();
FileInputStream fis = new FileInputStream("1.jfif");
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer,0,len);
}
socket.shutdownOutput(); //关闭输出数据
//接收来自服务器端的信息
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer1 = new byte[20];
int len1;
while ((len1 = is.read(buffer1)) != -1) {
baos.write(buffer1,0,len1);
}
System.out.println(baos.toString());
fis.close();os.close();socket.close();
is.close();baos.close();
}
@Test //服务端
public void server() throws IOException { //异常用try-catch-finally处理
ServerSocket ss = new ServerSocket(9090);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("1copy.jfif");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) { //read()会阻塞
fos.write(buffer,0,len); //所以在客户端传输完文件后需要手动关闭输出数据
}
//服务器端给客户端反馈
OutputStream os = socket.getoutputStream();
os.write("你好,服务器已接收到信息".getBytes());
fos.close();is.close();socket.close();
ss.close();os.close();
}
4. UDP网络编程
可以随便发送,不管接收端有没有回应
@Test //发送端
public void sender() throws IOException { //异常用try-catch-finally处理
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
socket.close();
}
@Test //接收端
public void receiver() throws IOException { //异常用try-catch-finally处理
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
5. URL编程
5.1 URL的理解
统一资源定位符,对应着互联网的某一资源地址
5.2 URL的5个基本结构
http://localhost:8080/examples/beauty.jpg?username=Tom
协议 主机名 端口号 资源地址 参数列表
5.3常用方法
5.4从URL下载文件
public static void main(String[] args) { //从Tomcat上下载文件
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL("http://localhost:8080/day26_InetAddress/1.jfif");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
fos = new FileOutputStream("day26_InetAddress/1fromTomcat.jfif");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printstacktrace();
} finally {
try {
if(is != null)
is.close();
} catch (IOException e) {
e.printstacktrace();
}
try {
if(is != null)
fos.close();
} catch (IOException e) {
e.printstacktrace();
}
if (urlConnection != null)
urlConnection.disconnect();
}
}
十三、Java反射
1.反射的概述
1.1反射的理解
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何
类的内部信息,并能直接操作任意对象的内部属性及方法。
框架 = 反射 + 注解 + 设计模式。
1.2反射机制的“动态性”
@Test
public void test2(){
int num = new Random().nextInt(3);
String classpath = "";
switch (num) {
case 0:
classpath = "java.util.Date";
break;
case 1:
classpath = "java.lang.Object";
break;
case 2:
classpath = "com.personal.java1_reflection.Person";
break;
}
try {
Object obj = getInstance(classpath);
System.out.println(obj);
} catch (Exception e) {
e.printstacktrace();
}
}
public Object getInstance(String classpath) throws Exception {
Class<?> clazz = Class.forName(classpath);
return clazz.newInstance();
}
2. Class类
2.1 Class类的理解
①类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
②换句话说,Class的实例就对应着一个运行时类。
③加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
来获取此运行时类。
2.2 Class实例的方法
运行时类中必须要有空参构造器,并且权限修饰符的权限要够,通常设置为public
@Test //CLass实例的方法
public void test4() throws ClassNotFoundException {
//方法一:调用运行时类的属性
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);
//方法二:通过运行时类的对象
Person p1 = new Person();
Class<? extends Person> clazz2 = p1.getClass();
System.out.println(clazz2);
//方法三:调用class的静态方法:forName(String classpath) (使用频率高)
Class<?> clazz3 = Class.forName("com.personal.java1_reflection.Person");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2); //true
System.out.println(clazz1 == clazz3); //true
//方法四:使用加载器:ClassLoader(了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class<?> clazz4 = classLoader.loadClass("com.personal.java1_reflection.Person");
System.out.println(clazz4);
}
2.3 Class实例可以是哪些结构
①class:外部类,成员(成员内部,静态内部类),局部内部类,匿名内部类
②interface:接口
③[]:数组
④enum:枚举
⑤annotation:注解@inteface
⑥primitive type:基本数据类型
⑦void
3. ClassLoader的了解
3.1类的加载过程
3.2类的加载器的作用
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.class对象,作为方法区中类数据的访问入口。
3.3使用Classloader加载src目录下的配置文件
@Test
public void test3() throws IOException {
Properties pros = new Properties();
//getResourceAsstream配置文件默认识别为:当前module下的src
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream ras = classLoader.getResourceAsstream("jdbc1.properties");
pros.load(ras);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("用户:"+user+";密码:"+password);
}
4.反射的应用
我们可以通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等。。。。
4.1操作运行时类中的指定属性
@Test //操作运行时类中的指定属性 重点
public void test2() throws Exception {
Class<Person> clazz = Person.class;
Person p = clazz.getConstructor().newInstance();
Field name = clazz.getDeclaredField("name");
//保证当前属性是可访问的
name.setAccessible(true);
name.set(p,"Tom");
System.out.println(name.get(p));
}
4.2操作运行时类中的指定方法
@Test //操作运行时类中的指定方法 重点
public void test3() throws Exception {
Class<Person> clazz = Person.class;
Person p = clazz.getConstructor().newInstance();
//获取指定某个方法,需要指明 名称 和 形参列表
Method show = clazz.getDeclaredMethod("show", String.class);
show.setAccessible(true);
//invoke()表示调用,括号指明 调用者 和 赋值;invoke有返回值,返回值即方法的返回值
Object chn = show.invoke(p, "CHN");
System.out.println(chn);
//调用static方法
Method showDes = clazz.getDeclaredMethod("showDes");
showDes.setAccessible(true);
showDes.invoke(Person.class);
showDes.invoke(null); //这样也行,因为static用哪个对象调用都一样
}
4.3操作运行时类中的指定构造器
@Test //操作运行时类中的指定构造器
public void test4() throws Exception {
Class<Person> clazz = Person.class;
Constructor<Person> constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Person person = constructor.newInstance("Tom");
System.out.println(person);
}
5.动态代理
5.1代理模式的原理
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
5.2静态代理
静态代理的缺点:
① 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。
② 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
5.3动态代理
动态代理的特点:
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
动态代理需要解决的两个主要问题:
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。(通过Proxy.newProxyInstance()实现)
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
(通过InvocationHandler接口的实现类及其方法invoke() )
interface Human {
String getBelief();
void eat(String food);
}
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃"+food);
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human instance = (Human) ProxyFactory.getProxyInstance(superMan);
String belief = instance.getBelief();
System.out.println(belief);
instance.eat("麻辣烫");
AntaClothFactory antaClothFactory = new AntaClothFactory();
ClothFactory proxyCloth = (ClothFactory) ProxyFactory.getProxyInstance(antaClothFactory);
proxyCloth.produceCloth();
}
}
class ProxyFactory{
//调用此方法。返回代理类对象
public static Object getProxyInstance(Object object) {//被代理类对象object
MyInvocationHandler invocationHandler = new MyInvocationHandler();
invocationHandler.bind(object);
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), invocationHandler);
}
}
class MyInvocationHandler implements InvocationHandler {
private Object obj; //需要使用被代理类的对象赋值
public void bind(Object obj) {
this.obj = obj;
}
//当我们通过代理类的对象,调用方法a,就会自动调用下面方法 invoke()
//将被代理类要执行的方法a就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method即为代理类调用的方法,此方法也作为被代理类对象要调用的方法
Object returnValue = method.invoke(obj, args);//即invoke()的返回值
return returnValue;
}
}
十四、JDK8的其他新特性
1. Lambda表达式
1.1 Lambda表达式的基本语法
举例: (o1,o2) -> Integer.compare(o1,o2);
格式:
-> :lambda操作符 或 箭头操作符
->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表
->右边:lambda体 (其实就是重写的抽象方法的方法体
1.2 Lambda表达式的使用
->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只一个参数,其一对()也可以省略
->右边:lambda体应该使用一对{}包裹;如果lambda体只一条执行语句(可能是return语句,省略这一对{}和return关键字
@Test
public void test1(){
Runnable r1 = () -> System.out.println("hello");
r1.run();
Consumer<String> con = (String s) -> System.out.println(s);
con.accept("hi");
}
@Test
public void test2(){
Consumer<String> con = (s) -> System.out.println(s);
con.accept("呃呃");
}
@Test
public void test3(){
Comparator<Integer> com = (o1, o2) -> o1.compareto(o2);
System.out.println(com.compare(12,23));
}
2.函数式接口
2.1函数式接口的使用说明
如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。
我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
Lambda表达式的本质:作为函数式接口的实例
2.2 Lambda表达式的4个基本的函数式接口
3.方法引用
3.1方法引用的理解
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法。
使用情境:当要传递给Lambda体的操作,已经实现的方法了,可以使用方法引用!
格式:类(或对象) :: 方法名
3.2使用要求
要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同。
当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName。
使用建议:如果给函数式接口提供实例,恰好满足方法引用的使用情境,大家就可以考虑使用方法引用给函数式接口提供实例。如果大家不熟悉方法引用,那么还可以使用lambda表达式。
3.3方法引用的示例
// 情况一:对象 :: 实例方法
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1(){
Consumer<String> con1 = (str) -> System.out.println(str);
con1.accept("北京");
Consumer<String> con2 = System.out::println;
con2.accept("香港");
}
// 情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3(){
Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
System.out.println(com1.compare(23, 45));
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(55, 78));
}
// 情况三:类 :: 实例方法 (难度)
// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareto(t2)
@Test
public void test5(){
Comparator<String> com1 = (s1, s2) -> s1.compareto(s2);
System.out.println(com1.compare("abc", "abd"));
Comparator<String> com2 = String::compareto;
System.out.println(com1.compare("abc", "abe"));
}