一. 变量和基本数据类型
在Java中,变量是程序存储数据的基本单位。每个变量都有一个特定的数据类型,这个数据类型定义了变量可以存储什么类型的数据,以及可以对这些数据执行什么操作。
变量的类型
-
基本数据类型(Primitive Types): 直接存储值。包括:
- 整数类型:
byte
(1字节),short
(2字节),int
(4字节),long
(8字节) - 浮点类型:
float
(4字节),double
(8字节) - 字符类型:
char
(2字节,用于存储单个字符) - 布尔类型:
boolean
(true 或 false)
- 整数类型:
-
引用数据类型(Reference Types): 存储的是对内存中对象或数组的引用(或地址)。包括:
- 类(Class)
- 接口(Interface)
- 数组(Array)
声明变量
声明变量时,必须指定变量的类型和名称。可选地,可以在声明时初始化变量
java
int age = 30; // 声明并初始化一个整型变量
double price = 19.99; // 声明并初始化一个双精度浮点变量
char grade = 'A'; // 声明并初始化一个字符变量
boolean isPassed = true; // 声明并初始化一个布尔变量
变量命名规则
- 变量名称可以包含字母、数字、下划线(_)或美元符号($)。
- 变量名称不能以数字开头。
- 变量名称应该明确且易于理解,通常使用驼峰命名法(例如:
studentName
,numberOfStudents
)
变量的作用域
变量的作用域决定了在哪个部分的代码中可以访问该变量。在Java中,变量的作用域通常由它声明的位置决定:
局部变量:在方法内部声明的变量,其作用域仅限于方法内。
局部变量是在方法内、构造器或者任何代码块(例如:for循环、if语句)内部声明的变量。
- 局部变量只能在声明它们的代码块内部访问。
- 当代码块执行完毕,局部变量的生命周期就结束了,它们所占用的内存将被回收。
java
void myMethod() {
int localVar = 100; // localVar的作用域仅限于myMethod方法内
System.out.println(localVar);
}
成员变量(或字段):在类中但在方法之外声明的变量,其作用域是整个类。
成员变量是在类中但在方法之外声明的变量。它们也被称为字段或属性。
- 成员变量可以被类中所有的方法、构造器和特定的代码块访问。
- 它们的生命周期与对象的生命周期相同。当一个对象被创建时,成员变量就被初始化;当对象被垃圾回收时,成员变量的生命周期也就结束了。
java
public class MyClass {
int memberVar = 99; // memberVar的作用域是整个类
void myMethod() {
System.out.println(memberVar); // 可以访问成员变量
}
}
类变量 :使用static
关键字声明的变量,其作用域是静态的,属于类本身而不是类的实例。
类变量是使用static
关键字声明的变量。它们属于类,而不是类的任何特定实例。
- 类变量可以在没有创建类的实例的情况下访问。
- 类变量的生命周期从类被加载开始,直到程序结束或类被卸载。
java
public class MyClass {
static int classVar = 10; // classVar的作用域是整个类,并且是静态的
void myMethod() {
System.out.println(classVar); // 可以访问类变量
}
static void myStaticMethod() {
System.out.println(classVar); // 静态方法也可以访问类变量
}
}
二. 控制流语句
Java提供了多种控制流语句来根据不同的条件执行不同的代码块。
控制流语句在编程中用于根据条件(如比较结果)或强制重复执行某段代码,直到满足特定条件为止。Java中的控制流语句可以分为几个主要类型:决策制作(如if
、switch
)、循环(如for
、while
、do-while
)和分支(如break
、continue
、return
)。
1. 决策制作
a. If-Else 语句
if
语句是最基本的控制流语句,它允许根据条件的真假执行不同的代码块。
java
if (condition) {
// Executes this block if condition is true
} else {
// Executes this block if condition is false
}
b. Switch 语句
switch
语句提供了一种执行多个分支的方法,基于同一个变量或表达式的不同值
java
switch (expression) {
case value1:
// Executes this block if expression equals value1
break;
case value2:
// Executes this block if expression equals value2
break;
// You can have any number of case statements.
default:
// Executes this block if none of the above cases match
}
2. 循环
a. For 循环
for
循环提供了一种在给定条件为真时重复执行代码块的方式,通常用于遍历数组或集合。
java
for (initialization; condition; update) {
// Code to execute for each iteration
}
b. While 循环
while
循环在给定条件为真时重复执行代码块,但它会在执行循环体之前检查条件。
java
while (condition) {
// Code to execute as long as the condition is true
}
c. Do-While 循环
do-while
循环至少执行一次代码块,然后在每次迭代结束时检查条件。
java
do {
// Code to execute
} while (condition);
3. 分支
a. Break
break
语句用于立即退出循环或switch
语句,不再执行剩余的代码或迭代。
b. Continue
continue
语句跳过当前循环的剩余代码,并进入下一次迭代(如果条件允许)。
c. Return
return
语句用于从方法中退出,并可选地返回一个值。当执行到return
语句时,方法将结束执行并返回给调用者。
三. 方法
1.方法定义
方法定义包括访问修饰符、返回类型、方法名、参数列表(可选)和方法体。方法签名由方法名和参数列表组成。
java
accessModifier returnType methodName(parameters) {
// 方法体
// 可以包含返回语句
}
- 访问修饰符 :确定其他类是否可以访问该方法。常用的有
public
、private
、protected
和默认(不指定)。 - 返回类型 :方法可能返回的数据类型。如果方法不返回任何值,使用
void
。 - 方法名:遵循驼峰命名法,应清晰反映方法的功能。
- 参数列表:括号内,由逗号分隔的参数序列,每个参数都需要类型和名称。方法可以没有参数。
2. 方法调用
调用方法意味着执行方法中的代码。对于非静态方法,需要使用对象的引用来调用。对于静态方法,可以直接通过类名调用。
java
Student.printInfo();//静态方法
Student cst2209=new Student();
cst2209.printInfo();//非静态方法
3.参数
方法可以接受传入的参数,这些参数在方法执行时使用。Java支持值传递,意味着传递给方法的是参数值的拷贝。
- 形式参数:在方法定义中指定的参数。
- 实际参数:在方法调用时提供给方法的值。
4.返回类型
方法可以返回一个值。返回类型必须与方法声明中的返回类型相匹配。如果方法不返回任何值,则使用void
。
java
public int add(int a, int b) {
return a + b; // 返回两个整数的和
}
5.方法重载
方法重载是类中定义多个名称相同但参数列表不同的方法。这允许方法根据提供的参数类型或数量执行不同的任务。
java
public class MathUtils {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
四. 类
类的基本结构
一个类通常包含以下几个部分:
-
字段(Field):也称为属性,用于描述对象的状态。字段代表对象需要保存的数据。
-
方法(Method):定义对象能够执行的操作。方法通过操作字段来实现其功能。
-
构造器(Constructor):是一种特殊类型的方法,用于初始化新创建的对象。构造器的名称必须与类名相同。每个类必须有无参构造方法和全参构造方法
-
块(Block):用于初始化代码,分为静态块和非静态块。
-
嵌套类和接口:类可以包含嵌套类或接口。
定义一个类
下面是一个简单的类定义示例:
java
public class Dog {
// 字段
String breed;
int age;
String color;
// 构造器
public Dog(String breed, int age, String color) {
this.breed = breed;
this.age = age;
this.color = color;
}
public Dog(){}
// 方法
void bark() {
System.out.println("Woof!");
}
void displayInfo() {
System.out.println("Breed: " + breed + " Age: " + age + " Color: " + color);
}
}
创建和使用对象
要使用类,首先需要通过调用其构造器创建类的实例(对象)。然后,可以使用对象访问其字段和方法。
java
public class TestDog {
public static void main(String[] args) {
// 创建对象
Dog myDog = new Dog("Bulldog", 5, "White");
// 调用方法
myDog.bark();
myDog.displayInfo();
}
}
代码段
代码块(Blocks)在Java中是用于组织代码的一种结构,它们可以在类中或方法中定义,用大括号 {}
包围一系列语句。Java中的代码块主要可以分为三种类型:局部代码块、实例初始化块(非静态初始化块)和静态初始化块。让我们详细探讨每种类型。
1. 局部代码块
局部代码块是定义在方法内部的代码块,用于限制变量的生命周期,这些变量只在代码块内部有效。
java
void myMethod() {
System.out.println("Before block");
{
// 这是一个局部代码块
int x = 100;
System.out.println("Inside block, x = " + x);
}
// 这里无法访问x,因为它的作用域仅限于上面的代码块内
// System.out.println(x); // 这行会导致编译错误
}
2.实例初始化块(非静态初始化块)
实例初始化块在每次创建类的实例时执行,用于初始化实例变量。如果类中有多个实例初始化块,它们按照在类中出现的顺序执行
java
public class MyClass {
// 实例初始化块
{
System.out.println("Instance initializer block executed");
}
public MyClass() {
System.out.println("Constructor executed");
}
}
// 输出:
// Instance initializer block executed
// Constructor executed
3. 静态初始化块
静态初始化块在类首次加载到JVM时执行一次,用于初始化静态变量。如果类中有多个静态初始化块,它们按照在类中出现的顺序执行。
java
public class MyClass {
static {
System.out.println("Static initializer block executed");
}
public MyClass() {
System.out.println("Constructor executed");
}
}
// 输出:
// Static initializer block executed
// Constructor executed
作用和用途
- 局部代码块:主要用于控制变量的作用域,以及在特定的作用域内限制变量的生命周期。
- 实例初始化块:用于在构造对象时执行某些初始化操作,特别是当类有多个构造器且这些构造器需要执行一些共同的初始化代码时非常有用。
- 静态初始化块:用于初始化静态变量,或者在类加载时执行一次性的设置操作。
类的特性
-
封装:通过将数据(属性)和代码(方法)绑定到一起,并设置访问权限,保证了数据的安全性和隐藏性。
-
继承:允许新的类继承现有类的属性和方法。
-
多态:允许一个方法具有多个不同表现形式。
五. 访问限定符
在Java中,访问限定符(也称为访问修饰符)定义了类、变量、方法和构造器的访问控制级别。访问控制是面向对象编程的一个重要方面,它帮助保护数据并确保代码的安全性。Java提供了四种访问修饰符:public
、protected
、default
(没有指定修饰符)和private
。让我们详细了解每种修饰符的含义及其应用场景。
1. Public
- 含义:可以被任何其他类访问。
- 应用场景:用于那些需要从其他包中访问的类、方法和变量。例如,公共API的入口点。
2. Protected(介于private和public之间)
- 含义:可以被同一个包内的类以及其他包中该类的子类访问。
- 应用场景:当你想隐藏类的某些细节,但又允许子类访问这些属性或方法时使用。
3. Default (没有指定)
- 含义:只能被同一个包内的类访问。
- 应用场景:当你的类或成员不需要跨包访问时。这是类、变量、方法默认的访问级别,如果你不指定任何访问修饰符。
4. Private
- 含义:只能在同一个类中被访问。
- 应用场景:用于类的内部工作机制,需要隐藏的部分。这有助于封装,确保外部代码不能直接访问类的内部状态。
访问控制和继承
访问控制符对类成员的继承也有影响。例如,protected
成员可以被子类访问,即使子类不在同一个包中。但是,private
成员即使在子类中也不可访问。
java
public class MyClass {
public int publicVar = 1;
protected int protectedVar = 2;
int defaultVar = 3; // 默认访问修饰符
private int privateVar = 4;
public void method() {
// 所有变量在这里都是可访问的
}
}
class OtherClass extends MyClass {
// publicVar 和 protectedVar 在这里是可访问的
// defaultVar 只有当OtherClass与MyClass在同一个包中时可访问
// privateVar 在这里不可访问
}
六. 继承和多态
继承
继承是面向对象编程中的一个核心概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。继承提供了一种机制,通过它我们可以创建一个新类在现有类的基础上,增加新的功能,或者修改现有功能的行为。这样不仅促进了代码的重用,还使代码的管理和维护变得更加容易。
基本概念
- 父类(基类):被继承的类,它提供了子类可以直接使用或者可以通过方法重写来改变行为的属性和方法。
- 子类(派生类):继承父类的类,它可以使用父类的属性和方法,并且可以添加自己特有的属性和方法,也可以重写继承自父类的方法。
使用继承
在Java中,使用extends
关键字来表示继承。子类只能继承一个父类,因为Java不支持多重继承,但可以通过实现多个接口来达到多重继承的效果。
java
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
// 继承了Animal的eat方法
void bark() {
System.out.println("Dog is barking");
}
}
// 使用
Dog myDog = new Dog();
myDog.eat(); // 调用从Animal继承的方法
myDog.bark(); // 调用Dog类自己的方法
super
关键字
super
关键字在子类中被用来引用父类的属性和方法。特别是当需要在子类中调用父类被重写的方法时,super
非常有用。
java
class Parent {
void show() {
System.out.println("Parent's show()");
}
}
class Child extends Parent {
@Override
void show() {
super.show(); // 调用父类的show()
System.out.println("Child's show()");
}
}
构造器和继承
- 子类的构造器默认会调用父类的无参构造器。如果父类没有无参构造器,子类的构造器需要通过
super
显式调用父类的其他构造器。 - 构造器不被继承,但子类的构造过程中必须调用父类的构造器,以确保父类的属性被正确初始化。
继承的特性
- 单继承:每个类只能继承一个父类,这简化了继承结构,减少了复杂性。
- 多级继承:一个类可以继承另一个类,同时也可以被其他类继承,形成一个继承的层次结构。
- 层次继承:多个类继承单个类形成的结构。
多态
多态是面向对象编程中的一个核心概念,指的是同一个方法调用因为发送对象不同而具有不同的行为。多态性允许我们通过指向子类的父类引用来执行一个动作的多种不同形式。在Java中,多态可以通过继承(inheritance)和接口(interfaces)来实现,并主要体现在方法的重写(overriding)和重载(overloading)上。
方法重写(Override)
方法重写是多态的一种表现形式。当子类中的方法与父类中的某个方法具有相同的名称、返回类型和参数列表时,子类可以提供自己的实现。这允许使用父类引用调用子类的方法。
- 动态绑定:在运行时,JVM决定要调用的方法版本,基于实际对象的类型,而不是引用变量的类型。
java
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
void sound(){
System.out.println("Cat meows");
}
}
Animal myAnimal = new Dog();
myAnimal.sound(); // 输出: Dog barks
myAnimal = new Cat();
myAnimal.sound(); // 输出: Cat meows
方法重载(Overload)
方法重载发生在同一个类中,当两个或两个以上的方法具有相同的名称但参数列表不同(参数数量或类型不同)时。这是编译时多态性的一个例子,因为编译器知道要调用哪个方法。
java
class Display {
void show(int x) {
System.out.println("Show int: " + x);
}
void show(String s) {
System.out.println("Show String: " + s);
}
}
Display display = new Display();
display.show(10); // 输出: Show int: 10
display.show("Hello"); // 输出: Show String: Hello
多态的优势
- 提高代码的可维护性:可以编写更通用的代码,而不是针对特定类型的代码。
- 增加代码的可扩展性:通过简单地添加新类和方法来扩展应用程序,而不必修改现有的代码。
- 接口和抽象类:多态允许我们使用抽象类和接口作为引用类型,提供了一种强大的方式来实现松耦合的设计。
多态的条件
- 必须有继承或实现接口。
- 必须有方法重写。
- 必须有父类引用指向子类对象。
七. 接口
在Java中,接口(Interface)是一种引用类型,它是完全抽象的,即它不能有任何的实现代码,直到Java 8之前。从Java 8开始,接口可以包含默认方法和静态方法,它们有具体的实现。接口是定义一组方法规范的方式,类通过实现接口来遵循这些规范。接口是实现多态和实现解耦的关键工具之一。
接口的基本特性
- 抽象方法 :在Java 8之前,接口只能包含抽象方法,这些方法默认是
public abstract
类型的,且不包含实现体。 - 默认方法:Java 8引入了默认方法,允许在接口中包含具有实现体的方法。这使得开发者能够向接口添加新方法,而不破坏实现这些接口的现有类。
- 静态方法:Java 8也允许在接口中定义静态方法。
- 常量 :接口可以包含常量,这些常量默认是
public static final
类型的。
定义接口
接口使用interface
关键字来定义。一个类通过implements
关键字来实现接口。
java
interface Animal {
void eat();
void travel();
}
class Dog implements Animal {
public void eat() {
System.out.println("Dog eats");
}
public void travel() {
System.out.println("Dog travels");
}
}
默认方法
接口的默认方法使用default
关键字修饰,提供了方法的默认实现。
java
interface Animal {
void eat();
default void sleep() {
System.out.println("Sleeping");
}
}
静态方法
接口中的静态方法与类中的静态方法类似,可以直接通过接口名调用。
java
interface Animal {
static void breathe() {
System.out.println("Breath air");
}
}
实现一个接口时,类必须实现接口中所有的抽象方法,这是确保类满足接口规范的要求。抽象方法是那些只有方法声明而没有方法体的方法。从Java 8开始,接口可以包含默认方法和静态方法,这两种方法都有自己的实现体,因此实现接口的类不需要实现这些方法。
示例
考虑以下接口,其中包含一个抽象方法、一个默认方法和一个静态方法:
java
interface MyInterface {
void abstractMethod(); // 抽象方法
default void defaultMethod() { // 默认方法
System.out.println("This is a default method.");
}
static void staticMethod() { // 静态方法
System.out.println("This is a static method.");
}
}
实现这个接口的类只需要提供abstractMethod
方法的实现,因为defaultMethod
已经有了实现,而staticMethod
属于接口自身:
java
interface MyInterface {
void abstractMethod(); // 抽象方法
default void defaultMethod() { // 默认方法
System.out.println("This is a default method.");
}
static void staticMethod() { // 静态方法
System.out.println("This is a static method.");
}
}
class MyClass implements MyInterface {
public void abstractMethod() {
System.out.println("This is the implementation of the abstract method.");
}
}
public class TestInterface {
public static void main(String[] args) {
// 调用接口的静态方法
MyInterface.staticMethod();
// 创建接口的实现类的实例
MyClass myClass = new MyClass();
// 调用抽象方法的实现
myClass.abstractMethod();
// 调用默认方法
myClass.defaultMethod();
}
}
八. 接口适配器
接口适配器(有时也称为适配器模式)是一种设计模式,用于解决接口和类之间不兼容问题,使它们能够一起工作。特别是在Java中,这个概念通常以接口的默认实现形式出现,特别适用于有许多方法但实现类只需要实现其中几个方法的接口。
接口适配器的基本思想
考虑一个情况,你有一个接口包含多个方法,而你只想实现这个接口的几个方法。直接实现这个接口会强迫你为所有方法提供实现,即使很多方法你都不需要。为了避免这种情况,可以使用一个抽象类作为适配器,这个抽象类实现了接口中的所有方法,但不做任何事(或只提供默认行为)。然后,你可以只选择性地覆盖你感兴趣的方法。
示例
假设有一个接口,它定义了多个方法:
java
interface MyInterface {
void method1();
void method2();
void method3();
void method4();
}
直接实现这个接口可能不太实用,因为你可能只对其中一两个方法感兴趣。这时,你可以创建一个抽象类来实现这个接口,并为所有方法提供空实现:
java
abstract class InterfaceAdapter implements MyInterface {
public void method1() {}
public void method2() {}
public void method3() {}
public void method4() {}
}
现在,当你需要实现MyInterface
接口时,你可以继承InterfaceAdapter
类,只重写你感兴趣的方法:
java
class ConcreteClass extends InterfaceAdapter {
@Override
public void method1() {
System.out.println("Method1's implementation.");
}
// 只重写了method1,其他方法保持空实现
}
但是接口适配器的写法在Java 8具有default和static接口方法之后就不是很有必要了。