文章目录
- java程序运行流程
- JDK、JRE、JVM的关系
- java数据类型
- 基本数据类型
- 基本数据类型之间的转换
- 引用数据类型
- 数组
- 类
- Java Bean
- 重写 equals和hashcode
- 接口
- 接口与类之间的关系
- 内置包装类
- 装箱和拆箱
- 包装类的应用
- Object类
- toString()
- equals()方法
- getClass() 方法
- 接收任意引用类型的对象
- Integer类
- Float类
- 字符串类
- String类常用API
- 集合
- 迭代器iterator
- Collection
- List
- List实现ArrayList
- List实现LinkedList
- ListIterator
- List实现vector
- List实现Stack
- Queue
- Deque
- ArrayDeque
- PriorityQueue
- Set
- HashSet
- LinkedHashSet
- TreeSet
- 双列集合Map
- Map的4种遍历方式
- Map实现HashMap
- Map实现LinkedHashMap
- Map实现TreeMap
java程序运行流程
源代码.java
-> 经过编译器javac.exe
编译为java字节码 源代码.class
-> 编译完成的代码可以在 java虚拟机JVM
中运行。
JDK、JRE、JVM的关系
-
JDK:(java开发工具箱)包含JAVA编译器工具和JRE。常见的java编辑工具有:
- javac.exe:将java源码编译为java.class字节码的工具
- java.exe:Java解释器,启动 JVM(Java虚拟机),将 .class 文件一行一行地解释成机器指令执行。(由 Java 虚拟机对字节码进行解释和执行)
-
JRE(java运行环境):它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。
-
JVM:java虚拟机,它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作,jvm 是 Java 能够跨平台的核心。
所以三者的关系是JDK(java开发工具箱)包含JRE(java运行环境),JRE包含JVM(java虚拟机)
java数据类型
基本数据类型
Java中定义了四类/八种基本数据类型
- 布尔型 boolean:只允许赋值
true
或false
- 字符型 char:java字符采用Unicode编码,每个字符占用两个字节,16位。
- 整数型---- byte, short, int, long,占用字节数和可表示范围依次翻倍
- 浮点数型---- float, double
整数类型byte、short、int、long占用的空间倍数上升。
默认的小数类型为double,如果要给浮点数类型赋值,需要在小数后边+f或F
表示浮点数。
基本数据类型之间的转换
自动类型转换:表示数据范围从小到大。
强制类型转换:如果是浮点数类型转为整数会去掉小数部分,丢失精度;
如果是整数类型之间或者浮点数类型之间由高范围转为低范围,会直接丢弃高位部分,导致原本的超出范围的正数可能出现负数
。
引用数据类型
引用数据类型建立在基本数据类型的基础上,包括数组、类和接口。
引用类型还有一种特殊的 null 类型。在实际开发中,程序员可以忽略 null 类型,假定 null 只是引用类型的一个特殊直接量。
注意:空引用(null)只能被转换成引用类型,不能转换成基本类型,因此不要把一个 null 值赋给基本数据类型的变量。
数组
长度固定,存放同一种数据类型的连续存储空间。
类
类是具有相同属性和服务的一组对象的集合
。为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和服务(功能)两个主要部分。
Java Bean
在java编程中,在编写一个具体的类时通常会包含:
- 一个默认的无参构建器,一个有参构造器
- 需要被序列化并且实现了序列化接口,一般是重写toString,将属性展示出
- 可能有一系列的可读写属性,并且一般是private的
- 有一系列的get、set方法
符合上边条件的类称为一个java bean。
java bean最大的特征是私有的属性,其作用也就是把一组数据组合成一个特殊的类便于传输。 例如:将所需要的物品放到箱子指定的格子当中,然后快递员将打包好的箱子发出去。这个箱子就是 Java Bean。
重写 equals和hashcode
equals()定义在Object类中,其底层是通过 ==来比较,也就是说通过比较两个对象的内存地址是否相同来判断是否是同一个对象。
很多对象都会重写equals这个方法,因为我们实际上想要的结果是比较两个对象的内容是否一致。
例如String类中就重写了equals:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
hashcode是由对象的地址计算得到的哈希码,这个hashcode值决定了对象存放在哈希表中的位置。
常见的散列表实现方式:用数组作为哈希函数的输出域,输入值经过哈希函数计算后得到哈希值。然后根据哈希值,在数组种找到对应的存储单元。当发生冲突时,对应的存储单元以链表的形式保存冲突的数据(链地址法)。
hashcode可以起到一定的判重作用,即hashcode值不一样,对象一定不一样。但hashcode一样的对象,可能也不相同,因为有哈希冲突!!!
按照重写equals的思路,我们判断两个对象是否一致,是比较他们的内容是否一致,那么根据地址计算的hashcode,也是不符合要求的!!!
因此重写equals()方法,必须同时重写hashcode()方法。
例如在下边这个类中,重写的equals,是在比较类的三个属性size、color、index是否相同,而重写的hashcode,是根据类的三个属性size、color、index计算得到的hashcode。
public class Card {
private String size;
private String color;
private int index;
public Card(){
}
public Card(String size, String color, int index) {
this.size = size;
this.color = color;
this.index = index;
}
@Override
public String toString() {
return size +
","+ color;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Card card = (Card) o;
return index == card.index && Objects.equals(size, card.size) && Objects.equals(color, card.color);
}
@Override
public int hashCode() {
return Objects.hash(size, color, index);
}
}
接口
接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合(接口里面都是抽象方法)
,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法,当然接口中也可以定义不需要实现的常量或者static方法。
接口是为了突破java单继承的局限性而来的。
接口与类之间的关系
-
类与类
- 类和类之间是继承关系,而且只允许单根继承,子类继承父类后,可以使用父类除封装之外的所有功能。
- 子类可以修改父类的原有功能,会发生方法重写。
- 方法重写需要满足方法头要相同,方法要用@Override标识。
- 重写改变的是方法体,是把子类的方法体改成新的业务
-
接口与接口之间
- 接口也可以继承
- 子接口可以使用父接口的所有功能。
- 接口是为了突破java单继承的局限性而来的。
- 接口里没有构造方法,没有变量只有常量,jdk1.8后可以有default或者static的普通方法。
- 接口可以多继承,多实现,还可以在继承的同时多实现(先继承再实现)。
-
类与接口之间
- 类和接口是实现关系,并且允许多实现
- 当实现类实现了接口后,需要重写接口里的所有方法,否则就包含 抽象方法,变成了一个抽象类。
内置包装类
在 Java 的设计中提倡一种思想,即一切皆对象。但是从数据类型的划分中,我们知道 Java 中的数据类型分为基本数据类型和引用数据类型,但是基本数据类型怎么能够称为对象呢?于是 Java 为每种基本数据类型分别设计了对应的类,称之为包装类(Wrapper Classes)
装箱和拆箱
基本数据类型转换为包装类的过程称为装箱,例如把 int 包装成 Integer 类的对象;包装类变为基本数据类型的过程称为拆箱,例如把 Integer 类的对象重新简化为 int。
Java 1.5 版本之前必须手动拆箱装箱,之后可以自动拆箱装箱
,也就是在进行基本数据类型和对应的包装类转换时,系统将自动进行装箱及拆箱操作,不用在进行手工操作,为开发者提供了更多的方便。例如:
public class Demo {
public static void main(String[] args) {
int m = 500;
Integer obj = m; // 自动装箱
int n = obj; // 自动拆箱
System.out.println("n = " + n);
Integer obj1 = 500;
System.out.println("obj等价于obj1返回结果为" + obj.equals(obj1));
}
}
包装类的应用
1) 实现 int 和 Integer 的相互转换
可以通过 Integer 类的构造方法将 int 装箱,通过 Integer 类的 intValue 方法将 Integer 拆箱。
public class Demo {
public static void main(String[] args) {
int m = 500;
Integer obj = new Integer(m); // 手动装箱
int n = obj.intValue(); // 手动拆箱
System.out.println("n = " + n);
Integer obj1 = new Integer(500);
System.out.println("obj等价于obj1的返回结果为" + obj.equals(obj1));
}
}
2) 将字符串转换为数值类型
① Integer 类(String 转 int 型)
int parseInt(String s);
② Float 类(String 转 float 型)
float parseFloat(String s)
注意:使用以上两种方法时,字符串中的数据必须由数字组成,否则转换时会出现程序错误。
public class Demo {
public static void main(String[] args) {
String str1 = "30";
String str2 = "30.3";
// 将字符串变为int型
int x = Integer.parseInt(str1);
// 将字符串变为float型
float f = Float.parseFloat(str2);
System.out.println("x = " + x + ";f = " + f);
}
}
3) 将整数转换为字符串
Integer 类有一个静态的 toString() 方法,可以将整数转换为字符串。例如:
public class Demo {
public static void main(String[] args) {
int m = 500;
String s = Integer.toString(m);
System.out.println("s = " + s);
}
}
Object类
Object 是 Java 类库中的一个特殊类,也是所有类的父类。也就是说,Java 允许把任何类型的对象赋给 Object 类型的变量。
当一个类被定义后,如果没有指定继承的父类,那么默认父类就是 Object 类
由于 Java 所有的类都是 Object 类的子类,所以任何 Java 对象都可以调用 Object 类的方法。常见的方法如表 所示。其中,toString()、equals() 方法和 getClass() 方法在 Java 程序中比较常用。
toString()
Object 类的 toString() 方法返回
“运行时类名@十六进制哈希码”格式的字符串
但很多类都重写了 Object 类的 toString() 方法
,用于返回可以表述该对象信息的字符串。
// 定义Demo类,实际上继承Object类
class Demo {
}
public class ObjectDemo01 {
public static void main(String[] args) {
Demo d = new Demo(); // 实例化Demo对象
System.out.println("不加toString()输出:" + d);
System.out.println("加上toString()输出:" + d.toString());
}
}
不加toString()输出:Demo@15db9742
加上toString()输出:Demo@15db9742
从程序的运行结果可以清楚的发现,加和不加 toString() 的最终输出结果是一样的,也就是说对象输出时一定会调用 Object 类中的 toString() 方法打印内容
equals()方法
在前面学习字符串比较时,曾经介绍过两种比较方法,分别是==
运算符和 equals()
方法.
==
运算符是比较两个引用变量是否指向同一个实例- equals() 方法是比较两个对象的内容是否相等,通常字符串的比较只是关心内容是否相等。
getClass() 方法
getClass() 方法返回对象所属的类,是一个 Class 对象。通过 Class 对象可以获取该类的各种信息,包括类名、父类以及它所实现接口的名字等。
接收任意引用类型的对象
既然 Object 类是所有对象的父类,则所有的对象都可以向 Object 进行转换,在这其中也包含了数组和接口类型,即一切的引用数据类型都可以使用 Object 进行接收。
interface A {
public String getInfo();
}
class B implements A {
public String getInfo() {
return "Hello World!!!";
}
}
public class ObjectDemo04 {
public static void main(String[] args) {
// 为接口实例化
A a = new B();
// 对象向上转型
Object obj = a;
// 对象向下转型
A x = (A) obj;
System.out.println(x.getInfo());
}
}
通过以上代码可以发现,虽然接口不能继承一个类,但是依然是 Object 类的子类,因为接口本身是引用数据类型,所以可以进行向上转型操作。
Integer类
Float类
字符串类
- String表示字符串类型,属于 引用数据类型,不属于基本数据类型。
- 在java中随便使用 双引号括起来 的都是String对象。
- java中规定,双引号括起来的字符串,是 不可变 的,也就是说"abc"自出生到最终死亡,不可变,不能变成"abcd",也不能变成"ab"
- 在JDK当中双引号括起来的字符串,例如:“abc” "def"都是直接存储在“方法区”的“字符串常量池”当中的。这是
因为字符串在实际的开发中使用太频繁。为了执行效率,所以把字符串放到了方法区的字符串常量池当中。
例子1
public class StringTest01 {
public static void main(String[] args) {
// 这两行代码表示底层创建了3个字符串对象,都在字符串常量池当中。
String s1 = "abcdef";
String s2 = "abcdef" + "xy";
// 分析:这是使用new的方式创建的字符串对象。这个代码中的"xy"是从哪里来的?
// 凡是双引号括起来的都在字符串常量池中有一份。
// new对象的时候一定在堆内存当中开辟空间。
String s3 = new String("xy");
}
}
例子2
public class StringTest02 {
public static void main(String[] args) {
String s1 = "hello";
// "hello"是存储在方法区的字符串常量池当中
// 所以这个"hello"不会新建。(因为这个对象已经存在了!)
String s2 = "hello";
// == 双等号比较的是变量中保存的内存地址
System.out.println(s1 == s2); // true
String x = new String("xyz");
String y = new String("xyz");
// == 双等号比较的是变量中保存的内存地址
System.out.println(x == y); //false
}
}
由此可见:字符串对象之间的比较不能使用“== ”。应该调用String类的equals方法
String类常用API
集合
集合区分于数组,它的大小是可变的。
数组可以存储基本数据类型和引用数据类型,而集合中只能存储引用数据类型(存储的为对象的内存地址)
单列集合Collection:Queue、List、Set
双列集合:Map
迭代器iterator
迭代器属于设计模式之一,迭代器模式提供了一种方法顺序访问一个聚合对象中的各个元素,而不保留对象的内部表示。iterator仅用于遍历集合,本身不存放对象
所有实现了Collection接口的集合类都有一个iterator(),方法,返回一个实现了iterator接口的对象。
iterator接口包含三个方法:
- hasNext():迭代器当前位置下一个是否还有元素
- next():移动迭代器到下一个位置,返回所在位置的元素
- remove:删除当前迭代器当前位置的元素
Collection接口返回的迭代器起初位置
在集合首元素的前一个位置
.
在idea中使用itit
快捷输出迭代器遍历方式:
LinkedList<Integer> L1 = new LinkedList<>();
Collections.addAll(L1,111,222,333);
Iterator<Integer> it = L1.iterator();
while (it.hasNext()) {
Integer next = it.next();
it.remove();
System.out.println(next);
}
System.out.println(L1);
上边代码会依次遍历集合,打印。
它的原理如下图:
1、起初it在集合首元素的前一个位置:
2、调用next,移动迭代器到下一个位置,并返回当前位置的元素。
3、调用迭代器的remove,删除当前位置的元素,使用迭代器删除时会自动更新迭代器和集合(后边的元素前移)
4、当迭代器移动到最后一个位置时,因为没有下一个元素,退出循环。
5、循环外部打印,结果为空列表
Collection
单列集合的顶层接口,既然是接口就不能直接使用,需要通过实现类!
List
List表示一串有序的集合,和Collection接口含义不同的是List突出有序的含义。
List其实比Collection多了添加方法add和addAll查找方法get,indexOf,set等方法,并且支持index下标操作。
Collection和List最大的区别就是Collection是无序的,不支持索引操作,而List是有序的,支持索引操作
List实现ArrayList
ArrayList底层就是一个Object[]数组,ArrayList底层数组默认初始化容量为10;
1、jdk1.8 中 ArrayList 底层先创建⼀个⻓度为 0 的数组
2、当第⼀次添加元素(调⽤ add() ⽅法)时,会初始化为⼀个⻓度为 10 的数组
当 ArrayList 中的容量使⽤完之后,则需要对容量进⾏扩容:
1、ArrayList 容ᰁ使⽤完后,会“⾃动”创建容量更⼤的数组,并将原数组中所有元素拷⻉过去,这会导致效率降低。
2、优化:可以使⽤构造⽅法 ArrayList (int capacity) 或 ensureCapacity(int capacity) 提供⼀个初始化容量,避免刚开始就⼀直扩容,造成效率较低。
ArrayList 构造⽅法:
- ArrayList():创建⼀个初始化容ᰁ为 10 的空列表
- ArrayList(int initialCapacity):创建⼀个指定初始化容ᰁ为 initialCapacity 的空列表
- ArrayList(Collection<? extends E> c):创建⼀个包含指定集合中所有元素的列表
ArrayList 特点:
- 优点:
- 向 ArrayList 末尾添加元素(add() ⽅法)时,效率较⾼
- 查询效率⾼
- 缺点:
- 扩容会造成效率较低(可以通过指定初始化容量,在⼀定程度上对其进⾏改善)
- 另外数组⽆法存储⼤数据量(因为很难找到⼀块很⼤的连续的内存空间)
- 向 ArrayList 中间添加元素(add(int index)),需要移动元素,效率较低 ,如果增/删操作较多,可考虑改⽤链表
List实现LinkedList
LinkedList 特点
数据结构: LinkedList 底层是⼀个双向链表
优点: 增/删效率⾼
缺点: 查询效率较低
LinkedList 也有下标,但是内存不⼀定是连续的(类似C++重载[]符号,将循位置访问模拟为循秩访问)
LinkedList 可以调⽤ get(int index) ⽅法,返回链表中第 index 个元素
但是,每次查找都要从头结点开始遍历
ListIterator
ListIterator:列表迭代器,iterator接口的子接口。有如下功能:
- 允许双向移动迭代器,即可以向前或者向后两个方向遍历List
- 在遍历时修改List元素的值
- 遍历时获取迭代器当前位置
- LinkedList.add()方法只能将数据添加到链表末尾,如果要将对象添加到链表的中间位置,就需要使用 ListIterator
ListIterator的接口定义如下:
ListIterator 有两种获取方式
- List.listIterator(),位置在起始位置前一个
- List.listIterator(int location),位置在索引位置的前一个。
使用ListIterator可以在指定位置插入元素:
因为使用迭代器操作集合(插入或者删除)时,迭代器和集合会更新,所以不会出错。
private static void listIteratorDemo2(List list) {
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
// 获取列表迭代器
ListIterator it=list.listIterator();
while(it.hasNext())
{
Object obj=it.next();
if(obj.equals("abc2"))
{
it.add("hello");
}
System.out.println(obj);
}
System.out.println(list);
}
List实现vector
vector 是线程安全的,它是操作方法都被synchronize修饰。
vector实际开发使用较少
List实现Stack
Stack也是List接口的实现类之一,和Vector一样,因为性能原因,更主要在开发过程中很少用到栈这种数据结构,不过栈在计算机底层是一种非常重要的数据结构。
栈的最大特点就是后进先出。
Stack<String> strings = new Stack<>();
strings.push("aaa");
strings.push("bbb");
strings.push("ccc");
System.err.println(strings.pop());
复制代码
Queue
Queue是继承自Collection的接口,是一种先进先出的数据结构。
package java.util;
public interface Queue<E> extends Collection<E> {
//集合中插入元素
boolean add(E e);
//队列中插入元素
boolean offer(E e);
//移除元素,当集合为空,抛出异常
E remove();
//移除队列头部元素并返回,如果为空,返回null
E poll();
//查询集合第一个元素,如果为空,抛出异常
E element();
//查询队列中第一个元素,如果为空,返回null
E peek();
}
Deque
Deque是继承自Queue的接口,相比Queue,支持两端弹出和压入元素。
Deque英文全称是Double ended queue,也就是俗称的双端队列。就是说对于这个队列容器,既可以从头部插入也可以从尾部插入,既可以从头部获取,也可以从尾部获取。
ArrayDeque
ArrayDeque是Java中基于数组实现的双端队列,在Java中Deque的实现有LinkedList和ArrayDeque,正如它两的名字就标志了它们的不同,LinkedList是基于双向链表实现的,而ArrayDeque是基于数组实现的。
PriorityQueue
PriorityQueue是Java中唯一一个Queue接口的直接实现,如其名字所示,优先队列,其内部支持按照一定的规则对内部元素进行排序。
Set
如果说List对集合加了有序性的化,那么Set就是对集合加上了唯一性。
Set的特点在于不可存放重复元素,没有索引,不能使用普通的for循环遍历,需要使用迭代器,或者for each遍历。
Set集合的功能基本上与Collection的API是一致的。
HashSet
无序、不重复、无索引
HashSet集合,底层采用哈希表存储数据
- jdk8之前,底层使用数组+链表组成
- jdk8之后,底层采用数组+链表+红黑树组成
数组区间作为哈希函数的输出域,当发生哈希冲突时,采用链表的形式,将新的元素挂在数组的下方。当冲突很多时,就可能导致链表过长,导致查询速度变慢,因此jdk8后边的版本中,当链表长度超过8时,会将链表重新组织为红黑树。
LinkedHashSet
有序、不重复、无索引
底层数据结构依然是哈希表,只是每个元素额外多了一个双链表机制,记录存储的顺序。
TreeSet
可排序(自定义比较规则)、不重复、无索引
TreeSet集合是一定要排序的,底层是基于红黑树的数据结构。
如果要使用TreeSet存放自定义数据类型,一定要自定义比较规则。
自定义比较规则的方式:
- 方式1:让自定义类(如学生类)实现Comparable接口,重写compareTo方法,来定定制比较规则
- 方式2:TreeSet集合含有参数构造器,可以设置Comparable接口对应的比较器对象,来定制比较规则。
即:因为TreeSet要保证唯一性,因此当比较规则返回0的时候,认为元素重复,会将重复元素去除,然而实际应用中,可能比较规则只针对于对象的某一个属性,对象的某一个属性相同,不能认为,对象重复,因此,注意过滤掉返回0的情况。
双列集合Map
Map是一种键值对的结构,就是常说的Key-Value结构,一个Map就是很多这样K-V键值对组成的,一个K-V结构我们将其称作Entry,在Java里,Map是用的非常多的一种数据结构。
Map的4种遍历方式
1)在 for 循环中使用 entries 实现 Map 的遍历(最常见和最常用的)。
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
for (Map.Entry<String, String> entry : map.entrySet()) {
String mapKey = entry.getKey();
String mapValue = entry.getValue();
System.out.println(mapKey + ":" + mapValue);
}
}
2)使用 for-each 循环遍历 key 或者 values,一般适用于只需要 Map 中的 key 或者 value 时使用。性能上比 entrySet 较好。
Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
// 打印键集合
for (String key : map.keySet()) {
System.out.println(key);
}
// 打印值集合
for (String value : map.values()) {
System.out.println(value);
}
3)使用迭代器(Iterator)遍历
Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Entry<String, String> entry = entries.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + ":" + value);
}
4)通过键找值遍历,这种方式的效率比较低,因为本身从键取值是耗时的操作,不推荐使用。
for(String key : map.keySet()){
String value = map.get(key);
System.out.println(key+":"+value);
}
Map实现HashMap
HashMap是Java中最常用K-V容器,采用了哈希的方式进行实现,HashMap中存储的是一个又一个Key-Value的键值对,我们将其称作Entry,HashMap对Entry进行了扩展(称作Node),使其成为链表或者树的结构使其存储在HashMap的容器里(是一个数组)。
Map实现LinkedHashMap
Map实现TreeMap
- TreeSet/TreeMap 是⾃平衡⼆叉树
- TreeSet/TreeMap 迭代器采⽤的是中序遍历⽅式
- ⽆序,不可重复,但是可排序。
排序规则定义与TreeSet类似。