目录
前言:
这节中小编主要与大家分享一下有关于String方面之前没有分享的剩下的知识点,主要了解什么是字符串常量池,创建对象时的一些小知识点,在反射、枚举以及lambda表达式中主要掌握反射以及枚举以及lambda表达式的基本使用,在泛型进阶中我们主要了解通配符的使用,好了话不多说我们来给大家一一讲解吧。
1. String
1.1 字符串常量池
1.1.1 创建对象的思考
我们先来思考一下下面这两种创建String对象的方式是否相同。
代码:
package 再谈String;
public class Test1 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s3 == s4);//false
}
}
结果:
在上述的结果中我们可以看到s1和s2引用的是同一个对象,而s3和s4不是同一个对象这是为什么呢?
在Java程序中,类似于:1,2,3,3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快更节省内存,Java为8种基本数据类型和String类都提供了常量池。
“池”是编程中常见的一种概念,是重要的提升效率的一种方式,我们会在未来的学习中遇到各种“内存池”、“线程池”、“数据库连接池”......
举个例子,比如:在大学的时候家里给大家大生活费的方式
- 家里经济拮据,每个月定时打生活费,有时候可能会晚,最差的情况下可能需要向家里张口要,速度就会比较慢。
- 家里有矿,一次性打一年的生活费放到银行卡中,随用随取,效率非常的高,常见的池的技术比如:数据库连接池、线程池等。
为了节省存储空间以及程序的运行效率,java中引入了:
- class文件常量池:每个.java源文件编译后生成.class文件中会保存当前类中的字面常量以及符号信息。
- 运行时常量池:在.class文件被加载时,.class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份。
- 字符串常量池
1.1.2 字符串常量池(StringTable)
字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(一种高效用来查找的数据结构),不同JDK版本下字符串常量池的位置以及默认大小是不同的:
JDK版本 | 字符串常量池的位置 | 大小设置 |
Java6 | (方法区)永久代 | 固定大小:1009 |
Java7 | 堆中 | 可设置,没有大小限制,默认大小:60013 |
Java8 | 堆中 | 可设置,有范围限制,最小是1009 |
关于方法区、堆等内存结果的具体局部,后续JVM中会给大家详细介绍。
1.1.3 再谈String对象创建
由于不同的JDK版本对字符串常量池的处理方式不同,此处在java8 HotSpot上分析。
1.直接使用字符串常量进行赋值
代码:
package 再谈String;
public class Test2 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);//true
}
}
结果:
2.通过new创建String类对象
代码:
package 再谈String;
public class Test3 {
public static void main(String[] args) {
String s3 = new String("world");
String s4 = new String("world");
System.out.println(s3 == s4);//false
}
}
结果:
下面来简单模拟一下创建对象的过程。
从上面我们可以看出只要是new出来的对象,都是唯一的。
通过上述的例子可以看出来,使用常量串创建String类型对象的效率更高,而且更节省空间,用户也可以将创建出来的字符串对象通过intern方式添加进字符串常量池中。至于什么是intern方法我们下面来给大家介绍一下。
1.1.4 intern方法
intern是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。
代码:
package 再谈String;
public class Test4 {
public static void main(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch);//s1对象并不在常量池中
s1.intern();//s1.intern;调用之后,会将s1对象的引用放入到常量池中
String s2 = "abc";//"abc"在常量池中存在了,s2创建时直接使用常量池中的"abc"的引用
System.out.println(s1 == s2);
}
}
结果:
注意:如果没有调用intern函数的话,那么结果就是false,在Java6和Java7、8中Intern的实现会有些许的差别。
2. 反射
2.1 反射的定义
Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到,那么我们就可以修改部分信息;这种动态获取信息以及动态调用对象的方法功能就称之为java语言的反射(reflection)机制。
2.2 反射的用途
- 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或者是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所有私有成员或是方法。
- 反射最重要的用途就是开发各种通用框架,比如在Spring中,我们将所有的类Bean交给Spring容器管理,无论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依注入时,容器会读取配置,而配置中给的就是类的信息,Spring根据这些信息,需要创建哪些Bean,Spring就动态创建这些类。
2.3 反射的基本信息
Java程序中许多对象在运行时会出现两种类型:运行时类型(RTTI)和编译时类型,例如Person p = new Student();这句代码中p在编译时类型为Person,运行时类型就为Student,程序需要在运行时发现对象和类的真实信息,而通过使用反射程序就能判断出该对象和类属于哪些类。
2.4 反射相关的类
类名 | 用途 |
class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量/类的属性 |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
2.4.1 Class类(反射机制的起源)
Class类代表类的实体,在运行的Java应用程序中表示类和接口。
Java文件被编译后,生成了.class文件,JVM此时就去要解读.class文件,被编译后的Java文件.class也被解析为一个对象,这个对象就是java.lang.Class。这样当程序在运行时,每个java文件最终变成了Class类对象的一个实例。我们通过Java的反射机制应用到这个实例,就可以去获取甚至去添改变这个类的属性和动作,使得这个类成为一个动态的类。
2.4.1.1 Class类中的相关方法
- 常获得类相关的方法:
方法 | 用途 |
getClassLoader() | 获得类的加载器 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的) |
forName(String className) | 根据类名返回类的对象 |
newInstance() | 创建类的实例 |
getName() | 获得类的完整路径的名字 |
- 常用获得类中属性相关的方法:(以下返回值为Field)
方法 | 用途 |
getField(String name) | 获得某个公有属性对象 |
getField() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
- 获得类中注解相关的方法:
方法 | 用途 |
getAnnotation(Class annotationClass) | 返回该类中参与类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有公有注解对象 |
getDeclaredAnnotation(Class annotationClass) | 返回该类中参与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所哟的注解对象 |
- 获得类中构造器相关的方法(以下返回值为Constructor)
方法 | 用途 |
getConstructor(Class...<?>parameterTypes) | 获得该类中与参数匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?>parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法(以下方法返回值为Method)方法
方法 | 用途 |
getMethod(String name,Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
2.5 反射实例
2.5.1 获得Class对象的三种方式
在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射的目的,即:在运行状态中,对于任意一个类,都能够知道这个类所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到,我们就可以修改部分类型的信息。
第一种:使用Class.forName("类的全路径名");静态方法。
前提:已经明确类的全路径名。
第二种:使用.class方法。
说明:仅适合在编译前就已经明确要操作的Class。
第三种:使用类对象的getClass()方法。
代码演示:
package 反射;
class Student{
//私有属性
private String name = "bit";
//公有属性
public int age = 19;
//不带参数的构造方法
public Student() {
System.out.println("Student()");
}
private Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Student(String,name)");
}
private void eat() {
System.out.println("i am eat");
}
public void sleep() {
System.out.println("i am pig");
}
private void function(String str) {
System.out.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test1 {
public static void main(String[] args) {
//1.通过getClass获取Class对象
Student s1 = new Student();
Class<?> c1 = s1.getClass();
//2.直接通过类名.class的方式得到,该方法最为安全可靠,程序性能更高
// 这说明任何一个类都有一个隐含的静态成员变量class
Class<?> c2 = Student.class;
//3.通过Class对象的forName()静态方法来获取,用的最多,
// 但可能会抛出ClassNotFoundException异常
Class<?> c3 = null;
try {
//注意这里是类的全路径,如果有包需要加包的路径
c3 = Class.forName("反射.Student");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
//一个类只能拥有一个class对象
//一个类在JVM中只会有一个Class实例,即我们对上面获取的
// c1,c2,c3进行equals比较,发现都是true
System.out.println(c1.equals(c2));
System.out.println(c1.equals(c3));
System.out.println(c2.equals(c3));
}
}
结果展示:
注意:
在上述三种获取class对象的方法中我们最常使用的就是第三种方法。通过.forName来进行获取。
2.5.2 反射的使用
接下来我们开始使用反射,我们依旧反射上面的Student类,把反射的逻辑写到另外的类当中进行理解。
注意:所有和反射相关的包都在import java.lang.reflect包下面。
2.5.2.1 反射创建对象
代码:
package 反射;
//在类外通过反射来创建出一个对象
public class ReflectClassDemo {
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("反射.Student");
Student student = (Student) c1.newInstance();
System.out.println("学生对象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
reflectNewInstance();
}
}
结果:
通过反射创建出来一个对象之后然后调用之前类中的不带参数的构造方法。
2.5.2.2 反射调用私有的构造方法
代码:
package 反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//在类外通过反射来创建出一个对象
public class ReflectClassDemo {
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("反射.Student");
Student student = (Student) c1.newInstance();
System.out.println("学生对象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("反射.Student");
Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
//相当于是程序的开关,确定是否运行上述代码
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("gaolele",20);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// reflectNewInstance();
reflectPrivateConstructor();
}
}
结果:
2.5.2.3 反射获取私有的属性
代码:
package 反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
//在类外通过反射来创建出一个对象
public class ReflectClassDemo {
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("反射.Student");
Student student = (Student) c1.newInstance();
System.out.println("学生对象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("反射.Student");
Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
//相当于是程序的开关,确定是否运行上述代码
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("gaolele",20);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void reflectPrivateField() {
try {
Class<?> c1 = Class.forName("反射.Student");
Field field = c1.getDeclaredField("name");
field.setAccessible(true);
//获取一个对象
Student student = (Student) c1.newInstance();
field.set(student,"唐老鸭");
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// reflectNewInstance();
// reflectPrivateConstructor();
reflectPrivateField();
}
}
结果:
2.5.2.4 反射获取私有方法
代码:
package 反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectClassDemo {
//在类外通过反射来创建出一个对象
public static void reflectNewInstance() {
try {
Class<?> c1 = Class.forName("反射.Student");
Student student = (Student) c1.newInstance();
System.out.println("学生对象:" + student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//通过反射调用私有的构造方法
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("反射.Student");
Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
//相当于是程序的开关,确定是否运行上述代码
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("gaolele",20);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//通过反射获取私有属性
public static void reflectPrivateField() {
try {
Class<?> c1 = Class.forName("反射.Student");
Field field = c1.getDeclaredField("name");
field.setAccessible(true);
//获取一个对象
Student student = (Student) c1.newInstance();
field.set(student,"唐老鸭");
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//通过反射调用私有方法
public static void reflectPrivateMethod() {
try {
Class<?> c1 = Class.forName("反射.Student");
Method method = c1.getDeclaredMethod("function", String.class);
method.setAccessible(true);
//获取一个对象
Student student = (Student) c1.newInstance();
method.invoke(student,"我是一个参数!");
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// reflectNewInstance();
// reflectPrivateConstructor();
// reflectPrivateField();
reflectPrivateMethod();
}
}
结果:
2.5.3 反射的优缺点
优点:
- 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。
- 增加程序的灵活性和扩展性,降低耦合性,提高子树自适能力。
- 反射已经运用在了很多流行框架中,如:Struts、Hibernate、Spring等等。
缺点:
- 使用反射会有效率问题,会导致程序效率降低。
- 反射技术绕过了源代码的技术,因而会带来维护问题,反射代码比相应的直接更复杂。
3. 枚举
枚举是在JDK1.5以后引入的。主要用途是:将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式:
public static final int RED = 1;
public static final int GREEN = 2;
public static final int BLACK = 3;
但是常量举例有不好的地方,例如:可能碰巧有个数字1,但是他有可能误会为是RED,现在我们可以直接用枚举来进行组织,这样一来,就拥有了类型,枚举类型。而不是普通的整形1。
public enum TestEnum{
RED,BLACK,GREEN;
}
优点:将常量组织起来统一进行管理
场景:错误状态码,消息类型,颜色的划分,状态机等等...
本质:是java.lang.Enum的子类,也就是说,自己写的枚举类,就算没有显示的继承Enum,但是其默认继承了这个类。
3.1 枚举的使用
代码实例:
package 枚举;
public class Test1 {
public enum TestEnum{
RED,BLACK,WHITE,GREED;
}
public static void main(String[] args) {
TestEnum testEnum = TestEnum.BLACK;
switch (testEnum) {
case RED:
System.out.println("red");
break;
case BLACK:
System.out.println("black");
break;
case WHITE:
System.out.println("white");
break;
case GREED:
System.out.println("greed");
break;
default:
break;
}
}
}
结果展示:
3.2 枚举的常用方法
Enum类的常用方法
方法名称 | 描述 |
values() | 以数组形式返回枚举类型的所有成员 |
ordinal() | 获取枚举成员的索引位置 |
valueOf() | 将普通字符串转换为枚举实例 |
comparTo() | 比较两个枚举成员在定义时的顺序 |
代码1:
package 枚举;
public class Test2 {
public enum TestEnum{
RED,BLACK,GREEN,WHITE;
}
public static void main(String[] args) {
//以数组的形式返回枚举类型的所有实例
TestEnum[] testEnums = TestEnum.values();
//打印枚举成员以及获取枚举成员的索引位置
for (int i = 0; i < testEnums.length; i++) {
System.out.println(testEnums[i] + " " + testEnums[i].ordinal());
}
System.out.println("======================");
//将普通字符串转换为枚举实例
System.out.println(TestEnum.valueOf("GREEN"));
}
}
结果1:
代码2:
package 枚举;
import static 枚举.Test3.TestEnum.BLACK;
import static 枚举.Test3.TestEnum.RED;
public class Test3 {
public enum TestEnum{
RED,BLACK,GREEN,WHITE;
}
public static void main(String[] args) {
//拿到枚举实例BLACK
TestEnum testEnum = BLACK;
//拿到枚举实例RED
TestEnum testEnum1 = RED;
System.out.println(testEnum.compareTo(testEnum1));
System.out.println(BLACK.compareTo(RED));
System.out.println(RED.compareTo(BLACK));
}
}
结果2:
刚刚说过,在java当中枚举实际上就是一个类,所以我们在定义枚举的时候,还可以这样定义和使用枚举:
注意:枚举的构造方法默认是私有的
代码:
package 枚举;
import javax.swing.text.html.parser.TagElement;
public class Test4 {
public enum TestEnum{
RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
private String name;
private int key;
/**
* 1.当枚举对象有参数后,需要提供相应的构造函数
* 2.枚举的构造函数默认是私有的,这个一定要记住
*/
private TestEnum(String name, int age) {
this.name = name;
this.key = age;
}
//获取枚举类中的索引值所对应的枚举成员
public static TestEnum getEnumKey(int key){
for (TestEnum t : TestEnum.values()) {
if (t.key == key) {
return t;
}
}
return null;
}
public static void main(String[] args) {
System.out.println(getEnumKey(2));
}
}
}
结果:
3.3 枚举的优缺点
优点:
- 枚举常量更简单安全。
- 枚举具有内置方法,代码更优雅。
缺点:
- 不可以继承,无法扩展。
4. 枚举和反射
4.1 枚举是否可以通过反射,拿到实例对象呢?
我们刚刚在反射里边看到了,任何一个类,哪怕其构造方法是私有的,我们也可以通过反射拿到它的实例对象,那么枚举的构造方法也是私有的,我们是否可以拿到呢?接下来小编就带着大家一起来探究一下吧。
同样我们使用上面提供的枚举类来进行举例。
代码:
package 反射与枚举;
import java.lang.reflect.Constructor;
public enum TestEnum {
RED("red", 1), BLACK("black", 2), WHITE("white", 3), GREEN("green", 4);
private String name;
private int key;
private TestEnum(String name, int key) {
this.name = name;
this.key = key;
}
public static TestEnum getEnumKey(int key) {
for (TestEnum t : TestEnum.values()) {
if (t.key == key) {
return t;
}
}
return null;
}
public static void reflectPrivateConstructor() {
try {
Class<?> c1 = Class.forName("反射与枚举.TestEnum");
//注意传入对应的参数,获得对应的构造对象,当前枚举类是提供了两个参数
//分别是String和int
Constructor<?> declareConstructorStudent = c1.getDeclaredConstructor(String.class, int.class,String.class, int.class);
//设置为true后可修改访问权限
declareConstructorStudent.setAccessible(true);
Object objectStudent = declareConstructorStudent.newInstance("绿色",666,"橙色",666);
TestEnum testEnum = (TestEnum) objectStudent;
System.out.println("获得枚举的私有构造函数:" + testEnum);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static void main(String[] args) {
reflectPrivateConstructor();
}
}
结果:
注意:上述结果我们可以看出枚举可以避免反射问题。
5. Lambda表达式
lambda表达式是JavaSE8中一个重要的新特性,lambda表达式允许你通过表达式功能接口,lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数主体(body,可以是一个表达式或一个代码块)。lambda表达式是基于数学中的λ演算得名,也可以称为闭包。
5.1 lambda表达式的语法
基本语法:(parameters)-> expression或(parameters)->{statements;}
lambda表达式由三部分组成:
- paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数,这里的参数类型可以明确的声明也可以不声明有JVM隐含的推断,另外当只有一个推断类型时可以省略掉圆括号。
- ->:可理解为“被用于”的意思。
- 方法体:可以是表达式也可以是代码块,是函数式接口里的方法的实现,代码块可以返回一个值或者什么都不返回,这里的代码块等同于方法的方法体,如果是表达式,也可以返回一个值或者什么都不返回。
5.2 函数式接口
要了解lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法。
注意:
- 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口。
- 如果我们在某个接口上声明了@FunctionalInterface注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以从某种意义上来说,只有你保证你的接口中只有一个抽象方法,你可以不加这个注解,加上就会自动进行检测。
定义方式:
@FunctionalInterface interface NoParameterNoReturn{ //注意:只能有一个抽象方法 void test(); }
也可以通过下述方法来定义:
@FunctionalInterface interface NoParameterNoReturn{ void test1(); default void test() { System.out.println("JDK1.8新特征,default默认方法可以有具体的实现"); } }
5.3 Lambda表达式的基本使用
首先我们先来准备好几个接口:
@FunctionalInterface
interface NoParameterNoReturn{
//注意:只能有一个抽象方法
void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn{
void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn{
void test(int a, double b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn{
int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn{
int test(int a);
}
//有返回值多个参数
@FunctionalInterface
interface MoreParameterReturn{
int test(int a, int b);
}
我们在上面提到过,lambda可以理解为:lambda就是匿名内部类的简化,实际上是创建了一个类,实现了接口,重写了接口的方法。
没有使用lambda表达式的时候调用方式:
public static void main(String[] args) {
NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
@Override
public void test() {
System.out.println("hello");
}
};
noParameterNoReturn.test();
}
具体的使用如下代码所示:
package lambda表达式;
@FunctionalInterface
interface NoParameterNoReturn{
//注意:只能有一个抽象方法
void test();
}
//@FunctionalInterface
//interface NoParameterNoReturn{
// void test1();
// default void test() {
// System.out.println("JDK1.8新特征,default默认方法可以有具体的实现");
// }
//}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn{
void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn{
void test(int a, double b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn{
int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn{
int test(int a);
}
//有返回值多个参数
@FunctionalInterface
interface MoreParameterReturn{
int test(int a, int b);
}
class Demo<T> {
}
public class Test1 {
public static void main(String[] args) {
NoParameterNoReturn noParameterNoReturn = () ->{
System.out.println("无参数无返回值");
};
noParameterNoReturn.test();
OneParameterNoReturn oneParameterNoReturn = (int a) -> {
System.out.println("一个参数无返回值:" + a);
};
oneParameterNoReturn.test(3);
//注意:当前后参数的类型一致的时候可以省略类型
MoreParameterNoReturn moreParameterNoReturn = (a,b) -> {
System.out.println("多个参数无返回值:" + a + " " + b);
};
moreParameterNoReturn.test(3,6);
NoParameterReturn noParameterReturn = () -> {
System.out.println("有返回值无参数!");
return 40;
};
int ret1 = noParameterReturn.test();
System.out.println(ret1);
OneParameterReturn oneParameterReturn = (int a) -> {
System.out.println("有返回值一个参数!");
return a;
};
int ret2 = oneParameterReturn.test(50);
System.out.println(ret2);
MoreParameterReturn moreParameterReturn = (int a, int b) -> {
System.out.println("有返回值多个参数!");
return a + b;
};
int ret3 = moreParameterReturn.test(60,70);
System.out.println(ret3);
}
public static void main2(String[] args) {
NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
@Override
public void test() {
System.out.println("hello");
}
};
noParameterNoReturn.test();
}
public static void main1(String[] args) {
//<>当中的数据类型不参与类型的组成 JVM当中没有泛型的概念 泛型 只存在于编译阶段
Demo<String> demo = new Demo<>();
System.out.println(demo);
Demo<Integer> demo1 = new Demo<>();
System.out.println(demo1);
}
}
结果如下所示:
注意:
- 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
- 参数的小括号里面只有一个参数,那么小括号也可以省略。
- 如果方法体中只有一句代码,那么大括号也可以省略。
- 如果方法体中只有一条语句,且是return语句,那么大括号可以省略,且去掉return关键字。
5.4 变量捕获
Lambda表达式中存在变量捕获,了解了变量捕获之后,我们才能更好的理解Lambda表达式的作用域,Java当中匿名类中,会存在变量捕获。
5.4.1 匿名内部类
匿名内部类就是没有名字的内部类,我们这里只是为了说明变量捕获,所以,匿名内部类只要会使用就好了,匿名接下来我们简单的看一下匿名内部类的使用就好了。
代码展示:
package lambda表达式;
class Test{
public void func() {
System.out.println("func()");
}
}
public class Test2 {
public static void main(String[] args) {
new Test(){
@Override
public void func() {
System.out.println("我是内部类,且重写了func这个方法!");
}
}.func();
}
}
结果展示:
注意:要调用内部类的时候直接在后面进行 .函数 即可。
上述我们看到的是一个简单的匿名内部类的例子,下面我们来演示一下匿名内部类的变量是如何捕获的。
5.4.2 匿名内部类的变量捕获
代码展示:
package lambda表达式;
class Demo1{
public void func() {
System.out.println("func()");
}
}
public class Test3 {
public static void main(String[] args) {
int a = 100;
new Demo1() {
@Override
public void func() {
System.out.println("我是内部类,其重写了func()这个方法!");
System.out.println("我是捕获到变量a == " + a + " 我是一个常量,或者是一个没有改变过值的变量!");
}
}.func();
}
}
结果展示:
注意:在上述代码当中变量a就是捕获的变量,这个变量要么是被final修饰,如果不是被final修饰的你要保证在使用之前没有修改,如下代码就是错误的代码!!!
由于有编译错误所以小编这里就不运行代码了。
我们可以看到上述代码中我在内部类中是不可以对变量进行修改的。
5.5 Lambda的变量捕获
在lambda当中也可以进行变量的捕获,具体我们看一下代码。
代码展示:
package lambda表达式;
@FunctionalInterface
interface NoParameterNoReturn1{
void test();
}
public class Test5 {
public static void main(String[] args) {
int a = 10;
NoParameterNoReturn1 noParameterNoReturn1 = () -> {
// a = 99;//error
System.out.println("捕获变量:" + a);
};
noParameterNoReturn1.test();
}
}
结果展示:
5.6 Lambda在集合当中的使用
为了能够让lambda和Java的集合类集更好的一起使用,集合当中也新增了部分接口,以便于lambda表达式对接。
对应的接口 | 新增的方法 |
Collection | removeIf() spliterator() stream() parallelStream() forEach() |
List | replaceAll() sort() |
Map | getOrDefault() forEach() replaceAll() putlfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge() |
下面我们将演示一些方法,注意:Collection和forEach()方法是从接口java.lang.lterable拿过来的。
5.6.1 Collection接口
forEach方法的演示:
该方法子啊接口Iterable当中,原型如下所示:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
该方法表示:对容器中的每一个元素执行action指定的动作。
代码如下所示:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
结果如下所示:
那么既然我们上面学习了有关于lambda的使用那么这里我们就可以修改上述代码。
代码展示:
package lambda表达式;
import java.util.ArrayList;
public class Test7 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
//表示调用一个不带参数的方法,其执行花括号内的语句,为原来的函数体内容。
list.forEach(s->{
System.out.print(s + " ");
});
}
}
结果展示:
5.6.2 List接口
sort()方法的演示:
sort方法源码:该方法根据c指定的比较规则对容器元素进行排序。
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
使用如下所示:
代码展示:
package lambda表达式;
import java.util.ArrayList;
import java.util.Comparator;
public class Test8 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//注意这里比较的是长度
return o1.length() - o2.length();
}
});
System.out.println(list);
}
}
结果展示:
修改为lambda表达式:
代码展示:
package lambda表达式;
import java.util.ArrayList;
public class Test9 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
//调用带有两个参数的方法,且返回长度的差值
list.sort((s1,s2) -> s1.length() - s2.length());
System.out.println(list);
}
}
结果展示:
5.6.3 Map接口
HashMap的forEach()
该方法原型如下所示:
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
具体的使用,它的作用是对Map中的每个映射执行action指定的操作。
代码展示:
package lambda表达式;
import java.util.HashMap;
import java.util.function.BiConsumer;
public class Test10 {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
map.put(1,"Hello");
map.put(2,"bit");
map.put(3,"hello");
map.put(4,"lambda");
map.forEach(new BiConsumer<Integer, String>() {
@Override
public void accept(Integer k, String v) {
System.out.println(k + " = " + v);
}
});
}
}
结果展示:
使用lambda表达式后的代码:
代码展示:
package lambda表达式;
import java.util.HashMap;
public class Test11 {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
map.put(1,"Hello");
map.put(2,"bit");
map.put(3,"hello");
map.put(4,"lambda");
map.forEach((k,v) -> System.out.println(k + " = " +v));
}
}
结果展示:
5.7 lambda表达式的优缺点
优点:
- 代码简洁,开发迅速。
- 方便函数式编程。
- 非常容易进行并行计算。
- Java引入lambda,改善了集合操作。
缺点:
- 代码可读性变差。
- 在非并行计算中,很多计算未必有传统的for性能要高。
- 不容易进行调试。
6. 泛型进阶
6.1 什么是泛型
一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类,如果要编译可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用许多类型,从代码上讲,就是对类型实现了参数化。
6.2 引出泛型
实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值。
思路:
- 我们以前学过的数组,只能存放指定类型的元素,例如:int[] array = new int[10];String[] str = new String[10];
- 所有类的父类,默认为Object类,数组是否可以创建为Object?
如下所示:
以上代码实现后发现:
- 任何类型数据都可以存放。
- 1号下标本身就是字符串,但是却编译报错,必须进行强制类型转换。
虽然在这种情况下,当前数组任何数据都可以存放,但是更多情况下,我们还是希望他只能够持有一种数据类型。而不是同时持有这么多类型,所以,泛型的主要目的就是:指定当前的容器,要持有什么类型的对象,让编译器去做检查,此时就需要把类型,作为参数传递,需要什么类型,就传入什么类型。
6.3 语法
class 泛型类名称<类型形参列表> {
//这里可以使用类型参数
}
class ClassName<T1,T2,...,Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类{
//这里可以使用类型参数
}
class ClassName<T1,T2,...,Tn> extends ParentClass<T1> {
//可以只使用部分类型参数
}
将上述代码改写后:
package lambda表达式;
class MyArray1<T> {
public T[] array = (T[])new Object[10];
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos, T val) {
this.array[pos] = val;
}
}
public class Test13 {
public static void main(String[] args) {
MyArray1<Integer> myArray1 = new MyArray1<>();
myArray1.setVal(0, 10);
myArray1.setVal(1, 12);
int ret = myArray1.getPos(1);
System.out.println(ret);
// myArray1.setVal(2,"bit");//后面就会出现编译错误,因为上面我们指定的类型是Integer
}
}
结果如下所示:
代码解释:
- 类名后的<T>代表占位符,表示当前类是一个泛型类。
- 在代码中我们使用的是:public T[] array = (T[])new Object[10];而不是T[] array = new T[10];意味着这样写是不对的。
- 在<>中我们加入Integer指定当前的类型。
- 既然以及指定类型那么我们在取数据的时候就不需要进行强制类型转换。
- myArray1.setVal(2,"bit");在此处代码会编译出错,此时因为上面已经指定了类型,所以编译器会在存放元素的时候帮助我们进行类型检查。
类型形参一般使用一个大小写字母表示,常用的名称有:
- E:表示Element
- K:表示Key
- V:表示Value
- N:表示NUmber
- T:表示Type
6.4 泛型类的使用
6.4.1 语法
泛型类<类型实参> 变量名;//定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参);//实例化一个泛型类对象
MyArray<Integer> list = new MyArray<Integer>();
注意:泛型只能接收类,所有的基本数据类型必须使用包装类。
6.4.2 裸类型
裸类型是一个泛型类但没有带着类型实参,例如MyArray list就是一个裸类型。
如:MyArray list = new MyArray();
注意:我们不要自己去使用裸类型,裸类型是为了兼容老版本的API保留的机制。
6.5 泛型的编译
6.5.1 擦除机制
什么是擦除机制呢?泛型到底是怎么编译的,我们可以通过查看字节码文件就会发现所有的T都是Object。在编译的过程中,将所有的T替换为Object这种机制,我们称为:擦除机制。
Java的泛型机制是在编译级别实现的,编译器生成的字节码在运行期间并不包含泛型的类型信息。
6.5.2 为什么不能实例化泛型类数组
代码:
package lambda表达式;
class MyArray2<T> {
public T[] array = (T[])new Object[10];
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos, T val) {
this.array[pos] = val;
}
public T[] getArray() {
return array;
}
}
public class Test14 {
public static void main(String[] args) {
MyArray2<Integer> myArray2 = new MyArray2<>();
Integer[] strings = myArray2.getArray();
}
}
结果:
原因:
替换后的方法为:
public Object[] getArray() { return array; }
将Object分配给Integer[]引用 ,程序就会报错,通俗的讲就是返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候,直接转给Integer类型的数组,编译器认为是不安全的。
正确的做法如下所示:
package lambda表达式;
import java.lang.reflect.Array;
class MyArray3<T> {
public T[] array;
public MyArray3() {
}
//通过反射创建,指定类型的数组
public MyArray3(Class<T> clazz, int capacity){
array = (T[]) Array.newInstance(clazz, capacity);
}
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos, T val) {
this.array[pos] = val;
}
public T[] getArray() {
return array;
}
}
public class Test15 {
public static void main(String[] args) {
MyArray3<Integer> myArray3 = new MyArray3<>(Integer.class,10);
Integer[] integers = myArray3.getArray();
}
}
6.6 泛型的上界
在定义泛型类的时候,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
语法:
class 泛型类名称<类型形参 extends 类型边界>{}
如下所示:
public class MyArray<E extends Number> {
}
只接受NUmber的子类作为E的类型实参。
- MyArray<Integer> //正常,因为Integer是Number的子类。
- MyArray<String> //编译错误,因为String不是Number的子类。
注意:如果没有指定类型的边界E,可以视为E extends Object。
复杂类型:
public class MyArray<E extends Comparable<E>>{
}
注意:E必须是实现了Comparator接口的。
6.7 泛型方法
6.7.1 定义语法
方法限定符<类型形参列表> 返回值类型 方法名称(形参列表){...}
实例演示:
package 泛型;
import java.util.Arrays;
public class Test5 {
//静态的泛型方法需要在static后用<>声明泛型类型参数
public static <E> void swap(E[] array, int i, int j) {
E t = array[i];
array[i] = array[j];
array[j] = t;
}
public static void main(String[] args) {
//使用类型推导
Integer[] a = {1,2,3,4};
swap(a,0,3);
System.out.println(Arrays.toString(a));
String[] b = {"hello", "bit","hello", "world"};
swap(b,0,3);
for (String str : b) {
System.out.print(str + " ");
}
//不使用类型推导
Integer[] c = {2,3,4,5,6};
Test5.<Integer>swap(c,0,4);
}
}
结果展示:
6.8 通配符
?用于在泛型的使用,即为通配符。
6.8.1 通配符解决什么问题
代码如下所示:
package 泛型;
class Message<T> {
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test6 {
public static void main(String[] args) {
Message<String> message = new Message<>();
message.setMessage("Hello world");
fun(message);
}
private static void fun(Message<String> message) {
System.out.println(message.getMessage());
}
}
结果如下所示:
以上程序会带来新的问题,如果现在泛型的类型设置不是String,而是Integer。
那么我们需要的解决方案:可以接收所有泛型类型,但是又不能够让用户随意修改,这种情况就需要通配符“?”来处理。
如下所示:
package 泛型;
class Message1<T> {
private T message;
public T getMessage1() {
return message;
}
public void setMessage1(T message) {
this.message = message;
}
}
public class Test7 {
public static void main(String[] args) {
Message1<Integer> message1 = new Message1<>();
message1.setMessage1(55);
fun(message1);
}
//此时使用通配符“?”描述的是可以接收任意类型,但是由于不确定类型,所以无法修改。
private static void fun(Message1<?> message1) {
System.out.println(message1.getMessage1());
}
}
结果展示:
在 “?”的基础上又产生了两个子通配符:
- ?extends 类:设置通配符上限。
- ?super类:设置统配符下限。
接下来我们就一一看一下吧。
6.8.2 通配符上界
语法:
<? extends 上界>
<? extends Number> //可以传入实参类型是Number或者Number的子类。
如下面例子所示:
package 泛型;
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Banana extends Fruit{
}
class Message3<T> {
//设置泛型
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test8 {
public static void main(String[] args) {
Message3<Apple> message3 = new Message3<>();
message3.setMessage(new Apple());
fun(message3);//1
Message3<Banana> message31 = new Message3<>();
message31.setMessage(new Banana());
fun(message31);//2
}
//此时使用统配符“?”描述的是他可以直接接收任意类型,但是由于不确定类型,所以无法修改。
private static void fun(Message3<? extends Fruit> message3) {
System.out.println(message3.getMessage());
}
}
结果:
注意:统配符的上界只能读取数据,不能写入数据。
6.8.3 通配符的下界
语法:
<? super 下界>
<? super Integer> //代表可以传入的实参的类型是Integer或者是Integer的父类类型。
代码:
package 泛型;
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Plate<T> {
private T plate;
public T getPlate() {
return plate;
}
public void setPlate(T plate) {
this.plate = plate;
}
}
public class Test9 {
public static void main(String[] args) {
Plate<Fruit> plate1 = new Plate<>();
plate1.setPlate(new Fruit());
fun(plate1);
Plate<Food> plate2 = new Plate<>();
plate2.setPlate(new Food());
fun(plate2);
}
private static void fun(Plate<? super Fruit> plate1) {
//此时可以修改,添加的是Fruit或者是Fruit的子类
plate1.setPlate(new Apple());//这个是Fruit的子类。
plate1.setPlate(new Fruit());//这个是Fruit本身
// Fruit fruit = plate1.getPlate();//不能接收,这里无法确定是哪个父类
System.out.println(plate1.getPlate());//只能直接输出
}
}
结果:
注意:统配符的下界,不能读取数据,只能写入数据。