一、类的封装
1.类的封装定义
封装是面向对象程序设计中的一个重要概念,它指的是将数据和方法组合成一个类,并对外部隐藏类的内部实现细节,只提供公共的接口供其他类使用。通过封装,可以确保数据的安全性和完整性,同时也能够简化类的使用和维护。
在Java中,封装通过访问修饰符来实现,通常将类的属性设置为私有的,然后提供公共的方法来访问和修改这些属性。这样可以有效地控制类的数据,防止外部直接对其进行操作,从而提高了类的安全性和可维护性。
例如,一个简单的Java类的封装定义如下:
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在这个例子中,name和age属性被设置为私有的,外部无法直接访问和修改它们。而通过公共的getName、setName、getAge和setAge方法,可以实现对这些属性的访问和修改。这样就实现了封装,保护了类的内部数据,并提供了公共的接口供其他类使用。
2.类的封装特性
(1)数据封装
数据封装是指将数据和操作数据的方法封装在一起,通过定义访问权限来保护数据,这样可以确保数据的安全性和一致性。在实际开发中,可以通过封装来隐藏对象的内部状态,只允许通过对象的方法来访问和修改数据,从而降低了对象之间的耦合性,提高了系统的可维护性和可扩展性。
(2)访问控制
访问控制是类的封装特性之一,通过访问修饰符来限制对类的成员的访问权限,实现了对类内部数据和方法的保护。这样可以避免外部的代码直接访问和修改类的内部数据,保证了数据的安全性和一致性。
(3)抽象
抽象是类的封装特性之一,通过抽象可以将具体的事物抽象成一个独立的实体,从而可以更加有效地管理程序。通过抽象,可以将共性的部分提取出来,形成一个抽象的类或接口,然后具体的实现可以根据需要进行扩展和改进,从而提高了程序的灵活性和可维护性。
(4)继承
继承是类的封装特性之一,通过继承可以将相关的操作封装放到基类中,然后子类可以继承基类的属性和方法,并根据需要进行相应的改进和扩展。这样可以减少代码的冗余,提高了程序的可复用性和扩展性。同时,通过继承可以构建类的层次结构,从而提高了代码的组织性和可读性。
3.this关键字
- 在类的方法中,this关键字可以用来引用当前对象的成员变量和方法。当方法内部的局部变量和成员变量同名时,使用this关键字可以明确指示使用的是成员变量,而不是局部变量。
public class MyClass {
private int x;
public void setX(int x) {
this.x = x; // 使用this关键字引用成员变量x
}
}
- 在构造方法中,this关键字可以用来调用当前类的其他构造方法。这种方式称为构造方法的重载。
public class MyClass {
private int x;
private int y;
public MyClass() {
this(0, 0); // 调用另一个构造方法
}
public MyClass(int x, int y) {
this.x = x;
this.y = y;
}
}
- 在匿名类中,this关键字可以用来引用外部类的实例。
public class OuterClass {
public void doSomething() {
// 在匿名类中使用this关键字引用外部类的实例
Runnable r = new Runnable() {
public void run() {
System.out.println("This is the outer class: " + OuterClass.this);
}
};
}
}
4.构造方法
(1)构造方法的定义
构造方法是一种特殊的方法,用于创建和初始化对象。构造方法的名称必须与类名相同,并且没有返回类型。构造方法在使用new关键字创建对象时自动调用,可以用来初始化对象的实例变量。
构造方法可以有参数,也可以没有参数。如果没有显式定义构造方法,则会有一个默认的无参构造方法。如果显式定义了构造方法,那么默认的无参构造方法将不再提供。
(2)构造方法的作用
-
初始化实例变量:构造方法可以在创建对象时对实例变量进行初始化,确保对象在被使用之前具有合适的状态。
-
执行必要的设置操作:构造方法可以执行一些必要的设置操作,例如连接数据库、初始化资源、注册监听器等。
-
提供对象的初始化选项:通过构造方法的重载,可以为对象提供不同的初始化选项,以适应不同的需求。
-
在子类中调用父类的构造方法:子类的构造方法可以调用父类的构造方法,从而确保父类的实例变量得到正确的初始化。
(3)构造方法的特殊性
-
方法名与类名相同:构造方法的方法名必须与类名完全相同,包括大小写。这是Java用于识别构造方法的规则之一。
-
没有返回类型:构造方法没有返回类型,包括void。在定义构造方法时不需要指定返回类型,因为构造方法的主要目的是初始化对象,而不是返回值。
-
在创建对象时自动调用:构造方法在使用new关键字创建对象时自动调用。它负责初始化对象的实例变量,确保对象在被使用之前处于合适的状态。
-
可以重载:与普通方法一样,构造方法也可以进行重载。这意味着可以定义多个构造方法,它们具有不同的参数列表,从而为对象提供不同的初始化选项。
-
可以调用父类的构造方法:在子类的构造方法中,可以使用super关键字来调用父类的构造方法,以确保父类的实例变量得到正确的初始化。
二、类的继承
在Java中,类的继承是一种面向对象编程的特性,它允许一个类(子类)继承另一个类(父类)的属性和方法。通过类的继承,子类可以获得父类的属性和方法,并且可以在此基础上进行扩展或重写。
在Java中,使用关键字
extends
来实现类的继承。子类可以继承父类的非私有属性和方法,但不会继承父类的构造方法、私有属性和方法。子类可以通过继承来重用父类的代码,并且可以通过额外添加新的属性和方法来扩展父类的功能。// 父类 class Vehicle { String brand; String color; void start() { System.out.println("启动车辆"); } } // 子类 class Car extends Vehicle { int numOfSeats; void drive() { System.out.println("驾驶汽车"); } } public class Main { public static void main(String[] args) { Car myCar = new Car(); myCar.brand = "BMW"; myCar.color = "Red"; myCar.numOfSeats = 4; myCar.start(); // 继承父类的方法 myCar.drive(); // 子类新增的方法 } }
Vehicle
类是父类,Car
类是子类。Car
类继承了Vehicle
类的属性brand
和color
,并且新增了自己的属性numOfSeats
和方法drive
。通过继承,
Car
对象可以调用Vehicle
类的方法start
,同时还可以调用自己新增的方法drive
。这样就实现了对父类代码的重用,并且在此基础上进行了扩展。
三、抽象类与接口
1.抽象类
抽象类不能被实例化,只能被继承。抽象类用于定义一些共有的属性和方法,但不具体实现这些方法,而是由它的子类来实现。抽象类可以包含抽象方法和普通方法。抽象方法是没有方法体的方法,必须在子类中实现。通过使用抽象类,我们可以达到代码复用和统一规范的目的。
2.接口
接口是一组方法的集合,没有方法体,只有方法的声明。接口中的方法默认是公共的,无需使用关键字进行修饰。接口可以被类实现,一个类可以实现多个接口。通过实现接口,类可以获得接口中定义的方法,从而实现多态性。接口的使用可以提高代码的扩展性和灵活性,可以实现类之间的松耦合。
2.区别
1. 抽象类可以有构造方法,而接口不能有构造方法。
2. 类只能继承一个抽象类,但可以实现多个接口。
3. 抽象类可以有非抽象方法,而接口中的方法都是抽象方法。
4. 抽象类可以有属性,而接口不能有属性。
5. 抽象类的目的是为了实现代码复用,而接口的目的是为了实现多态性。
6. 抽象类的使用范围较窄,而接口可以广泛应用于类的设计。
四、类的多态
1.类的多态是什么
类的多态指的是一个对象可以在不同的上下文中具有不同的形态。多态通过继承和方法重写实现。
2.类的多态核心概念
多态的核心概念是父类的引用可以指向子类的对象,通过父类的引用调用子类的方法。这样可以达到动态绑定的效果,即在运行时根据对象的实际类型确定将要执行的方法。
3.类的多态作用
多态可以提高代码的可复用性和灵活性。通过多态,可以编写通用的父类代码,然后通过使用不同的子类实现具体的功能。这样就可以减少代码的冗余,并且在需要扩展功能的时候,只需要添加新的子类,而不需要修改父类的代码。
多态还可以实现代码的解耦合。不同的子类可以有不同的实现方式,但是它们都继承自同一个父类,这样可以在不影响其他代码的情况下,灵活地替换不同的子类实现。
4.类的多态的前提
类的多态的前提是有继承关系,即存在父类和子类。子类继承了父类的属性和方法,可以对父类的方法进行重写或者添加新的方法。通过父类的引用变量指向子类的实例对象,可以实现多态。
5.类的多态例子
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
public void sound() {
System.out.println("狗发出汪汪声");
}
}
class Cat extends Animal {
public void sound() {
System.out.println("猫发出喵喵声");
}
}
public class PolymorphismExample {
public static void main(String[] args) {
Animal animal1 = new Dog(); // 向上转型
Animal animal2 = new Cat(); // 向上转型
animal1.sound(); // output: 狗发出汪汪声
animal2.sound(); // output: 猫发出喵喵声
}
}
Animal是一个基类,而Dog和Cat是继承自Animal的子类。在main方法中,我们创建了一个Animal类型的引用变量animal1,并将其指向一个Dog对象,以及一个Animal类型的引用变量animal2,并将其指向一个Cat对象。这就是多态的体现,通过向上转型,我们可以将子类对象赋给父类引用变量。
调用animal1.sound()时,由于animal1是Dog类型的引用变量,所以会调用Dog类中的sound方法,打印出"狗发出汪汪声"。同理,当我们调用animal2.sound()时,由于animal2是Cat类型的引用变量,所以会调用Cat类中的sound方法,打印出"猫发出喵喵声"。
五、类的高级特性
1.Static修饰符
使用static修饰符可以使得成员和代码块与类本身关联,而不是与类的实例关联。下面是static修饰符的一些高级特性:
-
静态字段:被声明为static的字段属于类本身,而不是类的实例。它们在内存中只有一份副本,被所有实例共享。可以通过类名直接访问静态字段,无需创建类的实例。
-
静态方法:同样地,被声明为static的方法也属于类本身,而不是类的实例。它们可以通过类名直接调用,无需创建类的实例。静态方法一般用于实现通用的功能或工具方法,无需访问实例的状态。
-
静态代码块:静态代码块是在类被加载时执行的一段代码。它们可以用来初始化静态字段或执行其他必要的静态操作。静态代码块只会执行一次,即使类被实例化多次。
-
静态内部类:内部类可以被声明为static,在这种情况下,它们不持有对外部类实例的引用。可以直接通过外部类的类名访问静态内部类,并创建其实例。
-
静态导入:通过使用static import语句,可以直接导入类的静态成员,无需以类名限定。
2.Final修饰符
final
用于限制变量、方法和类的修改或继承。
-
当
final
修饰一个变量时,该变量被称为常量或者称为不可变变量。一旦给常量赋了初值后,就不能再对它进行修改。常量通常用大写字母表示,并且在声明时必须被初始化。例如:final int MAX_VALUE = 100;
尝试修改常量的值会导致编译错误。
-
当
final
修饰一个方法时,该方法被称为不可重写方法。即子类不能重写这个方法。通常情况下,final
方法是父类中的某个行为的最终实现,子类不能进行修改。例如:public class Parent { public final void doSomething() { // method implementation } } public class Child extends Parent { // compile error: Cannot override the final method from Parent public void doSomething() { // method implementation } }
-
当
final
修饰一个类时,该类被称为不可继承类。即不能有其他类继承该类。通常情况下,final
类是一个已经完整实现的类,不希望被其他类继承或修改。例如public final class FinalClass { // class implementation } public class ChildClass extends FinalClass { // compile error: Cannot inherit from final FinalClass }
使用final
修饰符可以提高代码的安全性和性能。常量的值不能被修改,确保了它们的一致性。不可重写方法和不可继承类可以防止子类对原始实现进行不必要的修改,提高了代码的可维护性和拓展性。
3.代码块
(1)普通代码块
普通代码块(也称为局部代码块)是在方法或语句中使用的一种代码块。普通代码块没有特殊的语法结构,可以在任何需要执行一组语句的地方使用。它的作用是在该代码块中定义的局部变量在代码块内部有效,代码块执行完毕后,局部变量就会被销毁。
示例:
public class Main {
public static void main(String[] args) {
{
int x = 10;
System.out.println("x = " + x);
}
// 在这里访问 x 将会报错,因为 x 的作用域只限于上面的代码块内部
// System.out.println("x = " + x);
}
}
(2)构造代码块
构造代码块是在类中定义的一种代码块。它在实例对象创建时被调用,并且在构造方法执行前执行。构造代码块用于在实例对象创建时对成员变量进行初始化,它不能被显示调用。
示例:
public class Main {
// 构造代码块
{
System.out.println("构造代码块");
}
// 构造方法
public Main() {
System.out.println("构造方法");
}
public static void main(String[] args) {
Main main = new Main();
}
}
(3)静态代码块
静态代码块是在类中定义的一种代码块。它在类被加载时执行,并且只执行一次。静态代码块用于在类被加载时进行静态成员的初始化,它不能被显示调用。
示例:
public class Main {
// 静态代码块
static {
System.out.println("静态代码块");
}
public static void main(String[] args) {
System.out.println("main 方法");
}
}
输出结果为:
静态代码块
main 方法
4.匿名对象
(1)概念
顾名思义,匿名对象指的就是没有名字的对象,在使用中理解为实例化一个类对象,但是并不把它赋给一个对应的类变量,而是直接使用。在理解匿名对象前,我们先创建一个类便于后面的使用。
class Student{
String name;
public void showInfo(){
System.out.println("Name is: " + this.name);
}
}
(2)匿名对象特征
-
语法上:只创建对象,但不用变量来接收,例如:假设现在Student类如上所示,通常情况我们在使用这个类时需要先实例化类对象,然后调用类的属性和方法
Student s = new Student(); s.name = "Forlogen"; s.showInfo(); // Forlogen
而匿名类并不使用变量接收直接使用
System.out.println(new Student().name)
(3)匿名对象的使用
-
匿名对象本质上仍然是一个对象,所以它具备对象的所有功能
-
每次创建一个匿名对象都是一起全新的对象,即每次创建的匿名对象都是不同的对象,它们在堆内存中具有不同的地址。而且一个匿名对象只能使用一次。这怎么理解呢?同样假设现在想使用Student这个类,每次实例化匿名对象得到的都是新的对象:
// 第一个匿名对象 public class AnonymousTest { public static void main(String[] args) { System.out.println(new Student().name = "Kobe"); // Kobe System.out.println(new Student().name); // null }
从输出中可以看出,前后两个实例化的匿名对象是不同的,第一个我们为其进行赋值,打印出的就是赋值的结果;而第二个并没有对name属性就行赋值,那么返回的就是String类型的默认值null
-
只想调用匿名对象的方法,例如在使用Scanner类获取键盘输入时,我们常这样做:
Scanner sc = new Scanner(System.in); // 10 int num = sc.nextInt(); System.out.println(num); //10
而如果只想输出从键盘获取的值,就可以使用匿名对象
System.out.println(new Scanner(System.in).nextInt());
-
直接使用匿名对象作为方法的参数和返回值
// 将匿名对象作为返回值 public class AnonymousTest { public static void main(String[] args) { int n = getScanner().nextInt(); System.out.println(n); } public static Scanner getScanner(){ return new Scanner(System.in); } }
// 将匿名对象作为函数参数 public class AnonymousTest { public static void main(String[] args) { Scanner sc1 = new Scanner(System.in); getScanner(sc1); } public static void getScanner(Scanner sc){ System.out.println(sc.nextInt()); } }
(3)缺点
一个匿名对象只能使用一次,造成内存空间的浪费。
(4)优点
由于匿名对象不需要在实例化后赋给变量,因此当大量需要使用匿名对象时,可以节省保存变量所需的栈空间
JVM的垃圾回收会根据对象是否可达来判断是否为垃圾,如果不可达,则在下一次的垃圾回收中进行回收。而匿名对象创建后只能使用一次,当使用结束后它就是垃圾了,这样便于JVM快速回收,节省堆内存空间。
5.内部类基础
(1)成员内部类
成员内部类看起来像是外部类的一个成员,所以内部类可以拥有private、public等访问权限修饰;当然,也可以用static来修饰。成员内部类分为:
- 静态成员内部类:使用static修饰类;
- 非静态成员内部类:未用static修饰类,在没有说明是静态成员内部类时,默认成员内部类指的就是非静态成员内部类;
只有成员内部类才能加static变成静态内部类。
(2)静态嵌套类
静态嵌套类是指在一个类内部定义的另一个静态类。静态嵌套类与非静态嵌套类的区别在于,静态嵌套类不依赖于外部类的实例而存在,可以直接通过外部类的类名来访问。
静态嵌套类通常用于将相关的类组织在一起,使代码更加清晰和易于维护。它们可以被用来实现一些辅助功能,或者作为外部类的一部分来提供更好的封装性。
静态嵌套类的定义方式如下:
public class OuterClass {
// 外部类的成员和方法
static class NestedStaticClass {
// 静态嵌套类的成员和方法
}
}
在外部类中,可以通过类名直接访问静态嵌套类:
OuterClass.NestedStaticClass nested = new OuterClass.NestedStaticClass();
(3)方法内部类
方法内部类是指在一个方法内部定义的类。这种类的作用域被限制在定义它的方法内部,外部的代码无法直接访问这个类。
方法内部类通常用于解决某个特定方法的问题,或者在方法内部实现一些辅助功能。它可以访问方法的参数和局部变量,但是必须是final的,因为方法内部类的对象可能会在方法外部被访问,而final变量在方法执行完毕后仍然存在。
方法内部类的定义方式如下:
public class OuterClass {
public void someMethod() {
class MethodInnerClass {
// 方法内部类的成员和方法
}
}
}
在方法内部,可以直接实例化和访问方法内部类:
public void someMethod() {
MethodInnerClass inner = new MethodInnerClass();
}
方法内部类只能在定义它的方法内部使用,外部的代码无法直接访问它。因此,方法内部类的作用域是有限的,它主要用于解决一些局部性的问题,提高代码的可读性和封装性。
(4)匿名内部类
匿名内部类是一种没有名字的内部类,通常用于创建只需使用一次的类实例。匿名内部类通常用于创建接口或抽象类的实例,同时可以在创建实例的同时实现其抽象方法或接口方法。
匿名内部类的语法形式通常如下所示:
SomeInterface obj = new SomeInterface() {
// 实现接口方法或抽象类的方法
};
或者在创建继承类的实例时:
SomeClass obj = new SomeClass() {
// 实现父类的方法
};
在这种情况下,{}中的代码块就是匿名内部类的实现部分。
例如,如果有一个接口SomeInterface:
public interface SomeInterface {
void doSomething();
}
可以通过匿名内部类来创建实例并实现接口方法:
SomeInterface obj = new SomeInterface() {
public void doSomething() {
System.out.println("Doing something...");
}
};
匿名内部类通常在创建实例的同时实现接口或抽象类的方法,这样可以避免创建一个新的具名类。它通常用于临时需要实现某个接口或抽象类的情况,使代码更加简洁和直观。
(5)内部类的总结
-
成员内部类:
- 成员内部类是定义在一个类内部的类,它与外部类实例相关联。
- 成员内部类可以访问外部类的所有成员,包括私有成员。
- 实例化成员内部类需要先实例化外部类,然后通过外部类实例来创建内部类实例。
-
静态嵌套类:
- 静态嵌套类是定义在一个类内部的静态类,它与外部类实例无关。
- 静态嵌套类可以直接通过外部类的类名来访问,不需要先实例化外部类。
- 静态嵌套类通常用于组织相关的类,提高代码的清晰度和可维护性。
-
方法内部类:
- 方法内部类是定义在一个方法内部的类,它的作用域被限制在定义它的方法内部。
- 方法内部类可以访问方法的参数和局部变量,但这些变量必须是final的或实际上是final的。
- 方法内部类通常用于解决特定方法的问题,提高代码的封装性。
-
匿名内部类:
- 匿名内部类是一种没有名字的内部类,通常用于创建只需使用一次的类实例。
- 匿名内部类通常用于创建接口或抽象类的实例,并在创建实例的同时实现其抽象方法或接口方法。
- 匿名内部类可以使代码更加简洁和直观,避免创建一个新的具名类。
六、集合
集合框架的核心接口是Collection接口,它是所有集合类的基本接口,定义了集合的基本操作方法,如添加、删除、遍历等。常用的集合类包括ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等。
- ArrayList:动态数组,可以根据需要动态调整大小。
- LinkedList:双向链表,可以在任意位置插入和删除元素。
- HashSet:无序集合,不允许出现重复元素。
- TreeSet:有序集合,元素按照自然顺序排序或者自定义比较器排序。
- HashMap:无序映射,以键值对的形式存储元素,键不允许重复。
- TreeMap:有序映射,以键值对的形式存储元素,按照键的自然顺序或者自定义比较器排序。
七、异常
异常是指程序运行过程中发生的意外情况或错误的情况。当程序发生异常时,通常会抛出一个异常对象,然后程序会根据异常类型执行相应的处理逻辑。
常见的异常类包括:
-
Checked Exception,也叫受检异常:这些异常在代码中必须显式地进行处理,否则编译器会报错。常见的Checked Exception包括IOException、SQLException等。
-
Unchecked Exception,也叫非受检异常:这些异常不需要显式地进行处理,编译器不会强制要求处理。常见的Unchecked Exception包括NullPointerException、ArrayIndexOutOfBoundsException等。
-
Error:这些异常通常是由于系统错误或者资源不足导致的,一般情况下无法恢复,程序也不需要处理。常见的Error包括OutOfMemoryError、StackOverflowError等。
异常处理的流程如下:
-
抛出异常:当发生异常情况时,通过throw语句抛出一个异常对象。
-
捕获异常:使用try-catch语句块捕获异常,并在catch块中处理异常。可以使用多个catch块来捕获不同类型的异常。
-
处理异常:在catch块中对捕获到的异常进行处理,比如输出错误信息、日志记录、进行重试等。
-
抛出新异常:在catch块中也可以选择捕获到的异常不进行处理,而是抛出一个新的异常对象。