1.数据类型
变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。
内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。
1.1 基本数据类型
Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。基本数据类型通常变量申请的内存空间都在栈中申请。
byte:
- byte 数据类型是8位、有符号的,以二进制补码表示的整数;
- 最小值是 -128(-2^7);
- 最大值是 127(2^7-1);
- 默认值是 0;
- byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
- 例子:byte a = 100,byte b = -50。
short:
- short 数据类型是 16 位、有符号的以二进制补码表示的整数
- 最小值是 -32768(-2^15);
- 最大值是 32767(2^15 - 1);
- Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
- 默认值是 0;
- 例子:short s = 1000,short r = -20000。
int:
- int 数据类型是32位、有符号的以二进制补码表示的整数;
- 最小值是 -2,147,483,648(-2^31);
- 最大值是 2,147,483,647(2^31 - 1);
- 一般地整型变量默认为 int 类型;
- 默认值是 0 ;
- 例子:int a = 100000, int b = -200000。
long:
- long 数据类型是 64 位、有符号的以二进制补码表示的整数;
- 最小值是 -9,223,372,036,854,775,808(-2^63);
- 最大值是 9,223,372,036,854,775,807(2^63 -1);
- 这种类型主要使用在需要比较大整数的系统上;
- 默认值是 0L;
- 例子: long a = 100000L,long b = -200000L。
"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。
float:
- float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
- float 在储存大型浮点数组的时候可节省内存空间;
- 默认值是 0.0f;
- 浮点数不能用来表示精确的值,如货币;
- 例子:float f1 = 234.5f。
double:
-
double 数据类型是双精度、64 位、符合 IEEE 754 标准的浮点数;
-
浮点数的默认类型为 double 类型;
-
double类型同样不能表示精确的值,如货币;
-
默认值是 0.0d;
-
例子:
double d1 = 7D ; double d2 = 7.; double d3 = 8.0; double d4 = 8.D; double d5 = 12.9867;
7 是一个 int 字面量,而 7D,7. 和 8.0 是 double 字面量。
boolean:
- boolean数据类型表示一位的信息;
- 只有两个取值:true 和 false;
- 这种类型只作为一种标志来记录 true/false 情况;
- 默认值是 false;
- 例子:boolean one = true。
char:
- char 类型是一个单一的 16 位 Unicode 字符;
- 最小值是 \u0000(十进制等效值为 0);
- 最大值是 \uffff(即为 65535);
- char 数据类型可以储存任何字符;
- 例子:char letter = 'A';。
可以通过相应的包装类的内置属性,打印出最大值以及
java
public class PrimitiveTypeTest {
public static void main(String[] args) {
// byte
System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE);
System.out.println("包装类:java.lang.Byte");
System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE);
System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE);
System.out.println();
// short
System.out.println("基本类型:short 二进制位数:" + Short.SIZE);
System.out.println("包装类:java.lang.Short");
System.out.println("最小值:Short.MIN_VALUE=" + Short.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE=" + Short.MAX_VALUE);
System.out.println();
// int
System.out.println("基本类型:int 二进制位数:" + Integer.SIZE);
System.out.println("包装类:java.lang.Integer");
System.out.println("最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE);
System.out.println("最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE);
System.out.println();
// long
System.out.println("基本类型:long 二进制位数:" + Long.SIZE);
System.out.println("包装类:java.lang.Long");
System.out.println("最小值:Long.MIN_VALUE=" + Long.MIN_VALUE);
System.out.println("最大值:Long.MAX_VALUE=" + Long.MAX_VALUE);
System.out.println();
// float
System.out.println("基本类型:float 二进制位数:" + Float.SIZE);
System.out.println("包装类:java.lang.Float");
System.out.println("最小值:Float.MIN_VALUE=" + Float.MIN_VALUE);
System.out.println("最大值:Float.MAX_VALUE=" + Float.MAX_VALUE);
System.out.println();
// double
System.out.println("基本类型:double 二进制位数:" + Double.SIZE);
System.out.println("包装类:java.lang.Double");
System.out.println("最小值:Double.MIN_VALUE=" + Double.MIN_VALUE);
System.out.println("最大值:Double.MAX_VALUE=" + Double.MAX_VALUE);
System.out.println();
// char
System.out.println("基本类型:char 二进制位数:" + Character.SIZE);
System.out.println("包装类:java.lang.Character");
// 以数值形式而不是字符形式将Character.MIN_VALUE输出到控制台
System.out.println("最小值:Character.MIN_VALUE="
+ (int) Character.MIN_VALUE);
// 以数值形式而不是字符形式将Character.MAX_VALUE输出到控制台
System.out.println("最大值:Character.MAX_VALUE="
+ (int) Character.MAX_VALUE);
}
}
类型默认值:
1.2 引用数据类型
**引用类型是指存储对象或数组的引用(地址),而不是存储实际的值。**通过引用,程序可以访问对象或数组的属性和方法。
在 Java 中,主要有以下几种引用类型:
-
对象类型(Object Types):
- 通过类(class)定义的对象属于引用类型。例如:
String
、ArrayList
、用户定义的类等。
- 通过类(class)定义的对象属于引用类型。例如:
-
数组类型(Array Types):
- 数组是一个引用类型,可以存储同一类型的多个值。例如:
int[]
、String[]
等。
- 数组是一个引用类型,可以存储同一类型的多个值。例如:
对象或者数组的默认值是NULL。
1.3 基本数据类型之间的转换
整型、实型(常量)、字符型数据可以混合运算。
运算中,不同类型的数据先转化为同一类型,然后进行运算。
1.3.1 自动类型转换(隐式转换)
自动类型转换通常发生在如下几种情况中:
-
低精度到高精度的转换,包括:
byte
、short
、char
之间的自动提升。int
、long
之间的自动提升。- 浮点数从小类型到大类型。
-
非数值类型到数值类型的转换。
-
Wrapper类到基本类型的转换。
例如:
java
byte b = 10;
short s = b; // 隐式转换,从 byte 到 short
int i = s; // 隐式转换,从 short 到 int
float f = i; // 隐式转换,从 int 到 float
在自动类型转换中,你需要注意的是避免由于溢出或精度缺失导致的不准确结果。例如,将一个非常大的整数赋值给一个更小的整数类型可能会导致数据截断。
1.3.2 强制类型转换(显式转换)
显式类型转换通常需要用户明确指定转换的类型,即使用(目标类型)
来包裹要转换的表达式或变量。
- 从高精度到低精度的转换。
- 从一个数值类型到另一个数值类型的转换。
- 从数值类型到字符或布尔类型的转换。
例如:
java
float f = 10.5f;
int i = (int) f; // 显示转换,从 float 到 int,结果为 10
在强制类型转换中,需要注意的是可能会引起数据丢失。例如,将一个浮点数转换为整数时,浮点数的小数部分会直接被忽略。
隐含强制类型转换
-
1、 整数的默认类型是 int。
-
- 小数默认是 double 类型浮点型,在定义 float 类型时必须在数字后面跟上 F 或者 f。
1.4 引用数据类型之间的转换
1.4.1 类类型之间的转换
1. 向上转型(Upcasting)
向上转型是指将子类引用赋值给父类引用。这个过程是安全的,因为子类是父类的扩展,父类引用能够包含子类对象。
java
class Parent {
void display() {
System.out.println("Parent");
}
}
class Child extends Parent {
void display() {
System.out.println("Child");
}
}
Parent parent = new Child(); // 向上转型
parent.display(); // 输出 "Child"
在向上转型时,请注意以下几点:
- 不会发生数据丢失。
- 在父类引用中只能访问父类定义的方法和属性,如果需要访问子类特有的方法,需要进行向下转型。
2. 向下转型(Downcasting)
向下转型是指将父类引用赋值给子类引用。这个过程是可能不安全的,因为如果父类引用实际上指向的是一个不同的类型(而不是目标子类的对象),将会引发ClassCastException
。
java
Parent parent = new Child(); // 向上转型
Child child = (Child) parent; // 向下转型
在向下转型时,需要注意:
- 确保父类引用指向的确实是子类对象,否则会抛出
ClassCastException
。 - 推荐使用
instanceof
操作符,返回 true 或者 false 进行检查。
java
if (parent instanceof Child) {
Child child = (Child) parent; // 安全的转换
}
使用instanceof
:在进行向下转型之前,使用instanceof
可以有效避免转换错误。
1.4.2 接口类型之间的转换
1. 实现接口的类之间的转换
Java允许类实现多个接口,可以在接口之间进行转换。确保实际对象实现了所转换到的接口。如果不实现,转换将失败。
java
interface Animal {
void makeSound();
}
class Dog implements Animal {
public void makeSound() {
System.out.println("Woof");
}
}
Animal animal = new Dog(); // 向上转型
Dog dog = (Dog) animal; // 向下转型
在接口类型转换时的注意事项:
- 使用
instanceof
检查对象是否实现了目标接口。
java
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 安全的转换
}
1.4.3 数组类型转换
Java中的数组也是引用类型,数组类型之间的转换需要注意以下几点:
- 数组的向上转型,例如,将一个
String[]
引用赋给Object[]
,是安全的,因为所有数组的父类是Object
。
java
String[] stringArray = new String[5];
Object[] objArray = stringArray; // 向上转型
- 当进行向下转型时,需要确保实际的数组对象类型与目标类型一致。
java
Object[] objArray = new String[5];
String[] stringArray = (String[]) objArray; // 安全的转换
如果尝试将不同类型的数组进行向下转型,会引发ArrayStoreException
。
1.4.4 向上转型拿不到子类的特有成员
为何无法通过父类引用访问子类特有的方法?
-
类型擦除(编译阶段) :父类引用视图的对象类型是
Animal
。即使实际引用的是Dog
,编译器只知道animal
是一种Animal
,因此只能使用Animal
类中定义的方法。编译器在编译时只查找Animal
类的方法,不会考虑Dog
中的特有方法。 -
静态类型( 编译类型**)与动态类型(运行类型)**:
- 静态类型 是指声明变量时的类型(在上面的例子中,
animal
的静态类型是Animal
)。 - 动态类型 是指实际引用的对象类型(在这里是
Dog
)。 - 由于编译器在编译时仅依赖于静态类型,它无法知道在运行时实际引用的是
Dog
,因此只能使用Animal
的方法。
- 静态类型 是指声明变量时的类型(在上面的例子中,
因此只能通过向下转型来实现访问子类的特有成员。
那为什么访问子类和父类的共同方法,会优先调用子类的方法呢?
- 动态绑定:Java采用动态绑定(或称晚绑定)来决定在运行时哪个方法应被调用。即使使用父类引用来调用方法,Java会查看实际对象的类型(动态类型)而不是引用类型(静态类型),然后调用实际对象的实现。
1.5 数组
Java 语言中提供的数组是用来存储固定大小的同类型元素。
创建数组的三种方式:
java
dataType[] arrayRefVar = new dataType[arraySize];
java
dataType[] arrayRefVar = {value0, value1, ..., valuek};
java
// new dataType[]{value0, value1, ..., valuek} , 这里的[]不要写大小
// 因为编译器识别不出{}里面的数据占了多少
dataType[] arrayRefVar = new dataType[]{value0, value1, ..., valuek};
注意事项:
- 数组大小固定,一旦创建后不能更改,如果需要动态大小,可以考虑使用
ArrayList
等集合类。
数组常用的方法:
Arrays.copyOf(数组,指定拷贝长度)
方法进行数组复制。Arrays.sort()
方法对数组进行排序。Arrays.binarySearch()
方法可以在已排序的数组中查找元素。Arrays.toString()
方法,可以将数组转换为字符串
1.6 String、StringBuffer、StringBuild
1. String
-
不可变性 :
String
对象一旦创建,其内容不可更改。如果对String
进行修改(比如拼接),会生成一个新的String
对象,原有字符串保持不变。 -
性能:由于每次修改都需要创建新的对象,频繁操作会导致性能开销较大。
-
线程安全 :由于不可变性,
String
是线程安全的。 -
使用场景:适合使用在字符串内容不会改变的场合,如常量字符串。
示例:
java
String str = "Hello";
str += " World"; // 生成新的 String 对象
2. StringBuffer
-
可变性 :
StringBuffer
是可变的,意味着在原有对象上执行修改操作时,不会生成新的对象。 -
性能 :由于是可变的,在执行较多的字符串操作(如拼接、插入、删除等)时,性能优于
String
。 -
线程安全 :
StringBuffer
是线程安全的,所有方法都是同步的,因此适合在多线程环境中使用。 -
使用场景:适合在多线程环境中进行字符串操作的场合。
示例:
java
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 修改原有对象
3. StringBuilder
-
可变性 :与
StringBuffer
类似,StringBuilder
也是可变的,专为字符串操作设计。 -
性能 :性能优于
StringBuffer
,因为StringBuilder
的方法不是同步的,它的性能更高。 -
线程安全 :
StringBuilder
不是线程安全的,不适合在多线程环境中使用。 -
使用场景:适合在单线程环境下进行大量字符串操作。
示例:
java
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 修改原有对象
2.Iterator(迭代器)
2.1 集合实现好的迭代器
Iterator(迭代器)是 Java 集合框架中的一种机制,是一种用于遍历集合(如列表、集合和映射等)的接口。
它提供了一种统一的方式来访问集合中的元素,而不需要了解底层集合的具体实现细节。
Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。
Iterator 是 Java 迭代器最简单的实现,ListIterator 是 Collection API 中的接口, 它扩展了 Iterator 接口。
迭代器接口定义了几个方法,最常用的是以下三个:
-
next() - 返回迭代器的下一个元素,并将迭代器的指针移到下一个位置。
-
hasNext() - 用于判断集合中是否还有下一个元素可以访问。
-
remove() - 从集合中删除迭代器最后访问的元素(可选操作)。
代码演示:
java
// 引入 ArrayList 和 Iterator 类
import java.util.ArrayList;
import java.util.Iterator;
public class RunoobTest {
public static void main(String[] args) {
// 创建集合
ArrayList<String> sites = new ArrayList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
sites.add("Zhihu");
// 获取迭代器
Iterator<String> it = sites.iterator();
// 输出集合中的所有元素
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
2.2 自定义迭代器
有一个需求就是普通数据也想用迭代器,那必须得自己封装...
封装迭代器:
java
package demo;
import java.util.Iterator;
public class MyDataStructure implements Iterable<Integer> {
private int arr[];
public MyDataStructure(int[] arr) {
this.arr = arr;
}
@Override
public Iterator<Integer> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<Integer>{
private int index = 0;
@Override
public boolean hasNext() {
return index < arr.length;
}
@Override
public Integer next() {
return arr[index++];
}
}
}
使用迭代器:
java
package demo;
import java.util.Iterator;
public class TestIterator {
public static void main(String[] args) {
int arr[] = new int[]{11,22,33};
MyDataStructure mds = new MyDataStructure(arr);
// 增强for循环
// for (int num : mds) {
// System.out.println(num);
// }
Iterator<Integer> iterator = mds.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
注意:由于增强For循环也是迭代器实现的,因此也可以使用增强For循环。。。