Java 8 增强的包装类
8中基本数据类型不具备对象的特性,Java提供了包装类(Wrapper Class)的概念,称之为基本数据类型的包装类。对应关系如下:
自动装箱是把一个基本类型变量直接赋给对应的包装类,或者赋给一个 Object 变量(Object 是所有类的父类,子类对象可以直接赋给父类对象):
Integer inObj = 5;
Object boolObj = true;
自动拆箱是把包装类直接赋给一个对应的基本类型变量:
int it = inOb
借助包装类和自动装箱,自动拆箱,可以把基本类型近似的当成对象使用;反过来,也可以把包装类近似的当成基本类型使用。
包装类还有一个功能是基本类型变量和字符串之间的转换。把字符串类型转换成基本类型有两种方式:
1.利用包装类提供的 parseXxx(String s)静态方法(除了 Character 包装类之外)。
2.利用包装类提供的 Xxx(String s)构造器。
基本类型变量和字符串间的转换关系:
虽然包装类是引用类型变量,但是可以与数据类型的值直接比较。
包装类比较的时候最好使用 Java 为所有包装类提供的静态方法 compare:
处理对象
Java 中判断两个变量是否相等有两种方式:== 和 equals()。
1.使用 == 来判断时,如果是两个基本类型,且都是数值类型(不一定是同一个数据类型,会隐式转换)。
int it = 65;
char ch = 'A';
System.out.println(it == ch); // true
对于引用变量来说,要他们的指向同一个对象才会返回 true。
String a =
"1"
;
String b =
"1"
;
System.out.println(a == b);
// true
String c =
new
String(
"1"
);
String d =
new
String(
"1"
);
System.out.println(c == d);
// false
System.out.println(c.equals(d));
//true
("1" 直接量和 new String("1") 的区别查看“常量池”)
2.有时判断两个引用变量是否相等时,希望有一种类似于“值相等”的方式,需要使用 equals();equals() 方法是 Object 类提供的实例方法,因此所有的引用变量都可以使用,问题是这个方法对于引用变量来说和 == 运算符的效果是一样的,如果希望采用自定义的相等标准,可以重写 equals() 方法,这里 String 已经重写了 equals() 方法。
通常而言,重写 equals() 方法应该满足一下条件:
类成员
Java 类中只能包含5种成员:
成员变量,方法,构造器,初始化块,内部类(包括接口,枚举)。其中可以用 static 修饰的就是类成员:
类变量,类方法,类初始化块,内部类。构造器不能用 static 修饰
类变量可以通过类来访问,也可以通过类的对象来访问,但是通过对象访问实际上系统会在底层转换为通过类来访问,因为对象实际上不持有类变量,所以从程序运行来看,一个类的所有实例访问的类变量共享同一块内存区。(很多语言不允许通过对象来访问类变量,对象只能访问实例变量)
类方法也是类成员的一种,类方法也是属于类的,通常使用类作为调用者,也可以使用对象来调用,效果与类变量类似。
静态(类)初始化块也是类成员的一种,执行顺序:类初始化快 —》普通初始化块 —》构造器。
单例类
通常把构造器用 public 修饰,这样允许任何类自由的创建该类的对象。但在某些时候,一个类始终只能创建一个实例,那么这个类叫做单例类。
把该类的构造器用 private 修饰,从而把该类的所有构造器隐藏起来。根据良好封装的原则,一旦该类的构造器隐藏,就需要提供一个 public 方法来作为该类的访问点,用于创建该类的对象,该方法只能用 static 修饰,因为调用该方法的时候没有对象,因此调用该方法的一定是类不是对象。
除此之外,该类还必须缓存已经创建的对象,不然该类不知道是否已经创建过对象了,也就无法保证只创建一个对象。该类使用成员变量来保存创建的对象,该成员变量需要被上文的静态方法访问,因此该成员变量为类变量。
如下为懒汉模式最简单的一种:
class
Singleton {
private
Singleton() {}
// 隐藏构造器
private static
Singleton instance;
// 类变量缓存创建的实例
public static
Singleton getInstance() {
// 返回值类型是Singleton
if
(instance ==
null
) {
instance =
new
Singleton();
}
return
instance;
}
}
public class
SingletonTest {
public static void
main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
// true 这两个是同一个对象
}
}
final 修饰符
final 修饰变量时表示一旦获得了初始值就不可重新赋值,而不是说不能被赋值。
final 成员变量:
成员变量是随类初始化或实例初始化而初始化的。当类初始化时,系统会为类变量分配内存并分配默认值;当创建对
象时,系统会为该对象的实例变量分配内存并分配默认值。静态初始化块—》类变量赋初始值;普通初始化块,构造器—》实例变量赋初始值。
Java 规定 final 修饰的成员变量必须有程序员显式地指定初始值:
1.类变量必须在类初始化块中指定初始值或在声明该变量时指定初始值,二选一。
2.实例变量必须在普通初始化块、构造器或声明该变量时指定初始值,三选一。
final 局部变量:
如果 final 修饰的局部变量在定义时没有指定默认值,在可以在后面的代码中对变量赋值,但只能一次。
如果用 final 修饰引用类型变量,因为引用类型变量仅仅保存的是一个引用,final 只保证这个引用类型变量地址不变,保证一直引用同一个对象,但这个对象可以发生变化。
final 宏变量:
对于 final 变量来说,不管是成员变量还是局部变量,主要满足三个条件就是 final 宏变量,相当于直接量:
1.使用 final 修饰。
2.定义该变量时指定了初始值。
3.该初始值可以在编译的时候确定下来(被赋值的表达式是基本的算术运算或者字符串连接运算,都可以在编译的时候确定下来)。
final int a = 5;
System.out.println(a);
上面的程序中 System.out.println(a); 实际上是 System.out.println(5);
final 方法:
final 修饰的方法不可以被重写,如果父类中一个方法用 public 和 final 修饰,那么在继承他的子类中重写这个方法会报错,只能使用;如果在父类中一个方法用 private 和 final 修饰,那么在继承他的子类中根本不可以访问这个方法,不可见,也就没有重写一说,所以可以在子类中出现与该方法同名,同形参列表,同返回值类型的方法。
final 方法只是不能在子类中被重写,可以重载。final 方法会被隐式的用 final 修饰。
final 类:
final 修饰的类不可以被继承。
抽象类
抽象类和抽象方法必须使用 abstract 修饰,有抽象方法一定是抽象类,抽象类可以没有抽象方法。
归纳来说,抽象类可以说是有得有失,“得”在于抽象类可以包含抽象方法,“失”在于抽象类不能用于创建实例,只能当做父类派生出子类。
抽象方法和空方法不一样:
public abstract void test(a, b); //抽象方法
public void test(a, b){}; //空方法,它只是方法体内为空而已
示例:
Shape.java
public abstract class
Shape {
{
System.out.println(
"执行 Shape 的初始化块。。。"
);
}
private
String color;
public abstract double
calPerimeter();
public abstract
String getType();
public
Shape() {
}
public
Shape(String color) {
System.out.println(
"执行 Shape 的构造器。。。"
);
}
public void
setColor(String color) {
this
.color = color;
}
public
String getColor() {
return this
.color;
}
}
Triangle.java
public class
Triangle
extends
Shape {
private double
a;
private double
b;
private double
c;
public
Triangle(String color,
double
a,
double
b,
double
c) {
setColor(color);
this
.setSides(a, b, c);
}
public void
setSides(
double
a,
double
b,
double
c) {
if
(a >= b + c || b >= a + c || c >= a + b) {
System.out.println(
"三角形两边之和必须大于第三边!"
);
return
;
}
this
.a = a;
this
.b = b;
this
.c = c;
}
public double
calPerimeter() {
return
a + b + c;
}
public
String getType() {
return
getColor() +
"三角形"
;
}
}
Cricle.java
public class
Cricle
extends
Shape {
private double
radius;
public
Cricle(String color,
double
radius) {
setColor(color);
this
.radius = radius;
}
public void
setRadius(
double
radius) {
this
.radius = radius;
}
public double
calPerimeter() {
return
2
* Math.PI * radius;
}
public
String getType() {
return
getColor() +
"圆形"
;
}
public static void
main(String[] args) {
Shape s1 =
new
Triangle(
"黑色"
,
3
,
4
,
5
);
Shape s2 =
new
Cricle(
"黄色"
,
3
);
System.out.println(s1.getType());
System.out.println(s1.calPerimeter());
System.out.println(s2.getType());
System.out.println(s2.calPerimeter());
}
static 和 abstract 不能同时修饰某个方法;private 和 abstract 不能同时修饰某个方法。
抽象类体现的是一种模板模式的设计,抽象类作为多个子类的通用模板,避免了子类设计的随意性,子类在抽象类的基础上扩展,大致上会保留抽象类的行为方式。模板模式也是常见且简单的设计模式之一。
Java 8 改进的接口
抽象类是从多个类中提取出来的抽象模板,如果将抽象进行的更加彻底,则可以提炼出一种更加特殊的“抽象类”— 接口,接口里不能有普通方法(普通抽象类中可以有),所有方法都是抽象方法。
与类定义不同,接口定义不再使用 class 关键字了,而是使用 interface 关键字。接口定义的是一种规范,不存在构造器和初始化块,接口内部可以包含成员变量(只能是静态常量),方法(只能是抽象实例方法,类方法和默认方法),内部类(包括内部接口,枚举)定义。接口里的所有成员都是 public 修饰符,可以省略掉 public 修饰符,但是一旦使用只能是 public。对于接口内的静态常量而言,系统自动为这些成员变量增加 public static final 修饰符,因为没有构造器和初始化块,因此接口内的成员变量只能在定义时指定初始值。接口内的普通方法系统自动添加 public abstract 修饰符。
举例如下:
public interface
Output {
int
MAX_CACHE_LINE =
50
;
// 接口里定义的成员变量只能是常量,系统自动添加 public static final
void
out();
// 接口里定义的普通方法只能是 public 的抽象方法,系统自动添加 public abstract
void
getData(String msg);
// 接口里定义的普通方法只能是 public 的抽象方法,系统自动添加 public abstract
default void
print(String... msgs) {
// 接口里定义默认方法,需要用 default 修饰,系统自动添加 public
for
(String msg : msgs) {
System.out.println(msg);
}
}
default void
test() {
// 接口里定义默认方法,需要用 default 修饰,系统自动添加 public
System.out.println(
"默认的 test() 方法"
);
}
static
String staticTest() {
// 接口里里定义类方法,需要用 static 修饰,系统自动添加 public
return
"接口里的类方法"
;
}
}
接口的继承:
接口的继承与类继承不同,接口支持多继承。
interface
interfaceA {
int
PROP_A =
5
;
}
interface
interfaceB {
int
PROP_B =
6
;
}
interface
interfaceC
extends
interfaceA, interfaceB {
int
PROP_C =
7
;
}
public class
InterfaceExtendsTest {
public static void
main(String[] args) {
System.out.println(interfaceC.PROP_A);
// 5
System.out.println(interfaceC.PROP_B);
// 6
System.out.println(interfaceC.PROP_C);
// 7
}
}
使用接口:
接口有如下用途:
1.定义变量,也可用于强制类型转换。
2.调用接口中定义的常量。
3.被其他类实现。
一个类可以实现多个接口,实现使用 implements 关键字。简单的例子:
interface
Alarm{
void
alarm();
}
abstract class
Door{
void
open();
void
close();
}
class
AlarmDoor
extends
Door
implements
Alarm{
void
open(){
//...
}
void
close(){
//...
}
void
alarm(){
//...
}
}
开门,关门,报警,如果将三个功能都放在抽象类里,那么这个抽象类的所有子类就都要具有报警功能,但有个门不一定具有报警功能;如果三个功能都放在接口里,那么所有需要用到报警功能的类就要实现这个接口的开门和关门功能,但火灾报警器就根本不具备开关门的功能。
内部类
内部类定义在其他类的内部,包含内部类的类也被称为外部类。内部类提供了更好的封装,内部类隐藏在外部内之内,内部类可以直接访问外部类的私有数据,外部类不能访问内部类的实现细节。匿名内部类适合用于创建仅需使用一次的类。定义内部类和外部类的语法大致相同,内部类比外部类多可以使用三个修饰符:private,protected,static;非静态内部类不能拥有静态成员。
非静态内部类:
成员内部类分为:静态内部类和非静态内部类,使用 static 修饰的成员内部类就是静态内部类,反正就是非静态成员内部类。
非静态成员内部类示例:
public class
Cow {
private double
weight;
public
Cow() {
}
public
Cow(
double
weight) {
this
.weight = weight;
}
private class
CowLeg {
// 非静态内部类
private double
length;
private
String color;
public
CowLeg() {
}
public
CowLeg(
double
length, String color) {
this
.length = length;
this
.color = color;
}
public double
getLength() {
return
length;
}
public void
setLength(
double
length) {
this
.length = length;
}
public
String getColor() {
return
color;
}
public void
setColor(String color) {
this
.color = color;
}
public void
info() {
System.out.println(
"当前牛腿的颜色是:"
+ color +
",高:"
+ length);
System.out.println(
"本牛腿所在奶牛重:"
+ weight);
}
}
public void
test() {
CowLeg c1 =
new
CowLeg(
1.12
,
"黑白相间"
);
c1.info();
}
public static void
main(String[] args) {
Cow cow =
new
Cow(
378.5
);
cow.test();
}
}
静态内部类
使用 static 来修饰一个内部类,这个内部类就属于外部类本身,而不是属于外部类的某个对象。称为静态内部类(类内部类)。静态内部类可以包含静态成员,也可以包含非静态成员。静态内部类不可以访问外部类的实例成员,只能访问外部类的类成员。