[huayang]
变量和数据类型
在 Java 中,变量必须先定义后使用,在定义变量的时候,可以给它一个初始值
注意到第一次定义变量 x
的时候,需要指定变量类型 int
,因此使用语句 int x = 100;
。而第二次重新赋值的时候,变量 x
已经存在了,不能再重复定义,因此不能指定变量类型 int
,必须使用语句 x = 200;
基本数据类型整数类型:byte,short,int,long浮点数类型:float,double (double与float的区别取值范围和字节数 Double更大)字符类型:char布尔类型:boolean
注意 char
类型使用单引号'
,且仅有一个字符,要和双引号 "
的字符串类型区分开
常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。
使用 var
定义变量,仅仅是少写了变量类型而已
定义变量时,要遵循作用域最小化原则,尽量将变量定义在尽可能小的作用域,并且,不要重复使用变量名
整数运算
特别注意:整数的除法对于除数为 0 时运行时将报错,但编译不会报错。
移位运算
在计算机中,整数总是以二进制的形式表示
比如:
<< 表示向左位移一位
>> 表示向右位移一位
还有一种无符号的右移运算,使用 >>>
,它的特点是不管符号位,右移后高位总是补 0
int n = -536870912;int a = n >> 1; // 11110000 00000000 00000000 00000000 = -268435456int b = n >> 2; // 11111000 00000000 00000000 00000000 = -134217728int c = n >> 28; // 11111111 11111111 11111111 11111110 = -2int d = n >> 29; // 11111111 11111111 11111111 11111111 = -1
位运算
与运算,符号为|,1|1 为 1 反之都为 0
或运算,符号为 &,0&0 为 0 反之都为 1
异或运算,符号为 ^,同为 0 异为 1
在Java的计算表达式中,运算优先级从高到低依次是:()! ~ ++ --* / %+ -<< >> >>>&|+= -= *= /=记不住也没关系,只需要加括号就可以保证运算的优先级正确。
在运算过程中,如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型
要注意,超出范围的强制转型会得到错误的结果,原因是转型时,int
的两个高位字节直接被扔掉,仅保留了低位的两个字节
因此,强制转型的结果很可能是错的。
浮点数运算
浮点数运算和整数运算相比,只能进行加减乘除这些数值计算,不能做位运算和移位运算。
在计算机中,浮点数虽然表示的范围大,但是,浮点数有个非常重要的特点,就是浮点数常常无法精确表示。
可以将浮点数强制转型为整数。在转型时,浮点数的小数部分会被丢掉。如果转型后超过了整型能表示的最大范围,将返回整型的最大值
布尔运算
布尔运算是一种关系运算,包括以下几类:
- 比较运算符:
>
,>=
,<
,<=
,==
,!=
- 与运算
&&
- 或运算
||
- 非运算
!
关系运算符的优先级从高到低依次是:
!
>
,>=
,<
,<=
==
,!=
&&
||
短路运算
布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。
三元运算符
int n = -100; int x = n >= 0 ? n : -n;//n=100
字符和字符串
在 Java 中,字符和字符串是两个不同的类型。
Java 在内存中总是使用 Unicode 表示字符
转义字符 \
字符串连接
使用 +
连接任意字符串和其他数据类型
如果用 +
连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串
从 Java 13 开始,字符串可以用 """..."""
表示多行字符串(Text Blocks)了
不可变特性
数组类型
// 5位同学的成绩: int[] ns = new int[5]; ns[0] = 68; ns[1] = 79; ns[2] = 91; ns[3] = 85; ns[4] = 62;
可以用数组变量.length
获取数组大小
// 5位同学的成绩: int[] ns = new int[5]; System.out.println(ns.length); // 5
也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小
int[] ns = new int[] { 68, 79, 91, 85, 62 };
还可以进一步简写为:
int[] ns = { 68, 79, 91, 85, 62 };
输入和输出
println
是 print line 的缩写,表示输出并换行。因此,如果输出后不想换行,可以用 print()
占位符 说明%d 格式化输出整数%x 格式化输出十六进制整数%f 格式化输出浮点数%e 格式化输出科学计数法表示的浮点数%s 格式化字符串
由于 % 表示占位符,因此,连续两个 %% 表示一个 % 字符本身。
if 判断
引用类型判断内容相等要使用 equals()
switch 多重选择
如果 option
的值没有匹配到任何 case
,这时,可以给 switch
语句加一个 default
,当没有匹配到任何 case
时,执行 default
public class Main { public static void main(String[] args) { int option = 99; switch (option) { case 1: System.out.println("Selected 1"); break; case 2: System.out.println("Selected 2"); break; case 3: System.out.println("Selected 3"); break; default: System.out.println("Not selected"); break; } }}
使用 switch
时,注意 case
语句并没有花括号 {}
,而且,case
语句具有 “穿透性”,漏写 break
将导致意想不到的结果
如果有几个 case
语句执行的是同一组语句块,可以这么写:
public class Main { public static void main(String[] args) { int option = 2; switch (option) { case 1: System.out.println("Selected 1"); break; case 2: case 3: System.out.println("Selected 2, 3"); break; default: System.out.println("Not selected"); break; } }}
switch
语句还可以匹配字符串
switch 表达式
从 Java 12 开始,switch
语句升级为更简洁的表达式语法,使用类似模式匹配(Pattern Matching)的方法,保证只有一种路径会被执行,并且不需要 break
语句
public class Main { public static void main(String[] args) { String fruit = "apple"; int opt = switch (fruit) { case "apple" -> 1; case "pear", "mango" -> 2; default -> 0; }; // 注意赋值语句要以;结束 System.out.println("opt = " + opt); }}
注意新语法使用 ->
,如果有多条语句,需要用 {}
括起来。不要写 break
语句,因为新语法只会执行匹配的语句,没有穿透效应。
yield
返回
while 循环
while (条件表达式) { 循环语句}// 继续执行后续代码
注意到 while
循环是先判断循环条件,再循环,因此,有可能一次循环都不做
do while 循环
do while
循环则是先执行循环,再判断条件,条件满足时继续循环,条件不满足时退出。它的用法是:
do { 执行循环语句} while (条件表达式);
for 循环
使用 for
循环时,计数器变量 i
要尽量定义在 for
循环中
for each 循环
int[] ns = { 1, 4, 9, 16, 25 }; for (int n : ns) { System.out.println(n); }
break 和 continue
break
在循环过程中,可以使用 break
语句跳出当前循环
要特别注意,break
语句总是跳出自己所在的那一层循环
continue
continue
则是提前结束本次循环,直接继续执行下次循环
遍历数组
打印数组内容
Java 标准库提供了 Arrays.toString()
,可以快速打印数组内容
import java.util.Arrays;public class Main { public static void main(String[] args) { int[] ns = { 1, 1, 2, 3, 5, 8 }; System.out.println(Arrays.toString(ns)); }}
数组排序
实际上,Java 的标准库已经内置了排序功能,我们只需要调用 JDK 提供的 Arrays.sort()
就可以排
import java.util.Arrays;public class Main { public static void main(String[] args) { int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 }; Arrays.sort(ns); System.out.println(Arrays.toString(ns)); }}
多维数组
二维数组
二维数组就是数组的数组。定义一个二维数组如下:
public class Main { public static void main(String[] args) { int[][] ns = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; System.out.println(ns.length); // 3 }}
打印一个二维数组使用 Java 标准库的 Arrays.deepToString()
:
import java.util.Arrays;public class Main { public static void main(String[] args) { int[][] ns = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; System.out.println(Arrays.deepToString(ns)); }}
三维数组
三维数组就是二维数组的数组。可以这么定义一个三维数组:
int[][][] ns = { { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }, { {10, 11}, {12, 13} }, { {14, 15, 16}, {17, 18} }};
面向对象基础
定义 class
在 Java 中,创建一个类,例如,给这个类命名为 Person
,就是定义一个 class
:
class Person { public String name; public int age;}
一个 class
可以包含多个字段(field
),字段用来描述一个类的特征。上面的 Person
类,我们定义了两个字段,一个是 String
类型的字段,命名为 name
,一个是 int
类型的字段,命名为 age
。因此,通过 class
,把一组数据汇集到一个对象上,实现了数据封装。
public
是用来修饰字段的,它表示这个字段可以被外部访问
在 OOP 中,class
和 instance
是 “模版” 和 “实例” 的关系;
定义 class
就是定义了一种数据类型,对应的 instance
是这种数据类型的实例;
class
定义的 field
,在每个 instance
都会拥有各自的 field
,且互不干扰;
通过 new
操作符创建新的 instance
,然后用变量指向它,即可通过变量来引用这个 instance
;
指向 instance
的变量都是引用变量。
访问实例变量可以用变量.字段
Person ming = new Person();ming.name = "Xiao Ming"; // 对字段name赋值ming.age = 12; // 对字段age赋值System.out.println(ming.name); // 访问字段namePerson hong = new Person();hong.name = "Xiao Hong";hong.age = 15;
方法
为了避免外部代码直接去访问 field
,我们可以用 private
修饰 field
,拒绝外部访问
把 field
从 public
改成 private
,外部代码不能访问这些 field
我们需要使用方法(method
)来让外部代码可以间接修改 field
- 方法内部遇到 return 时返回,void 表示不返回任何值(注意和返回 null 不同);
- 外部代码通过 public 方法操作实例,内部代码可以调用 private 方法;
- 理解方法的参数绑定。
this 变量
它始终指向当前实例。因此,通过 this.field
就可以访问当前实例的字段
如果没有命名冲突,可以省略 this
构造方法
public class Main { public static void main(String[] args) { // TODO: 给Person增加构造方法: Person ming = new Person("小明", 12);/** 等同于Person ming = new Person(); ming.setName("小明"); ming.setAge(12);**/ System.out.println(ming.getName()); System.out.println(ming.getAge()); }}class Person { private String name; private int age; public Person(String n,int a){ name = n; age = a; } public String getName() { return name; } public int getAge() { return age; }}
没有定义构造方法时,编译器会自动创建一个默认的无参数构造方法;
可以定义多个构造方法,编译器根据参数自动判断;
可以在一个构造方法内部调用另一个构造方法,便于代码复用。
方法重载
多个相同名称的方法如果想在同一个类中共存,那么这些同名的方法一定是参数的个数或者参数的数据类型不一样,这种同名的方法就叫做重载(Overload)
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。
继承
Java 使用 extends
关键字来实现继承
class Person { private String name; private int age; public String getName() {...} public void setName(String name) {...} public int getAge() {...} public void setAge(int age) {...}}class Student extends Person { // 不要重复name和age字段/方法, // 只需要定义新增score字段/方法: private int score; public int getScore() { … } public void setScore(int score) { … }}
多态
若编译时的类型和运行时的类型不一样就会出现多态
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。
在子类Student中,覆写这个run()方法:class Student extends Person { @Override public void run() { System.out.println("Student.run"); }}
注意:方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在 Java 程序中,出现这种情况,编译器会报错。
小结
- 子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为;
- Java 的方法调用总是作用于运行期对象的实际类型,这种行为称为多态;
final
修饰符有多种作用:final
修饰的方法可以阻止被覆写;final
修饰的 class 可以阻止被继承;final
修饰的 field 必须在创建对象时初始化,随后不可修改。
抽象类
由于多态的存在,每个子类都可以覆写父类的方法
抽象类中有一个抽象方法就必须是抽象类,抽象方法用ABSTRACT
修饰。
因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。
使用 abstract
修饰的类就是抽象类。我们无法实例化一个抽象类:
abstract class Person { public abstract void run();}
Person p = new Person(); // 编译错误
定义了抽象的方法要实现类的时候就必须要复写
- 如果不实现抽象方法,则该子类仍是一个抽象类;
面向抽象编程
面向抽象编程的本质就是:
- 上层代码只定义规范(例如:
abstract class Person
); - 不需要子类就可以实现业务逻辑(正常编译);
- 具体的业务逻辑由不同的子类实现,调用者并不关心。
接口
如果一个抽象类没有字段,所有方法全部都是抽象方法:
abstract class Person { public abstract void run(); public abstract String getName();}
就可以把该抽象类改写为接口:interface
。
在 Java 中,使用 interface
可以声明一个接口:
interface Person { void run(); String getName();}
接口定义的所有方法默认都是 public abstract
的,所以这两个修饰符不需要写出来(写不写效果都一样)
当一个具体的 class
去实现一个 interface
时,需要使用 implements
关键字。
class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void run() { System.out.println(this.name + " run"); } @Override public String getName() { return this.name; }}
一个类可以实现多个 interface
,多个接口用,分隔
如果类没有实现接口所有方法,那么这个类就要定义抽象类
接口继承
一个 interface
可以继承自另一个 interface
。interface
继承自 interface
使用 extends
,它相当于扩展了接口的方法。
先写继承后写实现
静态字段和静态方法
在一个 class
中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。
还有一种字段,是用 static
修饰的字段,称为静态字段:static field
class Person { public String name; public int age; // 定义静态字段number: public static int number;}
对于静态字段,无论修改哪个实例的静态字段,效果都是一样的
静态方法
有静态字段,就有静态方法。用 static
修饰的方法称为静态方法
包
在 Java 中,我们使用 package
来解决名字冲突。
package ming; // 申明包名mingpublic class Person {}
import
在一个 class
中,我们总会引用其他的 class
第一种,直接写出完整类名,例如:
// Person.javapackage ming;public class Person { public void run() { mr.jun.Arrays arrays = new mr.jun.Arrays(); }}
第二种写法是用 import
语句,导入小军的 Arrays
,然后写简单类名:
// Person.javapackage ming;// 导入完整类名:import mr.jun.Arrays;public class Person { public void run() { Arrays arrays = new Arrays(); }}
在写 import
的时候,可以使用 *
,表示把这个包下面的所有 class
都导入进来(但不包括子包的 class
):
作用域
我们经常看到 public
、protected
、private
这些修饰符。在 Java 中,这些修饰符可以用来限定访问作用域。
public
定义为 public
的 class
、interface
可以被其他任何类访问
private
定义为 private
的 field
、method
无法被其他类访问
protected
protected
作用于继承关系。定义为 protected
的字段和方法可以被子类访问,以及子类的子类
上面的 protected
方法可以被继承的类访问
package xyz;class Main extends Hello { void foo() { // 可以访问protected方法: hi(); }}
package
包作用域是指一个类允许访问同一个 package
的没有 public
、private
修饰的 class
,以及没有 public
、protected
、private
修饰的字段和方法
package abc;// package权限的类:class Hello { // package权限的方法: void hi() { }}
只要在同一个包,就可以访问 package
权限的 class
、field
和 method
:
package abc;class Main { void foo() { // 可以访问package权限的类: Hello h = new Hello(); // 可以调用package权限的方法: h.hi(); }}
局部变量
在方法内部定义的变量称为局部变量,局部变量作用域从变量声明处开始到对应的块结束
final
用 final
修饰 class
可以阻止被继承
package abc;// 无法被继承:public final class Hello { private int n = 0; protected void hi(int t) { long i = t; }}
用 final
修饰 method
可以阻止被子类覆写
package abc;public class Hello { // 无法被覆写: protected final void hi() { }}
用 final
修饰 field
可以阻止被重新赋值
package abc;public class Hello { private final int n = 0; protected void hi() { this.n = 1; // error! }}
用 final
修饰局部变量可以阻止被重新赋值:
package abc;public class Hello { protected void hi(final int t) { t = 1; // error! }}
最佳实践
如果不确定是否需要 public
,就不声明为 public
,即尽可能少地暴露对外的字段和方法。
把方法定义为 package
权限有助于测试,因为测试类和被测试类只要位于同一个 package
,测试代码就可以访问被测试类的 package
权限方法。
一个.java
文件只能包含一个 public
类,但可以包含多个非 public
类。如果有 public
类,文件名必须和 public
类的名字相同。
内部类(Inner Class)
如果一个类定义在另一个类的内部,这个类就是 Inner Class:
class Outer { class Inner { // 定义了一个Inner Class }}
上述定义的 Outer
是一个普通类,而 Inner
是一个 Inner Class,它与普通类有个最大的不同,就是 Inner Class 的实例不能单独存在,必须依附于一个 Outer Class 的实例。示例代码如下:
public class Main { public static void main(String[] args) { Outer outer = new Outer("Nested"); // 实例化一个Outer Outer.Inner inner = outer.new Inner(); // 实例化一个Inner inner.hello(); }}class Outer { private String name; Outer(String name) { this.name = name; } class Inner { void hello() { System.out.println("Hello, " + Outer.this.name); } }}
[/huayang]
FROM:浅浅淡淡[hellohy]
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论