前言:这一篇笔记是我跟着菜鸟教程学的,附了一点ppt,掌握这些就非常足够对付软工期末考试的java程序题了,当然也还要自己理解并多敲代码,但是多线程的那一部分还需自己再找别的资料(期末考多线程只会出现在程序阅读题,不会让自己编写) 注:其中也附上了一些我在学习时看的别的博客
java
1. 基础语法
一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。
-
对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
-
类:类是一个模板,它描述一类对象的行为和状态。(类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如MyFirstJavaClass)
-
方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。
-
实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。
小伙伴们应该都是有c基础的吧~不知道会不会在刚开始学习java的时候跟我一样有下面这联想呢?
Q:按照我的理解,java中的类与c语言中的数据类型typedef很像,java中的方法可以看作是c语言中的函数,你觉得我理解的对吗,你觉得java中的对象可以看作c语言中的什么
A:你的理解在某种程度上是正确的。让我们详细比较一下:
-
类和typedef:Java中的类可以看作是具有属性和方法的自定义数据结构,类似于C语言中使用typedef和结构体(struct)创建的自定义数据类型。然而,Java中的类更具面向对象特征,例如封装、继承和多态,而C语言是过程式编程语言,所以Java类的功能要比C语言中通过typedef定义的数据类型更丰富。
-
方法和函数:Java中的方法确实类似于C语言中的函数。它们都用于定义一段可重复使用的代码块,可以接受输入参数并返回结果。然而,Java中的方法通常与类关联,属于面向对象的范畴,而C语言中的函数独立存在,是一种过程式编程方式。此外,Java方法可以声明为实例方法、静态方法等。
关于Java对象与C语言中的类比,Java对象可以看作是C语言结构体(struct)的实例。当你在Java中实例化一个类时,你创建了一个该类的对象。这些对象具有类定义的属性和方法。在C语言中,通过定义一个结构体,然后创建该结构体的变量,类似于创建一个对象。然而,请注意,C语言中的结构体仅限于数据,而不包含与数据相关的操作,这是C语言与Java之间最显著的区别之一。 总的来说,尽管这两者具有某些相似之处,但Java是面向对象编程语言,而C语言是过程式编程语言。因此,它们的编程范式和实现方式在许多方面都有很大差异。
-
2. 类和对象
软件对象也有状态和行为。软件对象的状态就是属性,行为通过方法体现。在软件开发中,方法操作对象内部状态的改变,对象的相互调用也是通过方法来完成
类可以看成是创建 Java 对象的模板。
一个类可以包含的类型变量有:
局部变量(在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁)
成员变量(成员变量定义在类之中与方法体之外。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问)
类变量(静态变量)(类变量也声明在类中,方法体之外,但必须声明为 static 类型)
构造方法:
构造方法是一种特殊的方法,它用于初始化新创建的对象。在创建一个对象的时候,至少要调用一个构造方法。
-
构造方法的名称必须与类的名称相同。
-
构造方法没有返回类型,即使是 void 也不需要声明。
-
一个类可以有多个构造方法,这些构造方法之间通过参数列表的不同而区分(方法重载)。
方法重载:允许一个类中有多个方法使用相同的名称,但是有不同的参数列表。通过提供多个方法版本,方法重载允许我们处理不同类型和数量的参数,并在一个类中以一致的方式命名相似操作。
-
如果没有为类显式地定义构造方法,Java 编译器会为该类提供一个默认的无参数构造方法。如果你有显式定义任何构造方法,编译器不再提供默认构造方法。
public class Puppy{ public Puppy(){ } public Puppy(String name){ // 这个构造器仅有一个参数:name } }
//具有显式定义构造方法的类。
public class Employee { private String name; private int age;//成员变量 // 显式定义无参数构造方法 public Employee() { this.name = ""; this.age = 0; } // 显式定义有参数构造方法 public Employee(String name, int age) { this.name = name; this.age = age; } }
在例二中,我们为 Employee 类提供了两个构造方法。一个是无参数的构造方法,另一个是有参数的构造方法。当我们创建 Employee 类的对象时,可以选择调用哪个构造方法: Employee e1 = new Employee(); // 调用无参数构造方法 Employee e2 = new Employee("张三", 25); // 调用有参数构造方法
创建对象:
对象是根据类创建的。在Java中,使用关键字 new 来创建一个新的对象,需三步:
-
声明:声明一个对象,包括对象名称和对象类型
-
实例化:使用关键字 new 来创建一个对象
当使用"new"关键字创建一个对象时,会在堆内存中为该对象分配内存空间,并返回对该对象的引用。
-
初始化:对象的初始化则是为对象的成员变量赋初值
访问实例变量和方法:
通过已创建的对象来访问成员变量和调用成员方法
/* 创建对象 */ Object referenceVariable = new Constructor(); /* 访问类中的变量 */ referenceVariable.variableName; /* 访问类中的方法 */ referenceVariable.methodName(); public class Puppy{ //一个名为puppy的public类 int puppyAge;//成员变量 public Puppy(String name){//构造函数 System.out.println("小狗的名字是 : " + name ); } public void setAge( int age ){ puppyAge = age; } ///定义了一个名为setAge的方法,它有一个整型参数age。这个方法用于设置实例的puppyAge变量值。 public int getAge( ){ System.out.println("小狗的年龄为 : " + puppyAge ); return puppyAge; } ///定义了一个名为getAge的方法,它没有参数。这个方法用于输出并返回实例的puppyAge变量值。 /// 在main方法中,创建名为myPuppy的Puppy类实例并将名称传递给构造函数:Puppy myPuppy = new Puppy("tommy"); public static void main(String[] args){ /* 创建对象 */ Puppy myPuppy = new Puppy( "tommy" ); /* 使用setAge方法设置myPuppy实例的年龄 */ myPuppy.setAge( 2 ); /* 使用getAge方法获取并打印myPuppy实例的年龄 */ myPuppy.getAge( ); /*直接访问puppyAge成员变量并输出它的值 */ System.out.println("变量值 : " + myPuppy.puppyAge ); } } 小狗的名字是 : tommy 小狗的年龄为 : 2 变量值 : 2
源文件声明规则:
-
一个源文件中只能有一个 public 类 ,可有多个非publilc类
-
源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java
-
如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。
-
如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。
-
import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明
package -> import -> class
内部类
Java 一个类中可以嵌套另外一个类。要访问内部类,可以通过创建外部类的对象,然后创建内部类的对象来实现。
嵌套类有两种类型:
非静态内部类
一个类中嵌套着另外一个类。 它有访问外部类成员的权限, 通常被称为内部类。必须首先实例化外部类,然后创建内部类的对象来实现
class OuterClass { int x = 10; class InnerClass { int y = 5; } } 这里有一个名为 MyMainClass 的公共类,其中包含了程序的 main 方法,该方法是 Java 程序的入口点。 public class MyMainClass { public static void main(String[] args) { 在 main 方法内,我们首先创建了一个 OuterClass 类型的新对象,并将它赋给名为 myOuter 的变量 OuterClass myOuter = new OuterClass(); 接下来,我们创建了一个名为 myInner 的新 OuterClass.InnerClass 类型的对象。要创建这个对象,我们需要使用 myOuter 对象来引用 InnerClass 对象,因此可以通过 myOuter.new InnerClass() 来创建实例。 OuterClass.InnerClass myInner = myOuter.new InnerClass(); System.out.println(myInner.y + myOuter.x); } }
内部类可以使用 private 或 protected 来修饰,如果你不希望内部类被外部类访问可以使用 private 修饰符(私有的内部类)
静态内部类
静态内部类可以使用 static 关键字定义,静态内部类无法访问外部成员。静态内部类我们不需要创建外部类来访问,可以直接访问它
class OuterClass { int x = 10; class InnerClass { public int myInnerMethod() { return x; } } } public class MyMainClass { public static void main(String[] args) { OuterClass myOuter = new OuterClass(); OuterClass.InnerClass myInner = myOuter.new InnerClass(); System.out.println(myInner.myInnerMethod()); } }
2. 基本数据类型(两大)
变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间
1. 内置数据类型
Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | 'u0000' |
String (or any object) | null |
boolean | false |
2. 引用类型![](https://i-blog.csdnimg.cn/blog_migrate/b3c3aa416c044aed5975cd416010832e.png)
-
在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。
-
对象、数组都是引用数据类型。
-
所有引用类型的默认值都是null。
-
一个引用变量可以用来引用任何与之兼容的类型。
-
例子:Site site = new Site("Runoob")
java常量
常量在程序运行时是不能被修改的。在 Java 中使用 final 关键字来修饰常量,声明方式和变量类似: final double PI = 3.1415927;
byte、int、long、和short都可以用十进制、16进制以及8进制的方式来表示。
当使用字面量的时候,前缀 0 表示 8 进制,而前缀 0x 代表 16 进制 int decimal = 100; int octal = 0144; int hexa = 0x64;
自动类型转换
整型、实型(常量)、字符型数据可以混合运算。运算中,不同类型的数据先转化为同一类型,然后进行运算。
转换从低级到高级。 低 ------------------------------------> 高 byte,short,char—> int —> long—> float —> double
-
不能对boolean类型进行类型转换。
-
不能把对象类型转换成不相关类的对象。
-
在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
-
转换过程中可能导致溢出或损失精度
强制类型转换
-
条件是转换的数据类型必须是兼容的。
-
格式:(type)value type是要强制类型转换后的数据类型
隐含强制类型转换
-
整数的默认类型是 int。
-
小数默认是 double 类型浮点型,在定义 float 类型时必须在数字后面跟上 F 或者 f
3. 变量类型
所有的变量在使用前必须声明 type identifier [ = value][, identifier [= value] ...] ;
type -- 数据类型 identifier -- 是变量名 加= value就是声明的同时初始化
变量类型:
-
局部变量(Local Variables定义在方法、构造方法或语句块中的变量,作用域只限于当前方法、构造方法或语句块中。局部变量必须在使用前声明,并且不能被访问修饰符修饰。
-
成员变量(实例变量)(Instance Variables定义在类中、方法之外 的变量,作用域为整个类,可以被类中的任何方法、构造方法和语句块访问。成员变量可以被访问修饰符修饰。
-
静态变量(类变量)(Class Variables , 定义位置、作用域、访问域斗鱼成员变量相同。静态变量的值在程序运行期间只有一个副本(无论创建了多少个类的实例,这些实例共享同一个静态变量。当对静态变量进行修改时,所有使用该静态变量的地方都会受到影响)。静态变量可以被访问修饰符修饰。
静态变量是属于类的,而不是属于类的实例。当类被加载到内存中时,静态变量就会被创建并分配内存空间。不同于实例变量,静态变量不需要通过创建类的实例来访问,可以直接使用类名来访问
-
参数变量(Parameters)方法定义时声明的变量,作为调用该方法时传递给方法的值。参数变量的作用域只限于方法内部。
public class RunoobTest // 成员变量 private int instanceVar; // 静态变量(类变量) private static int staticVar; //定义类RunoobTest的method方法 //参数变量 public void method(int paramVar) { // 局部变量 int localVar = 10; instanceVar = localVar; staticVar = paramVar; System.out.println("成员变量: " + instanceVar); System.out.println("静态变量: " + staticVar); System.out.println("参数变量: " + paramVar); System.out.println("局部变量: " + localVar); } public static void main(String[] args) { RunoobTest v = new RunoobTest(); v.method(20); 调用对象 v 的 method() 方法,并传递一个整型参数 20 } }
Java 参数变量
accessModifier returnType methodName(parameterType parameterName1, parameterType parameterName2, ...) { // 方法体 }
parameterType -- 表示参数变量的类型 parameterName -- 表示参数变量的名称
在调用方法时,我们必须为参数变量传递值,这些值可以是常量、变量或表达式。
方法参数变量的值传递方式有两种:值传递和引用传递。
-
值传递在方法调用时,传递的是实际参数的值的副本。当参数变量被赋予新的值时,只会修改副本的值,不会影响原始值。Java 中的基本数据类型都采用值传递方式传递参数变量的值。
-
引用传递在方法调用时,传递的是实际参数的引用(即内存地址)。当参数变量被赋予新的值时,会修改原始值的内容。Java 中的对象类型采用引用传递方式传递参数变量的值。
Java 局部变量
局部变量是在栈上分配的。局部变量必须在使用前声明,并且不能被访问修饰符修饰,因为它们的作用域已经被限制在了声明它们的方法、代码块或构造函数中。
局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。
成员变量(实例变量)
-
当一个对象被实例化之后,每个成员变量的值就跟着确定。成员变量的值在创建对象时被分配,在对象被销毁的时候销毁。
-
成员变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息。
-
成员变量可以声明在使用前或者使用后。
-
访问修饰符可以修饰成员变量。
-
成员变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把成员变量设为私有。通过使用访问修饰符可以使成员变量对子类可见。
-
成员变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。变量的值可以在声明时指定,也可以在构造方法中指定;
-
成员变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:
ObjectReference.VariableName。 accessModifier type variableName; accessModifier --表示访问修饰符,可以是 public、protected、private 或默认访问级别(即没有显式指定访问修饰符)。 type -- 表示变量的类型。 variableName -- 表示变量的名称。
类变量(静态变量)
-
静态变量的生命周期与程序的生命周期一样长,即它们在类加载时被创建,在整个程序运行期间都存在,直到程序结束才会被销毁。因此,静态变量可以用来存储整个程序都需要使用的数据,如配置信息、全局变量等。
-
静态变量在类加载时被初始化,其初始化顺序与定义顺序有关。 如果一个静态变量依赖于另一个静态变量,那么它必须在后面定义 public class MyClass { public static int count = 0; // 其他成员变量和方法 }
静态变量是与类相关的,因此可以通过类名来访问静态变量,也可以通过实例名来访问静态变量。因为静态变量是与类相关的,不依赖于任何实例,这点与实例变量不同。
MyClass.count = 10; // 通过类名访问 MyClass obj = new MyClass();//创建对象--意为--- obj.count = 20; // 通过实例名访问
MyClass obj = new MyClass(); 表示创建一个名为“obj”的新实例(对象)并使用“MyClass”类进行实例化。
MyClass
:这是您自定义的类,表示一个特定类型的对象。实际名称可能因您的具体程序而不同。
obj
:这是一个变量名,用于引用新创建的“MyClass”对象实例。您可以选择任何您喜欢的变量名,只要它遵循Java命名规则。
new
:这是Java关键字,用于创建并分配内存给新对象。
MyClass()
:这是“MyClass”类的构造函数,用于实例化对象时初始化和分配内存。在这种情况下,可能是一个默认构造函数,没有参数。通过这段代码,您现在拥有一个名为“obj”的“MyClass”类型的新对象,您可以使用该对象访问并操作“MyClass”中定义的属性和方法。
静态变量通常用于以下场景:
-
存储全局状态或配置信息
-
计数器或统计信息
-
缓存数据或共享资源
-
工具类的常量或方法
-
单例模式中的实例变量
4. Java 修饰符
修饰符用来定义类、方法或者变量,通常放在语句的最前端。
访问控制修饰符
访问控制符来保护对类、变量、方法和构造方法的访问
default(默认,或者叫包私有)
(即默认,什么也不写): 在同一包(package)内可见,其他包中的类无法访问。使用对象:类、接口、变量、方法。
private(私有)
私有成员只能在其所在的类中访问,类外部无法访问。使用对象:变量、方法。 注意:不能修饰类(外部类)和接口 声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。 Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。
public class Logger { private String format; public String getFormat() { /一个公共方法 return this.format; } public void setFormat(String format) { ///这是一个使用了公有访问控制的函数 this.format = format; } }
public(公共)
公共成员可以在任何地方访问。使用对象:类、接口、变量、方法 如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。·由于类的继承性,类所有的公有方法和变量都能被其子类继承。
protected(受保护)
protected修饰的静态变量可以在同一个包及其子类中被访问。使用对象:变量、方法。 注意:不能修饰类(外部类),可以修饰外部类。
-
子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
-
子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
访问控制和继承
方法继承的规则:
-
父类中声明为 public 的方法在子类中也必须为 public。
-
父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
-
父类中声明为 private 的方法,不能够被子类继承。
非访问修饰符
static(静态)
用来修饰类方法和类变量。
-
静态变量 独立于对象(对象,即类的实例)的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
-
静态方法 static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量(因为非静态变量属于类的实例,而静态方法没有实例,无法访问非静态变量)。静态方法从参数列表得到数据,然后计算这些数据。
同理:静态方法是属于类的,而不是属于类的实例。静态方法可以通过类名来调用,而不需要创建类的实例。
静态方法中不能使用非静态变量。
final(终结)
用来修饰类、方法和变量
-
final类 final 类不能被继承,没有类能够继承 final 类的任何特性。
"继承类"(inheritance)是计算机编程中的一个概念,尤其在面向对象编程(Object-Oriented Programming, OOP)中。继承是一种创建新类的方法,这些新类是从现有类派生出来的。新类(通常称为子类或派生类)可以继承现有类(通常称为父类或基类)的属性和方法。这种方式使得来自父类的代码可以在子类中复用,而无须编写重复的代码。同时,子类还可以扩展或覆盖父类中的特性,以满足不同的需求。 继承提供了一种分层和模块化的方法,有助于减少代码重复,并使代码更易于维护。通过继承,程序员可以建立类之间的关系,这样可以在不修改基类代码的前提下对代码进行扩展。
继承、重写:
// makeSound()方法的继承与重写 class Animal { public void makeSound() { System.out.println("动物发出叫声"); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("狗发出汪汪叫"); } } public class OverrideDemo { public static void main(String[] args) { Animal animal = new Animal(); animal.makeSound(); // 输出 "动物发出叫声" Animal dog = new Dog(); // 父类引用指向子类对象 dog.makeSound(); // 输出 "狗发出汪汪叫" } }
-
final方法 父类中的 final 方法可以被子类继承,但是不能被子类重写(final修饰的方法不能被继承类重新定义)。声明 final 方法的主要目的是防止该方法的内容被修改。
-
final变量
被
final
修饰的实例变量必须在声明时或构造函数中进行初始化,否则会报错。final 修饰符通常和 static 修饰符一起使用来创建类常量。修饰的变量为常量,是不可修改的
public class Test{ final int value = 10; // 下面是声明常量的实例 public static final int BOXWIDTH = 6; static final String TITLE = "Manager"; public void changeValue(){ value = 12; //将输出一个错误 } }
abstract (抽象)
用来创建抽象类和抽象方法。
-
抽象类
-
抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
-
一个类不能同时被 abstract 和 final 修饰。
abstract class Caravan{ private double price; private String model; private String year; public abstract void goFast(); //抽象方法 public abstract void changeColor(); }
-
-
抽象方法
-
抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供。抽象方法的声明以分号结尾,例如:public abstract sample();。抽象方法不能被声明成 final 和 static。
-
任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。(即抽象方法必须在派生类中实现)
-
抽象类可以不包含抽象方法,也可包含非抽象方法。但有抽象方法的一定是抽象类。
-
synchronized(同步)
声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。
ransient(瞬时)
用于序列化类的成员变量,表示变量值不被序列化,避免敏感数据泄露。
序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。
序列化:序列化是一种将对象的状态信息转换为字节流的过程,这样可以将该对象存储在磁盘中或通过网络进行传输。反序列化是将这个字节流重新转换回原始对象的过程。在Java中,可以通过实现
java.io.Serializable
接口来使一个类成为可序列化的。
volatile (易失性)
用于多线程环境下的变量,表示该变量的值可能会在多个线程间共享,确保可见性和有序性。
volatile修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。一个 volatile 对象引用可能是 null。
5. 运算符
位运算符
位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节型(byte)等类型。
位运算符作用在所有的位上,并且按位运算。
设A = 0011 1100
操作符 | 描述 | 例子 |
---|---|---|
& | 如果相对应位都是1,则结果为1,否则为0 | (A&B),得到12,即0000 1100 |
| | 如果相对应位都是 0,则结果为 0,否则为 1 | (A | B)得到61,即 0011 1101 |
^ | 如果相对应位值相同,则结果为0,否则为1 | (A ^ B)得到49,即 0011 0001 |
〜 | 按位取反运算符翻转操作数的每一位,即0变成1,1变成0。 | (〜A)得到-61,即1100 0011 |
<< | 按位左移运算符。左操作数按位左移右操作数指定的位数。(去掉后两位) | A << 2得到240,即 1111 0000 |
>> | 按位右移运算符。左操作数按位右移右操作数指定的位数。(去掉前两位) | A >> 2得到15即 1111 |
>>> | 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。 | A>>>2得到15即0000 1111 |
短路逻辑运算符 &&
当使用与逻辑运算符时,在两个操作数都为true时,结果才为true,但是当得到第一个操作为false时,其结果就必定是false,这时候就不会再判断第二个操作了。(也不会进行第二个操作)
赋值运算符
操作符 | 描述 | 例子 |
---|---|---|
= | 简单的赋值运算符,将右操作数的值赋给左侧操作数 | C = A + B将把A + B得到的值赋给C |
+ = | 加和赋值操作符,它把左操作数和右操作数相加赋值给左操作数 | C + = A等价于C = C + A |
- = | 减和赋值操作符,它把左操作数和右操作数相减赋值给左操作数 | C - = A等价于C = C - A |
* = | 乘和赋值操作符,它把左操作数和右操作数相乘赋值给左操作数 | C * = A等价于C = C * A |
/ = | 除和赋值操作符,它把左操作数和右操作数相除赋值给左操作数 | C / = A,C 与 A 同类型时等价于 C = C / A |
(%)= | 取模和赋值操作符,它把左操作数和右操作数取模后赋值给左操作数 | C%= A等价于C = C%A |
<< = | 左移位赋值运算符 | C << = 2等价于C = C << 2 |
>> = | 右移位赋值运算符 | C >> = 2等价于C = C >> 2 |
&= | 按位与赋值运算符 | C&= 2等价于C = C&2 |
^ = | 按位异或赋值操作符 | C ^ = 2等价于C = C ^ 2 |
| = | 按位或赋值操作符 | C | = 2等价于C = C | 2 |
instanceof 运算符
该运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)。
( Object reference variable ) instanceof (class/interface type) String name = "James"; boolean result = name instanceof String; // 由于 name 是 String 类型,所以返回真
运算优先级
下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。
类别 | 操作符 | 关联性 |
---|---|---|
后缀 | () [] . (点操作符) | 左到右 |
一元 | expr++ expr-- | 从左到右 |
一元 | ++expr --expr + - ~ ! | 从右到左 |
乘性 | * /% | 左到右 |
加性 | + - | 左到右 |
移位 | >> >>> << | 左到右 |
关系 | > >= < <= | 左到右 |
相等 | == != | 左到右 |
按位与 | & | 左到右 |
按位异或 | ^ | 左到右 |
按位或 | | | 左到右 |
逻辑与 | && | 左到右 |
逻辑或 | | | | 左到右 |
条件 | ?: | 从右到左 |
赋值 | = + = - = * = / =%= >> = << =&= ^ = | = | 从右到左 |
逗号 | , | 左到右 |
6. 循环结构
while
do ...while
do { //代码语句 }while(布尔表达式);
for
for(初始化; 布尔表达式; 更新) { //代码语句 }
主要用于数组的增强型 for 循环
声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。 表达式:表达式是要访问的数组名,或者是返回值为数组的方法。
for(声明语句 : 表达式) { //代码句子 } ///实例 public class Test { public static void main(String[] args){ int [] numbers = {10, 20, 30, 40, 50}; 此循环用来遍历数组,每次迭代都会将数组中的一个元素赋值给变量 x,并将x与一个逗号输出到控制台 for(int x : numbers ){ System.out.print( x ); System.out.print(","); } System.out.print("\n"); String [] names ={"James", "Larry", "Tom", "Lacy"}; for( String name : names ) { System.out.print( name ); System.out.print(","); } } }
break 关键字
break 主要用在循环语句或者 switch 语句中,用来跳出整个语句块。
break 跳出最里层的循环,并且继续执行该循环下面的语句。 continue 关键字
continue 适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。
在 for 循环中,continue 语句使程序立即跳转到更新语句。
在 while 或者 do…while 循环中,程序立即跳转到布尔表达式的判断语句。
7. Number和Math类
一般地,当需要使用数字的时候,我们通常使用内置数据类型,如:byte、int、long、double 等。
实际开发过程中,我们经常会遇到需要使用对象(引用类型),而不是内置数据类型的情形。为了解决这个问题,Java 语言为每一个内置数据类型提供了对应的包装类。
所有的包装类(Integer、Long、Byte、Double、Float、Short )都是抽象类 Number 的子类。
这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang 包。
下面是一个使用 Integer 对象的实例: public class Test{ public static void main(String[] args){ Integer x = 5;//自动装箱 x = x + 10;//自动拆箱 System.out.println(x); //自动装箱 } }
当 x 被赋为整型值时,由于x是一个对象,所以编译器要对x进行装箱。然后,为了使x能进行加运算,所以要对x进行拆箱
这段描述涉及到了 Java 里的自动装箱(Autoboxing)和自动拆箱(Unboxing)的概念。
在 Java 中,有基本数据类型(如 int, char, float, double, boolean 等)和引用数据类型(如类、数组和接口等)。基本数据类型存储的是实际数值,而引用数据类型存储的是对对象的引用(类似指针概念)。
有时候我们需要使用基本数据类型作为对象。为了提供这种功能,Java 为每一个基本数据类型提供了对应的包装类,例如 Integer(对应 int 类型),Character(对应 char 类型),Boolean(对应 boolean 类型)等。这些包装类的对象可以在泛型、集合类等地方使用,这在 Java 中提供了极大的便利。
在上面的例子中,定义了一个名为 x 的 Integer 对象,并将其值设置为 5,这里发生了自动装箱。所谓装箱,表示将基本数据类型转换为对象。在例子中,整数 5 是一个 int 类型,但因为将其赋值给了一个 Integer 类型的变量 x,所以编译器会自动将 int 类型数值 5 转换为一个 Integer 类型对象。
接下来,x 被用于与整数 10 相加。由于 x 是一个 Integer 对象(引用类型),而 10 是一个 int(基本类型),因此需要将 x 这个 Integer 对象转换回 int 类型才能进行加法运算。这个转换过程叫做“拆箱”。
在实例代码中,加法操作完成后,x 的值已经更新至 15。为了输出这个值,编译器会将新的 int 值自动装箱成一个 Integer 对象,并通过 System.out.println() 方法打印出结果,输出为 "15"。
总结一下,自动装箱和拆箱实际上是 Java 编译器在编译期间为我们在基本数据类型和它们对应的包装类之间自动做的转换。这为我们在编写代码时提供了很大的便利,使代码更加简洁和易懂。
序号 | 方法与描述 |
---|---|
1 | xxxValue() 将 Number 对象转换为xxx数据类型的值并返回。 |
2 | compareTo() 将number对象与参数比较。 |
3 | equals() 判断number对象是否与参数相等。 |
4 | valueOf() 返回一个 Number 对象指定的内置数据类型 |
5 | toString() 以字符串形式返回值。 |
6 | parseInt() 将字符串解析为int类型。 |
7 | abs() 返回参数的绝对值。 |
8 | ceil() 返回大于等于( >= )给定参数的的最小整数,类型为双精度浮点型。 |
9 | floor() 返回小于等于(<=)给定参数的最大整数 。 |
10 | rint() 返回与参数最接近的整数。返回类型为double。 |
11 | round() 它表示四舍五入,算法为 Math.floor(x+0.5),即将原来的数字加上 0.5 后再向下取整,所以,Math.round(11.5) 的结果为12,Math.round(-11.5) 的结果为-11。 |
12 | min() 返回两个参数中的最小值。 |
13 | max() 返回两个参数中的最大值。 |
14 | exp() 返回自然数底数e的参数次方。 |
15 | log() 返回参数的自然数底数的对数值。 |
16 | pow() 返回第一个参数的第二个参数次方。 |
17 | sqrt() 求参数的算术平方根。 |
18 | sin() 求指定double类型参数的正弦值。 |
19 | cos() 求指定double类型参数的余弦值。 |
20 | tan() 求指定double类型参数的正切值。 |
21 | asin() 求指定double类型参数的反正弦值。 |
22 | acos() 求指定double类型参数的反余弦值。 |
23 | atan() 求指定double类型参数的反正切值。 |
24 | atan2() 将笛卡尔坐标转换为极坐标,并返回极坐标的角度值。 |
25 | toDegrees() 将参数转化为角度。 |
26 | toRadians() 将角度转换为弧度。 |
27 | random() 返回一个随机数。 |
8. Java Character 类
Character 类用于对单个字符进行操作。
Character 类在对象中包装一个基本类型 char 的值。Character类提供了一系列方法来操纵字符。你可以使用Character的构造方法创建一个Character类对象,例如:Character ch = new Character('a');
将一个char类型的参数传递给需要一个Character类型参数的方法时,那么编译器会自动地将char类型参数转换为Character对象。 这种特征称为装箱,反过来称为拆箱。
也就是说,本来需要使用类的构造方法创建类对象,但是装箱可自动将参数转换为对象。装箱就是一种特殊的类型转换,它将基本数据类型自动转换为相应的包装类(Wrapper Class)对象。相应的,拆箱(Unboxing)是将包装类对象自动转换回基本数据类型的过程。
eg:
正常情况下,如果我们想使用一个整数并将其存储在一个Integer类的对象中,我们需要使用构造方法:
int num = 42; Integer numObj = new Integer(num);
但是,自从Java 5开始,我们可以利用自动装箱特性,让编译器自动进行转换:
int num = 42; Integer numObj = num; // 自动装箱
,,再比如上面Number类中的 Integer x=5;同时,在需要的时候,编译器也会自动进行拆箱。
int anotherNum = numObj; // 自动拆箱
// 原始字符 'a' 装箱到 Character 对象 ch 中 Character ch = 'a'; // 原始字符 'x' 用 test 方法装箱 // 返回拆箱的值到 'c' char c = test('x');
序号 | 方法与描述 |
---|---|
1 | isLetter() 是否是一个字母 |
2 | isDigit() 是否是一个数字字符 |
3 | isWhitespace() 是否是一个空白字符 |
4 | isUpperCase() 是否是大写字母 |
5 | isLowerCase() 是否是小写字母 |
6 | toUpperCase() 指定字母的大写形式 |
7 | toLowerCase() 指定字母的小写形式 |
8 | toString() 返回字符的字符串形式,字符串的长度仅为1 |
9. Java String 类
在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。
创建字符串的方式同样有两种
String str = "LIZIHAN" //简单 String str = new String("LIZIHAN") //使用关键字和构造函数来创建 String 对象
区别: String 创建的字符串存储在公共池中,而 new 创建的字符串对象在堆上
字符串的存储和管理方式是为了提高性能和减少内存占用。
直接赋值: 当我们使用双引号直接赋值给一个字符串变量时,Java会先检查字符串常量池(String Pool)中是否存在相同的字符串对象。如果存在,Java会让这个字符串变量指向已经存在的字符串对象,若不存在,Java会在字符串池中创建一个新的字符串对象。这样可以避免存储相同内容的多个字符串对象,从而减少内存占用。这种方法是因为字符串对象是不可变的,所以字符串常量池中的字符串可以被多个变量共享。
通过关键字
new
创建: 当我们使用new
关键字创建一个字符串对象时,Java会在堆内存中创建一个新的字符串对象,无论字符串常量池中是否存在相同的字符串对象。这样创建的字符串对象在heap内存中,且不会被其他变量共享。这种方法适用于需要创建一个新的不可变字符串对象的场景,但这样会占用更多的内存。省流:将字符串对象存储在公共池(字符串常量池)中的主要原因是节省内存并提高性能,因为在这种情况下,具有相同内容的字符串变量会共享相同的字符串对象。而将
new
创建的字符串对象存储在堆上是为了满足某些需要创建独立字符串对象的场景。选择创建字符串对象的方式要根据实际应用场景和需求来决定。
String 类是不可改变的解析: String s = "Google"; System.out.println("s = " + s); s = "Runoob"; System.out.println("s = " + s);
如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 字符串长度
用于获取有关对象的信息的方法称为访问器方法。
String 类的一个访问器方法是 length() 方法,它返回字符串对象包含的字符数 连接字符串
方法1: "我的名字是 ".concat("Runoob"); 方法2: "Hello," + " runoob" + "!"
创建格式化字符串
我们知道输出格式化数字可以使用 printf() 和 format() 方法。
String 类使用静态方法 format() 返回一个String 对象而不是 PrintStream 对象。
String 类的静态方法 format() 能用来创建可复用的格式化字符串,而不仅仅是用于一次打印输出。 System.out.printf("浮点型变量的值为 " + "%f, 整型变量的值为 " + " %d, 字符串变量的值为 " + "is %s", floatVar, intVar, stringVar); String fs; fs = String.format("浮点型变量的值为 " + "%f, 整型变量的值为 " + " %d, 字符串变量的值为 " + " %s", floatVar, intVar, stringVar);
10.Java StringBuffer 和 StringBuilder 类
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
-
如果要操作少量的数据用 String ;
-
单线程操作大量数据用StringBuilder (它的方法不是线程安全的,单数对于buffer有速度优势);
-
多线程操作大量数据,用StringBuffer。
StringBuffer 类支持的主要方法:
序号 | 方法描述 |
---|---|
1 | public StringBuffer append(String s) 将指定的字符串追加到此字符序列。 |
2 | public StringBuffer reverse() 将此字符序列用其反转形式取代。 |
3 | public delete(int start, int end) 移除此序列的子字符串中的字符。 |
4 | public insert(int offset, int i) 将 int 参数的字符串表示形式插入此序列中。 |
5 | insert(int offset, String str) 将 str 参数的字符串插入此序列中。 |
6 | replace(int start, int end, String str) 使用给定 String 中的字符替换此序列的子字符串中的字符。 |
StringBuffer 类的其他常用方法:
序号 | 方法描述 |
---|---|
1 | int capacity() 返回当前容量。 |
2 | char charAt(int index) 返回此序列中指定索引处的 char 值。 |
3 | void ensureCapacity(int minimumCapacity) 确保容量至少等于指定的最小值。 |
4 | void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 将字符从此序列复制到目标字符数组 dst 。 |
5 | int indexOf(String str) 返回第一次出现的指定子字符串在该字符串中的索引。 |
6 | int indexOf(String str, int fromIndex) 从指定的索引处开始,返回第一次出现的指定子字符串在该字符串中的索引。 |
7 | int lastIndexOf(String str) 返回最右边出现的指定子字符串在此字符串中的索引。 |
8 | int lastIndexOf(String str, int fromIndex) 返回 String 对象中子字符串最后出现的位置。 |
9 | int length() 返回长度(字符数)。 |
10 | void setCharAt(int index, char ch) 将给定索引处的字符设置为 ch 。 |
11 | void setLength(int newLength) 设置字符序列的长度。 |
12 | CharSequence subSequence(int start, int end) 返回一个新的字符序列,该字符序列是此序列的子序列。 |
13 | String substring(int start) 返回一个新的 String ,它包含此字符序列当前所包含的字符子序列。 |
14 | String substring(int start, int end) 返回一个新的 String ,它包含此序列当前所包含的字符子序列。 |
15 | String toString() 返回此序列中数据的字符串表示形式。 |
-
StringBuffer 类:StringBuffer
-
StringBuilder 类:StringBuilder
11.Java 数组
Java 语言中提供的数组是用来存储固定大小的同类型元素。
你可以声明一个数组变量,如 numbers[100] 来代替直接声明 100 个独立变量 number0,number1,....,number99。 声明数组变量
首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法: dataType[] arrayRefVar; // 首选的方法 dataType arrayRefVar[]; // 效果相同,但不是首选方法 eg: double[] myList; double myList[]; 创建数组
arrayRefVar = new dataType[arraySize]; ///Java语言使用new操作符来创建数 eg: myList = new double[10] ///数组变量的声明,和创建数组可以用一条语句完成 dataType[] arrayRefVar = new dataType[arraySize]; //或者 dataType[] arrayRefVar = {value0, value1, ..., valuek};
数组作为函数的参数
//一个打印 int 数组中元素的方法: public static void printArray(int[] array) { for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } } ///调用方法 printArray(new int[]{3, 1, 2, 6, 4, 2}); //或用增强数组s String [] names={LZH,LHN,YYH,WZY} for(String name : names){ System.out.print(name); }
草突然顿悟,原来方法就是函数啊
多维数组
String[][] str = new String[3][4]; dounle[][] myList = new double[2][9]
多维数组的动态初始化(以二维数组为例)
-
直接为每一维分配空间,如上(行列数必须为正整数)
-
从最高维开始,分别为每一维分配空间 //s[0]=new String[2] 和 s[1]=new String[3] 是为最高维分配引用空间,也就是为最高维限制其能保存数据的最长的长度,然后再为其每个数组元素单独分配空间 s0=new String("Good") 等操作。 String s = new String2; s[0] = new String[2]; s[1] = new String[3]; s0 = new String("Good"); s0 = new String("Luck"); s1 = new String("to"); s1 = new String("you"); s1 = new String("!");
13.Arrays 类
java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
具有以下功能:
-
给数组赋值:通过 fill 方法。 public static void fill(int[] a, int val) 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素
-
对数组排序:通过 sort 方法,按升序。 public static void sort(Object[] a) 对指定对象数组根据其元素的自然顺序进行升序排列。
-
比较数组:通过 equals 方法比较数组中元素值是否相等。 public static boolean equals(long[] a, long[] a2) 如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。
-
查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。 public static int binarySearch(Object[] a, Object key) 用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
14. Java 日期时间
java.util 包提供了 Date 类来封装当前的日期和时间。 Date 类提供两个构造函数来实例化 Date 对象。
第一个构造函数使用当前日期和时间来初始化对象。Date( )
第二个构造函数接收一个参数,该参数是从 1970 年 1 月 1 日起的毫秒数。
Date(long millisec)
Date对象有各种方法,可以比较调用方法的date对象是否在指定日期之前或之后(boolean after(Date date)、boolean before(Date date));可以比较调用方法的date对象和指定日期是否相等,可返回布尔或整数,其实也代表布尔啦(int compareTo(Date date)、boolean equals(Object date));可以获取当前的日期并打印字符串形势(String toString( ))......
日期比较的三种方法
-
使用 getTime() 方法获取两个日期(自1970年1月1日经历的毫秒数值),然后比较这两个值。
-
使用方法 before(),after() 和 equals()。例如,一个月的12号比18号早,则 new Date(99, 2, 12).before(new Date (99, 2, 18)) 返回true。
-
使用 compareTo() 方法,它是由 Comparable 接口定义的,Date 类实现了这个接口。
15. 正则表达式
感觉就是字符串的各种匹配什么的,也不止字符串,对文本的搜索、编辑、处理
16.Java 方法
在一些其它语言中方法指过程和函数。一个返回非void类型返回值的方法称为函数;一个返回void类型返回值的方法叫做过程。
修饰符 返回值类型 方法名(参数类型 参数名){ ... 方法体 ... return 返回值; }
修饰符和参数是可选的
方法名为main的是主方法,也就是c语言里说的主函数
问:java文件名与类名一致,但是万一一个文件有不止一个类怎么办
答:在Java中,通常情况下一个源文件(.java)的名称应与其中的public类的名称相同。如果一个文件中包含多个类,那么只能有一个类是public的,且文件名需要与public类的名称保持一致。
对于非public类,它们可以有相同的或者不同的名称,与文件名无关。
问:为什么总是感觉主函数里的string参数都没有用
答:是
public static void main(String[] args)
。其中的参数是一个 String 类型的数组:String[] args
。这个参数通常用于从命令行接收程序运行时输入的参数。args 是一个数组,存储着传递给主程序的一系列字符串。在有些代码示例中,我们没有使用 args 数组,但它仍然需要被包括在 main 方法的定义中,因为这是 Java 程序的一个基本要求。
注意,java中对象是按引用传递的,而不是按值传递的
命令行参数的使用
如果希望在运行一个程序时候再传递给它消息,需要靠传递命令行参数给main()函数实现。命令行参数是在执行程序时候紧跟在程序名字后面的信息。
比如一个打印数组内容的for函数,数组内容可以在命令行给出: $ javac CommandLine.java $ java CommandLine this is a command line 200 -100 args[0]: this args[1]: is args[2]: a args[3]: command args[4]: line args[5]: 200 args[6]: -100
可变参数
可变参数声明如下:一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。
typeName... parameterName public class VarargsDemo { public static void main(String[] args) { // 调用可变参数的方法 printMax(34, 3, 3, 2, 56.5); printMax(new double[]{1, 2, 3}); } public static void printMax( double... numbers) { if (numbers.length == 0) { System.out.println("No argument passed"); return; } double result = numbers[0]; for (int i = 1; i < numbers.length; i++){ if (numbers[i] > result) { result = numbers[i]; } } System.out.println("The max value is " + result); } }
finalize() 方法
在对象被垃圾收集器析构(回收)之前调用,用来清除回收对象。使用 finalize() 来确保一个对象打开的文件被关闭了。
protected void finalize() // 在这里终结代码 } { / protected 是一个限定符,它确保 finalize() 方法不会被该类以外的代码调用
当然,Java 的内存回收可以由 JVM 来自动完成。如果你手动使用,则可以使用上面的方法
public class FinalizationDemo { public static void main(String[] args) { Cake c1 = new Cake(1); Cake c2 = new Cake(2); Cake c3 = new Cake(3); c2 = c3 = null; System.gc(); //调用Java垃圾收集器 } } class Cake extends Object { private int id; public Cake(int id) { this.id = id; System.out.println("Cake Object " + id + "is created"); } protected void finalize() throws java.lang.Throwable { super.finalize(); System.out.println("Cake Object " + id + "is disposed"); } }
这段 Java 代码主要是为了演示对象的终结(Finalization)和垃圾收集(Garbage Collection)的概念。让我们详细解释一下:
首先,创建了一个名为
FinalizationDemo
的公共类,其中有一个main
方法。这是程序的入口点。在
main
方法中,创建了三个Cake
类的实例:c1
、c2
和c3
。每个实例的构造函数都接收一个整数参数(分别为1、2、3)。然后,将
c2
和c3
的引用设置为null
。这意味着原来的Cake
对象实例不再被其他对象引用,这使它们成为垃圾回收的候选对象。接下来,调用
System.gc();
语句来尝试触发垃圾收集器。请注意,调用System.gc();
并不保证立即进行垃圾回收,而只是一个建议给垃圾回收器的提示。
Cake
类是自定义的类,扩展了 Java 的Object
类。它有一个私有的整数成员变量id
和一个构造函数,该构造函数接收一个参数并将其赋值给id
。在构造函数中,输出一条信息,表示创建了一个具有特定id
的Cake
对象。在
Cake
类中有一个受保护的finalize
方法,它覆盖了Object
类的finalize
方法。这个方法在垃圾收集器回收对象之前被调用。在finalize
方法中,首先调用super.finalize();
以确保父类的finalize
方法被正确执行,然后输出一条信息,表示具有特定id
的Cake
对象已被释放。这段代码的目的是向你展示创建、终结和垃圾回收过程。在实际开发中,应避免过度依赖
finalize
方法和显式调用System.gc()
,因为垃圾回收机制通常会自动处理不再使用的对象。在大多数情况下,你应该使用其他方法(如 try-with-resources)来管理资源释放。
16. scanner类
用scanner类来获取用户的输出 Scanner s = new Scanner(System.in);用构造方法创建scanner对象的基本语法
通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要 使用 hasNext 与 hasNextLine 判断是否还有输入的数据
import java.util.Scanner; public class ScannerDemo { public static void main(String[] args) { Scanner scan = new Scanner(System.in); // 从键盘接收数据 // next方式接收字符串 System.out.println("next方式接收:"); // 判断是否还有输入 if (scan.hasNext()) { String str1 = scan.next(); System.out.println("输入的数据为:" + str1); } scan.close(); } }
next():
-
一定要读取到有效字符后才可以结束输入。
-
对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
-
只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
-
next() 不能得到带有空格的字符串。
nextLine():
-
以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
-
可以获得空白。
如果要输入 int 或 float 类型的数据,在 Scanner 类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取:
import java.util.Scanner; public class ScannerDemo { public static void main(String[] args) { Scanner scan = new Scanner(System.in); // 从键盘接收数据 int i = 0; float f = 0.0f; System.out.print("输入整数:"); if (scan.hasNextInt()) { // 判断输入的是否是整数 i = scan.nextInt(); // 接收整数 System.out.println("整数数据:" + i); } else { // 输入错误的信息 System.out.println("输入的不是整数!"); } System.out.print("输入小数:"); if (scan.hasNextFloat()) { // 判断输入的是否是小数 f = scan.nextFloat(); // 接收小数 System.out.println("小数数据:" + f); } else { // 输入错误的信息 System.out.println("输入的不是小数!"); } scan.close(); } }
-
Java 流(Stream)、文件(File)和IO
Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。
Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。
18. Java 异常处理
异常通常有:
-
用户输入了非法数据。
-
要打开的文件不存在。
-
网络通信时连接中断,或者JVM内存溢出。
需要掌握以下三种类型的异常:
-
检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
-
运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
-
错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
Exception 类的层次
所有的异常类是从 java.lang.Exception 类继承的子类。
Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。Error 用来指示运行时环境发生的错误。例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
异常类有两个主要的子类:IOException 类和 RuntimeException 类。
Java 内置异常类
Java 语言定义了一些异常类在 java.lang 标准包中。
标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。
去看列出的java的非检查性异常与定义在 java.lang 包中的检查性异常类。
异常方法
下面的列表是 Throwable 类的主要方法:
序号 | 方法及说明 |
---|---|
1 | public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 |
2 | public Throwable getCause() 返回一个 Throwable 对象代表异常原因。 |
3 | public String toString() 返回此 Throwable 的简短描述。 |
4 | public void printStackTrace() 将此 Throwable 及其回溯打印到标准错误流。。 |
5 | public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
6 | public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 |
捕获异常
使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方,其代码块中的代码称为保护代码,使用 try/catch 的语法如下:
try { // 程序代码 }catch(ExceptionName e1) { //Catch 块 包含要捕获异常类型的声明 }
当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。
如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。
多重捕获块
一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
try{ // 程序代码 }catch(异常类型1 异常的变量名1){ // 程序代码 }catch(异常类型2 异常的变量名2){ // 程序代码 }catch(异常类型3 异常的变量名3){ // 程序代码 }
如果保护代码中发生异常,异常被抛给第一个 catch 块。如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。如果不匹配,它会被传递给第二个 catch 块。如此,直到异常被捕获或者通过所有的 catch 块。
throw关键字
throw
关键字用于显式地抛出一个异常。可以使用throw
在方法中手动抛出异常,当某个条件满足时,执行流程会被中断,并引发一个特定类型的异常。通常,throw
语句后跟着一个异常对象。
例如,下面的代码中,在方法中判断 num 是否小于 0,如果是,则抛出一个 IllegalArgumentException 异常。
public void checkNumber(int num) { if (num < 0) { throw new IllegalArgumentException("Number must be positive"); } }
throws 关键字
throws
关键字用于声明一个方法可能抛出的异常类型。它用于方法签名的末尾,标明可能抛出哪些特定类型的异常。这有助于提醒调用该方法的代码,可能需要处理或传播这些异常。如果一个方法可能抛出多种异常,可以使用逗号分隔列出它们。
例如,这段代码首先打开并读取指定文件,然后逐行打印文件的内容,最后关闭 BufferedReader。如果过程中出现任何 I/O(输入/输出)错误,将抛出 IOException。
///这段代码用于从指定文件路径读取文件内容并逐行打印至控制台。 public void readFile(String filePath) throws IOException { BufferedReader reader = new BufferedReader(new FileReader(filePath)); ///创建了一个新的 FileReader 对象,用于从指定文件路径读取数据,然后将其传递给一个新的 BufferedReader 对象。BufferedReader 提供了缓冲功能以提高文件读取效率。 String line = reader.readLine(); ///这一行从缓冲阅读器中读取文件的第一行。如果已经读到文件末尾,readLine() 方法将返回 null while (line != null) { ///这是一个 while 循环,当 line 不等于 null 时,表示还有内容可以继续读取。 System.out.println(line); line = reader.readLine();///继续从 BufferedReader 中读取下一行内容,并将其赋值给 line 变量。如果已经读到文件末尾, readLine() 方法将返回 null,循环将自动终止。 } reader.close(); 关闭 BufferedReader,这是释放文件资源并避免可能的资源泄露的重要步骤。 }
finally关键字
finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
try{ // 程序代码 }catch(异常类型1 异常的变量名1){ // 程序代码 }catch(异常类型2 异常的变量名2){ // 程序代码 }finally{ // 程序代码 }
-
catch 不能独立于 try 存在。
-
在 try/catch 后面添加 finally 块并非强制性要求的。
-
try 代码后不能既没 catch 块也没 finally 块。
-
try, catch, finally 块之间不能添加任何代码。
try-with-resources
JDK7 之前所有被打开的系统资源,比如流、文件或者 Socket 连接等,都需要被开发者手动关闭,否则将会造成资源泄露。
ry-with-resources是Java 7引入的一种语法糖,它实质上是一种更简洁、更安全的方法来处理资源的释放,尤其是在有异常抛出时。在Java中,许多资源,如文件、网络连接或数据库连接等,必须在使用完毕后显式关闭以避免资源泄漏。try-with-resources提供了一种更简洁的方式来实现这一目的。
try (resource declaration) { // 使用的资源 } catch (ExceptionType e1) { // 异常块 } try (声明并初始化资源) { // 使用资源的代码 } catch (异常类型 e) { // 处理异常的代码 } finally { // 可选,无论是否有异常抛出,这里的代码都会执行 }
当执行离开try语句块时,声明并初始化的资源会自动关闭。这意味着你不再需要显式调用资源的close()
方法。为了使用try-with-resources,资源类必须实现java.lang.AutoCloseable
或java.io.Closeable
接口,这两者都包括了close()
方法。
这种语法在处理多个资源时也非常有用。在这种情况下,可以在try子句括号内声明和初始化多个资源,用分号隔开
try (Resource1Type res1 = initializeResource1(); Resource2Type res2 = initializeResource2()) { // 使用资源的代码 } catch (异常类型 e) { // 处理异常的代码 }
声明自定义异常
-
所有异常都必须是 Throwable 的子类。
-
如果希望写一个检查性异常类,则需要继承 Exception 类。
-
如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
可如是定义: class MyException extends Exception{ }
19. java继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法。所以子类会具有父类的一般特性也会具有自身的特性。 class 子类 extends 父类
作用就是提取相同部分作为父类,可以少些重复性高的代码,便于维护
继承的特性
-
子类拥有父类非 private 的属性、方法。
-
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
-
子类可以用自己的方式实现父类的方法。
-
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
-
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
extends关键字
implements关键字
super与this关键字
super关键字:通过super关键字来实现对父类成员的访问,引用当前对象的父类。
this关键字:指向自己的引用
final关键字
构造器
当一个子类继承父类时,它并不会继承父类的构造器,但是子类必须调用父类的构造器,以确保父类的属性和行为也能够被正确地初始化。这个调用可以通过使用 super
关键字来实现。
如果父类的构造器带有参数,则子类的构造器必须显式地调用父类的构造器,并且提供适当的参数列表;如果父类的构造器没有参数,则子类的构造器不需要显式地调用父类的构造器,系统会自动调用父类的无参构造器。
显式调用 public class Parent { public Parent(String name) { // 构造器代码 } } public class Child extends Parent { public Child(String name) { super(name); // 显式调用父类构造器 // 子类构造器代码 } } 隐式调用 public class Parent { public Parent() { // 构造器代码 } } public class Child extends Parent { public Child() { // 子类构造器代码 } }
总之必须调用
实例:
class SuperClass { private int n; SuperClass(){ System.out.println("SuperClass()"); } SuperClass(int n) { System.out.println("SuperClass(int n)"); this.n = n; } } // SubClass 类继承 class SubClass extends SuperClass{ private int n; SubClass(){ // 自动调用父类的无参数构造器 System.out.println("SubClass"); } public SubClass(int n){ super(300); // 调用父类中带有参数的构造器 System.out.println("SubClass(int n):"+n); this.n = n; } } // SubClass2 类继承 class SubClass2 extends SuperClass{ private int n; SubClass2(){ super(300); // 调用父类中带有参数的构造器 System.out.println("SubClass2"); } public SubClass2(int n){ // 自动调用父类的无参数构造器 System.out.println("SubClass2(int n):"+n); this.n = n; } } public class TestSuperSub{ public static void main (String args[]){ System.out.println("------SubClass 类继承------"); SubClass sc1 = new SubClass(); SubClass sc2 = new SubClass(100); System.out.println("------SubClass2 类继承------"); SubClass2 sc3 = new SubClass2(); SubClass2 sc4 = new SubClass2(200); } }
输出为:
------SubClass 类继承------ SuperClass() SubClass SuperClass(int n) SubClass(int n):100 ------SubClass2 类继承------ SuperClass(int n) SubClass2 SuperClass() SubClass2(int n):200
可以看出:
SuperClass() SubClass
这两个是supclass对象无参数构造函数实例化的结果;
SuperClass()
是调用父类无参数构造器的结果,SubClass
是supclass对象对父类无参数函数方法重写的结果
重写(Override)
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常
方法的重写规则
-
参数列表与被重写方法的参数列表必须完全相同。
-
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
-
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
-
父类的成员方法只能被它的子类重写。
-
声明为 final 的方法不能被重写。
-
声明为 static 的方法不能被重写,但是能够被再次声明。
-
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
-
子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
-
重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
-
构造方法不能被重写。
-
如果不能继承一个类,则不能重写该类的方法。
重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。(典型的方法重载)
重载规则:
-
被重载的方法必须改变参数列表(参数个数或类型不一样);
-
被重载的方法可以改变返回类型和访问修饰符;
-
被重载的方法可以声明新的或更广的检查异常;
-
方法能够在同一个类中或者在一个子类中被重载。
-
无法以返回值类型作为重载函数的区分标准。
多态
当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法;要想调用父类中被重写的方法,则必须使用关键字 super
看这个link里的虚函数
输出的前两行是’Employee 构造函数‘
是因为在创建
Salary
对象s和Employee
对象e时,都会调用Employee
类的构造函数ava中,子类的构造函数必须调用父类的构造函数;创建
Salary
对象s
时,首先调用Employee
类的构造函数,然后再调用Salary
类的构造函数,当创建Employee
对象e
时,同样会调用Employee
类的构造函数。
多态存在的三个必要条件
-
继承
-
重写
-
父类引用指向子类对象:Parent p = new Child();
多态的实现方式
-
方式一:重写:
-
方式二:接口
-
方式三:抽象类和抽象方法
封装
实现封装:
-
修改属性的可见性来限制对属性的访问(一般限制为private)
-
对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问。任何要访问类中私有成员变量的类都要通过这些getter和setter方法
/* 文件名: EncapTest.java */ public class EncapTest{ private String name; private String idNum; private int age; public int getAge(){ return age; } public String getName(){ return name; } public String getIdNum(){ return idNum; } public void setAge( int newAge){ age = newAge; } public void setName(String newName){ name = newName; } public void setIdNum( String newId){ idNum = newId; } }
接口
接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法
类描述对象的属性和方法。接口则包含类要实现的方法
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。
枚举
这段代码定义了一个枚举类型
Color
,其中包含三个枚举常量:RED、GREEN和BLUE。每个枚举常量都有一个私有的构造函数,用于在创建枚举常量时输出相应的提示信息。此外,Color
枚举类还包含一个公共方法colorInfo
用于输出颜色信息。
包
其实就是文件夹啦~
抽象类
抽象类总结规定
-
抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
-
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
-
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
-
构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
-
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。