how2j学习总结-未完

Markdown

标题

#+空格+ 一级标题

字体

粗体 %两边两个*

斜体 %两边一个*

斜体+加粗 %两边三个*

删除线 %两边两个~

引用

文本引用效果 %用>+内容

分割线



%三个- 或者三个~

图片

logo

% ! + [] + ()

超链接

点击跳转

%[] + ()

列表

  1. A

  2. B %数字+点+空格

  • A
  • B %减号+空格

表格

名字|性别|生日

–|--|–|

张三|男|7.2 %源代码模式删除空格

名字性别生日
张三7.2

代码

public

%三个`

预科

冯诺依曼体系结构

结构图

系统软件

DOS(磁盘操作系统)、windows、Linux、Unix、Mac、Android、iOS

快捷键

alt + F4 %关闭当前窗口

win + E %打开我的电脑

win + D %最小化所有页面

DOS

  1. win + R 输入cmd
  2. 任意文件夹下面,按住shift+鼠标右键,打开powershell
  3. 资源管理器地址栏前面加cmd
 D: %盘符+冒号进入磁盘
 dir %显示目录
 cd + 地址 %change directory进入
 cd + /d + D:\电影 %跨盘符切换
 cd.. %返回上一级
 cls %clean screen清理屏幕
 ipconfig %查看电脑ip config配置
 
 #打开应用
 calc
 mspaint
 notepad
 
 #ping 命令
 ping www.google.com
 
 #创建文件夹
 md
 
 #文件
 cd 空格 >a.txt
 del a.txt
 rd

摩尔定律

第三代语言

C语言时典型的面向过程的语言,C++、Java时典型的面向对象的语言。

Java入门

高可用 高性能 高并发

特性

简单性、面向对象、可移植性、高性能、分布式、动态性、多线程、安全性、健壮性

跨平台

write once、run anywhere

版本

JavaSE:标准版(桌面程序,控制台开发)

JavaME:嵌入式开发(几乎淘汰)

JavaEE:企业级开发(web端,服务器开发)

JDK、JRE、JVM

JDK:Java Development Kit %开发者工具箱

JRE:Java Runtine Environment %运行环境

JVM:Java Virtual Machine %虚拟机

安装开发环境

  1. 配置环境变量JAVA_HOME
  2. 配置path变量

测试安装成功

cmd–> java -version

目录

bin:可执行程序

include:引入C语言中的一些头文件

jre:运行环境

lib:库文件

src:资源文件 类

hello world

记事本编辑editplus配置:关闭文件类型及语法自动完成;关闭自动保存创建备份

public class hello{
	public static void main(String[] args){
		System.out.print("hello world");
	}
}

编译javac ().java

运行java ()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olU8pZWX-1633015148278)(F:\学习\code\hello.png)]

Java程序运行机制

compile编译型 解释型

IDEA

集成开发环境IDE(Integrated Development Environment ):用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面等工具。

Java基础

注释

  1. 单行注释://
  2. 多行注释:/* ··· */
  3. 文档注释:/** … */

有趣的代码注释

标识符

Java所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。

标识符以字母、美元符$、下划线开始。首字符之后可以是任意组合(字母、下划线、美元符、数字)。(变量可以中文)

大小敏感。

关键字列表:

数据类型

强类型语言:要求变量使用严格符合规定,所有变量必须先定义才使用。

弱类型语言:VB、JS

八大基本数据类型

byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-2^7 ~ 2^7-1(-128~127),默认值0

short:短整型,在内存中占16位,即2个字节,取值范围-2^15 ~ 2^15-1(-32768~32767),默认值0

int:整型,用于存储整数,在内在中占32位,即4个字节,取值范围-2^31 ~ 2^31-1 (-2147483648~2147483647)约21亿,默认值0

long:长整型,在内存中占64位,即8个字节-263~263-1,默认值0L

Java语言的整型常数默认为int型,声明long型常量可以后加‘ l ’或‘ L ’ 。

float:浮点型,在内存中占32位,即4个字节,用于存储带小数点的数字(与double的区别在于float类型有效小数点只有6~7位),默认值0

double:双精度浮点型,用于存储带有小数点的数字,在内存中占64位,即8个字节,默认值0

注意: 默认的小数值是double类型的

注意: 默认的小数值是double类型的
所以 float f = 54.321会出现编译错误,因为54.321的默认类型是 double,其类型 长度为64,超过了float的长度32
在数字后面加一个字母f,直接把该数字声明成float类型
float f2 = 54.321f,
这样就不会出错了

当以f或者F结尾的时候,就表示一个float类型的浮点数,否则就是double类型(以d或者D结尾,写不写都可以)。
浮点数还可以用E或者e表示(科学计数法)
e2表示10的二次方,即100
1.234e2 = 1.234x100

char:字符型,用于存储单个字符,占16位,即2个字节,取值范围0~65535,默认值为空

字符的字面值放在单引号中

字符串的字面值放在双引号中

需要注意的是,\表示转义,比如需要表示制表符,回车换行,双引号等就需要用 \t \r \n " 的方式进行

 public static void main(String[] args) {
     String name = "盖伦";
     char a= 'c';

     //以下是转义字符
     char tab = '\t'; //制表符
     char carriageReturn = '\r'; //回车
     char newLine = '\n'; //换行
     char doubleQuote = '\"'; //双引号
     char singleQuote = '\''; //单引号
     char backslash = '\\'; //反斜杠

 }
}

boolean:布尔类型,占1个字节,用于判断真或假(仅有两个值,即true、false),默认值false

byte short int long 
float double
char
boolean
public class Helloword {
        public static void main(String[] args) {
            long val = 26L; //以L结尾的字面值表示long型
            int decVal = 26; //默认就是int型
            int hexVal = 0x1a; //16进制
            int oxVal = 032; //8进制
            int binVal = 0b11010; //2进制
            System.out.println(oxVal+val);
        }
    }

引用类型

String:String类型其实并不是基本类型,但是它是如此广泛的被使用,常常被误以为是一种基本类型。
String类型是Immutable的,一旦创建就不能够被改变

接口

数组


数据类型拓展

System.out.println(Integer.toBinaryString(i2)); //查看一个整数对应的二进制的方法

整数拓展:二进制0b、十进制、八进制0、十六进制0x(0-9,A-F)

浮点数拓展:float有限、离散、舍入误差、大约、接近但不等于(最好不要使用float);银行常用BigDecimal

System.out.println((int)c); //强制转码

所有字符本质还是数字

编码Unicode表:(97=a、65=A) 2字节 0-65536(2^16)

转义字符

字符作用
\n换行 \n 换行符,使光标定位到下一行。
\r回车 \r 回车符,使光标回到当前行的行首。如果之前该行有内容,则会被覆盖;
\t制表 (相当于tab)
\f换页

\t 制表符(相当于键盘上按下TAB键之间的距离,一般为8个空格。)

使用制表符是把输出的切入点移动到下一个能被8整除的位置上。
即当打印小于八格的结果,用空格补足八格再打印下一个结果;当大于等于八格,小于十六格,补足十六格;以此类推。

\n换行符

public class text02 {
    public static void main(String[] args) {
        System.out.println("1234567 12345678 0.1234567 0.12345678");
        System.out.println("1234567\t12345678\t0.12345\t0.12345678");
        System.out.println("1234567 \n12345678\n0.1234567\n0.12345678");
    }
}

类型转换

小数的优先级大于整数

byte,short,char->int->long->float->double

//强制转换 (类型)变量名 高到低 要避免内存溢出

//自动转换 低到高

运算时如果有一个参数是L或者double,结果位L或者double

运算时如果比int低,结果均为int

注意

  1. 不能对布尔值转换
  2. 不能把对象类型转换为不相干类型
  3. 高到低时,强制转换
  4. 可能出现内存溢出或者精度问题

int money=10_0000_0000; JDK7新特性,数字之间可以用下划线分割

变量

type varName [=value] [{,varName[=value]}];//数据类型 变量名 =值;可以使用逗号隔开来声明多个同类型变量。

当一个变量被声明在类下面
变量就叫做字段 或者属性成员变量Field
比如变量i,就是一个属性。
那么从第2行这个变量声明的位置开始,整个类都可以访问得到
所以其作用域就是从其声明的位置开始的整个类

声明在方法内的变量,叫做局部变量
其作用域在声明开始的位置,到其所处于的块结束位置

声明在方法内的变量,叫做局部变量
其作用域在声明开始的位置,到其所处于的块结束位置

public class HelloWorld {
 
    public void method1() {
        int i  = 5;  //其作用范围是从声明的第4行,到其所处于的块结束12行位置
        System.out.println(i);
        {            //子块
            System.out.println(i); //可以访问i
            int j = 6;
            System.out.println(j); //可以访问j
        }
        System.out.println(j); //不能访问j,因为其作用域到第10行就结束了
    }
 
}

变量作用域

  1. 类变量:从属类
  2. 实例变量:在类里,从属于对象,如果不初始化,会变成默认值(基本类型0 0.0 Boolean默认false,此外都是null
  3. 局部变量:在方法内,必须声明和初始化
public class Variable{
    static int salary = 2500; //类变量
    String str = "hello"; //实例变量
    int age;
    
    public void method(){
        int i = 0; //局部变量
        System.out.println(i);
        
        //实例变量 变量类型 变量名字 = new Variable();
        Variable variable = new Variable();
        System.out.println(variable.str); //输出null
        System.out.println(variable.age); //输出0
        
        //类变量 static
        System.out.println(salary);
    }
}

常量

初始化(initialize)后不能再改变值

final 常量名=值;//一般使用大写字母

如果在声明的时候未赋值,那么可以在后面代码进行唯一的一次赋值

inal 除了修饰变量,还可以修饰类修饰方法

final修饰的类不能被继承 final定义的方法不能被重写 final定义的常量不能被重写赋值

final int[] a = {1,2,3} 这种栈里a变量空间存放的是堆内存的地址。地址是不可变的,但是数组里的值是可以变

final 修饰的变量在方法中,可以先初始化再赋值。 但是如果是全局变量,必须在初始化的时赋值,不然会报错。

当final修饰形参时,不能在方法里赋值,因为在调用方法的时候,就一定会第一次赋值了,后面不能再进行多次赋值

public class text03 {
    //修饰符,不存在先后顺序
    final static double PI = 3.14;
    //static final double PI = 3.14;

    public static void main(String[] args) {
        System.out.println(PI);
    }
}

表达式

表达式是由变量、操作符以及方法调用所构成的结构。

; 也是一个完整的表达式

从**{** 开始 到对应的**}** 结束,即一个块

public class HelloWorld { //类对应的块
    public static void main(String[] args) { //主方法对应的块
        System.out.println("abc");
    }
}

命名规范

类成员变量:首字母小写和驼峰原则:monthSalary 除了第一个单词以外,后面的单词首字母大写

局部变量:首字母小写和驼峰原则

常量:大写字母和下划线:MAX_VALUE

类名:首字母大写和驼峰原则:GoodMan

方法名:首字母小写和驼峰原则:runRun()

运算符

算术运算符:+、-、*、/、%(模运算,取余)、++、–

赋值运算符:=

关系运算符:>、<、>=、<=、==、!=、instanceof

逻辑运算符:&&、||、!

& &&长路与 和 短路与
| ||长路或 和 短路或
!取反
^异或^
public static void main(String[] args){
//长路与  无论第一个表达式的值是true或者false,第二个的值,都会被运算
//短路与 只要第一个表达式的值是false的,第二个表达式的值,就不需要进行运算了

//长路或  无论第一个表达式的值是true或者false,第二个的值,都会被运算
//短路或 只要第一个表达式的值是true的,第二个表达式的值,就不需要进行运算了
int i = 2;
System.out.println( i== 1 | i++ ==2  ); //无论如何i++都会被执行,所以i的值变成了3
System.out.println(i);

}

位运算符:&、|、^、~、>>、<<、>>>(了解)

Integer.toBinaryString()一个整数的二进制表达
|位或
&位与
^异或
~取非
<< >>左移 右移
>>>带符号右移与无符号右移
public class HelloWorld {
 public static void main(String[] args) {
     int i  =5;
     int j = 6;
     System.out.println(Integer.toBinaryString(i)); //5的二进制是 101
     System.out.println(Integer.toBinaryString(j)); //6的二进制是110
     System.out.println(i^j); //所以 5^6 对每一位进行或运算,得到 011->3

     System.out.println(i^0); //任何数和自己进行异或 都等于 0
     System.out.println(i^i); //任何数和0 进行异或 都等于自己
 }
}

条件运算符 ?、:

扩展赋值运算符:+=、-=、*=、/=

Ctrl+D 在IDEA中复制当前行到下一行

//运算时如果有一个参数是L或者double,结果位L或者double
//运算时如果比int低,结果均为int,包括char
public class Text04 {
    public static void main(String[] args) {
        long a = 123456789L;
        int b = 12356;
        short c = 100;
        byte d = 5;
        double e = 3.14;
        char f = 'g';
        System.out.println(a+b);//Long
        System.out.println(b+c);//Int
        System.out.println(c+d);//Int
        System.out.println(d+e);//Double
        System.out.println(d+f);//Int

    }
}

一元运算符
int b = a++; //执行完这段代码后,先给b赋值,再自增
int c = ++a; //执行完这段代码前,先自增,再赋值

幂运算需要使用工具类

Math.pow(3,2); //9

逻辑与会造成短路运算

public class Text05 {    
    public static void main(String[] args) {        
        boolean a = true;        
        boolean b = false;        
        System.out.println("a && b: "+ (a && b)); //逻辑与运算:两个都为真,结果真;若第一个为假,会造成短路运算
        System.out.println("a || b: "+ (a || b));        
        System.out.println("!(a && b): "+ !(a && b));        //短路运算        
        int c = 5;        
        boolean d = (c<4) && (c++<4);        
        System.out.println(d);        
        System.out.println(c); //5    
    }
}

位运算是对二进制位的运算

A&B 与

A|B 或

A^B 异或(相同为0,不同为1)

~B 非

最快速度运算2*8(位运算,效率高)

0000 0000    00000 0001    10000 0010    20000 0100    40000 1000    8

<< 左移 (*2) >>右移(/2)

>>:带符号右移。正数右移高位补0,负数右移高位补1。比如:

4>>1,结果为2;

-4>>1,结果为-2.

>>>:无符号右移。无论正数还是负数,高位通通补0.

对于正数而言,>>和>>>没有区别。

负数转换为二进制,就是将其相反数(正数)的补码的每一位变反(1变0,0变1)最后将变完了的数值加1,就完成了负数的补码运算。这样就变成了二进制。

对于负数而言,-2>>>1,结果是2147483647(Integer.MAX_VALUE)

-1>>>1,结果是2147483647(Integer.MAX_VALUE)

a+=b; //a=a+b

a-=b; //a=a-b

+=即自加
i+=2;
等同于
i=i+2;
其他的 -= , *= , /= , %= , &= , |= , ^= , >>= , >>>= 都是类似

public class Text06 {    
    public static void main(String[] args) {        
        int a = 10;        
        int b = 20;        
        //字符串连接符 + , String        
        System.out.println(a+b); //30        
        System.out.println(""+a+b); //1020        
        System.out.println(a+b+""); //30    
    }
}

三元运算符 条件运算符

x ?y :z //如果x==ture,则结果为y,否则结果为z

public class Text07 {    
    public static void main(String[] args) {        
        int score = 80;        
        String type = score < 60 ? "不及格" : "及格";        
        System.out.println(type);    
    }
}

优先级

Java 语言中运算符的优先级共分为 14 级,其中 1 级最高,14 级最低。在同一个表达式中运算符优先级高的先执行。

**结合性:**当一个运算对象两侧的运算符优先级别相同时,则按运算符的结合性来确定表达式的运算顺序。

优先级运算符结合性
1()、[]、{}从左向右
2!、+、-、~、++、–从右向左
3*、/、%从左向右
4+、-从左向右
5«、»、>>>从左向右
6<、<=、>、>=、instanceof从左向右
7==、!=从左向右
8&从左向右
9^从左向右
10|从左向右
11&&从左向右
12||从左向右
13?:从右向左
14=、+=、-=、*=、/=、&=、|=、^=、~=、«=、»=、>>>=从右向左

包机制

包的本质就是一个文件夹

一般使用公司域名倒置作为包名:com.baidu.www

为了使用某一个包的成员,我们需要再Java程序中明确导入该包。使用“import”语句可以完成此功能。

import package1.package2.classname;

import package1.package2.*; //导入这个包下所有的类

JavaDoc(/**… */)

加在类上面就是类的注释,加在方法上面就是方法的注释

@author

@version 版本号

@since 指明需要最早使用的jdk版本

@param 参数名

@return 返回值情况

@throws 异常抛出情况

打开cmd,进入该类所在的目录,然后输入

javadoc -encoding UTF-8 -charset UTF-8 Text08.java

  • -encoding UTF-8:表示设置编码。
  • -charset UTF-8:也表示设置编码。

IDEA

Java流程控制

Scanner对象

注意: 使用Scanner类,需要在最前面加上

import java.util.Scanner;

实现程序和人的交互,java.util.Scanner是Java5的新特征,可以通过Scanner类来获取用户的输入。

Scanner s = new Scanner(System.in);

通过Scanner类的next()与nextLine()方法获取输入的字符串

String str = scanner.nextLine();

读取前,需要使用hasNext()与hasNextLine()判断是否还有输入数据。

凡是属于IO流的类如果不关闭会一直占用资源 即输入输出流

scanner.close();

package com.wang.operator;

import java.util.Scanner;

public class Demo01 {

    public static void main(String[] args) {

        //创建一个扫描器对象,用于接收键盘数据
        Scanner scanner = new Scanner(System.in);

        System.out.println("使用next方式接收:");

        //判断用户有没有输入字符串
        if(scanner.hasNext()){
            //使用next方式接收
            String str = scanner.next();
            System.out.println("输入的内容为:"+str); //+为连接符
        }

        //凡是属于IO流的类如果不关闭会一直占用资源 即输入输出流
        scanner.close();
    }
}

next()读取到空格结束;

nextLine()读取到enter结束;

next() 与 nextLine() 区别

next():

  • 1、一定要读取到有效字符后才可以结束输入。
  • 2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
  • 3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
  • next() 不能得到带有空格的字符串。

nextLine():

  • 1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
  • 2、可以获得空白。

需要注意的是,如果在通过nextInt()读取了整数后,再接着读取字符串,读出来的是回车换行:"\r\n",因为nextInt仅仅读取数字信息,而不会读取回车换行"\r\n".

所以,如果在业务上需要读取了整数后,接着读取字符串,那么就应该连续执行两次nextLine(),第一次是取走回车换行,第二次才是读取真正的字符串

import java.util.Scanner;

public class HelloWorld {
 public static void main(String[] args) {
     Scanner s = new Scanner(System.in);
     int i = s.nextInt();
     System.out.println("读取的整数是"+ i);
     String rn = s.nextLine();
     String a = s.nextLine();
     System.out.println("读取的字符串是:"+a);
 }
}

判断是否是整数与小数数据

package com.wang.operator;

import java.sql.SQLOutput;
import java.util.Scanner;

public class Demo02 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in); //new Scanner(System.in);

        int i = 0;
        float f = 0.0f;

        System.out.println("请输入整数:");

        if (scanner.hasNextInt()){
            i =scanner.nextInt();
            System.out.println("整数数据:"+i);
        }else{
            System.out.println("输入的不是整数数据!");
        }

        System.out.println("请输入小数:");

        if (scanner.hasNextFloat()){
            f =scanner.nextFloat();
            System.out.println("小数数据:"+f);
        }else{
            System.out.println("输入的不是小数数据!");
        }


        scanner.close();

    }
}

输入多个数字,求和与平均值,enter确认,非数字结束并输出

package com.wang.operator;

import java.util.Scanner;

public class Demo03 {
    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);

        double sum = 0;
        int m = 0;

        while(scanner.hasNextDouble()){
            double x = scanner.nextDouble();
            m++;
            sum = sum + x;
            System.out.println("输入第"+m+"个数据,当前结果sum="+sum);

        }

        System.out.println(m+"个数的和为"+sum);
        System.out.println(m+"个数的平均值为"+(sum/m));


        scanner.close();

    }
}

顺序结构

顺序执行,任何算法的基本结构。

选择结构

//如果只有一个表达式可以不用写括弧,看上去会简约一些

     if(b){
         System.out.println("yes1");
     }

     if(b)
         System.out.println("yes1");

在第6行,if后面有一个分号; 而[分号也是一个完整的表达式]
如果b为true,会执行这个分号,然后打印yes
如果b为false,不会执行这个分号,然后打印yes
这样,看上去无论如何都会打印yes

public class HelloWorld {
 public static void main(String[] args) {

     boolean b = false;

     if (b);
         System.out.println("yes");

 }
}
  1. if单选择结构
  2. if双选择结构
  3. if多选择结构
  4. 嵌套的if结构
  5. switch多选择结构:判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。
package com.wang.operator;

import java.util.Scanner;

public class Demo04 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println("输入内容");
        String s = scanner.nextLine();

        //判断字符是否相等
        if (s.equals("Hello")){
            System.out.println(s);
        }else{
            System.out.println("不一致");
        }

        System.out.println("End");

        scanner.close();
    }
}

switch多选择结构:判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。

变量类型可以是byte、short、int、char、String

注: 每个表达式结束,都应该有一个break;
注: String在Java1.7之前是不支持的, Java从1.7开始支持switch用String的,编译后是把String转化为hash值,其实还是整数
注: enum是枚举类型,在枚举章节有详细讲解

case标签必须为字符串常量或者字面量

case穿透:不加break就全输出

switch(expression){case value: break;default:}

package com.wang.operator;

public class Demo05 {
    public static void main(String[] args) {
        String name = "wang";

        switch (name){
            case "王":
                System.out.println("王");
                break;
            case "wang":
                System.out.println("wang");
                break;
            default:
                System.out.println("error");

        }
    }
}

反编译 查看class文件 ctrl+alt+shift+s打开项目结构

while循环结构

while(布尔表达式){

// 循环内容

}

我们大多数情况会让循环停止下来,需要一个让表达式失效的方式来结束循环。

**死循环:**while(ture){} / /等待客户端连接、定时检查

do…while循环(先执行后判断)和while(先判断后执行)相似,不同在于do…while循环至少执行一次。

package com.wang.operator;

public class Demo06 {
    public static void main(String[] args) {

        int a = 0;
        while (a < 0){
            System.out.println(a);
            a--;
        }
        System.out.println("===============");
        do{
            System.out.println(a);
            a--;
        }while(a<0 && a>-10);
    }
}

for循环结构

最有效、最灵活的循环结构(100.for+enter自动生成语句)

for(初始化;布尔表达式;更新){

//代码语句

}

死循环 for( ; ; ){}

// 计算0到100之间奇数与偶数和
package com.wang.operator;

public class Demo07 {
    public static void main(String[] args) {

        int oddSum = 0;
        int evenSum = 0;

        for (int i = 0; i < 100; i++) {
            if (i%2!=0){
                oddSum+=i;
            }else{
                evenSum+=i;
            }
        }

        System.out.println("奇数和:"+oddSum+"\n"+"偶数和:"+evenSum);

    }

}

print 输出完不会换行

println输出完会换行

//输出5的倍数,每行输出3个
package com.wang.operator;

public class Demo08 {
    public static void main(String[] args) {

        for(int i=1; i<=1000 ; i++){
            if(i%5==0){
                System.out.print(i+"\t");
            }
            if(i%15==0){
                System.out.println("\n");
            }
        }
    }
}
//打印九九乘法表
package com.wang.operator;public class Demo09 {    
    public static void main(String[] args) {        
        for(int i=1;i<=9;i++){            
            for(int j=1;j<=9;j++){                
                if(j<=i){                    
                    System.out.print(j+"*"+i+"="+(j*i)+"\t");                
                }else{                    
                    System.out.println();                    
                    break;                
                }            
            }        
        }    
    }
}
//利用j<=i的动态变化来优化
package com.wang.operator;public class Demo09 {    
    public static void main(String[] args) {        
        for(int i=1;i<=9;i++){            
            for(int j=1;j<=i;j++){                    
                System.out.print(j+"*"+i+"="+(j*i)+"\t");            
            }            
            System.out.println();        
        }    
    }
}

增强for循环:主要用于数组或集合的增强for循环。

for(声明语句:表达式){

//代码句子

}

package com.wang.operator;public class Demo10 {    
    public static void main(String[] args) {        
        int[] numbers = {10,20,30,40,50,60}; //定义了一个数组        
        for (int i=0;i<=5;i++){            
            System.out.println(numbers[i]);        
        }        
        System.out.println("========================");        
        //遍历数组的元素        
        for (int x:numbers){            
            System.out.println(x);        
        }    
    }
}

break、continue、goto

break用于强行退出循环,不执行循环中剩余的语句(可在switch中使用)

直接结束当前for循环

public class HelloWorld {
public static void main(String[] args) {

  //打印单数    
  for (int j = 0; j < 10; j++) {
      if(0==j%2) 
          break; //如果是双数,直接结束循环

      System.out.println(j);
  }
}
}

continue用于终止某次循环过程,接着进行下一次是否执行循环的判定

如果是双数,后面的代码不执行,直接进行下一次循环

public class HelloWorld {
public static void main(String[] args) {

  //打印单数    
  for (int j = 0; j < 10; j++) {
      if(0==j%2) 
          continue; //如果是双数,后面的代码不执行,直接进行下一次循环

      System.out.println(j);
  }
}
}

**标签:**指后面跟一个冒号的标识符,如:label:

package com.wang.operator;public class Demo11 {    
    public static void main(String[] args) {        
        //打印101-150之间所有的质数        
        //带标签的continue        
        int count = 0;        
        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+" ");        
        }    
    }
}

使用标签结束外部循环

在外部循环的前一行,加上标签
在break的时候使用该标签
即能达到结束外部循环的效果

public class HelloWorld {
public static void main(String[] args) {

  //打印单数    
  outloop: //outloop这个标示是可以自定义的比如outloop1,ol2,out5
  for (int i = 0; i < 10; i++) {

      for (int j = 0; j < 10; j++) {
          System.out.println(i+":"+j);
          if(0==j%2) 
              break outloop; //如果是双数,结束外部循环
      }

  }

}
}

借助boolean变量结束外部循环
需要在内部循环中修改这个变量值
每次内部循环结束后,都要在外部循环中判断,这个变量的值

public class HelloWorld {
public static void main(String[] args) {
  boolean breakout = false; //是否终止外部循环的标记
  for (int i = 0; i < 10; i++) {

      for (int j = 0; j < 10; j++) {
          System.out.println(i + ":" + j);
          if (0 == j % 2) {
              breakout = true; //终止外部循环的标记设置为true
              break;
          }
      }
      if (breakout) //判断是否终止外部循环
          break;
  }

}
}

Java方法

一个方法只完成1个功能,这样有利于后期扩展。

ctrl+/注释

//形式参数,用来定义作用的

//实际参数,实际调用传递给他的参数

修饰符 返回值类型 方法名(参数类型 参数名){

方法体

return 返回值;

}

值传递和引用传递: Java是值传递

//输入两个整数比较大小
package com.wang.method;

import java.util.Scanner;

public class Demo01 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int num1 =0;
        int num2 =0;

        System.out.println("输入第一个整数:");
        if (scanner.hasNextInt()) {
             num1 = scanner.nextInt();
        }else{
            System.out.println("输入的不是整数数据");
        }

        System.out.println("输入第二个整数:");
        if (scanner.hasNextInt()) {
            num2 = scanner.nextInt();
        }else{
            System.out.println("输入的不是整数数据");
        }

        int result = max(num1,num2);

        if (result!=0) {
            System.out.println("更大的数为:" + result);
        }else{
            System.out.println("两数相等!");
        }
    }

    public static int max(int num1,int num2){

        int result = 0; //return要提取到最外面

        if (num1==num2){
            return 0; //终止方法
        }
        if (num1<num2){
            result=num2;
        }else{
            result=num1;
        }
        return result;
    }
}

重载

在一个类中,有相同的函数名称,但形参不同的函数。

方法名称相同时,编译器根据调用方法的参数个数,参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。

规则:参数列表必须不同(个数不同、或类型不同、参数排列顺序不同等)。

命令行传参

有时候希望运行一个程序时再传递给它消息。这要靠传递命令行参数给main()函数实现。

可变参数

在方法声明中,在指定参数类型后加一个省略号(…)。

一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。(不定项参数)

package com.wang.method;

public class Demo02 {
    public static void main(String[] args) {
        //调用可变参数
        Demo02 demo02 = new Demo02();
        demo02.pringMax(12,13,2,3,4);
    }

    public void pringMax(double... numbers){ //在方法里,使用操作数组的方式处理参数 numbers 即可
        if (numbers.length == 0){
            System.out.println("No argument passed");
            return;
        }

        double result = 0;
        //排序
        for(int i=1;i< numbers.length;i++){
            if (numbers[i]>result){
                result=numbers[i];
            }
        }
        System.out.println("The max value is "+result);
    }
}

递归

A方法调用A方法。

递归结构:

  1. 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环。
  2. 递归体:什么时候需要调用自身方法。
package com.wang.method;

public class Demo03 {
    //阶乘
    public static void main(String[] args) {
        System.out.println(f(8));
    }

    public static int f(int n){
        if (n==1){
            return 1;
        }else{
            return n*f(n-1);
        }
    }
}

Java使用栈机制,递归会调用大量的函数,可能导致内存崩溃

计算器(结果尚未未实现整数输出功能)

写一个计算器,要求实现加减乘除功能,并且能够循环接受新的数据,通过用户交互实现

  • 写四个方法:加减乘除
  • 利用循环+switch进行用户交互
  • 传递需要操作的两个数
  • 输出结果
package com.wang.method;

import java.util.Scanner;

public class Demo04 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        double num1 =0;
        double num2 =0;

        System.out.println("开始计算!");

        if(scanner.hasNextInt()){
            num1= scanner.nextInt();
        }else{
            if(scanner.hasNextDouble()) {
                num1 = scanner.nextDouble();
            }else {
                System.out.println("输入一个数!");
            }
        }

        String c="";
        if(scanner.hasNext()){
            c= scanner.next();
        }

        if(scanner.hasNextInt()){
             num2= scanner.nextInt();
        }else{
            if(scanner.hasNextDouble()) {
                 num2 = scanner.nextDouble();
            }else {
                System.out.println("输入一个数!");
            }
        }

        switch (c){
            case "+":
                System.out.println(num1+"+"+num2+"="+add(num1,num2));
                break;
            case "-":
                System.out.println(num1+"-"+num2+"="+sub(num1,num2));
                break;
            case "*":
                System.out.println(num1+"*"+num2+"="+mul(num1,num2));
                break;
            case "/":
                System.out.println(num1+"/"+num2+"="+div(num1,num2));
                break;
            default:
                System.out.println("不正确的运算符");
        }


    }

    //加
    public static int add(int num1,int num2){
        int result = num1 +num2;
        return result;
    }
    public static double add(double num1,double num2){
        double result = num1 +num2;
        return result;
    }
    //减
    public static int sub(int num1,int num2){
        int result = num1 -num2;
        return result;
    }
    public static double sub(double num1,double num2){
        double result = num1 -num2;
        return result;
    }
    //乘
    public static int mul(int num1,int num2){
        int result = num1 *num2;
        return result;
    }
    public static double mul(double num1,double num2){
        double result = num1 *num2;
        return result;
    }
    //除
    public static int div(int num1,int num2){
        if (num2==0) {
            System.out.println("error");
            return 0;
        }else{
            int result = num1 / num2;
            return result;
        }
    }
    public static double div(double num1,double num2){
        if (num2==0) {
            System.out.println("error");
            return 0;
        }else{
            double result = num1 / num2;
            return result;
        }
    }

}

数组

数组是相同类型数据的有序集合。每个数组元素可以通过一个下标来访问他们。

声明数组变量,才能在程序中使用数组。

dataType[] arrayRefVar; //声明一个数组。首选的方法、int[] nums;
dataType arrayRefVar[]; //效果相同,但不是首选方法(C和C++首选)

nums = new int[10]; //创建一个数组

Java语言使用new操作符来创建数组:

dataType[] arrayRefVar = new dataType[arraySizw]; //int[] nums = new int[10];

获取数组长度:

array.length

创建数组的时候,要指明数组的长度。
new int[5]
引用概念:
如果变量代表一个数组,比如a,我们把a叫做引用
与基本类型不同
int c = 5; 这叫给c赋值为5
声明一个引用 int[] a;
a = new int[5];
让a这个引用,指向数组

public class HelloWorld {
public static void main(String[] args) {
  //声明一个引用
  int[] a;
  //创建一个长度是5的数组,并且使用引用a指向该数组
  a = new int[5];

  int[] b = new int[5]; //声明的同时,指向一个数组

}
}

把一个数组的值,复制到另一个数组中

System.arraycopy(src, srcPos, dest, destPos, length)
src: 源数组
srcPos: 从源数组复制数据的起始位置
dest: 目标数组
destPos: 复制到目标数组的起始位置
length: 复制的长度

public class HelloWorld {
  public static void main(String[] args) {
      int a [] = new int[]{18,62,68,82,65,9};

      int b[] = new int[3];//分配了长度是3的空间,但是没有赋值

      //通过数组赋值把,a数组的前3位赋值到b数组

      //方法一: for循环

      for (int i = 0; i < b.length; i++) {
          b[i] = a[i];
      }

      //方法二: System.arraycopy(src, srcPos, dest, destPos, length)
      //src: 源数组
      //srcPos: 从源数组复制数据的起始位置
      //dest: 目标数组
      //destPos: 复制到目标数组的启始位置
      //length: 复制的长度       
      System.arraycopy(a, 0, b, 0, 3);

      //把内容打印出来
      for (int i = 0; i < b.length; i++) {
          System.out.print(b[i] + " ");
      }

  }
}

内存分析

堆://创建数组、赋值

  1. 存放new的对象和数组

  2. 可以被所有的线程共享,不会存放别的对象引用

栈://声明数组

  1. 存放基本变量类型(会包含这个基本类型的具体数值)
  2. 引用对象的变量(会存放这个引用在堆里面的具体地址)

方法区:

  1. 可以被所有的线程共享
  2. 包含了所有的class和static变量

三种初始化

如果指定了数组的内容,就不能同时设置数组的长度。

  1. 静态初始化:创建+赋值
int[] a = {1,2,3,4,5,6,7,8,9};
Man[] mans = {new Man(1,1),new Man(2,2)};
  1. 动态初始化:
int[] a = new int[2];
a[0]=1;
a[1]=2;
  1. 数组的默认初始化:数组是引用类型,他的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。

四个基本特点

  1. 长度确定
  2. 元素类型相同,不允许混合
  3. 元素可以是任何数据类型,包括基本类型和引用类型
  4. 数组变量属引用类型,数组可看成对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。

数组边界

合法区间:[0,length-1]

ArrayIndexOutOfBoundsException:数组下标越界异常!

package com.wang.array;

public class Demo01 {
    public static void main(String[] args) {

        int[] nums = new int[10];
        nums[0] = 1;

        for (int i = 0; i < nums.length ; i++) {
            System.out.print(nums[i]+" ");
        }

        System.out.println(nums[10]);

        for (int num :nums){ // nums.for
            System.out.println(nums); //适合遍历输出,但是取不到下标
        }
    }
}

使用方法打印与反转整数与小数类型数组

package com.wang.array;

public class Demo02 {
    public static void main(String[] args) {
        int[] arrays = {1,2,3,4,5};
        double[] arrays2 = {1.1,2.2,3,4,5};

        int[] reverse = reverse(arrays);
        double[] reverse2 = reverse(arrays2);
        printArray(reverse);
        printArray(reverse2);
    }

    //打印数组
    public static void printArray(int[] arrays){
        for (int array : arrays) {
            System.out.print(array+" ");
        }
        System.out.println("");
    }
    public static void printArray(double[] arrays){
        for (double array : arrays) {
            System.out.print(array+" ");
        }
        System.out.println("");
    }

    //反转数组
    public static int[] reverse(int[] arrays){
        int[] result = new int[arrays.length];
        for(int i=0, j=result.length-1; i < arrays.length;i++,j--){
            result[j]=arrays[i];
        }
        return result;
    }
    public static double[] reverse(double[] arrays){
        double[] result = new double[arrays.length];
        for(int i=0, j=result.length-1; i < arrays.length;i++,j--){
            result[j]=arrays[i];
        }
        return result;
    }
}

多维数组

多维数组可以看出是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组。

二维数组:

int a[][] = new int[2][5]; //两行五列

Java中使用多维数组不多,主要是面向对象!

package com.wang.array;

public class Demo03 {
    public static void main(String[] args) {
        int[][] a ={{1,2},{1,2,3},{1},{1,2}};
        System.out.println(a.length); //几行
        System.out.println(a[1].length); //二行几列
        
        for (int i=0; i<a.length;i++){ //遍历输出二维数组
            for(int j=0; j<a[i].length;j++){ //fori
                System.out.print(a[i][j]+" ");
            }
            System.out.println("");
            
        }
    }
}

Arrays类

Arrays是针对数组的工具类,可以进行 排序,查找,复制填充等功能。 大大提高了开发人员的工作效率。

数组的工具类:java.util.Arrays

copyOfRange(int[] original, int from, int to) // 第二个参数表示开始位置(取得到)// 第三个参数表示结束位置(取不到)数组复制
toString()转换为字符串
sort排序
binarySearch搜索
equals判断是否相同
fill填充
import java.util.Arrays;

public class HelloWorld {
public static void main(String[] args) {
  int a[] = new int[] { 18, 62, 68, 82, 65, 9 };

  // copyOfRange(int[] original, int from, int to)
  // 第一个参数表示源数组
  // 第二个参数表示开始位置(取得到)
  // 第三个参数表示结束位置(取不到)
  int[] b = Arrays.copyOfRange(a, 0, 3);

  for (int i = 0; i < b.length; i++) {
      System.out.print(b[i] + " ");
  }

}
}
package com.wang.array;

import java.util.Arrays;

public class Demo04 {
    public static void main(String[] args) {
        int[] a = {1,2,4,6,4,234,5423};

        System.out.println(a); //[I@1b6d3586,hashcode

        //打印数组元素Array.toString
        System.out.println(Arrays.toString(a));

        int a[] = new int[] { 18, 62, 68, 82, 65, 9 };//复制数组 
        // copyOfRange(int[] original, int from, int to)
        // 第一个参数表示源数组
        // 第二个参数表示开始位置(取得到)
        // 第三个参数表示结束位置(取不到)
        int[] b = Arrays.copyOfRange(a, 0, 3);

        Arrays.sort(a); //排序
        System.out.println(Arrays.toString(a));
        
        Arrays.fill(a,2,4,0); //数组填充,二到四个元素被零填充[2,4)左闭右开
        System.out.println(Arrays.toString(a));
        
        Arrays.fill(a,0); //数组填充
        System.out.println(Arrays.toString(a));
    }
}

给数组赋值:通过Arrays.fill方法

对数组排序:通过Arrays.sort方法,按升序

比较数组:通过Arrays.equals方法比较数组中元素值是否相等

查找数组元素:通过Arrays.binarySearsh方法能对排序好的数组进行二分查找法操作

查询元素出现的位置
需要注意的是,使用binarySearch进行查找之前,必须使用sort进行排序
如果数组中有多个相同的元素,查找结果是不确定的

import java.util.Arrays;

public class HelloWorld {
public static void main(String[] args) {
  int a[] = new int[] { 18, 62, 68, 82, 65, 9 };

  Arrays.sort(a);

  System.out.println(Arrays.toString(a));
  //使用binarySearch之前,必须先使用sort进行排序
  System.out.println("数字 62出现的位置:"+Arrays.binarySearch(a, 62));
}
}

冒泡排序

总共有八大排序。

冒泡排序和选择排序

1.冒泡排序是比较相邻位置的两个数,而选择排序是按顺序比较,找最大值或者最小值;

2.冒泡排序每一轮比较后,位置不对都需要换位置,选择排序每一轮比较都只需要换一次位置;

3.冒泡排序是通过数去找位置,选择排序是给定位置去找数;

冒泡排序可以借助boolean变量结束外部循环,

嵌套循环,时间复杂度O(n2)

package com.wang.array;

import java.util.Arrays;

public class Demo05 {
    public static void main(String[] args) {
        /*
        冒泡排序
        1.比较数组中,两个相邻的元素,如果第一个数比第二个数大,我们就交换他们的位置
        2.每一次比较,都会产生出一个最大,或者最小的数字
        3.下一轮则可以少一次排序
        4.依次循环,直到结束
         */
        int[] a={1,2,3,4};
        int[] sort = sort(a);
        System.out.println(Arrays.toString(sort));

    }

    public static int[] sort(int[] array){ //优化-当有一轮没有交换位置的时候,说明已经排序好了,则结束
        int temp = 0;

        //外层循环,判断要走多少次
        for (int i = 0; i < array.length-1; i++) {

            boolean flag = false; //通过flag标识位减少没有意义的比较

            //内层循环,比价判断两个数,如果第一个数,比第二个数大,则交换位置
            for (int j = 0;j < array.length-1-i;j++){
                if (array[j+1]<array[j]){ //flag的作用点在这里!
                    temp = array[j];
                    array[j]= array[j+1];
                    array[j+1]=temp;
                    flag = true;
               
            }

            if (flag==false){
                break;
            }
        }
        return array;
    }
}

稀疏数组

稀疏数组的存放和读取

package com.wang.array;

public class Demo06 {
    public static void main(String[] args) {
        int[][] a=new int[11][11];
        a[1][2]=1;
        a[2][3]=2;

        System.out.println("输出原始数组:");

        arraysPrint(a);

        System.out.println("===============");
        //转换为稀疏数组保存
        //获取有效值个数
        
        //1.计数不为零的个数
        int sum =0;
        for (int i = 0; i < a.length; i++) {
            for (int j = 0; j < a[i].length; j++) {
                if(a[i][j]!=0){
                    sum++;
                }
            }
        }
        System.out.println("有效值的个数:"+sum);

        //2.创建一个稀疏数组
        int[][] a2=new int[sum+1][3];

        a2[0][0]=a.length;
        a2[0][1]=a[0].length;
        a2[0][2]=sum;

        //3.遍历二维数组,将非零值存放进去
        int count=0;//用来表示存放行数
        for (int i = 0; i < a.length; i++) {
            for (int j = 0; j < a[i].length; j++) {
                if(a[i][j]!=0){
                    count++;
                    a2[count][0]=i;
                    a2[count][1]=j;
                    a2[count][2]=a[i][j];
                }
            }
        }

        //4.输出稀疏数组
        System.out.println("稀疏数组:");

        arraysPrint(a2);

        System.out.println("===============");
        System.out.println("还原:");

        //1.读取稀疏数组值
        int[][] a3= new int[a2[0][0]][a2[0][1]];
        //2.给其中元素还原值
        for (int i = 1; i < a2.length; i++) {
            a3[a2[i][0]][a2[i][1]]=a2[i][2];
        }
        //输出还原数组
        arraysPrint(a3);
    }

    public static void arraysPrint(int[][] arrays){
        for (int[] ints : arrays) { // a.for遍历输出
            for (int anInt : ints) {
                System.out.print(anInt+"\t");
            }
            System.out.println();
        }
    }
}

arrays.for遍历输出

面向对象编程OOP

引用的概念,如果一个变量的类型是 类类型,而非基本类型,那么该变量又叫做引用。

//创建一个对象
new Hero;
//使用一个引用来指向这个对象
Hero h = new Hero();
Hero h2 = h;  //h2指向h1所指向的对象

一个引用,同一时间,只能指向一个对象。

属性(这里的属性应该包含了构造器的定义)加方法变成一个类。

**面向对象思想:**抽象。物以类聚,分类的思维方式。对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。

面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据。

三大特性:

  1. 封装
  2. 继承
  3. 多态

从认识论的角度:先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。

从代码运行的角度:先有类后有对象。类是对象的模板。

类与对象的创建

使用new关键字创建对象。

使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。

package com.wang.oop;

//学生类
public class Student {

    //属性:字段
    String name;
    int age;
    
    //这里有一个默认的构造器
    // public Student() {
    // }

    //方法
    public void study(){
        System.out.println(this.name+"在学习");//this指这一个类
    }
}
package com.wang.oop;

//一个项目应该只存在一个main方法
public class Application {

    public static void main(String[] args) {

        //类:抽象的,实例化
        //类实例化后会返回一个自己的对象!
        //student对象就是一个Student类的具体实例!
        Student xiaoming = new Student();
        Student xiaohong= new Student();

        xiaoming.name="小明";
        xiaoming.age= 3;

        System.out.println(xiaoming.name);
        System.out.println(xiaoming.age);


    }
}

构造器

通过一个类创建一个对象,这个过程叫做实例化

实例化是通过调用构造方法(又叫做构造器)实现的

类中的构造器也成为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:

  1. 必须和类的名字相同
  2. 必须没有返回类型,也不能写void

类的创建

package com.wang.oop;

public class Person {
    //一个类即使什么都不写,它也会存在一个方法
    //显示的定义构造器

    String name;
    int age;

    //实例化初始值
    //使用new关键字,本质是在调用构造器

    //无参构造 默认构造器
    public Person(){ //Person person = new Person();想使用这样new Person()时必须有这样一个构造器
    }

    //有参构造:一旦定义了有参构造,无参必须显示定义
    public Person(String name){ Person person = new Person("wang");自动判断,调用有参构造,相当于方法的重载
        this.name =name;//前面的name代表类,后面的代表传进来的参数
    }

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
}
/*
     构造器:
        1.和类名相同
        2.没有返回值
     作用:
        1.new 本质在调用构造方法
        2.初始化对象的值
     注意点:
        1.定义有参构造之后,如果想使用无参构造,显示的定义一个无参的构造

     Alt + Insert

     this. =  //this后面的指这一个类
 */

类的调用

package com.wang.oop;

public class Application02 {
    public static void main(String[] args) {
        Person person = new Person(); //因为使用了这个,所以需要显示定义一个无参构造
        Person person1 = new Person("wang");
        Person person2=new Person("wang",23);

        System.out.println(person.name);
        System.out.println(person1.name);
        System.out.println(person2.name+person2.age);
    }
}

this

this这个关键字,相当于普通话里的“
this即代表当前对象,也就是调用方法的对象

//参数名和属性名一样
//在方法体中,只能访问到参数name
public void setName1(String name){
  name = name;
}

//为了避免setName1中的问题,参数名不得不使用其他变量名
public void setName2(String heroName){
  name = heroName;
}

//通过this访问属性
public void setName3(String name){
  //name代表的是参数name
  //this.name代表的是属性name
  this.name = name;
}

如果要在一个构造方法中,调用另一个构造方法,可以使用this()

package com.wang.oop.demo01;

public class Hero {

String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度

//带一个参数的构造方法
public Hero(String name){
  System.out.println("一个参数的构造方法");
  this.name = name;
}

//带两个参数的构造方法
public Hero(String name,float hp){
  this(name); //调用了一个参数的构造方法
  System.out.println("两个参数的构造方法");
  this.hp = hp;
}

public static void main(String[] args) {
  Hero teemo =  new Hero("提莫",383);

  System.out.println(teemo.name);

}

}

创建对象内存分析

如果一个变量是基本类型
比如 int hp = 50;
我们就直接管hp叫变量
=表示赋值的意思
如果一个变量是类类型
比如 Hero h = new Hero();
我们就管h叫做引用
=不再是赋值的意思
=表示指向的意思
比如 Hero h = new Hero();
这句话的意思是
引用h,指向一个Hero对象

栈://声明数组

  1. 存放基本变量类型(会包含这个基本类型的具体数值)
  2. 引用对象的变量(会存放这个引用在堆里面的具体地址)

堆://创建数组、赋值

  1. 存放new的对象和数组

  2. 可以被所有的线程共享,不会存放别的对象引用

方法区:

  1. 可以被所有的线程共享
  2. 包含了所有的class和static变量

理解Java的值传递

关于teemo为什么最终仍然指向“旧”提莫:
第31行调用revive方法,实参为teemo,teemo本身是一个引用,占用一个内存单元,其中存放“旧”提莫的地址值,而java为值传递,所以该调用只是将teemo的值,即“旧”提莫的地址值,将该值拷贝下来,复制给形参h,此时teemo显然仍指向“旧”提莫,h现在存放“旧”提莫的地址值,但是通过调用构造方法,新建了一个Hero对象,并将"新"提莫的地址值赋值给h。
结果:teemo指向“旧”提莫,h指向“新”提莫。
理解关键:java基于值传递而非引用传递。
public class Hero {

String name; //姓名        
float hp; //血量        
float armor; //护甲        
int moveSpeed; //移动速度     
public Hero(){         
}

public Hero(String name,float hp){
  this.name = name;
  this.hp = hp;
}

//复活
public void revive(Hero h){
  h = new Hero("提莫",383);
}

public static void main(String[] args) {
  Hero teemo =  new Hero("提莫",383);

  //受到400伤害,挂了
  teemo.hp = teemo.hp - 400;         
  teemo.revive(teemo); //teemo.hp = -17;因为形参h传递到了实参teemo的指向,即地址,然后又指向了新构造的对象        
  //问题: System.out.println(teemo.hp); 输出多少? 怎么理解?         
}      
}

小结

  1. 类与对象

    类是一个模板:抽象;对象是一个具体的实例

  2. 方法

    定义、调用!

  3. 对象的引用

    引用类型:基本类型(8)

    对象是通过引用来操作的:栈–>堆

  4. 属性:字段Field 成员变量

    默认初始化:

    数字:0 0.0;char:u0000;boolean:false;引用:null

    修饰符 属性类型 属性名 = 属性值!

  5. 对象的创建和使用

    必须使用new 关键字创造对象,构造器

    对象的属性 wang.name

    对象的方法 wang.sleep()

  6. 静态的属性 属性

    动态的行为 方法

封装

“高内聚,低耦合:”

高内聚,类的内部数据操作细节自己完成,不允许外部干涉;低耦合,仅暴露少量的方法给外部使用。

**封装(数据的隐藏):**通常应禁止直接访问一个对象中数据的实际表示,而应该通过操作接口来访问,这称为信息隐藏。

属性私有,get/set

/*

  1. 提高程序的安全性,保护数据
  2. 隐藏代码的实现细节
  3. 统一接口
  4. 提高系统的可维护性
    */
package com.wang.oop.demo05;

public class Student {

        //private:私有

        //属性私有
        private String name;
        private int id;
        private char sex;
        private int age;

        //提供一些可以操作这个属性的方法
        //提供一些public的get、set方法

        //get获得这个数据
        public String getName(){
                return this.name;
        }

        //set给这个数据设置值
        public void setName(String name){
                this.name=name;
        }

        //alt +insert 自动生成get、set
        public int getId() {
                return id;
        }

        public void setId(int id) {
                this.id = id;
        }

        public int getAge() {
                return age;
        }

        public void setAge(int age) { //封装可以规避这种不合法的数据
                if(age>130 || age<0){ //不合法
                        this.age = 0;
                }else {
                this.age = age;
                }
        }
}
/*
1. 提高程序的安全性,保护数据
2. 隐藏代码的实现细节
3. 统一接口
4. 提高系统的可维护性
 */

继承

继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。

extends:扩展。子类是父类的扩展。

Java中类只有单继承,没有多继承!

package com.wang.oop.demo06;
//Java中,所有的类都默认直接或者间接继承Object类
//基类、父类
public class Person /*extend Object*/ {

    private int money = 10_0000_0000;

    public void say(){
        System.out.println("say hi");
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
    //ctrl + H 打开继承项
}

package com.wang.oop.demo06;
//派生类、子类
public class Student extends Person{
}

package com.wang.oop.demo06;
public class Application {
    public static void main(String[] args) {
        Student student = new Student();

        student.say(); //可以调用Person类中的方法
    }
}

Super

public class Student extends Person{
    public Student() {
        super(); //隐藏代码,调用了父类的无参构造,且调用父类的构造器,必须要在子类构造器的第一行
        //当父类没有无参构造,子类无参构造需要显示的定义父类有参
    }


    private String name = "da";

    public void test(String name){
        System.out.println(name); //zhuang
        System.out.println(this.name); //da
        System.out.println(super.name); //wang
    }
}
/*
父类Hero提供了一个有参的构造方法:
public Hero(String name){
  this.name = name;
}
但是没有提供无参的构造方法
子类应该怎么处理?
*/
package charactor;   
public class Hero {
    public String name;
    protected float hp;
   
    public Hero(String name){
        this.name = name;
    }     
//    故意不提供无参的构造方法
//    public Hero(){
//    }     
    public static void main(String[] args) {     
    }       
}

//子类添加有参构造方法
public ADHero(String name) {
        super(name);
        System.out.println("adHero的构造方法");
         
}

重写

重写都是方法的重写,和属性无关。

重写:需要又继承关系,子类重写父类的方法!

  1. 方法名必须相同
  2. 参数列表必须相同
  3. 修饰符:范围可以扩大: public > protected > default > private
  4. 抛出的异常:范围 可以被缩小,但不能扩大; ClassNotFoundException --> Exception(大)

为什么要重写:父类的功能,子类不一定需要,或者不一定满足。

Alt + Insert ; override;

package com.wang.oop.demo07;
//重写都是方法的重写,和属性无关。
public class A extends B{
    //Override 重写
    @Override //注解:有功能的注释!
    public void test() {
        super.test();
    }
}

隐藏

与重写类似,方法的重写是子类覆盖父类的对象方法

隐藏,就是子类覆盖父类的类方法

package charactor;
  
public class Hero {
    public String name;
    protected float hp;
  
    //类方法,静态方法
    //通过类就可以直接调用
    public static void battleWin(){
        System.out.println("hero battle win");
    }   
}

package charactor;
public class ADHero extends Hero implements AD{  
    @Override
    public void physicAttack() {
        System.out.println("进行物理攻击");
    }
     
    //隐藏父类的battleWin方法
    public static void battleWin(){
        System.out.println("ad hero battle win");
    }   
     
    public static void main(String[] args) {
        Hero.battleWin();
        ADHero.battleWin();
    }  
}

Hero h =new ADHero();

h.battleWin(); //battleWin是一个类方法
h是父类类型的引用
但是指向一个子类对象
h.battleWin(); 会调用父类的方法?还是子类的方法?

**父类引用指向子类对象: **

**静态方法, 引用是啥类型就输出啥类的方法; **

非静态方法, 对象是啥类型就输出啥类型的方法;

  //当继承的方法为静态方法时,父类指向子类,JVM使用的是静态绑定
  //当继承的方法不是静态方法时,父类指向子类,JVM使用的是动态绑定
  //想要继承的方法为静态的方法时,想要调用子类的重写方法,直接子类名称.静态方法
  //当出现private,final,static,以及构造器的时候,JVM会调用静态绑定

多态

操作符的多态
+ 可以作为算数运算,也可以作为字符串连接

同一个操作符在不同情境下,具备不同的作用
如果+号两侧都是整型,那么**+代表 数字相加**
如果+号两侧,任意一个是字符串,那么**+代表字符串连接**

类的多态: 都是同一个类型,调用同一个方法,却能呈现不同的状态
父类引用指向子类对象

要实现类的多态,需要如下条件

  1. 父类(接口)引用指向子类对象
  2. 调用的方法有 重写
/*
如果物品的种类特别多,那么就需要设计很多的方法
比如useArmor,useWeapon等等

这个时候采用多态来解决这个问题
设计一个方法叫做useItem,其参数类型是Item
如果是使用血瓶,调用该方法
如果是使用魔瓶,还是调用该方法
无论英雄要使用什么样的物品,只需要一个方法即可
*/
package charactor;

import property.Item;
import property.LifePotion;
import property.MagicPotion;

public class Hero {
  public String name;
  protected float hp;

  public void useItem(Item i){
      i.effect();
  }

  public static void main(String[] args) {

      Hero garen =  new Hero();
      garen.name = "盖伦";

      LifePotion lp =new LifePotion();
      MagicPotion mp =new MagicPotion();

      garen.useItem(lp);
      garen.useItem(mp);     

  }

}

动态编译:类型:可扩展性更强;

即同一方法可以根据发送对象的不同而采用多种不同的行为方式。

多态注意事项:

  1. 多态是方法的多态,属性没有多态
  2. 父类和子类,有联系 (类型转换异常:ClassCastException!)
  3. 存在条件:继承关系,方法需要重写,父类引用指向子类对象! Father f1 = new Son();

有些方法不能重写:

  1. static 方法,属于类 不属于实例
  2. final 常量
  3. private 方法
package com.wang.oop;
import com.wang.oop.demo08.Person;
import com.wang.oop.demo08.Student;
public class Application {    
    public static void main(String[] args) {        
        //一个对象的实际类型是确定的        
        //可以指向的引用类型不确定:父类的引用指向子类        
        //Student 能调用的方法都是自己的或者继承父类的!        
        Student s1 = new Student();        
        //Person 父类型,可以指向子类,但是不能调用子类独有的方法        
        Person s2 = new Student();        
        Object s3 = new Student(); //Object是所有子类的祖宗类        
        // 对象能执行哪些方法,主要看对象左边的类型,和右边关系不大!        
        s1.eat();        
        ((Student)s2).eat(); 
        //强制转换 子类重写了父类的方法,执行子类的方法   
    }
}

Object

Object类是所有类的父类

声明一个类的时候,默认是继承了Object
public class Hero extends Object

toString

Object类提供一个toString方法,所以所有的类都有toString方法
toString()的意思是返回当前对象的字符串表达
通过 System.out.println 打印对象就是打印该对象的toString()返回值

        System.out.println(h.toString());
        //直接打印对象就是打印该对象的toString()返回值
        System.out.println(h);
finalize

当一个对象没有任何引用指向的时候,它就满足垃圾回收的条件

当它被垃圾回收的时候,它的finalize() 方法就会被调用。

finalize() 不是开发人员主动调用的方法,而是由虚拟机JVM调用的。

package charactor;
  
public class Hero {
    public String name;
    protected float hp;
      
    public String toString(){
        return name;
    }
     
    public void finalize(){
        System.out.println("这个英雄正在被回收");
    }
      
    public static void main(String[] args) {
        //只有一引用
        Hero h;
        for (int i = 0; i < 100000; i++) {
            //不断生成新的对象
            //每创建一个对象,前一个对象,就没有引用指向了
            //那些对象,就满足垃圾回收的条件
            //当,垃圾堆积的比较多的时候,就会触发垃圾回收
            //一旦这个对象被回收,它的finalize()方法就会被调用
            h = new Hero();
        }
 
    }
}
equals

equals() 用于判断两个对象的内容是否相同

假设,当两个英雄的hp相同的时候,我们就认为这两个英雄相同

package charactor;
  
public class Hero {
    public String name;
    protected float hp;
      
    public boolean equals(Object o){ //Hero类向上转型
        if(o instanceof Hero){
            Hero h = (Hero) o; //Object类向下转型
            return this.hp == h.hp; //this指的是当前对象,也就是调用equal方法的对象,所以this.hp指的是h1.hp
        }
        return false;
    }
      
    public static void main(String[] args) {
        Hero h1= new Hero();
        h1.hp = 300;
        Hero h2= new Hero();
        h2.hp = 400;
        Hero h3= new Hero();
        h3.hp = 300;
         
        System.out.println(h1.equals(h2));
        System.out.println(h1.equals(h3));
    }
}
hashCode

hashCode方法返回一个对象的哈希值,但是在了解哈希值的意义之前,讲解这个方法没有意义。

线程同步相关方法

Object还提供线程同步相关方法
wait()
notify()
notifyAll()
这部分内容的理解需要建立在对线程安全有足够的理解的基础之上,所以会放在线程交互 的章节讲解

getClass()

getClass()会返回一个对象的类对象,属于高级内容,不适合初学者过早接触,关于类对象的详细内容请参考反射机制

练习

重写Item的 toString(), finalize()和equals()方法
toString() 返回Item的name + price
finalize() 输出当前对象正在被回收
equals(Object o) 首先判断o是否是Item类型,然后比较两个Item的price是否相同

package com.wang.oop.demo11;

public class Item {
    public String name;
    protected float price;

    public String toString(){
        return this.name +"价格"+this.price;
    }
    public void finalize(){
        System.out.println("当前对象正在被回收");
    }
    public boolean equals(Object o){
        if (o instanceof Item){
            Item h = (Item) o;
            return this.price==h.price;
        }
        return false;
    }

    public static void main(String[] args) {
        Item s1 = new Item();
        s1.name="宝剑";
        s1.price=800;
        Item s2 = new Item();
        s2.name="好盾牌";
        s2.price=800;

        System.out.println(s1.toString());
        System.out.println(s1.equals(s2));
    }
}

final

final修饰类,方法,基本类型变量,引用的时候分别有不同的意思。

修饰类

当Hero被修饰成final的时候,表示Hero不能够被继承
其子类会出现编译错误

修饰方法

Hero的useItem方法被修饰成final,那么该方法在ADHero中,不能够被重写

修饰基本类型变量

final修饰基本类型变量,表示该变量只有一次赋值机会
修饰引用

final修饰引用
h引用被修饰成final,表示该引用只有1次指向对象的机会

常量

对象转型

子类转父类(向上转型),说的通

父类转子类(向下转型),有的时候行,有的时候不行,所以必须进行强制转换。强制转换的意思就是 转换有风险,风险自担。

以下是对完整的代码的关键行分析
14行: 把ad当做Hero使用,一定可以
转换之后,h引用指向一个ad对象
15行: h引用有可能指向一个ad对象,也有可能指向一个support对象
所以把h引用转换成AD类型的时候,就有可能成功,有可能失败
因此要进行强制转换,换句话说转换后果自负
到底能不能转换成功,要看引用h到底指向的是哪种对象
在这个例子里,h指向的是一个ad对象,所以转换成ADHero类型,是可以的
16行:把一个support对象当做Hero使用,一定可以
转换之后,h引用指向一个support对象
17行:这个时候,h指向的是一个support对象,所以转换成ADHero类型,会失败。
失败的表现形式是抛出异常 ClassCastException 类型转换异常

父类转子类(向下转型)

package charactor;

import charactor1.Support;

public class Hero {
public String name;
protected float hp;

public static void main(String[] args) {
  Hero h =new Hero();
  ADHero ad = new ADHero();
  Support s =new Support();

  h = ad;
  ad = (ADHero) h;
  h = s;
  ad = (ADHero)h;
}

}

没有继承关系的两个类,互相转换,一定会失败
虽然ADHero和APHero都继承了Hero,但是彼此没有互相继承关系
把魔法英雄当做物理英雄来用”,在语义上也是说不通的

没有继承关系的两个类,互相转换

实现类转换成接口(向上转型)

接口转换成实现类(向下转型)

10行: ad引用指向ADHero, 而adi引用是接口类型:AD,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功
12行: adi实际上是指向一个ADHero的,所以能够转换成功
14行: adi引用所指向的对象是一个ADHero,要转换为ADAPHero就会失败。

假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero没有magicAttack方法的。

接口转换成实现类(向下转型)

package charactor;

public class Hero {
public String name;
protected float hp;

public static void main(String[] args) {
  ADHero ad = new ADHero();

  AD adi = ad;

  ADHero adHero = (ADHero) adi;

  ADAPHero adapHero = (ADAPHero) adi;
  adapHero.magicAttack();
}

}

[Java 向上转型和向下转型](

1、把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转型。

如Father father = new Son();

2、把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转型,要向下转型,必须先向上转型为了安全可以用instanceof判断。

如father就是一个指向子类对象的父类引用,把father赋给子类引用son 即Son son =(Son)father;

其中father前面的(Son)必须添加,进行强制转换。

3、upcasting 会丢失子类特有的方法,但是子类overriding 父类的方法,子类方法有效,向上转型只能引用父类对象的属性,要引用子类对象属性,则要写getter函数。

4、向上转型的作用,减少重复代码,父类为参数,调有时用子类作为参数,就是利用了向上转型。这样使代码变得简洁。体现了JAVA的抽象编程思想。

**父类引用指向子类对象: **

静态方法, 引用是啥类型就输出啥类的方法;

非静态方法, 对象是啥类型就输出啥类型的方法;

package com.wang.oop.Demo09;
/*
向上转型后父类引用不能调用子类自己的方法,就是父类没有但是子类的方法,如果调用不能编译通过,比如子类的speak方法。
非要调用子类的属性呢?如果不向下转型就需要给需要的属性写getter方法。

class Male extends Human {
   String name = "Male";
   public String getName(){
       return this.name;
   }
}
非要调用子类扩展的方法,比如speak方法,就只能向下转型了。     
*/

public class Human {
   public void sleep() {
       System.out.println("Human sleep..");
   }
   public static  void doSleep(Human h){
       h.sleep();

   }//此时传递的参数是父类对象,但是实际调用时传递子类对象,就是向上转型。
   public static void main(String[] args) {
       Human h = new Male();// 向上转型
       doSleep(new Male());//此处匿名子类对象,当然实际应用时应该是用上面的向上转型公式,然后将子类对象传递进来,这样以后好在向下转型,此处没有向下转型,所以直接用了匿名类对象。
       doSleep(new Female());

   }
}


class Male extends Human {
   @Override
   public void sleep() {
       System.out.println("Male sleep..");
   }
}

class Female extends Human {
   @Override
   public void sleep() {
       System.out.println("Female sleep..");
   }
}

instanceof 和类型转换

用来测试一个对象是否为一个类的实例,用法为:

boolean result = obj instanceof Class // obj必须是引用类型,不能是基本类型

当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。

注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

package com.wang.oop;
import com.wang.oop.demo06.Teacher;
import com.wang.oop.demo08.Person;
import com.wang.oop.demo08.Student;
public class Application {    
    public static void main(String[] args) {        
        //Object > String        
        //Object > Person > Teacher        
        //Object > Person > Student        
        Object object = new Student();        
        System.out.println(object instanceof Student); //true        
        System.out.println(object instanceof Person); //true        
        System.out.println(object instanceof Object); //true        
        System.out.println(object instanceof Teacher); //false        
        System.out.println(object instanceof String); //false        
        System.out.println("=========================");        
        Person person = new Student();        
        System.out.println(person instanceof Student); //true        
        System.out.println(person instanceof Person); //true        
        System.out.println(person instanceof Object); //true        
        // System.out.println(person instanceof String); //编译报错            
    }
}

x instanceof y

编译能不能通过取决于x与y有没有父子关系;看x引用的左边

结果是true还是false看的是x的子类型是不是y的子类型;即看x引用的右边

访问修饰符

private修饰属性

使用private修饰属性
自身:是可以访问的
同包子类:不能继承
不同包子类:不能继承
同包类:不能访问
其他包类:不能访问

注: 红色字体,表示不可行

private 私有的

package/friendly/default

没有修饰符即代表package/friendly/default
float maxHP; 血量上限

package/friendly/default 不写

protected 受保护的

受保护的修饰符
protected float hp; 血量

protected 受保护的

public 公共的

公共的修饰符
public String name; 姓名
任何地方,都可以访问

public 公共的

总结

总结

那么什么情况该用什么修饰符呢?
从作用域来看,public能够使用所有的情况。 但是大家在工作的时候,又不会真正全部都使用public,那么到底什么情况该用什么修饰符呢?

  1. 属性通常使用private封装起来
  2. 方法一般使用public用于被调用
  3. 会被子类继承的方法,通常使用protected
  4. package用的不多,一般新手会用package,因为还不知道有修饰符这个东西

再就是作用范围最小原则
简单说,能用private就用private,不行就放大一级,用package,再不行就用protected,最后用public。 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来

static关键字

代码块:在构造方法前

static静态代码块:最先执行,且只执行一次

类属性

当一个属性被static修饰的时候,就叫做类属性,又叫做静态属性
当一个属性被声明成类属性,那么所有的对象,都共享一个值
与对象属性对比:
不同对象的 对象属性 的值都可能不一样。
比如盖伦的hp 和 提莫的hp 是不一样的。
但是所有对象的类属性的值,都是一样的。

package com.wang.oop.demo01;
/*
类属性: 又叫做静态属性
对象属性: 又叫实例属性,非静态属性
如果一个属性声明成类属性,那么所有的对象,都共享这么一个值
给英雄设置一个类属性叫做“版权" (copyright), 无论有多少个具体的英雄,所有的英雄的版权都属于 Riot Games公司。
*/
public class Hero {

  public String name; //实例属性,对象属性,非静态属性
  protected float hp;
  static String copyright= "版权由Riot Games公司所有";;//类属性,静态属性

  public static void main(String[] args) {
      Hero garen =  new Hero();
      garen.name = "盖伦";

      //Hero.copyright = "版权由Riot Games公司所有";

      System.out.println(garen.name);
      System.out.println(garen.copyright);

      Hero teemo =  new Hero();
      teemo.name = "提莫";
      System.out.println(teemo.name);
      System.out.println(teemo.copyright);

  }
}

访问类属性有两种方式

  1. 对象.类属性

teemo.copyright

  1. 类.类属性

Hero.copyright

这两种方式都可以访问类属性,访问即修改和获取,但是建议使用第二种 类.类属性 的方式进行,这样更符合语义上的理解

类方法

类方法: 又叫做静态方法

对象方法: 又叫实例方法,非静态方法

访问一个对象方法,必须建立在有一个对象的前提的基础上
访问类方法,不需要对象的存在,直接就访问

package charactor;
/*
类方法: 又叫做静态方法

对象方法: 又叫实例方法,非静态方法

访问一个对象方法,必须建立在有一个对象的前提的基础上
访问类方法,不需要对象的存在,直接就访问
*/
public class Hero {
    public String name;
    protected float hp;
 
    //实例方法,对象方法,非静态方法
    //必须有对象才能够调用
    public void die(){
        hp = 0;
    }
     
    //类方法,静态方法
    //通过类就可以直接调用
    public static void battleWin(){
        System.out.println("battle win");
    }
     
    public static void main(String[] args) {
           Hero garen =  new Hero();
           garen.name = "盖伦";
           //必须有一个对象才能调用
           garen.die();
            
           Hero teemo =  new Hero();
           teemo.name = "提莫";
            
           //无需对象,直接通过类调用
           Hero.battleWin();
         
    }
}

和访问类属性一样,调用类方法也有两种方式

  1. 对象.类方法

garen.battleWin();

  1. 类.类方法

Hero.battleWin();

这两种方式都可以调用类方法,但是建议使用第二种 类.类方法 的方式进行,这样更符合语义上的理解。
并且在很多时候,并没有实例,比如在前面练习的时候用到的 随机数的获取办法

Math.random()

random()就是一个类方法,直接通过类Math进行调用,并没有一个Math的实例存在。

如果在某一个方法里,调用了对象属性,比如

​ public String getName(){

​ return name;

​ }

name属性是对象属性,只有存在一个具体对象的时候,name才有意义。 如果方法里访问了对象属性,那么这个方法,就必须设计为对象方法

属性初始化

对象属性初始化

对象属性初始化有3种

  1. 声明该属性的时候初始化
  2. 构造方法中初始化
  3. 初始化块
package charactor;
 
public class Hero {
    public String name = "some hero"; //声明该属性的时候初始化
    protected float hp;
    float maxHP;
     
    {
        maxHP = 200; //初始化块
    }  
     
    public Hero(){
        hp = 100; //构造方法中初始化
         
    }
     
}

类属性初始化

类属性初始化有2种

  1. 声明该属性的时候初始化
  2. 静态初始化块
package charactor;
 
public class Hero {
    public String name;
    protected float hp;
    float maxHP;
     
    //物品栏的容量
    public static int itemCapacity=8; //声明的时候 初始化
     
    static{
        itemCapacity = 6;//静态初始化块 初始化
    }
     
    public Hero(){
         
    }
     
    public static void main(String[] args) {
        System.out.println(Hero.itemCapacity);
    }
     
}

属性初始化顺序

类属性(静态变量声明、静态初始化块(前后顺序))>对象属性(变量属性声明、初始化块(前后顺序))>构造方法

单例模式

单例模式又叫做 Singleton模式,指的是一个类,在一个JVM里,只有一个实例存在。

单例模式三要素

  1. 构造方法私有化
  2. 静态属性指向实例
  3. public static的 getInstance方法,返回第二步的静态属性

饿汉式单例模式

/*
GiantDragon 应该只有一只,通过私有化其构造方法,使得外部无法通过new 得到新的实例。
GiantDragon 提供了一个public static的getInstance方法,外部调用者通过该方法获取12行定义的对象,而且每一次都是获取同一个对象。 从而达到单例的目的。
这种单例模式又叫做饿汉式单例模式,无论如何都会创建一个实例
*/
package com.wang.oop.demo02;

public class GaintDragon {
    //私有化构造方法使得该类无法在外部通过new 进行实例化
    private GaintDragon(){
    }

    //准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个
    private static GaintDragon instance = new GaintDragon();

    //public static 方法,提供给调用者获取12行定义的对象
    public static GaintDragon getInstance(){ //返回一个对象
        return instance;
    }

}

package com.wang.oop.demo02;

public class TestGiantDragon{
    public static void main(String[] args){
        //new实例化会报错
        //GaintDragon g = new GaintDragon();

        //只能通过getInstance得到private对象的地址
        GaintDragon g1 = GaintDragon.getInstance();
        GaintDragon g2 = GaintDragon.getInstance();
        GaintDragon g3 = GaintDragon.getInstance();

        //都是一个对象
        System.out.println(g1==g2); //ture
        System.out.println(g1==g3); //ture
    }
}

懒汉式单例模式

懒汉式单例模式与饿汉式单例模式不同,只有在调用getInstance的时候,才会创建实例

package charactor;
 
public class GiantDragon {
  
    //私有化构造方法使得该类无法在外部通过new 进行实例化
    private GiantDragon(){       
    }
  
    //准备一个类属性,用于指向一个实例化对象,但是暂时指向null
    private static GiantDragon instance;
      
    //public static 方法,返回实例对象
    public static GiantDragon getInstance(){
        //第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象
        if(null==instance){
            instance = new GiantDragon();
        }
        //返回 instance指向的对象
        return instance;
    }     
}

饿汉式与懒汉式

饿汉式是立即加载的方式,无论是否会用到这个对象,都会加载。
如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿。

懒汉式,是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量(鉴于同学们学习的进度,暂时不对线程的章节做展开)。
使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。

看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式

枚举

枚举enum是一种特殊的类(还是类),使用枚举可以很方便的定义常量

比如设计一个枚举类型 季节,里面有4种常量

public enum Season {
	SPRING,SUMMER,AUTUMN,WINTER //注:因为是常量,所以一般都是全大写
}

一个常用的场合就是switch语句中,使用枚举来进行判断.

package com.wang.oop.demo03;

public class Application {
    public static void main(String[] args) {
        Season season = Season.SPRING; //实例
        switch (season){
            case SPRING:
                System.out.println("春天");
                break;
            case SUMMER:
                System.out.println("夏天");
                break;
        }
    }
}

遍历枚举

借助增强型for循环,可以很方便的遍历一个枚举都有哪些常量

public class HelloWorld {
    public static void main(String[] args) {
        for (Season s : Season.values()) { //Season.value()
            System.out.println(s);
        }
    }
}
package com.wang.oop.demo03;

public class Application {
    public enum Char{ //在类里面定义
        TANK,WIZARD,ASSASSIN,ASSIST,WARRIOR
    }
    public static void main(String[] args) {
        for (Char type:Char.values()){
            switch(type){
                case TANK :
                    System.out.println("坦克");
                    break;
                case ASSIST:
                    System.out.println("");
            }
        }
    }
}

abstract抽象类

作用:子类继承的时候必须重写这个方法。避免开发的时候忘记重写了

在类中声明一个方法,这个方法没有实现体,是一个“空”方法

当一个类有抽象方法的时候,该类必须被声明为抽象类

package charactor;

public abstract class Hero {
String name;

float hp;

float armor;

int moveSpeed;

public static void main(String[] args) {

}

// 抽象方法attack
// Hero的子类会被要求实现attack方法
public abstract void attack();

}

抽象类可以没有抽象方法

一旦一个类被声明为抽象类,就不能够被直接实例化

abstract抽象方法,只有方法名字,没有方法的实现。

  1. 不能new这个抽象类,只能靠子类去实现它;约束!
  2. 抽象类中可以写普通的方法。
  3. 抽象方法必须在抽象类中。

接口

接口:只有规范!自己无法写方法~专业的约束!约束和实现分离:面向接口编程。

接口的本质是契约!

声明接口的关键字是interface

接口中定义的基本类型都是静态常量:public static final

接口中的所有定义其实都是抽象的public abstract

*接口都需要实现类:类的结尾Impl,implements

实现了接口的类,就需要重写接口的方法

多继承,利用接口实现

package com.wang.oop.Demo04;

public interface AD {
    public void physicAttack();
}

public class ADHero implements AD{
    @Override
    public void physicAttack() {
        System.out.println("physic attack");
    }

    public static void main(String[] args) {
        ADHero ad = new ADHero();
        ad.physicAttack();
    }
}

抽象类和接口的区别

区别1:
子类只能继承一个抽象类,不能继承多个
子类可以实现多个接口
区别2:
抽象类可以定义
public,protected,package,private
静态和非静态属性
final和非final属性
但是接口中声明的属性,只能是
public
静态
final的
即便没有显式的声明
注: 抽象类和接口都可以有实体方法。 接口中的实体方法,叫做默认方法

    //resistMagic即便没有显式的声明为 public static final
    //但依然默认为public static final
    int resistMagic = 0;

内部类

非静态内部类

可以直接在一个类里面定义

语法: new 外部类().new 内部类()
作为Hero的非静态内部类,是可以直接访问外部类的private实例属性name的

package charactor;
 
public class Hero {
    private String name; // 姓名
 
    float hp; // 血量
 
    float armor; // 护甲
 
    int moveSpeed; // 移动速度
 
    // 非静态内部类,只有一个外部类对象存在的时候,才有意义
    // 战斗成绩只有在一个英雄对象存在的时候才有意义
    class BattleScore {
        int kill;
        int die;
        int assit;
 
        public void legendary() {
            if (kill >= 8)
                System.out.println(name + "超神!");
            else
                System.out.println(name + "尚未超神!");
        }
    }
 
    public static void main(String[] args) {
        Hero garen = new Hero();
        garen.name = "盖伦";
        // 实例化内部类
        // BattleScore对象只有在一个英雄对象存在的时候才有意义
        // 所以其实例化必须建立在一个外部类对象的基础之上
        BattleScore score = garen.new BattleScore();
        score.kill = 9;
        score.legendary();
    }
 
}

静态内部类

在一个类里面声明一个静态内部类
比如敌方水晶,当敌方水晶没有血的时候,己方所有英雄都取得胜利,而不只是某一个具体的英雄取得胜利。
与非静态内部类不同,静态内部类水晶类的实例化 不需要一个外部类的实例为基础,可以直接实例化
语法:new 外部类.静态内部类();
因为没有一个外部类的实例,所以在静态内部类里面不可以访问外部类的实例属性和方法
除了可以访问外部类的私有静态成员外,静态内部类和普通类没什么大的区别

package charactor;
  
public class Hero {
    public String name;
    protected float hp;
  
    private static void battleWin(){
        System.out.println("battle win");
    }
     
    //敌方的水晶
    static class EnemyCrystal{
        int hp=5000;
         
        //如果水晶的血量为0,则宣布胜利
        public void checkIfVictory(){
            if(hp==0){
                Hero.battleWin();
                 
                //静态内部类不能直接访问外部类的对象属性
                System.out.println(name + " win this game");
            }
        }
    }
     
    public static void main(String[] args) {
        //实例化静态内部类
        Hero.EnemyCrystal crystal = new Hero.EnemyCrystal();
        crystal.checkIfVictory();
    }
  
}

匿名类

匿名类指的是在声明一个类的同时实例化它,使代码更加简洁精练
通常情况下,要使用一个接口或者抽象类,都必须创建一个子类

有的时候,为了快速使用,直接实例化一个抽象类,并“当场”实现其抽象方法。
既然实现了抽象方法,那么就是一个新的类,只是这个类,没有命名。
这样的类,叫做匿名类

package charactor;
   
public abstract class Hero {
    String name; //姓名
          
    float hp; //血量
          
    float armor; //护甲
          
    int moveSpeed; //移动速度
      
    public abstract void attack();
      
    public static void main(String[] args) {
          
        ADHero adh=new ADHero();
        //通过打印adh,可以看到adh这个对象属于ADHero类
        adh.attack();
        System.out.println(adh);
        
        //因为在实现抽象方法时,实际已经生成了一个新(匿名)类,就如讲解中示例的Hero$1这个类。
        //换言之,也就不是将抽象类Hero实例化了,实际上实例化的是匿名类Hero$1,这才是匿名的真意。
        Hero h = new Hero(){
            //当场实现attack方法
            public void attack() {
                System.out.println("新的进攻手段");
            }
        };
        h.attack();
        //通过打印h,可以看到h这个对象属于Hero$1这么一个系统自动分配的类名
          
        System.out.println(h);
    }
      
}

本地类

本地类可以理解为有名字的匿名类
内部类与匿名类不一样的是,内部类必须声明在成员的位置,即与属性和方法平等的位置。
本地类和匿名类一样,直接声明在代码块里面,可以是主方法,for循环里等等地方

package charactor;
   
public abstract class Hero {
    String name; //姓名
          
    float hp; //血量
          
    float armor; //护甲
          
    int moveSpeed; //移动速度
      
    public abstract void attack();
      
    public static void main(String[] args) {
          
        //与匿名类的区别在于,本地类有了自定义的类名
        class SomeHero extends Hero{
            public void attack() {
                System.out.println( name+ " 新的进攻手段");
            }
        }
         
        SomeHero h  =new SomeHero();
        h.name ="地卜师";
        h.attack();
    }
      
}

匿名类中使用外部局部变量

在匿名类中使用外部的局部变量,外部的局部变量必须修饰为final

为什么要声明为final,其机制比较复杂,请参考第二个Hero代码中的解释

注:在jdk8中,已经不需要强制修饰成final了,如果没有写final,不会报错,因为编译器偷偷的帮你加上了看不见的final

默认方法

默认方法是JDK8新特性,指的是接口也可以提供具体方法了,而不像以前,只能提供抽象方法

Mortal 这个接口,增加了一个默认方法 revive,这个方法有实现体,并且被声明为了default

package charactor;
 
public interface Mortal {
    public void die();
 
    default public void revive() {
        System.out.println("本英雄复活了");
    }
}

作用:

假设没有默认方法这种机制,那么如果要为Mortal增加一个新的方法revive,那么所有实现了Mortal接口的类,都需要做改动。

但是引入了默认方法后,原来的类,不需要做任何改动,并且还能得到这个默认方法

通过这种手段,就能够很好的扩展新的类,并且做到不影响原来的类

//问: ADAPHero同时实现了AD,AP接口,那么 ADAPHero 对象调用attack()的时候,是调用哪个接口的attack()?
//ADAPhero 实现AD AP 接口,必须重写该接口的方法,这样才不会报错

UML图

UML-Unified Module Language
统一建模语言,可以很方便的用于描述类的属性,方法,以及类和类之间的关系

类图

接口图

继承关系

带箭头的实线,表示 Spider,Cat, Fish都继承于Animal这个父类.

实现关系

表示 Fish实现了 Pet这个接口

继承与接口练习

/*
this后加 . 用来调用非构造方法和成员熟悉,this后不加 . 用来调用构造方法。this(“”)用来调用本类中的构造方法。
super(参数)用来调用父类构造方法。
 */

题目:

\1. 创建Animal类,它是所有动物的抽象父类。
\2. 声明一个受保护的整数类型属性legs,它记录动物的腿的数目。
\3. 定义一个受保护的构造器,用来初始化legs属性。
\4. 声明抽象方法eat。
\5. 声明具体方法walk来打印动物是如何行走的(包括腿的数目)。

\1. Spider继承Animal类。
\2. 定义默认构造器,它调用父类构造器来指明所有蜘蛛都是8条腿。
\3. 实现eat方法

根据UML类创建pet(宠物)接口
\1. 提供getName() 返回该宠物的名字
\2. 提供setName(String name) 为该宠物命名
\3. 提供 play()方法

\1. 该类必须包含String属性来存宠物的名字。
\2. 定义一个构造器,它使用String参数指定猫的名字;该构造器必须调用超类构造器来指明所有的猫都是四条腿。
\3. 另定义一个无参的构造器。该构造器调用前一个构造器(用this关键字)并传递一个空字符串作为参数
\4. 实现Pet接口方法。
\5. 实现eat方法。

public abstract class Animal {
    String name;
    protected int legs;
    protected Animal(String name, int legs){
        this.name=name;
        this.legs=legs;
    }
    public abstract void eat();
    public void walk(Animal h){
        System.out.println(name+"用"+legs+"条腿走路");
    }
    public static void main(String[] args) {
        Animal h = new Animal("猪",4) {
            @Override
            public void eat() {
                System.out.println(this.name+"拱白菜");
            }
        };
        h.eat();
        h.walk(h);
    }
}


public class Spider extends Animal{
    public Spider(String name , int legs){
        super(name, 8);
    }
    @Override
    public void eat() {
        System.out.println(this.name+"吃虫子");
    }
    public static void main(String[] args) {
        Spider spider = new Spider("蜘蛛",8);
        spider.eat();
        spider.walk(spider);
    }
}


public interface Pet {
    String getName();
    void setName(String name);
    void play();
}


public class Cat extends Animal implements Pet {
    public Cat(String name,int legs){
        super(name,4);
    }
    /*
    this后加 . 用来调用非构造方法和成员熟悉,this后不加 . 用来调用构造方法。this(“”)用来调用本类中的构造方法。
    super(参数)用来调用父类构造方法。
     */
    public Cat(){ //无参构造调用该类中前一个构造器
        this(" ",0);
    }
    @Override
    public void eat() {
        System.out.println(this.name+"吃鱼");
    }
    @Override
    public String getName() {
        System.out.println(name);
        return name;
    }
    @Override
    public void setName(String name) {
        this.name=name;
    }
    @Override
    public void play() {
        System.out.println(name+"自己玩");
    }
    public static void main(String[] args) {
        Cat cat = new Cat("猫",4);
        cat.eat();
        cat.getName();
        cat.setName("大猫");
        cat.getName();
        cat.walk(cat);
        cat.play();
    }
}

数字与字符串

装箱拆箱

封装类

所有的基本类型,都有对应的类类型
比如int对应的类是Integer
这种类就叫做封装类

package digit;
 
public class TestNumber {
 
    public static void main(String[] args) {
        int i = 5;
         
        //把一个基本类型的变量,转换为Integer对象
        Integer it = new Integer(i);
        //把一个Integer对象,转换为一个基本类型的int
        int i2 = it.intValue();
         
    }
}

Number类

数字封装类有
Byte,Short,Integer,Long,Float,Double
这些类都是抽象类Number的子类

基本类型转封装类与封装类转基本类型

package digit;
 
public class TestNumber {
 
    public static void main(String[] args) {
        int i = 5;
 
        //基本类型转换成封装类型
        Integer it = new Integer(i);
         
        //封装类型转换成基本类型
        int i2 = it.intValue(); //intValue()
         
    }
}

自动装箱

不需要调用构造方法,通过=符号 自动把 基本类型 转换为 类类型 就叫装箱

package digit;
 
public class TestNumber {
 
    public static void main(String[] args) {
        int i = 5;
 
        //基本类型转换成封装类型
        Integer it = new Integer(i);
         
        //自动转换就叫装箱
        Integer it2 = i;
         
    }
}

自动拆箱

不需要调用Integer的intValue方法,通过=就自动转换成int类型,就叫拆箱

package digit;
  
public class TestNumber {
  
    public static void main(String[] args) {
        int i = 5;
  
        Integer it = new Integer(i);
          
        //封装类型转换成基本类型
        int i2 = it.intValue();
         
        //自动转换就叫拆箱
        int i3 = it;
          
    }
}

Integer.MAX_VALUE

int的最大值可以通过其对应的封装类Integer.MAX_VALUE获取

package digit;
  
public class TestNumber {
  
    public static void main(String[] args) {
 
        //int的最大值
        System.out.println(Integer.MAX_VALUE);
        //int的最小值      
        System.out.println(Integer.MIN_VALUE);
          
    }
}

拆箱与装箱

package com.wang.oop.demo13;

public class Demo01 {
    public static void main(String[] args) {
        int i = 5;
        Integer it = new Integer(i);
        Integer it2 = i; //装箱
        int i2 = it; //拆箱

        byte b = 127;
        Byte by = new Byte(b);
        Byte by2 = b; //装箱
        byte b2 =by; //拆箱

        Byte by3 = i; //Byte和int不能自动装箱
        Integer it3 = b; //Integer和byte不能自动装箱
        byte b3 = it; //byte和Integer不能自动拆箱
        int i3 = by; //int和Byte能自动拆箱

    }
}

字符串转换

数字转字符串

方法1: 使用String类的静态方法valueOf
方法2: 先把基本类型装箱为对象,然后调用对象的toString

package digit;
  
public class TestNumber {
  
    public static void main(String[] args) {
        int i = 5;
         
        //方法1
        String str = String.valueOf(i);
         
        //方法2
        Integer it = i;
        String str2 = it.toString();
         
    }
}

字符串转数字parse

调用Integer的静态方法parseInt //parse解析的意思

package digit;
  
public class TestNumber {
  
    public static void main(String[] args) {
 
        String str = "999";
         
        int i= Integer.parseInt(str);
         
        System.out.println(i);
         
    }
}

常用Math类

四舍五入, 随机数,开方,次方,π,自然常数

package digit;
  
public class TestNumber {
  
    public static void main(String[] args) {
        float f1 = 5.4f;
        float f2 = 5.5f;
        //5.4四舍五入即5
        System.out.println(Math.round(f1));
        //5.5四舍五入即6
        System.out.println(Math.round(f2));
         
        //得到一个0-1之间的随机浮点数(取不到1)
        System.out.println(Math.random());
         
        //得到一个0-10之间的随机整数 (取不到10)
        System.out.println((int)( Math.random()*10));
        //开方
        System.out.println(Math.sqrt(9));
        //次方(2的4次方)
        System.out.println(Math.pow(2,4));
         
        //π
        System.out.println(Math.PI);
         
        //自然常数
        System.out.println(Math.E); //2.71828...
    }
}

利用Math方法算出自然常数,与判断质数

package numberAndString.mathMethod;
 
public class Test {
    public static void main(String[] args) {
        System.out.println(Math.E);
        int n = Integer.MAX_VALUE;
        System.out.println(Math.pow((1 + 1d / n), n));//自然常数
        
        int num =1;
        for (int i = 0; i < 10000000; i++) {
            boolean prime = isPrime(i);
            if(prime) {
                num+=1;
            }
        }
        System.out.println(num);
    }
 
    public static boolean isPrime(int a) {
        boolean flag = true;
        if (a < 2) {// 素数不小于2
            return false;
        } else {
            for (int i = 2; i <= Math.sqrt(a); i++) {
                if (a % i == 0) {// 若能被整除,则说明不是素数,返回false
                    flag = false;
                    break;// 跳出循环
                }
            }
        }
        return flag;
    }
}

格式化输出

如果不使用格式化输出,就需要进行字符串连接,如果变量比较多,拼接就会显得繁琐
使用格式化输出,就可以简洁明了

%s 表示字符串
%d 表示数字
%n 表示换行

%d表示整数,%f表示小数

package digit;
  
public class TestNumber {
  
    public static void main(String[] args) {
 
        String name ="盖伦";
        int kill = 8;
        String title="超神";
         
        //直接使用+进行字符串连接,编码感觉会比较繁琐,并且维护性差,易读性差
        String sentence = name+ " 在进行了连续 " + kill + " 次击杀后,获得了 " + title +" 的称号";
         
        System.out.println(sentence);
         
        //使用格式化输出
        //%s表示字符串,%d表示数字,%n表示换行
        String sentenceFormat ="%s 在进行了连续 %d 次击杀后,获得了 %s 的称号%n";
        //使用printf格式化输出
        System.out.printf(sentenceFormat,name,kill,title);
        //使用format格式化输出
        System.out.format(sentenceFormat,name,kill,title);
         
    }
}

printf和format能够达到一模一样的效果,在printf中直接调用了format

format格式化方法

package com.wang.oop.demo13;

import java.util.Locale;

public class Demo03 {
    public static void main(String[] args) {
        int year = 2020;
        //总长度,左对齐,补0,千位分隔符,小数点位数,本地化表达

        //直接打印数字
        System.out.format("%d%n",year);
        //总长度是8,默认右对齐
        System.out.format("%8d%n",year);
        //总长度是8,左对齐
        System.out.format("%-8d%n",year);
        //总长度是8,不够补0
        System.out.format("%08d%n",year);
        //千位分隔符
        System.out.format("%,8d%n",year*10000);

        //小数点位数
        System.out.format("%.2f%n",Math.PI);

        //不同国家的千位分隔符
        System.out.format(Locale.FRANCE,"%,.2f%n",Math.PI*10000);
        System.out.format(Locale.US,"%,.2f%n",Math.PI*10000);
        System.out.format(Locale.UK,"%,.2f%n",Math.PI*10000);

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mtd8kua-1633015831054)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210905224605712.png)]

字符

保存一个字符的时候用char,封装类Character

        char c1 = 'a';
        char c2 = '1';//字符1,而非数字1
        char c3 = '中';//汉字字符
        char c4 = 'ab'; //只能放一个字符

        char c1 = 'a';
        Character c = c1; //自动装箱
        c1 = c;//自动拆箱

        //常用方法
        System.out.println(Character.isLetter('a'));//判断是否为字母
        System.out.println(Character.isDigit('a')); //判断是否为数字
        System.out.println(Character.isWhitespace(' ')); //是否是空白
        System.out.println(Character.isUpperCase('a')); //是否是大写
        System.out.println(Character.isLowerCase('a')); //是否是小写
         
        System.out.println(Character.toUpperCase('a')); //转换为大写
        System.out.println(Character.toLowerCase('A')); //转换为小写
 
        String a = 'a'; //不能够直接把一个字符转换成字符串
        String a2 = Character.toString('a'); //转换为字符串

        //常见转义
        System.out.println("使用\\t制表符可以达到对齐的效果");
        System.out.println("abc\tdef");
        System.out.println("ab\tdef");
        System.out.println("a\tdef");
         
        System.out.println("一个\\t制表符长度是8");
        System.out.println("12345678def");
          
        System.out.println("换行符 \\n");
        System.out.println("abc\ndef");
 
        System.out.println("单引号 \\'");
        System.out.println("abc\'def");
        System.out.println("双引号 \\\"");
        System.out.println("abc\"def");
        System.out.println("反斜杠本身 \\");
        System.out.println("abc\\def");

通过Scanner从控制台读取字符串,然后把字符串转换为字符数组
参考的转换方式:

String str = “abc123”;

char[] cs = str.toCharArray();

package com.wang.oop.demo13;

import java.util.Scanner;

public class Demo05 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入字符串:");
        String str = scanner.nextLine();
        char[] cs = str.toCharArray();
        for (int i = 0; i < cs.length; i++) {
            if(Character.isUpperCase(cs[i]))
                System.out.print(cs[i]);
            if(Character.isDigit(cs[i]))
                System.out.print(cs[i]);
        }
        scanner.close();
    }
}

字符串

字符串是一个类,所以我们见到的字符串都是对象。

常见创建字符串手段:
\1. 每当有一个字面值出现的时候,虚拟机就会创建一个字符串
\2. 调用String的构造方法创建一个字符串对象
\3. 通过+加号进行字符串拼接也会创建新的字符串对象

package character;
 
public class TestString {
 
    public static void main(String[] args) {
        String garen ="盖伦"; //字面值,虚拟机碰到字面值就会创建一个字符串对象
         
        String teemo = new String("提莫"); //创建了两个字符串对象
         
        char[] cs = new char[]{'崔','斯','特'};
         
        String hero = new String(cs);//  通过字符数组创建一个字符串对象
         
        String hero3 = garen + teemo;//  通过+加号进行字符串拼接
    }
}

String 被修饰为final,所以是不能被继承的

immutable 是指不可改变的
比如创建了一个字符串对象
String garen =“盖伦”;
不可改变的具体含义是指:
不能增加长度
不能减少长度
不能插入字符
不能删除字符
不能修改字符
一旦创建好这个字符串,里面的内容 永远 不能改变

String 的表现就像是一个常量

字符串格式化String.format

        //直接使用+进行字符串连接,编码感觉会比较繁琐,并且维护性差,易读性差
        String sentence = name+ " 在进行了连续 " + kill + " 次击杀后,获得了 " + title +" 的称号";
          
        System.out.println(sentence);
         
        //格式化字符串
        //%s表示字符串,%d表示数字,%n表示换行
        String sentenceFormat ="%s 在进行了连续 %d 次击杀后,获得了 %s 的称号%n";
         
        String sentence2 = String.format(sentenceFormat, name,kill,title);
         
        System.out.println(sentence2);

length方法返回当前字符串的长度
可以有长度为0的字符串,即空字符串

随机字符串

创建一个长度是5的随机字符串,随机字符有可能是数字,大写字母或者小写字母

package com.wang.oop.demo13;

import java.util.Random;

public class Demo06 {
    public static void main(String[] args) {
        //0-9 48-57; A-Z 65-90; a-z 97-122
        String str = creatString(5);
        System.out.println(str);

    }

    public static String creatString(int length){
        String str = "";
        int num;
        char c;
        Random rd = new Random();

        for(int i =0; i<length; i++){
            num = rd.nextInt(75)+48;  // 随机数范围:[48,122)
            if((num>=58 && num<=64) || (num>=91 && num<=96)){
                i--;
                continue;
            }
            c = (char)num;
            str = str +c;
        }
        return str;
    }
}

字符串数组排序

    public static String[] sort(String[] str){
        String temp = "";
        for (int i = 0; i< str.length; i++){
            for (int j = 0; j < str.length-i-1; j++) {
                char[] ch0 = str[j].toCharArray();
                char[] ch1 = str[j+1].toCharArray();
                if(Character.toLowerCase(ch0[0]) > Character.toLowerCase(ch1[0])){
                    temp = str[j];
                    str[j] = str[j+1];
                    str[j+1] = temp;
                }
            }
        }
        return str;
    }

穷举法破解密码

    public static String crack(int length,String str){
        String result ="";
        char[] ch0 = str.toCharArray();
        for (int i = 0; i < length; i++) {
            for (int j = 33; j < 126; j++) {
                if(ch0[i]==(char)j)
                    result+=(char)j;
            }
        }
        return result;
    }

操纵字符串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LT4q3Zm2-1633015831055)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210906233610594.png)]

charAt(int index) 获取指定位置的字符
        String sentence = "盖伦,在进行了连续8次击杀后,获得了 超神 的称号";         
        char c = sentence.charAt(0);       
        System.out.println(c);

toCharArray() 获取对应的字符数组
        String sentence = "盖伦,在进行了连续8次击杀后,获得了超神 的称号"; 
        char[] cs = sentence.toCharArray(); //获取对应的字符数组         
        System.out.println(sentence.length() == cs.length);

subString 截取子字符串
        String sentence = "盖伦,在进行了连续8次击杀后,获得了 超神 的称号";         
        //截取从第3个开始的字符串 (基0)
        String subString1 = sentence.substring(3);        
        System.out.println(subString1);         
        //截取从第3个开始的字符串 (基0)
        //到5-1的位置的字符串
        //左闭右开
        String subString2 = sentence.substring(3,5);        
        System.out.println(subString2);

split 根据分隔符进行分隔
        String sentence = "盖伦,在进行了连续8次击杀后,获得了 超神 的称号";        
        //根据,进行分割,得到3个子字符串
        String subSentences[] = sentence.split(",");
        for (String sub : subSentences) {
            System.out.println(sub);
        }

trim 去掉首尾空格
        String sentence = "        盖伦,在进行了连续8次击杀后,获得了 超神 的称号      ";         
        System.out.println(sentence);
        //去掉首尾空格
        System.out.println(sentence.trim());

toLowerCase 全部变成小写
toUpperCase 全部变成大写
        String sentence = "Garen";         
        //全部变成小写
        System.out.println(sentence.toLowerCase());
        //全部变成大写
        System.out.println(sentence.toUpperCase());

indexOf lastIndexOf 判断字符或者子字符串出现的位置
contains 是否包含子字符串
        String sentence = "盖伦,在进行了连续8次击杀后,获得了超神 的称号";  
        System.out.println(sentence.indexOf('8')); //字符第一次出现的位置         
        System.out.println(sentence.indexOf("超神")); //字符串第一次出现的位置          
        System.out.println(sentence.lastIndexOf("了")); //字符串最后出现的位置          
        System.out.println(sentence.indexOf(',',5)); //从位置5开始,出现的第一次,的位置          
        System.out.println(sentence.contains("击杀")); //是否包含字符串"击杀"

replaceAll 替换所有的
replaceFirst 只替换第一个
        String sentence = "盖伦,在进行了连续8次击杀后,获得了超神 的称号"; 
        String temp = sentence.replaceAll("击杀", "被击杀"); //替换所有的         
        temp = temp.replaceAll("超神", "超鬼");        
        System.out.println(temp);         
        temp = sentence.replaceFirst(",","");//只替换第一个       
        System.out.println(temp);

例子

    public static String upperFirst(String sentence){ //每个单词的首字母都转换为大写
        String sentence1 = "";
        String[] str = sentence.split(" ");
        for (int i = 0; i < str.length; i++) {
            char ch0 =Character.toUpperCase(str[i].charAt(0));
            String str1 = str[i].substring(1);
            str[i] = ch0 + str1;
            sentence1 += str[i]+" ";
        }
        return sentence1;
    }

    public static void specilFirst(String sentence,char p){ //统计这段绕口令有多少个以p开头的单词,不分大小写
        String[] str = sentence.split(" ");
        p = Character.toUpperCase(p);
        int num = 0;
        for (int i = 0; i < str.length; i++) {
            char ch0 = Character.toUpperCase(str[i].charAt(0));
            if(p==ch0)
                num++;
        }
        System.out.printf("有%d个以%c开头的单词",num,p);
        System.out.println();
    }

    public static String upperLower(String sentence){ //间隔大写小写模式
        String sentence1 = "";
        String[] str = sentence.split(" ");
        for (int i = 0; i < str.length; i++) {
            char[] ch0 = str[i].toCharArray();
            for (int j = 0; j < ch0.length; j++) {
                if(j%2==0)
                    ch0[j]=Character.toUpperCase(ch0[j]);
                else
                    ch0[j]=Character.toLowerCase(ch0[j]);
            }
            String word = new String(ch0); //通过字符数组创建一个字符串对象
            sentence1 += word +" ";
        }
        return sentence1;
    }

    public static String upperLast(String sentence){ //每个单词的最后一个字母变大写
        String sentence1 = "";
        String[] str = sentence.split(" ");
        for (int i = 0; i < str.length; i++) {
            char[] ch0 = str[i].toCharArray();
            ch0[ch0.length-1] = Character.toUpperCase(ch0[ch0.length-1]);
            str[i]=new String(ch0);
            sentence1 += str[i]+" ";
        }
        return sentence1;
    }

    public static String lastWordUpperFirst(String sentence,String word){ //最后一个想找的单词首字母大写
        int index = sentence.lastIndexOf(word);
        char[] ch0 = sentence.toCharArray();
        ch0[index]=Character.toUpperCase(ch0[index]);
        String str = new String(ch0); //通过字符数组创建一个字符串对象
        return str;
    }
}

比较字符串

str1和str2的内容一定是一样的!
但是,并不是同一个字符串对象

package character;
 
public class TestString {
 
    public static void main(String[] args) {
 
        String str1 = "the light";         
        String str2 = new String(str1);
         
        //==用于判断是否是同一个字符串对象
        System.out.println( str1  ==  str2);       
    }
}

是否是同一个对象-特例

一般说来,编译器每碰到一个字符串的字面值,就会创建一个新的对象
所以在第6行会创建了一个新的字符串"the light"
但是在第7行,编译器发现已经存在现成的"the light",那么就直接拿来使用,而没有进行重复创建

package character;
 
public class TestString {
 
    public static void main(String[] args) {
        String str1 = "the light";
        String str3 = "the light";
        System.out.println( str1  ==  str3);
    } 
}

equals equalsIgnoreCase

使用equals进行字符串内容的比较,必须大小写一致
equalsIgnoreCase,忽略大小写判断内容是否一致

package character;
  
public class TestString {
  
    public static void main(String[] args) {
  
        String str1 = "the light";
          
        String str2 = new String(str1);
         
        String str3 = str1.toUpperCase();
 
        //==用于判断是否是同一个字符串对象
        System.out.println( str1  ==  str2); //false
         
        System.out.println(str1.equals(str2));//完全一样返回true
         
        System.out.println(str1.equals(str3));//大小写不一样,返回false
        System.out.println(str1.equalsIgnoreCase(str3));//忽略大小写的比较,返回true
         
    }
  
}

是否以子字符串开始或者结束

        String str1 = "the light";
         
        String start = "the";
        String end = "Ight";
         
        System.out.println(str1.startsWith(start));//以...开始
        System.out.println(str1.endsWith(end));//以...结束

StringBuffer

StringBuffer是可变长的字符串

append delete insert reverse追加 删除 插入 反转
length capacity长度 容量
        String str1 = "let there ";
 
        StringBuffer sb = new StringBuffer(str1); //根据str1创建一个StringBuffer对象
        sb.append("be light"); //在最后追加
         
        System.out.println(sb);
         
        sb.delete(4, 10);//删除4-10之间的字符
         
        System.out.println(sb);
         
        sb.insert(4, "there ");//在4这个位置插入 there
         
        System.out.println(sb);
         
        sb.reverse(); //反转
         
        System.out.println(sb);

为什么StringBuffer可以变长?
和String内部是一个字符数组一样,StringBuffer也维护了一个字符数组。 但是,这个字符数组,留有冗余长度
比如说new StringBuffer(“the”),其内部的字符数组的长度,是19,而不是3,这样调用插入和追加,在现成的数组的基础上就可以完成了。
如果追加的长度超过了19,就会分配一个新的数组,长度比原来多一些,把原来的数据复制到新的数组中,看上去 数组长度就变长了 参考MyStringBuffer
length: “the”的长度 3
capacity: 分配的总空间 19

注: 19这个数量,不同的JDK数量是不一样的

        String str1 = "the";
 
        StringBuffer sb = new StringBuffer(str1);
         
        System.out.println(sb.length()); //内容长度
         
        System.out.println(sb.capacity());//总空间

StringBuffer性能

String与StringBuffer的性能区别?

生成10位长度的随机字符串
然后,先使用String的+,连接10000个随机字符串,计算消耗的时间
然后,再使用StringBuffer连接10000个随机字符串,计算消耗的时间

时间System.currentTimeMillis

package character;
 
public class TestString {
 
    public static void main(String[] args) {
        int total = 10000;
        String s = randomString(10);
        StringBuffer sb = new StringBuffer();
         
        String str1 = "";
        long start = System.currentTimeMillis();
         
        for (int i = 0; i <total; i++) {
            str1+=s;
        }
        long end = System.currentTimeMillis();
        System.out.printf("使用字符串连接+的方式,连接%d次,耗时%d毫秒%n",total,end-start);
        
        total *=100;
        start = System.currentTimeMillis();
        for (int i = 0; i <total; i++) {
            sb.append(s);
        }
        end = System.currentTimeMillis();
        System.out.printf("使用StringBuffer的方式,连接%d次,耗时%d毫秒%n",total,end-start);
         
    }
 
    private static String randomString(int length) {
        String pool = "";
        for (short i = '0'; i <= '9'; i++) {
            pool += (char) i;
        }
        for (short i = 'a'; i <= 'z'; i++) {
            pool += (char) i;
        }
        for (short i = 'A'; i <= 'Z'; i++) {
            pool += (char) i;
        }
        char cs[] = new char[length];
        for (int i = 0; i < cs.length; i++) {
            int index = (int) (Math.random() * pool.length());
            cs[i] = pool.charAt(index);
        }
        String result = new String(cs);
        return result;
    }
 
}

边界条件判断
插入之前,首先要判断的是一些边界条件。 比如插入位置是否合法,插入的字符串是否为空

扩容
\1. 要判断是否需要扩容。 如果插入的字符串加上已经存在的内容的总长度超过了容量,那么就需要扩容。
\2. 数组的长度是固定的,不能改变的,数组本身不支持扩容。 我们使用变通的方式来解决这个问题。
\3. 根据需要插入的字符串的长度和已经存在的内容的长度,计算出一个新的容量。 然后根据这个容量,创建一个新的数组,接着把原来的数组的内容,复制到这个新的数组中来。并且让value这个引用,指向新的数组,从而达到扩容的效果。

插入字符串
\1. 找到要插入字符串的位置,从这个位置开始,把原数据看成两段,把后半段向后挪动一个距离,这个距离刚好是插入字符串的长度
\2. 然后把要插入的数据,插入这个挪出来的,刚刚好的位置里。

修改length的值
最后修改length的值,是原来的值加上插入字符串的长度

insert(int, char)
参数是字符的insert方法,通过调用insert(int, String) 也就实现了。

append
追加,就是在最后位置插入。 所以不需要单独开发方法,直接调用insert方法,就能达到最后位置插入的效果

日期

Date类
注意:是java.util.Date;
而非 java.sql.Date,此类是给数据库访问的时候使用的

时间原点概念

所有的数据类型,无论是整数,布尔,浮点数还是字符串,最后都需要以数字的形式表现出来。

日期类型也不例外,换句话说,一个日期,比如2020年10月1日,在计算机里,会用一个数字来代替。

那么最特殊的一个数字,就是零. 零这个数字,就代表Java中的时间原点,其对应的日期是1970年1月1日 8点0分0秒 。 (为什么是8点,因为中国的太平洋时区是UTC-8,刚好和格林威治时间差8个小时)

为什么对应1970年呢? 因为1969年发布了第一个 UNIX 版本:AT&T,综合考虑,当时就把1970年当做了时间原点。

所有的日期,都是以为这个0点为基准,每过一毫秒,就+1。

创建日期对象

        // 当前时间
        Date d1 = new Date();
        System.out.println("当前时间:");
        System.out.println(d1);
        System.out.println();
        // 从1970年1月1日 早上8点0分0秒 开始经历的毫秒数
        Date d2 = new Date(5000);
        System.out.println("从1970年1月1日 早上8点0分0秒 开始经历了5秒的时间");
        System.out.println(d2);

getTime

getTime() 得到一个long型的整数
这个整数代表 从1970.1.1 08:00:00:000 开始 每经历一毫秒,增加1
直接打印对象,会看到 “Tue Jan 05 09:51:48 CST 2016” 这样的格式,可读性比较差,为了获得“2016/1/5 09:51:48”这样的格式需要日期格式化

        //注意:是java.util.Date;
        //而非 java.sql.Date,此类是给数据库访问的时候使用的
        Date now= new Date();
        //打印当前时间
        System.out.println("当前时间:"+now.toString());
        //getTime() 得到一个long型的整数
        //这个整数代表 1970.1.1 08:00:00:000,每经历一毫秒,增加1
        System.out.println("当前时间getTime()返回的值是:"+now.getTime());
          
        Date zero = new Date(0);
        System.out.println("用0作为构造方法,得到的日期是:"+zero);

System.currentTimeMillis()

当前日期的毫秒数
new Date().getTime() 和 System.currentTimeMillis() 是一样的
不过由于机器性能的原因,可能会相差几十毫秒,毕竟每执行一行代码,都是需要时间的

练习

借助随机数,创建一个从1995.1.1 00:00:00 到 1995.12.31 23:59:59 之间的随机日期

package com.wang.oop.datad;

import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;

public class Date1995 {
    public static void main(String[] args) throws ParseException { //SimpleDateFormat 日期格式化类
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String str1 = "1995-01-01 00:00:00";
        String str2 = "1995-12-31 23:59:59";
        Date date1 = sf.parse(str1); //转换为日期形式
        System.out.println(sf.format(date1));
        Date date2 = sf.parse(str2);
        System.out.println(sf.format(date2));

        long d1 = date1.getTime();
        long d2 = date2.getTime();

        long date = (long)(Math.random()*(d2-d1)+d1);
        System.out.println(sf.format(date));
    }
}

SimpleDateFormat 日期格式化类

日期转字符串

y 代表年;M 代表月;d 代表日;H 代表24进制的小时;h 代表12进制的小时;m 代表分钟;s 代表秒;S 代表毫秒

        //y 代表年
        //M 代表月
        //d 代表日
        //H 代表24进制的小时
        //h 代表12进制的小时
        //m 代表分钟
        //s 代表秒
        //S 代表毫秒
        SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS" );
        Date d= new Date();
        String str = sdf.format(d);
        System.out.println("当前时间通过 yyyy-MM-dd HH:mm:ss SSS 格式化后的输出: "+str);
         
        SimpleDateFormat sdf1 =new SimpleDateFormat("yyyy-MM-dd" );
        Date d1= new Date();
        String str1 = sdf1.format(d1);
        System.out.println("当前时间通过 yyyy-MM-dd 格式化后的输出: "+str1);

练习

package com.wang.oop.datad;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

//准备一个长度是9的日期数组
//使用1970年-2000年之间的随机日期初始化该数组
//按照这些日期的时间进行升序排序
//比如 1988-1-21 12:33:22 就会排在 1978-4-21 19:07:23 前面,因为它的时间更小,虽然日期更大

public class DateFormat {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        Date start = sdf.parse("1970-1-1 0:0:0");
        Date end = sdf.parse("2000-12-31 23:59:59");

        Date[] dates = new Date[9];
        long rand;
        for (int i = 0; i <dates.length ; i++) {
            rand = (long)(Math.random()*(end.getTime()-start.getTime())+start.getTime());
            dates[i] = new Date(rand);
        }
        System.out.println("得到的随机日期数组:");
        for (int i = 0; i < dates.length; i++) {
            System.out.print(sdf.format(dates[i])+"\t\t");
            if (i%3==2)
                System.out.println();
        }

        long temp;
        for (int i = 0; i < dates.length; i++) {
            boolean flag = false;
            for (int j = 0; j <dates.length-i-1 ; j++) {
                if (dates[j].getTime()>dates[j+1].getTime()){
                    temp = dates[j].getTime();
                    dates[j] = new Date(dates[j+1].getTime());
                    dates[j+1] = new Date(temp);
                    flag = true;
                }
            }
            if (flag==false)
                break;
        }
        System.out.println("排序后的随机日期数组:");
        for (int i = 0; i < dates.length; i++) {
            System.out.print(sdf.format(dates[i])+"\t\t");
            if (i%3==2)
                System.out.println();
        }

    }
}

Calendar

Calendar类即日历类,常用于进行“翻日历”,比如下个月的今天是多久

采用单例模式获取日历对象Calendar.getInstance();

package date;
  
//
import java.util.Calendar;
import java.util.Date;
  
public class TestDate {
  
    public static void main(String[] args) {
        //采用单例模式获取日历对象Calendar.getInstance();
        Calendar c = Calendar.getInstance();
          
        //通过日历对象得到日期对象
        Date d = c.getTime();
  
        Date d2 = new Date(0);
        c.setTime(d2); //把这个日历,调成日期 : 1970.1.1 08:00:00
    }
}

add方法,在原日期上增加年/月/日
set方法,直接设置年/月/日

package com.wang.oop.datad;
import java.util.Calendar;
import java.util.Date;
import java.text.SimpleDateFormat;

public class TextCalendar {

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        Calendar c = Calendar.getInstance();
        Date now = c.getTime();
        // 当前日期
        System.out.println("当前日期:\t" + format(c.getTime()));

        // 下个月的今天
        c.setTime(now);
        c.add(Calendar.MONTH, 1);
        System.out.println("下个月的今天:\t" +format(c.getTime()));

        // 去年的今天
        c.setTime(now);
        c.add(Calendar.YEAR, -1);
        System.out.println("去年的今天:\t" +format(c.getTime()));

        // 上个月的第三天
        c.setTime(now);
        c.add(Calendar.MONTH, -1);
        c.set(Calendar.DATE, 3);
        System.out.println("上个月的第三天:\t" +format(c.getTime()));
        
        c.setTime(now);
        c.add(Calendar.MONTH,2);
        c.set(Calendar.DATE,-2);
        System.out.println("下个月的倒数第三天:\t" +format(c.getTime()));

    }

    private static String format(Date time) {
        return sdf.format(time);
    }
}

异常

异常:Exception

检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。在编译时不能被简单的忽略。

运行时异常:是可能被程序员避免的异常。可以在编译时被忽略。

错误:错误不是异常,是脱离程序员控制的问题。在代码中通常被忽略。

异常处理机制

抛出异常

捕获异常

异常处理五个关键字:try、catch、finally、throw、throws

package com.wang.exception;

public class Demo01 {
    public static void main(String[] args) {
        int a = 0;
        int b = 0;

        //Ctrl + Alt + T
        try{ //try监控区域
            System.out.println(a/b);
        }catch (Error e){ //catch(想要捕获的异常类型!) 捕获异常
            System.out.println("Error");
            e.printStackTrace(); //打印错误的栈信息
        }catch (Exception e){
            System.out.println("Exception");
        }catch (Throwable t){
            System.out.println("Throwable");
        }finally { //处理善后工作
            System.out.println("finally");
        }
    }

}

在开发中,如果去调用别人写的方法时,是否能知道别人写的方法是否会发生异常?这是很难判断的。针对这种情况,Java总允许在方法的后面使用throws关键字对外声明该方法有可能发生异常,这样调用者在调用方法时,就明确地知道该方法有异常,并且必须在程序中对异常进行处理,否则编译无法通过。

如下面代码

public static void main(String[] args) {
    // TODO Auto-generated method stub
   int result = divide(4,2);
   System.out.println(result);
}

public static int divide(int x,int y) throws Exception
{
    int result = x/y;
    return result;
}

这时候 编译器上会有错误提示 Unhandled exception type Exception
所以需要对调用divide()方法进行try…catch处理

public static void main(String[] args) {
    
  try {
     int    result = divide(4,2);
      System.out.println(result);
   } catch (Exception e) {
    
      e.printStackTrace();
   }
  
}

public static int divide(int x,int y) throws Exception
{
    int result = x/y;
    return result;
}
}
package com.wang.exception;

public class Demo02 {
    public static void main(String[] args) {

        new Demo02().test(0,0);

    }

    //假设这方法中,处理不了这个异常,方法上抛出异常throws
    public void test(int a,int b) throws ArithmeticException{
        if(b==0){
            throw new ArithmeticException(); //主动抛出的异常,一般在方法中使用
        }
    }
}
  1. 编译时异常

    在Java 中,Exception类中除了RuntimeException 类及其子类外都是编译时异常。编译时异常的特点是Java编译器会对其进行检查,如果出现异常就必须对异常进行处理,否则程序无法编译通过。

处理方法

使用try… catch 语句对异常进行捕获

使用throws 关键字声明抛出异常,调用者对其进行处理

2.运行时异常

RuntimeException 类及其子类运行异常。运行时异常的特点是Java编译器不会对其进行检查。也就是说,当程序中出现这类异常时,即使没有使用try… catch 语句捕获使用throws关键字声明抛出。程序也能编译通过。运行时异常一般是程序中的逻辑错误引起的,在程序运行时无法修复。例如 数据取值越界。

3.自定义异常

JDK中定义了大量的异常类,虽然这些异常类可以描述编程时出现的大部分异常情况,但是在程序开发中有时可能需要描述程序中特有的异常情况。例如divide()方法中不允许被除数为负数。为类解决这个问题,在Java中允许用户自定义异常,但自定义的异常类必须继承自Exception或其子类。例子如下

package www.kangxg.jdbc;

public class DivideDivideByMinusException  extends Exception {
/**
 * 
 */
private static final long serialVersionUID = 1L;
 
public DivideDivideByMinusException(){
    super();
}

public DivideDivideByMinusException(String message)
{
    super(message);
}
}
package www.kangxg.jdbc;

public class Example {
public static void main(String[] args) throws Exception {
    
    try {
         int    result = divide(4,-2);
          System.out.println(result);
     } catch (DivideDivideByMinusException e) {
        
         System.out.println(e.getMessage());
     }
  
}

public static int divide(int x,int y) throws DivideDivideByMinusException
{
    if(y<0)
    {
        throw new DivideDivideByMinusException("被除数是负数");
    }
    int result = x/y;
    return result;
}
    }

new FileInputStream(f)异常

Java中通过 new FileInputStream(f) 试图打开某文件,就有可能抛出文件不存在异常FileNotFoundException
如果不处理该异常,就会有编译错误

        File f= new File("d:/LOL.exe");
          
        //试图打开文件LOL.exe,会抛出FileNotFoundException,如果不处理该异常,就会有编译错误
        new FileInputStream(f);

异常处理常见手段: try catch finally throws

1.将可能抛出FileNotFoundException 文件不存在异常的代码放在try里

2.如果文件存在,就会顺序往下执行,并且不执行catch块中的代码

  1. 如果文件不存在,try 里的代码会立即终止,程序流程会运行到对应的catch块中
  2. e.printStackTrace(); 会打印出方法的调用痕迹,如此例,会打印出异常开始于TestException的第16行,这样就便于定位和分析到底哪里出了异常

FileNotFoundException是Exception的子类,使用Exception也可以catch住FileNotFoundException

多异常捕捉办法

有的时候一段代码会抛出多种异常,比如

new FileInputStream(f);

Date d = sdf.parse("2016-06-03");

这段代码,会抛出 文件不存在异常 FileNotFoundException 和 解析异常ParseException
解决办法之一是分别进行catch

catch (FileNotFoundException e) {
   System.out.println("d:/LOL.exe不存在");
   e.printStackTrace();
} catch (ParseException e) {
   System.out.println("日期格式解析错误");
   e.printStackTrace();
}

另一个种办法是把多个异常,放在一个catch里统一捕捉

catch (FileNotFoundException | ParseException e) {

这种方式从 JDK7开始支持,好处是捕捉的代码更紧凑,不足之处是,一旦发生异常,不能确定到底是哪种异常,需要通过instanceof 进行判断具体的异常类型

    public static void main(String[] args) {
 
        File f = new File("d:/LOL.exe");
 
        try {
            System.out.println("试图打开 d:/LOL.exe");
            new FileInputStream(f);
            System.out.println("成功打开");
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date d = sdf.parse("2016-06-03");
        } catch (FileNotFoundException | ParseException e) {
            if (e instanceof FileNotFoundException)
                System.out.println("d:/LOL.exe不存在");
            if (e instanceof ParseException)
                System.out.println("日期格式解析错误");
 
            e.printStackTrace();
        }

finally

无论是否出现异常,finally中的代码都会被执行

        try{
            System.out.println("试图打开 d:/LOL.exe");
            new FileInputStream(f);
            System.out.println("成功打开");
        }
        catch(FileNotFoundException e){
            System.out.println("d:/LOL.exe不存在");
            e.printStackTrace();
        }
        finally{
            System.out.println("无论文件是否存在, 都会执行的代码");
        }

throws

考虑如下情况:
主方法调用method1
method1调用method2
method2中打开文件

method2中需要进行异常处理
但是method2不打算处理,而是把这个异常通过throws****抛出去
那么method1就会接到该异常。 处理办法也是两种,要么是try catch处理掉,要么也是抛出去
method1选择本地try catch住 一旦try catch住了,就相当于把这个异常消化掉了,主方法在调用method1的时候,就不需要进行异常处理了

public class TestException {
 
    public static void main(String[] args) {
        method1();
 
    }
 
    private static void method1() {
        try {
            method2();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
 
    private static void method2() throws FileNotFoundException {
 
        File f = new File("d:/LOL.exe");
 
        System.out.println("试图打开 d:/LOL.exe");
        new FileInputStream(f);
        System.out.println("成功打开");
 
    }
}

throw 和 throws

throws与throw这两个关键字接近,不过意义不一样,有如下区别:
\1. throws 出现在方法声明上,而throw通常都出现在方法体内。
\2. throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某个异常对象。

异常分类

可查异常: CheckedException
可查异常即必须进行处理的异常,要么try catch住,要么往外抛,谁调用,谁处理,比如 FileNotFoundException
如果不处理,编译器,就不让你通过

运行时异常RuntimeException指: 不是必须进行try catch的异常
常见运行时异常:
除数不能为0异常:ArithmeticException
下标越界异常:ArrayIndexOutOfBoundsException
空指针异常:NullPointerException
在编写代码的时候,依然可以使用try catch throws进行处理,与可查异常不同之处在于,即便不进行try catch,也不会有编译错误
Java之所以会设计运行时异常的原因之一,是因为下标越界,空指针这些运行时异常太过于普遍,如果都需要进行捕捉,代码的可读性就会变得很糟糕。

错误Error,指的是系统级别的异常,通常是内存用光了
默认设置下,一般java程序启动的时候,最大可以使用16m的内存
如例不停的给StringBuffer追加字符,很快就把内存使用光了。抛出OutOfMemoryError
与运行时异常一样,错误也是不要求强制捕捉的

运行时异常: 都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常 (编译异常): 是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

Throwable

Throwable是类,Exception和Error都继承了该类
所以在捕捉的时候,也可以使用Throwable进行捕捉
如图: 异常分ErrorException
Exception里又分运行时异常可查异常

自定义异常

一个英雄攻击另一个英雄的时候,如果发现另一个英雄已经挂了,就会抛出EnemyHeroIsDeadException
创建一个类EnemyHeroIsDeadException,并继承Exception
提供两个构造方法

  1. 无参的构造方法
  2. 带参的构造方法,并调用父类的对应的构造方法
class EnemyHeroIsDeadException extends Exception{
     
    public EnemyHeroIsDeadException(){
         
    }
    public EnemyHeroIsDeadException(String msg){
        super(msg);
    }
}

在Hero的attack方法中,当发现敌方英雄的血量为0的时候,抛出该异常
\1. 创建一个EnemyHeroIsDeadException实例
\2. 通过throw 抛出该异常
\3. 当前方法通过 throws 抛出该异常

在外部调用attack方法的时候,就需要进行捕捉,并且捕捉的时候,可以通过e.getMessage() 获取当时出错的具体原因

package charactor;
  
public class Hero {
    public String name;
    protected float hp;
 
    public void attackHero(Hero h) throws EnemyHeroIsDeadException{
        if(h.hp == 0){
            throw new EnemyHeroIsDeadException(h.name + " 已经挂了,不需要施放技能" );
        }
    }
 
    public String toString(){
        return name;
    }
     
    class EnemyHeroIsDeadException extends Exception{
         
        public EnemyHeroIsDeadException(){
             
        }
        public EnemyHeroIsDeadException(String msg){
            super(msg);
        }
    }
      
    public static void main(String[] args) {
         
        Hero garen =  new Hero();
        garen.name = "盖伦";
        garen.hp = 616;
 
        Hero teemo =  new Hero();
        teemo.name = "提莫";
        teemo.hp = 0;
         
        try {
            garen.attackHero(teemo);
             
        } catch (EnemyHeroIsDeadException e) {
            // TODO Auto-generated catch block
            System.out.println("异常的具体原因:"+e.getMessage());
            e.printStackTrace();
        }
         
    }
}

每一个throws的异常都需要catch住

接口IStringBuffer中声明的方法需要抛出异常

    public static class IndexIsNegativeException extends Exception{
        public IndexIsNegativeException(){
        }
        public IndexIsNegativeException(String msg){
            super(msg);
        }
    }

    public static class IndexOutOfRangeException extends Exception{
        public IndexOutOfRangeException(){
        }
        public IndexOutOfRangeException(String msg){
            super(msg);
        }
    }

    public static class NullPointerException extends Exception{
        public NullPointerException(){
        }
        public NullPointerException(String msg){
            super(msg);
        }
    }
//**每一个throws的异常都需要catch住**
// 接口IStringBuffer中声明的方法需要抛出异常
    public void delete(int start, int end) throws IndexIsNegativeException,IndexOutOfRangeException,NullPointerException {
        //边界条件判断
        if (start<0)
            throw new IndexIsNegativeException("start下标为负异常");
        if (start>length)
            throw new IndexOutOfRangeException("start下标超出范围异常");
        if (end<0)
            throw new IndexIsNegativeException("end下标为负异常");
        if (end>length)
            throw new IndexOutOfRangeException("end下标超出范围异常");
        if (start>=end)
            throw new IndexIsNegativeException("下标异常");

        System.arraycopy(value,end,value,start,length-end);
        length -= end-start;

    }

        try{
            sb.insert(1000, "let ");
            System.out.println(sb);
            
        }catch (IndexOutOfRangeException i){
            System.out.println(i.getMessage());
            i.printStackTrace();
        } catch (NullPointerException n) {
            System.out.println(n.getMessage());
            n.printStackTrace();
        } catch (IndexIsNegativeException i) {
            System.out.println(i.getMessage());
            i.printStackTrace();
        }

    }

练习

这是一个类图
Account类: 银行账号
属性: balance 余额
方法: getBalance() 获取余额
方法: deposit() 存钱
方法: withdraw() 取钱
OverdraftException: 透支异常,继承Exception
属性: deficit 透支额

package exception;
 
public class OverDraftException extends Exception{
 
    private double deficit;
 
    public double getDeficit() {
        return deficit;
    }
 
    public OverDraftException(String msg, double deficit) {
        super(msg);
        this.deficit = deficit;
    }
     
}

package exception;
   
public class Account {
   
    protected double balance;
   
    public Account(double balance) {
        this.balance = balance;
    }
   
    public double getBalance() {
        return balance;
    }
       
    public void deposit(double amt){
        this.balance+=amt;
    }
    public void withdraw(double amt) throws OverDraftException{
        if(this.balance<amt)
            throw new OverDraftException("余额不足", amt-this.balance);
           
        this.balance-=amt;
    }
       
    public static void main(String[] args) {
        //开户存了1000
        Account a = new Account(1000);
        //存钱1000
        a.deposit(1000);
        //查看余额
        System.out.println(a.getBalance());
           
        try {
            //取2001
            a.withdraw(2001);
        } catch (OverDraftException e) {
            System.err.println("透支金额:"+e.getDeficit());
            e.printStackTrace();
        }
           
    }
       
}

类: CheckingAccount 支票账户,具备透支额度,继承Account
属性:overdraftProtection 透支额度

package exception;
 
public class CheckingAccount extends Account {
 
    private double overdraftProtection;
 
    public CheckingAccount(double balance) {
        super(balance);
    }
 
    public CheckingAccount(double balance, double overdraftProtection) {
        super(balance);
 
        this.overdraftProtection = overdraftProtection;
    }
 
    public void withdraw(double amt) throws OverDraftException {
        if (amt > this.balance + overdraftProtection) {
            double deficit = amt - (this.balance + overdraftProtection);
            throw new OverDraftException("透支额度超标", deficit);
        }
        this.balance -= amt;
    }
 
    public static void main(String[] args) {
        //开户存了1000块,拥有500的透支额度
        CheckingAccount a = new CheckingAccount(1000, 500);
        //存了1000
        a.deposit(1000);
        //查询余额
        System.out.println(a.getBalance());
        try {
            a.withdraw(600);
            System.out.println(a.getBalance());
            a.withdraw(600);
            System.out.println(a.getBalance());
            a.withdraw(600);
            System.out.println(a.getBalance());
            a.withdraw(600);
            System.out.println(a.getBalance());
            a.withdraw(600);
            System.out.println(a.getBalance());
        } catch (OverDraftException e) {
            System.err.println("透支超额:"+e.getDeficit());
            e.printStackTrace();
        }
 
    }
 
}

I/O

f1.getAbsolutePath(); // 绝对路径

f.exist(); //文件是否存在

f.isDirectory(); //是否是文件夹

f.isFile(); //是否是文件(非文件夹)

f.length(); //文件长度

f.lastModified(); //文件最后修改时间

f.setLastModified(0); //设置文件修改时间为1970.1.1 08:00:00

f.renameTo(f2); //文件重命名

f.list(); // 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)

File[]fs= f.listFiles(); // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)

f.getParent(); // 以字符串形式返回获取所在文件夹

f.getParentFile(); // 以文件形式返回获取所在文件夹

f.mkdir(); // 创建文件夹,如果父文件夹skin不存在,创建就无效

f.mkdirs(); // 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹

f.createNewFile(); // 创建一个空文件,如果父文件夹skin不存在,就会抛出异常

f.getParentFile().mkdirs(); // 所以创建一个空文件之前,通常都会创建父目录

f.listRoots(); // 列出所有的盘符c: d: e: 等等

f.delete(); // 刪除文件

f.deleteOnExit(); // JVM结束的时候,刪除文件,常用于临时文件的删除

        import java.io.File;
        // 绝对路径
        File f1 = new File("d:/LOLFolder");
        System.out.println("f1的绝对路径:" + f1.getAbsolutePath());

        // 相对路径,相对于工作目录,如果在eclipse中,就是项目目录
        File f2 = new File("LOL.exe");
        System.out.println("f2的绝对路径:" + f2.getAbsolutePath());

        // 把f1作为父目录创建文件对象
        File f3 = new File(f1, "LOL.exe");
        System.out.println("f3的绝对路径:" + f3.getAbsolutePath());

        File f = new File("d:/LOLFolder/LOL.exe");
        System.out.println("当前文件是:" +f);
        //文件是否存在
        System.out.println("判断是否存在:"+f.exists());
         
        //是否是文件夹
        System.out.println("判断是否是文件夹:"+f.isDirectory());
          
        //是否是文件(非文件夹)
        System.out.println("判断是否是文件:"+f.isFile());
          
        //文件长度
        System.out.println("获取文件的长度:"+f.length());
          
        //文件最后修改时间
        long time = f.lastModified();
        Date d = new Date(time);
        System.out.println("获取文件的最后修改时间:"+d);
        //设置文件修改时间为1970.1.1 08:00:00
        f.setLastModified(0);
          
        //文件重命名
        File f2 =new File("d:/LOLFolder/DOTA.exe");
        f.renameTo(f2);
        System.out.println("把LOL.exe改名成了DOTA.exe");
         
        System.out.println("注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息");

遍历文件夹及子文件夹

package com.wang.IO;

import java.io.File;

public class Demo01 {
    static long minSize=Long.MAX_VALUE;
    static long maxSize=0;
    static File minFile = null;
    static File maxFile = null;

    public static void main(String[] args) {
        File f1 =new File("D:/电影");
        traverse(f1);

        System.out.printf("最大的文件是%s,其大小是%,d字节%n",maxFile.getAbsoluteFile(),maxFile.length());
        System.out.printf("最小的文件是%s,其大小是%,d字节%n",minFile.getAbsoluteFile(),minFile.length());
    }

    public static void traverse(File f1){
        if (f1.isFile()){
            if (f1.length()>maxSize){
                maxSize = f1.length();
                maxFile = f1;
            }
            if(f1.length()!=0 && f1.length()<minSize){
                minSize = f1.length();
                minFile = f1;
            }
        }

        if(f1.isDirectory()){
            File[] fs = f1.listFiles();
            if(null!=fs)
                for (File f : fs) {
                    traverse(f); //递归
                }
        }
    }
}

当不同的介质之间有数据交互的时候,JAVA就使用流来实现。
数据源可以是文件,还可以是数据库,网络甚至是其他的程序

比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流
InputStream字节输入流
OutputStream字节输出流
用于以字节的形式读取和写入数据

所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。 如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。

            File f = new File("d:/lol.txt");
            // 创建基于文件的输入流
            FileInputStream fis = new FileInputStream(f);
            // 通过这个输入流,就可以把数据从硬盘,读取到Java的虚拟机中来,也就是读取到内存中

InputStream是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
FileInputStream 是InputStream子类,以FileInputStream 为例进行文件读取

        try {
            //准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66
            File f =new File("d:/lol.txt");
            //创建基于文件的输入流
            FileInputStream fis =new FileInputStream(f);
            //创建字节数组,其长度就是文件的长度
            byte[] all =new byte[(int) f.length()];
            //以字节流的形式读取文件所有内容
            fis.read(all);
            for (byte b : all) {
                //打印出来是65 66
                System.out.println(b);
            }
             
            //每次使用完流,都应该进行关闭
            fis.close();
              
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

OutputStream是字节输出流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
FileOutputStream 是OutputStream子类,以FileOutputStream 为例向文件写出数据

注: 如果文件d:/lol2.txt不存在,写出操作会自动创建该文件。
但是如果是文件 d:/xyz/lol2.txt,而目录xyz又不存在,会抛出异常

    public static void main(String[] args) {
        try {
            // 准备文件lol2.txt其中的内容是空的
            File f = new File("d:/lol2.txt");
            // 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Y
            byte data[] = { 88, 89 };
 
            // 创建基于文件的输出流
            FileOutputStream fos = new FileOutputStream(f);
            // 把数据写入到输出流
            fos.write(data);
            // 关闭输出流
            fos.close();
             
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    public static void main(String[] args) {
        try {
            File f = new File("d:/xyz/abc/def/lol2.txt");
             
            //因为默认情况下,文件系统中不存在 d:\xyz\abc\def,所以输出会失败
             
            //首先获取文件所在的目录
            File dir = f.getParentFile();
            //如果该目录不存在,则创建该目录
            if(!dir.exists()){
//              dir.mkdir(); //使用mkdir会抛出异常,因为该目录的父目录也不存在
                dir.mkdirs(); //使用mkdirs则会把不存在的目录都创建好
            }
 
            byte data[] = { 88, 89 };
 
            FileOutputStream fos = new FileOutputStream(f);
 
            fos.write(data);
 
            fos.close();
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }

拆分文件与合并文件

public class TestStream {
    public static void main(String[] args) {
//        int eachSize = 100*1024;
//        File srcFile = new File("D://JavaSE.md");
//        splitFile(srcFile,eachSize);
        murgeFile("d://新建文件夹","JavaSE.md");

    }

    private static void splitFile(File srcFile, int eachSize) {
  
        if (0 == srcFile.length())
            throw new RuntimeException("文件长度为0,不可拆分");
  
        byte[] fileContent = new byte[(int) srcFile.length()];
        // 先把文件读取到数组中
        try {
            FileInputStream fis = new FileInputStream(srcFile);
            fis.read(fileContent);
            fis.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 计算需要被划分成多少份子文件
        int fileNumber;
        // 文件是否能被整除得到的子文件个数是不一样的
        // (假设文件长度是25,每份的大小是5,那么就应该是5个)
        // (假设文件长度是26,每份的大小是5,那么就应该是6个)
        if (0 == fileContent.length % eachSize)
            fileNumber = (int) (fileContent.length / eachSize);
        else
            fileNumber = (int) (fileContent.length / eachSize) + 1;
  
        for (int i = 0; i < fileNumber; i++) {
            String eachFileName = srcFile.getName() + "-" + i;
            File eachFile = new File(srcFile.getParent(), eachFileName);
            byte[] eachContent;
  
            // 从源文件的内容里,复制部分数据到子文件
            // 除开最后一个文件,其他文件大小都是100k
            // 最后一个文件的大小是剩余的
            if (i != fileNumber - 1) // 不是最后一个
                eachContent = Arrays.copyOfRange(fileContent, eachSize * i, eachSize * (i + 1));
            else // 最后一个
                eachContent = Arrays.copyOfRange(fileContent, eachSize * i, fileContent.length);
  
            try {
                // 写出去
                FileOutputStream fos = new FileOutputStream(eachFile);
                fos.write(eachContent);
                // 记得关闭
                fos.close();
                System.out.printf("输出子文件%s,其大小是 %d字节%n", eachFile.getAbsoluteFile(), eachFile.length());
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    /**
     * 合并的思路,就是从eclipse.exe-0开始,读取到一个文件,就开始写出到 eclipse.exe中,直到没有文件可以读
     * @param folder
     *            需要合并的文件所处于的目录
     * @param fileName
     *            需要合并的文件的名称
     * @throws FileNotFoundException
     */
    private static void murgeFile(String folder, String fileName) {
 
        try {
            // 合并的目标文件
            File destFile = new File(folder, fileName);
            FileOutputStream fos = new FileOutputStream(destFile);
            int index = 0;
            while (true) {
                //子文件
                File eachFile = new File(folder, fileName + "-" + index++);
                //如果子文件不存在了就结束
                if (!eachFile.exists())
                    break;
 
                //读取子文件的内容
                FileInputStream fis = new FileInputStream(eachFile);
                byte[] eachContent = new byte[(int) eachFile.length()];
                fis.read(eachContent);
                fis.close();
                 
                //把子文件的内容写出去
                fos.write(eachContent);
                fos.flush();
                System.out.printf("把子文件 %s写出到目标文件中%n",eachFile);
            }
 
            fos.close();
            System.out.printf("最后目标文件的大小:%,d字节" , destFile.length());
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         
    }
}

关闭流

所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。 如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。

在try的作用域里关闭文件输入流,在前面的示例中都是使用这种方式,这样做有一个弊端;
如果文件不存在,或者读取的时候出现问题而抛出异常,那么就不会执行这一行关闭流的代码,存在巨大的资源占用隐患。 不推荐使用

在finally中关闭

这是标准的关闭流的方式
\1. 首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally.
\2. 在finally关闭之前,要先判断该引用是否为空
\3. 关闭的时候,需要再一次进行try catch处理

这是标准的严谨的关闭流的方式,但是看上去很繁琐,所以写不重要的或者测试代码的时候,都会采用上面的有隐患try的方式,因为不麻烦~

package stream;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
 
public class TestStream {
 
    public static void main(String[] args) {
        File f = new File("d:/lol.txt");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(f);
            byte[] all = new byte[(int) f.length()];
            fis.read(all);
            for (byte b : all) {
                System.out.println(b);
            }
 
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 在finally 里关闭流
            if (null != fis)
                try {
 
                    fis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }
 
    }
}

使用try()的方式

把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
这种编写代码的方式叫做 try-with-resources, 这是从JDK7开始支持的技术

所有的流,都实现了一个接口叫做 AutoCloseable,任何类实现了这个接口,都可以在try()中进行实例化。 并且在try, catch, finally结束的时候自动关闭,回收相关资源。

    public static void main(String[] args) {
        File f = new File("d:/lol.txt");
  
        //把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
        try (FileInputStream fis = new FileInputStream(f)) {
            byte[] all = new byte[(int) f.length()];
            fis.read(all);
            for (byte b : all) {
                System.out.println(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
  
    }

字符流

Reader字符输入流
Writer字符输出流
专门用于字符的形式读取和写入数据

FileReader 是Reader子类,以FileReader 为例进行文件读取

    public static void main(String[] args) {
        // 准备文件lol.txt其中的内容是AB
        File f = new File("d:/lol.txt");
        // 创建基于文件的Reader
        try (FileReader fr = new FileReader(f)) {
            // 创建字符数组,其长度就是文件的长度
            char[] all = new char[(int) f.length()];
            // 以字符流的形式读取文件所有内容
            fr.read(all);
            for (char b : all) {
                // 打印出来是A B
                System.out.println(b);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }

FileWriter 是Writer的子类,以FileWriter 为例把字符串写入到文件

    public static void main(String[] args) {
        // 准备文件lol2.txt
        File f = new File("d:/lol2.txt");
        // 创建基于文件的Writer
        try (FileWriter fr = new FileWriter(f)) {
            // 以字符流的形式把数据写入到文件中
            String data="abcdefg1234567890";
            char[] cs = data.toCharArray();
            fr.write(cs);
  
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
  
    }

文件加密与解密

public static void main(String[] args) {
    File f1 = new File("d://test.txt");
    File f2 = new File("d://test2.txt");
    File f3 = new File("d://test3.txt");
    encodeFile(f1,f2);
    decodeFile(f2,f3);
}

public static void encodeFile(File encodingFile,File encodedFile){
    try(FileReader fr= new FileReader(encodingFile)){
        char[] all = new char[(int)encodingFile.length()];
        fr.read(all);
        for (int i = 0; i < all.length; i++) {
            if(all[i]=='9')
                all[i]='0';
            else if (all[i]<'9' && all[i]>='0')
                all[i]+=1;
            else if (all[i]=='z' | all[i]=='Z')
                all[i]-=25;
            else if ((all[i]<'z' && all[i]>='a')||(all[i]<'Z' && all[i]>='A'))
                all[i]+=1;
        }
        try(FileWriter fw = new FileWriter(encodedFile)){
            fw.write(all);
        }
    }catch(IOException e){
        e.printStackTrace();
    }
}

public static void decodeFile(File decodingFile,File decodedFile){
    try(FileReader fr= new FileReader(decodingFile)){
        char[] all = new char[(int)decodingFile.length()];
        fr.read(all);
        for (int i = 0; i < all.length; i++) {
            if(all[i]=='0')
                all[i]='9';
            else if (all[i]<'9' && all[i]>='0')
                all[i]-=1;
            else if (all[i]=='a' | all[i]=='A')
                all[i]+=25;
            else if ((all[i]<'z' && all[i]>='a')||(all[i]<'Z' && all[i]>='A'))
                all[i]-=1;
        }
        try(FileWriter fw = new FileWriter(decodedFile)){
            fw.write(all);
        }
    }catch(IOException e){
        e.printStackTrace();
    }
}

编码方式

工作后经常接触的编码方式有如下几种:
ISO-8859-1 ASCII 数字和西欧字母
GBK GB2312 BIG5 中文
UNICODE (统一码,万国码)

其中
ISO-8859-1 包含 ASCII
GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文。
UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中

两个16进制一个字节,8个对应4个字节

比如在ISO-8859-1中,a 字符对应的数字是0x61
而UNICODE中对应的数字是 0x00000061,倘若一篇文章大部分都是英文字母,那么按照UNICODE的方式进行数据保存就会消耗很多空间

在这种情况下,就出现了UNICODE的各种减肥子编码, 比如UTF-8对数字和字母就使用一个字节,而对汉字就使用3个字节,从而达到了减肥还能保证健康的效果

UTF-8,UTF-16和UTF-32 针对不同类型的数据有不同的减肥效果,一般说来UTF-8是比较常用的方式

package stream;
 
import java.io.UnsupportedEncodingException;
 
public class TestStream {
 
    public static void main(String[] args) {
        String str = "中";
        showCode(str);
    }
 
    private static void showCode(String str) {
        String[] encodes = { "BIG5", "GBK", "GB2312", "UTF-8", "UTF-16", "UTF-32" };
        for (String encode : encodes) {
            showCode(str, encode);
        }
 
    }
 
    private static void showCode(String str, String encode) {
        try {
            System.out.printf("字符: \"%s\" 的在编码方式%s下的十六进制值是%n", str, encode);
            byte[] bs = str.getBytes(encode);
 
            for (byte b : bs) {
                int i = b&0xff;
                System.out.print(Integer.toHexString(i) + "\t");
            }
            System.out.println();
            System.out.println();
        } catch (UnsupportedEncodingException e) {
            System.out.printf("UnsupportedEncodingException: %s编码方式无法解析字符%s\n", encode, str);
        }
    }
}

文件的编码方式-记事本

字符保存在文件中肯定也是以数字形式保存的,即对应在不同的棋盘上的不同的数字
记事本打开任意文本文件,并且另存为,就能够在编码这里看到一个下拉。
ANSI 这个不是ASCII的意思,而是采用本地编码**的意思。如果你是中文的操作系统,就会使GBK,如果是英文的就会是ISO-8859-1

Unicode UNICODE原生的编码方式
Unicode big endian 另一个 UNICODE编码方式
UTF-8 最常见的UTF-8编码方式,数字和字母用一个字节, 汉字用3个字节。

  byte[] bs = str.getBytes("UTF-8");
  String str = new String(bs,"GBK");

      int i = b&0xff;
      System.out.print(Integer.toHexString(i) + "\t");

FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符
而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替,像这样:

new InputStreamReader(new FileInputStream(f),Charset.forName(“UTF-8”));

        File f = new File("E:\\project\\j2se\\src\\test.txt");
        System.out.println("默认编码方式:"+Charset.defaultCharset());
        //FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
        //而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
        try (FileReader fr = new FileReader(f)) {
            char[] cs = new char[(int) f.length()];
            fr.read(cs);
            System.out.printf("FileReader会使用默认的编码方式%s,识别出来的字符是:%n",Charset.defaultCharset());
            System.out.println(new String(cs));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替
        //并且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式
        try (InputStreamReader isr = new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"))) {
            char[] cs = new char[(int) f.length()];
            isr.read(cs);
            System.out.printf("InputStreamReader 指定编码方式UTF-8,识别出来的字符是:%n");
            System.out.println(new String(cs));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

缓存流

以介质是硬盘为例,字节流和字符流的弊端
在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。

为了解决以上弊端,采用缓存流。
缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。

缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了IO操作

缓存字符输入流 BufferedReader 可以一次读取一行数据

创建文件字符流
缓存流必须建立在一个存在的流的基础上

    public static void main(String[] args) {
        // 准备文件lol.txt其中的内容是
        // garen kill teemo
        // teemo revive after 1 minutes
        // teemo try to garen, but killed again
        File f = new File("d:/lol.txt");
        // 创建文件字符流
        // 缓存流必须建立在一个存在的流的基础上
        try (
                FileReader fr = new FileReader(f);
                BufferedReader br = new BufferedReader(fr);
            )
        {
            while (true) {
                // 一次读一行
                String line = br.readLine();
                if (null == line)
                    break;
                System.out.println(line);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

PrintWriter 缓存字符输出流, 可以一次写出一行数据

缓存流必须建立在一个存在的流的基础上

    public static void main(String[] args) {
        // 向文件lol2.txt中写入三行语句
        File f = new File("d:/lol2.txt");
          
        try (
                // 创建文件字符流
                FileWriter fw = new FileWriter(f);
                // 缓存流必须建立在一个存在的流的基础上              
                PrintWriter pw = new PrintWriter(fw);              
        ) {
            pw.println("garen kill teemo");
            pw.println("teemo revive after 1 minutes");
            pw.println("teemo try to garen, but killed again");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
   
    }

有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush

        try(FileWriter fr = new FileWriter(f);PrintWriter pw = new PrintWriter(fr);) {
            pw.println("garen kill teemo");
            //强制把缓存中的数据写入硬盘,无论缓存是否已满
                pw.flush();           
            pw.println("teemo revive after 1 minutes");
                pw.flush();
            pw.println("teemo try to garen, but killed again");
                pw.flush();
        }...

移除文件中的注释

    public static void removeComments(File javaFile){
        StringBuilder sb = new StringBuilder();//定义字符串容器
        //读取数据
        try(
            FileReader fr = new FileReader(javaFile);
            BufferedReader br = new BufferedReader(fr);
        ){
            while(br.ready()){
                String str = br.readLine();     //读取数据
                if(str.trim().startsWith("//")){    //过滤单行注释
                    continue;
                }
                sb.append(str + "\n");  //添加到字符串容器
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
         
        //写入数据
        try(
            FileWriter fw = new FileWriter(javaFile);
            PrintWriter pw = new PrintWriter(fw);
        ){
            pw.write(sb.toString());    //写入读取数据
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

数据流

DataInputStream 数据输入流
DataOutputStream 数据输出流

使用数据流的writeUTF()和readUTF() 可以进行数据的格式化顺序读写
如本例,通过DataOutputStream 向文件顺序写出 布尔值,整数和字符串。 然后再通过DataInputStream 顺序读入这些数据。

注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException,因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。

    public static void main(String[] args) {
        write();
        read();
    }
 
    private static void read() {
        File f =new File("d:/lol.txt");
        try (
                FileInputStream fis  = new FileInputStream(f);
                DataInputStream dis =new DataInputStream(fis);
        ){
            boolean b= dis.readBoolean();
            int i = dis.readInt();
            String str = dis.readUTF();
             
            System.out.println("读取到布尔值:"+b);
            System.out.println("读取到整数:"+i);
            System.out.println("读取到字符串:"+str);
 
        } catch (IOException e) {
            e.printStackTrace();
        }
         
    }
 
    private static void write() {
        File f =new File("d:/lol.txt");
        try (
                FileOutputStream fos  = new FileOutputStream(f);
                DataOutputStream dos =new DataOutputStream(fos);
        ){
            dos.writeBoolean(true);
            dos.writeInt(300);
            dos.writeUTF("123 this is gareen");
        } catch (IOException e) {
            e.printStackTrace();
        }
         
    }

对象流

对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘

对象的所有属性都打包发送

一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口

**注:**把一个对象序列化有一个前提是:这个对象的类,必须实现了Serializable接口

package charactor;
 
import java.io.Serializable;
 
public class Hero implements Serializable {
    //表示这个类当前的版本,如果有了变化,比如新设计了属性,就应该修改这个版本号
    private static final long serialVersionUID = 1L;
    public String name;
    public float hp;
 
}


package stream;    
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
  
import charactor.Hero;
    
public class TestStream {
    
    public static void main(String[] args) {
        //创建一个Hero garen
        //要把Hero对象直接保存在文件上,务必让Hero类实现Serializable接口
        Hero h = new Hero();
        h.name = "garen";
        h.hp = 616;
          
        //准备一个文件用于保存该对象
        File f =new File("d:/garen.lol");
 
        try(
            //创建对象输出流
            FileOutputStream fos = new FileOutputStream(f);
            ObjectOutputStream oos =new ObjectOutputStream(fos);
            //创建对象输入流              
            FileInputStream fis = new FileInputStream(f);
            ObjectInputStream ois =new ObjectInputStream(fis);
        ) {
            oos.writeObject(h);
            Hero h2 = (Hero) ois.readObject();
            System.out.println(h2.name);
            System.out.println(h2.hp);
               
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
            
    }
}
package com.wang.IO;
import com.wang.IO.Hero;

import java.io.*;
/*
准备一个长度是10,类型是Hero的数组,使用10个Hero对象初始化该数组

然后把该数组序列化到一个文件heros.lol

接着使用ObjectInputStream 读取该文件,并转换为Hero数组,验证该数组中的内容,是否和序列化之前一样
 */
public class ObjectStream {
    public static void main(String[] args) {
        Hero[] hs = new Hero[10];
        for (int i = 0; i < hs.length; i++) {
            hs[i] = new Hero(new String("hero"+i),600);
        }
        File f = new File("D:/test.txt");
        try(
                FileOutputStream fos = new FileOutputStream(f);
                ObjectOutputStream oos = new ObjectOutputStream(fos);

                FileInputStream fis = new FileInputStream(f);
                ObjectInputStream ois = new ObjectInputStream(fis);
                ){
            oos.writeObject(hs);
            Hero[] h2 = (Hero[])ois.readObject();
            for (Hero hero : h2) {
                System.out.println(hero.name);
                System.out.println(hero.hp);
                System.out.println(hero.money);
            }

        }catch (IOException e){
            e.printStackTrace();
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

System.in

System.out 是常用的在控制台输出数据的
System.in 可以从控制台输入数据

package stream;
 
import java.io.IOException;
import java.io.InputStream;
 
public class TestStream {
 
    public static void main(String[] args) {
        // 控制台输入
        try (InputStream is = System.in;) {
            while (true) {
                // 敲入a,然后敲回车可以看到
                // 97 13 10
                // 97是a的ASCII码
                // 13 10分别对应回车换行
                int i = is.read();
                System.out.println(i);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

自动创建有一个属性的类文件。
通过控制台,获取类名,属性名称,属性类型,根据一个模板文件,自动创建这个类文件,并且为属性提供setter和getter

package stream;
 
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
 
public class TestStream {
 
    public static void main(String[] args) {
        // 接受客户输入
        Scanner s = new Scanner(System.in);
        System.out.println("请输入类的名称:");
        String className = s.nextLine();
        System.out.println("请输入属性的类型:");
        String type = s.nextLine();
        System.out.println("请输入属性的名称:");
        String property = s.nextLine();
        String Uproperty = toUpperFirstLetter(property);
         
        // 读取模版文件
        File modelFile = new File("E:\\project\\j2se\\src\\Model.txt");
        String modelContent = null;
        try (FileReader fr = new FileReader(modelFile)) {
            char cs[] = new char[(int) modelFile.length()];
            fr.read(cs);
            modelContent = new String(cs);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }      
         
        //替换
         
        String fileContent = modelContent.replaceAll("@class@", className);
        fileContent = fileContent.replaceAll("@type@", type);
        fileContent = fileContent.replaceAll("@property@", property);
        fileContent = fileContent.replaceAll("@Uproperty@", Uproperty);
        String fileName = className+".java";
         
        //替换后的内容
        System.out.println("替换后的内容:");
        System.out.println(fileContent);
        File file = new File("E:\\project\\j2se\\src",fileName);
 
        try(FileWriter fw =new FileWriter(file);){
            fw.write(fileContent);
        } catch (IOException e) {
            e.printStackTrace();
        }
         
        System.out.println("文件保存在:" + file.getAbsolutePath());
    }
     
    public static String toUpperFirstLetter(String str){
        char upperCaseFirst =Character.toUpperCase(str.charAt(0));
        String rest = str.substring(1);
        return upperCaseFirst + rest;
         
    }
}

ArrayList

为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是
ArrayList
容器的容量"capacity"会随着对象的增加,自动增长
只需要不断往容器里增加英雄即可,不用担心会出现数组的边界问题。

方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9O031LZe-1633015948494)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210922224739825.png)]

add 有两种用法
第一种是直接add对象,把对象加在最后面

heros.add(new Hero("hero " + i));

第二种是在指定位置加对象

heros.add(3, specialHero);

    public static void main(String[] args) {
        ArrayList heros = new ArrayList();
 
        // 把5个对象加入到ArrayList中
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i));
        }
        System.out.println(heros);
 
        // 在指定位置增加对象
        Hero specialHero = new Hero("special hero");
        heros.add(3, specialHero);
 
        System.out.println(heros.toString());
 
    }

通过方法contains 判断一个对象是否在容器中
判断标准: 是否是同一个对象,而不是name是否相同

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BRJUYfc2-1633015948495)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210922225134583.png)]

通过get获取指定位置的对象,如果输入的下标越界,一样会报错

indexOf用于判断一个对象在ArrayList中所处的位置
与contains一样,判断标准是对象是否相同,而非对象的name值是否相等

remove用于把对象从ArrayList中删除
remove可以根据下标删除ArrayList的元素

heros.remove(2);

也可以根据对象删除

heros.remove(specialHero);

set用于替换指定位置的元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-guyWB3jG-1633015948496)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210922225432468.png)]

size 用于获取ArrayList的大小

heros.size();

toArray可以把一个ArrayList对象转换为数组。
需要注意的是,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组

package collection;
 
import java.util.ArrayList;
 
import charactor.Hero;
 
public class TestCollection {
    public static void main(String[] args) {
        ArrayList heros = new ArrayList();
 
        // 初始化5个对象
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i));
        }
        Hero specialHero = new Hero("special hero");
        heros.add(specialHero);
        System.out.println(heros);
        Hero hs[] = (Hero[])heros.toArray(new Hero[]{});
        System.out.println("数组:" +hs);
 
    }
}

addAll 把另一个容器所有对象都加进来

heros.addAll(anotherHeros);

clear 清空一个ArrayList

heros.clear();

List接口

ArrayList实现了接口List
常见的写法会把引用声明为接口List类型
注意:是java.util.List,而不是java.awt.List

    public static void main(String[] args) {
        //ArrayList实现了接口List
         
        //常见的写法会把引用声明为接口List类型
        //注意:是java.util.List,而不是java.awt.List
        //接口引用指向子类对象(多态)
         
        List heros = new ArrayList();
        heros.add( new Hero("盖伦"));
        System.out.println(heros.size());
         
    }

泛型Generic

不指定泛型的容器,可以存放任何类型的元素
指定了泛型的容器,只能存放指定类型的元素以及其子类

    public static void main(String[] args) {
          
        //对于不使用泛型的容器,可以往里面放英雄,也可以往里面放物品
        List heros = new ArrayList();
          
        heros.add(new Hero("盖伦"));
          
        //本来用于存放英雄的容器,现在也可以存放物品了
        heros.add(new Item("冰杖"));
          
        //对象转型会出现问题
        Hero h1=  (Hero) heros.get(0);
        //尤其是在容器里放的对象太多的时候,就记不清楚哪个位置放的是哪种类型的对象了
        Hero h2=  (Hero) heros.get(1);
          
        //引入泛型Generic
        //声明容器的时候,就指定了这种容器,只能放Hero,放其他的就会出错
        List<Hero> genericheros = new ArrayList<Hero>();
        genericheros.add(new Hero("盖伦"));
        //如果不是Hero类型,根本就放不进去
        //genericheros.add(new Item("冰杖"));
          
        //除此之外,还能存放Hero的子类
        genericheros.add(new APHero());
         
        //并且在取出数据的时候,不需要再进行转型了,因为里面肯定是放的Hero或者其子类
        Hero h = genericheros.get(0);
         
    }

为了不使编译器出现警告,需要前后都使用泛型,像这样:

List genericheros = new ArrayList();
不过JDK7提供了一个可以略微减少代码量的泛型简写方式

List genericheros2 = new ArrayList<>();

后面的泛型可以用<>来代替,聊胜于无吧

遍历

for循环遍历

        // 第一种遍历 for循环
        System.out.println("--------for 循环-------");
        for (int i = 0; i < heros.size(); i++) {
            Hero h = heros.get(i);
            System.out.println(h);
        }

使用迭代器Iterator遍历集合中的元素

for (Iterator iterator = heros.iterator(); iterator.hasNext()😉 {}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S87dovk9-1633015948498)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210922230633500.png)]

        List<Hero> heros = new ArrayList<Hero>();
         
        //放5个Hero进入容器
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero name " +i));
        }
         
        //第二种遍历,使用迭代器
        System.out.println("--------使用while的iterator-------");
        Iterator<Hero> it= heros.iterator();
        //从最开始的位置判断"下一个"位置是否有数据
        //如果有就通过next取出来,并且把指针向下移动
        //直到"下一个"位置没有数据
        while(it.hasNext()){
            Hero h = it.next();
            System.out.println(h);
        }
        //迭代器的for写法
        System.out.println("--------使用for的iterator-------");
        for (Iterator<Hero> iterator = heros.iterator(); iterator.hasNext();) {
            Hero hero = (Hero) iterator.next();
            System.out.println(hero);
        }

使用增强型for循环可以非常方便的遍历ArrayList中的元素,这是很多开发人员的首选。

不过增强型for循环也有不足:
无法用来进行ArrayList的初始化
无法得知当前是第几个元素了,当需要只打印单数元素的时候,就做不到了。 必须再自定下标变量。

        // 第三种,增强型for循环
        System.out.println("--------增强型for循环-------");
        for (Hero h : heros) {
            System.out.println(h);
        }

练习

首先初始化一个Hero集合,里面放100个Hero对象,名称分别是从
hero 0
hero 1
hero 2

hero 99.

通过遍历的手段,删除掉名字编号是8的倍数的对象

/*
    这个练习的考点是,不能够在使用Iterator和增强for循环遍历数据的同时,删除数据,否则会抛出ConcurrentModificationException

解决办法,是准备一个临时容器,专门用来保存需要删除的对象。 然后再删掉
    */
public static void main(String[] args) {
        List<Hero> heros = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
            heros.add(new Hero("hero name "+i));
        }

        //准备一个容器,专门用来装要删除的对象
        List<Hero> deletingHeros = new ArrayList<>();

        for (Hero h : heros) {
            int id = Integer.parseInt(h.name.substring(10));
            if (0 == id % 8)
                deletingHeros.add(h);
        }
        for (Hero h : deletingHeros) {
            heros.remove(h);
        }
//      heros.removeAll(deletingHeros); //直接通过removeAll删除多个Hero对象
        System.out.println(heros);
    
-------------------------------------------------------------------------------------------------------------
    //迭代器方法
           Iterator<Hero> iterator = heros.iterator();
        // 申明初始化迭代器iterator
        for (int i = 8; i <= 100 ; i += 8) {
            String name = "hero " + i;
            while(iterator.hasNext()){
                Hero h = iterator.next();
                if(h.name.equals(name)){
                    // Itr的remove()删除元素不会报错
                    iterator.remove();
                    System.out.println(h.name+"已被删除");
                    break;
                }
            }
        }
        System.out.println(heros);

集合框架

序列分先进先出FIFO,先进后出FILO
FIFO在Java中又叫Queue 队列
FILO在Java中又叫Stack 栈

LinkedList双向链表

Deque双向链表

除了实现了List接口外,LinkedList还实现了双向链表结构Deque,可以很方便的在头尾插入删除数据

什么是链表结构: 与数组结构相比较,数组结构,就好像是电影院,每个位置都有标示,每个位置之间的间隔都是一样的。 而链表就相当于佛珠,每个珠子,只连接前一个和后一个,不用关心除此之外的其他佛珠在哪里。

        //LinkedList是一个双向链表结构的list
        LinkedList<Hero> ll =new LinkedList<Hero>();
         
        //所以可以很方便的在头部和尾部插入数据
        //在最后插入新的英雄
        ll.addLast(new Hero("hero1"));
        ll.addLast(new Hero("hero2"));
        ll.addLast(new Hero("hero3"));
        System.out.println(ll);
         
        //在最前面插入新的英雄
        ll.addFirst(new Hero("heroX"));
        System.out.println(ll);
         
        //查看最前面的英雄
        System.out.println(ll.getFirst());
        //查看最后面的英雄
        System.out.println(ll.getLast());
         
        //查看不会导致英雄被删除
        System.out.println(ll);
        //取出最前面的英雄
        System.out.println(ll.removeFirst());
         
        //取出最后面的英雄
        System.out.println(ll.removeLast());
         
        //取出会导致英雄被删除
        System.out.println(ll);

Queue队列 先进先出FIFO

LinkedList 除了实现了List和Deque外,还实现了Queue接口(队列)。
Queue是先进先出队列 FIFO,常用方法:
offer 在最后添加元素
poll 取出第一个元素
peek 查看第一个元素

        //和ArrayList一样,LinkedList也实现了List接口
        List ll =new LinkedList<Hero>();
          
        //所不同的是LinkedList还实现了Deque,进而又实现了Queue这个接口
        //Queue代表FIFO 先进先出的队列
        Queue<Hero> q= new LinkedList<Hero>();

        //加在队列的最后面
        System.out.print("初始化队列:\t");
        q.offer(new Hero("Hero1"));

        //取出第一个Hero,FIFO 先进先出
        Hero h = q.poll();
        System.out.println(h);

        //把第一个拿出来看一看,但是不取出来
        h=q.peek();
        System.out.print("查看peek()第一个元素:\t");

FILO先进后出栈Stack

FIFO(先入先出的)队列类似的一种数据结构是FILO先入后出Stack

push 在最后添加元素

poll 取出最后一个元素

peek 查看最后一个元素

二叉树

二叉树由各种节点组成
二叉树特点:
每个节点都可以有左子节点,右子节点
每一个节点都有一个

二叉树排序

假设通过二叉树对如下10个随机数进行排序
67,7,30,73,10,0,78,81,10,74
排序的第一个步骤是把数据插入到该二叉树中
插入基本逻辑是,小、相同的放左边大的放右边
\1. 67 放在根节点
\2. 7 比 67小,放在67的左节点
\3. 30 比67 小,找到67的左节点7,30比7大,就放在7的右节点
\4. 73 比67大, 放在67的右节点
\5. 10 比 67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,放在30的左节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ikSADnb1-1633015948500)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210925225110935.png)]

package collection;
  
public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;
  
    // 值
    public Object value;
  
    // 插入 数据
    public void add(Object v) {
        // 如果当前节点没有值,就把数据放在当前节点上
        if (null == value)
            value = v;
  
        // 如果当前节点有值,就进行判断,新增的值与当前值的大小关系
        else {
            // 新增的值,比当前值小或者相同
             
            if ((Integer) v -((Integer)value) <= 0) {
                if (null == leftNode)
                    leftNode = new Node();
                leftNode.add(v);
            }
            // 新增的值,比当前值大
            else {
                if (null == rightNode)
                    rightNode = new Node();
                rightNode.add(v);
            }
  
        }
  
    }
  
    public static void main(String[] args) {
  
        int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
  
        Node roots = new Node();
        for (int number : randoms) {
            roots.add(number);
        }
  
    }
}

二叉树排序:遍历

通过上一个步骤的插入行为,实际上,数据就已经排好序了。 接下来要做的是看,把这些已经排好序的数据,遍历成我们常用的List或者数组的形式

二叉树的遍历分左序,中序,右序
左序即: 中间的数遍历后放在左边
中序即: 中间的数遍历后放在中间
右序即: 中间的数遍历后放在右边
如图所见,我们希望遍历后的结果是从小到大的,所以应该采用中序遍历

 // 中序遍历所有的节点
    public List<Object> values() {
        List<Object> values = new ArrayList<>();
  
        // 左节点的遍历结果
        if (null != leftNode)
            values.addAll(leftNode.values());
  
        // 当前节点
        values.add(value);
  
        // 右节点的遍历结果
        if (null != rightNode)
  
            values.addAll(rightNode.values());
  
        return values;
    }

HashMap

HashMap储存数据的方式是—— 键值对

package collection;
   
import java.util.HashMap;
   
public class TestCollection {
    public static void main(String[] args) {
        HashMap<String,String> dictionary = new HashMap<>();
        dictionary.put("adc", "物理英雄");
        dictionary.put("apc", "魔法英雄");
        dictionary.put("t", "坦克");
         
        System.out.println(dictionary.get("t"));
    }
}

对于HashMap而言,key是唯一的,不可以重复的。
所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。
不过,同一个对象可以作为值插入到map中,只要对应的key不一样

heroMap.clear(); //清空map

keySet()可以获取所有的key, values()可以获取所有的value

package com.wang.arraylist;
/*准备一个ArrayList其中存放3000000(三百万个)Hero对象,其名称是随机的,格式是hero-[4位随机数]
hero-3229
hero-6232
hero-9365
...

因为总数很大,所以几乎每种都有重复,把名字叫做 hero-5555的所有对象找出来
要求使用两种办法来寻找
1. 不使用HashMap,直接使用for循环找出来,并统计花费的时间
2. 借助HashMap,找出结果,并统计花费的时间
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.wang.oop.demo01.Hero;

public class HashMapTest {
    public static void main(String[] args) {
        List<Hero> heros = new ArrayList<>();
        for (int i = 0; i < 300_0000; i++) {
            heros.add(new Hero("hero-"+(int)(Math.random()*9000+1000)));
        }

        forFine(heros);
        hashMap(heros);

    }

    public static void forFine(List h){
        int num=0;
        long start = System.currentTimeMillis();
        for (int i = 0; i < h.size(); i++) {
           Hero hero = (Hero) h.get(i);
           if (hero.name.equals("hero-5555"))
               num++;
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-start)+",共找到"+num+"个");
    }

    public static void hashMap(List<Hero> hs){
        HashMap<String,List<Hero>> heroMap = new HashMap<>();
        long start = System.currentTimeMillis();
        for (Hero h : hs) { //已存在则往里放,不存在则新建,链表都是list,但是地址不同
            List<Hero> list = heroMap.get(h.name);
            if(list==null){
                list = new ArrayList<>();
                heroMap.put(h.name,list);
            }
            list.add(h);
        }

        List<Hero> result = heroMap.get("hero-5555");
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-start)+",共找到"+result.size()+"个");

    }
}

HashSet

Set中的元素,不能重复

Set中的元素,没有顺序。
严格的说,是没有按照元素的插入顺序排列

HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。

package collection;
 
import java.util.HashSet;
 
public class TestCollection {
    public static void main(String[] args) {
        
    	HashSet<String> names = new HashSet<String>();
    	
    	names.add("gareen");
    	
    	System.out.println(names);
    	
    	//第二次插入同样的数据,是插不进去的,容器中只会保留一个
    	names.add("gareen");
    	System.out.println(names);
    }
}

Set不提供get()来获取指定位置的元素
所以遍历需要用到迭代器,或者增强型for循环

package collection;
 
import java.util.HashSet;
import java.util.Iterator;
 
public class TestCollection {
    public static void main(String[] args) {
    	HashSet<Integer> numbers = new HashSet<Integer>();
    	
    	for (int i = 0; i < 20; i++) {
			numbers.add(i);
		}
    	
    	//Set不提供get方法来获取指定位置的元素
    	//numbers.get(0)
    	
    	//遍历Set可以采用迭代器iterator
    	for (Iterator<Integer> iterator = numbers.iterator(); iterator.hasNext();) {
			Integer i = (Integer) iterator.next();
			System.out.println(i);
		}
    	
    	//或者采用增强型for循环
    	for (Integer i : numbers) {
			System.out.println(i);
		}
    	
    }
}

HashSet和HashMap的关系

HashSet自身并没有独立的实现,而是在里面封装了一个Map.
HashSet是作为Map的key而存在的
而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。

private static final Object PRESENT = new Object();

package com.wang.arraylist;
/*
在比较字符串章节,有一个同样的练习
创建一个长度是100的字符串数组
使用长度是2的随机字符填充该字符串数组
统计这个字符串数组里重复的字符串有多少种
使用HashSet来解决这个问题
*/
import java.util.HashSet;
import java.util.Random;

public class Demo02 {
    public static void main(String[] args) {
        String[] str = randomString(100);
        for (int i = 0;i<str.length;i++) {
            if (i>0 && i%20==0)
                System.out.println();
            System.out.print(str[i]+" ");
        }

        HashSet<String> strs = new HashSet<>();
        HashSet<String> doubleStrs = new HashSet<>();
        for (String s : str) {
            if(!strs.add(s))
                doubleStrs.add(s);
        }
        System.out.println();
        System.out.println("重复的有:"+(str.length-strs.size()));
        for (String s : doubleStrs) {
            System.out.print(s+" ");
        }

    }

    public static String[] randomString(int length){
        String[] str = new String[length];
        char[] c = new char[2];

        for (int i = 0; i < length; i++) {
            c[0] = (char)randomInt();
            c[1] = (char)randomInt();
            str[i] = new String(c);
        }
        return str;
    }

    public static int randomInt(){
        Random rd = new Random();
        int num = 0;
        num = rd.nextInt(75)+48;
        if((num>=58 && num<=64) || (num>=91 && num<=96)){
            num = randomInt();
        }else {
            return num;
        }
        return num;
    }
}

Collection

Collection是 Set List Queue和 Deque的接口
Queue: 先进先出队列
Deque: 双向链表

**注:**Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的
**注:**Deque 继承 Queue,间接的继承了 Collection

Collection

LinkedList是双向链表,它实现了Deque接口,也就是双向对列接口

接口只能继承接口,不能实现,继承接口后可以增加新的方法。

Collections

Collections是一个类,容器的工具类,就如同Arrays是数组的工具类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Maw5MIQD-1633015948503)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210927222338867.png)]

public class TestCollection {
    public static void main(String[] args) {
        //初始化集合numbers
        List<Integer> numbers = new ArrayList<>();
         
        for (int i = 0; i < 10; i++) {
            numbers.add(i);
        }
         
        System.out.println("集合中的数据:");
        System.out.println(numbers);
        
        //翻转reverse
        Collections.reverse(numbers);
         
        System.out.println("翻转后集合中的数据:");
        System.out.println(numbers);
         
    }
}
        //混淆shuffle
        Collections.shuffle(numbers);    
        System.out.println("混淆后集合中的数据:");

        //排序sort
        Collections.sort(numbers);
        System.out.println("排序后集合中的数据:");

        //交换swap
        Collections.swap(numbers,0,5);
        System.out.println("交换0和5下标的数据后,集合中的数据:");

        //滚动rotate,向右滚动指定单位的长度
        Collections.rotate(numbers,2);
        System.out.println("把集合向右滚动2个单位,标的数据后,集合中的数据:");

        //synchronizedList 把非线程安全的List转换为线程安全的List。
        System.out.println("把非线程安全的List转换为线程安全的List");
        List<Integer> synchronizedNumbers = (List<Integer>) Collections.synchronizedList(numbers);

集合框架的联系与区别

ArrayList vs HashSet

ArrayList: 有顺序
HashSet: 无顺序

HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。

不保证Set的迭代顺序; 确切的说,在不同条件下,元素的顺序都有可能不一样

换句话说,同样是插入0-9到HashSet中, 在JVM的不同版本中,看到的顺序都是不一样的。 所以在开发的时候,不能依赖于某种臆测的顺序,这个顺序本身是不稳定的.

List中的数据可以重复
Set中的数据不能够重复
重复判断标准是:
首先看hashcode是否相同
如果hashcode不同,则认为是不同数据
如果hashcode相同,再比较equals,如果equals相同,则是相同数据,否则是不同数据

ArrayList vs LinkedList

ArrayList 插入,删除数据慢
LinkedList, 插入,删除数据快
ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。
LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢

ArrayList和LinkedList的区别

插入数据

定位数据

100000组数据在最后插入和中间插入整数都是ArrayList比较快,

用add插入时如果位置比较靠前linkedlist会比较快

HashMap vs Hashtable

HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
区别1:
HashMap可以存放 null
Hashtable不能存放null
区别2:
HashMap不是线程安全的类
Hashtable是线程安全的类

        //HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
         
        HashMap<String,String> hashMap = new HashMap<String,String>();
         
        //HashMap可以用null作key,作value
        hashMap.put(null, "123");
        hashMap.put("123", null);
         
        Hashtable<String,String> hashtable = new Hashtable<String,String>();
        //Hashtable不能用null作key,不能用null作value
        hashtable.put(null, "123");
        hashtable.put("123", null);

几种set

HashSet: 无序
LinkedHashSet: 按照插入顺序
TreeSet: 从小到大排序

        HashSet<Integer> numberSet1 =new HashSet<Integer>();
        //HashSet中的数据不是按照插入顺序存放
        numberSet1.add(88);
        numberSet1.add(8);
        numberSet1.add(888);
          
        System.out.println(numberSet1);
          
        LinkedHashSet<Integer> numberSet2 =new LinkedHashSet<Integer>();
        //LinkedHashSet中的数据是按照插入顺序存放
        numberSet2.add(88);
        numberSet2.add(8);
        numberSet2.add(888);
          
        System.out.println(numberSet2);
        TreeSet<Integer> numberSet3 =new TreeSet<Integer>();
        //TreeSet 中的数据是进行了排序的
        numberSet3.add(88);
        numberSet3.add(8);
        numberSet3.add(888);
          
        System.out.println(numberSet3);

HashMap原理与字典

在展开HashMap原理的讲解之前,首先回忆一下大家初中和高中使用的汉英字典。

比如要找一个单词对应的中文意思,假设单词是Lengendary,首先在目录找到Lengendary在第 555页。

然后,翻到第555页,这页不只一个单词,但是量已经很少了,逐一比较,很快就定位目标单词Lengendary。

555相当于就是Lengendary对应的hashcode

-----hashcode概念-----
所有的对象,都有一个对应的hashcode(散列值)
比如字符串“gareen”对应的是1001 (实际上不是,这里是方便理解,假设的值)
比如字符串“temoo”对应的是1004
比如字符串“db”对应的是1008
比如字符串“annie”对应的也****是1008

-----保存数据-----
准备一个数组,其长度是2000,并且设定特殊的hashcode算法,使得所有字符串对应的hashcode,都会落在0-1999之间
要存放名字是"gareen"的英雄,就把该英雄和名称组成一个键值对,存放在数组的1001这个位置上
要存放名字是"temoo"的英雄,就把该英雄存放在数组的1004这个位置上
要存放名字是"db"的英雄,就把该英雄存放在数组的1008这个位置上
要存放名字是"annie"的英雄,然而 "annie"的hashcode 1008对应的位置已经有db英雄了,那么就在这里创建一个链表,接在db英雄后面存放annie

-----查找数据-----
比如要查找gareen,首先计算"gareen"的hashcode是1001,根据1001这个下标,到数组中进行定位,(根据数组下标进行定位,是非常快速的) 发现1001这个位置就只有一个英雄,那么该英雄就是gareen.
比如要查找annie,首先计算"annie"的hashcode是1008,根据1008这个下标,到数组中进行定位,发现1008这个位置有两个英雄,那么就对两个英雄的名字进行逐一比较(equals),因为此时需要比较的量就已经少很多了,很快也就可以找出目标英雄
这就是使用hashmap进行查询,非常快原理。

这是一种用空间换时间的思维方式

分析HashMap性能卓越的原因

HashSet的数据是不能重复的,相同数据不能保存在一起,到底如何判断是否是重复的呢?
根据HashSet和HashMap的关系,我们了解到因为HashSet没有自身的实现,而是里面封装了一个HashMap,所以本质上就是判断HashMap的key是否重复。

再通过上一步的学习,key是否重复,是由两个步骤判断的:
hashcode是否一样
如果hashcode不一样,就是在不同的坑里,一定是不重复的
如果hashcode一样,就是在同一个坑里,还需要进行equals比较
如果equals一样,则是重复数据
如果equals不一样,则是不同数据。

      //重写hashCode
      public static int hashcode(String str){
        if (0 == str.length())
            return 0;

        int num = 0;
        char[] c = str.toCharArray();
        for (int i = 0; i < c.length; i++) {
            num+=c[i];
        }
        num = num * 32;

        num = num<0?0-num:num;
        num%=2000;

        return num;
    }

比较器 Comparator

假设Hero有三个属性 name,hp,damage
一个集合中放存放10个Hero,通过Collections.sort对这10个进行排序
那么到底是hp小的放前面?还是damage小的放前面?Collections.sort也无法确定
所以要指定到底按照哪种属性进行排序
这里就需要提供一个Comparator给定如何进行两个对象之间的
大小
比较

Collections.sort(heros,c); //重写的比较器大于时正序,小于时倒序

    public static void main(String[] args) {
        Random r =new Random();
        List<Hero> heros = new ArrayList<Hero>();
            
        for (int i = 0; i < 10; i++) {
            //通过随机值实例化hero的hp和damage
            heros.add(new Hero("hero "+ i, r.nextInt(100), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
            
        //直接调用sort会出现编译错误,因为Hero有各种属性
        //到底按照哪种属性进行比较,Collections也不知道,不确定,所以没法排
        //Collections.sort(heros);
            
        //引入Comparator,指定比较的算法
        Comparator<Hero> c = new Comparator<Hero>() {
            @Override
            public int compare(Hero h1, Hero h2) {
                //按照hp进行排序
                if(h1.hp>=h2.hp) //大于时正序,小于时倒序 ;返回1正序,返回-1倒序
                    return 1;  //正数表示h1比h2要大
                else
                    return -1;
            }
        };
        Collections.sort(heros,c); //
        System.out.println("按照血量排序后的集合:");
        System.out.println(heros);
    }

Comparable

使Hero类实现Comparable接口
在类里面提供比较算法
Collections.sort就有足够的信息进行排序了,也无需额外提供比较器Comparator
注: 如果返回-1, 就表示当前的更小,否则就是更大;重写的大于时返回1正序,小于时倒序

public class Hero implements Comparable<Hero>{
    @Override
    public int compareTo(Hero anotherHero) {
        if(damage<anotherHero.damage)
            return 1; 
        else
            return -1;
    }

练习

/*
默认情况下,TreeSet中的数据是从小到大排序的,不过TreeSet的构造方法支持传入一个Comparator

public TreeSet(Comparator comparator) 

通过这个构造方法创建一个TreeSet,使得其中的的数字是倒排序的
*/
        public static void main(String[] args) {
        TreeSet<Integer> nums = new TreeSet<>();
        for (int i = 0 ; i < 10 ; i++){
            nums.add((int)(Math.random()*100));
        }
        System.out.println(nums);

        Comparator<Integer> c = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                if (o1 < o2)
                    return 1;
                else
                    return -1;
            }
        };
        TreeSet<Integer> num = new TreeSet<>(c); //有参构造TreeSet
        for (int i = 0 ; i < 10 ; i++){
            num.add((int)(Math.random()*100));
        }
        System.out.println(num);
    }

聚合操作

JDK8之后,引入了对集合的聚合操作,可以非常容易的遍历,筛选,比较集合中的元素。

像这样:

​ String name =heros

​ .stream()

​ .sorted((h1,h2)->h1.hp>h2.hp?-1:1)

​ .skip(2)

​ .map(h->h.getName())

​ .findFirst()

​ .get();

但是要用好聚合,必须先掌握Lambda表达式,聚合的章节讲放在Lambda与聚合操作部分详细讲解

    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 10; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
 
        System.out.println("初始化集合后的数据 (最后一个数据重复):");
        System.out.println(heros);
         
        //传统方式
        Collections.sort(heros,new Comparator<Hero>() { //重写比较器
            @Override
            public int compare(Hero o1, Hero o2) {
                return (int) (o2.hp-o1.hp);
            }
        });
         
        Hero hero = heros.get(2);
        System.out.println("通过传统方式找出来的hp第三高的英雄名称是:" + hero.name);
         
        //聚合方式
        String name =heros
            .stream()
            .sorted((h1,h2)->h1.hp>h2.hp?-1:1)
            .skip(2)
            .map(h->h.getName())
            .findFirst()
            .get();
 
        System.out.println("通过聚合操作找出来的hp第三高的英雄名称是:" + name);
         
    }

泛型

ArrayList 默认接受Object类型的对象,所以所有对象都可以放进ArrayList中
所以get(0) 返回的类型是Object
接着,需要进行强制转换才可以得到APHero类型或者ADHero类型。
如果软件开发人员记忆比较好,能记得哪个是哪个,还是可以的。 但是开发人员会犯错误,比如第20行,会记错,把第0个对象转换为ADHero,这样就会出现类型转换异常

        ArrayList heros = new ArrayList();
         
        heros.add(new APHero());
        heros.add(new ADHero());
         
        APHero apHero =  (APHero) heros.get(0);
        ADHero adHero =  (ADHero) heros.get(1);
         
        ADHero adHero2 =  (ADHero) heros.get(0);

使用泛型的好处:
泛型的用法是在容器后面添加
Type可以是类,抽象类,接口
泛型表示这种容器,只能存放APHero,ADHero就放不进去了。

子类对象可以放进去

假设容器的泛型是Hero,那么Hero的子类APHero,ADHero都可以放进去
和Hero无关的类型Item还是放不进去

支持泛型的类

设计一个支持泛型的栈MyStack
设计这个类的时候,在类的声明上,加上一个,表示该类支持泛型。
T是type的缩写,也可以使用任何其他的合法的变量,比如A,B,X都可以,但是一般约定成俗使用T,代表类型。

public class MyStack<T> {
   
    LinkedList<T> values = new LinkedList<T>();
       
    public void push(T t) {
        values.addLast(t);
    }
   
    public T pull() {
        return values.removeLast();
    }
   
    public T peek() {
        return values.getLast();
    }
       
    public static void main(String[] args) {
        //在声明这个Stack的时候,使用泛型<Hero>就表示该Stack只能放Hero
        MyStack<Hero> heroStack = new MyStack<>();
        heroStack.push(new Hero());
        //不能放Item
        heroStack.push(new Item());
         
        //在声明这个Stack的时候,使用泛型<Item>就表示该Stack只能放Item
        MyStack<Item> itemStack = new MyStack<>();
        itemStack.push(new Item());
        //不能放Hero
        itemStack.push(new Hero());
    }

通配符

? extends

ArrayList heroList<? extends Hero> 表示这是一个Hero泛型或者其子类泛型
heroList 的泛型可能是Hero
heroList 的泛型可能是APHero
heroList 的泛型可能是ADHero
所以 可以确凿的是,从heroList取出来的对象,一定是可以转型成Hero的

但是,不能往里面放东西,因为
放APHero就不满足
放ADHero又不满足

? extends

    public static void main(String[] args) {
          
        ArrayList<APHero> apHeroList = new ArrayList<APHero>();
        apHeroList.add(new APHero());
         
        ArrayList<? extends Hero> heroList = apHeroList;
          
        //? extends Hero 表示这是一个Hero泛型的子类泛型
          
        //heroList 的泛型可以是Hero
        //heroList 的泛型可以使APHero
        //heroList 的泛型可以使ADHero
          
        //可以确凿的是,从heroList取出来的对象,一定是可以转型成Hero的
          
        Hero h= heroList.get(0);
          
        //但是,不能往里面放东西
        heroList.add(new ADHero()); //编译错误,因为heroList的泛型 有可能是APHero
          
    }
? super

ArrayList heroList<? super Hero> 表示这是一个Hero泛型或者其父类泛型
heroList的泛型可能是Hero
heroList的泛型可能是Object

可以往里面插入Hero以及Hero的子类
但是取出来有风险,因为不确定取出来是Hero还是Object

? super

    public static void main(String[] args) {
  
        ArrayList<? super Hero> heroList = new ArrayList<Object>();
          
        //? super Hero 表示 heroList的泛型是Hero或者其父类泛型
          
        //heroList 的泛型可以是Hero
        //heroList 的泛型可以是Object
          
        //所以就可以插入Hero
        heroList.add(new Hero());
        //也可以插入Hero的子类
        heroList.add(new APHero());
        heroList.add(new ADHero());
          
        //但是,不能从里面取数据出来,因为其泛型可能是Object,而Object是强转Hero会失败
        Hero h= heroList.get(0);
          
    }
泛型通配符?

泛型通配符? 代表任意泛型
既然?代表任意泛型,那么换句话说,这个容器什么泛型都有可能

所以只能以Object的形式取出来
并且不能往里面放对象,因为不知道到底是一个什么泛型的容器

泛型通配符?

    public static void main(String[] args) {
  
        ArrayList<APHero> apHeroList = new ArrayList<APHero>();
         
        //?泛型通配符,表示任意泛型
        ArrayList<?> generalList = apHeroList;
 
        //?的缺陷1: 既然?代表任意泛型,那么换句话说,你就不知道这个容器里面是什么类型
        //所以只能以Object的形式取出来
        Object o = generalList.get(0);
 
        //?的缺陷2: 既然?代表任意泛型,那么既有可能是Hero,也有可能是Item
        //所以,放哪种对象进去,都有风险,结果就什么什么类型的对象,都不能放进去
        generalList.add(new Item()); //编译错误 因为?代表任意泛型,很有可能不是Item
        generalList.add(new Hero()); //编译错误 因为?代表任意泛型,很有可能不是Hero
        generalList.add(new APHero()); //编译错误  因为?代表任意泛型,很有可能不是APHero
  
    }
小结

如果希望只取出,不插入,就使用? extends Hero
如果希望只插入,不取出,就使用? super Hero
如果希望,又能插入,又能取出,就不要用通配符?

/*
把练习-支持泛型的二叉树改造成 支持泛型 <T extends Comparable>,并在比较的时候使用compare方法

把Node设计为
public class Node<T extends Comparable<T>> { 

就表示只能使用那些实现了Comparable接口的泛型,比如Integer,Hero
Node<Integer> roots = new Node<>();
Node<Hero> heros = new Node<>();
 
但是GiantDragon就不行,因为它没有实现Comparable接口
Node<GiantDragon> dragons = new Node<>();
*/
package collection;
 
import java.util.ArrayList;
import java.util.List;
 
import charactor.GiantDragon;
import charactor.Hero;
 
public class Node<T extends Comparable<T>> {
 
    public Node<T> leftNode;
 
    public Node<T> rightNode;
 
    public T value;
 
    public void add(T t) {
 
        if (null == value)
            value = t; 
        else {
            //t和value都是T 类型,而T类型extends Comparable,所以必然提供compare接口
            if (t.compareTo(value) <= 0) {
                if (null == leftNode)
                    leftNode = new Node<T>();
                leftNode.add(t);
            }
 
            else {
                if (null == rightNode)
                    rightNode = new Node<T>();
                rightNode.add(t);
            }
        }
    }
 
    public List<T> values() {
        List<T> values = new ArrayList<>();
 
        if (null != leftNode)
            values.addAll(leftNode.values());
 
        values.add(value);
 
        if (null != rightNode)
 
            values.addAll(rightNode.values());
 
        return values;
    }
 
    public static void main(String[] args) {
 
        int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
 
        Node<Integer> roots = new Node<>();
        for (int number : randoms) {
            roots.add(number);
        }
 
        System.out.println(roots.values());
         
        //Hero实现了 Comparable接口,所以可以作为Node的泛型
        Node<Hero> heros = new Node<>();
         
        //GiantDragon 没有实现 Comparable接口,所以不能作为Node的泛型
        Node<GiantDragon> dragons = new Node<>();
         
    }
}

对象转型

根据面向对象学习的知识,子类转父类是一定可以成功的

但是子类泛型不能转换为父类泛型。

父类泛型也不能转子类泛型。

Lambda

匿名类方式遍历满足条件的数据

首先准备一个接口HeroChecker,提供一个test(Hero)方法
然后通过匿名类的方式,实现这个接口

接着调用filter,传递这个checker进去进行判断,这种方式就很像通过Collections.sort在对一个Hero集合排序,需要传一个Comparator的匿名类对象进去一样。

package lambda;
   
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
   
import charactor.Hero;
   
public class TestLambda {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("使用匿名类的方式,筛选出 hp>100 && damange<50的英雄");
        HeroChecker checker = new HeroChecker() {
            @Override
            public boolean test(Hero h) {
                return (h.hp>100 && h.damage<50);
            }
        };
           
        filter(heros,checker);
    }
   
    private static void filter(List<Hero> heros,HeroChecker checker) {
        for (Hero hero : heros) {
            if(checker.test(hero))
                System.out.print(hero);
        }
    }
   
}

package lambda; 
import charactor.Hero;
public interface HeroChecker {
    public boolean test(Hero h);
}

从匿名类演变成Lambda表达式

Lambda表达式可以看成是匿名类一点点演变过来

  1. 匿名类的正常写法

    HeroChecker c1 = new HeroChecker() {

    ​ public boolean test(Hero h) {

    ​ return (h.hp>100 && h.damage<50);

    ​ }

    };

  2. 把外面的壳子去掉
    只保留方法参数方法体
    参数和方法体之间加上符号 ->

    HeroChecker c2 = (Hero h) ->{

    ​ return h.hp>100 && h.damage<50;

    };

  3. 把return和{}去掉

    HeroChecker c3 = (Hero h) ->h.hp>100 && h.damage<50;

  4. 把 参数类型和圆括号去掉(只有一个参数的时候,才可以去掉圆括号)

    HeroChecker c4 = h ->h.hp>100 && h.damage<50;

  5. 把c4作为参数传递进去

    filter(heros,c4);

  6. 直接把表达式传递进去

    filter(heros, h -> h.hp > 100 && h.damage < 50);

匿名方法

匿名类 概念相比较,
Lambda 其实就是匿名方法,这是一种把方法作为参数进行传递的编程思想。

虽然代码是这么写

filter(heros, h -> h.hp > 100 && h.damage < 50);

但是,Java会在背后,悄悄的,把这些都还原成匿名类方式
引入Lambda表达式,会使得代码更加紧凑,而不是各种接口和匿名类到处飞。

弊端

Lambda表达式虽然带来了代码的简洁,但是也有其局限性。
\1. 可读性差,与啰嗦的但是清晰的匿名类代码结构比较起来,Lambda表达式一旦变得比较长,就难以理解
\2. 不便于调试,很难在Lambda表达式中增加调试信息,比如日志
\3. 版本支持,Lambda表达式在JDK8版本中才开始支持,如果系统使用的是以前的版本,考虑系统的稳定性等原因,而不愿意升级,那么就无法使用。

Lambda比较适合用在简短的业务代码中,并不适合用在复杂的系统中,会加大维护成本。

改写Comparator

        Comparator<Hero> c = new Comparator<Hero>() {
            @Override
            public int compare(Hero o1, Hero o2) {
                if (o1.hp<=o2.hp)
                return 1;
                else
                    return -1;
            }
        };
        Collections.sort(heros,c);

        Collections.sort(heros,(h1,h2)-> h1.hp>h2.hp?1:-1);

聚合操作

要了解聚合操作,首先要建立Stream管道的概念
Stream 和Collection结构化的数据不一样,Stream是一系列的元素,就像是生产线上的罐头一样,一串串的出来。
管道指的是一系列的聚合操作。

管道又分3个部分
管道源:在这个例子里,源是一个List
中间操作: 每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。
结束操作:当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回, 结束操作才进行真正的遍历行为,在遍历的时候,才会去进行中间操作的相关判断

注: 这个Stream和I/O章节的InputStream,OutputStream是不一样的概念。

管道源

把Collection切换成管道源很简单,调用stream()就行了。

heros.stream()
但是数组却没有stream()方法,需要使用

Arrays.stream(hs)
或者

Stream.of(hs)

package lambda;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
 
import charactor.Hero;
 
public class TestAggregate {
 
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        //管道源是集合
        heros
        .stream()
        .forEach(h->System.out.println(h.name));
         
        //管道源是数组
        Hero hs[] = heros.toArray(new Hero[heros.size()]);
        Arrays.stream(hs)
        .forEach(h->System.out.println(h.name));
         
    }
}

中间操作

每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。
中间操作比较多,主要分两类
对元素进行筛选 和 转换为其他形式的流
对元素进行筛选:
filter 匹配
distinct 去除重复(根据equals判断)
sorted 自然排序
sorted(Comparator) 指定排序
limit 保留
skip 忽略
转换为其他形式的流
mapToDouble 转换为double的流
map 转换为任意类型的流

package lambda;
  
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
 
import charactor.Hero;
  
public class TestAggregate {
  
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        //制造一个重复数据
        heros.add(heros.get(0));
        System.out.println("初始化集合后的数据 (最后一个数据重复):");
        System.out.println(heros);
        System.out.println("满足条件hp>100&&damage<50的数据");
          
        heros
            .stream()
            .filter(h->h.hp>100&&h.damage<50)
            .forEach(h->System.out.print(h));
          
        System.out.println("去除重复的数据,去除标准是看equals");
        heros
            .stream()
            .distinct()
            .forEach(h->System.out.print(h));
        System.out.println("按照血量排序");
        heros
            .stream()
            .sorted((h1,h2)->h1.hp>=h2.hp?1:-1)
            .forEach(h->System.out.print(h));
          
        System.out.println("保留3个");
        heros
            .stream()
            .limit(3)
            .forEach(h->System.out.print(h));
          
        System.out.println("忽略前3个");
        heros
            .stream()
            .skip(3)
            .forEach(h->System.out.print(h));
          
        System.out.println("转换为double的Stream");
        heros
            .stream()
            .mapToDouble(Hero::getHp)
            .forEach(h->System.out.println(h));
          
        System.out.println("转换任意类型的Stream");
        heros
            .stream()
            .map((h)-> h.name + " - " + h.hp + " - " + h.damage)
            .forEach(h->System.out.println(h));
          
    }
}

中间操作

结束操作

当进行结束操作后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回,。
结束操作才真正进行遍历行为,前面的中间操作也在这个时候,才真正的执行。
常见结束操作如下:
forEach() 遍历每个元素
toArray() 转换为数组
min(Comparator) 取最小的元素
max(Comparator) 取最大的元素
count() 总数
findFirst() 第一个元素

package lambda;
  
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
 
import org.omg.Messaging.SYNC_WITH_TRANSPORT;
 
import charactor.Hero;
  
public class TestAggregate {
  
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("遍历集合中的每个数据");
        heros
            .stream()
            .forEach(h->System.out.print(h));
        System.out.println("返回一个数组");
        Object[] hs= heros
            .stream()
            .toArray();
        System.out.println(Arrays.toString(hs));
        System.out.println("返回伤害最低的那个英雄");
        Hero minDamageHero =
        heros
            .stream()
            .min((h1,h2)->h1.damage-h2.damage)
            .get();
        System.out.print(minDamageHero);
        System.out.println("返回伤害最高的那个英雄");
 
        Hero mxnDamageHero =
                heros
                .stream()
                .max((h1,h2)->h1.damage-h2.damage)
                .get();
        System.out.print(mxnDamageHero);     
         
        System.out.println("流中数据的总数");
        long count = heros
                .stream()
                .count();
        System.out.println(count);
 
        System.out.println("第一个英雄");
        Hero firstHero =
                heros
                .stream()
                .findFirst()
                .get();
         
        System.out.println(firstHero);
         
    }
}

结束操作

常用

随机数

import java.util.Random;
import static java.lang.Math.*;

a[i][j]=(int)(Math.random()*100);
a[i][j]=new Random().nextInt(100);
     Random rd=new Random();
     num=rd.nextInt(75)+48;         // 随机数范围:[48,122)

System.arraycopy()

把一个数组的值,复制到另一个数组中

System.arraycopy(src, srcPos, dest, destPos, length)
src: 源数组
srcPos: 从源数组复制数据的起始位置
dest: 目标数组
destPos: 复制到目标数组的起始位置
length: 复制的长度

Array.deepToString

Arrays.deepToString()主要用于数组中还有数组的情况(即多维数组,包括二维数组)

实例

离黄金分割点 0.618最近

寻找某两个数相除,其结果 离黄金分割点 0.618最近

分母和分子不能同时为偶数
分母和分子 取值范围在[1-20]

package com.wang.base;

import java.util.Scanner;

public class Demo01 {
    public static void main(String[] args) {
        double diff = 1;
        int a = 0;
        int b = 0;
        for (int i = 1; i <= 20; i++) {
            for (int j = 1; j < i; j++) {
                double val = Math.abs((double)j/i - 0.618);
                if (val<diff){
                     a =i;
                     b =j;
                    diff=val;
                }
            }
        }
        System.out.println("最近的两个数是:"+b+"和"+a);
    }
}

寻找所有的水仙花数

水仙花数定义:
\1. 一定是3位数
\2. 每一位的立方,加起来恰好是这个数本身,比如153=111+555+333

寻找所有的水仙花数

package com.wang.base;

public class Demo02 {
    public static void main(String[] args) {
        int num1 = 0;
        System.out.println("水仙花数:");
        for (int i = 1; i <=9 ; i++) {
            for (int j = 0; j <=9; j++) {
                for (int k = 0; k <=9; k++) {
                    num1 = i*100+j*10+k;
                    if(num1==Math.pow(i,3)+Math.pow(j,3)+Math.pow(k,3))
                        System.out.print(num1+" ");
                }
            }
        }
    }
}

四元方程

package com.wang.base;

public class Demo03 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                for (int k = 0; k < 100; k++) {
                    for (int l = 0; l < 100; l++) {
                        if (i+j==8 && k-l==6 && i+k==14 && j+l==10)
                            System.out.println(i+" "+j+" "+k+" "+l);
                    }
                }
            }
        }
    }
}

import java.util.Scanner;
 
public class HelloWorld {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        boolean flag = true;
 
        do {
            System.out.print("第一行两数相加的和:");
            int a = scanner.nextInt();
            System.out.print("第二行两数相加的和:");
            int b = scanner.nextInt();
            System.out.print("第一列两数相加的和:");
            int c = scanner.nextInt();
            System.out.print("第二列两数相加的和:");
            int d = scanner.nextInt();
 
            int m, n, x, y;//四个数字从左到右从上到下
            for (m = a; m >= 0; m--) {
                n = a - m;
                x = c - m;
                y = x - b;
                if (y + n == d) {
                    System.out.println("四个数字从左到右从上到下为:" + m + " " + n + " " + x + " " + y + " ");
                    flag = false;
                    break;
                }
            }
            if (flag) {
                System.out.println("输入错误,请重新输入。");
            }
        } while (flag);
    }
}

选择排序法和冒泡排序法

package com.wang.base;

public class Demo04 {
    public static void main(String[] args) {
        int[] a= new int[10];
        for (int i = 0; i < a.length; i++)
            a[i] = (int)(Math.random()*100);
        print(a);

        //选择法正排序
        for(int i = 0;i<a.length-1;i++){
            for(int j= i+1;j<a.length;j++){
                if(a[j]<a[i]){
                    int temp = a[i];
                    a[i]=a[j];
                    a[j]=temp;
                }
            }
        }
        print(a);

        //冒泡法倒排序
        for(int i = 0;i<a.length-1;i++){
            boolean flag = false;
            for(int j=0;j<a.length-i-1;j++){
                if(a[j+1]>a[j]){
                    int temp = a[j];
                    a[j]= a[j+1];
                    a[j+1]=temp;
                    flag = true;
                }
            }
            if(!flag)
                break;
        }
        print(a);
    }

    public static int[] reverse(int[] a){
        for (int i = 0; i < a.length/2; i++) {
            int b = a[i];
            a[i] = a[a.length-i-1];
            a[a.length-i-1] = b;
        }
        return a;
    }

    public static void print(int[] a){
        for(int i:a)
            System.out.print(i+" ");
        System.out.println();
    }
}

二维数组排序

import java.util.Arrays;
 
public class HelloWorld {
    public static void main(String[] args) {
        int a[][] = new int[5][8];
        for (int i = 0; i < a.length; i++) {
            for (int j = 0; j < a[i].length; j++) {
                a[i][j] = (int) (Math.random() * 100);
            }
        }
 
        System.out.println("打印二维数组");
        for (int[] i : a) {
            System.out.println(Arrays.toString(i));
        }
 
        // 把二维数组复制到一维数组
        int b[] = new int[a.length * a[0].length];
        for (int i = 0; i < a.length; i++) {
            System.arraycopy(a[i], 0, b, i * a[i].length, a[i].length);
        }
        // 对一维数组进行排序
        Arrays.sort(b);
        // 把一维数组复制到二维数组
        for (int i = 0; i < a.length; i++) {
            System.arraycopy(b, a[i].length * i, a[i], 0, a[i].length);
        }
        System.out.println("打印排序后的二维数组");
        for (int[] i : a) {
            System.out.println(Arrays.toString(i));
        }
 
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值