内部类是定义在类中的类,其主要作用是将逻辑上相关的类放到一起。而匿名内部类是一种特殊的内部类,它没有类名,在定义类或者实现接口的同时,就生成该类的一个对象,由于不会在其他地方用到该类,所以不用取名字,因而被称为匿名内部类。
下面的例子可以看看出来内部类是包含在类中的类,可以看做是外部类的一个成员,所以也称为成员类。
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(); } }
在文件管理方面,内部类在编译完成之后,所产生的文件名称为“外部类名$内部类名.class”。所以上述案例在变异后会产生两个文件:Out.class和Out$Student.class"
在内部类对象中保存了一个对外部类对象的引用,挡在内部类的成员方法中访问某一个变量的时候,如果在该方法和内部类中都没有定义过这个变量,调用就会被传递给内部类中保存的那个外部类对象的引用。通过那个外部类对象的引用去调用这个变量,在内部类调用外部类的方法也是一样的道理。
上述代码在内存中的分布图
Java将内部类作为一个成员,就如同成员变量或者成员方法。内部类可以被声明为private或protected。因此,内部类与外部类的访问原则是:在外部类中,通过一个内部类的对象引用内部类中的成员。反之,在内部类中可以直接引用它的外部类的成员,包括静态成员、实例成员及私有成员。内部类也可以通过创建对象从外部类之外被调用,但必须将内部类声明为public的。
语法格式:
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表达式都对于一个函数式接口,可以将Lambda表达式看做是实现函数式接口的匿名内部类的一个对象。
为了让编译器能确保一个接口满足函数式接口的要求,Java8提供了@FunctionalInterface注解。例如,Runnable接口就是一个函数式接口,下面是它的注解方式:
如果接口使用了@FunctionalInterface
来注解,而本身并非是函数式接口,则在编译时出错。函数式接口只能有一个抽象方法需要被实现,但有如下特殊情况的除外。
函数式接口中有Object类中覆盖的方法,也就是equals()、toString()、hashCode()等方法。
例如,Comparator接口就是一个函数式接口:
该接口声明了多个方法,但是equals()方法是Object类中的方法。
@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); }
Lambda表达式是可以传递给方法的一段代码。可以使一条语句,也可以是一个代码开,因为不需要方法名,所以说Lambda表达式是一种匿名方法,即没有方法名的方法。
Java中任何Lambda表达式必须有对于的函数式接口,正是因为这样的要求,但是为了分辨出是Lambda表达式使用的接口,所以最好还是使用注解@FunctionalInterface
注解声明,这样就表示此接口为函数式接口。
函数式接口之所以重要是因为可以使用Lambda表达式创建一个与匿名内部类等价的对象,正因为如此Lambda表达式可以被看做是使用精简语法的匿名内部类。
a.outShape( () -> { System.out.println("我可以是任何形状"); } );
相对于匿名内部类来说,Lambda表达式的语法省略了接口类型与方法名,->左边是参数列表,而右边是方法体。Lambda表达式通常由参数列表、箭头和方法体三部分组成:
(类型1 参数1, 类型2 参数2, ...) -> {方法体}
为了将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); } }
Java8之后版本增加了双冒号“ ::”运算符用于方法引用。方法引用并不是调用方法。如果传递的Lambda表达式有实现的方法,则可以使用方法引用来代替Lambda表达式。可以将方法引用理解为是Lambda表达式的另外一种表现形式,方法引用就是用双冒号运算符“::”来简化Lambda表达式的。
说明
第一种:匿名内部类方式
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("我是一个消费型接口");
参考博文:
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了。
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; } }