java常识准备:

发布时间 2023-08-15 14:26:31作者: #岛

java基础

1.常识准备:

内存以字节形式存在,一个字节占八位

字符型数据占两个字节

整形,单精度浮点数占四个字节

双精度浮点数占八个字节

Math常用方法:

Math.ceil()//只要有小数就+1

Math.floor()//不取小数

Math.round()//四舍五入

String常用方法:

String.builder().reserve().toString()//字符串反转

常见的数据存储

栈内存:存放基本数据类型,对象的引用和方法调用遵循先入后出原则

堆内存:存放所有new出来的对象和数组(堆内存与数据结构的堆完全两回事,分配类似于链表 )

常量池:存放基本类型常量和字符串常量(public,static,final)

静态域:存放静态成员(static)

非RAM存储:硬盘等

2.数据结构以及运算符

常量:final 变量

变量:1.类成员变量(在类中声明,作用于该类)2.局部变量(在方法中声明,只在当前代码中有效)3.方法参数变量(作用于整个方法)

位运算符:

&(与运算符,先将整数化为二进制数再进行运算)

|(或运算符,先将整数化为二进制数再进行运算)

^(按位异域,运算两个二进制相同结果为零,否则为一)

<<,>>(左,右二进制移位,eg:a>>1)

~(按位取反)

java基础

3.程序控制语句

1.break实现goto功能(java中并没有goto语句):

eg:……one{

tow{

if(……)

break one;//结束直接跳转到one层

}

}

continue语句:强制跳过本次循环而进行下次循环

4.类,对象,方法

一个类可以包含以下类型变量

1.局部变量:

定义在方法,构造方法或者语句块中的变量。当该方法被执行时局部变量被创建,方法结束后变量自动销毁,作用范围仅限该方法内。(构造方法名称与类同名,一个类 可以有多个构造方法)

2.成员变量:

定义在方法外,类内的变量。在创建对象的时候被实例化,可以被类中的方法,构造方法和特定类的语句块访问

3.类变量(静态变量):

也在类内方法体之外,指声明为public/private,final和static类型的变量。无论类有多少个对象,类变量只有一份拷贝。在第一次被访问时创建,在程序结束时销毁

修饰符

default:同一包内可见。使用对象:类,接口,变量,方法

private:同一类内可见。使用对象:变量,方法

public:对所有类可见。使用对象:类,接口,变量,方法

protected:对同一包内的类和所有子类可见。使用对象:变量,方法

Number&Math类

包装类

包装类(Integer ,Long,Byte,Double,Float,Short)是抽象类Number的子类,将内部数据类型当做对象使用,称为装箱,相似的也有拆箱的操作

Math类:

包含执行基本数学运算的属性和方法(如指数,对数,平方根,三角函数)

xxxValue():将number对象转换为xxx数据类型的值并返回

equals():判断是否相等

valueOf():返回一个Number对象指定的内置数据类型

toString():返回字符串类型值

parseInt():返回整形值

abs():绝对值

ceil():返回>=给定数的最小整数

floor():返回<=给定数的最大整数

rint():返回与给定数最接近的整数

round():四舍五入

min()/max():返回两个数最小/大值

sqrt():算术平方根

pow():返回第一个参数的第二个参数的次方

random():随机数

Character类

转义字符

\t:插入tab键

\b:插入后退键

\n:换行

\r:插入回车

Character方法:

isLetter():是否是字母

isDigit():是否是一个数字

isWhitespac():是否是一个空白字符

isUpperCase():是否大写字母

isLowerCase():是否小写字母

toUpperCase():指定大写字母

toLowerCase():指定小写字母

toString():返回字符的字符串形式,字符长度仅为1

String类

String类是不可变得,一旦创建了String对象,那他的值就不可变如果需要对字符串做很多修改,应该选择使用StringBuffer&StringBuilder

将整数转化为字符串方法:

1.String str=String.valueOf(int x);

2.String str=Integer.toString(int x);

将字符串转数字:

转整形:1.int x=Integer.parseInt(String s);

​ 2.int x=Integer.valueOf(String s);

转浮点型:float n=Float.parseFloat(String s)

常用方法:

连接字符串:String1.concat(String2)/使用+号

返回索引指定的char值:char charAt(int index)

按字典顺序比较两个字符串 :String compareTo(String anotherString)

字符串与指定StringBuffer有共同顺序的时候返回true:boolean contentEquals(StringBuffer sb)

两个字符串进行比较:boolean equalslgnoreCase(String anotherString)

将所有的字符转换成大写:String toUpperCase();

String类操作字符串的方法

1.boolean equals(str)------判断两字符串是否一样

2.intl ength()------------------计算字符串长度

3.char charAt(index)-------索引字符串指定第index个字符的位置

4.int compare To(str)----------比较两字符串大小,若所调用字符串大于str返回一个大于0的值,小于str返回一个小于0的值等于返回0

5.int indexOf(str)---------------在调用字符串查找str指定的字符串,返回第一个匹配的索引值,没有匹配返回-1

6.int lastIndexOf(str)----------在调用字符串查找str指定的字符串,返回最后一个匹配的索引值,没有匹配返回-1(注意与indexOf的区别)

equals()方法是比较两个字符串是否相等,而==是来判断连个字符串是否指的是同一个对象

StringBuffer与StringBuilder类

String对象的内容是不可变的,而StringBuffer的类所创建的对象是可变的,即setCharAt()方法

Scanner的几种注意事项

hasNext()
判断扫描器中当前扫描位置后是否还存在下一段。
hasNextLine()
如果在此扫描器的输入中存在另一行,则返回 true。
next()
查找并返回来自此扫描器的下一个完整标记。
nextLine()
此扫描器执行当前行,并返回跳过的输入信息。
nextInt()
将控制台扫描的整形数据返回。
next()一定要读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键、Tab键或Enter键等结束符,next()方法会自动将其去掉,只有在输入有效字符之后,next()方法才将其后输入的空格键、Tab键或Enter键等视为分隔符或结束符。
简单地说,next()查找并返回来自此扫描器的下一个完整标记。完整标记的前后是与分隔模式匹配的输入信息,所以next方法不能得到带空格的字符串。
而nextLine()方法的结束符只是Enter键,即nextLine()方法返回的是Enter键之前的所有字符,它是可以得到带空格的字符串的。

数组

增强for循环: //注意for-each循环只能用来遍历,不能改变其原有值

for(数据类型 元素迭代变量名:循环遍历的集合名);

eg(遍历二维数组):

int a[][]=new int[10][10]

for(int x[]:a){

for(int y:x[])

}

Arrays类

Arrays.sort(Object a[]):按升序排序

Arrays.equals(Object a[],Object b[]):比较数组是否相等

Arrays.binarySearch(Object a[],Object key):用二分查找法在给定数组中搜索给定值的对象key,数组在调用前必须排序好,如果查找值包含在数组中,则返回搜索键的索引,否则返回-1.

日期

包含于java.util包

使用构造函数初始化对象:Date() 或者构造函数接收参数Date(long m)

获取当前日期:Date date=new Date(); date.toString();

日期比较:before(),after(),equals()。如new Date(99.5.18).before(new Date(00,4,10))返回true

使用SimpleDateFormat格式化日期

Date date=new Date();

SimpleDateFormat ft =new SimpleDateFormat("yyyy-mm-dd hh:mm:ss");

ft.format(date);

解析字符串为时间(parse())

String input="2000-04-10";

SimpleDateFormat ft=new SimpleDateFormat("yyyy-mm-dd");

Date t=ft.parse(input);

Java休眠(sleep()):

阻塞当前线程 Thread.sleep(1000*3);

方法

方法重载

一个类中创建另一个相同名字但参数不同的方法

方法重写

子类方法对父类方法的重新

流(Stream),文件(File)和IO

Stream:可以理解为一个数据的序列,输入流标识从一个源读取数据,输出流表示向一个目标写数据

image-20230213172453112

字符流与字节流区别

字节流是直接操作文件本身的,字符流使用了缓冲区。

字符流好还是字节流好

使用字节流更好,因为所有文件在硬盘或传输时都是以字节方式进行的,字符只有在内存中才会形成

FileInputStream

该流用于从文件读取/写入数据

image-20230213183946661

InputStream f=new FileInputStream("c:/java/hello");

或者File f=new File("c:/java/hello");

InputStream in=new FileInputStream(f);

in.read(); //读取流,读取文件f的内容

in.close(); //关闭流

FileOutputStream

OutputStream o=new FileOutputStream("c:/java/hello");

或者

byte[] b={11,22,33};

File f=new File("c:/java/hello");

OutputStream o=new FileOutputStream(f);

o.write(b); //将b写进文件f里面

o.close();

创建目录:

String d="/tmp/user/java/bin";

File f=new File(d);

f.mkdirs(); //创建目录

读取目录:

String dnanme="/temp";

File f=new File(s);

String[] s=f.list(); //提取包含的文件和文件夹列表

删除文件:

需要保证该目录下没有其他文件才能正确删除,否则删除失败

String dnanme="/temp";

File f=new File(s);

deleteFolder(folder); //删除文件

f.delete(); //删除目录

Scanner

创建Scanner的对象:

Scanner sc=new Scanner(System.in);

使用该对象,通过如下方法实现各个数据类型的输入

nextLine();接受字符串,返回输入回车之前所有的字符,可以获得空白字符。

next():自动去掉空白符,只有输入有效字符才可以输入结束。

sc.hasNextInt(); //判断输入是否是int型,是返回true,否则返回fall

异常处理

image-20230213194704847

异常 描述
ArithmeticException 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException 试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException 当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException 线程没有处于请求操作所要求的适当状态时抛出的异常。
IndexOutOfBoundsException 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException 如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException 当应用程序试图在需要对象的地方使用 null 时,抛出该异常
NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
SecurityException 由安全管理器抛出的异常,指示存在安全侵犯。
StringIndexOutOfBoundsException 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException 当不支持请求的操作时,抛出该异常。

下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。

异常 描述
ClassNotFoundException 应用程序试图加载类时,找不到相应的类,抛出该异常。
CloneNotSupportedException 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException 拒绝访问一个类的时候,抛出该异常。
InstantiationException 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException 一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException 请求的变量不存在
NoSuchMethodException 请求的方法不存在

finally

无论是否发生异常,finally中的代码总会被执行

java面向对象

继承

注意:

继承不支持多继承,即一个子类不能同时有两个父类。使用关键字extend来继承父类

接口实现可以实现多继承特性,一个类可以实现多个接口。使用关键字implement来实现接口

继承特性:

  • 子类可以使用父类的非private的属性,方法

  • 子类可以对父类进行扩展,用自己的方式实现父类的方法

super与this

super:通过此关键字实现对父类成员的访问,用来引用当前对象的父类

this:指向自己的引用

public class Main {
    public static void main(String[] args) {
cat cat=new cat();
cat.eatTest();
    }
}
class animal{
    void eat1(){
        System.out.println("animal:eat");
    }
}
class cat extends animal{
    @Override
    void eat(){
        System.out.println("cat:eat");
    }
    void eatTest(){
        this.eat();
        super.eat1();
    }
}

构造器:

作用:

创建对象时初始化。当创建完一个对象,系统会对这个对象的实例进行默认的初始化,如果想改变这种默认的初始化,就要自己通过创建构造器来实现。

如果父类的构造器(构造函数或构造方法)带有参数,子类需要调用时需要用到super关键字,如果父类构造器没有参数子类直接调用即可。

重写与重载

重写:

子类对父类允许访问的方法(主要根基类的修饰符public,private等)的重新编写,返回值与参数都不能变。即外壳不变,核心重写。

注意:

animal cat=new cat();  //cat是animal 类型的,cat类的对象
cat.eat();   //编译时只会会检查引用类型,检查animal类型是否有eat()方法,但是在运行时jvm虚拟机运行的是特定对象cat类的方法

构造方法不能被重写

声明finall的方法不能被重写

声明为static的方法不能被重写,但能够被再次声明

重载:

重载是在一个类里面,方法名字相同,参数不同,返回类型可以相同也可以不相同

方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

image-20230214185903703

image-20230214190109767

多态

多态是一个行为具有多个不同表现形式或形态的能力,即一个接口使用不同的实例而执行不同的操作

多态存在的三个条件:

继承 重写 父类引用指向子类对象(Parent p=new Child()?

向上转型:

父类引用指向子类对象 ,语法如:Parent p=new Child();

把子类对象直接赋值给父类引用,不用强制转换。

注意:向上转型对象不能访问子类特有的方法和属性,只能访问对父类重写的方法和父类拥有的属性

多态的实现方式:

  • 重写
  • 接口
  • 抽象类与抽象方法

抽象类

抽象类没有足够的信息来描绘一个具体的对象,不能实例化一个对象,所有必须被继承才能被使用。使用abstract class关键字来定义抽象类。

抽象方法:

public abstract class Employee {

private String name;

private String address;

private int number;

public abstract double computePay();

//其余代码

}

声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

封装

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类代码必须通过严格的接口控制。主要功能在于我们修改自己的实现代码,而不用修改那些调用我们的代码的程序片段。

class Person{
    private String name; //属性设置为私有,只有在本类内才可以被访问
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

接口

是抽象方法的集合不是类,接口通常以interface来声明。一个类通过继承接口的方式从而继承接口的抽象方法。

接口与类的区别:

  • 接口不能实例化对象
  • 接口没有构造方法
  • 接口不能包含成员变量,除了static和final变量
  • 接口不能被类继承,而是被类实现
  • 接口支持多继承
public interface Animal {
    public void eat();
    public void run();
}
public class Dog implements Animal {//类实现接口必须实现接口中所有的方法,否则类必须声明为抽象的类
    @Override
    public void eat() {

    }

    @Override
    public void run() {

    }
}

接口的继承:

一个接口是可以继承另一个接口的

public interface Animal {
    public void eat();
    public void run();
}
 interface Cat extends Animal{
    public void travel();
}
public class Dog implements Cat {
    @Override
    public void eat() {

    }

    @Override
    public void run() {

    }

    @Override
    public void travel() {

    }
}

枚举

枚举是一个特殊类,一般表示一组常量。如一年的四个季节,一个星期的7天,方向的东南西北。枚举使用enum关键字来定义,各个常量用“,”分开

enum Color{
    Red,Yellow,Green;
}
public class Main {
    public static void main(String[] args) {
        Color c=Color.Red;
        System.out.println(c);

    }
}

枚举也可以声明在内部类中

public class Main {
    enum Color{
        Red,Yellow,Green;
    }

    public static void main(String[] args) {
        Color c=Color.Red;
        System.out.println(c);

    }
}

增强for循环来迭代枚举:

public class Main {
    enum Color{
        Red,Yellow,Green;
    }

    public static void main(String[] args) {
        for (Color c:Color.values()){
            System.out.println(c);
        }

    }
}
  • values()返回枚举中的所有的值;
  • ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样
  • valueOf()返回指定字符串值的枚举常量
enum Color
{
    RED, GREEN, BLUE;
}

 class Test
{
    public static void main(String[] args)
    {
        // 调用 values()
        Color[] arr = Color.values();

        // 迭代枚举
        for (Color col : arr)
        {
            // 查看索引
            System.out.println(col + " at index " + col.ordinal());
        }

        // 使用 valueOf() 返回枚举常量,不存在的会报错 IllegalArgumentException
        System.out.println(Color.valueOf("RED"));
        // System.out.println(Color.valueOf("WHITE"));
    }
}

包的作用:

把功能相似的或相关的类或接口组织在同一个包中,方便类的查找和使用

包限定了访问权限,拥有包访问权限的类才能访问某个包中的类

java高级

多线程

基本概念:

​ 程序:为完成特定任务,用某种语言编写的一组指令的集合

​ 进程:程序的一次执行的过程,或者正在运行的一个程序

​ 线程:程序进一步细化线程,一条程序内部的一条执行路径

创建多线程

方法一

(继承Thread,此方法不能再继承其他类,有一定局限性):

java.lang.Thread类创建多线程:

class MyThread extends Thread{
public void run(){

//此方法中编写线程所需要的实现的功能

}

}
public class void ThreadTest{
pubic static void main(String[] arg){
MyThread mh=new MyThread();
mh.start();//执行start()方法线程处于就绪状态,获取cpu资源后执行run();方法处于执行状态
}
}

注意:

​ 想要获得多个线程,就造多个对象去调start()方法

​ 线程可能出现的阻塞状态:1.等待阻塞:运行状态执行wait()方法

​ 2.同步阻塞:线程在获取synchronized同步 锁失败(同步锁被其他线程占用)

​ 3.其他阻塞:调用线程调用sleep()或者join()发出I/o请求时,线程进入阻塞状态。sleep()状态超时,join()等待线程终止或者超时,或者i/o处理完毕,线程重新转入就绪状态

匿名内部类来实现创建多个线程

public class MyThread{

new Thread(){

//线程一需要完成的功能

}.start();

new Thread(){

//线程二需要完成的功能

}.start();

}

方法二:

实现Runnable接口创建线程(优先考虑,天然共享数据,可继承其他类)

class myThread implement Runnable{

public void run(){

//重写run方法,实现线程所要实现的功能

}

}

class ThreadTest{

myThread mt=new myThread();//创建一个可运行的对象

Thread t=new Threat(mt);//在该对象上构造一个线程

t.start();//运行线程

}

改进创建多个线程:

class MyThread implement Runnable{

Thrread t;

MyThread(String name){

t=new Thread(name);

t.start();

}

public void run(){

}

}

class UseThread {

public static void main(String arg[]){

MyThraed m1=new MyThread("name1");

MyThraed m1=new MyThread("name2");

MyThraed m1=new MyThread("name3");

}

}

方法三:

通过Callable和Future创建线程

public class CallableThreadTest implement Callable{

public static void main(String arg[]{

CallableThreadTest ct=new CallableThreadTest();//创建callable实例

FutureTask ft=new FutureTask<>(ct);//使用FutureTask类来包装Callable对象

new Thread(ft,"").start;

public Integer call() throws Exception{}//实现call()方法

})

}

线程常用的方法

1.start():启动当前线程,调用当前线程的run()

2.run():通常需要重写Thread类中的方法,

3.currentThread();静态方法,返回当前代码的线程

5.setName();设置当前线程的名字;

线程的生命周期

image-20220312163157819

线程安全

当一个线程未执行完毕,下一个线程已经开始执行可能会造成线程安全问题,以下是几种解决线程安全的方案

1.同步代码块

synchronized(同步监视器(锁)){

//需要被同步的代码(即操作共享数据的代码)

//同步监视器要求多线程必须要共用同一把锁,锁即是任何一个类的对象(实际开发中用this来获取当前对象,不需要再另创建对象了,但继承方式来创建多线程不能用this,要用类名.class才是同一个锁)

}

该方法解决了线程安全问题,但是单线程的过程效率低

注意使用该方法解决继承Thread类的多线程安全时,创建多个对象也就同时创建多个锁,此时锁不是唯一的,需要用static来修饰类的对象(锁)

2.同步方法

该方法用synchronized修饰

实现runnable接口的多线程设锁:

private synchronized void 自定义方法名(){需要监视的数据操作}

继承Thread类的多线程设锁:

private static synchronized void 自定义方法名(){需要监视的数据操作}

注意:同步方法仍然涉及到同步监视器,知识不需要我们显示的声明

​ 非静态的同步方法,同步监视器是this,静态的同步方法的同步监视器是类本身

线程通信

线程死锁

线程控制:挂起,停止和恢复

java数据结构

  • 枚举(Enumeration)
  • 位集合(BitSet)
  • 向量(Vector)
  • 栈(Stack)
  • 字典(Dictionary)
  • 哈希表(Hashtable)
  • 属性(Properties

枚举:

接口定义了一种从数据结构中取回连续元素的方式

主要方法有:

序号 方法描述
1 boolean hasMoreElements( ) 测试此枚举是否包含更多的元素。
2 Object nextElement( ) 如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。
public class EnumerationTester {

    public static void main(String args[]) {
        Enumeration<String> days;
        Vector<String> dayNames = new Vector<String>();
        dayNames.add("Sunday");
        dayNames.add("Monday");
        dayNames.add("Tuesday");
        dayNames.add("Wednesday");
        dayNames.add("Thursday");
        dayNames.add("Friday");
        dayNames.add("Saturday");
        days = dayNames.elements();
        while (days.hasMoreElements()){
            System.out.println(days.nextElement());
        }
    }
}

位集合(bitSet)

实现了一组可以单独设置和清除的位或标志。该类在处理一组布尔值的时候非常有用,只需要给每一个赋值值一位,然后对位进行适当的设置或清除,就可以对布尔值进行操作了

向量Vector

该类和ArrayList非常相似动态数组,但是该类是同步的,好处是创建对象时不必给对象指定大小,大小会根据需要动态的变化

几种向量的构造方法:

Vector v=new Vector();
Vector v=new Vector(int size);//指定大小的向量
Vector v=new Vector(int size,int incr);//指定大小,并增量用incr指定,每次增加incr
Vector v=new Vector(Collection c);//构造方法创建一个包含集合c的向量
序号 方法描述
1 void add(int index, Object element) 在此向量的指定位置插入指定的元素。
2 boolean add(Object o) 将指定元素添加到此向量的末尾。
3 boolean addAll(Collection c) 将指定 Collection 中的所有元素添加到此向量的末尾,按照指定 collection 的迭代器所返回的顺序添加这些元素。
4 boolean addAll(int index, Collection c) 在指定位置将指定 Collection 中的所有元素插入到此向量中。
5 void addElement(Object obj) 将指定的组件添加到此向量的末尾,将其大小增加 1。
6 int capacity() 返回此向量的当前容量。
7 void clear() 从此向量中移除所有元素。
8 Object clone() 返回向量的一个副本。
9 boolean contains(Object elem) 如果此向量包含指定的元素,则返回 true。
10 boolean containsAll(Collection c) 如果此向量包含指定 Collection 中的所有元素,则返回 true。
11 void copyInto(Object[] anArray) 将此向量的组件复制到指定的数组中。
12 Object elementAt(int index) 返回指定索引处的组件。
13 Enumeration elements() 返回此向量的组件的枚举。
14 void ensureCapacity(int minCapacity) 增加此向量的容量(如有必要),以确保其至少能够保存最小容量参数指定的组件数。
15 boolean equals(Object o) 比较指定对象与此向量的相等性。
16 Object firstElement() 返回此向量的第一个组件(位于索引 0) 处的项)。
17 Object get(int index) 返回向量中指定位置的元素。
18 int hashCode() 返回此向量的哈希码值。
19 int indexOf(Object elem) 返回此向量中第一次出现的指定元素的索引,如果此向量不包含该元素,则返回 -1。
20 int indexOf(Object elem, int index) 返回此向量中第一次出现的指定元素的索引,从 index 处正向搜索,如果未找到该元素,则返回 -1。
21 void insertElementAt(Object obj, int index) 将指定对象作为此向量中的组件插入到指定的 index 处。
22 boolean isEmpty() 测试此向量是否不包含组件。
23 Object lastElement() 返回此向量的最后一个组件。
24 int lastIndexOf(Object elem) 返回此向量中最后一次出现的指定元素的索引;如果此向量不包含该元素,则返回 -1。
25 int lastIndexOf(Object elem, int index) 返回此向量中最后一次出现的指定元素的索引,从 index 处逆向搜索,如果未找到该元素,则返回 -1。
26 Object remove(int index) 移除此向量中指定位置的元素。
27 boolean remove(Object o) 移除此向量中指定元素的第一个匹配项,如果向量不包含该元素,则元素保持不变。
28 boolean removeAll(Collection c) 从此向量中移除包含在指定 Collection 中的所有元素。
29 void removeAllElements() 从此向量中移除全部组件,并将其大小设置为零。
30 boolean removeElement(Object obj) 从此向量中移除变量的第一个(索引最小的)匹配项。
31 void removeElementAt(int index) 删除指定索引处的组件。
32 protected void removeRange(int fromIndex, int toIndex) 从此 List 中移除其索引位于 fromIndex(包括)与 toIndex(不包括)之间的所有元素。
33 boolean retainAll(Collection c) 在此向量中仅保留包含在指定 Collection 中的元素。
34 Object set(int index, Object element) 用指定的元素替换此向量中指定位置处的元素。
35 void setElementAt(Object obj, int index) 将此向量指定 index 处的组件设置为指定的对象。
36 void setSize(int newSize) 设置此向量的大小。
37 int size() 返回此向量中的组件数。
38 List subList(int fromIndex, int toIndex) 返回此 List 的部分视图,元素范围为从 fromIndex(包括)到 toIndex(不包括)。
39 Object[] toArray() 返回一个数组,包含此向量中以恰当顺序存放的所有元素。
40 Object[] toArray(Object[] a) 返回一个数组,包含此向量中以恰当顺序存放的所有元素;返回数组的运行时类型为指定数组的类型。
41 String toString() 返回此向量的字符串表示形式,其中包含每个元素的 String 表示形式。
42 void trimToSize() 对此向量的容量进行微调,使其等于向量的当前大小。

栈Stack

栈是Vector的一个子类,它实现了一个标准的后进先出的栈。

堆栈只定义了默认构造函数,用来创建一个空栈。 堆栈除了包括由Vector定义的所有方法,也定义了自己的一些方法。

除了由Vector定义的所有方法,自己也定义了一些方法:

序号 方法描述
1 boolean empty() 测试堆栈是否为空。
2 Object peek( ) 查看堆栈顶部的对象,但不从堆栈中移除它。
3 Object pop( ) 移除堆栈顶部的对象,并作为此函数的值返回该对象。
4 Object push(Object element) 把项压入堆栈顶部。
5 int search(Object element) 返回对象在堆栈中的位置,以 1 为基数。

字典(Dictionary)

已过时,可以用Map接口来获取键值的存储

是一个抽象类,定义了键映射到值的数据结构。通过特定的键而不是整数索引访问数据。

序号 方法描述
1 Enumeration elements( ) 返回此 dictionary 中值的枚举。
2 Object get(Object key) 返回此 dictionary 中该键所映射到的值。
3 boolean isEmpty( ) 测试此 dictionary 是否不存在从键到值的映射。
4 Enumeration keys( ) 返回此 dictionary 中的键的枚举。
5 Object put(Object key, Object value) 将指定 key 映射到此 dictionary 中指定 value。
6 Object remove(Object key) 从此 dictionary 中移除 key (及其相应的 value)。
7 int size( ) 返回此 dictionary 中条目(不同键)的数量。

哈希表(Hashtable)

与HashMap相似,却支持同步,与HashMAp一样存放的是键值对,当时用一个哈希表时,要指定键的对象以及链接到该键的值,然后经过哈希处理,得到的散列码被用作存储在该表中值的索引

有四种构造方法:

Hashtable();
Hashtable(int size);  //创建指定大小的哈希表
Hashtable(int size,float fillRatio);//创建指定大小的哈希表并指定填充比例
Hashtable(Map m);

序号 方法描述
1 void clear( ) 将此哈希表清空,使其不包含任何键。
2 Object clone( ) 创建此哈希表的浅表副本。
3 boolean contains(Object value) 测试此映射表中是否存在与指定值关联的键。
4 boolean containsKey(Object key) 测试指定对象是否为此哈希表中的键。
5 boolean containsValue(Object value) 如果此 Hashtable 将一个或多个键映射到此值,则返回 true。
6 Enumeration elements( ) 返回此哈希表中的值的枚举。
7 Object get(Object key) 返回指定键所映射到的值,如果此映射不包含此键的映射,则返回 null. 更确切地讲,如果此映射包含满足 (key.equals(k)) 的从键 k 到值 v 的映射,则此方法返回 v;否则,返回 null。
8 boolean isEmpty( ) 测试此哈希表是否没有键映射到值。
9 Enumeration keys( ) 返回此哈希表中的键的枚举。
10 Object put(Object key, Object value) 将指定 key 映射到此哈希表中的指定 value。
11 void rehash( ) 增加此哈希表的容量并在内部对其进行重组,以便更有效地容纳和访问其元素。
12 Object remove(Object key) 从哈希表中移除该键及其相应的值。
13 int size( ) 返回此哈希表中的键的数量。
14 String toString( ) 返回此 Hashtable 对象的字符串表示形式,其形式为 ASCII 字符 ", " (逗号加空格)分隔开的、括在括号中的一组条目。
public class HashTableDemo {

   public static void main(String args[]) {
      // Create a hash map
      Hashtable balance = new Hashtable();
      Enumeration names;
      String str;
      double bal;

      balance.put("Zara", new Double(3434.34));
      balance.put("Mahnaz", new Double(123.22));
      balance.put("Ayan", new Double(1378.00));
      balance.put("Daisy", new Double(99.22));
      balance.put("Qadir", new Double(-19.08));

      // Show all balances in hash table.
      names = balance.keys();
      while(names.hasMoreElements()) {
         str = (String) names.nextElement();
         System.out.println(str + ": " +
         balance.get(str));
      }
      System.out.println();
      // Deposit 1,000 into Zara's account
      bal = ((Double)balance.get("Zara")).doubleValue();
      balance.put("Zara", new Double(bal+1000));
      System.out.println("Zara's new balance: " +
      balance.get("Zara"));
   }
}

属性(Properties)

表示列表中每个键及其对应值都是一个字符串

序号 方法描述
1 String getProperty(String key) 用指定的键在此属性列表中搜索属性。
2 String getProperty(String key, String defaultProperty) 用指定的键在属性列表中搜索属性。
3 void list(PrintStream streamOut) 将属性列表输出到指定的输出流。
4 void list(PrintWriter streamOut) 将属性列表输出到指定的输出流。
5 void load(InputStream streamIn) throws IOException 从输入流中读取属性列表(键和元素对)。
6 Enumeration propertyNames( ) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
7 Object setProperty(String key, String value) 调用 Hashtable 的方法 put。
8 void store(OutputStream streamOut, String description) 以适合使用 load(InputStream)方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。
public class PropDemo {

    public static void main(String args[]) {
        Properties capitals = new Properties();
        Set states;
        String str;

        capitals.put("Illinois", "Springfield");
        capitals.put("Missouri", "Jefferson City");
        capitals.put("Washington", "Olympia");
        capitals.put("California", "Sacramento");
        capitals.put("Indiana", "Indianapolis");

        // Show all states and capitals in hashtable.
        states = capitals.keySet(); // get set-view of keys
        Iterator itr = states.iterator();
        while(itr.hasNext()) {
            str = (String) itr.next();
            System.out.println("The capital of " +
                    str + " is " + capitals.getProperty(str) + ".");
        }
        System.out.println();

        // look for state not in list -- specify default
        str = capitals.getProperty("Florida", "Not Found");
        System.out.println("The capital of Florida is "
                + str + ".");
    }
}

集合框架

image-20230215194140215

集合框架是一个用来代表和操纵集合的统一架构,所有集合架构都包含如下内容:

  • 接口:代表抽象数据类型
  • 实现类:集合接口的具体表现,本质上讲是可重复使用的数据结构,如ArrayList,LinkList,HashSet,HashMap。
  • 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

image-20230216154631313

集合接口:

序号 接口描述
1 Collection 接口 Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。Collection 接口存储一组不唯一,无序的对象。
2 List 接口 List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。List 接口存储一组不唯一,有序(插入顺序)的对象。
3 Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组唯一,无序的对象。
4 SortedSet 继承于Set保存有序的集合。
5 Map Map 接口存储一组键值对象,提供key(键)到value(值)的映射。
6 Map.Entry 描述在一个Map中的一个元素(键/值对)。是一个 Map 的内部接口。
7 SortedMap 继承于 Map,使 Key 保持在升序排列。
8 Enumeration 这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。

List

java集合框架主要包括两种容器,一种集合(Collection),一种图(Map)

collection下主要接口有list(linklist,Arraylist),set(HashSet),queue

Set和List的区别

  • \1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
  • \2. Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>
  • \3. List 和数组类似,可以动态增长,根据实际存储的数据的长度自动增长 List 的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector>

ArrayList

在java.util包中,该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低。

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。
方法 描述
add() 将元素插入到指定位置的 arraylist 中
addAll() 添加集合中的所有元素到 arraylist 中
clear() 删除 arraylist 中的所有元素
clone() 复制一份 arraylist
contains() 判断元素是否在 arraylist
get() 通过索引值获取 arraylist 中的元素
indexOf() 返回 arraylist 中元素的索引值
removeAll() 删除存在于指定集合中的 arraylist 里的所有元素
remove() 删除 arraylist 里的单个元素
size() 返回 arraylist 里元素数量
isEmpty() 判断 arraylist 是否为空
subList() 截取部分 arraylist 的元素
set() 替换 arraylist 中指定索引的元素
sort() 对 arraylist 元素进行排序
public class ListTest {
    public static void main(String[] args) {
        ArrayList<String> a=new ArrayList<String>();
        a.add("dsm");//添加
        a.add("tzf");
        a.add("I");
        a.add("LOVE");
        a.add("YOU");
        System.out.println(a);
        a.set(1,"tzf1");  //修改
        System.out.println(a);
        a.remove(1);  //删除
        System.out.println(a);
        System.out.println(a.size());//大小
        Collections.sort(a);//排序
        System.out.println("Collection:"+a);
        a.sort(Comparator.naturalOrder());//元素升序排序
        System.out.println("升序:"+a);



    }
}

LinkLedist

该类实现了List接口,允许有null(空)元素。

主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。

例如:

List list=Collections.synchronizedList(newLinkedList(...));
  • LinkedList 实现了 Queue 接口,可作为队列使用。
  • LinkedList 实现了 List 接口,可进行列表的相关操作。
  • LinkedList 实现了 Deque 接口,可作为队列使用。
  • LinkedList 实现了 Cloneable 接口,可实现克隆。

LinkedList 查找效率低。

链表的简单创建

LinkList L=new LinkList();

链表的添加:

L.add("***");

L.addFirst("***")//添加到首节点

L.addLast("***");//添加到为节点;

L.add(int index,E element)//指定位置插入

链表的删除:

L.removeFirst();//移除首部元素

L.removeLast();//移除尾部元素

L.remove(int index)//移除指定位置元素

获取头部元素

L.getFirst();

L.getLast();

L.get(int index);//获取指定位置的元素

方法 描述
public boolean add(E e) 链表末尾添加元素,返回是否成功,成功为 true,失败为 false。
public void add(int index, E element) 向指定位置插入元素。
public boolean addAll(Collection c) 将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。
public void addFirst(E e) 元素添加到头部。
public void addLast(E e) 元素添加到尾部。
public boolean offer(E e) 向链表末尾添加元素,返回是否成功,成功为 true,失败为 false。
public boolean offerFirst(E e) 头部插入元素,返回是否成功,成功为 true,失败为 false。
public boolean offerLast(E e) 尾部插入元素,返回是否成功,成功为 true,失败为 false。
public void clear() 清空链表。
public E removeFirst() 删除并返回第一个元素。
public E removeLast() 删除并返回最后一个元素。
public boolean remove(Object o) 删除某一元素,返回是否成功,成功为 true,失败为 false。
public E remove(int index) 删除指定位置的元素。
public E poll() 删除并返回第一个元素。
public E remove() 删除并返回第一个元素。
public E get(int index) 返回指定位置的元素。
public E getFirst() 返回第一个元素。
public E getLast() 返回最后一个元素。
public int indexOf(Object o) 查找指定元素从前往后第一次出现的索引。
public int lastIndexOf(Object o) 查找指定元素最后一次出现的索引。
public E peek() 返回第一个元素。
public E element() 返回第一个元素。
public E peekFirst() 返回头部元素。
public E peekLast() 返回尾部元素。
public E set(int index, E element) 设置指定位置的元素。
public int size() 返回链表元素个数。
public T[] toArray(T[] a) 返回一个由链表元素转换类型而成的数组。

HashSet

HashSet基于HashMap实现的实现的

不允许有重复元素的集合,允许有null值

是无序的不会记录插入顺序,并不是线程安全的,如果多个线程同时修改HashSet最终结果是不确定的,必须在多线程访问时显示同步对HashSet的并发访问

public class ListTest {
    public static void main(String[] args) {
        HashSet<String> s = new HashSet<>();
        s.add("T");
        s.add("Z");//添加元素
        s.add("F");
        s.add(".");
        System.out.println(s.contains("T"));//判断元素是否存在
s.remove(".");//删除元素
        System.out.println(s.size());//容器大小
        for (String i:s){ //迭代
            System.out.println(i);
        }
    }
}

HashMap

是一个散列表,存储内容是键值对(key-value)映射。

最多允许一条记录的建为NULL,不支持线程同步

时无序的,不会记录插入的顺序

public class ListTest {
    public static void main(String[] args) {
        HashMap<Integer,String> map = new HashMap<Integer,String>();
        map.put(1,"t");//添加数据
        map.put(2,"z");
        map.put(3,"f");
        map.put(4,".");
        System.out.println(map.get(1));//访问数据
        map.remove(4);
        System.out.println(map.size());
        for (int i:map.keySet()){  //所有的key值
            System.out.println("key:"+i+"value:"+map.get(i));
        }
        for (String s:map.values()){//所有的value值
            System.out.println(s);
        }


    }
}
方法 描述
clear() 删除 hashMap 中的所有键/值对
clone() 复制一份 hashMap
isEmpty() 判断 hashMap 是否为空
size() 计算 hashMap 中键/值对的数量
put() 将键/值对添加到 hashMap 中
putAll() 将所有键/值对添加到 hashMap 中
remove() 删除 hashMap 中指定键 key 的映射关系
containsKey() 检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue() 检查 hashMap 中是否存在指定的 value 对应的映射关系。
replace() 替换 hashMap 中是指定的 key 对应的 value。
replaceAll() 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。
get() 获取指定 key 对应对 value
forEach() 对 hashMap 中的每个映射执行指定的操作。
keySet() 返回 hashMap 中所有 key 组成的集合视图。
values() 返回 hashMap 中存在的所有 value 值。
compute() 对 hashMap 中指定 key 的值进行重新计算

Iterator(迭代器)

用于访问集合的方法,可以用来迭代ArrayLiast和HashSet等集合

it.next()返回迭代器下一个元素

it.hashNext()检测集合中是否还有元素

it.remove()将迭代器返回的元素删除

import java.util.*;

public class ListTest {
    public static void main(String[] args) {
        ArrayList<String> a=new ArrayList<String>();
        a.add("dsm");//添加
        a.add("tzf");
        a.add("I");
        a.add("LOVE");
        a.add("YOU");
        Iterator<String> i=a.iterator();
        while (i.hasNext()){
            if (i.next().equals("tzf")){
                i.remove();
            }
        }
        System.out.println(a);
        

    }
}

泛型

泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型好处:统一数据类型,把运行时期的问题提前到了编译前,避免类型转换可能出现的异常

方法中参数类型不确定时,1使用类名后面的定义的泛型(所有方法都能使用),2在方法申明上定义自己的泛型(只有本方法能用)

注意:

  • 泛型中不能写基本的数据类型,要写包装类
  • 指定泛型之后,传递数据时可以传入该类型或者该类的子类型
ArrayList<animal> list=new ArrayList<animal>();
list.add(new animal());
list.add(cat);
  • 如果不写泛型,类型默认是object

自定义泛型:

泛型可以写在类,方法,接口后面,表示泛型类,泛型方法,泛型接口。

泛型类:

修饰符 class 类名<类型> { }

eg:

/*
* <E>表示任意类型数据
* */
public class Animal<E>{
    
}
public class MyArrayList<E> {
  Object[] obj=  new Object[10];
    int size=0;

    public boolean add(E e){
        obj[size]=e;
        size++;
        return true;
    }

    public E get(int size ){
        return (E)obj[size];
    }

    @Override
    public String toString() {
        return super.toString();
    }
}
MyArrayList<String> ml = new MyArrayList<>();
ml.add("t");
ml.add("z");
ml.add("f");
System.out.println(ml.get(2));

泛型方法:

public static<E> void addAll(ArrayList<E> list,E...e){
     for (E e1:e){
    System.out.println(e1);
     }
 }
//E...e  是可变参数,底层是数组

泛型接口:

public class myArrayList<E> implements List<E>{
    
}

泛型不具备继承性,但是数据具备继承性

序列化

就是一种用来处理对象流(就是将对象的内容流化)的机制,对流化后的对象进行读写操作,也可以对流化后的对象传输于网络之间。

Java中有一些字符是不能直接传输的,需要编码才能传输,这个编码就是序列化。

序列化原因:

  • 网络传输。网络传输的数据必须是二进制数据,但在Java中都是对象无法在网络中传输
  • 对象持久化。将内存中的对象状态保存到文件或数据库中
  • 实现分布式。如RMI(远程方法调用)要利用对象序列化允许远程主机的服务,就像本地机运行对象时一样

如何实现:

一个类要想实现序列化和反序列化 ,该类需要实现java.io.Serializable接口

序列化:ObjectOutputStream#writeObject(Object pbj)方法

public class Employee implements Serializable {
    public String name;
    public int age;
    public String address;
}
public class Main {
        Employee el = new Employee();
        el.address="zzuli";
        el.age=18;
        el.name="tzf";
        try {
            FileOutputStream fileOut = new FileOutputStream("/tmp/employee.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(el);
            out.close();
            fileOut.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

反序列化:ObjectInputStream#readObject()方法

public class Main {
    public static void main(String[] args) {
        Employee e1=null;
        try {
            FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);

             e1= (Employee) in.readObject();
            in.close();
            fileIn.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(e1.address);
        System.out.println(e1.age);
        System.out.println(e1.name);

    }
}

缺点:

效率低,序列后的流数据比较大

所以使用第三方序列化方式,如JSON,Hession

注意:

transient修饰的属性,静态static修饰的属性不会被序列化

网络编程

常用的软件结构

C/S:客户端/服务器形式,如QQ,微信等软件

B/S:浏览器/服务器形式,如谷歌,火狐等

网络通信协议:

img

两台主机如何通信:

1.定位主机地址(ip与端口号)

2.通信(网络通信协议TCP,UDP)

获取ip:

public class Main {

//        网络编程
        try {
            InetAddress i=InetAddress.getByName("localhost");
            System.out.println(i.getAddress());//地址
            System.out.println(i.getCanonicalHostName());//规范的名字
            System.out.println(i.getHostAddress());//ip
            System.out.println(i.getHostName());//域名,或者自己电脑的名字

        } catch (UnknownHostException e) {
            e.printStackTrace();
        }


    }
}

端口:

表示计算机上的一个程序的进程,不同的进程有不同的端口号不能冲突

端口号分类:

公有端口(0~1023):http-80,https-443,ftp-21,Telent-23

程序注册端口(1024~49151分配给用户或程序):Tomcat-8080,Mysql-3306,Oracle-1521

动态私有(49152~65535)

通信协议(TCP/IP):

TCP:是一种面向连接的、可靠的、基于字节流的传输层通信协议

三次握手建立连接,四次挥手断开连接

UDP:位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。

Socket编程

TCP实现聊天:

客户端向服务器发数据:

public class TcpClientDemo {
    public static void main(String[] args) {
        Socket socket=null;
        OutputStream out=null;
        try {
//            获得服务器ip以及端口号,127.0.0.0是本机ip
            InetAddress  byName= InetAddress.getByName("127.0.0.1");
        int port=9999;
//        进行通信连接
            socket = new Socket(byName,port);
//            获得输出流
            out = socket.getOutputStream();
//            输出数据
            out.write("你好".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (out!=null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

    }

}

服务器接收数据并显示出来

public class TcpServerDemo {
    public static void main(String[] args) {
        ServerSocket sever=null;
        InputStream inputStream=null;
        ByteArrayOutputStream bo=null;
        try {
//            创建端口号
            sever = new ServerSocket(9999);
//等待客户连接
            Socket accept = sever.accept();
//         获取输入流,读取客户端信息
            inputStream= accept.getInputStream();
//创建管道流
            bo= new ByteArrayOutputStream();
//            创建缓存区,保存输入流
            byte[] buffer=new byte[1024];
            int len;
//            将输入流数据保存到缓存中去,下一步使用管道流将缓存区数据读出来
while ((len=inputStream.read(buffer))!=-1){
//            使用管道流将缓存区的数据读出来,如果不使用管道流数据编码可能会出错
    bo.write(buffer,0,len);
}
            System.out.println(bo.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (bo!=null) {
                try {
                    bo.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream!=null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (sever!=null){
                try {
                    sever.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

TCP文件上传和下载

服务器:

public class TcpServerDemo2 {
    public static void main(String[] args) throws Exception {
//        创建服务器的端口号
        ServerSocket serverSocket = new ServerSocket(9000);
//等待连接,如果服务器启动没有客户端连接,程序不会停止
        Socket accept = serverSocket.accept();
//        服务器获取输入流
        InputStream inputStream = accept.getInputStream();
//        获取文件管道流
        FileOutputStream bo = new FileOutputStream(new File("receive.jpg"));
//创建缓存区
        byte[] buffer=new byte[1024];
        int len;
//        将输入流数据线放入缓存区
        while ((len=inputStream.read(buffer))!=-1){
//            在使用文件输出流把缓存区数据输出
            bo.write(buffer,0,len);
        }
//告诉客户顿已经传送完毕,提醒其关闭流
        OutputStream outputStream = accept.getOutputStream();
        outputStream.write("我已经传送完毕,你可以断开了".getBytes());

//        关闭流
        bo.close();
        inputStream.close();
        accept.close();
        serverSocket.close();

    }
}

客户端:

public class TcpClientDemo2 {
    public static void main(String[] args) throws Exception {
//        获取ip和端口号,创建连接对象
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000);

//        获得输出流
        OutputStream outputStream = socket.getOutputStream();

//        读入文件
        FileInputStream fileInputStream = new FileInputStream("1.jpg");
//        创建缓存区,需要使用输入流先把文件读到缓存区,再使用输出流写出文件到目标地址
        byte[] buffer=new byte[1024];
        int len;
//        将文件写入缓存区
        while ((len=fileInputStream.read(buffer))!=-1){
//            将缓存区文件读出
            outputStream.write(buffer,0,len);
        }
//        输出完毕,关闭输出
        socket.shutdownOutput();

//        输入流,接受服务器传回来的确认信息
        InputStream inputStream = socket.getInputStream();
//字符串管道流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        byte[] bytes = new byte[1024];
        int len1;
        while ((len1=inputStream.read(bytes))!=-1){
            byteArrayOutputStream.write(bytes,0,len1);
        }
        System.out.println(byteArrayOutputStream.toString());

//        关闭流,先打开的后关闭,后打开的先关闭
        fileInputStream.close();
        outputStream.close();
        socket.close();


    }
}

Udp消息发送:

public class UdpServerDemo01 {
    public static void main(String[] args) throws Exception {
//建立socket
        DatagramSocket datagramSocket = new DatagramSocket();

//        建立一个包
        String msg="你好服务器";
        InetAddress localHost = InetAddress.getByName("localHost");
        int port=9090;
//        数据,数据长度起始,要发给谁
        DatagramPacket datagramPacket = new DatagramPacket(msg.getBytes(), 0, msg.length(),localHost,port);
//发送包
        datagramSocket.send(datagramPacket);
        datagramSocket.close();



    }

}
public class UdpClientDemo01 {
    public static void main(String[] args) throws Exception {
//        建立一个端口9090
        DatagramSocket socket = new DatagramSocket(9090);
//        建立一个容器,存接收到的数据
        byte[] bytes = new byte[1024];
//        建立包
        DatagramPacket Packet = new DatagramPacket(bytes, 0, bytes.length);
//        接收包
        socket.receive(Packet);
//        输出包的内容
        System.out.println(new String(Packet.getData(),0,Packet.getLength()));
        socket.close();
    }
}

多线程编程:

线程概念

程序:指令和数据的有序集合,静态的

进程:程序执行一次过程,是一个动态的概念

线程:一个进程的中的一个任务,一个进程至少有一个线程,线程是CPU调度和执行的单位

注意:很多多线程是模拟出来的,真正的多线程是指多个CPU即多核。一个CPU在一个时间点只能执行一个代码,由于切换的很快,给人一种同时执行的错觉

线程实现(重点)

继承Tread类(重要)

public class TestThread01 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("2222");
        }
    }
    public static void main(String[] args) {

        TestThread01 testThread01 = new TestThread01();
        testThread01.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("1111");
        }
    }

}

实现Runnable接口(重要)

推荐使用,避免单继承局限性,灵活方便,方便一个对象被多个线程使用

public class CreatThread02 implements Runnable{
//    当开启线程时,执行run方法,run方法跟主类方法是同步的
    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            System.out.println("222");
        }
    }

    public static void main(String[] args) {
        CreatThread02 creatThread02 = new CreatThread02();
//        开启一个线程
        new Thread(creatThread02).start();
        for (int i = 0; i < 500; i++) {
            System.out.println("1111");
        }

    }
}

实现Callable接口

public class CreatThread03 implements Callable {
    public static void main(String[] args) {
//        为接口类创建对象
        CreatThread03 c3 = new CreatThread03();
//        使用FutureTask类包装Callable对象
        FutureTask futureTask = new FutureTask<>(c3);
//        使用futertask对象开启一个线程
        new Thread(futureTask).start();
    }
//    实现Callable接口的时候,需要实现其call方法
    @Override
    public Object call() throws Exception {
        return null;
    }
}

线程池创建线程:

package com.tzf.Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CreatThread04 {
    public static void main(String[] args) {
//创建线程池对象,参数为5,代表有5个线程的线程池
        ExecutorService service = Executors.newFixedThreadPool(5);
        TaskRunable runable = new TaskRunable();
//        使用线程池获取一个线程对象
        service.submit(runable);
//        再获取一个线程对象
        service.submit(runable);
//        关闭线程池
        service.shutdown();
    }
}

class TaskRunable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("dsm"+i);
        }
    }
}

线程状态

image-20230220170348001

image-20230222203625201

注意:在java中实际上是没有定义线程的运行状态,因为当线程运行是jvm将线程管理权已经移交出去了,所有就没有对运行状态定义

线程停止:new Thread().stop();

线程休眠:new Thread().sleep();//放大问题的发生性,每个对象都有一把锁,sleep不会自己释放锁

线程礼让:new Thread().yield();

线程插队(强制执行):new Thread().join();

package com.tzf.Thread;

public class ThreadJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("vip"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadJoin threadJoin = new ThreadJoin();
        Thread thread = new Thread(threadJoin);
        thread.start();
        for (int i = 0; i < 200; i++) {
            if (i==50){

                System.out.println("--------------------");
                thread.join();//Thread线程插队,主线程等待,等Thread线程执行完主线程才能执行
            }
            System.out.println("主线程"+i);
        }
    }
}

观测线程状态:Thread.getState();

获取线程优先级:Thread.getPriority();

改变线程优先级:Thread.setPriority();

守护线程:

  • 线程分为用户线程和守护线程
  • jvm必须保证用户线程执行完毕,不用等待守护线程执行完毕(如操作日志,监控内存,垃圾回收gc)

线程同步(重点)

多线程在使用同一个资源时,我们需要线程同步即线程等待机制,需要将线程进入对象的等待池中形成队列,等待前面的线程使用完毕,下一个线程才能使用。

线程安全=形成队列+锁

锁带来的问题:

  • 一个线程获得排它锁时,独占资源,其他线程需要挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程,导致优先级倒置,引起性能问题

同步代码块(synchronized):

格式:synchronized(锁){

操作共享数据的代码

}

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
//        MyThread thread2= new MyThread();
//        MyThread thread3 = new MyThread();
        new Thread(thread1).start();
        new Thread(thread1).start();
        new Thread(thread1).start();

    }
}

class MyThread implements Runnable{
 int ticke=0;
//创建锁对象,static保证其唯一性
 static Object obj=  new Object();
    @Override
   public void run() {
            while (true) {
                //        同步代码块区域,相当于加了一把锁,只允许一条进程拿到其资源,只有锁释放掉后下一条进程拿到锁才能进去(锁对象或者可以直接使用Thread.class)
                synchronized (obj) {
                    if (ticke<100) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticke++;
          System.out.println(Thread.currentThread().getName() + "票正在卖第" + ticke + "张");
                    }

            }
        }
    }
}

同步方法:

  • 同步方法是锁住方法里面的所有代码
  • 锁对象不能自己指定(非静态:this,静态:当前类的字节码文件对象)
package com.tzf.Thread;

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
//        MyThread thread2= new MyThread();
//        MyThread thread3 = new MyThread();
        new Thread(thread1).start();
        new Thread(thread1).start();
        new Thread(thread1).start();

    }
}

class MyThread implements Runnable{
static int ticke=0;

    @Override
    public void run() {
//        同步代码块区域,相当于加了一把锁,只允许一条进程拿到其资源,只有锁释放掉后下一条进程拿到锁才能进去
            while (true) {
                if (method()){
                    break;
                }
        }
    }

    private synchronized boolean method(){
        if (ticke==100) {
            return true;
        }
        else  {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticke++;
            System.out.println(Thread.currentThread().getName() + "票正在卖第" + ticke + "张");
        }
        return false;
    }
}

锁(lock)

我们无法直接看到同步代码块与同步方法在哪里加上锁,在哪里释放锁。为了更清晰的表达如何加锁和释放锁,jdk5以后提供了一个新的锁对象。

注意:Lock是接口不能直接实例化,这里采用实现类ReentrantLock来实例化

package com.tzf.Thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
//        MyThread thread2= new MyThread();
//        MyThread thread3 = new MyThread();
        new Thread(thread1).start();
        new Thread(thread1).start();
        new Thread(thread1).start();

    }
}

class MyThread implements Runnable{
static int ticke=0;
//创建锁对象,static保证其唯一性
 static Object obj=  new Object();

 static Lock lock=new ReentrantLock();
    @Override
    public void run() {
            while (true) {
//                加锁
                lock.lock();
                try {
                    if (ticke==100) {
                        break;
                    }
                    else  {
                            Thread.sleep(100);
                        ticke++;
                       System.out.println(Thread.currentThread().getName() + "票正在卖第" + ticke + "张");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
//                    释放锁
                    lock.unlock();
                }

            }
    }
}

死锁问题:

不要让两个锁嵌套着使用,否则很容易发生死锁问题

image-20230222170739809

等待唤醒机制(生产者与消费者):

打破多线程的随机性,实现多线程有序轮流进行

下面举例厨师与顾客的例子,厨师做一碗面条,顾客吃一碗面条,两者线程有序交替进行

package com.tzf.Thread.ThreadwaitDemo;

public class Desk {

//    是否有面条
    public static int foodflag=0;
//    总共能吃的碗数
    public static int count=10;
//    锁的对象
    public static Object lock=new Object();
}
package com.tzf.Thread.ThreadwaitDemo;

public class Cook implements Runnable{
    @Override
    public void run() {

        /*
        * 1.循环
        * 2.同步代码块
        * 3.判断共享数据是否到了末位
        * 4.没有到末尾执行核心代码块
        * */

        while (true){
            synchronized (Desk.lock){
                if (Desk.count==0){
                    break;
                }
                else {
//                    判断桌子状态,是否有碗
                    if (Desk.foodflag==1){
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        System.out.println("厨师做了一碗面条");
                        //                    修改桌子状态
                        Desk.foodflag=1;
//                    唤醒所有进程
                        Desk.lock.notifyAll();

                    }


                }
            }
        }

    }
}
package com.tzf.Thread.ThreadwaitDemo;

public class Foodie implements Runnable{

    @Override
    public void run() {

        while (true){
            synchronized (Desk.lock) {
//                判断总碗数
                if (Desk.count == 0) {
                    break;
                } else {
//                    判断桌子的状态,是否有碗
                    if (Desk.foodflag==0){
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
//桌子上的碗总数减一
                    Desk.count--;
                    System.out.println("吃货"+Desk.count);
//                    修改桌子的状态
                    Desk.foodflag=0;
//                    唤醒所有的线程
                    Desk.lock.notifyAll();
                }
            }

        }
    }
}
package com.tzf.Thread.ThreadwaitDemo;

public class ThreadDemo {
    public static void main(String[] args) {
        Cook cook = new Cook();
        Foodie foodie = new Foodie();
        new Thread(cook).start();
        new Thread(foodie).start();
    }
}
/*
* 厨师做了一碗面条
吃货还能吃9碗
厨师做了一碗面条
吃货还能吃8碗
厨师做了一碗面条
吃货还能吃7碗
厨师做了一碗面条
吃货还能吃6碗
厨师做了一碗面条
吃货还能吃5碗
厨师做了一碗面条
吃货还能吃4碗
厨师做了一碗面条
吃货还能吃3碗
厨师做了一碗面条
吃货还能吃2碗
厨师做了一碗面条
吃货还能吃1碗
厨师做了一碗面条
吃货还能吃0碗

* */

等待唤醒机制(阻塞队列方式实现)

image-20230222203206352

image-20230222200037324

package com.tzf.Thread.ThreadQueueDemo;

import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread{
    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {

        while (true) {
//          使用阻塞队列不需要加锁,因为阻塞队列方法ArrayBlockingQueue底层源码已经实现锁的功能
            try {
//              将阻塞队列的首位拿出
                queue.put("面条");
                System.out.println("厨师做了一碗面");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
      }
    }
package com.tzf.Thread.ThreadQueueDemo;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {
    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
//          使用阻塞队列不需要加锁,因为阻塞队列方法ArrayBlockingQueue底层源码已经实现锁的功能
            try {
//              将阻塞队列的首位拿出
                String take = queue.take();
//                输出语句写在了锁的外面,控制台阅读时可能无法真是看出效果,但真实数据是在所得里面处理的
                System.out.println(take);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
package com.tzf.Thread.ThreadQueueDemo;

import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
    public static void main(String[] args) {
        //在测试类创建阻塞队列管道,再通过构造函数的参数传递给两个线程,使其两个线程使用的是同一个阻塞队列管道。在这个例子我们设置管道最多装一个
        ArrayBlockingQueue<String> strings = new ArrayBlockingQueue<String>(1);
        Cook cook = new Cook(strings);
        Foodie foodie = new Foodie(strings);

        cook.start();
        foodie.start();


    }
}

线程通信

反射

加载类,并允许以编程的方式剖析类中的各种成分(成员变量,方法,构造器等)

1、加载类,获取类的字节码:也就是Class对象

获取class对象的三种方式:

  • Class c1=类名.class
  • 调用Class提供的方法:public static Class forName(String package);
  • Object提供的方法:public Class getClass(); Class c3=对象.getClass();
public void ClassTest() throws ClassNotFoundException {
        Class<Users> c1= Users.class;//第一种方式获取类
        c1.getName();//获取类的全名
        c1.getSimpleName();//获取类的简名字,如Student

        Class.forName("com.tzf.pojo.Users");//第二种获取类的方式

        Users users = new Users();
        users.getClass();//第三中获取类的名字的方式
        //注意:这三种获取方式获取到的类是同一个,hashcode值相同

    }

2.获取类的构造器

Class<Users> c1= Users.class;//第一种方式获取类
Constructor[] constructors = c1.getDeclaredConstructors();//获取全部构造器
for(Constructor c:constructors){
    System.out.println(c.getName());//遍历查询所有
}

        Constructor constructor = c1.getDeclaredConstructor(int.class);//获取有参构造器

3、获取类的成员变量

Class<Users> c1= Users.class;//第一种方式获取类
Field[] fields = c1.getDeclaredFields();//获取全部成员变量
Field name = c1.getDeclaredField("name");//根据成员变量的名字获取成员变量

获取成员变量后作用依然是赋值和取值:

Class<Users> c1= Users.class;//第一种方式获取类
Field[] fields = c1.getDeclaredFields();//获取全部成员变量
        Field name = c1.getDeclaredField("name");//根据成员变量的名字获取成员变量
        //赋值
        Users u = new Users();
        name.set(u,"阿飞");
        //取值
        String name1= (String) name.get(u);

4.获取类的成员方法


Class<Users> c1= Users.class;//第一种方式获取类
//        获取类的成员方法
        Method[] methods = c1.getDeclaredMethods();//获取每个成员方法

//        通过方法名获取某一个成员方法
        Method getName = c1.getDeclaredMethod("getName");

获取成员方法后的作用:

//        通过方法名获取某一个成员方法
        Method getName = c1.getDeclaredMethod("getName");

//        利用类名执行成员方法

        getName.setAccessible(true);//禁止检查访问权限
//        invoke单独使用时不能访问私有方法,禁止访问权限后可以访问私有方法
       getName.invoke(u,"阿飞");
//

Class类的常用方法:

getFields()—— 获得类的public类型的属性。

getDeclaredFields()—— 获得类的所有属性

getField(String name)—— 获得类的指定属性

getMethods()—— 获得类的public类型的方法

getMethod (String name,Class [] args)—— 获得类的指定方法

getConstrutors()—— 获得类的public类型的构造方法

getConstrutor(Class[] args)—— 获得类的特定构造方法

getDeclaredConstrutors()---获取全部构造器

newInstance()—— 通过类的无参构造方法创建对象

getName()—— 获得类的完整名字

getPackage()—— 获取此类所属的包

getSuperclass()—— 获得此类的父类对应的Class对象

反射使用场景

  • 基本作用:得到一个类的全部信息,如类的成员变量,类名,成员方法。并能够最这些信息进行操作
  • 可以破坏封装性
  • 适合做java的框架,基本上主流框架都是基于反射设计出一些通用的功能

eg:

设计一个框架,作用是得到任意的一个类都能讲类中的属性信息保存到指定文件中去

public class ObjectFrame {
    public static void saveObject(Object obj) throws IllegalAccessException, FileNotFoundException {
        PrintStream ps = new PrintStream(new FileOutputStream("SpringReview03-anno\\src\\data.txt",true));
//        得到类
        Class c = obj.getClass();
//           得到类名
        String name = c.getSimpleName();
//          得到成员变量数组
        Field[] fields = c.getDeclaredFields();
//        遍历数组
        for (Field f:fields){
//            禁止检查访问控制权限
            f.setAccessible(true);
//            得到成员变量的名字
            String name1 = f.getName();
//              得到成员变量的值
            String value = (String) f.get(obj);
        }
        ps.close();
    }
}
@Test
public void FrameTest() throws FileNotFoundException, IllegalAccessException {
    Student s1 = new Student("阿飞", 1);
    Teacher t1 = new Teacher("min", 20);
    ObjectFrame.saveObject(s1);
    ObjectFrame.saveObject(t1);
}

java8新特性

stream

stream诞生的价值

简化程序员对集合的操作,stream的出现依托于lambda表达式的出现(极大提高编程的效率和可读性)

stream是什么?

首先stream流与io流没有毛线关系,可以把它看做一个高级一次性迭代器,在迭代的过程中对数据进行一系列的操作(如过滤,获取特定元素等)

流的分类

中间操作:可以有多个,每次返回一个新的流,可以进行链式操作

终端操作:只能有一个,执行完就用光了

eg:

List<String> l=new ArrayList<>();
l.add("aa");
l.add("bb");
l.add("bb");
l.add("cc");
long count=l.stream().distinct().count();

distinct()是中间操作(去重),count()是终端操作(返回元素个数)

创建流

如果是数组可以用Arrays.stream()或者Stream.of()创建流

如果是集合直接使用stream()创建,因为该方法已经添加到Collection中

eg:

String[] arr = new String[]{"武汉加油", "中国加油", "世界加油"};
        Stream<String> stream = Arrays.stream(arr);

        stream = Stream.of("武汉加油", "中国加油", "世界加油");

        List<String> list = new ArrayList<>();
        list.add("武汉加油");
        list.add("中国加油");
        list.add("世界加油");
        stream = list.stream();

操作流

1、过滤

filter(过滤条件)

public class FilterStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("周杰伦");
        list.add("王力宏");
        list.add("陶喆");
        list.add("林俊杰");
        Stream<String> stream = list.stream().filter(element -> element.contains("王"));
        stream.forEach(System.out::println);
    }
}

2、映射

把流中的元素转化新的流中的元素,使用map()方法

public class MapStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("周杰伦");
        list.add("王力宏");
        list.add("陶喆");
        list.add("林俊杰");
        //把stream<string>的流转化stream<Intrger>流
        Stream<Integer> stream = list.stream().map(String::length);
        stream.forEach(System.out::println);
    }
}

输出

3
3
2
3

3、匹配

一般有三种方法进行元素匹配

  • anyMatch(),只要有一个元素匹配传入的条件,返回true
  • allMatch(),全部传入的元素都匹配条件,返回true
  • noneMatch(),全部传入的元素都不匹配传入的条件,返回true

eg:

public class MatchStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("周杰伦");
        list.add("王力宏");
        list.add("陶喆");
        list.add("林俊杰");

        boolean  anyMatchFlag = list.stream().anyMatch(element -> element.contains("王"));
        boolean  allMatchFlag = list.stream().allMatch(element -> element.length() > 1);
        boolean  noneMatchFlag = list.stream().noneMatch(element -> element.endsWith("沉"));
        System.out.println(anyMatchFlag);
        System.out.println(allMatchFlag);
        System.out.println(noneMatchFlag);
    }
}

输出

true
true
true

4、组合

reduce()方法主要将Stream中的元素组合起来

reduce() 方法的主要作用是把 Stream 中的元素组合起来,它有两种用法:

  • Optional reduce(BinaryOperator accumulator)

没有起始值,只有一个参数,就是运算规则,此时返回 Optional

  • T reduce(T identity, BinaryOperator accumulator)

有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。

public class ReduceStreamDemo {
    public static void main(String[] args) {
        Integer[] ints = {0, 1, 2, 3};
        List<Integer> list = Arrays.asList(ints);
//Lambda表达式((a, b) -> a + b))操作运算
        Optional<Integer> optional = list.stream().reduce((a, b) -> a + b);
//类名::方法名 运算       
        Optional<Integer> optional1 = list.stream().reduce(Integer::sum);
        System.out.println(optional.orElse(0));
        System.out.println(optional1.orElse(0));
//有起始值6,在起始值的基础上操作数据
        int reduce = list.stream().reduce(6, (a, b) -> a + b);
        System.out.println(reduce);
        int reduce1 = list.stream().reduce(6, Integer::sum);
        System.out.println(reduce1);
    }
}

输出:

6
6
12
12

转换流

stream是将数组或集合转换成流,那么就有逆操作将流 转换为其他数据格式,collect()方法即此功能

public class CollectStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("周杰伦");
        list.add("王力宏");
        list.add("陶喆");
        list.add("林俊杰");
//String[]::new相当于返回了一个指定长度的字符串数组。
        String[] strArray = list.stream().toArray(String[]::new);
        System.out.println(Arrays.toString(strArray));

        List<Integer> list1 = list.stream().map(String::length).collect(Collectors.toList());
        List<String> list2 = list.stream().collect(Collectors.toCollection(ArrayList::new));
        System.out.println(list1);
        System.out.println(list2);

        String str = list.stream().collect(Collectors.joining(", ")).toString();
        System.out.println(str);
    }
}

输出:

[周杰伦, 王力宏, 陶喆, 林俊杰]
[3, 3, 2, 3]
[周杰伦, 王力宏, 陶喆, 林俊杰]
周杰伦, 王力宏, 陶喆, 林俊杰

Lambda

简化匿名类的语法,使java走向函数式编程

作用:

  • 允许将一个函数作为一个方法的参数传递到方法中
  • 是代码看起来简洁紧凑,可读性高,但没有提升性能

本质:接口实现类的具体对象

应用场景:复用性没用这么强的场景

语法

1. (parameters) -> expression
2. 或
3. (parameters) ->{ statements; }

分三部分:
a) -> :我们称之为“箭头操作符”或“lambda操作符”
b) -> 左侧:
  lambda的形参列表,等效于 对应的接口的那个抽象方法的形参列表,例子中testMethod方法没有参数。
  可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
c) -> 右侧:
  抽象方法的方法体
  可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  可选的返回关键字:如果主体只有一个表达式,return关键词可以省略。

eg:

/**
 * 有一个参数,无返回值
 */
interface TestInterface02 {
    void testMethod(int num);
}
 
class Test02 {
    public static void main(String[] args) {
        TestInterface02 t1 = (int num) -> System.out.println("这是有一个参数的实现:" + num);
        //  1、参数名字可以随便起
        TestInterface02 t2 = (int x) -> System.out.println("这是有一个参数的实现:" + x);
        //  2、 参数的类型可以省略 -》 上下文的类型推断:比如下方的list 和 int数组
        
        TestInterface01 t = () -> System.out.println("这是一个无参的,无返回值的方法");
        List<String> list1 = new ArrayList<String>();
        List<String> list2 = new ArrayList<>();
        int[] arr1 = new int[]{1, 2, 3, 4};
        int[] arr2 = {1, 2, 3, 4};
        TestInterface02 t3 = (x) -> System.out.println("这是有一个参数的实现:" + x);
        // 3、只有一个参数的话,() 可以省略
        TestInterface02 t4 = x -> System.out.println("这是有一个参数的实现:" + x);
        
        t1.testMethod(10);
        t2.testMethod(20);
        t3.testMethod(20);
        t4.testMethod(20);
    }
}
/**
 * 2个以上参数,有返回值
 */
interface TestInterface03 {
    int testMethod(int num1, int num2);
}
 
class Test03 {
    public static void main(String[] args) {
        // 多个参数,() 不可省略
        // 方法体中多条语句,{} 不可省略
        TestInterface03 t1 = (x, y) -> {
            System.out.println("2个以上参数,有返回值" + x + "---" + y);
            return 666;
        };
        // 如果只有一行,return 可以省略
        TestInterface03 t2 = (x, y) -> 666;
        System.out.println(t1.testMethod(10, 20));
        System.out.println(t2.testMethod(10, 20));
    }
}

函数式编程

函数式接口就是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,可以被隐式转换为lambda表达式,通过注解@FunctionalInterface可以定义一个函数式接口

eg:

class Test05 {
    public static void buySomething(double money, Consumer<Double> sm) {
        sm.accept(money);
    }
 
    public static void main(String[] args) {
        buySomething(999, new Consumer<Double>() {
            @Override
            public void accept(Double o) {
                System.out.println("我买了件大衣!!!花了" + o + "元钱");
            }
        });
 
        // 用lambda表达优化
        buySomething(999, o -> System.out.println("我买了件大衣!!!花了" + o + "元钱"));
    }
}