Java面向对象一篇通:从类与对象到Lambda(万字详解)
目录
1. 类(Class)
2. 对象(Object)
3. 类与对象的关系
4. 类的基本语法
1. 构造器(Constructor)
2. this 关键字
3. 封装(Encapsulation)
4. Javabean
5. static 关键字
总结
5.final
6.继承
1.认识继承
2.权限修饰符
3.继承的特点编辑
1.单继承
2.就近原则
4.方法重写
5.子类构造器的特点
7.多态
1.认识多态
2.多态的好处
3.多态下的类型转换
8.单例类
1.饿汉式单例类编辑
2.懒汉式单例类
9.枚举类
10.抽象类
11.接口
注意事项
13.代码块
14.内部类
1.成员内部类
2.静态内部类
3.局部内部类
15.函数式编程
16.lambada表达式
1. 从匿名内部类到Lambda的初始转换
2. 参数类型的省略
3. 单参数的括号省略
4. 单行代码的大括号和分号省略
5. 多参数但无操作的参数忽略
6. 方法引用的进一步简化
简化规则总结表
简化流程示例
关键原理
要想了解面向对象那么我们就要先了解一下去对象那么:什么是对象呢?
🔑现实中的明星就可以看到做对象,她代表一种特殊的类型
面向对象编程(Object-Oriented Programming, OOP)是一种以对象为核心的编程范式。它将现实世界中的事物抽象成程序中的“对象”,每个对象都包含状态和行为,从而使程序结构更清晰、更易于管理和扩展。
在 Java 中,对象是类的一个具体实例。可以把类看作是模板或蓝图,而对象则是根据这个模板创建出的实际“实体”。每个对象都具有:
-
状态(属性):对象的特性,例如一个
Star
对象可能有姓名和年龄。 -
行为(方法):对象能执行的操作,比如
speak()
方法。
对象通过 new
关键字创建,并在内存中分配空间,从而能够独立地保存状态和执行行为。 PS: 类的命名规则也会有讲究的采用[[java命名规则|大驼峰命名法]]
类型 | 命名规则 | 示例 |
---|---|---|
类名 | 大驼峰命名法 | PersonInfo |
接口名 | 大驼峰命名法 | DataService |
方法名 | 小驼峰命名法 | getUserInfo() |
变量名 | 小驼峰命名法 | userAge |
常量 | 全大写 + 下划线 | MAX_VALUE |
众所周知,面向对象有三大特性:封装、继承、多态,下面我们就来讲解面向对象吧!
1. 类(Class)
-
定义: 类是一种 模板或蓝图,它定义了一类对象共有的属性(成员变量)和行为(成员方法)。类本身并不占用内存,只有在创建对象时才会分配内存。
-
作用:
-
组织代码: 将相关的数据和方法封装在一起,形成一个独立的模块。
-
抽象现实: 用类来描述现实世界中的事物,将其共同的特性抽取出来。
-
-
语法示例:
public class Person { // <span style="color: red;">属性</span> private String name; private int age; // <span style="color: red;">构造方法</span>:用于初始化对象 public Person(String name, int age) { this.name = name; this.age = age; } // <span style="color: red;">方法</span>:定义对象的行为 public void speak() { System.out.println("Hello, my name is " + name); } }
-
重点总结:
-
类是 抽象描述,而不是具体存在的实例。
-
它定义了对象的 属性 和 行为。
-
2. 对象(Object)
-
定义: 对象是类的 具体实例。当你根据一个类创建对象时,就在内存中为这个对象分配了存储空间,并且这个对象拥有类中定义的属性和方法。
-
作用:
-
数据存储: 对象拥有自己独立的属性值,即使是同一个类的不同对象,其状态也可以不同。
-
行为执行: 对象可以调用类中定义的方法,从而实现特定的功能。
-
-
创建对象的语法:
public class Main { public static void main(String[] args) { // <span style="color: red;">创建对象</span>:使用 new 关键字调用构造方法 Person person = new Person("Alice", 25); person.speak(); // 输出:Hello, my name is Alice } }
-
重点总结:
-
对象是类的 实例化,在内存中存在具体数据。
-
每个对象都有自己的 状态(属性值)和 行为(方法)。
-
3. 类与对象的关系
-
类是模板,对象是实例 一个类可以创建多个对象,每个对象都根据同一个模板构建,但它们的数据可以不同。
重点: 类提供了抽象和定义,对象则是真正参与运算和数据存储的实体。
-
内存分配: 类的定义本身不会占用太多内存,而对象创建时会在堆内存中分配空间存储属性数据。
-
行为调用: 通过对象调用类中定义的方法,实现对数据的操作和功能的执行。
4. 类的基本语法
1. 构造器(Constructor)
-
定义: 构造器是 用于创建和初始化对象的特殊方法。 它的名称必须与类名相同,并且没有返回值。
-
特点:
-
自动调用: 每次使用
new
创建对象时,都会自动调用构造器。 -
重载: 可以根据需要定义多个构造器(构造器重载),以支持不同的初始化方式。
-
无参构造器: 如果没有显式定义构造器,编译器会自动提供一个默认的无参构造器。
-
-
示例:
public class Person { private String name; private int age; // 无参构造器 public Person() { this.name = "Unknown"; this.age = 0; } // 带参构造器 public Person(String name, int age) { this.name = name; this.age = age; } }
-
类默认就自带了一个无参构造器
-
如果为类定义了有参数构造器,类默认的无参数构造器就没有了,此时如果还想用无参数构造器,就必须自己手写一个无参数构造器出来。
2. this 关键字
-
定义: this 是一个引用变量,指向当前对象的引用。
-
用途:
-
区分成员变量与局部变量: 当成员变量与局部变量同名时,使用
this
可以明确指向当前对象的成员变量。 -
调用构造器: 可用
this(...)
调用本类中其它构造器,避免代码重复。 -
返回当前对象: 在链式调用中,经常用
this
返回当前对象。
-
-
示例:
public class Person { private String name; private int age; public Person(String name, int age) { // 使用 this 解决局部变量和成员变量同名问题 this.name = name; this.age = age; } public Person() { // 调用带参构造器 this("Unknown", 0); } public printThis }
3. 封装(Encapsulation)
-
定义: 封装是 将数据(属性)和操作数据的方法组合在一起,并对外隐藏实现细节 的一种技术。
-
实现方式:
-
访问修饰符: 使用
private
、protected
和public
控制对类内部成员的访问。 -
Getter 和 Setter 方法: 通过公开的方法来访问和修改私有属性,保证数据安全性和合法性。
-
-
优点:
-
数据保护: 限制外部直接访问和修改对象状态。
-
提高代码维护性: 内部实现可随时修改,而不影响外部调用者。
-
-
示例:
public class Student { // 私有属性,外部不可直接访问 private String name; private int age; // Getter 方法 public String getName() { return name; } // Setter 方法 public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { if(age >= 0) { // 合法性检查 this.age = age; } } }
使用私有关键字(private),来隐藏成员变量,使用公开的pubilc修饰的get和set方法来合理暴露成员变量的取值和赋值!
4. Javabean
-
定义: JavaBean 是一种遵循特定 编写规范的 Java 类,用于封装多个对象属性,便于组件化开发和数据传输。(可暂且看做是[[Javabean和实体类|实体类]])
-
规范要求:
-
类必须有一个 无参构造器。
-
类的属性通常使用私有访问修饰符,并提供 public 的 Getter/Setter 方法。
-
实现序列化接口(
Serializable
),以支持对象的序列化(可选,但在某些场景下要求)。
-
-
示例:
import java.io.Serializable; public class Employee implements Serializable { private String id; private String name; // 无参构造器 public Employee() { } // Getter 和 Setter 方法 public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
5. static 关键字
-
定义: static 用于声明类级别的成员(变量或方法),这些成员不依赖于任何对象的实例,而属于整个类。
-
用途:
-
静态变量: 类变量,在所有对象之间共享同一份数据。
-
静态方法: 可以在不创建对象的情况下调用,通常用于工具类或与对象状态无关的操作。
-
静态代码块: 用于类加载时进行一次性的初始化操作。
-
-
注意事项:
-
静态方法中不能直接访问非静态(实例)成员。
-
静态成员在内存中只有一份,适合保存常量或全局状态。 总结:
-
特性 | 说明 |
---|---|
属于类 | static 方法属于类,而不是对象。 |
访问限制 | 只能访问 static 变量和 static 方法,不能访问实例变量和实例方法。 |
不能使用 this 和 super | 因为 static 方法不属于对象。 |
适合作为工具方法 | 适用于无状态、通用的功能,如 Math.pow() 、Arrays.sort() 。 |
不能被重写,但可以隐藏 | 静态方法在子类中不会覆盖父类方法,而是隐藏。 |
main 方法必须是 static | JVM 需要在不创建对象的情况下调用 main 方法。 |
-
示例:
public class MathUtil { // 静态变量:常量 public static final double PI = 3.14159; // 静态方法:工具方法 public static int add(int a, int b) { return a + b; } // 静态代码块:类加载时执行 static { System.out.println("MathUtil 类加载..."); } } // 调用方式: // MathUtil.PI // MathUtil.add(3, 5); 如果在对象中,我们可以直接用类名来调用静态方法 //学生类 public class Student { private double score; // 静态方法:有static修饰,属于类持有。 public static void printHelloWorld(){ System.out.println("Hello World"); System.out.println("Hello World"); System.out.println("Hello World"); } // 实例方法:没有static修饰,属于对象持有。 public void printPass(){ System.out.println(score >= 60 ? "通过了" : "您挂科了!"); } public void setScore(double score) { this.score = score; } } //测试类 public class Test { public static void main(String[] args) { // 目标:认识static修饰和不修饰方法的区别 // 1、类名.静态方法(推荐) Student.printHelloWorld(); // 2、对象.静态方法(不推荐) Student s1 = new Student(); s1.printHelloWorld(); // 3、对象名.实例方法 // Student.printPass(); // 报错 s1.setScore(59.5); s1.printPass(); // 规范:如果这个方法只是为了做一个功能且不需要直接访问对象的数据,这个方法直接定义成静态方法 // 如果这个方法是对象的行为,需要访问对象的数据,这个方法必须定义成实例方法 // Test.printHelloWorld2(); printHelloWorld2(); } public static void printHelloWorld2(){ System.out.println("Hello World"); System.out.println("Hello World"); System.out.println("Hello World"); System.out.println("Hello World"); } }
总结
-
构造器:用于初始化对象,没有返回值,并且可以重载。
-
this:引用当前对象,用于区分成员变量和局部变量,以及调用其他构造器。
-
封装:隐藏对象的内部实现细节,通过访问修饰符和 Getter/Setter 实现。
-
Javabean:遵循特定规范的 Java 类,便于组件化开发,通常具备无参构造器和 Getter/Setter 方法。
-
static:声明类级别成员,不依赖于对象实例,可用于常量、工具方法等场景.
掌握这些概念有助于编写结构清晰、易维护且高内聚低耦合的 Java 程序。
5.final
-
认识final
final关键字是最终的意思
修饰类:表示该类为最终类,意思是该类不能被继承了。
修饰方法:表示该方法为最终方法,不能被重写。
修饰变量:表示变量不能被修改,只能被赋值一次。
PS: **final修饰基本类型的变量,变量存储的数据不能被改变。 final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的。
-
常量
-
使用了 static final 修饰的成员变量就被称为常量.
-
作用:常用于记录系统的配置信息。 注意:常量名的命名规范:建议使用大写英文单词,多个单词使用下划线连接起来。 优势:1. 代码的可读性更好,维护性也更好。 2.程序编译后,会被宏替换,出现常量的地方,会被替换成字面量,这和字面量的性质是一样的
6.继承
1.认识继承
了解继承之前我们先来了解一下为什么要有继承,我们来看下面这段代码 可以看到这段代码有很多重复的部分,这部分代码的复用性不高,所以我们可以把相同的代码用一个父类来表示,用子类继承父类的代码,这样我们就可以实现代码的复用 !
2.权限修饰符
private:z只能在本类中访问(改变参数,可以提供get和set方法) 缺省:本类,和同一个包中的类 [[protected在多态中的访问权限|protected]]::本类同一个包中的类,子孙类(只要被继承的类都可以🧐) pubic:全局都可以访问
3.继承的特点
1.单继承
继承中的类为什么不支持多继承呢?
假设如果有一个子类继承了两个父类,那两个父类中又有相同的方法,这时候去调用就会乱套
2.就近原则
-
在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则的。
-
先子类局部范围找,然后子类成员范围找,然后父类成员范围找,如果父类范围还没有找到则报错。
-
如果子父类中,出现了重名的成员,会优先使用子类的,如果此时一定要在子类中使用父类的怎么办?
-
可以通过super关键字,指定访问父类的成员:
super.父类成员变量/父类成员方法
4.方法重写
-
当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
-
重写小技巧:使用Override注解,他可以指定java编译器,检查我们方法重写的格式是否正确,代码可读性也会更好。
方法重写的其它注意事项
-
子类重写父类方法时,访问权限必须大于或者等于父类该方法的权限( public > protected > 缺省 )。
-
重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小。
-
私有方法、静态方法不能被重写,如果重写会报错的。
5.子类构造器的特点
-
子类构造器都会先调用父类构造器,在调用自己的
-
子类构造器都会调用super()方法(写不写都有),会自动调用父类的无参构造器
-
如果父类没有无参数构造器,则我们必须在子类构造器的第一行手写super(….),指定去调用父类的有参数构造器。
// 父类
// 继承的好处:1.代码复用 2.减少了重复代码。
public class People {
private String name;
private char sex;
public People() {
}
public People(String name, char sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
package com.itheima.extends6constructor;
// 子类
public class Teacher extends People {
private String skill; // 技术。
public Teacher() {
//自动调用无参构造器super();
}
public Teacher(String name, String skill, char sex) {
// 子类构造器调用父类构造器的应用场景。
// 可以把子类继承自父类有参构造器的参数
super(name, sex);
this.skill = skill;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
//测试类
public class Test{
public static void main(String[] args) {
// 目标:子类构造器调用父类构造器的应用场景。
Teacher t = new Teacher("dlei", "java、大数据、微服务", '男');
System.out.println(t.getName());
System.out.println(t.getSkill());
System.out.println(t.getSex());
}
}
补充:可以通过this()去调用兄弟构造器
ps:在super不能再static方法中调用super
class Parent {
static void display() {
System.out.println("Parent static 方法");
}
}
class Child extends Parent {
static void display() {
// super.display(); // ❌ 错误,static 方法不能使用 super
System.out.println("Child static 方法");
}
}
7.多态
1.认识多态
什么是多态?
-
多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态。
-
多态的具体代码体现
多态的前提
-
有继承/实现关系;存在父类引用子类对象;存在方法重写。
多态的一个注意事项
-
多态是对象、行为的多态,Java中的属性(成员变量)不谈多态。
2.多态的好处
使用多态的好处
-
在多态形式下,右边对象是解耦合的,更便于扩展和维护。
-
定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利。
多态下会产生的一个问题,怎么解决?
-
多态下不能使用子类的独有功能。(子类中有,父类中没有的方法)
public class Animal {
String name = "动物";
public void run(){
System.out.println("动物会跑~~~");
}
}
//继承了动物类
public class Tortoise extends Animal{
String name = "乌龟";
@Override
public void run() {
System.out.println("🐢跑的贼慢~~~");
}
}
public class Wolf extends Animal{
String name = "狼";
@Override
public void run() {
System.out.println("🐺跑的贼溜~~~");
}
}
public class Test {
public static void main(String[] args) {
// 1、对象多态、行为多态。
Animal a1 = new Wolf();
a1.run(); // 方法:编译看左边,运行看右边
//结果:🐺跑的贼溜~~~
System.out.println(a1.name); // 成员变量:编译看左边,运行也看左边
Animal a2 = new Tortoise();
a2.run(); // 方法:编译看左边,运行看右边
//结果:🐢跑的贼慢~~~
System.out.println(a2.name); // 成员变量:编译看左边,运行也看左边
}
}
^9eed78
注意:多态·的权限访问也看左边,例如:使用[[protected在多态中的访问权限|protected]]访问限定符时,虽然重写了子类的方法,但是,权限的检查是在编译的时候进行的。所以,虽然
protected
规定子孙类可以访问,但是利用多态的性质编译的时候是在左边父类检查权限限定符
3.多态下的类型转换
//动物类、狼类、乌龟类在上方[[#^314268]]
public class Test {
public static void main(String[] args) {
// 目标:认识多态的代码。
// 1、多态的好处1: 右边对象是解耦合的。
Animal a1 = new Tortoise();
a1.run();
// a1.shrinkHead(); // 多态下的一个问题:多态下不能调用子类独有功能。
// 强制类型转换:可以解决多态下调用子类独有功能
Tortoise t1 = (Tortoise) a1;
t1.shrinkHead();
// 有继承关系就可以强制转换,编译阶段不会报错!
// 运行时可能会出现类型转换异常:ClassCastException
// Wolf w1 = (Wolf) a1;
System.out.println("=======================");
Wolf w = new Wolf();
go(w);
Tortoise t = new Tortoise();
go(t);
}
public static void go(Animal a){
System.out.println("开始。。。。。");
a.run();
// a1.shrinkHead(); // 报错,多态下不能调用子类独有功能。
// java建议强制转换前,应该判断对象的真实类型,再进行强制类型转换。
if(a instanceof Wolf){
Wolf w1 = (Wolf) a;
w1.eatSheep();
}else if(a instanceof Tortoise){
Tortoise t1 = (Tortoise) a;
t1.shrinkHead();
}
}
}
ps:instanceof
是 JavaScript(以及 Java、PHP 等语言)中的一个运算符,用于检查一个对象是否是某个构造函数(类)的实例。
8.单例类
了解单例类之前,先来了解一下设计模式!
-
Java 中的[[Java中的设计模式|设计模式]](Design Patterns)是一套被广泛认可的代码编写最佳实践,它们提供了解决特定软件设计问题的通用方法。设计模式可以帮助程序员编写更高效、可维护、可扩展的代码。
-
通常有三种类型创建型、行为型、结构型。
-
设计模式不是必须使用的规则,而是提高代码质量的经验总结。学习设计模式可以帮助你编写更易维护、可扩展的 Java 代码,在实际开发中灵活运用。
1.饿汉式单例类
// 饿汉式单例类
public class A {
// 2、定义一个静态变量,用于存储本类的一个唯一对象。
// public static final A a = new A();
private static A a = new A();
// 1、私有化构造器: 确保单例类对外不能创建太多对象,单例才有可能性。
private A() {
}
// 3、提供一个公开的静态方法,返回这个类的唯一对象。
public static A getInstance() {
return a;
}
}
2.懒汉式单例类
// 懒汉式单例类。
public class B {
// 2、私有化静态变量
private static B b;
// 1、私有化构造器
private B() {
}
// 3、提供静态方法返回对象: 真正需要对象的时候才开始创建对象。
public static B getInstance() {
if (b == null) {
// 第一次拿对象时,会创建对象,给静态变量b记住。
b = new B();
}
return b;
}
}
9.枚举类
枚举类是一种特殊的类
-
枚举类中的第一行,只能写枚举类的对象名称,且要用逗号隔开。
-
这些名称,本质是常量,每个常量都记住了枚举类的一个对象。
特点
-
枚举都是最终类,不可以被继承,枚举类都是继承java.lang.Enum类的。
-
枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量会记住枚举类的一个对象。
-
枚举类的构造器都是私有的(写不写都只能是私有的),因此,枚举类对外不能创建对象。
-
编译器为枚举类新增了几个方法。
-
枚举类适合做信息的分类与标识 ps:枚举上下左右
public class Constant {
public static final int UP = 0; // 上
public static final int DOWN = 1; // 下
public static final int LEFT = 2; // 左
public static final int RIGHT = 3; // 右
}
public class Test2 {
public static void main(String[] args) {
// 目标:掌握枚举类的应用场景:做信息的分类和标志。
// 需求:模拟上下左右移动图片。
// 第一种是常量做信息标志和分类: 但参数值不受约束。
move(Constant.UP);
// 第二种是枚举做信息标志和分类: 参数值受枚举类约束。
move2(Direction.DOWN);
}
public static void move2(Direction direction){
// 根据这个方向做移动:上下左右。
switch (direction){
case UP :
System.out.println("向上移动");
break;
case DOWN :
System.out.println("向下移动");
break;
case LEFT :
System.out.println("向左移动");
break;
case RIGHT :
System.out.println("向右移动");
break;
}
}
public static void move(int direction){
// 根据这个方向做移动:上下左右。
switch (direction){
case Constant.UP :
System.out.println("向上移动");
break;
case Constant.DOWN :
System.out.println("向下移动");
break;
case Constant.LEFT :
System.out.println("向左移动");
break;
case Constant.RIGHT :
System.out.println("向右移动");
break;
default:
System.out.println("输入有误");
}
}
}
10.抽象类
-
在Java中有一个关键字叫:abstract,它就是抽象的意思,可以用它修饰类、成员方法。
-
abstract修饰类,这个类就是抽象类。
-
abstract修饰方法,这个方法就是抽象方法。
修饰符 abstract class 类名{ 修饰符 abstract 返回值类型 方法名称(形参列表); }
// 抽象方法:必须abstract修饰,只有方法签名,不能有方法体 public abstract class A { public abstract void test(); }
-
抽象类中不一定要有抽象方法,有抽象方法的类必须是抽象类。
-
类有的成员:成员变量、方法、构造器,抽象类都可以有。
-
抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
-
一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
11.接口
接口的好处
-
弥补了类单继承的不足,一个类同时可以实现多个接口,使类的角色更多,功能更强大。
-
让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现(更利于程序的解耦合)。
public interface A {
// JDK 8之前,接口中只能定义常量和抽象方法。
// 1、常量:接口中定义常量可以省略public static final不写,默认会加上去。
String SCHOOL_NAME = "黑马程序员";
// public static final String SCHOOL_NAME2 = "黑马程序员";
// 2、抽象方法: 接口中定义抽象方法可以省略public abstract不写,默认会加上去。
// public abstract void run();
void run();
String go();
}
public interface B {
void play(); // 玩
}
// C被称为实现类。同时实现了多个接口。
// 实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则这个类必须定义成抽象类
class C implements B , A{
@Override
public void run() {
System.out.println("C类重写了run方法");
}
@Override
public String go() {
return "黑马找磊哥!";
}
@Override
public void play() {
System.out.println("C类重写了play方法");
}
}
从JDK8开始,接口interface增加了三种方法
public interface A{
/**
* 1、默认方法(实例方法):使用用default修饰,默认会被加上public修饰。 * 注意:只能使用接口的实现类对象调用
**/
default void test1(){
...
}
/**
* 2、私有方法:必须用private修饰(JDK 9开始才支持)
**/
private void test2(){
...
}
/**
* 3、类方法(静态方法):使用static修饰,默认会被加上public修饰。 * 注意:只能用接口名来调用。
**/
static void test3(){
...
}
}
-
默认方法:使用default修饰,使用实现类的对象调用。
-
静态方法:static修饰,必须用当前接口名调用
-
私有方法:private修饰,jdk9开始才有的,只能在接口内部被调用。
-
他们都会默认被public修饰。
注意事项
1、接口与接口可以多继承:一个接口可以同时继承多个接口。
2、一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承,也不支持多实现。
4、一个类继承了父类,又同时实现了接口,如果父类中和接口中有同名的默认方法,实现类会优先用父类的。
5、一个类实现了多个接口,如果多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
13.代码块
代码块是类的5大构造成分之一(成员变量、构造器、方法、代码块、内部类) 代码块分为两种:
-
静态代码块:
Ø 格式:static { }
Ø特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次。
Ø作用:完成类的初始化,例如:对静态变量的初始化赋值。
public class CodeDemo1 {
public static String schoolName;
public static String[] cards = new String[54];
// 静态代码块:有static修饰,属于类,与类一起优先加载,自动执行一次
// 基本作用:可以完成对类的静态资源的初始化
static {
System.out.println("===静态代码块执行了====");
schoolName = "黑马程序员";
cards[0] = "A";
cards[1] = "2";
cards[2] = "3";
// ...
}
public static void main(String[] args) {
// 目标: 认识代码块,搞清楚代码块的基本作用。
System.out.println("===main方法执行了====");
System.out.println(schoolName);
System.out.println(Arrays.toString(cards)); // 返回数组的内容观察
}
}
-
实例代码块:
Ø 格式:{ }
Ø 特点:每次创建对象时,执行实例代码块,并在构造器前执行。
Ø 作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值。
public class CodeDemo2 {
private String name;
private String[] direction = new String[4]; // 实例变量
// 实例代码块:无static修饰。属于对象,每次创建对象时,都会优先执行一次。
// 基本作用:初始化对象的实例资源。
System.out.println("=========实例代码块执行了=========");
name = "itheima"; // 赋值
direction[0] = "N";
direction[1] = "S";
direction[2] = "E";
direction[3] = "W";
}
public static void main(String[] args) {
// 目标:实例代码块。
System.out.println("=========main方法执行了=========");
new CodeDemo2();
new CodeDemo2();
new CodeDemo2();
}
}
14.内部类
-
如果一个类定义在另一个类的内部,这个类就是内部类。
-
场景:当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。
1.成员内部类
-
就是类中的一个普通成员,类似前面我们学过的普通的成员变量、成员方法。
public class Outer {
// 成员内部类
public class Inner {
}
}
成员内部类中访问其他成员的特点 1、成员内部类种可以直接访问外部类的实例成员、静态成员。 2、成员内部类的实例方法中,可以直接拿到当前外部类对象,格式是:外部类名.this 。
public class InnerClassDemo1 {
public static void main(String[] args) {
// 目标:搞清楚成员内部类的语法。
// 成员内部类创建对象的格式:
// 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
Outer.Inner oi = new Outer().new Inner();
oi.setName("王麻子");
oi.show();
People.Heart heart = new People().new Heart();
heart.show();
}
}
class People {
private int heartBeat = 100;
public class Heart {
private int heartBeat = 80;
public void show() {
int heartBeat = 200;
System.out.println(heartBeat);// 200
System.out.println(this.heartBeat);// 80
System.out.println(People.this.heartBeat);// 100
}
}
}
2.静态内部类
有static修饰的内部类,属于外部类自己持有
外部类名.内部类名 对象名 = new 外部类.内部类(…); Outer.Inner in = new Outer.Inner();
特点
-
有static修饰的内部类。
-
外部类名.内部类名 对象名 = new 外部类.内部类(…);
-
可以直接访问外部类的静态成员,不能直接访问外部类的实例成员。
3.局部内部类
4.🔔匿名内部类(重点)
-
是一种特殊的局部内部类;
-
所谓匿名:指的是程序员不需要为这个类声明名字,默认有个隐藏的名字。
new 类或接口(参数值…) { 类体(一般是方法重写); };
特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象。 作用:用于更方便的创建一个子类对象。
public abstract class Animal {
public abstract void cry();
}
public class Test {
public static void main(String[] args) {
// 目标:认识匿名内部类,搞清楚其基本作用。
// 匿名内部类实际上是有名字:外部类名$编号.class
// 匿名内部类本质是一个子类,同时会立即构建一个子类对象
Animal a = new Animal(){
@Override
public void cry() {
System.out.println("🐱猫是喵喵喵的叫~~~~");
}
};
a.cry();
}
}
匿名内部类的真实使用场景
public class Test {
public static void main(String[] args) {
// 目标:搞清楚匿名内部类的使用形式(语法): 通常可以做为一个对象参数传输给方法使用。
// 需求:学生,老师都要参加游泳比赛。
Swim s1 = new Swim() {
@Override
public void swimming() {
System.out.println("学生🏊贼快~~~~");
}
};
start(s1);
System.out.println("====================================");
start(new Swim() {
@Override
public void swimming() {
System.out.println("老师🏊贼溜~~~~");
}
});
}
// 设计一个方法,可以接收老师,和学生开始比赛。
public static void start(Swim s) {
System.out.println("开始。。。。");
s.swimming();
System.out.println("结束。。。。");
}
}
interface Swim {
void swimming(); // 游泳方法
}
调用别人提供的方法实现需求时,这个方法正好可以让我们传输一个匿名内部类对象给其使用。
15.函数式编程
-
此“函数”类似于数学中的函数(强调做什么),只要输入的数据一致返回的结果也是一致的
-
使用Lambda函数替代某些匿名内部类对象,从而让程序代码更简洁,可读性更好。
public class LambdaDemo1 {
public static void main(String[] args) {
// 目标:认识Lambda表达式:搞清楚其基本作用。
Animal a = new Animal() {
@Override
public void cry() {
System.out.println("🐱是喵喵喵的叫~~~~");
}
};
a.cry();
//错误示范:Lambda并不是可以简化全部的匿名内部类,Lambda只能简化函数式接口的匿名内部类。
// Animal a1 = () -> {
// System.out.println("🐱是喵喵喵的叫~~~~");
// };
// a1.cry();
System.out.println("=====================================================");
/* Swim s1 = new Swim() {
@Override
public void swimming() {
System.out.println("学生🏊贼快~~~~");
}
}; */
// Lambda只能简化函数式接口的匿名内部类。
Swim s1 = () -> {
System.out.println("学生🏊贼快~~~~");
};
s1.swimming();
}
}
abstract class Animal{
public abstract void cry();
}
16.lambada表达式
Lambda表达式的简化流程主要围绕减少冗余代码展开,通过逐步省略不必要的语法元素来实现。以下是典型的简化步骤和规则:
1. 从匿名内部类到Lambda的初始转换
原始匿名内部类:
Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } };
第一步Lambda转换:
Runnable r = () -> { System.out.println("Hello"); };
关键变化:
-
删除接口名和函数名(编译器通过函数式接口推断)
-
用
->
符号替代方法结构
2. 参数类型的省略
当参数类型可以被编译器推断时,可省略显式类型声明:
// 原始带类型声明 Comparator<String> c = (String a, String b) -> a.compareTo(b); // 省略类型(编译器自动推断为String) Comparator<String> c = (a, b) -> a.compareTo(b);
3. 单参数的括号省略
当只有一个参数时,可省略参数括号:
// 原始带括号 Consumer<String> c = (s) -> System.out.println(s); // 省略括号 Consumer<String> c = s -> System.out.println(s);
4. 单行代码的大括号和分号省略
当Lambda体只有单条语句时:
-
可省略
{}
和;
-
隐含
return
(如果有返回值)
// 原始带大括号 Function<Integer, Integer> f = (x) -> { return x * x; }; // 简化后(省略括号、分号和return) Function<Integer, Integer> f = x -> x * x;
5. 多参数但无操作的参数忽略
若参数未被使用,可用 _
占位(Java 8不支持,Java 9+部分支持):
// 假设一个接口方法接受两个参数但只用第一个 BiConsumer<Integer, Integer> c = (a, _) -> System.out.println(a);
6. 方法引用的进一步简化
当Lambda仅调用已有方法时,可替换为方法引用:
// Lambda表达式 Consumer<String> c = s -> System.out.println(s); // 方法引用简化 Consumer<String> c = System.out::println;
简化规则总结表
简化条件 | 可省略元素 | 示例 |
---|---|---|
单参数 | 参数括号 () | s -> ... |
可推断类型 | 参数类型声明 | (a, b) -> ... |
单行代码 | 大括号 {} 和分号 ; | x -> x * x |
仅返回单行值 | return 关键字 | (a, b) -> a + b |
直接调用现有方法 | Lambda体 → 方法引用 | System.out::println |
简化流程示例
原始匿名类:
List<String> list = Arrays.asList("a", "b"); Collections.sort(list, new Comparator<String>() { @Override public int compare(String a, String String b) { return a.compareTo(b); } });
逐步简化:
-
转换为Lambda:
Collections.sort(list, (String a, String b) -> { return a.compareTo(b); });
-
省略参数类型:
Collections.sort(list, (a, b) -> { return a.compareTo(b); });
-
省略大括号和
return
:Collections.sort(list, (a, b) -> a.compareTo(b));
-
方法引用最终简化:
Collections.sort(list, String::compareTo);
关键原理
-
函数式接口:Lambda依赖只有一个抽象方法的接口(如
Runnable
,Comparator
)。 -
类型推断:编译器通过上下文自动推断参数类型和返回类型。
-
简洁性优先:设计目标是减少模板代码,提升可读性。
通过以上步骤,可以逐步将冗长的匿名内部类代码简化为高度紧凑的Lambda表达式或方法引用。
lambada表达式的简化案例
btn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("登录成功!"); } }); btn.addActionListener((ActionEvent e) -> { System.out.println("登录成功!"); }); btn.addActionListener((e) -> { System.out.println("登录成功!"); }); btn.addActionListener(e -> { System.out.println("登录成功!"); }); btn.addActionListener(e -> System.out.println("登录成功!")); win.setVisible(true);
// 需求:按钮年龄升序排序。可以调用sun公司写好的API直接对数组进行排序。 Arrays.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getAge() - o2.getAge(); // 按照年龄升序! } }) Arrays.sort(students, (Student o1, Student o2) -> { return o1.getAge() - o2.getAge(); // 按照年龄升序! }) Arrays.sort(students, (o1, o2) -> { return o1.getAge() - o2.getAge(); // 按照年龄升序! }); Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());