一:封装
1.1 引入封装概念
封装的概念在生活中也有类似的体现。比如,手机就是一个封装了各种功能和数据的工具。我们只需要按下按钮、触摸屏幕等操作,我们只需要知道按下这个按钮就会发生特定的功能,而无需了解手机内部的复杂工作原理。手机作为一个封装的对象,向外界提供了对应的方法(例如拨打电话、发送短信、上网等),这些方法隐藏了实现细节,只关注用户需求的实现。
另一个例子是汽车。我们可以通过汽车的各种控制组件(如方向盘、刹车、加速器等)来操纵汽车,我们只需要知道,踩油门汽车就会加速,插钥匙汽车就会启动,而无需直接了解发动机、传动系统等复杂的内部结构和工作原理。汽车作为一个封装的对象,隐藏了内部的实现细节,通过提供外部操作接口来满足用户需求。
1.2 如何实现封装
在Java中,封装是一种面向对象编程的概念,它将数据和对数据的操作封装在一个类中,为外部隐藏实现细节,只暴露必要的方法供外部访问。通过封装,可以保证数据的安全性和一致性,并提高代码的可维护性和复用性。
在Java中,封装通过使用访问修饰符来实现:
private
:表示私有属性或方法,只能在类的内部访问,外部类和子类都无法直接访问。public
:表示公共属性或方法,可以在任何地方访问。protected
:表示受保护的属性或方法,可以在类内部、同一包内及其子类中访问。(这个在此有个印象就可以,后面会讲什么是子类)default
:这个表示什么都不写,能够让同一包中的同一类和同一包的不同类进行访问
NO | 范围 | private | default | protected | public |
1 | 同一包中的同一类 | √ | √ | √ | √ |
2 | 同一包中的不同类 | √ | √ | √ | |
2 | 不同包中的子类 | √ | √ | ||
4 | 不同包中的非子类 | √ |
封装的好处:
- 数据隐藏和安全性:通过隐藏对象的实现细节,只暴露必要的方法,可以避免外部直接访问和修改数据,保证数据的安全性。
- 系统的可维护性:封装可以将复杂的实现细节隐藏起来,使得代码更加模块化和易于维护。
- 修改的灵活性:通过封装,可以在不影响外部代码的情况下,修改类的内部实现细节。
下面通过一个代码例子来示例它们的作用:
public class Computer { private String cpu; // cpu private String memory; // 内存 public String screen; // 屏幕 String brand; // 品牌---->default属性(什么都不写) public Computer(String brand, String cpu, String memory, String screen) { this.brand = brand; this.cpu = cpu; this.memory = memory; this.screen = screen; } } public class TestComputer { public static void main(String[] args) { Computer p = new Computer("HW", "i7", "8G", "13*14"); System.out.println(p.brand); // default属性:只能被本包中类访问 System.out.println(p.screen); // public属性: 可以任何其他类访问 // System.out.println(p.cpu); // private属性:只能在Computer类中访问,不能被其他类访问,因为不能访问,所以这里会报错,所以注释了 } }
注意:访问权限处理可以限定类中成员的可见性(成员变量和成员方法),也可以控制类的可见性
假如一个类是deault的,但这个类中的成员变量和成员方法被public修饰,那么不在同一个包中的类不能访问到这个类中被public修饰的成员变量或者成员方法,因为类都访问不到,自然就访问不到类中的属性了
1.3什么是包?
bag和src就是两个不同的包,在包中可以有很多类,src包通常用于存放源代码的文件夹,其他包用于存放不同类型的代码或资源文件
如图所示:在src中有c,g,Main三个类,这三个类都是同一包中的不同类,而bag和src则是两个不同的包
1.4 package和improt
在Java中,package
和import
关键字是用于管理和引用类的机制。下面我将简要介绍它们,并提供一些代码示例。
package
关键字:
package
关键字用于定义类所属的包。包提供了一种逻辑上组织类的方式,可以方便地查找和管理类。- 包名是一个使用点分隔符
.
分隔的标识符序列。通常包名是小写字母,按照惯例使用反转的域名作为包名的前缀。 - 包声明通常在每个Java源文件的开头。例如:
package com.example.mypackage;
- 一个包可以包含多个类,它们可以彼此访问和使用。
import
关键字:
import
关键字用于导入其他包中的类或者类的静态成员。import
语句通常在包声明之后,类声明之前。例如:import java.util.List;
- 如果要导入整个包,可以使用
.*
通配符。例如:import java.util.*;
下面是一个示例代码,演示了如何使用package
和import
:
package com.example.mypackage; import java.util.List; public class MyClass { public static void main(String[] args) { List<String> myList = new ArrayList<>(); myList.add("Hello"); myList.add("World"); for (String str : myList) { System.out.println(str); } } }
import java.util.List; 表示导入了Java中的List类。这段代码告诉编译器在当前的Java类中使用List类,而无需每次都使用完整的类名java.util.List来引用它。这使得在代码中使用List类更加方便和简洁。
package com.example.mypackage; 是一个Java中的包声明语句。它指定了当前类所属的包名为 com.example.mypackage。
在Java中,虽然使用通配符(*)来导入整个包的所有类可以更加方便,但也可能引发命名冲突的问题。虽然我们写import java.util.*;
更加方便,但是我们更建议写import java.util.List;
下面是一个简答的代码示例:
import java.util.*; import java.sql.*; public class Test { public static void main(String[] args) { // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错 Date date = new Date(); System.out.println(date.getTime()); } }
这段代码的运行结果是:
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
因为util 和 sql 中都存在一个 Date 这样的类,此时就会出现歧义,导致编译出错
1.4.1 java.lang包
java.lang
是 Java 编程语言中一个特殊的包,此包在JDK1.1后会自动导入程序中,它是 Java 标准类库的核心部分。该包包含了一些常用的类,这些类被自动导入到每一个 Java 程序中。
以下是一些常用的 java.lang
类:
Object
:这是所有类的父类,包含了一些通用的方法,例如equals()
、hashCode()
、toString()
等。String
:用于表示字符串,并提供了许多处理字符串的方法,如拼接、截取、替换、转换大小写等。Integer
、Double
、Float
、Boolean
等:这些类用于包装基本数据类型,提供了一些方便的方法来处理和转换基本数据类型。Math
:提供了一些常用的数学运算方法,如绝对值、平方根、最大值、最小值等。System
:提供了与系统相关的一些属性和方法,如标准输入输出、系统时间、系统退出等。Exception
:是所有异常的基类,用于处理程序中可能发生的异常情况。
这些是 java.lang
包中一些常用的类,它们在各种 Java 应用程序中经常被使用。由于 java.lang
包是默认导入的,因此无需显式地导入这些类即可在程序中使用它们。
二:static关键字
static是java中的一个关键字,可以修饰变量和方法,甚至类,在此我们只讲解修饰变量和方法,修饰类马上就会讲到
2.1再谈学生类
public class Student { private String name; private String gender; private int age; private double gpa; public Student(String name, String gender, int age, double gpa) { this.name = name; this.gender = gender; this.age = age; this.gpa = gpa; } public static void main(String[] args) { Student s1 = new Student("Li leilei", "男", 18, 3.8); Student s2 = new Student("Han MeiMei", "女", 19, 4.0); Student s3 = new Student("Jim", "男", 18, 2.6); } }
在这段代码中,我们实例化了三个学生对象,每个对象都有直接的名字,性别,年龄和绩点,假设这三个同学是在同一个班上课的,那么我们是否需要再给每个学生再加一个课室的成员变量呢?
仔细想想发现,因为这三个学生的姓名,性别,年龄和绩点都可能存在不同,因此分别存储是有必要的,但是他们都是在同一件课室上课,所以就没必要存储三份课室的数据了,为了能只存储一份数据,因此java为我们提供了static关键字
2.2static修饰成员变量和成员方法
在Java中,
- 被static修饰的成员,称之为静态成员,也可以称为类成员
- 被static修饰的方法叫做静态成员方法
一个类的属性或者方法被static修饰后不属于某个具体的对象,是所有对象所共享的。静态成员变量和静态成员方法最大的特性:
- 不属于某个具体的对象,是所有对象所共享的。
静态成员变量和静态成员方法的特性:
- 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中,存储在方法区当中
- 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
- 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
在此我解释一下,属于对象的意思就是说这个东西(属性或者方法)是属于对象的,所以我们可以通过这个对象来访问这个东西,如果这个东西不属于对象,那么我们就不能通过这个对象来访问这个东西了
而属于类的意思就是说我们可以直接通过类来访问这个东西,因为类都会被自动加载的,所以可以直接通过类名来访问,而不需要再实例化一个对象,所以当多个对象之间需要共享某种属性的时候,我们可以将这些变量声明为static变量
下面是简单的代码示例:
public class Example { // 静态变量 public static int count = 0; // 静态方法 public static void incrementCount() { count++; } public static void main(String[] args) { // 直接通过类名调用静态变量和静态方法 Example.count = 10; Example.incrementCount(); // 通过对象调用静态变量和静态方法 Example obj = new Example(); obj.count = 20; obj.incrementCount(); } }
注意点:
- 静态方法和静态成员都是优先于对象而存在的,在加载类的时候它们同时也就被加载了,所以静态方法存在的时候,你的对象不一定存在,所以说静态方法中不能访问非静态的成员
- 而对于非静态方法和非静态成员,我们都要通过实例化一个对象才能够用,当我们实例化出对象的时候,静态成员或者方法早就被加载了,所以非静态方法可以调用静态的成员和静态的方法
- 因为静态方法可以通过类名直接调用,所以静态方法中不需要通过任何引用来调用这个静态方法,所以在静态方法中就没必要存在this引用了,所以java就规定了在静态方法中不能够有this引用
因为被static修饰的变量或者方法是被所有对象共享的,所以这个数据只会生成一份,并且如果对象A修改了这个属性或者方法,那么在对象B中,这个属性或者方法也会被影响,因为这个数据只有一份,并且是共享的,所以我们在修改static修饰的成员或者方法的时候需要小心
三:代码块
在Java中,代码块是一组用大括号括起来的语句。代码块可以用于实现特定的逻辑功能,比如初始化变量、执行某些操作或者限制变量的作用域。Java中有两种类型的代码块:
- 静态代码块(Static Blocks):
- 静态代码块是使用
static
关键字定义的,被用于初始化静态成员变量或执行一些只需要在类加载时执行一次的操作。 - 静态代码块在类加载时执行,且仅执行一次。
- 静态代码块的声明在类的成员位置,没有方法名,并且被包裹在大括号中。
- 示例代码:
public class MyClass { static { // 静态代码块中的逻辑 // 初始化静态成员变量 // 执行一些只需要在类加载时执行一次的操作 } }
- 实例代码块(Instance Blocks):
- 实例代码块是不带任何关键字的普通代码块,在类的内部定义。
- 实例代码块在创建对象时执行,每次创建对象都会执行一次。
- 实例代码块可以用来初始化实例成员变量。
- 示例代码:
public class MyClass { { // 实例代码块中的逻辑 // 初始化实例成员变量 // 执行一些需要在每次创建对象时执行的操作 } }
注意事项:
- 静态代码块和实例代码块都可以包含任何有效的Java代码。
- 静态代码块在类加载时执行,实例代码块在对象创建时执行。
- 静态代码块的执行顺序优先于实例代码块,而实例代码块的执行顺序优先于构造方法。
代码块在Java中是很有用的,可以在初始化类和对象时执行特定的操作,提供更灵活和可控的代码逻辑。
构造方法和代码块都能够对对象进行初始化,那么我们什么时候该用哪个呢?
构造方法和代码块都可以用于对象的初始化,但在不同的情况下使用它们有不同的目的和适用性。
构造方法是用于创建和初始化对象的特殊方法。它们在对象被创建时被调用,并用于对对象的实例变量进行初始化。构造方法可以带有参数,用于接受外部传入的值,并将其赋给相应的实例变量。通常情况下,我们使用构造方法来确保对象在被创建时具有正确的初始状态。可以有多个构造方法,它们可以根据传入的参数个数或类型的不同而重载。
代码块是在类中定义的一段代码,用于在对象被创建之前进行额外的初始化操作。代码块可以分为两种类型:静态代码块和实例代码块。
我们要根据情况选择使用构造方法或代码块:
- 如果需要在对象被创建时对实例变量进行初始化需要传入参数,应使用构造方法,因为代码块虽然也能对对象进行初始化,但是不能够传入参数
- 如果需要在对象创建之前对实例变量进行额外的初始化操作,可以使用实例代码块。
- 如果需要对静态成员变量进行初始化,可以使用静态代码块。
需要注意的是,构造方法和代码块是可以共存的,它们之间的执行顺序是:静态代码块(如果有) -> 实例代码块(如果有) -> 构造方法。
下面通过一个简单的代码例子来说明:
public class Example { private int a; static { System.out.println("静态代码块执行"); } { System.out.println("实例代码块执行"); } public Example() { System.out.println("构造方法执行"); } public static void main(String[] args) { Example example = new Example(); } }
输出结果:
静态代码块执行
实例代码块执行
构造方法执行
注意事项
- 静态代码块不管生成多少个对象,其只会执行一次
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行
- 实例代码块和构造方法只有在创建对象时才会执行,有多少个对象就执行多少次
四:内部类
4.1内部类概念的引入
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。
下面我们来举一个内部类在现实生活中的体现:
一个人是一个对象,一个心脏同样也是一个对象,但是人这个对象能脱离心脏这个对象而独立存在吗?显然是不能的,所以在人这个类中还有一个心脏这个对象,一个类中还有另一个类,这就是内部类
4.2内部类的分类
在Java中,内部类是定义在其他类内部的类。内部类可以访问外部类的成员(包括私有成员)并与外部类进行通信。内部类主要有以下几种类型的内部类:成员内部类、静态内部类、方法内部类和匿名内部类。
4.2.1成员内部类
- 成员内部类:成员内部类是定义在其他类的内部的类。它与外部类有一个包含关系,并且可以直接访问外部类的成员。以下是一个成员内部类的简单示例代码:
public class OuterClass { private int outerData; public void outerMethod() { InnerClass inner = new InnerClass(); inner.innerMethod(); } public class InnerClass { public void innerMethod() { outerData = 10; // 访问外部类的成员变量 System.out.println("Inner method"); } } }
在上面的示例中,InnerClass
是 OuterClass
的成员内部类。innerMethod()
方法可以直接访问 outerData
变量。
注意事项
- 外部类中的任何成员都可以在实例内部类方法中直接访问
- 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
- 实例内部类对象必须在先有外部类对象前提下才能创建
- 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问
- 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
- 实例内部类的非静态方法中包含了一个指向外部类对象的引用
对于创建成员内部类对象:
当创建成员内部类对象时,可以使用以下两种方式:
- 直接使用外部类的实例去创建内部类对象:
OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
- 这种方式需要先创建外部类的实例(
new OutClass()
),然后通过(.)在外部类实例的基础上再实例化内部类对象(new InnerClass()
)。
因为实例内部类所处的位置与外部类成员位置相同,所以我们在描述一个内部类的时候,要带上外部类,所以OutClass.InnerClass
是这个内部类的类型
- 先创建外部类对象,再使用外部类对象去创建内部类对象:
OutClass outClass = new OutClass(); OutClass.InnerClass innerClass2 = outClass.new InnerClass();
- 这种方式先创建外部类的实例(
new OutClass()
),然后通过外部类对象的实例化内部类对象(outClass.new InnerClass()
)。
无论使用哪种方式,都可以实例化成员内部类对象。这两种方式的选择取决于具体的使用场景和代码结构。
对于第6点,也就是说,在含有内部类的外部类中,它会有两个this引用
- this.data1
- outerclass.this.data2
我们把outerclass.this理解成一个整体就好,代表这是外部类的引用
4.2.2静态内部类
- 静态内部类:静态内部类是使用
static
修饰符定义的内部类。它与外部类没有包含关系,并且可以直接从外部类的静态方法访问。以下是一个静态内部类的简单示例代码:
public class OuterClass { private static int outerData; public static void outerMethod() { InnerClass inner = new InnerClass(); inner.innerMethod(); } public static class InnerClass { public void innerMethod() { outerData = 10; // 访问外部类的静态变量 System.out.println("Inner method"); } } }
在上面的示例中,InnerClass
是 OuterClass
的静态内部类。innerMethod()
方法可以直接访问 outerData
变量。
注意事项
- 在静态内部类中只能访问外部类中的静态成员
- 创建静态内部类对象时,不需要先创建外部类对象
对于第一点:
如果我们确实想访问外部类的非静态成员,该怎么办呢?
我们虽然不能直接访问外部类的非静态成员,但是可以通过创建外部类的实例来访问外部类的非静态成员和非静态方法
class outerclass{ private int x; public void foot(){ } static class staticInnerClass(){ void bar(){ outerclass outer = new outerclass(); outer.x = 10; outer.foot(); } } }
对于第二点:
OutClass.InnerClass innerClass = new OutClass.InnerClass();
这就是静态内部类对象的创建方式
注意,static修饰类的话,这个类只能是静态内部类,并且这个内部类种能有非静态的成员变量和成员方法,这与静态方法中不能有非静态成员变量和非静态成员方法并不矛盾
4.2.3方法内部类
- 方法内部类:方法内部类是定义在方法内部的类。方法内部类只在方法的作用域内可见,可以访问外部类的成员,并且只能访问
final
修饰的局部变量。以下是一个方法内部类的简单示例代码:
public class OuterClass { public void outerMethod() { int final outerData = 10; class InnerClass { public void innerMethod() { System.out.println("Inner method: " + outerData); } } InnerClass inner = new InnerClass(); inner.innerMethod(); } }
在上面的示例中,InnerClass
是 outerMethod()
方法内部的类。它可以访问 outerData
变量。
4.2.4匿名内部类
匿名内部类需要用到很多后面的知识,这里我们后面再进行讲解。