• java基础高级


    java

    jvm

    image-20220805193809220

    helloworld

    public class test1 {
    
    	public static void main(String[]args){
    		System.out.println("hello world你好");
    	}
    }
    
    常见错误
    大小写
    中文字符错误
    
    

    变量和数据类型运算符

    二进制:略

    变量本质:java是强类型语言

    声明变量

    double salary;
    long earthPopulation;
    int age;
    

    变量初始化

    int age = 18; 
    double e = 2.718281828;
    int i ,j; // 两个变量的数据类型都是 int
    

    局部变量:方法或语句内部

    成员变量:类内部,方法外部

    静态:类内部,static修饰

    局部变量:

    public void test() {
    int i;
    int j = i+5 ; // 编译出错,变量 i 还未被初始化
    } 
    
    public void test() {
    int i;
    i=10;
    int j = i+5 ; // 编译正确
    } 
    

    常量

    final定义一个常量

    final type varName = value;
    public class TestConstants {
        public static void main(String[ ] args) {
            final double PI = 3.14;
    // PI = 3.15; //编译错误,不能再被赋值!
            double r = 4;
            double area = PI * r * r;
            double circle = 2 * PI * r;
            System.out.println("area = " + area);
            System.out.println("circle = " + circle);
        }
    }
    
    

    新人注意

    变量和常量命名规范
     所有变量、方法、类名:见名知义
     类成员变量:首字母小写和驼峰原则: monthSalary
     局部变量:首字母小写和驼峰原则
     常量:大写字母和下划线:MAX_VALUE
     类名:首字母大写和驼峰原则: Man, GoodMan
     方法名:首字母小写和驼峰原则: run(), runRun()
    
    

    基本数据类型

    整型,浮点型,字符串型,布尔型

    运算符

    image-20220806174022653

    二元运算符

    整数运算

    如果两个操作数有一个为 long, 则结果也为 long。
     没有 long 时,结果为 int。即使操作数全为 short,byte,结果也是 int。
    

    浮点运算

    如果两个操作数有一个为 double,则结果为 double。
     只有两个操作数都是 float,则结果才为 float。
    
    

    取模运算

    其操作数可以为浮点数,一般使用整数,结果是“余数”,“余数”符号和左边操
    
    
    int a = 3;
    int b = a++; //执行完后,b=3。先给b赋值,再自增。
    System.out.println("a="+a+"\nb="+b);
    a = 3;
    b = ++a; //执行完后,b=4。a先自增,再给b赋值
    System.out.println("a="+a+"\nb="+b);
    

    赋值扩展赋值运算符

    image-20220806174152284

    int a=3;
    int b=4;
    a+=b;//相当于a=a+b;
    System.out.println("a="+a+"\nb="+b);
    a=3;
    a*=b+3;//相当于a=a*(b+3)
    System.out.println("a="+a+"\nb="+b);
    

    关系运算

    = == != > < >= <=

    位运算符

    左移<<右移>>

    字符串连接符 “+”

    条件运算符

    三目运算

    为真选前,false 选后

    image-20220806174337595

    运算符优先级

    image-20220806174353570

    大家不需要去刻意的记这些优先级,表达式里面优先使用小括号来组织!!
     逻辑与、逻辑或、逻辑非的优先级一定要熟悉!(逻辑非>逻辑与>逻辑或)。如:
     a||b&&c 的运算结果是:a||(b&&c),而不是(a||b)&&c
    

    数据类型转换

    自动类型转换

    image-20220806174504413

    容量小的数据类型可以自动转换为容量大的数据类型

    强制类型转换

    double x = 3.94; 
    int nx = (int)x; //值为 3
    char c = 'a';
    int d = c+1;
    System.out.println(nx);
    System.out.println(d);
    System.out.println((char)d)
    

    转换常见的问题

    1. 操作比较大的数时,要留意是否溢出,尤其是整数操作时。
    2. L 和 l 的问题:
    (1) 不要命名名字为 l 的变量,字母 l 容易和数字 1 混淆。
    (2) long 类型使用大写 L,不要用小写 l。
    int money = 1000000000; //10亿
    int years = 20;
    //返回的total是负数,超过了int的范围
    int total = money*years;
    System.out.println("total="+total);
    //返回的total仍然是负数。默认是int,因此结果会转成int值,再转成long。但是已经发
    生//了数据丢失
    long total1 = money*years; 
    System.out.println("total1="+total1);
    //返回的total2正确:先将一个因子变成long,整个表达式发生提升。全部用long来计
    算。
    long total2 = money*((long)years); 
    System.out.println("total2="+total2);
    

    键盘输入

    import java.util.Scanner;
    public class Welcome2 {
        public static void main(String[ ] args) {
            Scanner scanner = new Scanner(System.in);
    // 将输入的一行付给 string1
            String string1 = scanner.nextLine();
    // 将输入单词到第一个空白符为止的字符串付给 string2
            String string2 = scanner.next();
    // 将输入的数字赋值给变量
            int a = scanner.nextInt();
            System.out.println("-----录入的信息如下-------");
            System.out.println(string1);
            System.out.println(string2);
            System.out.println(a * 10);
        }
    }
    

    控制流程

    顺序

    循环

    条件判断

    if else

    if else-if else

    public class Test1 {
        public static void main(String[ ] args) {
    //通过掷三个骰子看看今天的手气如何?
            int i = (int)(6 * Math.random()) + 1;//通过 Math.random()产生随机数
            int j = (int)(6 * Math.random()) + 1;
            int k = (int)(6 * Math.random()) + 1;
            int count = i + j + k;
    //如果三个骰子之和大于 15,则手气不错
            if(count > 15) {
                System.out.println("今天手气不错");
            }
    //如果三个骰子之和在 10 到 15 之间,则手气一般
            if(count >= 10 && count <= 15) { //错误写法:10<=count<=15
                System.out.println("今天手气很一般");
            }
    //如果三个骰子之和小于 10,则手气不怎么样
            if(count < 10) {
                System.out.println("今天手气不怎么样");
            }
        	System.out.println("得了" + count + "分");
        }
    }
    
    public class Test2 {
        public static void main(String[ ] args) {
    //随机产生一个[0.0, 4.0)区间的半径,并根据半径求圆的面积和周长
    
        double r = 4 * Math.random();
        //Math.pow(r, 2)求半径 r 的平方
        double area = 3.14* r*r;
        double circle = 2 * Math.PI * r;
    System.out.println("半径为: " + r);
            System.out.println("面积为: " + area);
            System.out.println("周长为: " + circle);
    //如果面积>=周长,则输出"面积大于等于周长",否则,输出周长大于面积
            if(area >= circle) {
            System.out.println("面积大于等于周长");
            } else {
            System.out.println("周长大于面积");
            }
    	}
    }
    
    public class Test5 {
        public static void main(String[ ] args) {
            int age = (int) (100 * Math.random())
            System.out.print("年龄是" + age + ", 属于");
    //15 岁以下儿童;15-24 青年;25-44 中年;45-64 中老年;65-84 老年;85 以上老寿星
            if (age < 15) {
            System.out.println("儿童, 喜欢玩!");
            } else if (age < 25) {
            System.out.println("青年, 要学习!");
            } else if (age < 45) {
            System.out.println("中年, 要工作!");
            } else if (age < 65) {
            System.out.println("中老年, 要补钙!");
            } else if (age < 85) {
            System.out.println("老年, 多运动!");
            } else {
            System.out.println("老寿星, 古来稀!");
            }
    	}
    }
    

    switch-case

    switch (表达式) {
    case 值 1: 
    语句块 1;
    [break];
    case 值 2:
    语句块 2;
    [break];
     … … … … …
    [default:
    默认语句块;]
    }
    

    switch根据表达式值匹配标签处开始执行,一直执行到break处或者switch末尾,如果表达式值case值不匹配

    switch中表达式值,

    //grade 表示大学年级
    int grade = 1;
    if(grade==1) {
         System.out.println("大学一年级,可以放松一下,学着谈
        谈恋爱");
    }else if(grade==2){
         System.out.println("大学二年级,少玩点游戏,不空虚,
        不慌嘛?");
    }else if(grade==3) {
         System.out.println("大学三年级,专业课开始了,好好
        学,找份好工作");
    }else{
         System.out.println("大四了,要毕业了。因为跟着尚学堂
        学习,好工作搞定!");
    }
    switch (grade){
         case 1:
         System.out.println("大学一年级");
         break;
         case 2:
         System.out.println("大学二年级");
         break;
     case 3:
    
    
    public class TestSwitch02 {
     public static void main(String[] args){
     int month = 2; //1 表示 1 月,2 表示 2 月,....
     if(month==1||month==2||month==3){
         System.out.println("春季");
     }else if(month==4||month==5||month==6){
     	System.out.println("夏季");
     }else if(month==7||month==8||month==9){
     	System.out.println("秋季");
     }else{
     System.out.println("冬季");
     }
     System.out.println("========使用 switch 改造上面的代码,switch 特别适合多值判断
    =============");
     switch (month){
         case 1:
         case 2:
         case 3:
         System.out.println("春季");
         break;
         case 4:
         case 5;
         case 6:
     System.out.println("夏季");
         break;
         case 7:
         case 8:
         case 9:
     	System.out.println("秋季");
         break;
         default:
         System.out.println("冬季");
     }
     }
    }
    
    

    循环

    当型
    当布尔表达式条件为 true 时,反复执行某语句,当布尔表达式的值为 false 时才停止 循环,比如:while 与 for 循环。
    
    直到型
    
    先执行某语句, 再判断布尔表达式,如果为 true,再执行某语句,如此反复,直到布 尔表达式条件为 false 时才停止循环,比如 do-while 循环。
    

    while

    1. 在循环刚开始时,会计算一次“布尔表达式”的值,若条件为真,执行循环体。而对于后
    来每一次额外的循环,都会在开始前重新计算一次。
    2. 语句中应有使循环趋向于结束的语句,否则会出现无限循环–––"死"循环
    
    public class Test7 {
        public static void main(String[ ] args) {
            int i = 0;
            int sum = 0;
    // 1+2+3+…+100=?
            while (i <= 100) {
                sum += i;//相当于 sum = sum+i;
                i++;
            }
            System.out.println("Sum= " + sum);
        }
    }
    

    do-while

    do-while 循环结构:求 1-100 之间的累加和

    public class Test8 {
        public static void main(String[ ] args) {
            int i = 0;
            int sum = 0;
            do {
                sum += i; // sum = sum + i
                i++;
            } while (i <= 100); //此处的;不能省略
            System.out.println("Sum= " + sum);
        }
    }
    

    for

    for (初始表达式; 布尔表达式; 迭代因子) {
     循环体;
    }
    

    初始化设置,循环变量初值

    条件判断部分为:布尔表达式
     迭代因子:控制循环变量的增减
    

    for 循环在执行条件判定后,先执行的循环体部分,再执行步进。

    public class Test10 {
        public static void main(String args[ ]) {
            int sum = 0;
    //1.求 1-100 之间的累加和
            for (int i = 0; i <= 100; i++) {
                sum += i;
            }
            System.out.println("Sum= " + sum);
    //2.循环输出 9-1 之间的数
            for(int i=9;i>0;i--){
                System.out.print(i+"、");
            }
            System.out.println();
    //3.输出 90-1 之间能被 3 整除的数
            for(int i=90;i>0;i-=3){
                System.out.print(i+"、");
            }
            System.out.println();
        }
    }
    

    逗号运算符

    public class Test11 {
        public static void main(String[ ] args) {
            for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) {
                System.out.println("i= " + i + " j= " + j);
            }
        }
    }
    

    无限循环

    public class Test12 {
        public static void main(String[ ] args) {
            for ( ; ; ) { // 无限循环: 相当于 while(true)
                System.out.println("北京尚学堂");
            }
        }
    }
    
    

    初始化变量作用域

    public class Test13 {
        public static void main(String[] args) {
            for(int i = 1; i < 10; i++) {
                System.out.println(i+" ");
            }
            System.out.println(i);//编译错误,无法访问在 for 循环中定义的变量 i
        }
    }
    
    

    嵌套循环

    public class Test14 {
        public static void main(String args[ ]) {
            for (int i=1; i <=5; i++) {
                for(int j=1; j<=5; j++){
                    System.out.print(i+" ");
                }
                System.out.println();
            }
        }
    }
    

    嵌套循环实现99乘法表

    public class Test15 {
        public static void main(String args[ ]) {
            for (int i = 1; i < 10; i++) { // i 是一个乘数
                for (int j = 1; j <= i; j++) { // j 是另一个乘数
                    System.out.print(j + "*" + i + "=" + (i * j < 10 ? (" " + i * j) : i * j) + " 
                            ");
                }
                System.out.println();
            }
        }
    }
    
    

    break和continue

    1. break 用于强行退出整个循环
    2. continue 用于结束本次循环,继续下一次
    
    //产生 100 以内的随机数,直到随机数为 88 时终止循环
    public class Test16 {
        public static void main(String[ ] args) {
            int total = 0;//定义计数器
    System.out.println("Begin");
            while (true) {
            total++;//每循环一次计数器加 1
            int i = (int) Math.round(100 * Math.random());
    //当 i 等于 88 时,退出循环
            if (i == 88) {
            break;
            }
            }
    //输出循环的次数
            System.out.println("Game over, used " + total + " times.");
        }
    }
    

    continue

    //把 100~150 之间不能被 3 整除的数输出,并且每行输出 5 个
    public class Test17 {
        public static void main(String[ ] args) {
            int count = 0;//定义计数器
            for (int i = 100; i < 150; i++) {
    //如果是 3 的倍数,则跳过本次循环,继续进行下一次循环
                if (i % 3 == 0){
                    continue;
                }
    //否则(不是 3 的倍数),输出该数
                System.out.print(i + "、");
                count++;//每输出一个数,计数器加 1
    //根据计数器判断每行是否已经输出了 5 个数
                if (count % 5 == 0) {
                    System.out.println();
                }
            }
        }
    
    

    带标签的 continue

    //控制嵌套循环跳转(打印 101-150 之间所有的质数)
    public class Test18 {
        public static void main(String args[ ]) {
            outer: for (int i = 101; i < 150; i++) {
                for (int j = 2; j < i / 2; j++) {
                    if (i % j == 0){
                        continue outer; //符合某条件,跳到外部循环继续
                    }
                }
                System.out.print(i + " ");
            }
        }
    }
    

    方法

    语句块语句块中定义的变量只能用于自己,外部不能使用。 语句块可以使用外部的变量,而外部不能使用语句块的变量;

    public class Test19 {
        public static void main(String[] args) {
            int n;
            int a;
            {
                int k;
                int n; //编译错误:不能重复定义变量 n
            } //变量 k 的作用域到此为止
        }
    
    }
    

    方法:一段用来完成特定功能的代码,
    方法用于定义该类的实例行为特征功能实现
    面向过程中,函数是最基本单位,整个程序由一个个函数调用组成
    面向对象中,整个程序基本单位是类,方法是从属与类和对象的
    
    
    方法的详细说明
    形式参数:在方法声明时用于接收外界传入的数据。(方法定义时)
    实参:调用方法时实际传给方法的数据。 (方法调用时
    返回值:执行完毕后,返还给调用它的环境的数据
    返回值:执行完毕后,返还给调用它的环境的数据
    
    public class Test20 {
    /** main 方法:程序的入口 */
    public static void main(String[ ] args) {
    int num1 = 10;
    int num2 = 20;
    //调用求和的方法:将 num1 与 num2 的值传给 add 方法中的 n1 与 n2
     // 求完和后将结果返回,用 sum 接收结果
    int sum = add(num1, num2);
    System.out.println("sum = " + sum);//输出:sum = 30
    //调用打印的方法:该方法没有返回值
    print();
    }
    /** 求和的方法 */
    public static int add(int n1, int n2) {
    int sum = n1 + n2;
    return sum;//使用 return 返回计算的结果
    }
    /** 打印的方法 */
    public static void print() {
    System.out.println("北京尚学堂...");
    }
    }
    
     return:终止方法运行,并返回的数据。
     Java 中传递参数,遵循值传递的原则(传递的都是数据的副本):
     基本类型传递的是该数据值的 copy 值。
     引用类型传递的是该对象引用的 copy 值,但指向的是同一个对象
    

    方法的重载

    重载:一个类中可以定义多个名称相同,但形式参数列表不同的方法。

    构成方法重载的条件:

    形参列表不同含义:形参类型,形参个数,形参顺序不同

    只有返回值不同不构成方法的重载

    只有形参名称,不构成方法重载

    public class Test21 {
        public static void main(String[ ] args) {
            System.out.println(add(3, 5));// 8
            System.out.println(add(3, 5, 10));// 18
            System.out.println(add(3.0, 5));// 8.0
            System.out.println(add(3, 5.0));// 8.0
    // 我们已经见过的方法的重载
            System.out.println();// 0 个参数
            System.out.println(1);// 参数是 1 个 int
            System.out.println(3.0);// 参数是 1 个 double
        }
    /** 求和的方法 *
        public static int add(int n1, int n2) {
            int sum = n1 + n2;
            return sum;
        }
        // 方法名相同,参数个数不同,构成重载
        public static int add(int n1, int n2, int n3) {
            int sum = n1 + n2 + n3;
            return sum;
        }
        // 方法名相同,参数类型不同,构成重载
        public static double add(double n1, int n2) {
            double sum = n1 + n2;
            return sum;
        }
        // 方法名相同,参数顺序不同,构成重载
        public static double add(int n1, double n2) {
            double sum = n1 + n2;
            return sum;
        }
        //编译错误:只有返回值不同,不构成方法的重载
        public static double add(int n1, int n2) {
            double sum = n1 + n2;
            return sum;
        }
        //编译错误:只有参数名称不同,不构成方法的重载
        public static int add(int n2, int n1) {
            double sum = n1 + n2;
            return sum;
        }
    }
    

    递归

    定义递归头:

    什么时候不调用自身方法。如果没有头,将陷入死循环,也就 是递归的结束条件。

    递归体

    什么时候需要调用自身方法。

    递归求阶乘

    public class Test22 {
        public static void main(String[ ] args) {
            long d1 = System.currentTimeMillis();
            factorial(10);
            long d2 = System.currentTimeMillis()
            System.out.printf("递归费时:"+(d2-d1)); //耗时:32ms
            }
    /** 求阶乘的方法*/
    static long factorial(int n){
            if(n==1){//递归头
            return 1;
            }else{//递归体
            return n*factorial(n-1);//n! = n * (n-1)!
            }
    	}
    }
    
    

    使用循环求 n!

    public class Test23 {
    	public static void main(String[ ] args) {
            long d3 = System.currentTimeMillis();
            int a = 10;
            int result = 1;
            while (a > 1) {
            result *= a * (a - 1);
            a -= 2;
    	}
    	long d4 = System.currentTimeMillis();
        System.out.println(result);
        System.out.printf("普通循环费时:"+(d4 - d3));
    	}
    }
    

    面向对象

    面向过程和面向对象思想

    面向过程和面向对象的区别
    面向过程和面向对象都是对软件分析、设计和开发的一种思想,它指导着人们以不同的
    方式去分析、设计和开发软件。C 语言是一种典型的面向过程语言,Java 是一种典型的面向
    对象语言。
    

    面向过程适合简单、不需要协作的事务,重点关注如何执行。 面向过程时,我们首先 思考“怎么按步骤实现?”并将步骤对应成方法,一步一步,最终完成。 这个适合简单任 务,不需要过多协作的情况下。比如,如何开车?我们很容易就列出实现步骤:

    面向对象(Oriented-Object)思想更契合人的思维模式。我们首先思考的是“怎么设计 这个事物?” 比如思考造车,我们就会先思考“车怎么设计?”,而不是“怎么按步骤造车 的问题”。这就是思维方式的转变。

    属性(field 成员变量)

    属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体。 在定义成员变量时可以对其初始化,如果不对其初始化,Java 使用默认的值对其初始化。

    image-20220809112306008

    属性定义格式:

    方法
    方法用于定义该类或该类实例的行为特征和功能实现。方法是类和对象行为特征的抽象。
    面向对象中,整个程序的基本单位是类,方法是从属于类和对象的。
    

    简单内存分析(帮助理解面向对象)

    image-20220809112456596

    构造方法(构造器 constructor) 构造器用于对象的初始化,而不是创建对象!

    image-20220809113532470

     构造器通过 new 关键字调用!!
     构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在
    构造器里使用 return 返回某个值。
     如果我们没有定义构造器,则编译器会自动定义一个无参的构造方法。如果已定义
    则编译器不会自动添加!
     构造器的方法名必须和类名一致
    

    例子

     定义一个“点”(Point)类用来表示二维空间中的点(有两个坐标)。要求如下:
     可以生成具有特定坐标的点对象。
     提供可以计算该“点”距另外一点距离的方法。
    class Point {
        double x, y;
        public Point(double _x, double _y) {
            x = _x;
            y = _y;
        }
        public double getDistance(Point p) {
            return Math.sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
        }
        public static void main(String[ ] args) {
            Point p1 = new Point(3.0, 4.0);
            Point origin = new Point(0.0, 0.0);
            System.out.println(p1.getDistance(origin));
        }
    }
    
    

    构造方法重载

    public class User {
        int id; // id
        String name; // 账户名
        String pwd; // 密码
        public User() {
        }
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
        public static void main(String[ ] args) {
            User u1 = new User();
            User u2 = new User(101, "高小七");
            User u3 = new User(100, "高淇", "123456");
        }
    }
    
    

    如果方法构造中形参名与属性名相同时,需要使用 this 关键字区分属性与形参。 this.id 表示属性 id;id 表示形参 id

    java虚拟机内存模型

    image-20220809113839396

    虚拟机栈(简称:栈)的特点如下:
    1. 栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变
    量、操作数、方法出口等)
    2. JVM 为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变
    量等)
    3. 栈属于线程私有,不能实现线程间的共享!
    4. 栈的存储特性是“先进后出,后进先出”
    5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!
    
    堆的特点如下:
    1. 堆用于存储创建好的对象和数组(数组也是对象)
    2. JVM 只有一个堆,被所有线程共享
    3. 堆是一个不连续的内存空间,分配灵活,速度慢!
    4. 堆被所有的线程所共享,在堆上的区域,会被垃圾回收器做进一步划分,例如新生
    代、老年代的划分。
    
    
    1. 方法区是 JAVA 虚拟机规范,可以有不同的实现。
    i. JDK7 以前是“永久代”
    ii. JDK7 部分去除“永久代”,静态变量、字符串常量池都挪到了堆内存中
    iii. JDK8 是“元数据空间”和堆结合起来。
    2. JVM 只有一个方法区,被所有线程共享!
    3. 方法区实际也是堆,只是用于存储类、常量相关的信息!
    4. 用来存放程序中永远是不变或唯一的内容。(类信息【Class 对象,反射机制中会
    重点讲授】、静态变量、字符串常量等)
    5. 常量池主要存放常量:如文本字符串、final 常量值。
    

    参数传值机制

    基本数据类型参数传值

    传递的是值的副本,副本改变不会影响原件

    引用类型参数传值

    传递的是值的副本。但是引用类型指的是“对象的地址”。因此,副本和原参数都指向 了同一个“地址”,改变“副本指向地址对象的值,也意味着原参数指向对象的值也发生了 改变”。

    垃圾回收机制

    Java 引入了垃圾回收机制,令 C++程序员最头疼的内存管理问题迎刃而解。Java 程序 员可以将更多的精力放到业务逻辑上而不是内存管理工作上,大大的提高了开发效率。

    垃圾回收原理和算法

    内存管理

    Java 的内存管理很大程度就是:堆中对象的管理,其中包括对象空间的分配和释放。
    对象空间的分配:使用 new 关键字创建对象即可
    对象空间的释放:将对象赋值 null 即可
    
    · 垃圾回收过程
    任何一种垃圾回收算法一般要做两件基本事情:
    1. 发现无用的对象
    2. 回收无用对象占用的内存空间。
    垃圾回收机制保证可以将“无用的对象”进行回收。
    无用的对象指的就是没有任何变量引用该对象。Java 的垃圾回收器通过相关算法发现
    无用对象,并进行清除和整理
    
    垃圾回收相关算法
    1. 引用计数法
    堆中的每个对象都对应一个引用计数器,当有引用指向这个对象时,引用计数器加
    1,而当指向该对象的引用失效时(引用变为 null),引用计数器减 1,最后如果该
    对象的引用计算器的值为 0 时,则 Java 垃圾回收器会认为该对象是无用对象并对
    其进行回收。优点是算法简单,缺点是“循环引用的无用对象”无法别识别。
    2. 引用可达法(根搜索算法)
    程序把所有的引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的引用
    节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找
    完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点
    

    JVM 调优和 Full GC

    在对 JVM 调优的过程中,很大一部分工作就是对于 Full GC 的调节。有如下原因可能
    导致 Full GC:
    1. 年老代(Tenured)被写满
    2. 永久代(Perm)被写满
    3. System.gc()被显式调用
    4. 上一次 GC 之后 Heap 的各域分配策略动态变化
    

    内存泄露

    1.创造大量无用对象
    2.静态集合类的使用
    3.各种连接对象(IO流对象,、数据库连接对象、网络连接对象)没关闭
    4.监听器使用不当
    释放对象,没有删除相应的监听器
    
    

    this关键字

    image-20220809115821415

    this 的用法:
     普通方法中,this 总是指向调用该方法的对象。
     构造方法中,this 总是指向正要初始化的对象
     this()调用重载的构造方法,避免相同的初始化代码。但只能在构造方法中用,并且
    必须位于构造方法的第一句。
     this 不能用于 static 方法中。
     this 是作为普通方法的“隐式参数”,由系统传入到方法中。
    
    this用法
    
    public class TestThis {
        int a, b, c;
        TestThis() {
            System.out.println("正要初始化一个Hello对象");
        }
        TestThis(int a, int b) {
    // TestThis(); //这样是无法调用构造方法的!
            this(); // 调用无参的构造方法,并且必须位于第一行!
            a = a;// 这里都是指的局部变量而不是成员变量
    // 这样就区分了成员变量和局部变量. 这种情况占了this使用情况大多数!
            this.a = a;
            this.b = b;
        }
        TestThis(int a, int b, int c) {
            this(a, b); // 调用带参的构造方法,并且必须位于第一行!
            this.c = c;
        }
        void sing() {
        }
        void eat() {
            this.sing(); // 调用本类中的sing();
            System.out.println("你妈妈喊你回家吃饭!");
        }
    public static void main(String[ ] args) {
        TestThis hi = new TestThis(2, 3);
        hi.eat();
        }
    }
    

    static关键字

    静态变量/静态方法生命周期和类相同,在整个程序执行期间都有效。它有如下特点:
     为该类的公用变量,属于类,被该类的所有实例共享,在类载入时被初始化。
     static 变量只有一份。
     一般用“类名.类变量/方法”来调用。
     在 static 方法中不可直接访问非 static 的成员。
    
    
    public class TestStatic {
        int id; // id
        String name; // 账户名
        String pwd; // 密码
        static String company = "北京尚学堂"; // 公司名称
        public TestStatic (int id, String name) {
            this.id = id;
            this.name = name;
    }
    public void login() {
            System.out.println(name);
            }
    public static void printCompany() {
    // login();//调用非静态成员,编译就会报错
            System.out.println(company);
            }
    public static void main(String[ ] args) {
            TestStatic u = new TestStatic (101, "高小七");
            TestStatic .printCompany();
            TestStatic .company = "北京阿里爷爷";
            TestStatic .printCompany();
    	}
    }
    

    静态初始化

    构造方法用于对象的普通属性初始化。
    静态初始化块,用于类的初始化操作,初始化静态属性。
    在静态初始化块中不能直接访问非 static 成员。
    静态初始化块执行顺序(学完继承再看这里):
     上溯到 Object 类,先执行 Object 的静态初始化块,再向下执行子类的静态初始化块,
    直到类的静态初始化块为止。
     构造方法执行顺序和上面顺序一样!!
    

    static静态初始化块

    public class TestStatic2 {
        static String company; //公司名称
        static {
            System.out.println("执行类的初始化工作");
            company = "北京尚学堂";
            printCompany();
        }
        public static void printCompany(){
            System.out.println(company);
        }
        public static void main(String[ ] args) {
        }
    }
    
    

    变量的分类和作用域

    image-20220809120648785

    package包

    \1. 通常是类的第一句非注释性语句。 2. 包名:域名倒着写即可,便于内部管理类。

    命名
    
    com.oracle.test;
    com.itbaizhan.gao.test;
    com.itbaizhan.gao.view;
    com.itbaizhan.view.model;
    
    写项目时都要加包,不要使用默认包。
     com.gao 和 com.gao.car,这是两个完全独立的包。只是逻辑上看,后者是前者的一部
    分。
    
    package com.itbaizhan;
    public class Test {
    public static void main(String[ ] args) {
    System.out.println("helloworld");
        }
    }
    
    

    image-20220809120902081

    导包

    Java 会默认导入 java.lang 包下所有的类,因此这些类我们可以直接使用。
     如果导入两个同名的类,只能用包名+类名来显示调用相关类:
    java.util.Date date = new java.util.Date()
    

    静态导入

    静态导入(static import): 其作用是用于导入指定类的静态属性和静态方法,这样我
    们可以直接使用静态属性和静态方法
    

    面向对象三大特征

    继承

    继承是面向对象编程的三大特征之一。继承让我们更加容易实现类的扩展。实现代码的 重用,不用再重新发明轮子(don’t reinvent wheels)。

    代码复用,更容易实现类的扩展

    方便建模

    继承的实现

    使用extends继承

    public class Test{
        public static void main(String[ ] args) {
            Student s = new Student("高淇",176,"Java");
            s.rest();
            s.study();
        }
    }
    class Person {
        String name;
        int height;
        public void rest(){
            System.out.println("休息一会!");
        }
    }
    class Student extends Person {
        String major; //专业
        public void study(){
            System.out.println("在尚学堂,学习Java");
        }
        public Student(String name,int height,String major) {
    //天然拥有父类的属性
            this.name = name;
            this.height = height;
            this.major = major;
        }
    }
    

    instanceof 运算符

    instanceof 是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象 时,返回 true;否则,返回 false。比如:

    public class Test{
        public static void main(String[ ] args) {
            Student s = new Student("高淇",172,"Java");
            System.out.println(s instanceof Person);
            System.out.println(s instanceof Student);
        }
    
    
    继承使用要点
    1. 父类也称作超类、基类。 子类:派生类等。
    2. Java 中只有单继承,没有像 C++那样的多继承。多继承会引起混乱,使得继承链
    过于复杂,系统难于维护。
    3. Java 中类没有多继承,接口有多继承。
    4. 子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见
    得可以直接访问(比如,父类私有的属性和方法)。
    5. 如果定义一个类时,没有调用 extends,则它的父类是:java.lang.Object。
    

    方法重写override

    子类重写父类的方法,可以用自身行为替换父类行为。重写是实现多态的必要条件。
    方法重写需要符合下面的三个要点:
    1. “= =”: 方法名、形参列表相同。
    2. “≤”:返回值类型和声明异常类型,子类小于等于父类。
    3. “≥”: 访问权限,子类大于等于父类。
    
    
    package com.itbaizhan.oop;
    /**
     * 测试方法的重写
     */
    public class TestOverride {
        public static void main(String[ ] args) {
            Horse h = new Horse();
            Plane p = new Plane();
            h.run();
            h.getVehicle();
            p.run();
        }
    }
    class Vehicle { //交通工具类
        public void run() {
            System.out.println("跑....");
        }
        public Vehicle getVehicle(){
            System.out.println("给你一个交通工具!");
            return null;
        }
    }
    class Horse extends Vehicle { // 马也是交通工具
        @Override
        public void run() {
            System.out.println("得得得....");
        }
        @Override
        public Horse getVehicle() {
            return new Horse();
        }
    }
    class Plane extends Vehicle {
        @Override
        public void run() {
            System.out.println("天上飞....");
        }
    }
    

    final关键字

    修饰变量:被修饰的变量不可改变
    修饰方法 方法不能重写
    修饰类 类不能继承
    
    
    

    Object类

    public class Person { ...}

    toString 方法

    Object 类中定义有 public String toString()方法,其返回值是 String 类型。Object 类中 toString 方法的源码为:

    public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    

    重写 toString()方法

    class Person {
        String name;
        int age;
        @Override
        public String toString() {
            return name+",年龄:"+age;
        }
    }
    public class Test {
        public static void main(String[ ] args) {
            Person p=new Person();
            p.age=20;
            p.name="李东";
            System.out.println("info:"+p);
            Test t = new Test();
            System.out.println(t);
        }
    }
    

    ==和 equals 方法

    “==”代表比较双方是否相同。如果是基本类型则表示值相等,如果是引用类型则表示地 址相等即是同一个对象。 equals() 提供定义“对象内容相等”的逻辑。比如,我们在公安系统中认为 id 相同的 人就是同一个人、学籍系统中认为学号相同的人就是同一个人。 equals()默认是比较两个对象的 hashcode。但,可以根据自己的要求重写 equals 方 法。

    
    public class TestEquals {
        public static void main(String[ ] args) {
            Person p1 = new Person(123,"高淇")
            Person p2 = new Person(123,"高小七");
            System.out.println(p1==p2); //false,不是同一个对象
            System.out.println(p1.equals(p2)); //true,id相同则认为两个对象内容相同
            String s1 = new String("尚学堂");
            String s2 = new String("尚学堂");
            System.out.println(s1==s2); //false, 两个字符串不是同一个对象
            System.out.println(s1.equals(s2)); //true, 两个字符串内容相同
        }
    }
    class Person {
        int id;
        String name;
    
        public Person(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            } else {
                if (obj instanceof Person) {
                    Person c = (Person) obj;
                    if (c.id == this.id) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
    
    

    super 关键字

    1. super“可以看做”是直接父类对象的引用。可通过 super 来访问父类中被子类覆盖的方
    法或属性。
    2. 使用 super 调用普通方法,语句没有位置限制,可以在子类中随便调用。
    3. 在一个类中,若是构造方法的第一行没有调用 super(...)或者 this(...); 那么 Java 默认都
    会调用 super(),含义是调用父类的无参数构造方法。
    
    public class TestSuper01 {
        public static void main(String[ ] args) {
            new ChildClass().f();
        }
    }
    class FatherClass {
        public int value;
        public void f(){
            value = 100;
            System.out.println ("FatherClass.value="+value);
        }
    }
    class ChildClass extends FatherClass {
        public int value;
        public int age;
        public void f() {
            super.f(); //调用父类的普通方法
            value = 200;
            System.out.println("ChildClass.value="+value);
            System.out.println(value);
            System.out.println(super.value); //调用父类的成员变量
        }
        public void f2() {
            System.out.println(age); }
    }
    
    

    继承树追溯

    属性/方法查找顺序:(比如:查找变量 h)

    查找当前类中有没有属性 h
     依次上溯每个父类,查看每个父类中是否有 h,直到 Object
     如果没找到,则出现编译错误。
     上面步骤,只要找到 h 变量,则这个过程终止。
    
    

    构造方法顺序,super调用父类对应的构造方法,先向上追溯到object,然后再依次向下执行类的初始化块和构造方法。

    封装

    提高代码的安全性。
     提高代码的复用性。
     “高内聚”:封装细节,便于修改内部代码,提高可维护性。
     “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。
    
    

    访问权限修饰符

    private

    default

    protected

    public

    image-20220809155237343

    【注】关于 protected 的两个细节:
    1. 若父类和子类在同一个包中,子类可访问父类的 protected 成员,也可访问父类对象的
    protected 成员。
    2. 若子类和父类不在同一个包中,子类可访问父类的 protected 成员,不能访问父类对象
    的 protected 成员
    

    属性一般使用private访问权限

    javabean

    public class Person {
        // 属性一般使用 private 修饰
        private String name;
        private int age;
        private boolean flag;
        // 为属性提供 public 修饰的 set/get 方法
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public boolean isFlag() {// 注意:boolean 类型的属性 get 方法是 is 开头的
            return flag;
        }
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    }
    
    

    封装的使用

    class Person {
        private String name;
        private int age;
        public Person() {
        }
        public Person(String name, int age) {
            this.name = name;
    // this.age = age;//构造方法中不能直接赋值,应该调用 setAge 方法
            setAge(age);
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public void setAge(int age) {
    //在赋值之前先判断年龄是否合法
            if (age > 130 || age < 0) {
                this.age = 18;//不合法赋默认值 18
            } else {
                this.age = age;//合法才能赋值给属性 age
            }
        }
        public int getAge() {
            return age;
        }
        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    }
    public class Test2 {
        public static void main(String[ ] args) {
            Person p1 = new Person();
    //p1.name = "小红"; //编译错误
    //p1.age = -45; //编译错误
            p1.setName("小红");
            p1.setAge(-45);
            System.out.println(p1);
            Person p2 = new Person("小白", 300);
            System.out.println(p2);
        }
    }
    

    多态

    多态指的是同一个方法调用,由于对象不同可能会有不同的行为

    多态的要点:
    1. 多态是方法的多态,不是属性的多态(多态与属性无关)。
    2. 多态的存在要有 3 个必要条件:继承,方法重写,父类引用指向子类对象。
    3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
    
    class Animal {
        public void shout() {
            System.out.println("叫了一声!");
        }
    }
    class Dog extends Animal {
        public void shout() {
            System.out.println("旺旺旺!");
        }
        public void seeDoor() {
            System.out.println("看门中....");
        }
    }
    class Cat extends Animal {
        public void shout() {
            System.out.println("喵喵喵喵!");
        }
    }
    public class TestPolym {
        public static void main(String[ ] args) {
            Animal a1 = new Cat(); // 向上可以自动转型
    //传的具体是哪一个类就调用哪一个类的方法。大大提高了程序的可扩展性。
            animalCry(a1);
            Animal a2 = new Dog();
            animalCry(a2);//a2 为编译类型,Dog 对象才是运行时类型。
            /*编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
             * 否则通不过编译器的检查。*/
            Dog dog = (Dog)a2;//向下需要强制类型转换
            dog.seeDoor();
        }
        // 有了多态,只需要让增加的这个类继承 Animal 类就可以了。
        static void animalCry(Animal a) {
            a.shout();
        }
    /* 如果没有多态,我们这里需要写很多重载的方法。
    * 每增加一种动物,就需要重载一种动物的喊叫方法。非常麻烦。
    static void animalCry(Dog d) {
    d.shout();
    }
    static void animalCry(Cat c) {
    c.shout();
    }*/
    }
    

    对象的转型

    1.父类引用指向子类对象,向上转型
    2.向上转型后父类引用变量只能调用它编译类型方法,不能调用它运行时类型方法,需要类型强制转换,叫向下转型
    
    类型转换
    public class TestCasting {
        public static void main(String[ ] args) {
            Object obj = new String("北京尚学堂"); // 向上可以自动转型
    // obj.charAt(0) 无法调用。编译器认为 obj 是 Object 类型而不是 String 类型
            /* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
             * 不然通不过编译器的检查。 */
            String str = (String) obj; // 向下转型
            System.out.println(str.charAt(0)); // 位于 0 索引位置的字符
            System.out.println(obj == str); // true.他们俩运行时是同一个对象
        }
    }
    
    

    向下转型中使用 instanceof

    public class TestCasting3 {
        public static void main(String[ ] args) {
            Object obj = new String("北京尚学堂");
            if(obj instanceof String){
                String str = (String)obj;
                System.out.println(str.charAt(0));
            }else if(obj instanceof StringBuffer){
                StringBuffer str = (StringBuffer) obj;
                System.out.println(str.charAt(0));
            }
        }
    }
    

    抽象方法

    1. 使用 abstract 修饰的方法,没有方法体,只有声明。
    2. 定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
    
    

    抽象类

    包含抽象方法类就是抽象类

    通过抽象类可以严格限制子类的设计,使子类之间更加通用

    抽象类的使用要点:
    1. 有抽象方法的类只能定义成抽象类
    2. 抽象类不能实例化,即不能用 new 来实例化抽象类。
    3. 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来 new 实例,
    只能用来被子类调用。
    4. 抽象类只能用来被继承。
    5. 抽象方法必须被子类实现
    

    接口 interface

    接口的作用

    · 为什么需要接口?接口和抽象类的区别? 接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全 面地专业地实现了:规范和具体实现的分离。

    接口和实现类不是父子关系,是实现规则的关系。

    定义接口

    定义接口的详细说明:
     访问修饰符:只能是 public 或默认。
     接口名:和类名采用相同命名机制。
     extends:接口可以多继承。
     常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
     方法:接口中的方法只能是:public abstract。 省略的话,也是 public abstract。
    
    要点
     子类通过 implements 来实现接口中的规范。
     接口不能创建实例,但是可用于声明引用变量类型。
     一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是 public 的。
     JDK1.8(不含 8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造
    方法、普通方法。
     JDK1.8(含 8)后,接口中包含普通的静态方法、默认方法。
    
    
    public class TestInterface {
        public static void main(String[ ] args) {
            Volant volant = new Angel();
            volant.fly();
            System.out.println(Volant.FLY_HIGHT);
    
            Honest honest = new GoodMan();
            honest.helpOther();
        }
    }
    /**飞行接口*/
    interface Volant {
        int FLY_HIGHT = 100; // 总是:public static final 类型的;
        void fly(); //总是:public abstract void fly();
    }
    /**善良接口*/
    interface Honest {
        void helpOther();
    }
    /**Angel 类实现飞行接口和善良接口*/
    class Angel implements Volant, Honest{
        public void fly() {
            System.out.println("我是天使,飞起来啦!");
        }
        public void helpOther() {
            System.out.println("扶老奶奶过马路!");
        }
    }
    class GoodMan implements Honest {
        public void helpOther() {
            System.out.println("扶老奶奶过马路!");
        }
    }
    class BirdMan implements Volant {
        public void fly() {
            System.out.println("我是鸟人,正在飞!");
        }
    }
    

    接口多继承

    接口支持多继承,和类继承类似,子接口extends父接口,会获得父接口

    image-20220809160855936

    接口的多继承

    interface A {
        void testa();
    }
    interface B {
        void testb();
    }
    /**接口可以多继承:接口 C 继承接口 A 和 B*/
    interface C extends A, B {
        void testc();
    }
    public class Test implements C {
        public void testc() {
        }
        public void testa() {
        }
        public void testb() {
        }
    }
    

    字符串 String 类详解

    String 类又称作不可变字符序列。
     String 位于 java.lang 包中,Java 程序默认导入 java.lang 包下的所有类。
     Java 字符串就是 Unicode 字符序列,例如字符串“Java”就是 4 个 Unicode 字
    符’J’、’a’、’v’、’a’组成的。
     Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义的类
    String,每个用双引号括起来的字符串都是 String 类的一个实例
    String e = "" ; // 空字符串
    String greeting = " Hello World ";
    "+"连接符
    
    int age = 18;
    String str = "age is" + age; //str 赋值为"age is 18"
    //这种特性通常被用在输出语句中:
    System.out.println("age is" + age)
    

    String 类和常量池

    image-20220809161241110

    字符串相等判断(以后一般判断字符串值是否相等,使用 equals())

    String g1 = "北京尚学堂";
    String g2 = "北京尚学堂";
    String g3 = new String("北京尚学堂");
    System.out.println(g1 == g2); // true
    System.out.println(g1 == g3); // false
    System.out.println(g1.equals(g3)); //true
    
    

    image-20220809161611327

    public class StringTest1 {
    public static void main(String[ ] args) {
    String s1 = "core Java";
    String s2 = "Core Java";
    System.out.println(s1.charAt(3));//提取下标为 3 的字符
    System.out.println(s2.length());//字符串的长度
    System.out.println(s1.equals(s2));//比较两个字符串是否相等
    System.out.println(s1.equalsIgnoreCase(s2));//比较两个字符串(忽略大小
    写)
    System.out.println(s1.indexOf("Java"));//字符串 s1 中是否包含 Java
    System.out.println(s1.indexOf("apple"));//字符串 s1 中是否包含 apple
    String s = s1.replace(' ', '&');//将 s1 中的空格替换成&
    System.out.println("result is :" + s);
    }
    }
    
    public class StringTest2 {
    public static void main(String[ ] args) {
    String s = "";
    String s1 = "How are you?";
    System.out.println(s1.startsWith("How"));//是否以 How 开头
    System.out.println(s1.endsWith("you"));//是否以 you 结尾
    s = s1.substring(4);//提取子字符串:从下标为 4 的开始到字符串结尾为止
    System.out.println(s);
    s = s1.substring(4, 7);//提取子字符串:下标[4, 7) 不包括 7
    System.out.println(s);
    s = s1.toLowerCase();//转小写
    System.out.println(s);
    s = s1.toUpperCase();//转大写
    System.out.println(s);
    String s2 = " How old are you!! ";
    s = s2.trim();//去除字符串首尾的空格。注意:中间的空格不能去除
    System.out.println(s);
    System.out.println(s2);//因为 String 是不可变字符串,所以 s2 不变
    }
    }
    

    字符串相等的判断

     equals 方法用来检测两个字符串内容是否相等。如果字符串 s 和 t 内容相等,则
    s.equals(t)返回 true,否则返回 false。
     要测试两个字符串除了大小写区别外是否是相等的,需要使用 equalsIgnoreCase
    方法。
     判断字符串是否相等不要使用"=="。
    
    "Hello".equalsIgnoreCase("hellO");//true  // 忽略大小写字符比较
    
    public class TestStringEquals {
    public static void main(String[ ] args) {
        String g1 = "北京尚学堂";
        String g2 = "北京尚学堂";
        String g3 = new String("北京尚学堂");
        System.out.println(g1 == g2); // true 指向同样的字符串常量对象
        System.out.println(g1 == g3); // false g3 是新创建的对象
        System.out.println(g1.equals(g3)); // true g1 和 g3 里面的字符串内容是一样
    的
    }
    }
    

    内部类

    我们把一个类放在另一个类的内部定义,称为内部类(inner class)

    image-20220809162306717

    内部类的两个要点:
     内部类提供了更好的封装。只能让外部类直接访问,不允许同一个包中的其他类直
    接访问。
     内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。但外部类
    不能访问内部类的内部属性。
    
    class Outer {
        private int age = 10;
        public void show(){
            System.out.println(age);//10
        }
        /**内部类 Inner*/
        public class Inner {
            //内部类中可以声明与外部类同名的属性与方法
            private int age = 20;
            public void show(){
                System.out.println(age);//20
            }
        }
    }
    

    非静态内部类

    1. 非静态内部类对象必须寄存在一个外部类对象里。因此,如果有一个非静态内部类
    对象那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对
    象。
    2. 非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类
    成员。
    3. 非静态内部类不能有静态方法、静态属性和静态初始化块。
    4. 成员变量访问要点:
        1. 内部类属性:this.变量名。
        2. 外部类属性:外部类名.this.变量名。
    
    /**外部类 Outer1*/
    class Outer1 {
        private int age = 10;
        public void show(){
            System.out.println(age);//10
        }
        /**内部类 Inner*/
        public class Inner1 {
            //内部类中可以声明与外部类同名的属性与方法
            private int age = 20;
            public void show(){
                System.out.println(age);//20
                System.out.println(Outer1.this.age); //10 访问外部类
                的普通属性
            }
        }
    }
    

    内部类访问

    外部类中定义内部类 new lnner();

    外部类以外地方使用非静态内部类

    静态内部类

    使用要点:
    1. 静态内部类可以访问外部类的静态成员,不能访问外部类的普通成员。
    2. 静态内部类看做外部类的一个静态成员。
    
    class Outer2{
        private int a = 10;
        private static int b = 20;
        //相当于外部类的一个静态成员
        static class Inner2{
            public void test(){
    // System.out.println(a); //静态内部类不能访问外部类的普
                通属性
                System.out.println(b); //静态内部类可以访问外部类的静
                态属性
            }
        }
    }
    public class TestStaticInnerClass {
        public static void main(String[ ] args) {
            //通过 new 外部类名.内部类名() 来创建内部类对象
            Outer2.Inner2 inner =new Outer2.Inner2();
            inner.test();
        }
    }
    

    匿名内部类

    /**
     * 测试匿名内部类
     */
    public class TestAnonymousInnerClass {
        public void test1(A a) {
            a.run();
        }
        public static void main(String[] args) {
            TestAnonymousInnerClass tac = new
                    TestAnonymousInnerClass();
            tac.test1(new A() {
                @Override
                public void run() {
                    System.out.println("匿名内部类测试! 我是新定义的
                            第一个匿名内部类!");
                }
            });
            tac.test1(new A() {
                @Override
                public void run() {
                    System.out.println("我是新定义的第二个匿名内部类
                            ");
                }
            });
        }
    }
    interface A {
        void run();
    }
    
    
    注意
     匿名内部类没有访问修饰符。
     匿名内部类没有构造方法。因为它连名字都没有那又何来构造方法呢。
    
    

    局部内部类

    /**
     * 测试局部内部类
     */
    public class TestLocalInnerClass {
        public void show() {
            //作用域仅限于该方法
            class Inner3 {
                public void fun() {
                    System.out.println("helloworld");
                }
            }
            new Inner3().fun();
        }
        public static void main(String[ ] args) {
            new TestLocalInnerClass().show();
        }
    }
    
    

    数组

    特点

    • 长度确定
    • 元素是同类型
    • 任何数据类型
    • 数组也是对象,数组变量是引用类型,数组元素是对象属性

    创建数组初始化

     声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM 才分配空间,这时
    才与长度有关。
     声明一个数组的时候并没有数组真正被创建。
     构造一个数组,必须指定长度。
    
    public class Test {
        public static void main(String args[ ]) {
            int[ ] s; // 声明数组;
            s = new int[10]; // 给数组分配空间;
            for (int i = 0; i < 10; i++) {
                s[i] = 2 * i + 1;//给数组元素赋值; 数组是对象,数组中的元素就是对象的属性
                System.out.println(s[i]);
            }
        }
    }
    

    image-20220815193857015

    class Man{
        private int age;
        private int id;
        public Man(int id,int age) {
            super();
            this.age = age;
            this.id = id;
        }
    }
    public class AppMain {
        public static void main(String[ ] args) {
            Man[ ] mans; //声明引用类型数组;
            mans = new Man[10]; //给引用类型数组分配空间;
            Man m1 = new Man(1,11);
            Man m2 = new Man(2,22);
            mans[0]=m1;//给引用类型数组元素赋值;
            mans[1]=m2;//给引用类型数组元素赋值;
        }
    }
    
    

    初始化

    数组初始化:静态初始化,动态初始化,默认初始化

    静态
    int [ ] a = { 1, 2, 3 };// 静态初始化基本类型数组;
    Man[ ] mans = { new Man(1, 1), new Man(2, 2) };// 静态初始化引用类型数组;
    

    数组动态初始化

    int[ ] a1 = new int[2];//动态初始化数组,先分配空间;
    a1[0]=1;//给数组元素赋值;
    a1[1]=2;//给数组元素赋值;
    
    

    数组常见操作

    遍历,拷贝

    数组元素下标的合法区间:[0, length-1]。我们可以通过下标来遍历数组中的元素,遍 历时可以读取元素的值或者修改元素的值。

    使用循环初始化遍历数组

    public class Test {
        public static void main(String[ ] args) {
            int[ ] a = new int[4];
    //初始化数组元素的值
            for(int i=0;i<a.length;i++){
                a[i] = 100*i;
            }
    //读取元素的值
            for(int i=0;i<a.length;i++){
                System.out.println(a[i]);
            }
        }
    }
    
    

    for-each 循环

    public class Test {
    public static void main(String[ ] args) {
        String[ ] ss = { "aa", "bbb", "ccc", "ddd" };
        for (String temp : ss) {
        System.out.println(temp);
        	}
        }
    }
    

    数组的拷贝

    public class Test {
        public static void main(String args[ ]) {
            String[ ] s = {"阿里","尚学堂","京东","搜狐","网易"};
            String[ ] sBak = new String[6];
            System.arraycopy(s,0,sBak,0,s.length);
            for (int i = 0; i < sBak.length; i++) {
                System.out.print(sBak[i]+ "\t");
            }
        }
    }
    
    import java.util.Arrays;
    public class Test {
        public static void main(String args[ ]) {
            int[ ] a = { 1, 2 };
            System.out.println(a); // 打印数组引用的值;
            System.out.println(Arrays.toString(a)); // 打印数组元素的值;
        }
    }
    

    菜鸟雷区 此处的 Arrays.toString()方法是 Arrays 类的静态方法,不是前面讲的 Object 的 toString() 方法。

    使用 Arrays 类对数组元素进行排序一

    import java.util.Arrays;
    public class Test {
        public static void main(String args[ ]) {
            int[ ] a = {1,2,323,23,543,12,59};
            System.out.println(Arrays.toString(a));
            Arrays.sort(a);
            System.out.println(Arrays.toString(a));
        }
    }
    
    
    

    使用 Arrays 类实现二分法查找法

    import java.util.Arrays;
    public class Test {
        public static void main(String[ ] args) {
            int[ ] a = {1,2,323,23,543,12,59};
            System.out.println(Arrays.toString(a));
            Arrays.sort(a); //使用二分法查找,必须先对数组进行排序;
            System.out.println(Arrays.toString(a));
            //返回排序后新的索引位置,若未找到返回负数。
            System.out.println("该元素的索引:"+Arrays.binarySearch(a, 12));
        }
    }
    
    import java.util.Arrays;
    public class Test {
    public static void main(String[ ] args) {
        int[ ] a= {1,2,323,23,543,12,59};
        System.out.println(Arrays.toString(a));
        Arrays.fill(a, 2, 4, 100); //将2到4索引的元素替换为100;
        System.out.println(Arrays.toString(a));
        }
    }
    
    声明
    public class Test {
    public static void main(String[ ] args) {
    // Java中多维数组的声明和初始化应按从低维到高维的顺序进行
    int[ ][ ] a = new int[3][ ];
    a[0] = new int[2];
    a[1] = new int[4];
    a[2] = new int[3];
    // int a1[ ][ ]=new int[ ][4];//非法
    }
    
    静态初始化
    public class Test {
        public static void main(String[ ] args) {
            int[ ][ ] a = { { 1, 2, 3 }, { 3, 4 }, { 3, 5, 6, 7 } };
            System.out.println(a[2][3]);
        }
    }
    
    动态初始化
    import java.util.Arrays;
    public class Test {
        public static void main(String[ ] args) {
            int[ ][ ] a = new int[3][ ];
    // a[0] = {1,2,5}; //错误,没有声明类型就初始化
            a[0] = new int[ ] { 1, 2 };
            a[1] = new int[ ] { 2, 2 };
            a[2] = new int[ ] { 2, 2, 3, 4 };
            System.out.println(a[2][3]);
            System.out.println(Arrays.toString(a[0]));
            System.out.println(Arrays.toString(a[1]));
            System.out.println(Arrays.toString(a[2]));
        }
    }
    
    
    
    

    数组存储表格

    image-20220815195558198

    Object[ ][ ] emps = new Object[3][ ];
    emps[0] = a1;
    emps[1] = a2;
    emps[2] = a3;
    
    import java.util.Arrays;
    public class Test {
        public static void main(String[ ] args) {
            Object[ ] a1 = {1001,"高淇",18,"讲师","2-14"};
            Object[ ] a2 = {1002,"高小七",19,"助教","10-10"};
            Object[ ] a3 = {1003,"高小琴",20,"班主任","5-5"};
            Object[ ][ ] emps = new Object[3][ ];
            emps[0] = a1;
            emps[1] = a2;
            emps[2] = a3;
            System.out.println(Arrays.toString(emps[0]));
            System.out.println(Arrays.toString(emps[1]));
            System.out.println(Arrays.toString(emps[2]));
        }
    }
    
    

    使用javabean保存表格信息

    import java.util.Arrays;
    public class Test {
        public static void main(String[ ] args) {
            Emp[] emps = {
                    new Emp(1001,"高淇",18,"讲师","2-14"),
                    new Emp(1002,"高小七",19,"助教","10-10"),
                    new Emp(1003,"高小八",20,"班主任","5-5")
            };
            for (Emp e:emps){
                System.out.println(e);
            }
        }
    }
    class Emp {
        private int id;
        private String name;
        private int age;
        private String job;
        private String hiredate;
        public Emp(int id, String name, int age, String job, String hiredate) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.job = job;
            this.hiredate = hiredate;
        }
        @Override
        public String toString() {
            return "["+id+","+name+","+age+","+job+","+hiredate+"]";
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public String getJob() {
            return job;
        }
        public void setJob(String job) {
            this.job = job;
        }
        public String getHiredate() {
            return hiredate;
        }
        public void setHiredate(String hiredate) {
            this.hiredate = hiredate;
        }
    }
    
    

    Comparable 接口

    多个对象比较,然后排序

    Comparable 接口中只有一个方法: public int compareTo(Object obj) obj 为要比较的对象

    方法中,将当前对象和 obj 这个对象进行比较,如果大于返回 1,等于返回 0,小于返回-1. (此 处的 1 也可以是正整数,-1 也可以是负整数)。 compareTo 方法的代码也比较固定:

    使用 Arrays 类对数组元素进行排序二

    import java.util.Arrays;
    public class Test {
        public static void main(String[ ] args) {
            Man[ ] msMans = { new Man(3, "a"), new Man(60, "b"), new Man(2, "c") };
            Arrays.sort(msMans);
            System.out.println(Arrays.toString(msMans));
        }
    }
    class Man implements Comparable {
        int age;
        int id;
        String name;
        public Man(int age, String name) {
            super();
            this.age = age;
            this.name = name;
        }
        public String toString() {
            return this.name;
        }
        public int compareTo(Object o) {
            Man man = (Man) o;
            if (this.age < man.age) {
                return -1;
            }
            if (this.age > man.age) {
                return 1;
            }
            return 0;
        }
    }
    

    冒泡

    二分查找

    冒泡排序算法重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就
    把他们交换过来,这样越大的元素会经由交换慢慢“浮”到数列的顶端。
    冒泡排序算法的运作如下:
    1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
    2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最
    后的元素应该会是最大的数。
    3. 针对所有的元素重复以上的步骤,除了最后一个。
    4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
    
    

    image-20220815200156254

    import java.util.Arrays;
    public class TestBubbleSort {
        public static void main(String[ ] args) {
            int[ ] values = { 3, 1, 6, 8, 9, 0, 7, 4, 5, 2 };
            bubbleSort(values);
            System.out.println(Arrays.toString(values));
        }
        public static void bubbleSort(int[ ] values) {
            int temp;
            for (int i = 0; i < values.length; i++) {
                for (int j = 0; j < values.length - 1 - i; j++) {
                    if (values[j] > values[j + 1]) {
                        temp = values[j];
                        values[j] = values[j + 1];
                        values[j + 1] = temp;
                    }
                }
            }
        }
    }
    
    
    优化
    
    
    
    import java.util.Arrays;
    public class TestBubbleSort2 {
        public static void main(String[ ] args) {
            int[ ] values = { 3, 1, 6, 8, 9, 0, 7, 4, 5, 2 };
            bubbleSort(values);
            System.out.println(Arrays.toString(values));
        }
        public static void bubbleSort2(int[ ] values) {
            int temp;
            for (int i = 0; i < values.length ; i++) {
    // 定义一个布尔类型的变量,标记数组是否已达到有序状态
                boolean flag = true;
    /*内层循环:每一趟循环都从数列的前两个元素开始进行比较,比较到无序
    数组的最后*/
                for (int j = 0; j < values.length - 1 - i; j++) {
    // 如果前一个元素大于后一个元素,则交换两元素的值;
                    if (values[j] > values[j + 1]) {
                        temp = values[j];
                        values[j] = values[j + 1];
                        values[j + 1] = temp;
                        //本趟发生了交换,表明该数组在本趟处于无序状态,需要继续比
                        较;
                        flag = false;
                    }
                }
                //根据标记量的值判断数组是否有序,如果有序,则退出;无序,则继续循
                环。
                if (flag) {
                    break;
                }
            }
        }
    }
    
    1.整个数列分成两部分:前面是无序数列,后面是有序数列。
    2.判断每一趟是否发生了数组元素的交换,如果没有发生,则说明此时数组已经有
    序,无需再进行后续趟数的比较了。此时可以中止比较
    

    二分

    import java.util.Arrays;
    public class TestBinarySearch {
        public static void main(String[ ] args) {
            int[ ] arr = { 30,20,50,10,80,9,7,12,100,40,8};
            int searchWord = 20; // 所要查找的数
            Arrays.sort(arr); //二分法查找之前,一定要对数组元素排序
            System.out.println(Arrays.toString(arr));
            System.out.println(searchWord+"元素的索引:
                    "+binarySearch(arr,searchWord));
        }
        public static int binarySearch(int[ ] array, int value){
            int low = 0;
            int high = array.length - 1;
            while(low <= high){
                int middle = (low + high) / 2;
                if(value == array[middle]){
                    return middle; //返回查询到的索引位置
                }
                if(value > array[middle]){
                    low = middle + 1;
                }
                if(value < array[middle]){
                    high = middle - 1;
                }
            }
            return -1; //上面循环完毕,说明未找到,返回-1
    	}
    }
    

    异常机制

    public class Test {
     public static void main(String[] args) {
         System.out.println("111");
         int a = 1/0;
         System.out.println("222");
         }
    }
    
    结果报错
    

    修改

    public class Test {
        public static void main(String[] args) {
            System.out.println("111");
            try {
                int a = 1/0;
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("222");
        }
    }
    
    
    
    ava 是采用面向对象的方式来处理异常的。处理过程:
     抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个
    对象,停止当前执行路径,并把异常对象提交给 JRE。
     捕获异常:JRE 得到该异常后,寻找相应的代码来处理该异常。JRE 在方法的调用
    栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止。
    

    异常分类

    Error 与 Exception 的区别
     我开着车走在路上,一头猪冲在路中间,我刹车。这叫一个异常。
     我开着车在路上,发动机坏了,我停车,这叫错误。系统处于不可恢复的崩溃状态。发
    动机什么时候坏?我们普通司机能管吗?不能。发动机什么时候坏是汽车厂发动机制
    造商的事
    
    Exception 是程序本身能够处理的异常。
    Exception 类是所有异常类的父类,其子类对应了各种各样可能出现的异常事件。 通
    常 Java 的异常可分为:
    1. RuntimeException 运行时异常
    2. CheckedException 已检查异常
    
    

    解决 ClassCastException 的典型方式:

    public class Test5 {
    public static void main(String[ ] args) {
        Animal a = new Dog();
        if (a instanceof Cat) {
        Cat c = (Cat) a;
        	}
        }
    }
    
    

    ArrayIndexOutOfBoundsException 异常

    public class Test6 {
        public static void main(String[ ] args) {
            int[ ] arr = new int[5];
            System.out.println(arr[5]);
        }
    }
    
    //增加判断解决越界问题
    

    NumberFormatException 异常

    public class Test7 {
    public static void main(String[ ] args) {
        String str = "1234abcf";
        System.out.println(Integer.parseInt(str));
        }
    }
    
    
    import java.util.regex.Matcher;
            import java.util.regex.Pattern;
    public class Test7 {
        public static void main(String[ ] args) {
            String str = "1234abcf";
            Pattern p = Pattern.compile("^\\d+$");
            Matcher m = p.matcher(str);
            if (m.matches()) { // 如果 str 匹配代表数字的正则表达式,才会转换
                System.out.println(Integer.parseInt(str));
            }
        }
    }
    
    
    try:
    try 语句指定了一段代码,该段代码就是异常捕获并处理的范围。在执行过程中,当任
    意一条语句产生异常时,就会跳过该条语句中后面的代码。代码中可能会产生并抛出一种或
    几种类型的异常对象,它后面的 catch 语句要分别对这些异常做相应的处理。
    一个 try 语句必须带有至少一个 catch 语句块或一个 finally 语句块。
    当异常处理的代码执行结束以后,不会回到 try 语句去执行尚未执行的代码。
    
    catch:
     每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类
    型的异常对象。
     catch 捕获异常时的捕获顺序:
     如果异常类之间有继承关系,先捕获子类异常再捕获父类异常。
     finally:
     不管是否发生了异常,都必须要执行。
     通常在 finally 中关闭已打开的资源,比如:关闭文件流、释放数据库连接等。
    

    try-catch-finally 语句块的执行过程详细分析:

    程序首先执行可能发生异常的 try 语句块。如果 try 语句没有出现异常则执行完后跳至
    finally 语句块执行;如果 try 语句出现异常,则中断执行并根据发生的异常类型跳至相应的
    catch 语句块执行处理。catch 语句块可以有多个,分别捕获不同类型的异常。catch 语句
    块执行完后程序会继续执行 finally 语句块。finally 语句是可选的,如果有的话,则不管是
    否发生异常,finally 语句都会被执行。
    
    import java.io.FileNotFoundException;
            import java.io.FileReader;
            import java.io.IOException;
    public class Test8 {
        public static void main(String[ ] args) {
            FileReader reader = null;
            try {
                reader = new FileReader("d:/a.txt");
                char c = (char) reader.read();
                char c2 = (char) reader.read();
                System.out.println("" + c + c2);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    异常的处理方式之二:声明异常(throws 子句)

    package com.bjsxt;
            import java.io.FileNotFoundException;
            import java.io.FileReader;
            import java.io.IOException;
    public class Test9 {
        public static void main(String[ ] args) {
            try {
                readFile("joke.txt");
            } catch (FileNotFoundException e) {
                System.out.println("所需文件不存在!");
            } catch (IOException e) {
                System.out.println("文件读写错误!");
            }
        }
        public static void readFile(String fileName) throws
                FileNotFoundException,
                IOException {
            FileReader in = new FileReader(fileName);
            int tem = 0;
            try {
                tem = in.read();
                while (tem != -1) {
                    System.out.print((char) tem);
                    tem = in.read();
                }
            } finally {
                if(in!=null) {
                    in.close();
                }
            }
        }
    }
    
    
    注意事项
     方法重写中声明异常原则:子类重写父类方法时,如果父类方法有声明异常,那么子类
    声明的异常范围不能超过父类声明的范围
    

    try-with-resource 自动关闭 AutoClosable 接口的资源

    package com.bjsxt;
            import java.io.FileReader;
    public class Test8 {
        public static void main(String[ ] args) {
            try(FileReader reader = new FileReader("d:/a.txt");) {
                char c = (char) reader.read();
                char c2 = (char) reader.read();
                System.out.println("" + c + c2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    

    自定义异常

    在程序中,可能会遇到 JDK 提供的任何标准异常类都无法充分描述清楚我们想要
    表达的问题,这种情况下可以创建自己的异常类,即自定义异常类。
     自定义异常类只需从 Exception 类或者它的子类派生一个子类即可。
     自定义异常类如果继承 Exception 类,则为 CheckedException 异常,必须对其
    进行处理;如果不想处理,可以让自定义异常类继承运行时异常
    RuntimeException 类。
     习惯上,自定义异常类应该包含 2 个构造器:一个是默认的构造器,另一个是带有
    详细信息的构造器。
    
    

    例子

    class Person {
        private String name;
        private int age;
        public void setName(String name) {
            this.name = name;
        }
        public void setAge(int age) throws IllegalAgeException {
            if (age < 0) {
                throw new IllegalAgeException("人的年龄不应该为负数");
            }
            this.age = age;
        }
        public String toString() {
            return "name is " + name + " and age is " + age;
        }
    }
    public class TestMyException {
        public static void main(String[ ] args) {
            Person p = new Person();
            try {
                p.setName("Lincoln");
                p.setAge(-1);
            } catch (IllegalAgeException e) {
                e.printStackTrace();
            }
            System.out.println(p);
        }
    }
    
    

    常用类

    基本数据类型的包装类

    我们经常要将基本数据转换成对象,解决这个问题

    image-20220815204001382

    public class WrapperClassTest {
        public static void main(String[ ] args) {
            Integer i = new Integer(10); //从 java9 开始被废弃
            Integer j = Integer.valueOf(50); //官方推荐
        }
    }
    

    包装类应用

    public class Test {
        /** 测试 Integer 的用法,其他包装类与 Integer 类似 */
        void testInteger() {
    // 基本类型转化成 Integer 对象
            Integer int1 = new Integer(10); //已经被废弃,不推荐使用
            Integer int2 = Integer.valueOf(20); // 官方推荐这种写法
    // Integer 对象转化成 int
            int a = int1.intValue();
    // 字符串转化成 Integer 对象
            Integer int3 = Integer.parseInt("334");
            Integer int4 = new Integer("999");
    // Integer 对象转化成字符串
            String str1 = int3.toString();
            // 一些常见 int 类型相关的常量
            System.out.println("int 能表示的最大整数:" + Integer.MAX_VALUE);
        }
        public static void main(String[ ] args) {
            Test test = new Test();
            test.testInteger();
        }
    }
    
    

    自动装箱和拆箱

    自动装箱(autoboxing)和拆箱(unboxing):将基本数据类型和包装类自动转换。

    自动装箱:
    基本类型的数据处于需要对象的环境中时,会自动转为“对象”。
    我们以 Integer 为例:
    Integer i = 5
    编译器会自动转成:Integer i = Integer.valueOf(5),这就是 Java 的自动装箱。
    
    自动拆箱:
    每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用 intValue()、
    doubleValue()等转型方法。
    Integer i = Integer.valueOf(5);
    int j = i;
    编译器会自动转成:int j = i.intValue();
    这样的过程就是自动拆箱。 
    

    自动装箱/拆箱的本质是: 自动装箱与拆箱的功能是编译器来帮忙,编译器在编译时依据您所编写的语法,决定是 否进行装箱或拆箱动作。

    Integer i = 100;//自动装箱
    //相当于编译器自动为您作以下的语法编译:
    Integer i = Integer.valueOf(100);//调用的是 valueOf(100),而不是 new Integer(100)
    
    Integer i = 100;
    int j = i;//自动拆箱
    //相当于编译器自动为您作以下的语法编译:
    int j = i.intValue()
    

    包装类空指针异常问题

    public class Test1 {
    public static void main(String[ ] args) {
        Integer i = null;
        int j = i;
        }
    }
    
    
    public class Test1 {
    public static void main(String[ ] args) {
        /*示例 8-5 的代码在编译时期是合法的,但是在运行时期会有错误
        因为其相当于下面两行代码*/
        Integer i = null; 
        int j = i.intValue();
        }
    }
    
    

    包装类缓存问题

    整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓 存处理,其目的是提高效率

    【示例】Integer 类相关源码
    public static Integer valueOf(int i) {
     if (i >= IntegerCache.low && i <= IntegerCache.high)
     return IntegerCache.cache[i + (-IntegerCache.low)];
     return new Integer(i);
    }
    
    这段代码中我们需要解释下面几个问题:
    1. IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。
    2. 一般情况下 IntegerCache.low为-128,IntegerCache.high为127,
    IntegerCache.cache为内部类的一个静态属性,如示例所示。
    
    【示例】IntegerCache 类相关源码
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[ ];
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
        private IntegerCache() {}
    }
    由上面的源码我们可以看到,静态代码块的目的就是初始化数组cache的,这个过程
            会在类加载时完成。
            下面我们做一下代码测试,如示例所示。
            【示例 8-9】包装类的缓存测试
    public class Test3 {
        public static void main(String[ ] args) {
            Integer in1 = -128;
            Integer in2 = -128;
            System.out.println(in1 == in2);//true 因为 123 在缓存范围内
            System.out.println(in1.equals(in2));//true
            Integer in3 = 1234;
            Integer in4 = 1234;
            System.out.println(in3 == in4);//false 因为 1234 不在缓存范围内
            System.out.println(in3.equals(in4));//true
        }
    }
    
    
    自动装箱调用的是 valueOf()方法,而不是 new Integer()方法。
     自动拆箱调用的 xxxValue()方法。
     包装类在自动装箱时为了提高效率,对于-128~127 之间的值会进行缓存处理。超过范
    围后,对象之间不能再使用==进行数值的比较,而是使用 equals 方法。
    

    字符串相关类

    String 类代表不可变的字符序列 StringBuilder 类和 StringBuffer 类代表可变字符序列。

    String 类源码分析
    String 类对象代表不可变的 Unicode 字符序列,因此我们可以将 String 对象称为“不
    可变对象”。 那什么叫做“不可变对象”呢?指的是对象内部的成员变量的值无法再改变。
    
    

    image-20220815205014653

    我们发现字符串内容全部存储到 value[ ]数组中,而变量 value 是 final 类型的,也就
    是常量(即只能被赋值一次)。 这就是“不可变对象”的典型定义方式。
    我们发现在前面学习 String 的某些方法,比如:substring()是对字符串的截取操作,
    但本质是读取原字符串内容生成了新的字符串。测试代码如下:
    
    
    
    【示例】String 类的简单使用
    public class TestString1 {
        public static void main(String[ ] args) {
            String s1 = new String("abcdef");
            String s2 = s1.substring(2, 4);
    // 打印:ab199863
            System.out.println(Integer.toHexString(s1.hashCode()));
    // 打印:c61, 显然 s1 和 s2 不是同一个对象
            System.out.println(Integer.toHexString(s2.hashCode()));
        }
    }
    在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的
            拼接。因此,在使用==进行 String 对象之间的比较时,我们要特别注意,如示例所示。
            【示例】字符串常量拼接时的优化
    public class TestString2 {
        public static void main(String[ ] args) {
    //编译器做了优化,直接在编译的时候将字符串进行拼接
            String str1 = "hello" + " java";//相当于 str1 = "hello java";
            String str2 = "hellojava";
            System.out.println(str1 == str2);//true
            String str3 = "hello";
            String str4 = " java";
    //编译的时候不知道变量中存储的是什么,所以没办法在编译的时候优化
            String str5 = str3 + str4;
            System.out.println(str2 == str5);//false
        }
    }
    
    
    

    StringBuffer 和 StringBuilder 可变字符序列

    StringBuffer 和 StringBuilder 都是可变的字符序列。
     StringBuffer 线程安全,做线程同步检查, 效率较低。
     StringBuilder 线程不安全,不做线程同步检查,因此效率较高。建议采用该类
    
    常用方法列表:
     重载的 public StringBuilder append(…)方法
     可以为该 StringBuilder 对象添加字符序列,仍然返回自身对象。
     方法 public StringBuilder delete(int start,int end)
     可以删除从 start 开始到 end-1 为止的一段字符序列,仍然返回自身对象。
     方法 public StringBuilder deleteCharAt(int index)
     移除此序列指定位置上的 char,仍然返回自身对象。
     重载的 public StringBuilder insert(…)方法
    可以为该 StringBuilder 对象在指定位置插入字符序列,仍然返回自身对象。
     方法 public StringBuilder reverse()
     用于将字符序列逆序,仍然返回自身对象。
     方法 public String toString() 返回此序列中数据的字符串表示形式。
     和 String 类含义类似的方法
    
    public int indexOf(String str)
    public int indexOf(String str,int fromIndex)
    public String substring(int start)
    public String substring(int start,int end)
    public int length() 
    char charAt(int index)
    
    【示例】StringBuffer/StringBuilder 基本用法
    public class TestStringBufferAndBuilder{
        public static void main(String[ ] args) {
    /**StringBuilder*/
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 7; i++) {
                sb.append((char) ('a' + i));//追加单个字符
            }
            System.out.println(sb.toString());//转换成 String 输出
            sb.append(", I can sing my abc!");//追加字符串
            System.out.println(sb.toString());
    /**StringBuffer,下面的方法同样适用 StringBuilder*/
            StringBuffer sb2 = new StringBuffer("北京尚学堂");
            sb2.insert(0, "爱").insert(0, "我");//插入字符串
            System.out.println(sb2);
            sb2.delete(0, 2);//删除子字符串
            System.out.println(sb2);
            sb2.deleteCharAt(0).deleteCharAt(0);//删除某个字符
            System.out.println(sb2.charAt(0));//获取某个字符
            System.out.println(sb2.reverse());//字符串逆序
        }
    }
    
    

    String 使用的陷阱

    String 一经初始化后,就不会再改变其内容了。对 String 字符串的操作实际上是对其
    副本(原始拷贝)的操作,原来的字符串一点都没有改变。比如:
     String s ="a"; 创建了一个字符串
    s = s+"b"; 实际上原来的"a"字符串对象已经丢弃了,现在又产生了另一个字符串
    s+"b"(也就是"ab")。 如果多次执行这些改变串内容的操作,会导致大量副本字符串对象
    存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的时间和空间性能,
    甚至会造成服务器的崩溃。
    相反,StringBuilder 和 StringBuffer 类是对原字符串本身操作的,可以对字符串进行
    修改而不产生副本拷贝或者产生少量的副本。因此可以在循环中使用。
    
    
    【示例】String 和 StringBuilder 在字符串频繁修改时的效率测试
    public class Test {
        public static void main(String[ ] args) {
    /**使用 String 进行字符串的拼接*/
            String str8 = "";
            long num1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
            long time1 = System.currentTimeMillis();//获取系统的当前时间
            for (int i = 0; i < 5000; i++) {
                str8 = str8 + i;//相当于产生了 5000 个对象
            }
            long num2 = Runtime.getRuntime().freeMemory();
            long time2 = System.currentTimeMillis();
            System.out.println("String 占用内存 : " + (num1 - num2));
            System.out.println("String 占用时间 : " + (time2 - time1));
    /**使用 StringBuilder 进行字符串的拼接*/
            StringBuilder sb1 = new StringBuilder("");
            long num3 = Runtime.getRuntime().freeMemory();
            long time3 = System.currentTimeMillis();
            for (int i = 0; i < 5000; i++) {
                sb1.append(i);
            }
            long num4 = Runtime.getRuntime().freeMemory();
            long time4 = System.currentTimeMillis();
            System.out.println("StringBuilder 占用内存 : " + (num3 - num4));
            System.out.println("StringBuilder 占用时间 : " + (time4 - time3));
        }
    }
    

    Date 时间类(java.util.Date)

    在标准 Java 类库中包含一个 Date 类。它的对象表示一个特定的瞬间,精确到毫秒。
     Date() 分配一个 Date 对象,并初始化此对象为系统当前的日期和时间,可以精
    确到毫秒)。
     Date(long date) 分配 Date 对象并初始化此对象,以表示自从标准基准时间以
    来的毫秒数。
     boolean equals(Object obj) 比较两个日期的相等性。
     long getTime() 返回毫秒数。
     String toString() 把此 Date 对象转换为以下形式的 String:
     dow mon dd hh:mm:ss zzz yyyy 其中:dow 是一周中的某一天 。
    
    
    long nowNum = System.currentTimeMillis(); //当前时刻对应的毫秒数
    Date d = new Date(); //当前时刻的对象
    System.out.println(d.getTime()); //返回时间对应的毫秒数
    Date d2 = new Date(1000L * 3600 * 24 * 365 * 150); //距离 1970
    年 150 年
    System.out.println(d2);
    
    
  • 相关阅读:
    Linux 简介
    5设计模式之桥接模式(结构模式)
    2设计模式之简单工厂模式(构造模式)
    3异步和多线程
    1设计模式之单例模式
    性能测试知多少---吞吐量
    NumberFormat DecimalFormat
    Java 005 枚举
    log4j
    Java Basic
  • 原文地址:https://www.cnblogs.com/abldh12/p/16566566.html
Copyright © 2020-2023  润新知