Java基础学习13之Lambda及初识线程池

516 阅读9分钟

Java基础学习13之Lambda及初识线程池

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。

关于作者

  • 作者介绍

🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。


接口定义加强

image-20210815221420943

造成此种尴尬的局面的核心问题在于,接口只是一个方法的声明,而没有具体方法的实现,所以随着时间的推移,如果出现以上的问题,该接口将无法继续使用。从JDK1.8为了解决这个问题,专门提供两种专门的接口。

  1. 可以使用default来定义普通方法,需要通过对象调用。
  2. 可以使用static来定义静态方法,通过接口名就能使用。

定义普通方法

package com.annotation;
interface IMess{
	public default void fun(){//追加普通方法,又方法体了
		System.out.println("hello,world!");
	}
	public void print();
}
class MessImpl implements IMess{

	@Override
	public void print() {
		// TODO Auto-generated method stub
		System.out.println("www.baidu.com");
	}
	
}
public class Demo3 {
	public static void main(String[] args) {
		MessImpl msg = new MessImpl();
		msg.print();
		msg.fun();
	}
}

定义static方法

package com.annotation;
interface IMess{
	public default void fun(){//追加普通方法,又方法体了
		System.out.println("hello,world!");
	}
	public static IMess getInstance(){//定义静态方法
		return new MessImpl();
	}
	public void print();
}
class MessImpl implements IMess{

	@Override
	public void print() {
		// TODO Auto-generated method stub
		System.out.println("www.baidu.com");
	}
	
}
public class Demo3 {
	public static void main(String[] args) {
//		MessImpl msg = new MessImpl();
		IMess msg = IMess.getInstance();
		msg.print();
		msg.fun();
	}
}

整体来说,接口更像抽象类,但是比抽象类强大在于,接口的子类依然可以实现多继承的关系,而抽象类继续保持单继承。

Lambda表达式

最具有代表性的就是haskell.函数式编程和面向对象编程为两大开发阵营

传统的面向对象开发

package com.annotation;
interface IMess{
	public void print();
}
public class Demo3 {
	public static void main(String[] args) {
		IMess msg = new IMess(){
			public void print(){
				System.out.println("Hello,world!");
			};
		};
		msg.print();
	}
}

使用匿名内部类来实现接口最大好处就是节约了一个文件,最大的缺点就是看的眼花缭乱,对于此操作有了更简化的体现。如果采用函数式编程模型。

函数式编程模型

package com.annotation;
interface IMess{
	public void print();
}
public class Demo3 {
	public static void main(String[] args) {
		IMess msg = ()->System.out.println("Hello,world!");
		msg.print();
	}
}

如果想要使用函数式编程前提,接口只能写一个方法,如果编写两个方法,则会出现语法错误。可以在接口的开头加上@FunctionalInterface声明这是一个函数接口。

实际上对于以上的语法形式:

  • (参数 )->单行语句;

image-20210815224619650

这个时候方法本身只包含一行语句,那么直接编写语句即可,如果要是有多行语句,则就需要我们使用"{}"

package com.annotation;
@FunctionalInterface
interface IMess{
	public void print();
}
public class Demo3 {
	public static void main(String[] args) {
		IMess msg = ()->{
			System.out.println("Hello,world!");
			System.out.println("Hello,world!");
			System.out.println("Hello,world!");
		};
		msg.print();
	}
}

如果现在你的表达式里面的内容只是一行进行数据的返回,那么直接使用语句即可,不用写return。

package com.annotation;
interface IMath{
	public int add(int x, int y);
}
public class Demo4 {
	public static void main(String[] args) {
		IMath msg = (p1, p2)-> p1 + p2;
		System.out.println(msg.add(10, 20));
	}
}

线程池

线程池就是多个线程封装在一起操作。

线程池概念

在生活中经常遇见,今天开发一个项目需要20个人一起开发

  • 这个活很大,有多少人要多少人

  • 这个活很大,但是要求10个人

  • 这个活很大,但只要求1个人做

image-20210817100145027

追加一个并发访问的程序报:java.util.concurrent,对于此线程池的操作的核心类和接口就定义在之中。这里面有两个核心的接口:

  1. 普通的执行线程池定义:Interface ExecutorService
  2. 调度线程池:Interface ScheduledExecutorService

如果要进行线程池的创建一般使用Class Executors这个类

  • 创建无法小限制的线程池:public static ExecutorService newCachedThreadPool()
  • 创建固定大小的线程池:public static ExecutorService newFixedThreadPool(int nThreads)
  • 单线程池:public static ExecutorService newSingleThreadExecutor()
  • 创建定时调度池:public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

线程池的实现

创建无限大小的线程池

package com.day13.demo;

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

public class PoolDemo1 {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		//创建了一个线程池的模型,但是后面没有线程
		ExecutorService executorService = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			int index = i;
			Thread.sleep(200);
			executorService.submit(()-> {
				System.out.println(Thread.currentThread().getName() + ",i = " + index);
			});
		}
		executorService.shutdown();
	}
}

创建固定大小的线程池

package com.day13.demo;

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

public class PoolDemo1 {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		//创建了一个线程池的模型,但是后面没有线程
		ExecutorService executorService = Executors.newFixedThreadPool(3);
		for (int i = 0; i < 10; i++) {
			int index = i;
			Thread.sleep(200);
			executorService.submit(()-> {
				System.out.println(Thread.currentThread().getName() + ",i = " + index);
			});
		}
		executorService.shutdown();
	}
}

创建我们的单线程线程池

package com.day13.demo;

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

public class PoolDemo1 {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		//创建了一个线程池的模型,但是后面没有线程
		ExecutorService executorService = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10; i++) {
			int index = i;
			executorService.submit(()-> {
				System.out.println(Thread.currentThread().getName() + ",i = " + index);
			});
		}
		executorService.shutdown();
	}
}

定时调度池

package com.day13.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class PoolDemo1 {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		//创建了一哥具备有三个线程大小的定时调度池
		ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
		for (int i = 0; i < 10; i++) {
			//Thread.sleep(200);
			int index = i;
			executorService.scheduleAtFixedRate(new Runnable(){
				@Override
				public void run() {
					// TODO Auto-generated method stub
						System.out.println(Thread.currentThread().getName() + ",i = " + index);
				}
				
			}, 3, 2, TimeUnit.SECONDS);//使用的是一个秒的单位,表示3秒后开始执行,而后每过2秒执行一次
		}
	}
}

线程池给我们开发者带来唯一好处的就是允许多个线程按照组的模式进行程序的处理,这样在一个业务逻辑非常复杂的情况下,性能就会得到很好的提升。

StringBuffer类

对于类库的学习,不可能全学完。你所需要知道就是如何面对解决问题的方法。要学会查询文档。

StringBuffer类之前首先来简单回顾一下String类的特点:

​ String类的对象有两种实例化方式,一种是直接赋值,只会开辟一块堆内存空间,而且对象可以自动入池,另外一种方式使用构造方法完成,会开辟两块空间,有一块空间将称为垃圾,并且不会自动入池,但是可以通过intern()方法手工入池;

​ 字符串常亮一旦声明则不能改变,而字符串对象可以改变,但是改变的是其内存地址的指向;

通过以上的几个特点就可以清楚的发现,String类是表示字符串使用最多的类,但是其不合适于被频繁修改的字符串操作上,所以在这种情况下,往往可以使用StringBuffer类,即:StringBuffer类方便用户进行内容的修改,在String类之中使用“+”作为数据库的连接操作,而在StringBuffer类之中使用append()方法进行数据的连接。

使用StringBuffer操作,StringBuffer内容可以改变。

package com.day13.demo;

public class BufferDemo {
	public static void main(String[] args) {
		StringBuffer buf = new StringBuffer();
		buf.append("hello").append(",world!");
		fun(buf);//修改了buf的内容
		System.out.println(buf);
	}
	public static void fun(StringBuffer tem){
		tem.append("\n").append("zsr");
	}
}

String和StringBuffer最大的区别就是:String的内容无法修改,而StringBuffer的内容可以修改。但是在开发的选择还是优先选择String类。

现在学习的字符串的操作类就有两个了:String,StringBuffer,那么下面通过这两个类的定义来研究一下关系:

String类:StringBuffer类:
public final class String
extends Object
implements Serializable,Comparable,CharSequence
public final class StringBuffer
extends Object
implements Serializable, CharSequence

可以发现两个类都是“CharSequence”接口的子类。这个接口描述的是字符集,所以串就属于字符集的子类,如果以后看见CharSequence最简单的联想就是字符串。但是有一个小小的问题需要注意一下就是String和StringBuffer两个类型无法直接转换。

利用StringBuffer:利用StringBuffer构造方法、append()方法

将String变为StringBuffer

1.直接利用StringBuffer类的构造方法,public StringBuffer(String str)

package com.day13.demo;

public class BufferTest{
	public static void main(String[] args) throws Exception {
		String str = "Hello World.";
		StringBuffer buf = new StringBuffer(str);
		fun(buf);
		System.out.println(buf);
}
	private static void fun(StringBuffer temp) {
		temp.append("\n").append("zsr");
	}
}

2.利用StringBuffer类的append()方法

package com.day13.demo;

public class BufferTest{
	public static void main(String[] args) throws Exception {
		String str = "Hello World.";
		StringBuffer buf = new StringBuffer();
		buf.append(str);
		fun(buf);
		System.out.println(sb);
	}
	private static void fun(StringBuffer temp) {
		temp.append("\n").append("zsr");
	}
}

将StringBuffer变成String,利用StringBuffer类的toString()方法完成

package com.day13.demo;

public class BufferTest{
	public static void main(String[] args) throws Exception {
		StringBuffer buf = new StringBuffer("hello,World!");
		String str = buf.toString();
		System.out.println(str);
	}
}

实际上StringBuffer还是有一些String类所没有的特点的。

1.字符串反转操作,public StringBuffer reverse()

package com.day13.demo;

public class BufferTest{
	public static void main(String[] args) throws Exception {
		StringBuffer buf = new StringBuffer("hello,World!");
		System.out.println(buf.reverse());
	}
}

2.删除指定范围内的数据,public StringBuffer delete(int start, int end)

package com.day13.demo;

public class BufferDemo {
	public static void main(String[] args) {
		StringBuffer buf = new StringBuffer("Hello,World!");
		System.out.println(buf.delete(5, 11));
    }
}

3.插入数据的方法, public StringBuffer insert(int offset, Object obj)

package com.day13.demo;

public class BufferDemo {
	public static void main(String[] args) {
		StringBuffer buf = new StringBuffer("Hello,World!");
		System.out.println(buf.delete(5,11).insert(0, "你好,"));
	}
}

面试题:请解释String、StringBuffer、StringBuilder的区别?

  • String的内容不可以修改,StringBuffer与StringBuilder内容可以修改
  • StringBuffer采用同步处理属于线程安全操作,StringBuilder采用异步处理属于线程不安全操作。

Runtime类

在每一个JVM的进程中,都会存在一个运行时的操作类的对象,而这对象所属的类型就是Runtime类。打开这个类的文档,发现这个类之中并没有构造方法的定义,可是按照之前所学,每个类至少有一个构造方法,而这个类的构造方法实际上存在只是不被外部看见而已,因为构造方法私有化了,这是一个标准的单例设计模式,既然是单例设计模式则在这个类之中一定会存在一个static型方法,可以取得本类的Runtime实例化对象:public static Runtime getRuntime()。

取得了Runtime类之后最主要的功能就是可以通过它来观察当前的内存操作情况:

方法名称类型描述
public long freeMemory()普通取得当前空余内存空间大小
public long totalMemory()普通取得当前可以使用总空间大小
public long maxMemory()普通取得最大的可用内存空间的大小
public native void gc()普通执行垃圾收集处理

观察一下内存信息的取得

package com.day13.demo;

public class RuntimeDemo {
	public static void main(String[] args) {
		Runtime run = Runtime.getRuntime();
		System.out.println("1、MAX=" + byteToM(run.maxMemory()));
		System.out.println("1、TOTAL=" +  byteToM(run.totalMemory()));
		System.out.println("1、FREE=" +  byteToM(run.freeMemory()));
	}
	public static double byteToM(long num){
		return (double) num / 1024 / 1024;
	}
}

gc垃圾回收

package com.day13.demo;

public class RuntimeDemo {
	public static void main(String[] args) {
		Runtime run = Runtime.getRuntime();
		System.out.println("1、MAX=" + byteToM(run.maxMemory()));
		System.out.println("1、TOTAL=" +  byteToM(run.totalMemory()));
		System.out.println("1、FREE=" +  byteToM(run.freeMemory()));
		String str = "";
		for (int i = 0; i < 2222; i++) {
			str += i;
		}
		System.out.println("2、MAX=" + byteToM(run.maxMemory()));
		System.out.println("2、TOTAL=" +  byteToM(run.totalMemory()));
		System.out.println("2、FREE=" +  byteToM(run.freeMemory()));
		run.gc();//垃圾收集
		System.out.println("3、MAX=" + byteToM(run.maxMemory()));
		System.out.println("3、TOTAL=" +  byteToM(run.totalMemory()));
		System.out.println("3、FREE=" +  byteToM(run.freeMemory()));
	}
	public static double byteToM(long num){
		return (double) num / 1024 / 1024;
	}
}

面试题:什么叫gc?如何处理

  • gc(Garbage Conllector):垃圾收集器,用于释放无用的内存空间
  • gc有两种处理形式,一种是自动不定期调用,另外一种是使用Runtime的gc()方法手工处理调用。

System类

实际上在之前进行的数组拷贝就是运用System类中的public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)

在这个类中提供有一个取得当前日期时间数的方法**public static long currentTimeMillis();**通过此方法可以取得某一个操作花费的时间。

观察currentTimeMillis()的使用

package com.day13.demo;

public class SystemDemo {
	public static void main(String[] args) {
		long start = System.currentTimeMillis();
		String str = "";
		for (int i = 0; i < 2222; i++) {
			str += i;
		}
		long end = System.currentTimeMillis();
		System.out.println("花费时间:" + (end - start) + "ms");
	}
}

可是在System类之中还存在了一个很有意思的方法:public static void gc(),但是这个gc()方法并不是一个新的gc()方法而是间接调用了一个Runtime类之中的gc()方法,不表示一个重写的方法。

在之前一直强调过一个概念:一个类对象的创建一定要使用构造方法,那么一个对象不使用构造方法了,就一个被释放,被释放的时候一改也有一个方法进行支持才对。所以要想做这种收尾的操作,可以让一个类去覆写object中的finalize()方法。此方法由Object类定义:protected void finalize() throws Throwable。在对象回收之前有可能出现异常或者错误,但是即使出现了也不会影响程序的执行,即:不会因为异常而导致程序的中断执行。

finalize()方法使用

package com.day13.demo;
class Person{
	public Person(){
		System.out.println("问啊娃娃,出来了!");
	}

	@Override
	protected void finalize() throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("我要下地狱了,下辈子不要当人了——");
		throw new Exception("继续要活几千年");
	}
	
}
public class FinalizeDemo {
	public static void main(String[] args) {
		Person person = new Person();
		person = null;
		System.out.println("已经转世不为人了");
		System.gc();
	}
}

面试题:请解释final、finally、finalize的区别?

  • final是一个关键字,用于定义不能够被继承的父类、不能够覆写的常量
  • finally是异常处理的统一出口
  • finalize是Object类中的方法,用于在对象回收前进行调用

对象克隆

克隆就是对象复制的一个概念,不过这种概念一般使用的比较少,因为很少有人去复制已经存在的对象。Object类本身就支持对象克隆方法。可以发现protected Object clone() throws CloneNotSupportedException;我们要想实现克隆,那么我们并不是所有类的对象可以随便克隆,需要被克隆的对象所在类一定要实现Cloneable接口,而最关键的是该接口并没有任何的抽象方法,所以该接口只是一个标识接口,表示一种能力。

对象克隆实现

package com.day13.demo;
class Per implements Cloneable{//必须实现此接口
	private String name;
	private int age;
	public Per(String name, int age) {
		super();
		this.name = name;
		this.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;
	}
	@Override
	public String toString() {
		return "Per [name=" + name + ", age=" + age + "]";
	}
	@Override
	//覆写权限扩大  protected 扩大到 public
	public Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();//父类负责克隆
	}
}
public class CloneDemo {
	public static void main(String[] args) throws Exception{
		 Per perA = new Per("kaco",12);
		 //perA.clone();不能在这写的原因是因为此方法是protected权限 只能在不同包的子类中实现此方法
		 Per perB = (Per) perA.clone();
		 perB.setAge(100);
		 System.out.println(perA);
		 System.out.println(perB);
	}
}

意义不大,需要清楚表示接口的作用,表示的是一个能力。