【JavaSE】Lambda、Int与Integer
【JavaSE】Lambda、Int与Integer
一、内部类与匿名内部类
内部类是定义在类中的类,其主要作用是将逻辑上相关的类放到一起。而匿名内部类是一种特殊的内部类,它没有类名,在定义类或者实现接口的同时,就生成该类的一个对象,由于不会在其他地方用到该类,所以不用取名字,因而被称为匿名内部类。
1、内部类
下面的例子可以看看出来内部类是包含在类中的类,可以看做是外部类的一个成员,所以也称为成员类。
public class Out {
private int age;//声明外部类的私有成员变量
public class Student{//声明内部类
String name; //访问内部类的成员变量name
public Student(String n,int a){
name = n; //访问内部类name
age = a; //访问外部类age
}
public void output(){
System.out.println("姓名:"+this.name+";年龄:"+age);
}
}
public void output(){
Student stu = new Student("刘洋",24); //创建内部类对象stu
stu.output();
}
public static void main(String[] args) {
Out g = new Out();
g.output();
}
}
①JVM层面分析
在文件管理方面,内部类在编译完成之后,所产生的文件名称为“外部类名$内部类名.class”。所以上述案例在变异后会产生两个文件:Out.class和Out$Student.class"
在内部类对象中保存了一个对外部类对象的引用,挡在内部类的成员方法中访问某一个变量的时候,如果在该方法和内部类中都没有定义过这个变量,调用就会被传递给内部类中保存的那个外部类对象的引用。通过那个外部类对象的引用去调用这个变量,在内部类调用外部类的方法也是一样的道理。
上述代码在内存中的分布图
②内部类的特性
Java将内部类作为一个成员,就如同成员变量或者成员方法。内部类可以被声明为private或protected。因此,内部类与外部类的访问原则是:在外部类中,通过一个内部类的对象引用内部类中的成员。反之,在内部类中可以直接引用它的外部类的成员,包括静态成员、实例成员及私有成员。内部类也可以通过创建对象从外部类之外被调用,但必须将内部类声明为public的。
2、匿名内部类
语法格式:
new TypeName(){//TypeName是父类名或者接口名,且括号“()”内不允许有参数
//匿名类的类体
}
TypeName Obj = new TypeName(){
//匿名类的类体
}
someMethod(new TypeName(){
//匿名类的类体
});
案例:
public class TestInnerClass{
public static void main(String[] args){
(
new Inner(){
void setName(String n){
name = n;
System.out.println("姓名:"+name);
}
}
).setName("张华");
}
static class Inner{//定义内部类
String name;
}
}
案例:利用接口创建匿名内部类对象并实现接口中抽象方法
interface IShape{
void shape();
}
class MyType{
public void outShape(IShape s){//方法参数是接口类型的变量
s.shape();
}
}
public class App13_5 {
public static void main(String[] args) {
MyType a = new MyType();//创建MyType类的对象a
a.outShape(new IShape() {//用接口名IShape创建匿名内部对象
@Override //必须覆盖接口中的shape()方法
public void shape() {
System.out.println("我可以是任何形状");
}
});
}
}
二、函数式接口和Lambda表达式
Lambda表达式指的是应用在只含有一个抽象方法的接口环境下的一种简化定义形式,可用于解决匿名内部类的定义复杂问题。
1、函数式接口(Functional Interface,FI)
指的是包含一个抽象方法的接口,因此也称为抽象方法接口。每一个Lambda表达式都对于一个函数式接口,可以将Lambda表达式看做是实现函数式接口的匿名内部类的一个对象。
为了让编译器能确保一个接口满足函数式接口的要求,Java8提供了@FunctionalInterface注解。例如,Runnable接口就是一个函数式接口,下面是它的注解方式:
如果接口使用了@FunctionalInterface
来注解,而本身并非是函数式接口,则在编译时出错。函数式接口只能有一个抽象方法需要被实现,但有如下特殊情况的除外。
-
函数式接口中有Object类中覆盖的方法,也就是equals()、toString()、hashCode()等方法。
-
例如,Comparator接口就是一个函数式接口:
-
该接口声明了多个方法,但是equals()方法是Object类中的方法。
- 函数式接口中智能生命一个抽象方法,但是静态方法和默认方法(即用default修饰的方法)不属于抽象方法,因此可以在函数式接口中定义静态方法和默认方法。例如Comparator接口
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
2、Lambda表达式
Lambda表达式是可以传递给方法的一段代码。可以使一条语句,也可以是一个代码开,因为不需要方法名,所以说Lambda表达式是一种匿名方法,即没有方法名的方法。
Java中任何Lambda表达式必须有对于的函数式接口,正是因为这样的要求,但是为了分辨出是Lambda表达式使用的接口,所以最好还是使用注解@FunctionalInterface
注解声明,这样就表示此接口为函数式接口。
函数式接口之所以重要是因为可以使用Lambda表达式创建一个与匿名内部类等价的对象,正因为如此Lambda表达式可以被看做是使用精简语法的匿名内部类。
①精简上述匿名内部类的案例
a.outShape(
() -> {
System.out.println("我可以是任何形状");
}
);
②语法
相对于匿名内部类来说,Lambda表达式的语法省略了接口类型与方法名,->左边是参数列表,而右边是方法体。Lambda表达式通常由参数列表、箭头和方法体三部分组成:
(类型1 参数1, 类型2 参数2, ...) -> {方法体}
- 参数列表中的参数都是匿名方法的形参,即输入参数。参数列表允许省略形参的类型,即一个参数的数据类型既可以显式声明,也可以由编译器的类型推断功能来推断出参数的类型;当参数是推断类型时,参数的数据类型将由JVM根据上下文自动推断出来。
- 方法体可以是单一的表达式或者由多条语句组成的与剧组。如果只有一条语句,则允许省略方法体的花括号,如果只有一条return语句,则return关键字也可以省略。
- 如果省略 return关键字的语句,则Lambda表达式会自动返回该语句的结果值。
- 如果没有参数,可以只写一个圆括号。
- 如果Lambda表达式只有一个参数,并且没有给出显式的数据类型,则圆括号可以省略。
3、Lambda表达式作为方法的参数
为了将Lambda表达式作为参数传递,接受Lambda表达式的参数必须是与该Lambda表达式兼容的函数式接口类型。
示例:
@FunctionalInterface
interface StringFunc{//定义函数式接口
public String func(String s);//抽象方法
}
public class App13_9 {
static String sop(StringFunc sf,String s){//静态方法
return sf.func(s);
}
public static void main(String[] args) {
String outStr,inStr = "Lambda 表达式 good";
System.out.println("源字符串:"+inStr);
outStr =sop ((str)->str.toUpperCase(),inStr);//将字符串inStr转换成大写
System.out.println("转换成大字符后:"+outStr);
outStr =sop((str->{
String result = "";
for (int i = 0; i <str.length() ; i++)
if (str.charAt(i)!=' ')
result += str.charAt(i);
return result;
}),inStr);
System.out.println("去掉空格的字符串:"+outStr);
}
}
- sop这个静态方法有两个参数,第一个参数sf是函数式接口类型StringFunc,因此该参数可以接受对任何StringFunc实例的引用,包括由Lambda表达式创建的实例,第二个参数s是String类型,也就是要操作的字符串。
- 在java.util.function包中定义了大量的函数式接口,如功能型接口Function<T,R>和BiFunction<T,U,R>等等,使用它们编写Lambda表达式更加容易。
三、方法引用
Java8之后版本增加了双冒号“ ::”运算符用于方法引用。方法引用并不是调用方法。如果传递的Lambda表达式有实现的方法,则可以使用方法引用来代替Lambda表达式。可以将方法引用理解为是Lambda表达式的另外一种表现形式,方法引用就是用双冒号运算符“::”来简化Lambda表达式的。
①引用形式
- 对象名::实例方法名
- 类名::静态方法名
- 类名::实例方法名
- 类名::new
说明
- 第四种引用构造方法时候,右面只能是关键字new。而其它三种可以加圆括号
- 前两种引用方式用于只有一个参数的情况,方法引用等同于提供了方法参数的Lambda表达式,比如System.out::print等同于System.out.print(s)。第三种引用方式用于两个以上的参数中,第一个参数是调用方法的对象。
②案例说明
-
第一种:匿名内部类方式
Consumer<String> con = new Consumer<String>{ @Override public ovid accept(String str){ System.out.println(str); } }
-
第二种:Lambda表达式
Consumer<String> con = str ->System.out.println(str)//创建实例 con.accept("我是一个消费型接口");
-
第三种:方法引用
Consumer<String> con = System.out::println con.accept("我是一个消费型接口");
四、Int与Integer
- Integer是int的包装类;int是基本数据类型;
- Integer变量必须实例化后才能使用;int变量不需要;
- Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
- Integer的默认值是null;int的默认值是0。
参考博文:
https://blog.csdn.net/chenliguan/article/details/53888018
-
由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)
-
Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)
-
非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。因为非new生成的Integer变量指向的是静态常量池中cache数组中存储的指向了堆中的Integer对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的对象引用(地址)不同。
-
JVM的内存结构
-
Integer i = new Integer(100); Integer j = 100; System.out.print(i == j); //false
-
-
对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
-
Integer i = 100; Integer j = 100; System.out.print(i == j); //true Integer i = 128; Integer j = 128; System.out.print(i == j); //false
-
java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100)。而java API中对Integer类型的valueOf的定义如下,对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127这个Integer对象进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了。
-
五、LeetCode-977.有序数组的平方
①题干
②Int数组转Integer数组
③代码实现
public class Solution {
public static int[] sortedSquares(int[] nums) {
//转化成包装类
Integer[] nums2 = Arrays.stream(nums).boxed().toArray(Integer[]::new);
//Lambda表达式
Arrays.sort(nums2, (o1, o2) -> Math.abs(o1) > Math.abs(o2) ? 1 : -1); // lambda 表达式
//方法引用
nums = Arrays.stream(nums2).mapToInt(Integer::valueOf).toArray();
for (int i = 0; i < nums.length; i++)
nums[i] *= nums[i];
return nums;
}
}