1.基本结构
1.1文件名与类名
public class HelloWorld{
public static void main(String[] args){
System.out.println("hello,world!你好,世界!");
}
}
①public class 类名
这个public
只能有一个
②文件名.java
这个文件名要和public class 类名
的类名一样;就如上面代码中类名是HelloWorld
,则该.java
文件的文件名也必须是HelloWorld
1.2main
程序执行的入口
public class HelloWorld{
public static void main(String[] args){
System.out.println("hello,world!你好,世界!");
}
}
class HelloChina{
public static void main(String a[]){
System.out.println("hello,world!你好,中国!");
}
}
①public static void main(String[] args)
是一个可执行的Java程序必须有的,因为它是执行语句的入口
②public static void main(String[] args)
其中的String[] args
可以写成String arg[]
,arg
也可以改成任意可表示数组的符号如a
等;有public
是因为它权限最大,有static
是因为它与类相关,有void
是因为它在栈帧最底部不需要返回值
③一个Java程序里可以有多个main
2.输出与输入
2.1输出
public class PrintTest{
public static void main(String[] args){
System.out.print(); //只输出,不换行
System.out.print(""+"\n"); //先输出后换行
System.out.println(); //先输出后换行
System.out.println("abc123"); //双引号里写字符串
System.out.println(123+4); //整数直接计算
System.out.println(0.1+0.2); //输出不是0.3,而是0.30000000000000004,因为现代编译器基本采用IEEE 754标准,所以不能实现每一个十进制小数对应一个二进制小数
System.out.println("Stu"+"dent"); //+(加号)为连接符
int a=1;
int b=1;
System.out.println(a==b); //==(连等号)为判断是否相等的符号
}
}
2.2输入
import java.util.Scanner; //导入Java的util包里的Scanner类
public class ScanTest{
public static void main(String[] args){
Scanner n=new Scanner(System.in); //创建Scanner类型的对象n,并使n能够获取控制台输入的功能即Scanner(System.in)
String name=n.next(); //创建String类型的对象name,把对象n的next()方法赋值给name,并开始输入name的值
System.out.println("name:"+name); //输出刚刚输入的name
}
}
3.基本数据类型
3.1 byte
{
byte b1=-128;
byte b2=127;
System.out.println(b1+"\n"+b2);
}
①byte类型的范围是-128~127
(即-2^7~2^7-1
)
②1个byte占1个字节(Byte
),8位(bit
)
3.2 short
{
short s1=-32768;
short s2=32767;
System.out.println(s1+"\n"+s2);
}
①short类型的范围是-32768~32767
(即-2^15~2^15-1
)
②1个short占2个字节(Byte
),16位(bit
)
3.3 int
{
int i1=-2147483648;
int i2=2147483647;
System.out.println(i1+"\n"+i2);
}
①int类型的范围是-2147483648~2147483647
(即-2^31~2^31-1
)
②1个int占4个字节(Byte
),32位(bit
)
3.4 long
{
long l1=-9223372036854775808L;
long l2=9223372036854775807L;
System.out.println(l1+"\n"+l2);
}
①long类型的范围是-9223372036854775808~9223372036854775807
(即-2^63~2^63-1
)
②long类型在数字的末尾要加上L或l
③1个long占8个字节(Byte
),64位(bit
)
3.5 float
{
float f1=9.999999F;
float f2=9999999F;
float f3=f2+1;
System.out.println(f1); //9.999999
System.out.println(f2); //9999999.0
System.out.println(f3); //1.0E7,这里的E7指的是10^7即10的7次方
float f4=-9999999F;
float f5=-(f4-1);
System.out.println(f4); //-9999999.0
System.out.println(f5); //-1.0E7
float f6=123123123f; //前7位数不会丢失精度,但从第8位开始就可能会出现精度丢失的情况
float f7=f6+1;
System.out.println("f6=" + f6); //f6=1.2312312E8
System.out.println("f7=" + f7); //f7=1.2312312E8
System.out.println(f6 == f7); //返回true,因为精度丢失了,它们输出一样的结果
}
①float类型的范围比较特殊,正常来说前7位都是正常的,超过7位后会出现E
来表示科学计数法;然后在前8位中有一部分也是正常的,但到了后面的部分会出现精度丢失的现象
②float类型在数字的末尾要加上F或f
③1个float占4个字节(Byte
),32位(bit
)
④Java中默认小数为double类型,所以如果要使用float类型就需要加上F或f
或者强制转换成float类型
3.6 double
double d1=0.0000001d;
System.out.println(d1); //1.0E-7,即1*(10^-7)
①double类型的范围要比float类型的要广很多,能精确到15~16
位,具体有效位数可以自行查找;但是和float类型一样,到了后面的部分也会出现精度丢失的现象
②double类型在数字的末尾要加上D或d
③1个double占8个字节(Byte
),64位(bit
)
3.7 char
{
char a1='a';
System.out.println("a1="+a1); //a1=a
char a2='1';
System.out.println("a2="+a2); //a2=1
char b1='\u0042';
System.out.println("b1="+b1); //b1=B
char b2='\t';
System.out.println("I love"+b2+"you"); //I love you
char b3='\n';
System.out.println(b3+"baby"); //(换行) baby
char c1=1;
System.out.println("c1="+c1); //c1=SOH,SOH(Start Of Headling)是ASCII码的第1个
char c2=48;
System.out.println("c2="+c2); //c2=0,ASCII码的第48个为0
System.out.println("c1+c2="+(c1+c2)); //49,1+48
System.out.println("a2+c1="+(a2+c1)); //50,49+1,a2的'1'为ASCII码的第49个
System.out.println("a2+c2="+(a2+c2)); //97,49+48
}
①char类型只能写入1个字符,字符表示Unicode(万国码)编码表中的每1个符号,每个符号使用单引号(''
)引起来,其中前128个符号和ASCII表相同,且这个字符可以是转义字符,如上面的'\u0042'
表示转义成Unicode的第42个即大写的B,'\t'
表示转义成制表符(即4个空格),'\n'
表示转义成换行符(即换1行)
Unicode转义字符 | 含义 |
---|---|
\u000a | 换行符 \n |
\u000d | 回车符 \r |
\u007b | 大括号左半部 { |
\u007d | 大括号右半部 } |
\u0008 | 退格符 \b |
\u0009 | 制表符 \t |
\u0022 | 双引号半边 " |
\u0027 | 单引号半边 ' |
\u002b | 加号 + |
\u003b | 分号 ; |
\u005c | 反斜杠 \ |
\u0041 ~ \u005a | 字符 A~Z |
\u0061 ~ \u007a | 字符 a~z |
②char类型的变量的值如果不带上单引号(''
),则只能写入数字(可以是1个,也可以是多个),表示直接把写入的变量的值视为ASCII码对应的十进制数字,如上面char c2=48;
意思是将ASCII码的第48个
(即是数字0
)赋值给c2
,所以System.out.println("c2="+c2);
这个的结果是c2=0
;而在System.out.println("a2+c1="+(a2+c1));
中,结果之所以是50
的原因是a2
和c1
在println()
中进行的是ASCII码的值的相加运算,因为a2='1'
,所以'1'
在ASCII码中的值是49
,而c1=1
,1
本来代表的就是ASCII码的值为1
,所以50=49+1
,System.out.println("c1+c2="+(c1+c2));
和System.out.println("a2+c2="+(a2+c2));
同理
③1个char占2个字节(Byte
),16位(bit
),它的取值范围是0~65535
3.8 boolean
boolean isMan=true;
if(isMan){
System.out.println("请进男厕");
}
else {
System.out.println("请进女厕");
}
①1个boolean占1位,有true
和false
这2个值,一个表示真
,一个表示假
,一般用于表示逻辑运算
②boolean类型true
和false
都不谈字节大小,但实际上会被转换成1
和0
,所以占4字节(Byte
)
4.引用数据类型
4.1 String
String str2="";
String str1="HelloChina!"; //String类型变量用""赋值,String类,属于引用数据类型,即是字符串
System.out.println(str1); //HelloChina!
int i2=128;
//String str=str1+i2-3; //报错,因为String类型表示字符串,-表示减法,所以+被视为了加法而非连接
boolean isMan=true;
//String str=isMan+i2+str1; //报错,因为布尔类型isMan开头会+会被视为加法而非连接,是错的,应该把str1放前面
String str=str2+i2; //String类型做连接运算后还是String类型
System.out.println(str); //128
String str3=i2+""; //i2=128被放进""里了得到了str3
System.out.println(str3); //128
int num=Integer.parseInt(str3); //用Integer.parseInt()把str3里的数字取出来
System.out.println(num-1); //127,128-1的结果
String str4=3.2f+"";
System.out.println(str4); //3.2
//int num0=Integer.parseInt(str4); //取不出来str4,因为Integer.parseInt()取的是纯数字的字符串
//System.out.println(num0-1);
System.out.println(3+4+"Hello"); //7Hello,遇上了"Hello"是字符串,所以后面所有只能做连接运算
System.out.println("Hello"+3+4); //Hello34
System.out.println('a'+1+"Hello"); //98Hello,先'a'+1是ASCII运算,再遇上"Hello"做连接运算
System.out.println("Hello"+'a'+1); //Helloa1
①String类型变量用""
赋值
②如果用连接运算的表达式来给String类型变量赋值,则首先不能出现-
,其次boolean的变量不能放在=
后的第一个,然后要么=
后面有String类型的变量,要么有""
5.关键字
5.1 this
this用于解决变量冲突问题(区分实例变量和局部变量),当局部变量和成员变量同名时,用this. XXX来指代本类中的的成员变量private XXX
public class Course{
private String name;
public void setName(String name){
this.name = name;
}
}
5.2 static
static用于修饰成员变量和方法(区分实例对象),一般于public搭配使用,且修饰后属于整个类(非实例对象),会被类的所有对象共享,所以它随着类的加载而加载,也只能被类调用,而且因为共享,如果有值有变动所有的都会一起变动
public class Course{
public static String name;
}
// 测试类
public class test{
public static void main(String[] args){
Course.name = "计算机组成原理";
}
}
5.3 super
在子类中用于访问父类的成员变量、方法、构造函数,因为Java的就近原则,只有自己类中没有的才去访问父类,且子类中与父类同名的属性和方法会被子类自己的覆盖掉,所以说用super.XXX可以用于区分是子类的成员还是父类的成员
//父类
class Animal {
protected String type="动物";
}
//子类
class Dog extends Animal {
public String type="哺乳动物";
public void printType() {
System.out.println("我是 " + type); //我是哺乳动物
System.out.println("我是一只 " + super.type); //我是一只动物
}
}
//主函数
class Main {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.printType();
}
}
5.4 final
用于修饰变量(类属性、对象属性、局部变量、形参)、方法(类方法、对象方法)、类,修饰后不能改变,其也不能被重写、继承等,因为fin了
public class Main {
public static void main(String[] args) {
String a = "xiaomeng2";
final String b = "xiaomeng";
String d = "xiaomeng";
String c = b + 2;
String e = d + 2;
System.out.println((a == c)); //true
System.out.println((a == e)); //false
}
}
5.5 instanceof
①类型检查:为了向下转型/强制转换时,确保对象是特定的子类或者实例
②条件逻辑:根据对象的具体类型执行特定行为
③多态行为:在多态情况下,根据实际对象类型执行特定行为
④格式:if(对象 instanceof 类型)
public class demo{
public static void main(String args[]){
//判断是否符合类型
if (obj instanceof String) {
//然后强制转换
String s = (String) obj;
//然后才能使用
s.stringMethod();
}
if (obj instanceof String s) {
// JDK16之后,如果类型匹配,则可以直接使用s
}
}
}
6.JavaBean
就是封装类,该私有私有,该暴露暴露,为了安全性考虑,满足:
①成员变量全部私有,提供其getter/setter方法
②需要一个无参构造器,有参可选
7.方法重写与方法重载的区别
重写多用于重写接口中的方法,重载多用于写同一个类中要实现的同一类型方法
特征 | 方法重写 (Override) | 方法重载 (Overload) |
---|---|---|
定义位置 | 子类中 | 同一个类中 (或父子类之间,但本质在同一个类) |
目的 | 提供父类方法的特定实现,实现运行时多态 | 提供处理不同类型/数量参数的同名方法,编译时多态 |
参数列表 | 必须完全相同 (类型、顺序、数量) | 必须不同 (类型、顺序、数量至少一项不同) |
返回类型 | 必须相同或协变 (Java 5+,子类重写方法的返回类型可以是父类方法返回类型的子类型) | 可以不同 (与参数列表不同无关) |
访问修饰符 | 不能比父类方法更严格 (可以相同或更宽松) | 没有限制 (可以任意) |
抛出异常 | 可以抛出相同的异常、子类异常或不抛出;不能抛出新的或更宽泛的检查型异常 | 没有限制 (可以抛出不同的异常) |
静态性 | 不能重写静态方法 (只能隐藏) | 可以重载静态方法 |
调用时机 | 运行时决定 (基于对象的实际类型 - 动态绑定) | 编译时决定 (基于引用类型和参数 - 静态绑定) |
@Override 注解 |
强烈建议使用 (编译器检查是否符合重写规则) | 不能使用 (语法错误) |
继承关系 | 必须存在于父子类之间 | 不要求继承关系 (同一个类内即可) |
核心多态性 | 运行时多态 (动态多态) | 编译时多态 (静态多态) |
8.构造器的执行
子类一定要先执行父类或更祖宗的无参构造器!若父类手动给出有参构造器,但没有补无参构造器,子类则会报错
9.静态代码块与代码块执行顺序
在VM中加载*.class*
①静态代码块/静态属性初始化
②普通代码块/普通属性初始化
③类中的构造方法
④继承关系中:父类静态代码块>子类静态代码块>父类普通代码块>父类构造方法>子类普通代码块>子类构造方法
⑤实例方法和静态方法(非静态代码块),不会被VM自动执行,只有被调用时执行
10.多态
一个方法不同实现,其好处:
①用统一的方法调用不同对象的特定行为
②动态扩展功能:子类重写父类方法,做到同一方法,不同实现
11.抽象
①抽象类不能被实例化
②继承抽象类的子类必须实现父类中的抽象方法,否则也必须被定义为抽象类
③有抽象方法一定是抽象类,抽象类中不一定要有抽象方法
④抽象方法不能用final修饰
⑤抽象方法不能被static修饰,因为static属于类,非对象,而抽象方法实现多态,不同对象实现方法不一样
⑥抽象方法不能被private修饰,因为private只能在父类的内部使用,而抽象方法需要在子类中被重写
⑦抽象类不能被实例化,但抽象类有构造器,用于初始化抽象类字段,为子类构造器提供基础
12.接口
①对外提供一组公共行为规范的标准,在Java中是一个抽象类型,非类,是抽象方法的集合,也无法被实例化,但可以被实现
②一个实现接口的类,如果没有实现接口中的所有方法,就必须声明为抽象类
③接口是隐式抽象,其访问修饰符只能是public或default,abstract关键字可以省略
public abstract interface A{}; //√
public interface A{}; //√
④接口中的方法也是隐式抽象,其修饰符只能是public abstract,public和abstract关键字都可以省略
public interface A{
public abstract void method1(); //√
void method2(); //√
}
⑤接口中的变量会被隐式定义为public static final,public、static和final关键字都可以被省略
public interface A{
public static final Integer VAR_1 = 10; //√
Integer VAR_2 = 10; //√
}
⑥JDK8之后,用default修饰可以在接口中添加一个有实现的默认方法,它也可以被子类重写,但如果没有被子类重写,则使用这个有实现的默认方法,默认方法会默认加上public修饰符,且只有使用接口的(实现类)子类对象调用,被多个(实现类)子类共享
public interface MyInterf{
default void defMethod(){
System.out.println("有实现的默认方法");
}
}
⑦JDK8之后,用static修饰可以在接口中添加一个属于接口的可以直接用接口调用的静态方法,且它不能被子类重写
public interface MyInterf{
static void staMethod(){
System.out.println("属于接口的静态方法");
}
}
⑧JDK9之后,用private定义私有方法,以减少多个默认方法的重复逻辑,但因为私有,所以只能接口内自己调用
public interface MyInterf{
default void defMethod1(){
//System.out.println("有实现的默认方法1");
commonMethod();
}
default void defMethod2(){
//System.out.println("有实现的默认方法2");
commonMethod();
}
private void commonMethod(){
System.out.println("通用方法");
}
}
⑨JDK17之后,用sealed密封一个类或者接口,再用permits列出所有允许继承或实现的子类,并用non-sealed解封
//密封继承
public abstract sealed class Animal permits Dog,Cat{
//...
}
//密封实现
public sealed interface IRun permits Bus,Car{
//...
}
//解封
public non-sealed class Bus implements IRun{
//...
}
13.数组
①存放相同类型的元素
②空间连续的,且定长的
③每个空间有自己的编号,起始位置编号为0,即数组的下标
④格式:T[] 数组名 = new T[]
public class demo{
public static void main(String[] args){
double[] b = {1.0,2.0,3.0};
//普通for循环遍历,取的是地址
for(int i = 0;i<b.length;i++){
b[i] *= 10; //b中的值改变了
}
for(int i = b.length -1;i>=0;i--){
System.out.println(b[i]);
}
//增强for循环遍历,从b中取所有值作e,非地址
for(double e : b){
e *= 10; //b中的原始值并没有改变
//System.out.println(e);
}
for(double e : b){
System.out.println(e);
}
}
}
14.ArrayList动态数组
①动态数组,可根据需求自动扩展或缩小
②格式:ArrayList<T> 数组名 = new ArrayList<>()
,T为引用类型
③ArrayList的用法:
public class demo{
public static void main(String[] args){
ArrayList<Integer> a = new ArrayList<>(); //size=0
a.add(1); //插入1进数组a,长度+1,size=1
a.add(10); //插入10进数组a,长度又+1,size=2
//a.get(2); //获取值错误,下标越界,没有为2的下标
a.add(1,20); //插入20在下标为1的地址,长度又+1,size=3,且原本下标为1的10向后进一位变到下标为2的位置
a.set(1,30); //修改下标为1的元素的值为30
a.remove(1); //按下标删除,删掉下标为1的元素20,长度-1,size=2,且原本下标为2的10向前退一位变到下标为1的位置,若想删除删掉对象为1的元素,得进行装箱处理a.remove(Integer.valueOf(1))
ArrayList<String> b = new ArrayList<>(); //size=0
b.add("A"); //添加“A”进数组b,长度+1,size=1
b.add("B"); //添加“B”进数组b,长度又+1,size=2
b.remove("B"); //按对象删除,删掉对象为“B”的元素,长度-1,size=1
}
}
public class demo{
public static void main(String[] args){
ArrayList<Integer> a = new ArrayList<>(); //size=0,这size就是整个数组的长度
a.add(1); //插入1进数组a,长度+1,size=1
a.add(10); //插入10进数组a,长度又+1,size=2
a.addAll(List.of(10,20,30,40,50)); //将这个集合加到a现有集合后面
int k = a.indexOf(10); //查找第一次出现10的下标位置k
List<Integer> a1 = a.subList(k,a.size()); //截取从下标位置k开始到下标位置为整个数组的长度-1(即.size()方法)的数组作为a1
for(int i =0;i<a.size();i++){
if(a.contains(30)){ //判断是否存在元素为30
a.remove(i); //从i=0开始删
}
}
for(int x: a){
System.out.println(x); //10,20,40,50
}
}
}
④ArrayList扩容机制:
-
初始容量默认为10
-
当size超过当前容量时扩容
-
默认增长因子为1.5倍,所以每次扩容为原来的1.5倍
-
扩容策略:
- 创建新数组:扩容1.5倍
- 复制元素:旧的复制到新的
- 更新引用:内部数组更新为新数组,若判断已经达到最大值,将不再扩容
15.二维数组(不常用)
格式:T[][] 数组名 = new T[R][C]
public class demo{
public static void main(String args[]){
int[ ][ ] array1 = new int[2][3];
int[ ][ ] array2 = new int[ ][ ]{{1,2,3},{4,5,6}};
int[ ][ ] array3 ={{1,2,3},{4,5,6}};
int[ ][ ] array4 = new int[2][ ]; //列可以不写,由元素实际长度决定
int[] a = {10,11,12};
int[] b = {20,21,22,23};
array4[0] = a;
array4[1] = b;
array1[0] = a;
array1[1] = b; //√
for(int i = 0;i<array4.length;i++){
for(int j = 0;j<array4.length;j++){ //i:0 j:0->3----i:2 j:3
System.out.print(array4[i][j]+"\t");
}
}
System.out.println();
}
}
16.Arrays类(操作数组)
所有方法都是静态方法,方便使用方法调用数组,用法:
Arrays.toString(Object[] a,Object key)
将数组转换成String类型输出Arrays.deepToString(Object[][] a,Object key)
打印多维数组Arrays.sort(Object[] a)
对指定数组元素升序排列Arrays.equals(long[] a1,long[] a2)
判断指定的两个long型数组是否相等Arrays.fill(int[] a,int val)
将指定的int值分配给指定int型数组指定范围内的每一个元素Arrays.binarySearch(Object[] a,Object key)
用二分查找在给定数组中搜寻查找对象,但调用前数组需排序好
17.List集合
-
List接口特点:
- 有序性:按照插入的顺序存储
- 允许重复:允许存储重复的元素
- 索引访问:提供了用索引访问的方法,如get(int index)
-
List接口常用实现类:
-
ArrayList类:随机访问快,插入、删除慢
-
LinkedList类:插入、删除快,随机访问慢(链表)
-
Vector类:线程安全,同步开销,性能低
-
Stack类:继承Vector类,多些栈操作(后进先出)的方法
-
List是Collection接口的子接口,拥有Collection所有方法外,还有一些对索引操作的方法
void add(int index, E element);
将元素element插入到List集合的index处;boolean addAll(int index, Collection<? extends E> c);
将集合c所有的元素都插入到List集合的index起始处;E remove(int index);
移除并返回index处的元素;int indexOf(Object o);
返回对象o在List集合中第一次出现的位置索引;int lastIndexOf(Object o);
返回对象o在List集合中最后一次出现的位置索引;E set(int index, E element);
将index索引处的元素替换为新的element对象,并返回被替换的旧元素
;E get(int index);
返回集合index索引处的对象;List<E> subList(int fromIndex, int toIndex);
返回从索引fromIndex(包含)到索引toIndex(不包含)所有元素组成的子集合;void sort(Comparator<? super E> c)
根据Comparator参数对List集合元素进行排序;void replaceAll(UnaryOperator<E> operator)
根据operator指定的计算规则重新设置集合的所有元素。ListIterator<E> listIterator();
返回一个ListIterator对象,该接口继承了Iterator接口,在Iterator接口基础上增加了以下方法,具有向前迭代功能且可以增加元素:
bookean hasPrevious()
返回迭代器关联的集合是否还有上一个元素;
E previous();
返回迭代器上一个元素;
void add(E e);
在指定位置插入元素;
18.Set集合
-
set接口特点:
- 不允许重复元素(去重复,核心是*equals()*方法)
- equals(Object obj):这是Object类的方法,默认实现是比较两个对象的内存地址,但可以被重写
- ==运算符:比较两个对象的内存地址是否相同,如果两个引用指向同一个对象实例,则true
- hashCode():该方法返回对象的哈希码(整数值),用于快速比较,如果两个对象相等(用equals),则应该有相同的哈希码
- 大多数实现不保证元素顺序(除了LinkedHashSet和TreeSet)
- 不允许重复元素(去重复,核心是*equals()*方法)
-
set实现类:
-
HashSet,基于哈希表的Set实现,高性能,快速查找、添加、删除
public class demo{ public static void main(String args[]){ Set<String> hashSet = new HashSet<>(); hashSet.add("apple"); hashSet.add("banana"); hashSet.add("null"); //允许一个null元素 System.out.print(hashSet); //不保证顺序,所以不能用普通for循环靠索引访问 //增强for循环取值 for(String s; hashSet){ System.out.print(s); } }
-
LinkedHashSet,继承HashSet,有双向链表来记录插入顺序
public class demo{ public static void main(String args[]){ LinkedHashSet<String> hashSet = new LinkedHashSet<>(); hashSet.add("apple"); hashSet.add("apple"); hashSet.add("banana"); hashSet.add("null"); //允许一个null元素 System.out.print(hashSet); //有序且去重 //增强for循环取值 for(String s; hashSet){ System.out.print(s); } }
-
TreeSet,实现SortedSet接口,底层使用红黑树结构,保证对数级别的性能
public class demo{ public static void main(String args[]){ Set<String> hashSet = new TreeSet<>(); hashSet.add("a"); hashSet.add("c"); hashSet.add("c"); hashSet.add("b"); System.out.print(hashSet); //按自然数排序 //增强for循环取值 for(String s; hashSet){ System.out.print(s); } }
19.Map集合(标识作用)
- Map顶层接口=Entry接口+Map自身接口方法
- Entry接口={Key: Value}
- Map其子接口:
- HashMap:有个LinkedHashMap子类,记录了插入顺序,但效率比HashMap低
- HashTable:同步的,线程安全的,且不允许null作为Key和Value
- TreeMap:基于红黑树,自然顺序排序
- Map其常用子集合:
- keySet:遍历所有Key
- values:遍历所有Value
- entrySet:遍历所有K-V
public class demo{ public static void main(String args[]){ Map<String,Integer> map = new HashMap<>(); //Key为字符串,Value为整型 map.put("red",1); map.put("blue",2); map.put("green",3); System.out.println(map); //无序输出 int x = map.get("blue"); //根据Key查找 System.out.println(X); //输出2 //keySet遍历所有Key,性能较差,但是方便 for(String key : map.keySet()){ //从所有Key中取Key System.out.println(key + ":" + map.get(key)); //得到所有Key后,再通过Key取得所有Value } //entrySet遍历所有K-V,效率最高,推荐使用 for(Map.Entry<String,Integer> entry : map.entrySet()){ //从所有K-V中取K-V System.out.println(entry.getKey() + ":" + entry.getValue()); } //查单词重复次数 String[] words = {"the","day","is","sunny","the","the","sunny","is"}; //单词数组 Map<String,Integer> times = new HashMap<>(); for(String word : words){ //从单词数组中遍历所有单词作为word也是Key if(times.containsKey(word)){ //判断word/Key是否出现 int count = times.get(word) + 1; times.put(word,count); //如果单词已经出现过一次,则Value+1 }else { times.put(word,1); //单词没有出现过,就Value=1 } } System.out.println(times); } }
20.集合排序
-
Collections.sort()
方法:- Java集合框架的静态方法,属于接口,不能用类调用
- 只传入待排序的集合,则默认自然排序(升序)
- 传入待排序的集合和比较器,则按比较器规则排序,可用于对象排序(见 3.Comparator
接口 )
public class demo{ public static void main(String args[]){ List<Integer> numbers = new ArrayList<>(); numbers.add(3); numbers.add(1); numbers.add(4); numbers.add(2); numbers.add(5); Collections.reverse(numbers); //反转 System.out.println(numbers); //5,2,4,1,3 Collections.sort(numbers); //升序 System.out.println(numbers); //1,2,3,4,5 } }
-
Comparable
接口 - String等八种封装类已经实现了Comparable接口,所以
Collections.sort()
可以直接对基本数据类型数据升序 - 但要对象排序,就得重写Comparable接口的
compareTo(T o)
方法
@Data public class Student implements Comparable<Student>{ private Integer sno; private String sname; private Double score; public Student(Integer sno,String sname,Double score){ this.sno = sno; this.sname = sname; this.score = score; } @Override public String toString(){ return "Student{" + "sno" + sno + ", sname='" + sname + '\'' + ", score=" + score + '}'; } //重写compareTo(T o)方法 @Override public int compareTo(Student o){ if(this.sno > o.sno){ return 1; //大于0 }else if(this.sno < o.sno){ return -1; //小于0 } return 0; //等于0 } /* 我们添加了4个Student对象,按照学号(sno)进行比较,添加顺序是:1002, 1003, 1004, 1001 插入过程: - 首先添加1002(作为根节点) - 添加1003:与1002比较,1003>1002,所以放在1002的右子树 - 添加1004:先与1002比较(1004>1002),转向右子树(1003),再与1003比较(1004>1003),所以放在1003的右子树 - 添加1001:与1002比较,1001<1002,所以放在1002的左子树 最终,树的结构如下: 1002 / \ 1001 1003 \ 1004 但是,TreeSet在遍历时是按照二叉搜索树的中序遍历(左-根-右),所以遍历顺序是:1001 -> 1002 -> 1003 -> 1004。 因此,输出顺序为: Student{sno=1001, sname='lucy', score=74.0} Student{sno=1002, sname='jack', score=88.0} Student{sno=1003, sname='rose', score=65.5} Student{sno=1004, sname='lily', score=89.0} 注意:虽然我们添加的顺序是1002、1003、1004、1001,但输出时却是按学号升序排列,这就是TreeSet自动排序的效果。 另外,如果两个对象的compareTo方法返回0,TreeSet会认为它们是相等的,不会添加新对象。例如,如果试图添加一个学号相同的Student对象,它将被视为重复元素,不会被加入集合。 关键特性 1. 动态排序:每次调用add()都会触发compareTo()比较,立即调整树结构保持有序 2. 去重机制:当compareTo()返回0时,视为逻辑相等(即使对象地址不同),拒绝插入 3. 性能保障:红黑树保证插入/查找/删除的时间复杂度为O(log n),远优于线性排序。 */ }
public class demo{ public static void main(String args[]){ Set<Student> students = new TreeSet<>(); //将Student对象存放在Set集合 // List<Student> students = new ArrayList<>(); //错误,因为ArrayList没有TreeSet里的自带排序功能,重写Comparable接口的compareTo(T o)方法后,还需要调用Collections.sort(students)方法才能实现排序 students.add(new Student(1002,"jack",88.0)); students.add(new Student(1003,"rose",65.5)); students.add(new Student(1004,"lily",89.0)); students.add(new Student(1001,"lucy",74.0)); //展示排序后的stu for(Student stu ; students){ System.out.println(stu.toString()); } } }
- String等八种封装类已经实现了Comparable接口,所以
-
Comparator
接口 - 需要创建单独排序类(比较器),然后重写
compare(T o1,T o2)
方法或用匿名内部类实现
//商品类 @Data public class Sku{ private String name; private Integer num; private Double price; @Override public String toString(){ return "Sku{" + "name" + name + ", num='" + num + '\'' + ", price=" + price + '}'; } }
//比较器SkuComparator public class SkuComparator implements Comparator<Sku>{ public final static String ASC = "asc"; public final static String DEC = "dec"; private String sortField; //排序字段 private String sort; //升序或降序 //构造器1:只有字段,无选择 public SkuComparator(String sortField){ this.sortField = sortField; this.sort = ASC; //默认升序 } //构造器2:自主选择升序类型 public SkuComparator(String sortField,String sort){ this.sortField = sortField; this.sort = sort; } //重写compare(T o1,T o2)方法 @Override public int compare(Sku o1,K,Sku o2){ switch(sortField){ case "name": if(this.sort.equals(ASC)){ return o1.getName().compareTo(o2.getName()); }else{ return o2.getName().compareTo(o1.getName()); } case "num": if(this.sort.equals(ASC)){ return o1.getNum()-o2.getNum(); }else{ return o2.getNum()-o1.getNum(); } case "price": if(this.sort.equals(ASC)){ return (int)(o1.getPrice()-o2.getPrice()); }else{ return (int)(o2.getPrice()-o1.getPrice()); } default: return 0; } } }
//测试 public class demo{ public static void main(String args[]){ //商品数量升序 Set<Sku> skus = new TreeSet<>(new SkuComparator("num")); skus.add(new Sku("A",100,200.0)); skus.add(new Sku("C",50,1200.0)); skus.add(new Sku("D",150,120.0)); skus.add(new Sku("B",200,1000.0)); System.out.println(skus); //价格降序 Set<Sku> skus1 = new TreeSet<>(new SkuComparator("price",SkuComparator.DEC)); skus1.add(new Sku("A",100,200.0)); skus1.add(new Sku("C",50,1200.0)); skus1.add(new Sku("D",150,120.0)); skus1.add(new Sku("B",200,1000.0)); System.out.println(skus1); //List集合价格降序 SkuComparator comparator2 = new SkuComparator("price",SkuComparator.DEC); List<Sku> skus2 = new ArrayList<>(); skus2.add(new Sku("A",100,200.0)); skus2.add(new Sku("C",50,1200.0)); skus2.add(new Sku("D",150,120.0)); skus2.add(new Sku("B",200,1000.0)); Collections.sort(skus2,comparator2); //必须加入比较器comparator2! System.out.println(skus2); } }
//匿名内部类,无比较器 public class demo{ public static void main(String args[]){ List<Sku> skus = new ArrayList<>(); skus.add(new Sku("A",100,200.0)); skus.add(new Sku("C",50,1200.0)); skus.add(new Sku("D",150,120.0)); skus.add(new Sku("B",200,1000.0)); //直接new一个Comparator<T>接口,重写compare(T o1,T o2)方法 Collections.sort(skus,new Comparator<Sku>(){ @Override public int compare(Sku o1,K,Sku o2){ return (int)(o1.getPrice()-o2.getPrice()); } }); System.out.println(skus); } }
- 需要创建单独排序类(比较器),然后重写
21.内部类
类的五大成分(成员变量、构造器、方法、代码块、内部类)之一
-
成员内部类:定义在外部类成员位上,与成员变量/方法平级,调用成员内部类的对象时要:
Train train = new Train(); //创建类实例对象train Train.Seat seat = train.getInnerInstance(); //把实例对象train调用的getInnerInstance()方法赋值给成员内部类Seat创建的seat seat.occpuy(); //用seat调用成员内部类Seat方法occpuy()
-
静态内部类:
- 定义在外部类成员位上,被static修饰,属于整个外部类,所以没有外部类实例的this引用,它只能访问外部类的静态成员
- 若外部类的成员和成员内部类的成员同名,则遵循就近原则,在成员内部类中访问的是自己成员内部类中的成员,如想访问外部类的同名成员,则用外部类名.静态成员名
- 若要在其他类中创建静态内部类的实例,则要保证这个静态内部类的在其他类中是可见的
- 静态内部类在编译后生成一个独立的类文件,其命名通常为外部类名$静态内部类名.class
-
局部内部类:
- 定义在一个局部位置,如方法、方法的参数、构造器或代码块内部,类似方法的局部变量,作用域仅限自己的代码块中
- 可以直接访问外部类的所有成员,也可以通过this引用访问外部类的成员
public class OuterClass{ private String outerField = "outer"; public void fun(){ //局部内部类 class LocalInnerClass{ public void foo(){ System.out.println(outerField); //直接访问 System.out.println(OuterClass.this.outerField); //通过外部类的this引用访问 } } } }
-
匿名内部类:没有名字的内部类(见 3.Comparator
接口 )
22.枚举类
定义一组固定命名的常量,其构造器是私有的,不能创建对象,每个常量就是一个实例
//创建 public enum ColorE{ RED("红色",1), BLUE("蓝色",2), YELLOW("黄色",3); private String desc; private Integer value; //自定义构造器 ColorE(String desc,Integer value){ this.desc = desc; this.value = value; } } //返回名称 System.out.println(ColorE.RED.name()); //返回枚举对象的序号 System.out.println(ColorE.RED.ordinal()); //输出0 //返回名称对应的枚举对象 System.out.println(ColorE.valueOf("RED")); //输出RED
23.泛型
- 上界约束:用extends,表示该泛型必须是其子类或实现了某个接口,没指定则默认约束是Object
<T extends Number> //类型参数T必须是Number类型或其子类
- 下界约束:用super,表示该泛型必须是其超类或其接口的实现类
<T super Integer> //类型参数T必须是Integer类型或者其父类
24.函数式接口与Lambda表达式
-
函数式接口:只有一个抽象方法,可用于匿名;只是一个形式,只要有返回值即可
-
Lambda表达式:
- 只有函数式接口的变量或者函数式接口才能赋值为Lambda表达式
- 形参类型可以全部不写
- 只有一个形参,类型可不写,
()
也可以不写 - 若Lambda表达式方法体只有一行代码,则可不写
{}
和;
,若这行是return
语句,则也不写return
//函数式接口 public Double compute(Double x,Double y){ return x*y; } //Lambda表达式 (x,y)->x*y //Lambda方法引用 Function<String,Integer> strToInt = s -> Integer.parseInt(s); //方法引用运算符:: Function<String,Integer> strToInt1 = Integer::parseInt; //引用String的无参构造器 Function<String,Integer> strObj = (s) -> new String(s); //用方法引用运算符 Function<String,Integer> strObj1 = String::new;
25.内存管理机制
25.1 Java内存区域
- 方法区(元空间,一般放在堆里)
- 功能:用于存储类的结构信息
- 类的元数据(对数据的描述:全限定类名、父类名、接口列表等):结构、方法、字段等
- 常量池:编译时生成的字面常量、符号引用
- 静态变量:因为静态变量属于类,所以随着类加载同时加载,所有实例共享
- JIT(即时编译器)编译后的机器代码
- 共享性:全局共享,所有线程都能访问
- 生命周期:JVM启动时创建,JVM销毁时销毁
- 避免内存溢出:使用本地内存,非JVM堆内存,避免了类的元数据过多而溢出
- 功能:用于存储类的结构信息
- 堆(Heap)
- 功能:Java最大的内存区域,用于存放所有对象实例(new)和引用数据类型(类、接口、数组、集合)
- 共享性:所有线程共享,需要同步机制管理堆内存的访问
- 生命周期:不会被释放,当对象不再被引用时,GC(垃圾收集器)会定期回收
- 内存泄漏(Memory Leak):内存没有正确释放,导致GC无法回收
- 内存溢出:
OutOfMemoryError
错误,因为内存泄漏或内存设置太小导致的,用-Xms
和-Xmx
检查最小/最大堆空间
- 栈(Stack)
- 功能:每个线程创建一个虚拟机栈,用于存储局部变量表、操作数栈、动态链接、方法出口(返回值)等信息
- 独立性:每个线程的栈是隔离的
- 生命周期:线程创建时创建,线程结束时销毁(自动释放)
- 过程:一个方法创建一个栈帧,先进压在底部,当前操作数栈计算后,将得到的帧数据压入发起调用的操作数栈(有return的情况),若遇到异常则查表,表中没有处理方法则结束当前方法,抛出异常
- 调整栈:
Java -Xss 1m demo_1
(demo_1设置为1MB栈大小)
- 程序计数器(Program Counter Register)
- 功能:用于记录程序下一条要执行的字节码行号
- 独立性:每个线程的PCR互不干扰
- 本地方法栈
- 功能:用于执行本地方法(非Java语言),通过JNI(Java Native Interface)与Java代码交互
25.2 String类的一些方法
-
String类
一个String对象最多存储2^32-1个字节(占4GB)的文本内容,它的内容是定长的,不可改变且能共享
//String源码 public final class String{ private final byte[] value; private final byte coder; }
-
创建字符串
//常用1 String str = "Hello"; //常用2 String str = new String("Hello"); //字符数组构造 char[] array = {'a','b','c'}; String str = new String(array); //提取数组中一部分创建 char[] array = {'a','b','c','d','e'}; String str = new String(array,1,3); //bcd
-
比较字符串
String str1 = "hello"; String str2 = "Hello"; String str3 String str = new String("Hello");= "hell"; String str4 = new String("hello"); System.out.println(str1 == str4); //false,new后开辟新的地址 System.out.println(str1.equals(str4)); //true,String类重写了equals方法,内容相同即相同 System.out.println(str1.compareTo(str3)); //1,compareTo方法按字典依次比较,如果字符不等则返回两个字符的大小差值,如果前几个都相等,只是长度不同,则返回两个字符串的长度差值 System.out.println(str1.compareToIgnoreCase(str2)); //0,compareTo方法
-
查找字符串
charAt(int index)
:返回index位置的字符,若index为负或越界,则抛出异常IndexOutOfBoundsException
indexOf(String str)
:返回str第一次出现的位置,没有则返回-1contains(String str)
:判断一个字符串是否包含另一个字符串startsWith(String str)
:判断前缀endsWith(String str)
:判断后缀
-
拆分与连接字符串
split()
方法:将字符串以指定格式全部拆分成字符串数组str1.concat(str2)
方法:将两个字符串连成一个字符串String.join("","",...)
方法:静态,将多个字符串以指定格式连成一个字符串+
:连接两个字符串,并传入新的地址
-
截取字符串
String str = "hello word"; System.out.println(str.substring(2)); //llo word System.out.println(str.substring(2,8)); //llo wo
-
字符串去左右空格,留中间空格
String str = " hello word "; System.out.println(str.trim()); //hello word
-
格式化字符串:
format
方法 -
可变长度字符串类
-
StingBuilder
类:线程不安全,效率快,适用单线程 -
StringBuffer
类:线程安全,效率低,适用多线程StringBuilder sb = new StirngBuilder("Hello"); String s = "Hello"; sb.append("WorldWaWa"); //HelloWorldWaWa,在Hello后面追加WorldWawa,原地址没变,还有预留位置 sb.insert(0,"Ni:"); //Ni:HelloWorldWaWa,在下标为0的位置插入Ni:,原地址没变 sb.insert(8," "); //Ni:Hello WorldWaWa,在下标为8-1的位置插入空格,原地址没变 sb.delete(14,sb.length()); //Ni:Hello World,删除从下标14到sb.length()-1之间的字符 System.out.println(sb.toString()); //"Ni:Hello World",用toString()转换成String类型
-
25.3 字符串常量池
-
字符串常量池属于常量池,放在方法区里,方法区一般又放在堆里,所以字符串常量是共享的,又因为String源码里value是用final修饰的,所以字符串常量是不可变的
-
当常量池中已经存在一个与其值相同的字符串,则不会开辟空间来存储它,而是直接将地址赋值给这个新的字符串
String s1 = "abc"; String s2 = "abc"; System.out.println(s1==s2); //true,因为s1和s2都是常量池中的地址
- 如果是用new关键字创建的字符串,会分别在常量池和堆中创建对象,即常量池一个,堆一个;若常量池中已经存在,则只在堆里创建
String s3 = new String("abc"); String s4 = new String("abc"); System.out.println(s3==s4); //false,因为s3和s4都是堆里的地址,虽然常量池中地址相同,但堆中不一样
- String对象调用
intern()
方法时则是返回该对象常量池中的地址
String s5 = new String("qwe"); String s6 = "qwe"; String s7 = s5.intern(); System.out.println(s5==s6); //false,因为s5是堆里的地址,s6是常量池中的地址 System.out.println(s6==s7); //true,因为s6和s7都是常量池中的地址
25.4 值传递与引用传递
- 值传递:形参是基本数据类型,用实际参数的值初始化自己的存储单元,是和实际参数不同的栈,不影响实际参数
- 引用传递:形参是引用数据类型,传的是地址,指向同一个地址空间,会影响实际参数
25.5 可变参数
public class demo{ public static void main(String args[]){ int m = add(1,2); int n = add(1,2,3); int k = add(1,2,3,4,5); System.out.println(String.format("m=%d,n=%d,k=%d",m,n,k)); } static int add(int x,int... a){ int sum = 0; for(int i=0;i<a.length;i++){ sum += a[i]; } return sum; } }
注:①可变参数在方法内部被当作数组来处理
②可变参数的实参可以是数组
③一个方法只能有一个可变参数
④可变参数可以和其他参数一起使用,但可变参数必须在末尾
25.6 GC机制
-
新生代:堆内存中,存放新创建的对象
-
Young GC
-
标记-清除算法:①所有对象放入Eden区,若Eden满了则标记需要的对象,放入Survivor区1,清空Eden区==>②Eden区又满了则标记需要的对象,将该对象和Survivor区1中的对象放入Survivor区2中,清空Eden区和Survivor区1==>③以此重复交换Survivor区1和Survivor区2
-
-
老年代:堆内存中,存放新时代中多次存活(默认15次,可修改上限)的对象
-
Full GC:
①剩余存活的对象>新生代的Survivor+老年代
②空间分配担保失败
③执行
System.gc()
时,但不一定立即触发
-
26.递归
- 递归简洁,但性能消耗大,效率低,要求高性能情况下应用循环迭代
- 递归两步骤
- 找递归终止条件
- 把一个问题拆分成多个子问题,再调用自身处理子问题
public class demo{ static int f(int n){ //终止条件 if(n==1) return 1; //调用自己 return n*f(n-1); } public static void main(String args[]){ int x = f(5); System.out.println(x); } }
//斐波那契数列 //循环 public class demo{ public static void main(String args[]){ int f1 = 1,f2 = 1; System.out.println(f1); System.out.println(f2); for(int i =2;i<20;i++){ int f3 = f1 + f2; System.out.println(f3); f1 = f2; f2 = f3; } } } //数组 public class demo{ public static void main(String args[]){ int[] f = new int[20]; f[0] = 1; f[1] = 1; for(int i =2;i<20;i++){ f[i] = f[i-1]+f[i-2]; System.out.println(f[i]); } } } //递归 public class demo{ int f(int n){ //终止条件 if(n==1 || n==2) return 1; //调用自己 return f(n-1)+f(n-2); } public static void main(String args[]){ int x = f(5); System.out.println(x); } }
27.反射
-
机制:在运行状态下,动态获取任意一个类的属性、方法或任意一个对象的方法和属性
-
原理:①在程序中创建对象(
new
)==>②JVM到本地磁盘找需要加载的类(Xxx.class
)==>③通过类加载器classLoader加载到内存中,创建类对象,一个类有且仅有一个class对象,class对象中包含了类的信息==>④在内存中创建对象空间 -
类:
①全限定类名:
包名.类名
②Class类的加载:
- 通过类名加载:
Class<?> xX = Class.forname(全限定类名)
- 通过字节码文件加载(推荐使用):
Class<?> xX = 类名.class
- 通过类实例加载:
Class<?> xX = 类实例名.getClass()
③Class类的常用方法:
-
创建类的实例(无参构造器,已过时):
newInstance()
-
获得类的加载器:
getClassLoader()
-
获取类的包:
getPackage()
-
获取类的名字:
getSimpleName()
-
获取当前类父类的名字:
getSuperclass()
-
获取当前类实现类或者接口:
getInterfaces()
-
获取所有共有的属性对象:
getFields()
-
获取某个共有的属性对象:
getField(String name)
-
获取所有属性对象(包括私有):
getDeclaredFields()
-
获取某个属性对象(包括私有):
getDeclaredField(String name)
-
获取所有共有的方法(包括父类):
getMethods()
-
按名称和参数类型获取某个共有的方法:
getMethod()
-
获取本类声明的所有方法(包括私有):
getDeclaredMethods()
-
按名称和参数类型获取本类声明的某个方法(包括私有):
getDeclaredMethod()
-
获取该类的所有的公有构造器:
getConstructors()
-
获取该类中与类型参数匹配的公有构造器:
getConstructor(Class... <?> parameterTypes)
-
获取该类的所有构造器:
getDeclaredConstructors
-
获取该类中与类型参数匹配的构造器(包括私有):
getDeclaredConstructor(Class... <?> parameterTypes)
public class demo{ public static void main(String args[]) throws Exception{ //1.加载Student类 Class<?> clazz = Student.class //2.创建类实例 Object o = clazz.getDeclaredConstructor(String.class,String.class) .newInstance("1001","李四"); //3.调用方法 System.out.println(o.toString()); } }
④Method类:反射中的一个内部类,用于表示类中的方法,通过Method类动态访问和修改类的方法
- 调用方法:
invoke(Object obj,Object... args)
public class demo{ public static void main(String args[]) throws Exception{ //1.加载Student类 Class<?> clazz = Student.class //2.调用无参构造器,创建Student类实例 Object student = clazz.getDeclaredConstructor().newInstance(); //3.已知方法名,获取setId、setName、concat方法 Method setId = clazz.getDeclaredMethod("setId",String.class); Method setName = clazz.getDeclaredMethod("setName",String.class); Method concat = clazz.getDeclaredMethod("concat",Long.class,String.class); //4.实例对象执行方法 setId.invoke(student,"1001"); setId.invoke(student,"李四"); String stuinfo = (String) concat.invoke(student,1002L,"李四"); //5.输出 System.out.println(student.toString()); System.out.println(stuinfo); } }
④Field类:反射中的一个内部类,用于表示类中的字段,通过Field类动态访问和修改类的字段
public class demo{ public static void main(String args[]) throws Exception{ //1.加载Student类 Class<?> clazz = Student.class //2.调用无参构造器,创建Student类实例 Object student = clazz.getDeclaredConstructor().newInstance(); //3.已知属性名,获取id、name属性 Field id = clazz.getDeclaredField("id"); Field name = clazz.getDeclaredField("name"); //4.给属性赋值 id.setAccessible(true); //解除属性的保护 id.set(student,"1001"); name.setAccessible(true); //解除属性的保护 name.set(student,"李四"); //5.输出 System.out.println(id.get(student)+"\t"+name.get(student)); } }
- 通过类名加载:
-
用法:
①创建
test.properties
,在里面只写:id:1001 name:aaa
②通过反射动态获取实例对象属性:
public class demo{ public static void main(String args[]) throws Exception{ //1.加载Student类 Class<?> clazz = Student.class //创建Student类的实例 Object student = clazz.getDeclaredConstructor().newInstance(); //2.读取文件内容 File file = new File("E:\\java\\demo\\src\\main\\java\\com\\text\\test.properties"); FileInputStream in = new FileInputStream(file); int c; StringBuilder sb = new StringBuilder(); while ((c=in.read()) != -1){ sb.append((char)c); } System.out.println(sb.toString()); //3.分割文件内容 String[] lines = sb.toString().split("\r\n"); //将读取出的文件内容按回车、换行分割放入lines里 for(String line : lines){ String[] kv = line.split(":"); //从lines里读取每一个line按“:”进行分割再放入kv里 // System.out.println(kv[0]+"--"+kv[1]); //每次按“:”分割后的第一个和第二个之间用“--”连接 Field field = clazz.getDeclaredField(kv[0]); //根据属性名kv[0]的内容来获取属性field field.setAccessible(true); //解除属性的保护 field.set(student,kv[1]); //给实例student的属性field赋值kv[1] } System.out.println(student.toString()); } }
通过反射动态获取实例对象方法:
public class demo{ public static void main(String args[]) throws Exception{ //1.加载Student类 Class<?> clazz = Student.class //创建Student类的实例 Object student = clazz.getDeclaredConstructor().newInstance(); //2.读取文件内容 File file = new File("E:\\java\\demo\\src\\main\\java\\com\\text\\test.properties"); FileInputStream in = new FileInputStream(file); int c; StringBuilder sb = new StringBuilder(); while ((c=in.read()) != -1){ sb.append((char)c); } System.out.println(sb.toString()); //3.分割文件内容 String[] lines = sb.toString().split("\r\n"); //将读取出的文件内容按回车、换行分割放入lines里 for(String line : lines){ String[] kv = line.split(":"); //从lines里读取每一个line按“:”进行分割再放入kv里 // System.out.println(kv[0]+"--"+kv[1]); //每次按“:”分割后的第一个和第二个之间用“--”连接 // Field field = clazz.getDeclaredField(kv[0]); //根据属性名kv[0]的内容来获取属性field // field.setAccessible(true); //解除属性的保护 // field.set(student,kv[1]); //给实例student的属性field赋值kv[1] String fieldName = kv[0].substring(0,1).toUpperCase()+kv[0].substring(1); //将属性名首字母大写并连接首字母后面的字母 Method method = clazz.getDeclareMethod("set"+fieldName,String.class); method.invoke(student,kv[1]); //执行实例student里的setfieldName方法并赋值kv[1] } System.out.println(student.toString()); } }
28.注解
-
注解/元数据,是JDK1.5之后的一个特性,与类、接口、枚举在同一个层次,且可以声明在包、类、字段、方法、局部变量、方法参数等前面,对这些元素进行说明
-
作用:
①编写文档:用代码里标识的元数据生成文档
②代码分析:用代码里标识的元数据对代码进行分析
③编译检查:用代码里标识的元数据让编译器实现编译检查
-
自定义注解:
①元注解控制注解的行为:
@Target(ElementType.METHOD) //注解只标记在方法上 /* 作用域(ElementType) 标记位置 TYPE 类、接口、枚举 METHOD 方法 FIELD 字段(包括枚举类常量) PARAMETER 方法参数 CONSTRUCTOR 构造器 LOCAL_VARIABLE 局部变量(编译后不保留) ANNOTATION_TYPE 其他注解 PACKAGE 包声明(需在package-info,java) */ @Retention(RetentionPolicy.RUNTIME) //运行时保留 @Documented //包含在Javadoc中
②注解体:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation{ //定义属性:类比接口中的方法需要实现,该属性也需要赋值;属性类型只能是基本数据类型、枚举、String、Class、其他注解或者它们的数组;若整个注解只有一个属性,则赋值时可省略其属性名 // String value(); //必须赋值的属性 String value() default ""; int priority() default 1; //带默认值的属性 }
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Logger{ String value() default "log"; String level() default "INFO"; }
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ExceptionHandler{ String value() default "error"; String message() default "内部错误"; }
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface NotNull{ String message() default "字段不能为空"; }
③注解体的实现(通过反射):
public class User{ @NotNull(message = "用户名不能为空") private String username; @NotNull(message = "昵称不能为空") private String nickName; //空值检查,@NotNull注解的实现 @ExceptionHandler(value = "warring",message = "数据检查异常") public void validate() throws Exception{ //遍历所有字段 for(Field field : this.getClass().getDeclaredField()){ if(field.isAnnotationPresent(NotNull.class)){ //判断字段上是否有@NotNull注解,若有则进行空值检查 field.setAccessible(true); //判断字段是否为空值 if(field.get(this) == null){ NotNull notNull = field.getAnnotation(NotNull.class); throw new Exception(notNull.message()); } } } } }
29.Stream流
-
Stream是处理集合的关键抽象概念,能执行复杂的查找(替代遍历)、过滤、映射数据等,其提供的API能对集合数据操作,类似SQL查询数据库
-
特点:
- 不是数据结构,不会保存数据
- 不会修改原来的数据,而是将数据保存到另一个对象
- 惰性求值(懒加载),只对操作进行记录,不会立即执行,要等到执行终止操作时才会进行实际的计算
-
创建流
①用Collection下的
stream()
和parallelStream()
方法将集合转换成流List<String> list = new ArrayList(); Stream<String> stream = list.stream(); //获取一个顺序流 Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
②用Arrays中的
stream()
方法,将数组转换成流Integer[] nums = new Integer[10]; Stream<Integer> stream = Arrays.stream(nums);
③用Stream中的静态方法
of()
、iterate()
、generate()
创建流Stream<Integer> stream1 = Stream.of(1,2,3,4,5,6); //将of()里的内容转换成流对象 Stream<Integer> stream2 = Stream.iterate(0,(x)->x+2).limit(6); //从0开始,每个元素+2迭代,截取6个元素 stream2.forEach(System.out::println); //遍历打印stream2中的元素0,2,4,6,8,10 Stream<Double> stream3 = Stream.generate(Math::random).limit(2); //生成随机数流对象,截取2个元素 Stream3.forEach(System.out::println);
④用BufferedReader下的
lines()
方法,将文件每行内容转换成流BufferedReader reader = new BufferedReader(new FileReader("D:\\test\\test.txt")); Stream<String> lineStream = reader.lines(); lineStream.forEach(System.out::println);
⑤用Pattern(正则表达式)下的
splitAsStream()
方法,将字符串分割成流Pattern pattern = Pattern.compile(","); //创建一个","的正则表达式模式 Stream<String> stringStream = pattern.splitaAsStream("a,b,c"); //传入("a,b,c")并按正则表达式模式来分割字符串,返回一个Stream<String>类型数据 stringStream.forEach(System.out::println); //a b c
-
方法链
一种编程技术,基本思想是一个方法调用后立即调用另一个方法,将多个方法链在一起
//正常写法 Stream<Integer> stream2 = Stream.iterate(0,(x)->x+2).limit(6); stream2.forEach(System.out::println); //方法链写法 Stream.iterate(0,(x)->x+2).limit(6).forEach(System.out::println);
-
操作流 Stream API
①筛选与切片
- filter:过滤流中的某些元素
- limit(n):截取前n个元素
- skip(n):跳过前n个元素,配合
limit(n)
实现分页 - distinct:用流中元素的
hashCode()
和equals()
去除重复元素
public class demo{ public static void main(String args[]){ //创建集合 List<Integer> list = List.of(6,4,6.7,3,9,8,10); //将集合转换成流,再筛选其中x>5的元素(过滤x<5的元素),再遍历打印 list.stream().filter(x->x>5).forEach(System.out::println); Stream<Integer> stream = Stream.of(6,4,6,7,9,8,10,12,14,14); Stream<Integer> newStream = Stream.filter(s->s>5) //6 6 7 9 8 10 12 14 14 .distinct() //去重,6 7 9 8 10 12 14 .skip(2) //跳过前两个元素,9 8 10 12 14 .limit(2) //截取前两个元素,9 8 newStream.forEach(System.out::println); //9 8 } }
②映射:对流里的每一个元素进行处理
- map:接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素,可用于数据类型转换(一进一出)
- flatMap:接收一个函数作为参数,将流中的每一个值替换成另一个流,然后把所有流连接成一个流(一进多出)
public class demo{ public static void main(String args[]){ List<String> list = Arrays.asList("a,b,c","10,20,30"); //将每个元素转换成一个新的且不带逗号的元素,一进一出 Stream<String> s1 = list.stream().map(s->s.replaceAll(",","")); //replaceAll方法替换所有 s1.forEach(System.out::println); //abc 102030,list里两个出来两个 //将逗号分隔的每个数据打印出来,一进多出 Stream<String> s3 = list.stream().flatMap(s->{ //将每个元素转换成一个stream String[] split = s.split(","); //[a,b,c] [10,20,30] Stream<String> s2 = Arrays.stream(split); return s2; //a b c 10 20 30,list里两个,分割出来六个 }); } }
-
排序
- sorted():自然排序,流中元素需实现Comparable接口
- sorted(Comparator com):定制排序,自定义Comparator比较器
public class demo{ public static void main(String args[]){ //自然排序 List<String> list = Arrays.asList("aa","ff","dd"); //String类自身已经实现Comparable接口 list.stream().sorted().forEach(System.out::println); //定制排序 List<Sku> skus = new ArrayList<>(); skus.add(new Sku("A",100,200.0)); skus.add(new Sku("C",50,1200.0)); skus.add(new Sku("D",150,120.0)); skus.add(new Sku("B",200,1000.0)); //先按价格降序,价格相同则数量升序 skus.stream().sorted( (o1,o2)->{ if(o2.getPrice() - o1.getPrice() == 0){ return o2.getNum() - o1.getNum(); } return (int)(o2.getPrice() - o1.getPrice()); } ).forEach(System.out::println); } }
-
消费
peek:得到流中的每一个元素,且能修改元素的值,但生成还是一个新的对象
public class demo{ public static void main(String args[]){ //定制排序 List<Sku> skus = new ArrayList<>(); skus.add(new Sku("A",100,200.0)); skus.add(new Sku("C",50,1200.0)); skus.add(new Sku("D",150,120.0)); skus.add(new Sku("B",200,1000.0)); //先按价格降序,价格相同则数量升序 skus.stream().sorted( (o1,o2)->{ if(o2.getPrice() - o1.getPrice() == 0){ return o2.getNum() - o1.getNum(); } return (int)(o2.getPrice() - o1.getPrice()); } ).forEach(System.out::println); //将数量>=150的商品降价10% skus.stream().filter(e -> e.getNum() >= 150) .peek(e -> e.setPirce(e.getPrice()*(1-0.1))) .forEach(System.out::println); //生成订单集合,订单:商品名称、数量、价格、金额 skus.stream().map(sku->{ double amount = sku.getNum()*sku.getPrice(); Order order = new Order( sku.getName(), sku.getNum(), sku.getPrice(), amount ); return order; }).forEach(System.out::println); } }
-
匹配、聚合
- allMatch
- noneMatch
- anyMatch
- findFirst
- findAny
- count
- max
- min
-
规约
Optional<T> reduce(BinaryOperator<T> accumulator)
:第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中的第二个元素;第二次执行时,第一个参数为第一次函数执行的结构,第二个参数为流中的第三个元素;依此类推
public class demo{ public static void main(String args[]){ List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer v = list.stream().reduce((x1,x2)->x1+x2).get(); //1+2=3-->3+3=6... System.out.println(v); //55 } }
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner)
-
收集
- collect:接收一个Collector实例,将流中元素收集成另一个数据结构,以二次利用
public class demo{ public static void main(String args[]){ //定制排序 List<Sku> skus = new ArrayList<>(); skus.add(new Sku("A",100,200.0)); skus.add(new Sku("C",50,1200.0)); skus.add(new Sku("D",150,120.0)); skus.add(new Sku("B",200,1000.0)); //先按价格降序,价格相同则数量升序 skus.stream().sorted( (o1,o2)->{ if(o2.getPrice() - o1.getPrice() == 0){ return o2.getNum() - o1.getNum(); } return (int)(o2.getPrice() - o1.getPrice()); } ).forEach(System.out::println); //生成订单集合,订单:商品名称、数量、价格、金额 List<Order> orders = skus.stream().map(sku->{ double amount = sku.getNum()*sku.getPrice(); Order order = new Order( sku.getName(), sku.getNum(), sku.getPrice(), amount ); return order; }).collect(Collectors.toList()); //收集商品集合,转换成订单集合,以对订单进行二次计算 orders.forEach(System.out::println); //再打印 //将数量>=150的商品降价10% List<Sku> sku1 = skus.stream().filter(e -> e.getNum() >= 150) .peek(e -> e.setPirce(e.getPrice()*(1-0.1))) .collect(Collectors.toList()); //收集所有降价的商品成一个变量sku1,以用于二次计算 sku1.forEach(System.out::println); //再打印 } }
-
Optional类
在Stream API中,有很多方法如max、min、reduce,返回值是Optional;
在Java中尝试访问空引用的属性调用空引用的方法是会报空指针异常,因此项目中会有很多条件判空,较为冗杂,所以Optional类引入了一种显式的方式来处理可能为空的对象,强制在可能为空的情况下进行显示;
Optional类似容器,可以包含各种类型的值,也可以是null,Optional类提供了许多方法以方便操作内部的值,常用的如get、orElse、orElseGet、orElseThrow等
①构建Optional对象
- empty():构建一个空的Optional对象
- of(T value):构建一个非空的Optional对象,如果为空则报错
- ofNullable(T value):构建一个Optional对象,允许为空
//1.构建一个空的Optional对象 Optional<Object> empty = Optional.empty(); Optional<Object> ob = Optional.ofNullable(null); //2.构建一个非空的Optional对象 Optional<Object> oa = Optional.of("123"); Optional<Object> oc = Optional.ofNullable("123");
②访问Optional的值
- get():取值,若取值对象为null,则报异常
- orElse():用于获取值或者在值为空的情况下提供一个默认值
- orElseGet():提供默认值的工厂
- orElseThrow():用于Optional对象中的值为空时抛出一个指定的异常
Sku sku = null; Optional<Sku> op = Optional.ofNullable(sku); // String name = op.get().getName(); //异常 String name1 = op.orElse(new Sku("A",100,200.0)).getName(); System.out.println(name1); //A String name2 = op.orElseGet(()->new Sku("B",100,200.0)).getName(); System.out.println(name2); //B op.orElseThrow(()->new Exception("对象为空")).getName(); String name = op.get().getName(); //抛出"对象为空"异常
③空检查
-
-
- Map顶层接口=Entry接口+Map自身接口方法
-