目录
要想了解面向对象那么我们就要先了解一下去对象那么:什么是对象呢?
🔑现实中的明星就可以看到做对象,她代表一种特殊的类型
面向对象编程(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());