1.关于toString()方法的讨论
对于Persom类的对象p:
System.out.println(p);
System.out.println(p.toString());
两行代码的效果完全一样,输出均是Person对象的toString()方法的返回值。toString()方法属于Object类,返回该对象实现类的“类名+@+hashCode”的值,因此如果要实现“自我描述”功能就需要重写toString()方法。
2.==和equals方法
String str1 = new String("hello");
String str2 = new String("hello");
对于两个引用类型的变量,只有它们指向同一个对象时,==判断才会返回true。==不可用于比较类型上没有父子关系的两个对象。例如,65、65.0f和'A'相等。但对于str1、str2,它们都是引用类型变量,分别指向两个通过new关键字创建的String对象,因此str1和str2不相等。
- 'hello'直接量和new String("hello")的区别:
当Java程序直接使用形如"hello"的字符串直接量时,JVM将会使用常量池来管理这些字符串;当使用new String("hello")时,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,保存在堆内存中。换句话说,new String("hello")一共产生了两个字符串对象。
pubilc class StringCompareTest
{
public static void main(String[] args)
{
String s1 = "疯狂JAVA";
String s2 = "疯狂";
String s3 = "Java";
String s4 = "疯狂" + "Java";
String s5 = "疯" + "狂" + "Java";
String s6 = s2 + s3;
String s7 = new String("疯狂Java");
System.out.println(s1 == s4);//输出true
System.out.println(s1 == s5);//输出true
System.out.println(s1 == s6);//输出false
System.out.println(s1 == s7);//输出false
}
}
JVM常量池保证相同的字符串直接量只有一个,并可以管理曾经用过的字符串直接量。不会产生多个副本。s1、s4、s5所引用的字符串可以在编译期就确定下来,因此都将引用常量池中的同一个字符串对象。例如执行String a = "java";语句之后,常量池中就会缓存一个字符串"java",如果程序再次执行String b = "java";,系统会让b直接指向常量池中的"java"字符串,因此a==b将会返回true。
equals()方法在判断两个字符串时,如str1.equals(str2)将返回true。而判断两个对象相等的标准与使用==运算符没有区别。但String已经重写了Object的equals()方法,只要两个字符串所包含的字符序列相同,将返回true。
3.final修饰符
final修饰的成员变量必须由程序员显式指定初始值。因为如果不指定初始值,这些成员变量的值将一直是系统默认分配的0、'\u0000'、false或null。而因系统不会对局部变量进行初始化,所以final修饰局部变量时,定义式可以不指定默认值,而在后面的代码中对该final变量赋初始值。
如果想让上文中定义的s1 == s6输出true也很简单,只要让编译器可以对s2、s3执行“宏替换”,这样编译器即可在编译阶段就确定s6的值,就会让s6指向字符串池中缓存的“疯狂Java”。也就是说,对s2、s3使用final修饰即可。
4.内部类
外部类不能访问内部类的实现细节,例如内部类的成员变量。方法中也可以定义内部类。
内部类的class文件格式:OuterClass$InnerClass.class。
内部类与外部类内存示意图:
如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可通过使用this、外部类类名.this来区分。如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员。
外部类不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问。
接口也可以定义内部类,默认使用public static修饰,也就是说接口内部类只能是静态内部类。
- 使用内部类
在外部类以外的地方定义内部类(包括静态和非静态)变量的语法格式如下:
OuterClass.InnerClass varName
由于非静态内部类对象必须寄生在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象。语法如下:
OuterInstance.new InnerConstructor()
当创建一个子类时,子类构造器总会调用父类的构造器,因此在创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,所以必须存在一个外部类对象。下面程序定义了一个子类继承了Out类的非静态内部类In类。
public class Sub Class extends Out.In
{
//显式定义SubClass的构造器
public SubClass(Out out)
{
//通过传入的Out对象显式调用In的构造器
out.super("hello");
}
}
在外部类以外使用静态内部类:因为静态内部类是外部类类相关的,因此创建静态内部类对象时无须创建外部类对象。在外部类以外的地方创建静态内部类实例的语法如下:
new OuterClass.InnerConstructor()
不管是静态还是非静态内部类,它们声明变量的语法完全一样。区别只是在创建内部类对象时,静态内部类只需使用外部类即可调用构造器,而非静态内部类必须使用外部类对象来调用构造器。
因为调用静态内部类的构造器时无须使用外部类对象,所以创建静态内部类的子类也比较简单:
public class StaticSubClass extends StaticOut.StaticIn{}
5.Lambda表达式
Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)实例。其中函数式接口代表只包含一个抽象方法的接口,可以包含多个默认方法、类方法,但只能声明一个抽象方法。由于Lambda表达式的结果就是被当成对象,因此程序中完全可以使用Lambda表达式进行赋值:
Runnable r = () -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};
由于Lambda表达式的目标类型必须是明确的函数式接口,所以如下代码将会报错:
Object obj = () -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};
不兼容的类型:Object不是函数接口
但如果:
Object obj = (Runnable)() -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};
这样就可以确定该表达式的目标类型为Runnable函数式接口。
总结:Lambda表达式的本质很简单,就是使用简洁的语法来创建函数式接口的实例——这种语法避免了匿名内部类的烦琐。
- 方法引用与构造器引用
1.引用类方法
例如,定义了如下函数式接口:
@FunctionalInterface
interface Converter{
Integer convert(String from);
}
该函数式接口中包含一个convert()抽象方法,该方法负责将String参数转换为Integer。下面代码使用Lambda表达式来创建一个Converter对象。
Converter converter1 = from -> Integer.valueOf(from);
接下来程序就可以调用converter1对象的convert()方法将字符串转换为整数了:
Integer val = converter1.convert("99");
System.out.println(val);
上述Lambda表达式的代码也可以用如下方法引用进行替换:
Converter converter1 = Integer::valueOf;
2.引用特定对象的实例方法
Converter converter2 = from -> "fkit.org".indexOf(from);
Integer value = converter2.convert("it");
System.out.println(value);//输出2
同理,Lambda表达式可以替换成:
Converter converter2 = "fkit.org"::indexOf;
3.引用某类对象的实例方法
定义了如下函数式接口:
@FunctionalInterface
interface MyTest
{
String test(String a, int b, int c);
}
下面使用Lambda表达式:
Mytest mt = (a,b,c) -> a.substring(b,c);
String str = mt.test("Java I Love you",2,9);
System.out.println(str);//输出:va I Lo
可替换为:
MyTest mt = String::substring;
4.引用构造器
定义了以下函数式接口:
@FunctionalInterface
interface YourTest
{
Frame win(String title);
}
YourTest yt = (String a) -> new Frame(a);
JFrame jf = yt.win("我的窗口");
System.out.println(jf);
可以替换成:
YourTest yt = JFrame::new;