接着上一篇的内容,继续学习Java面向对象的知识。
文章目录
一、Java8增强的包装类
- 基本数据类型和包装类之间的对应关系
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
- Java提供了自动装箱和自动拆箱;
自动装箱:把一个基本类型变量直接赋给对应的包装类变量,或者赋给 Object 变量;(Object 是所有类的父类,子类对象可以直接赋给父类变量)
自动拆箱:允许直接把包装类对象直接赋给一个对应的基本类型变量; - 自动装箱和自动拆箱时必须注意类型匹配,例如 Integer 只能自动拆成 int 类型变量;
- 包装类还可以实现 基本类型变量和字符串之间的转换。
(1)字符串类型的值转换为基本类型的值有两种方式:
①利用包装类提供的 parseXxx(String s) 静态方法;
②利用包装类提供的 Xxx(String s) 构造器;
(2)基本类型转换成字符串类型:
String 类提供了多个重载的 valueOf() 方法;
String intStr = "123";
int it1 = Integer.parseInt(intStr);
String str = String.valueOf(1.25);
- 只有两个包装类引用指向同一个对象时才会返回 true;
二、处理对象
1. 打印对象和 toString() 方法
class Apple{
private String color;
private double weight;
public Apple(){
}
public Apple(String color, double weight){
this.color = color;
this.weight = weight;
}
//重写 toString() 方法
public String toString(){
return "颜色:" + color + ",重量:" + weight;
}
}
public class ToStringTest {
public static void main(String[] args) {
Apple a = new Apple("red", 6.7);
System.out.println(a);
}
}
//输出结果:
颜色:red,重量:6.7
//猪:如果不重写 toString() 方法,则输出结果如下:
Apple@1b6d3586
2. == 和 equals 方法
- 两个基本类型的变量 且都是数值类型:只要变量值相等则返回 true;
- 两个引用类型的变量,只有指向同一个对象,“==”才会返回 true;
String str1 = new String("haha");
String str2 = new String("haha");
System.out.println(str1 == str2); //返回 false
System.out.println(str1.equals(str2)); //返回 true
- 常量池:专门用于管理在编译时被确定并被保存在已编译的 .class 文件中的一些数据。他包括了关于类、方法、接口中的常量,还包括字符串常量。
public class StringJoinTest {
public static void main(String[] args) {
String s1 = "ddzz";
String s2 = "dd" + "zz";
System.out.println(s1 == s2); //返回 true
String str1 = "dd";
String str2 = "zz";
String s3 = str1 + str2;
System.out.println(s1 == s3); //返回 false
}
}
分析:Java用常量池来管理曾经用过的字符串直接量。
当执行 String s1 = “ddzz” 后,常量池中会缓存一个字符串“ddzz”;
然后执行 String s2 = “dd” + “zz” 时,就会直接指向常量池中的字符串“ddzz”,
所以第一个会返回 true。
三、类成员
1. 理解类成员
- static 修饰的成员就是类成员,static 不能修饰构造器;类成员属于整个类,不属于单个实例;
- 当通过对象访问类变量时,系统会在底层转换为通过该类来访问类变量;
- 类成员不能访问实例成员。因为类成员是属于类的,类的作用域更大,可能出现这种情况:类成员已经完成初始化,但实例成员还没有开始初始化。
2. 单例类
- 如果一个类始终只能创建一个实例,则这个类被称为单例类;
四、final 修饰符
- final 关键字用于修饰类、变量、方法;
- final 修饰变量时,表示该变量一但获得了初始值就不可被改变;
- final 既可以修饰成员变量(类变量和实例变量),也可以修饰局部变量、形参;
1. final 成员变量
- final 修饰的类变量:必须在静态初始化块中指定初始值 或声明该类变量时指定初始值。而且只能在两个地方的其中之一指定;
- final 修饰的实例变量:必须在非静态初始化块、声明该类实例变量或构造器中指定初始值。而且只能在三个地方的其中之一指定。
2. final 局部变量
- 注意:使用 final 修饰的形参不能被赋值;
public void test(final int a){
a = 5; //该语句错误,不能对 final 修饰的形参赋值
}
3. final 修饰基本类型变量和引用类型变量的区别
- final 修饰基本类型变量:不能重新赋值;
- final 修饰引用类型变量:只保证这个引用变量所引用的地址不会改变,即一直引用同一个对象,但这个对象可以发生改变。
4. 可执行“宏替换”的 final 变量
- final 修饰符的一个重要用途就是定义“宏变量”;
5. final 方法
- final 修饰的方法不能被重写,但可以被重载;
- 注意区分是重写还是定义一个新的方法;
父类中是:public final void test(){}
子类中是:public void test(){}
这是重写,会出现编译错误;
父类中是:private final void test(){}
子类中是:public void test(){}
这是重新定义新的方法,可以正常运行;
6. final 类
- final 修饰的类不可以有子类;
7. 不可变类
- 不可变类的意思是创建该类的实例后,该实例的实例变量是不可改变的。(Java提供的8个包装类和 java.lang.String 类都是不可变类)
8. 缓存实例的不可变类
五、抽象类
1. 抽象方法和抽象类
- 用 abstract 修饰符来定义;有抽象方法的类只能被定义为抽象类,抽象类里可以没有抽象方法;
- 抽象方法不能有方法体,即没有花括号,与空方法体不同;
- 抽象类不能被实例化,不能使用 new 来创建实例,主要是用于被其子类调用;
- 抽象类可以包含的成员:成员变量、方法(普通或抽象方法)、构造器、初始化块、内部类(接口、枚举);
- 含有抽象方法的类:
①直接定义了一个抽象方法;
②继承了一个抽象父类,但没有完全实现父类包含的抽象方法;
③实现了一个接口,但没有完全实现接口包含的抽象方法; - 抽象类不能用于创建实例,只能被当作父类被其他子类继承;
- 抽象方法必须由子类重写来实现;
- 由于 final 修饰的类不能被继承,修饰的方法不能被重写,所以 final 和 abstract 永远不能同时使用。
- abstract 不能用于修饰 成员变量、局部变量、构造器;
- 利用抽象类和抽象方法的优势,可以更好地发挥多态的优势,使得程序更加灵活;
2. 抽象类的作用
六、Java8改进的接口
1. 接口的概念
- 抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,可以提炼出一种更加特殊的“抽象类”——接口。
- 接口里不能包含普通方法,接口里的方法都是抽象方法。
2. Java8中接口的定义
[修饰符] interface 接口名 extends 父接口1, 父接口2...{
}
- 一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类;
- 接口里不能包含构造器和初始化块定义;
- 接口里可以包含:成员变量(只能是静态常量)、方法(只能是抽象实例方法、类方法、默认方法)、内部类(包括内部接口、枚举);
- 在接口定义成员变量时,不管是否使用 public static final 修饰符,其成员变量都默认使用这三个来修饰;
- 接口里的普通方法总是使用 public abstract 来修饰;
- 接口里的默认方法必须用 default 修饰;
- 接口里的类方法必须用 static 修饰;
- 接口里的普通方法不能有方法体,但类方法和默认方法都必须有方法体;
- 注意:如果一个 Java 源文件里定义了一个 public 接口,则该源文件的主文件名必须与该接口名相同;
3. 接口的继承
- 接口支持多继承,一个接口可以有多个直接父接口;
interface interface1{
int a = 5;
void test1();
}
interface interface2{
int b = 6;
void test2();
}
interface interface3 extends interface1, interface2{
int c = 7;
void test3();
}
public class InterfaceExtendsTest {
public static void main(String[] args) {
System.out.println(interface3.a);
System.out.println(interface3.b);
System.out.println(interface3.c);
}
}
4. 使用接口
- 接口不能用于创建变量,但可以用于声明引用类型变量;
- 接口主要有如下用途:
①定义变量,也可以用于进行强制类型转换;
②调用接口中定义的常量;
③被其他类实现; - 类实现接口的语法格式如下(用关键字 implement ):
[修饰符] class 类名 extends 父类 implements 接口1, 接口2...{
}
- 实现接口与继承父类相似,一样可以获得所实现接口定义里的常量(成员变量)、方法(抽象方法和默认方法);
- 一个类实现了一个或多个接口之后,这个类必须完全实现这些接口所定义的全部抽象方法(即重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类;
- 实现接口方法时,必须使用 public 访问控制修饰符;
5. 接口和抽象类
-
接口和抽象类都具有如下特征:
①都不能被实例化,用于被其他类继承和实现;
②都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法; -
接口和抽象类在用法上存在如下差别:
接口 | 抽象类 |
---|---|
只能包含抽象方法、静态方法、默认方法,不能为普通方法提供实现 | 可以包含普通方法 |
只能定义静态常量,不能定义普通成员变量 | 既可以定义普通成员变量,也可以定义静态常量 |
不包含构造器 | 可以包含构造器 |
不能包含初始化块 | 可以包含初始化块 |
一个类可以有多个接口,接口弥补了java单继承的不足 | 一个类只能有一个直接父类 |
6. 面向接口编程
七、内部类
- 通常,类被定义成一个独立的单元;某些情况下,会把一个类放在另一个类的内部定义,这个定义在内部的类 被称为 内部类;
- 内部类主要有如下作用:
①提供了更好的封装,可以吧内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类;
②内部类成员可以直接访问外部类的私有数据,但外部类不能访问内部类的实现细节;
③匿名内部类适合用于创建那些仅需要使用一次的类; - 内部类与外部类的区别:
内部类 | 外部类 |
---|---|
可以多使用三个修饰符:private、protected、static | 外部类不可以使用这三个修饰符 |
非静态内部类不能拥有静态成员 | / |
1. 非静态内部类
- 通常,内部类都被作为成员内部类定义,而不是作为局部内部类;成员内部类是一种与成员变量、方法、构造器、初始化块相似的类成员;局部内部类和匿名内部类则不是类成员;
- 成员内部类分为两种:静态内部类、非静态内部类;
2. 静态内部类
- 用 static 修饰的内部类;
- 静态内部类 可以包含静态成员,也可以包含非静态成员;
- 静态内部类是外部类的一个静态成员,外部类的所有方法、所有初始化块中可以使用静态内部类来定义变量、创建对象等;
- 外部类不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。
- java 允许在接口里定义内部类,默认使用 public static 修饰;
3. 使用内部类
(1)在外部类内部使用内部类
- 与平常使用普通类基本相同;
- 注意:不要在外部类的静态成员中使用非静态内部类,因为静态成员不能访问非静态成员;
(2)在外部类以外使用非静态内部类
- 在外部类以外使用非静态内部类,则内部类不能使用 private;
- 省略修饰符的内部类,只能被和外部类处于同一个包中的其他类访问;
- 使用 protected 修饰的内部类,可以被与外部类处于同一个包中的其他类访问,也可以被外部类的子类访问;
- 使用 public 修饰的内部类,可以在任何地方被访问;
- 在外部类以外的地方定义内部类:
OuterClass.InnerClass varName
- 在外部类以外的地方创建非静态内部类实例:
OuterInstance.new InnerConstructor()
class Out{
//非静态内部类
class In{
public In(String msg){
System.out.println(msg);
}
}
}
public class CreateInnerInstance {
public static void main(String[] args) {
// Out.In in = new Out().new In("测试信息...");
/**
* 上面一行代码可用如下代码替换
* Out.In in;
* Out out = new Out();
* in = out.new In("测试信息...");
*/
}
}
(3)在外部类以外使用静态内部类
- 在外部类以外的地方创建静态内部类实例:
new OuterClass.InnerConstructor()
4. 局部内部类
- 局部内部类不能在外部类的方法以外的地方使用,因此局部内部类也不能使用访问控制符和 static 修饰;
5. Java8改进的匿名内部类
- 匿名内部类适合创建那种只需要使用一次的类,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用;
- 匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口;
- 匿名内部类不能是抽象类,不能定义构造器;
- 最常用的创建匿名内部类的方式是 需要创建某个接口类型的对象,如下程序:
interface Product{
public double getPrice();
public String getName();
}
public class AnonymousTest {
public void test(Product p){
System.out.println(p.getName() + "---" + p.getPrice());
}
public static void main(String[] args) {
AnonymousTest a = new AnonymousTest();
//调用test()需要传入Product参数,这里用匿名 实现类的实例
a.test(new Product() {
@Override
public double getPrice() {
return 520;
}
@Override
public String getName() {
return "dd";
}
});
}
}
-
定义匿名内部类不需要 class 关键字,而是在定义时直接生成该匿名内部类的对象;
-
由于匿名内部类不能是抽象类,所以它必须实现它抽象父类或接口里包含的所有方法;
-
如果通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,此处的相似指的是拥有相同的形参列表;
abstract class Device{
private String name;
public abstract double getPrice();
public Device(){
}
public Device(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class AnonymousInner {
public void test(Device d){
System.out.println(d.getName() + "---" + d.getPrice());
}
public static void main(String[] args) {
AnonymousInner ai = new AnonymousInner();
ai.test(new Device("dd") {
@Override
public double getPrice() {
return 520;
}
});
Device d = new Device() {
{
System.out.println("匿名内部类的初始化块");
}
@Override
public double getPrice() {
return 725;
}
public String getName(){
return "zz";
}
};
ai.test(d);
}
}
八、Lambda 表达式
Lambda 表达式支持将代码块作为方法参数,允许使用更简洁的代码来创建只有一个抽象方法的接口的实例。
1. Lambda 表达式入门
- Lambda 表达式的主要作用是:代替匿名内部类的繁琐语句;
它由三部分构成:
①形参列表;(允许省略形参类型)
②箭头(->);
③代码块;
interface Eatable{
void taste();
}
interface Flyable{
void fly(String weather);
}
interface Addable{
int add(int a, int b);
}
public class LambdaQs {
//调用该方法需要 Eatable 对象
public void eat(Eatable e){
System.out.println("eat---" + e);
e.taste();
}
//调用该方法需要 Flyable 对象
public void drive(Flyable f){
System.out.println("fly---" + f);
f.fly("是个晴天呢");
}
//调用该方法需要 Addable 对象
public void test(Addable ad){
System.out.println("add---" + ad.add(5,2));
}
public static void main(String[] args) {
LambdaQs lq = new LambdaQs();
lq.eat(() -> System.out.println("好吃的"));
lq.drive(weather -> {
System.out.println("天气:" + weather);
System.out.println("直升机平稳飞行");
});
lq.test(((a, b) -> a + b));
}
}
2. Lambda表达式与函数式接口
3. 方法引用与构造器引用
4. Lambda表达式与匿名内部类的联系与区别
5. 使用 Lambda 表达式调用 Arrays 的类方法
九、枚举类
1. 枚举类入门
- 关键字:enum,它与 class、interface 的地位相同;
- 一个 java 源文件只能最多定义一个 public 访问权限的枚举类;
- 枚举类与普通类的区别:
①枚举类可以实现一个或多个接口,不能显式继承其他父类;
②枚举类会默认用 final 修饰,因此不能有子类;
③枚举类的构造器只能使用 private 访问控制符;
④枚举类的所有实例必须在第一行显式列出,否则这个枚举类永远都不能产生实例;这些实例会默认用 public static final 修饰; - 枚举类默认提供了一个 values() 方法,该方法可以遍历所有枚举值;
2. 枚举类的成员变量、方法、构造器
3. 实现接口的枚举类
4. 包含抽象方法的枚举类
十、对象与垃圾回收
十一、修饰符的适用范围
外部类/接口 | 成员属性 | 方法 | 构造器 | 初始化块 | 成员内部类 | 局部成员 | |
---|---|---|---|---|---|---|---|
public | √ | √ | √ | √ | √ | ||
protected | √ | √ | √ | √ | |||
包访问控制符 | √ | √ | √ | √ | ○ | √ | ○ |
private | √ | √ | √ | √ | |||
abstract | √ | √ | √ | ||||
final | √ | √ | √ | √ | √ | ||
static | √ | √ | √ | √ | |||
strictfp | √ | √ | √ | ||||
synchronized | √ | ||||||
native | √ | ||||||
transient | √ | ||||||
volatile | √ | ||||||
default | √ |
(不用任何访问控制符 就是包访问控制符,初始化块和局部成员不能使用任何访问控制符,看起来像使用了包访问控制符)
十二、使用 JAR 文件
- JAR 文件的全称是 Java Archive File,意思是 Java 档案文件。