java.lang.Object类是Java语言中的根类,所有类的父类。
Object描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object。
如果一个类没有特别指定父类,那么默认则继承自Object类。
public String toString(); 返回该对象的字符串表示。
public boolean equals(Object obj):指示其他某个对象是否与此对象“相等”
public static boolean equals(Object a,Object b){
return (a==b)||(a!=null&&a.equals(b));
}
java.util.Date类 表示特定的瞬间,精确到毫秒。
标记字母 | 表示含义 |
---|---|
年 | y |
月 | M |
日 | d |
时 | H |
分 | m |
秒 | s |
成员方法:
字段值 | 含义 |
---|---|
YEAR | 年 |
MONTH | 月(从0开始,可以+1使用) |
DAY_OF_MONTH | 月中的天(几号) |
HOUR | 时(12小时制) |
HOUR_OF_DAY | 时(24小时制) |
MINUTE | 分 |
SECOND | 秒 |
DAY_OF_WEEK | 周中的天(周几,周日为1,可以-1使用) |
可以使用一个类,把基本数据类型的数据装起来,在类中定义一些方法,整个类叫做包装类,我们可以使用类中的方法来操作这些基本类型的数据(位于java.lang包中)
基本类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:
由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。
方法如下:
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator。Iterator接口也是Java集合中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于**迭代访问(即遍历)**Collection中的元素,因此Iterator对象也被称为迭代器。
增强for循环(for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
for(元素的数据类型 变量名 : Collection集合/数组){
//...
}
新for循环必须有被遍历的目标。目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现。
修饰符 class 类名<代表泛型的变量> {
//...
}
使用泛型:在创建对象的时候确定泛型
修饰符 <代表泛型的变量> 返回值类型 方法名(参数列表){
//...
}
使用泛型:调用方法时,确定泛型的类型
修饰符 interface 接口名{
//...
}
定义类时确定泛型的类型
始终不确定泛型的类型,直到创建对象时,确定泛型的类型
java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。List集合的元素是有序的,即元素的存入顺序和取出顺序一致。
有序可重复、有索引
java.util.ArrayList集合数据存储的结构是数组。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
java.util.LinkedList集合数据存储的结构是双向链表。方便元素的添加和删除。
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。
java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与 Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中的元素无序,并且都会以某种规则保证存入的元素不出现重复。
无序不可重复,无索引
java.util.HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的。java.util.HashSet底层的实现其实是一个java.util.HashMap支持
HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素的唯一性依赖于:hashCode与equals方法。
哈希表
jdk1.5之后出现的新特性
注意事项:
1.一个方法的参数列表,只能有一个可变参数
2.如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
java.utils.Collections是集合工具类,用来对集合进行操作。部分方法如下:
Java提供了专门的集合类用来存放映射对象关系的对象,即 java.util.Map接口。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
在HashMap下面有一个子类LinkedHashMap,它是链表和哈希表组合的一个数据存储结构,有序。
java.util.Hashtable
Java 9,添加了几种集合工厂方法,更方便创建少量元素的集合/map实例。新的List、Set、Map的静态工厂方法可以更方便地创建集合的不可变实例。
Throwable中的常用方法:
日常讲的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。
异常(Exception)的分类:
JVM检测到异常后
Java异常处理的五个关键字:try、catch、finally、throw、throws
throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。
使用格式:throw new 异常类名(参数);
注意事项:
Objects类由一些静态的实用方法组成,这些方法是null-save(空指针安全)或null-tolerant(容忍空指针),在它的源码中,对对象为null的值进行了抛出异常操作。
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了checkedException,而没有捕获处理,那么必须通过throws进行声明,让调用者去处理。
关键字throws运用于方法声明上,表示当前方法不处理异常,而是提醒该方法的调用者来处理异常。
声明异常格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
注意事项:
如果异常throws至JVM后,会立刻终止程序
异常处理:
捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
try{
可能会产生异常的代码
}catch(异常类型 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
try:该代码块中编写可能产生异常的代码。
catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。
try和catch都不能单独使用,必须连用
Throwable类中定义了一些查看方法:
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
多个异常:
要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
一般我们是使用一次捕获多次处理方式,格式如下:
try{
编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
1.自定义一个编译期异常: 自定义类 并继承于java.lang.Exception。
2.自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException。
public class RegisterException extends Exception/* RuntimeException*/ {
public RegisterException() {
super();
}
public RegisterException(String s) {
super(s);
}
}
并发与并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生。
一个程序运行后至少有一个进程,一个进程中可以包含多个线程
主线程:当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main Thread),因为它是程序开始时就执行的,如果你需要再创建线程,那么创建的线程就是这个主线程的子线程。每个进程至少有一个主线程
主线程的重要性体现在两方面:
1.是产生其他子线程的线程;
2.通常它必须最后完成执行(比如执行各种关闭动作)。
Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。
Java使用线程执行体来代表这段程序流。
Java中通过继承Thread类来创建并启动多线程的步骤如下:
1.定义Thread类的子类,并重写该类的run()方法[线程执行体]。
2.创建线程对象
3.调用线程对象的start()方法来启动该线程
调用start()方法的结果是两个线程并发的执行
void start()使该线程开始执行;java虚拟机调用该线程的run()方法
多次start()一个线程是非法的。通过Thread实例的start(),一个Thread的实例只能产生一个线程。一个Thread的实例一旦调用start()方法,这个实例的started标记就标记为true,事实中不管这个线程后来有没有执行到底,只要调用了一次start()就再也没有机会运行了。从new到等待运行是单行道,所以如果对一个已经启动的线程对象再调用一次start方法的话,会发生IllegalThreadStateException异常.
可以被重复调用的是run()方法。
Thread类中run()和start()方法的区别如下:
run()方法: 在本线程内调用该Runnable对象的run()方法,可以重复多次调用
start()方法: 启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程
多线程执行时,在栈内存中,每一个执行线程都有一片自己所属独立的的栈内存空间。进行方法的压栈和弹栈。
JVM完成main线程的创建,开辟main线程的栈空间
start()通知JVM创建新线程,并开辟另一块独立的栈空间供该线程使用;在此栈空间中执行run()
当执行线程的任务结束了,该线程就会被JVM在栈内存中释放。所有的执行线程都结束时,进程结束。
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}
public class demoo {
public static void main(String[] args) {
MyThread m=new MyThread("new");
m.start();
for (int i = 0; i < 10; i++) {
System.out.println("hi"+i);
}
}
}
构造方法:
常用方法:
采用 java.lang.Runnable接口创建线程
public class MyRunable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public class demoo {
public static void main(String[] args) {
//MyThread m=new MyThread("new");
MyRunable t=new MyRunable();
Thread m=new Thread(t,"hello");
m.start();
for (int i = 0; i < 10; i++) {
System.out.println("hi"+i);
}
}
}
匿名内部类:
public class NoNameInnerClassThread {
public static void main(String[] args) {
// new Runnable(){
// public void run(){
// for (int i = 0; i < 20; i++) {
// System.out.println("hi:"+i);
// }
// }
// }; //‐‐‐这个整体 相当于new MyRunnable()
Runnable r = new Runnable(){
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("hr:"+i);
}
}
};
new Thread(r).start();
for (int i = 0; i < 20; i++) {
System.out.println("hu:"+i);
}
}
}
格式:
synchronized(锁对象/同步锁/对象锁/对象监视器){
需要同步操作的代码
}
格式:
public synchronized void method(){
//可能会产生线程安全问题的代码
}
public class Ticket implements Runnable {
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while (true){
sellticket();
}
}
private synchronized void sellticket() {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖第 " + ticket-- + " 张票");
}
}
}
public class RunnableImpl implements Runnable {
private static int ticket = 100;
//静态访问静态
@Override
public void run() {
while (true) {
payTicket();
}
}
public static synchronized void payTicket() {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖第 " + ticket-- + " 张票");
}
}
}
java.util.concurrent.locks.Lock(接口)机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
public void lock():加同步锁。
public void unlock() :释放同步锁。
java.util.concurrent.locks.ReentrantLock implements Lock
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待 | )一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。便需要线程通信来帮助解决线程之间对同一份资源的使用或操作,避免争夺。通过等待唤醒机制使各个线程能有效的利用资源。
其实就是经典的生产者与消费者问题
线程池是一个可以容纳多个线程的容器,其中的线程可以反复使用。
Executors中创建线程池的方法:
ExecutorService中使用线程池的方法:
void shutdown()启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
使用线程池中线程对象的步骤:
做什么,而不是怎么做
面向对象的思想: 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
函数式编程思想: 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable接口来定义任务内容,并使用 java.lang.Thread类来启动该线程。
传统写法
public class demo2 {
public static void main(String[] args) {
//匿名内部类
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("多线程任务启动");
}
};
new Thread(task).start();
}
}
代码分析
对于Runnable的匿名内部类用法,可以分析出几点内容:
体验Lambda的更优写法
借助Java 8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:
public class demo2 {
public static void main(String[] args) {
new Thread(()-> System.out.println(Thread.currentThread().getName()+"new")).start();
new Thread(()-> System.out.println(Thread.currentThread().getName()+"new")).start();
}
}
格式由3个部分组成:
Lambda表达式的标准格式为:
(参数类型 参数名称) -> { 代码语句 }
格式说明:
举个例子
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + 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;
}
}
import java.util.Arrays;
import java.util.Comparator;
public class demo {
public static void main(String[] args) {
Person[] arr={
new Person("as", 19),
new Person("ba", 18),
new Person("cd", 20) };
// 匿名内部类
/*Comparator comp=new Comparator() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
};
Arrays.sort(arr,comp);*/
Arrays.sort(arr,(Person o1,Person o2)->{
return o1.getAge()-o2.getAge();
});
for (Person person : arr) {
System.out.println(person);
}
}
}
public interface Calculator {
int calc(int a, int b);
}
public class demm {
public static void main(String[] args) {
invokeCalc(34,59,(int a,int b)->{
return a+b;
});
}
private static void invokeCalc(int a, int b, Calculator calculator) {
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
}
}
省略规则
invokeCalc(34,59,(a,b)->a+b);
Lambda的使用前提
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
java.io.File类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作
遍历不存在的目录或者文件 NullPointerException
File类中:
举个例子
文件搜索优化版
public class FileFilterImp implements java.io.FileFilter {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".doc")||pathname.isDirectory();
}
}
public class demo {
public static void main(String[] args) {
File f=new File("E:\\软件");
getAllFile(f);
}
public static void getAllFile(File n){
//System.out.println(n);
File[] f=n.listFiles(new FileFilterImp());//传递过滤器对象
for (File file : f) {
if(file.isDirectory()){
getAllFile(file);
}else{
System.out.println(file);
}
}
}
}
/*File[] files = dir.listFiles((File d,String name)->{
return new File(dir, name).isDirectory() || name.toLowerCase().endsWith(".md");
});*/
举个例子
文件搜索
import java.io.File;
public class dihui {
public static void main(String[] args) {
File f=new File("E:\\软件");
getAllFile(f);
}
public static void getAllFile(File n){
//System.out.println(n);
File[] f=n.listFiles();
for (File file : f) {
if(file.isDirectory()){
getAllFile(file);
}
//String k=file.toString();
//String k=file.getName();
//String k=file.getPath();
//if(k.endsWith(".doc"))
//System.out.println(k);
if(file.getName().endsWith(".doc"))
System.out.println(file);
}
}
}
文件搜索优化版
public static void getAllFile(File n){
//System.out.println(n);
File[] f=n.listFiles((pathname)->pathname.getName().endsWith(".doc")||pathname.isDirectory());
for (File file : f) {
if(file.isDirectory()){
getAllFile(file);
}else{
System.out.println(file);
}
}
}
顶级父类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流InputStream | 字节输出流OutputStream |
字符流 | 字符输入流Reader | 字符输出流Writer |
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都是一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。public abstract void write(int b)
:将指定的字节输出流。close方法,当完成流的操作时,必须调用此方法,释放系统资源。
构造方法
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
写出字节数据
public class demo {
public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("E:\\a.txt");
fos.write(98);//b
fos.close();
}
}
public class demo {
public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("E:\\a.txt");
byte[] a="哈哈哈".getBytes();
fos.write(a);
fos.close();
}
}
public class demo {
public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("E:\\a.txt");
byte[] a="哈哈哈".getBytes();
fos.write(a,0,3);//哈
fos.close();
}
}
数据追加续写
public FileOutputStream(File file, boolean append)
: 创建文件输出流以写入由指定的File对象表示的文件。public FileOutputStream(String name, boolean append)
:创建文件输出流以指定的名称写入文件。这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了。
public class demo {
public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("E:\\a.txt",true);
byte[] a="哈哈哈".getBytes();
fos.write(a);
fos.close();
}
}
写出换行
Windows系统里,换行符号是\r\n
(回车符:回到一行的开头(return)。换行符:下一行(newline))
Unix:\n
Mac:\r,从 Mac OS X开始与Linux统一
public class demo {
public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("E:\\a.txt",true);
byte[] a="哈哈哈".getBytes();
for (int i = 0; i < 5; i++) {
fos.write(a);
fos.write("\r\n".getBytes());
}
fos.close();
}
}
java.io.InputStream抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。public abstract int read()
: 从输入流读取数据的下一个字节。public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。close方法,当完成流的操作时,必须调用此方法,释放系统资源。
java.io.FileInputStream类是文件输入流,从文件中读取字节。
构造方法
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。当创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException 。
读取字节数据
public class demo {
public static void main(String[] args) throws IOException {
FileInputStream fis=new FileInputStream("E:\\a.txt");
int len = fis.read();
System.out.println(len);
int leo = fis.read();
System.out.println(leo);
fis.close();
}
}
public class demo {
public static void main(String[] args) throws IOException {
FileInputStream fis=new FileInputStream("E:\\a.txt");
int len=0;
while ((len=fis.read())!=-1){
System.out.println(len);
}
fis.close();
}
}
public class demo {
public static void main(String[] args) throws IOException {
FileInputStream fis=new FileInputStream("E:\\a.txt");
int len=0;
byte[] b = new byte[5];
while ((len=fis.read(b))!=-1){
System.out.println(new String(b,0,len));//读取有效字节个数
}
fis.close();
}
}
String类构造方法:
String(byte[] bytes) :把字节数组转换为字符串
String(byte[] bytes, int offset, int length) :把字节数组的一部分转换为字符串,offset:数组开始的索引 转换的字节个数
文件复制
public class demo {
public static void main(String[] args) throws IOException {
FileInputStream fis=new FileInputStream("E:\\a.txt");
FileOutputStream fos = new FileOutputStream("D:\\a.txt");
byte[] b = new byte[1024];
int len;
while ((len = fis.read(b))!=-1) {
fos.write(b, 0 , len);
}
fos.close();
fis.close();
}
}
当使用字节流读取文本文件,遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。(GBK:2个字节;UTF-8:3个字节)
java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
: 从输入流读取一个字符。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。java.io.FileReader类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
idea中UTF-8
字节缓冲区:一个字节数组,用来临时存储字节数据。
构造方法
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。
读取字符数据
public class demo {
public static void main(String[] args) throws IOException {
FileReader fr=new FileReader("E:\\a.txt");
int len = 0;//虽然读取了一个字符,但是会自动提升为int类型
while ((len=fr.read())!=-1){
System.out.print((char)len);
}
fr.close();
}
}
public class demo {
public static void main(String[] args) throws IOException {
FileReader fr=new FileReader("E:\\a.txt");
int len = 0;
char[] cbuf = new char[8];
while ((len=fr.read(cbuf))!=-1){
System.out.print(new String(cbuf,0,len));
}
fr.close();
}
}
String(char[] value)
String(char[] value, int offset, int count)
java.io.Writer抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
void write(int c)
写入单个字符。void write(char[] cbuf)
写入字符数组。abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off数组的开始索引,len写的字符个数。void write(String str)
写入字符串。void write(String str, int off, int len)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。void flush()
刷新该流的缓冲。void close()
关闭此流,但要先刷新它。java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
构造方法
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
基本写出数据
public class demo {
public static void main(String[] args) throws IOException {
FileWriter fw=new FileWriter("E:\\b.txt");
fw.write(97);
fw.flush();//刷新缓冲区,流对象可以继续使用
fw.write('a');
fw.write('l');
fw.write(30000);
fw.close();
}
}
虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush方法了。
- flush :刷新缓冲区,流对象可以继续使用。
- close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
public class demo {
public static void main(String[] args) throws IOException {
FileWriter fw=new FileWriter("E:\\.txt");
char[] chars = "sw速度按市场".toCharArray();
fw.write(chars);
fw.write(chars,2,2);//写一部分
//fw.flush();
fw.close();
}
}
public class demo {
public static void main(String[] args) throws IOException {
FileWriter fw=new FileWriter("E:\\a.txt");
String chars = "sw速度按市场";
fw.write(chars);
fw.write(chars,2,2);
//fw.flush();
fw.close();
}
}
续写和换行
操作类似于FileOutputStream
public class demo {
public static void main(String[] args) throws IOException {
FileWriter fw=new FileWriter("E:\\a.txt",true);
String c = "市场";
for (int i = 0; i < 10; i++) {
fw.write(c+i+"\r\n");
}
fw.close();
}
}
字符流,只能操作文本文件,不能操作图片,视频等非文本文件。
当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流。
实际开发中并不一直把异常抛出这样处理,建议使用try…catch…finally 代码块,处理异常部分
JDK7前处理
public class HandleException1 {
public static void main(String[] args) {
// 声明变量
FileWriter fw = null;
try {
//创建流对象
fw = new FileWriter("fw.txt");
// 写出数据
fw.write("哈哈哈");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
JDK7的处理
还可以使用JDK7优化后的try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
public class HandleException2 {
public static void main(String[] args) {
// 创建流对象
try ( FileWriter fw = new FileWriter("fw.txt"); ) {
// 写出数据
fw.write("哈");
} catch (IOException e) {
e.printStackTrace();
}
}
}
JDK9的改进
JDK9中try-with-resource 的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close,我们来了解一下格式。
public class TryDemo {
public static void main(String[] args) throws IOException {
// 创建流对象
final FileReader fr = new FileReader("in.txt");
FileWriter fw = new FileWriter("out.txt");
// 引入到try中
try (fr; fw) {
// 定义变量
int b;
// 读取数据
while ((b = fr.read())!=-1) {
// 写出数据
fw.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
java.util.Properties 继承于Hashtable
唯一一个和IO流相结合。
Properties集合中的方法story,把集合中的临时数据,持久化写入到硬盘存储
Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
构造方法
public Properties()
:创建一个空的属性列表。基本的存储方法
public Object setProperty(String key, String value)
: 保存一对属性。public String getProperty(String key)
:使用此属性列表中指定的键搜索属性值。public Set stringPropertyNames()
:所有键的名称的集合(键值)。相当于Map中的keysetpublic class demm {
public static void main(String[] args) {
Properties prop=new Properties();
prop.setProperty("ss","155");
prop.setProperty("sd","154");
prop.setProperty("ssg","135");
Set<String> set=prop.stringPropertyNames();
for (String s : set) {
String va = prop.getProperty(s);
System.out.println(s+va);
}
}
}
与流相关的方法
public void store(OutputStream outStream, String comments)
:把集合中的临时数据,持久化写入到硬盘存储。public void store(Writer writer, String comments)
public void load(InputStream inStream)
: 从字节输入流中读取键值对。//不能中文public void load(Reader reader)
public static void main(String[] args) throws IOException {
Properties prop=new Properties();
prop.setProperty("ss","155");
prop.setProperty("sd","154");
prop.setProperty("ssg","135");
FileWriter fw=new FileWriter("E:\\a.txt");
prop.store(fw,"save");
fw.close();
}
public static void main(String[] args) throws IOException {
Properties prop=new Properties();
prop.load(new FileInputStream("E:\\a.txt"));
Set<String> set = prop.stringPropertyNames();
for (String s : set) {
String va = prop.getProperty(s);
System.out.println(s+va);
}
}
文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。
缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
构造方法
public BufferedInputStream(InputStream in
) :创建一个 新的缓冲输入流。BufferedInputStream(InputStream in, int size)
:创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。public BufferedOutputStream(OutputStream out, int size)
:创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
基本方法与普通字节流调用方式一致
构造方法
public BufferedReader(Reader in
) :创建一个 新的缓冲输入流。public BufferedReader(Reader in, int sz)
:创建一个使用指定大小输入缓冲区的缓冲字符输入流。public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。public BufferedWriter(Writer out, int sz)
:创建一个使用给定大小输出缓冲区的新缓冲字符输出流。// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
基本方法与普通字符流调用方式一致
特有方法
BufferedReader:public String readLine()
: 读一行文字。BufferedWriter:public void newLine()
: 写一行行分隔符,由系统属性定义符号。public static void main(String[] args) throws IOException {
BufferedReader bw=new BufferedReader(new FileReader("E:\\a.txt"));
String line=null;
while ((line=bw.readLine())!=null){
System.out.println(line);
System.out.println("========");
}
bw.close();
}
public static void main(String[] args) throws IOException {
BufferedWriter bw=new BufferedWriter(new FileWriter("E:\\a.txt",true));
bw.write("sHd从");
bw.newLine();
bw.write("sHd从");
bw.close();
}
字符编码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
指定了编码,它所对应的字符集自然就指定了。
ASCII字符集 :
ISO-8859-1字符集:
GBxxx字符集:
Unicode字符集 :
在IDEA中,使用FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,可能文件不是这个编码,就会出现乱码。
转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
InputStreamReader(InputStream in
): 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。public static void main(String[] args) throws IOException {
InputStreamReader isd=new InputStreamReader(new FileInputStream("E:\\a.txt"));
InputStreamReader isd2=new InputStreamReader(new FileInputStream("E:\\a.txt"),"GBK");
int read;
while ((read=isd.read())!=-1){
System.out.print((char)read);
}
isd.close();
while ((read=isd2.read())!=-1){
System.out.print((char)read);
}
isd.close();
}
转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
:创建一个指定字符集的字符流。public static void main(String[] args) throws IOException {
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("E:\\b.txt"));
osw.write("好是是");
osw.close();
OutputStreamWriter ow2=new OutputStreamWriter(new FileOutputStream("E:\\a.txt"),"GBK");
ow2.write("好是是");
ow2.close();
}
java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
public ObjectOutputStream(OutputStream out)
:创建一个指定OutputStream的ObjectOutputStream。构造举例,代码如下:
FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
序列化操作
1.一个对象要想序列化,必须满足两个条件:
public class Person implements java.io.Serializable{
private String name;
private transient int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + 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;
}
}
2.写出对象方法
public final void writeObject (Object obj
) : 将指定的对象写出。public static void main(String[] args) {
Person person = new Person();
person.setAge(18);
person.setName("美铝呀");
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\a.txt"));
out.writeObject(person);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
public ObjectInputStream(InputStream in)
: 创建一个指定InputStream的ObjectInputStream。反序列化操作1
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:
public final Object readObject ()
: 读取一个对象。
public static void main(String[] args) {
Person p=null;
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream("E:\\a.txt"));
p=(Person) in.readObject();
in.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(p);//Person{name='美铝呀', age=0},age没有被序列化
}
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。
反序列化操作2
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:
Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
...
}
平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
特点
构造方法
public PrintStream(String fileName)
: 使用指定的文件名创建一个新的打印流。
public PrintStream(File file)
: 使用指定的文件名创建一个新的打印流。
public PrintStream(OutputStream out)
: 使用指定的文件名创建一个新的打印流。
构造举例,代码如下:
PrintStream ps = new PrintStream("ps.txt");
如果使用继承父类的write方法写数据,那么查看数据时会查看编码表 97->a
如果使用自己特有的方法,原样输出 97->97
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("E:\\a.txt");
ps.write(97);
ps.println(97);
ps.close();
}
改变打印流向
System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个"小把戏",改变它的流向。
public static void main(String[] args) throws FileNotFoundException {
// 调用系统的打印流,控制台直接输出97
System.out.println(97);
// 创建打印流,指定文件的名称
PrintStream ps = new PrintStream("E:\\\\a.txt");
// 设置系统的打印流流向,输出到ps.txt
System.setOut(ps);
// 调用系统的打印流,ps.txt中输出97
System.out.println(97);
ps.close();
}
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。
java.net 包中提供了两种常见的网络协议的支持:
UDP:用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。
特点:数据被限制在64kb以内,超出这个范围就不能发送了。
数据报(Datagram):网络传输的基本单位
TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
协议
IP地址
ipconfig
ping 空格 IP地址
ping 220.181.57.216
127.0.0.1
、localhost
。端口号
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
常用的端口号
1.80端口 网络端口
2.数据库 mysql:3360 orcal:1521
3.Tomcat服务器:8080
TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
两端通信时步骤:
1.服务端程序,需要事先启动,等待客户端的连接。
2.客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
客户端与服务器端建立一个逻辑连接,这个连接中包含一个IO对象,通信的对象不仅仅是字符,所以IO对象是字节流对象。
在Java中,提供了两个类用于实现TCP通信程序:
1.客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
2.服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点,包含了IP地址和端口号的网络单位
构造方法
public Socket(String host, int port)
:创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。
成员方法
public InputStream getInputStream()
: 返回此套接字的输入流。public OutputStream getOutputStream()
: 返回此套接字的输出流。public void close()
:关闭此套接字。public void shutdownOutput()
: 禁用此套接字的输出流。客户端与服务器端进行交互,必须使用Socket中的提供的网络流,不能使用自己创建的流对象
当我们创建客户端对象Socket时,就会去请求服务器和服务器经过3次握手
此时服务器若没有启动,则会抛出异常
ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。
构造方法
public ServerSocket(int port)
:使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。成员方法
public Socket accept()
:侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。TCP通信分析图解
1.【服务端】启动,创建ServerSocket对象,等待连接。
2.【客户端】启动,创建Socket对象,请求连接。
3.【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4.【客户端】Socket对象,获取OutputStream,向服务端写出数据。
5.【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
到此,客户端向服务端发送数据成功。
自此,服务端向客户端回写数据。
6.【服务端】Socket对象,获取OutputStream,向客户端回写数据。
7.【客户端】Scoket对象,获取InputStream,解析回写数据。
8.【客户端】释放资源,断开连接。
客户端向服务器发送数据
public class server {
public static void main(String[] args) throws IOException {
System.out.println("服务器");
ServerSocket serverSocket=new ServerSocket(8888);
Socket server = serverSocket.accept();
InputStream in=server.getInputStream();
byte[] b = new byte[1024];
int len = in.read(b);
String msg = new String(b, 0, len);
System.out.println(msg);
in.close();
server.close();
}
}
public class client {
public static void main(String[] args) throws IOException {
System.out.println("客户端 发送数据");
Socket socket=new Socket("127.0.0.1",8888);
OutputStream outputStream=socket.getOutputStream();
outputStream.write("你好服务器".getBytes());
outputStream.close();
socket.close();
}
}
服务器向客户端回写数据
public class server {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
ServerSocket ss = new ServerSocket(6666);
Socket server = ss.accept();
InputStream is = server.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
String msg = new String(b, 0, len);
System.out.println(msg);
OutputStream out = server.getOutputStream();
out.write("我很好,谢谢你".getBytes());
out.close();
is.close();
server.close();
}
}
public class client {
public static void main(String[] args) throws IOException {
System.out.println("客户端 发送数据");
Socket client = new Socket("localhost", 6666);
OutputStream os = client.getOutputStream();
os.write("你好么? tcp ,我来了".getBytes());
InputStream in = client.getInputStream();
byte[] b = new byte[100];
int len = in.read(b);
System.out.println(new String(b, 0, len));
in.close();
os.close();
client.close();
}
}
原理:客户端使用本地的字节输出流,把文件上传到服务器,服务器把上传的文件保存到服务器的硬盘上,
客户端和服务器和本地硬盘进行读写,需要使用自己创建的字节流对象(本地流)
客户端和服务器之间进行读写,必须使用Socket中提供的字节流(网络流)
文件上传的原理,就是文件的复制
明确:数据源,数据目的地
public class fileClient {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\a.txt"));
Socket socket = new Socket("localhost", 6666);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] b = new byte[1024 * 8 ];
int len ;
while (( len = bis.read(b))!=-1) {
bos.write(b, 0, len);
bos.flush();
}
System.out.println("文件发送完毕");
bos.close();
socket.close();
bis.close();
System.out.println("文件上传完毕 ");
}
}
public class fileServer {
public static void main(String[] args) throws IOException {
System.out.println("服务器 启动ing");
ServerSocket socket=new ServerSocket(6666);
Socket sk=socket.accept();
BufferedInputStream bis = new BufferedInputStream(sk.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\k.txt"));
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
sk.close();
bis.close();
bos.close();
System.out.println("文件上传已保存");
}
}
文件上传优化分析
1.文件名称写死的问题
服务端保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,建议使用系统时间优化,保证文件名称唯一(自定义)
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名称
BufferedOutputStream bos = new BufferedOutputStream(fis);
2.循环接收的问题
服务端只保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断的接收不同用户的文件
// 每次接收新的连接,创建一个Socket
while(true){
Socket accept = serverSocket.accept();
......
}
3.效率问题
服务端在接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术优化
while(true){
Socket accept = serverSocket.accept();
// accept 交给子线程处理.
new Thread(() -> {
......
InputStream bis = accept.getInputStream();
......
}).start();
}
public class fileServer {
public static void main(String[] args) throws IOException {
System.out.println("服务器 启动ing");
ServerSocket socket=new ServerSocket(6666);
while(true){
Socket sk=socket.accept();
new Thread(() -> {
try {
BufferedInputStream bis = new BufferedInputStream(sk.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\"+System.currentTimeMillis()+".jpg"+".txt"));
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
bis.close();
bos.close();
System.out.println("文件上传已保存");
}catch (IOException e){
System.out.println(e);
}
}).start();
}
}
}
信息回写
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服务器 启动..... ");
// 1. 创建服务端ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
// 2. 循环接收,建立连接
while (true) {
Socket accept = serverSocket.accept();
/*
3. socket对象交给子线程处理,进行读写操作
Runnable接口中,只有一个run方法,使用lambda表达式简化格式
*/
new Thread(() -> {
try (
//3.1 获取输入流对象
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//3.2 创建输出流对象, 保存到本地 .
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
) {
// 3.3 读写数据
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
// 4.=======信息回写===========================
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上传成功".getBytes());
out.close();
//================================
//5. 关闭 资源
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保存");
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
public class FileUpload_Client {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 创建输入流,读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
// 1.2 创建输出流,写到服务端
Socket socket = new Socket("localhost", 6666);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//2.写出数据.
byte[] b = new byte[1024 * 8 ];
int len ;
while (( len = bis.read(b))!=-1) {
bos.write(b, 0, len);
}
// 关闭输出流,通知服务端,写出数据完毕
socket.shutdownOutput();
System.out.println("文件发送完毕");
// 3. =====解析回写============
InputStream in = socket.getInputStream();
byte[] back = new byte[20];
in.read(back);
System.out.println(new String(back));
in.close();
// ============================
// 4.释放资源
socket.close();
bis.close();
}
}
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
由于接口当中抽象方法的 public abstract 是可以省略的,所以定义一个函数式接口很简单:
public interface MyFunctionalInterface {
void myMethod();
}
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
对于刚刚定义好的 MyFunctionalInterface 函数式接口,典型使用场景就是作为方法的参数:
public class Demo09FunctionalInterface {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) {
inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法
doSomething(() ‐> System.out.println("Lambda执行啦!"));
}
}
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
性能浪费的日志案例
注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进行字符串拼接。例如: LOGGER.debug(“变量{}的取值为{}。”, “os”, “macOS”) ,其中的大括号 {} 为占位符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字符串拼接。这也是一种可行解决方案,但Lambda可以做到更好。
体验Lambda的更优写法
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, ()->msgA + msgB + msgC);
}
这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。
如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。
public class demo {
public static void startThread(Runnable run){
new Thread(run).start();
}
public static void main(String[] args) {
startThread(()->System.out.println(Thread.currentThread().getName()+"---->"+"线程启动"));
}
}
private static Comparator<String> newComparator() {
return (a, b) ‐> b.length() ‐ a.length();
}
public static void main(String[] args) {
String[] array = { "abc", "ab", "abcd" };
System.out.println(Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。
下面是最简单的几个接口及使用示例。
java.util.function.Supplier
接口仅包含一个无参的方法: T get()
。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
public class sa {
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(()-> msgA+msgB));
}
}
java.util.function.Consumer
接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
抽象方法:accept
Consumer 接口中包含抽象方法 void accept(T t)
,意为消费一个指定泛型的数据。基本使用如:
public class sa {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s-> System.out.println(s));
}
}
默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}
备注: java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合的情况:
private static void consumeString(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumeString(
s-> System.out.println(s.toLowerCase()),
s-> System.out.println(s.toUpperCase())
);
}
格式化打印信息
下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来。要求将打印姓名的动作作为第一个 Consumer 接口的Lambda实例,将打印性别的动作作为第二个 Consumer 接口的Lambda实例,将两个 Consumer 接口按照顺序“拼接”到一起。
public static void main(String[] args) {
String[] array = { "迪,女", "丽,女", "热巴,男" };
}
private static void consumeString(Consumer<String> one, Consumer<String> two,String arr[]) {
for (String s : arr) {
one.andThen(two).accept(s);
}
}
public static void main(String[] args) {
String[] array = { "迪,女", "丽,女", "热巴,男" };
consumeString(s-> System.out.print("姓名"+s.split(",")[0]),
s-> System.out.println("。姓别"+s.split(",")[1]+"。"),array);
}
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate
接口。
抽象方法:test
Predicate 接口中包含一个抽象方法: boolean test(T t)
。用于条件判断的场景:
private static boolean pre(String s, Predicate<String> predicate) {
return predicate.test(s);
}
public static void main(String[] args) {
String array = "迪,女, 丽女热巴,男";
boolean v=pre(array,(str)->str.length()>5);
System.out.println(v);
}
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and 。其JDK源码为:
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}
private static boolean pre(String s, Predicate<String> one,Predicate<String> two) {
return one.and(two).test(s);
//return one.test(s)&&two.test(s);
}
public static void main(String[] args) {
String array = "迪,女, 丽女热巴,男";
boolean v=pre(array,(str)->str.length()>5,(str)->str.contains(","));
System.out.println(v);
}
默认方法:or
与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”。JDK源码为:
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}
private static boolean pre(String s, Predicate<String> one,Predicate<String> two) {
return one.or(two).test(s);
//return one.test(s)||two.test(s);
}
public static void main(String[] args) {
String array = "迪,女, 丽女热巴,男";
boolean v=pre(array,(str)->str.length()>5,(str)->str.contains("h"));
System.out.println(v);
}
默认方法:negate
“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法 negate 的JDK源代码为:
default Predicate<T> negate() {
return (t) ‐> !test(t);
}
private static boolean pre(String s, Predicate<String> one) {
return one.negate().test(s);
//return !one.test(s);
}
public static void main(String[] args) {
String array = "迪,女, 丽女热巴,男";
boolean v=pre(array,(str)->str.length()>25);
System.out.println(v);
}
java.util.function.Function
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
抽象方法:apply
Function 接口中最主要的抽象方法为: R apply(T t)
,根据类型T的参数获取类型R的结果。
使用的场景例如:将 String 类型转换为 Integer 类型。
private static void method(String s,Function<String, Integer> function) {
int num = function.apply(s);
System.out.println(num + 20);
}
public static void main(String[] args) {
method("10",s->Integer.parseInt(s));
}
默认方法:andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。JDK源代码如:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
private static void method(String s,Function<String, Integer> one,Function<Integer, String> two) {
String num = one.andThen(two).apply(s);
System.out.println(num);
}
public static void main(String[] args) {
method("10",s->Integer.parseInt(s)+10,i->i+"20");//2020
}
几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元
素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。
循环遍历的弊端
Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行
了对比说明。
为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从
第一个到最后一个顺次处理的循环。前者是目的,后者是方式。
试想一下,如果希望对集合中的元素进行筛选过滤:
在Java 8之前的做法可能为:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
}
for (String name : shortList) {
System.out.println(name);
}
}
这段代码中含有三个循环,每一个作用不同:
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循
环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使
用另一个循环从头开始。
Stream的更优写法
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream().filter(s->s.startsWith("张")).filter(s->s.length()==3).forEach(System.out::println);
}
备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
Stream(流)是一个来自数据源的元素队列
和以前的Collection操作不同, Stream操作还有两个基础的特征:
当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结
果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以
像链条一样排列,变成一个管道。
java.util.stream.Stream
是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
举例
java.util.Collection
接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。java.util.Map
接口不是 Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况public static void main(String[] args) {
List<String> list = new ArrayList<>();
Stream<String> stream1=list.stream();
Set<String> set=new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String,String> map=new HashMap<>();
Set<String> keySet=map.keySet();
Stream<String> stream3=keySet.stream();
//必须单列转换
Collection<String> values=map.values();
Stream<String> stream4=values.stream();
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String,String>> stream5=entries.stream();
//数组
Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);
//可变参数可以传递数组
Integer[] arr={1,2,3,4,5};
Stream<Integer> stream7=Stream.of(arr);
String[] arr2={"s","df"};
Stream<String> stream8=Stream.of(arr2);
}
备注: of 方法的参数其实是一个可变参数,所以支持数组。
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
更多方法,请自行参考API文档。
逐一处理:forEach
虽然方法名字叫 forEach ,但是与for循环中的“for-each”昵称不同。void forEach(Consumer super T> action);
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。遍历之后就不能继续调用Stream流中的其他方法。
public static void main(String[] args) {
Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
stream.forEach(name‐> System.out.println(name));
}
过滤:filter
可以通过 filter 方法将一个流转换成另一个子集流。Stream
该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
该方法将会产生一个boolean值结果,代表指定的条件是否满足。如果结果为true,那么Stream流的 filter 方法将会留用元素;如果结果为false,那么 filter 方法将会舍弃元素。
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s ‐> s.startsWith("张"));
}
Stream流属于管道流,只能被消费(使用)一次,第一个Stream流调用完毕方法,数据就会转到下一个Stream上,而这时第一个Stream流已经使用完毕,就会关闭,所以第一个Stream流不能再调用方法。
映射:map
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
}
统计个数:count
正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:long count();
该方法返回一个long值代表元素个数(不再像旧集合那样是int值)
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s ‐> s.startsWith("张"));
System.out.println(result.count()); // 2
}
取用前几个:limit
limit 方法可以对流进行截取,只取用前n个。Stream
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 2
}
跳过前几个:skip
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:Stream
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
组合:concat
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :static
这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张无忌");
Stream<String> streamB = Stream.of("张翠山");
Stream<String> result = Stream.concat(streamA, streamB);
}
双冒号 ::
为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
语义分析
例如, System.out
对象中有一个重载的 println(String)
方法恰好就是我们所需要的。那么对于printString 方法的函数式接口参数,对比下面两种写法,完全等效:
注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常
推导与省略
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。
通过对象名引用成员方法,前提是对象名是已经存在的,成员方法也是已经存在的
public class Meth {
public void printUpperCase(String s){
System.out.println(s.toUpperCase());
}
}
public interface printable {
void print(String s);
}
public class hel {
public static void printst(printable p){
p.print("hello");
}
public static void main(String[] args) {
/*printst((s)->{
Meth obj=new Meth();
obj.printUpperCase(s);
});*/
Meth obj=new Meth();
printst(obj::printUpperCase);
}
}
通过类名引用静态方法,前提类已经存在,静态成员方法也已经存在。
public static void printst(int p,printable s){
System.out.println(s.print(p));
}
public static void main(String[] args) {
printst(-10,s->Math.abs(s));
printst(-10,Math::abs);
}
super是已经存在的,父类的成员方法也是存在的,可以直接使用super引用父类成员方法
public interface printable {
void greet();
}
public class Meth {
public void sayhello(){
System.out.println("Hello 我是:你爸");
}
}
public class m extends Meth {
@Override
public void sayhello() {
System.out.println("儿子");
}
public void me(printable g){
g.greet();
}
public void show(){
/*me(()->{
Meth h=new Meth();
h.sayhello();
});*/
//me(()->super.sayhello());
me(super::sayhello);
}
public static void main(String[] args) {
new m().show();
}
}
this是已经存在的,本类的成员方法存在,可以直接使用this引用本类的成员方法
@FunctionalInterface
public interface printable {
void buy();
}
public class Meth {
public void buyhouse(){
System.out.println("大别墅,爸");
}
public void marry( printable p){
p.buy();
}
public void kuaile(){
//marry(()->this.buyhouse());
marry(this::buyhouse);
}
public static void main(String[] args) {
new Meth().kuaile();
}
}
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new
的格式表示。
public class People {
private String name;
public People(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public interface PersonBuilder {
People buildPerson(String name);
}
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("好的话", People::new);
}
数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。
public interface ArrayBuilder {
int[] buildArray(int length);
}
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int arr[]=initArray(10,int[]::new);
System.out.println(arr.length);
}