计算机语言的分类:
为什么要学习Java语言?
Java是使用最广泛,且用法简单的语言。
Java是一门强类型的语言(对数据类型的划分很精细)。
Java有非常严谨的异常处理机制。
Java提供了对于大数据的基础性支持。hadoop的底层使用java编写。
1995年由SUN公司推出,Java之父:詹姆斯·高斯林,SUN公司在2009年被Oracle收购。
开跨两多面:开源跨平台,多线程多态面向对象。
Library->Java->JavaVirtualMachines->jdk1.8.0_301.jdk
目的:在任何路径下都可以使用JDK提供的工具。例如java,javac。
vim ~/.bash_profile
添加变量名,变量值。
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home
export CLASSPAHT=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$JAVA_HOME/bin:$PATH
验证:
在terminal中输入:java -version
程序的开发:编写->编译->执行
javac HelloWorld.java
会产生一个HelloWorld.class文件
java HelloWorld #由于设计规范,此处无需后缀,实际执行.class文件
显示运行结果
在project选项中设置SDK和Project Language Level
Modules->Java->Moduel SDK设置
关键字:被java赋予了特殊含义的单词。
public class 类名{
public static void main(String[] args){
System.out.println(这里的内容随便写);
}
}
在程序的执行中,其值不能发生改变的量。
在程序的执行中,其值可以在某个范围内发生改变的量。从本质上讲,变量其实是内存中的一小块区域,例如:
java中要求每个变量每次只能保存一个数据,而且必须要明确保存数据的数据类型。
变量的值在某一个范围内发生变化。(通过数据类型来限制)
能找到这一块区域。(通过变量名实现)
区域内必须有数据。(初始化值)
形式:
数据类型 变量名 = 初始化值;
java是一门强类型语言。其对数据的划分很精细。
整型 | ||
---|---|---|
byte | 1个字节 | -128~127 |
short | 2个字节 | |
int | 4个字节 | |
long | 8个字节 | |
浮点型 | ||
float | 4个字节 | |
double | 8个字节 | |
字符型 | ||
char | 2个字节 | |
布尔型 | ||
boolean | 1个字节 |
方式一:直接初始化变量
int a = 10;
方式二:先声明,后赋值
int a;
a = 10;
标识符就是用来给类,接口,方法,变量,包等起名字的规则。
hust.edu.cn -> cn.edu.hust
小类型转大类型,会自动提升为大类型,运算结果时大类型。
double a = 10;
手动将大类型转换成小类型,运算结果是小类型
short s = 1;
s = (short)(s+1);
System.out.println(s);//结果为2,short类型
Java中对于常量,有常量优化机制。
byte, short, char -> int -> long -> float -> double
运算符:连接常量和变量的符号。
表达式:用运算符把常量或者变量连接起来符合java语法的式子。
不同运算符连接的表达式体现的是不同类型的表达式。
整数相除,结果还是整数。除非有浮点型参与。
加号运算符在字符串之间表示连接。
System.out.println("Hello" + 5 + 5)//Hello55
System.out.println("Hello" + (5 + 5))//Hello10
System.out.println(5 + 5 + "Hello" + 5 + 5)//10Hello55
i++,先运算后自增
++i,先自增后运算
++和–都隐含了强制类型转换。
byte a = 1;
a = a++;//包含了强制类型转换,等价于下行代码
a = (byte)(a + 1);
基本的赋值运算符:=
拓展的赋值运算符:+=, -=, *=, /=, %=
a += b; 等价于 a = a + b;
赋值运算符的左边不能为常量。
拓展的赋值运算符默认包含了强制转换。
byte a = 1;
a += 1;//包含了强制类型转换,等价于下行代码
a = byte(a + 1);
==, !=, >, <, <=, >=,返回true或者false
&, |, !,^异或
符号 | 作用 | 说明 |
---|---|---|
&& | 短路与 | 作用和&相同,但是有短路效果,前面如果出现false,后面不执行 |
|| | 短路与 | 作用和|相同,但是有短路效果,前面如果出现true,后面不执行 |
(关系表达式)? 表达式1 : 表达式2;
首先执行关系表达式,如果true执行表达式1,如果false执行表达式2
写在类上面,package下面
import java.util.Scanner;
Scanner sc = new Scanner(System.in);
int a = sc.nextlnt();
某些代码在满足特定条件下才会执行;有些代码在满足特定条件下重复执行。这些都需要用到流程控制语句。
代码按照从上往下从左往右的顺序依次逐行执行。是java程序的默认结构。
System.out.println(10+10+"Hello"+10+10);
20Hello1010
if语句一般用为范围的判断
if(条件语句){
//语句体;
}
if(条件语句){
//语句体1;
}else{
//语句体2;
}
if(条件语句1){
//语句体1
}else if(条件语句2){
//语句体2
}else if(条件语句3){
//语句体3
}
···
else{
//语句体
}
一般用于固定值判断
switch(表达式){ //表达式的取值类型:byte, short, int, char, JDK5以后可以是枚举,JDK7以后可以是String
case 值1: //case后面跟的是要和表达式进行比较的值
语句体1; //语句体可以使一条或者多条语句
break; //break表示中断,结束switch语句
case 值2:
语句体2;
break;
case 值3:
语句体3;
break;
···
default: //default表示所有情况都不匹配时执行,写在任何位置都是最后才执行
语句体n;
break;
}
case穿透:
在switch语句中,如果case后面不写break,将出现case穿透现象,也就是不会在判断下一个case的值,直接向后运行,知道遇到break,或者整体switch结束。
switch(month){
case 1:
case 2:
case 12:
String season = "Winter";
break;
···
}
思考题:break关键字可以省略吗?
答:最后一个分支的break可以省略,其他分支如果省略break可能对结果有影响。
for(初始化条件;判断条件;控制条件){
//循环体
}
先执行初始化条件,然后执行判断条件,查看结果true还是false,如果true执行循环体,执行控制条件。
初始化条件;
while(判断条件){
//循环体;
//控制条件;
}
初始化条件;
do{
循环体;
控制条件;
}while(判断条件);
用得最多的是for循环的嵌套
for(初始条件;判断条件;控制条件){ //外循环
for(初始条件;判断条件;控制条件){ //内循环
//循环体
}
}
Math类似Scanner,是Java提供好的API(Application Programming Interface),内部提供了产生随机数的工程。
int num = (int)(Math.random()*100 + 1) //获取1到100之间的随机数
如果需要同时存储多个同类型的数据,我们可以通过数组来实现。
数组就是用来存储多个同类型元素的容器。
//格式一
数据类型[] 数组名 = new 数据类型[长度]; //数组名小驼峰命名法,推荐使用
//格式二
数据类型 数组名[] = new 数据类型[长度];
//上述两种定义方式只是写法不同,并无其他区别。格式一应用的多一些。
//格式一
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3, ...};
//格式二 语法糖(简化语法的格式)
数据类型[] 数组名 = {元素1, 元素2, 元素3, ...};
初始化数组不可同时给定长度和初值。
int[] arr = new int[3];
解释:
类型 | 元素默认值 |
---|---|
int | 0 |
double | 0.0 |
boolean | false |
String | null |
内存是计算机中的重要元件,也是临时存储区域,作用是运行程序。我们编写的程序是存放在硬盘当中的,在硬盘中的程序是不会运行的,必须放入内存中才能运行,运行完毕后会清空内存。
即:Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。为例提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
JVM的内存划分
public class ArrayDemo{
public static void main (String[] args){
int[] arr = new int[3];
System.out.println(arr[0]);
System.out.println(arr);//打印的是存储地址
}
}
public class StoragePic {
public static void main(String[] args) {
int[] arr = new int[3];
arr[0] = 11;
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr);
int[] arr2 = {1, 2};
arr2[1] = 22;
System.out.println(arr2[1]);
System.out.println(arr2);
}
}
产生原因:访问了数组中不存在的索引
解决方案:访问数组中存在的索引即可
产生原因:访问了空对象
解决方案:对对象赋具体的值
int[] arr = {11, 22, 33, 44, 55};
for(int i : arr) System.out.println(i);
public class ArrayDemo02 {
public static void main(String[] args) {
int[] arr = {55, 22, 66,77, 33, 44, 55};
int max = arr[0];
for(int i : arr){
if (i >= max) max = i;
}
System.out.println(max);
}
}
public class ReverseArray {
public static void main(String[] args) {
int[] arr = {11, 22, 33, 44, 55};
int temp;
for (int i = 0; i <= arr.length/2; i++){
temp = arr[i];
arr[i] = arr[arr.length-1-i];
arr[arr.length-1-i] = temp;
}
for(int i : arr) System.out.println(i);
}
}
方法也叫函数,是将具有独立功能的代码块组织成为一个整体,使其具有特殊功能的代码集。
修饰符 返回值的数据类型 方法名(参数类型 参数名1, 参数类型 参数名2){//这里可以写多个参数
//方法体;
return 具体的返回值;
}
public static void 方法名(){
//方法体;
}
public static void printNum(int number){
System.out.println("The number is "+ number);
}
public static String getSchool(){
return "Huazhong University of Science and Technology";
}
public static int getAdd(int a, int b){
return a+b;
}
同一个类中,出现方法名相同,但是参数列表不同的两个或多个的方法时,称为方法重载,方法重载与方法的修饰符、返回值的数据类型无关。
参数列表不同分为两种类型:
形参如果是基本类型,形参的改变不影响实参。
public class demo01 {
public static void main(String[] args) {
int num = 100;
change(num);
System.out.println(num);//打印结果仍然是100
}
public static void change(int num){
num = 200;
}
}
引用类型作为参数时,形参的改变直接影响实参(String类型除外,String类型当作形参使用时,用法和基本类型一致,后续API详解)
public class demo02 {
public static void main(String[] args) {
int[] arr = {10, 20, 30};
System.out.println(arr[1]);//打印结果为20
change(arr);
System.out.println(arr[1]);//打印结果为200
}
public static void change(int[] arr){
arr[1] = 200;
}
}
步骤:
当需求单一且简单,一步一步操作没有问题效率也挺高。但是如果需求更改、增多,把这些步骤和功能进行封装。封装时根据不同的功能进行不同的封装,功能类似的用一个类封装在一起。使用的时候找到对应的类。
思想特点:
面试题:什么是面向对象?
思路:概述,思想特点,举例,总结
我们是如何表示现实世界的事物的呢?
举例
类:学生;大象
对象:张三,23;北京动物园叫图图的大象
属性(成员变量):年龄,性别,专业;年龄,性别,品种
行为(成员方法):学习、吃饭、睡觉;吃饭、睡觉、迁徙
定义类其实就是定义类的成员(成员变量和成员方法)
public class student {
//属性(成员变量):姓名,年龄,性别
String name;
int age;
String sex;
//行为(成员方法):学习,吃饭,睡觉
public void study(){
System.out.println("学习");
}
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
类的使用就是使用类中定义的成员(成员变量和成员方法)
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.name);//null(默认值)
System.out.println(s.age); //0(默认值)
System.out.println(s.sex); //null(默认值)
s.name = "Nick";
s.age = 22;
s.sex = "Male";
System.out.println(s.name);//Nick
System.out.println(s.age); //22
System.out.println(s.sex); //Male
}
}
上述的代码中,我们可以任意的设置属性的值,包括我们可以设置一些非法值,例如把年龄设置成负数,这样做程序就容易出问题,针对这种情况,我们可以通过private关键字来优化。
private是一个关键字,也是访问权限修饰符的一种,它可以用来修饰类的成员(成员变量和成员方法)
被private修饰的内容只能在本类中直接使用。
public class Phone{
private int age;
public int getAge(){
return age;
}
public void setAge(int a){
age = a;
}
}
封装是面向对象编程思想的三大特征之一,所谓的封装就是隐藏对象的属性和细节,仅对外提供一个公共的访问方式。
面向对象的三大特征:封装、继承、多态
隐藏:private修饰符
公共访问方式:public成员方法
原则
this表示本类当前对象的引用,即谁调用,this就代表谁
用来解决局部变量和成员变量重名的问题
使用变量的原则:就近原则(局部位置有就使用,没有就去本类的成员位置找,有就使用,没有就报错)(不严谨,还会去父类找)
package cn.edu.hust.demo;
public class Student {
int x = 1;
public void method(){
int x = 2;
System.out.println(this.x);
System.out.println(x);
}
}
package cn.edu.hust.demo;
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
s.method();//1,2 this.x等价于s.x
s.x = 10;
s.method();//10,2
}
}
加入this关键字后标准额度JavaBean类的编写方法
public class Student {
private String name;
private int age;
public void setName(String name){
this.name = name;
}
public String getName(){
// return this.name;//能不多写就不写
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void study(){
System.out.println("好好学习,天天向上!");
}
public void sleep(){
System.out.println("为了保证精力,请好好休息!");
}
}
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
s.setAge(22);
s.setName("Nick");
System.out.println(s.getAge());
System.out.println(s.getName());
s.study();
s.sleep();
}
}
构造方法是用来创建对象的,可以给对象的各个成员变量赋值。
构造方法就是用来快速对对象的各个属性赋值的。
格式
public 类名(参数类型 参数名1,参数类型 参数名2){
//给对象的各个属性复制
}
注意事项
public class Student{
private String name;
private int name;
public Student(String name, int age){
this.name = name;
this.age = age;
}
}
public class StudentTest{
String name = "Nick";
int age = 22;
Student s = new Student(name, age);
}
JavaBean也可以称为实体类,其对象可以用于在程序中封装数据。
标准JavaBean必须满足如下要求:
多个类中存在相同属性和行为时,将这些内容抽取到单独的一个类中,那么这个类就无需再定义这些属性和行为了,只要继承那个类即可。这个关系就叫继承。
有了继承以后,我们在定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义新的成员。
格式
public class 类A extends 类B{ //A是子类,B是父类
}
好处:
提高了代码的复用型
提高了代码的可维护性
让类与类之间产生关系,是多态的前提
弊端:
使用变量遵循“就近原则”,局部位置有就使用,没有就去本类的成员位置查找,有就使用,没有就去父类的成员位置查找,有就使用,没有就报错。
super的用法和this很像
子类中所有的构造方法默认都会访问父类的空参构造
问:为什么这样设计?
答:用于子类对象访问父类数据前,对父类数据进行初始化
即每一个构造方法的第一行都有一个:super()
如果父类没有空参构造怎么办?
1)可以通过super(参数)的方式访问父类的带参构造
2)可以this(参数)的方式访问本类的其他构造
3)但是这样做比较麻烦,所以建议永远手动给出空参构造和带参构造。子类的空参构造访问父类的空参构造super();(可以省略不写,默认已有)。子类的带参构造访问父类的带参构造super(成员变量);
所有的类都间接继承自Object类,Object类是所有类的父类
public class Father {
public Father(int age){
System.out.println("Father带参构造");
}
public Father(){
System.out.println("Father空参构造");
}
}
public class Son extends Father {
public Son(int age){
super(age);
System.out.println("Son带参构造");
}
public Son(){
System.out.println("Son空参构造");
}
}
public class FatherTest {
public static void main(String[] args) {
Son s = new Son(10);
Son s2 = new Son();
}
}
打印结果为:
Father带参构造
Son带参构造
Father空参构造
Son空参构造
继承关系中,调用成员方法时,遵循“就近原则”,本类中有就直接使用,本类中没有就去父类查找。有就调用,没有就报错。
public class Father {
public Father(){
System.out.println("Father空参构造");
}
public Father(int age){
System.out.println("Father带参构造");
}
public void show(){
System.out.println("Father show");
}
}
public class Son extends Father{
public Son(){
System.out.println("Son空参构造");
}
public Son(int age){
super(age);
System.out.println("Son带参构造");
}
public void method(){
System.out.println("Son method");
}
}
public class FamilyTest {
public static void main(String[] args) {
Son s = new Son();
s.method();
s.show();
}
}
打印结果为:
Father空参构造
Son空参构造
Son method
Father show
子类中出现和父类一模一样的方法时,称为方法重写,要求返回值的数据类型也必须一样
应用场景
当子类需要使用父类的功能,而功能主题又有自己独有需求的时候,就可以考虑重写父类中的方法了,这样既沿袭了父类的功能,又定义了子类特有的内容
注意事项
private < 默认
public class Phone {
public Phone(){}
public Phone(String name){}
public void call(String name){
System.out.println("Calling "+name);
}
}
public class NewPhone extends Phone{
public NewPhone(){}
public NewPhone(String name){
super();
}
@Override
public void call(String name){
super.call(name);
System.out.println("播放彩铃");
}
}
public class PhoneTest {
public static void main(String[] args) {
Phone p = new Phone();
NewPhone np = new NewPhone();
p.call("Nick");
np.call("Nick");
}
}
打印结果:
Calling Nick
Calling Nick
播放彩铃
多态指的是同一个事物(或者对象)在不同时刻表现出来的不同状态。
前提条件
Person p = new Teacher();
原因:方法有重写,而变量没有。
public class Person{
int age = 50;
public void eat(){
System.out.println("Eat.")
}
}
public class Student extends Person{//条件之一:要有继承
int age = 20;
@Override//条件之二:要有方法重写
public void eat(){
System.out.println("Eat hamburgers.")
}
}
好处:提高了程序的拓展性
坏处:父类引用不能访问子类的特有功能
处理方法:向下转型
假设Student.class增加一个成员方法
public void study(){
System.out.println("Study.")
}
那么主类需要这样写
public class PersonTest{
public static void main(String[] args){
Person p = new Student();
//p.study() //会报错,因为p是Person类的引用,没有study()成员方法
Student s = (Student)p;
/*
向下转型,类似于强制类型转换
强制类型转换:int a = (int)10.3;
*/
s.study();
}
}
向上转型:
Person p = new Student();
向下转型:
Student s = (Student)p;
bug:
Person p = new Student();
Student s = (Student)p;//正确
Teacher t = (Teacher)p;//报错
实际开发中,方法的形参一般都是父类型,这样可以接受其任意子类对象,然后通过多态调用成员方法的规则调用指定子类对象的方法。
public class Animal {
public void eat(){
System.out.println("eat");
}
}
public class Cat extends Animal{
@Override
public void eat(){
System.out.println("cats eat fish");
}
}
public class Dog extends Animal{
@Override
public void eat(){
System.out.println("dogs eat meat");
}
}
public class AnimalTest {
public static void main(String[] args) {
Animal a1 = new Cat();
Animal a2 = new Dog();
show(a1);
show(a2);
}
public static void show(Animal a){
a.eat();
}
}
cats eat fish
dogs eat meat
格式
引用(对象) instanceof 数据类型
返回true或者false
作用
用来判断前边的引用(对象)是否是后边的数据类型
假设Cat类和Dog类分别增加成员方法:
public void catchMouse(){
System.out.println("catch mouse")
}
public void guardHome(){
System.out.println("guard home")
}
主类show方法改变为:
public static void show(Animal a){
a.eat();
if(a instanceof Cat){
Cat c = (Cat) a;
c.catchMouse();
}
else if(a instanceof Dog){
Dog d = (Dog) a;
d.guardHome();
}
}
打印结果为:
cats eat fish
catch mouse
Dogs eat meat
guard home
final是一个关键字,表示最终的意思,可以修饰类,成员变量,成员方法
final常见面试题
final修饰的变量是一个常量,那如果修饰的是基本类型或者引用类型的数据,有区别吗?
如果final修饰的是基本类型的常量,数值不能发生变化
如果final修饰的是引用类型的常量,地址值不能发生变化,但是该对象的属性值可以发生变化
final, finally, finalize这三个关键字之间的区别是什么?
static是一个关键字,表示静态的意思,可以修饰成员变量和成员方法。
static 修饰成员变量表示该成员变量只在内存中存储一份,可以共享和修改。
随着类的加载而加载
优先于对象存在
被static修饰的内容,能被该类下所有的对象共享
这也是我们判断是否用静态关键字的条件
可以通过**类名.**的形式调用
示例
public class Student {
String name;
static String school; //static修饰成员变量
public void show(){
System.out.println("Name:"+name+" School:"+school);
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
s1.name = "Nick";
Student.school = "HUST"; //通过“类名.”调用,所有对象共享
s2.name = "Eva";
s1.show();
s2.show();
}
}
Name:Nick School:HUST
Name:Eva School:HUST
回想前面我们的猫狗案例,提取出了一个动物类,这个时候我们可以通过Animal an = new Animal();来创建动物对象,但其实这是不对的。因为我说动物,你并不知道我说的是什么动物。只有看到了具体的动物,才知道是什么动物。所以说,动物本身不是一个具体的事物,而是一个抽象的事物。只有真正的猫、狗才是动物。同理,我们也可以对象,不同的动物吃的东西是不一样的,所以我们不应该在动物类中给出具体的体现,而是一个声明即可。
在Java中,一个没有方法体的方法定义为抽象方法(用abstract修饰),而类中如果有抽象方法,该类必须定义为抽象类(用abstract修饰)。
public abstract class Animal {
public abstract void eat();
public void sleep(){
System.out.println("Sleep");
}
}
public class Cat extends Animal{
@Override
public void eat(){
System.out.println("Eat");
}
}
public class Test {
public static void main(String[] args) {
//Animal an = new Animal(); 抽象类不能实例化
Animal an = new Cat();
an.eat();
an.sleep();
}
}
抽象类中可以有变量、常量、构造方法、抽象方法和非抽象方法(比普通类多了抽象方法)
思考:既然抽象类不能实例化,那要构造方法有什么用?
答:用于子类对象访问父类数据前,对父类数据进行初始化
继续回到我们的猫狗案例,我们想狗的一般行为就是看门,猫的一般行为是捉老鼠。但是有些狗猫可以训练出钻火圈、跳高、做算术等行为。这些行为并不是狗猫天生就具备的。所以这些额外的行为定义到动物类中就不合适,也不合适定义到猫类或者狗类中,因为只有部分猫和部分狗具备。
所以,为了体现事物功能的拓展性,Java就提供了接口来定义这些额外行为,并不给出具体实现,将来哪些猫狗需要被训练,只需要这部分猫狗把这些额外行为实现即可。
public abstract class Animal {
public abstract void eat();
}
public interface InterA{//父接口
public abstract void methodA();
}
public interface InterB extends InterA{//子接口
public abstract void methodB();
}
public class Cat extends Animal implements InterA, InterB{//由于InterB接口继承了InterA接口,所以这里只写InterB接口也可以
@Override//父类抽象方法要重写
public void eat(){
System.out.println("Eat fish.");
}
@Override//接口抽象方法要重写
public void methodA(){
System.out.println("A");
}
@Override//接口抽象方法要重写
public void methodB(){
System.out.println("B");
}
}
public class InterfaceTest {
public static void main(String[] args) {
//Animal an = new Animal();//**报错**。Animal是抽象类,不能实例化
Animal an = new Cat();
an.eat();//访问成员方法,编译看左(Animal类有eat方法),运行看右(Cat类有eat方法)
//an.methodA(); //**报错**。编译看左(Animal类没有methodA方法)
Cat c = (Cat) an;
c.methodB();
c.methodA();
}
}
包(package)就是文件夹,用来对类进行分类管理的,例如
package 包名1.包名2.包名3
package语句必须是程序的第一条可执行代码
package在一个程序中只能有一个
Package必须放在java文件的第一行有效代码处,且一个.java文件只有一个package
import表示导包,可以写多个,必须写在package和class之间
class表示在定义类,写在import后,建议只写一个
不同包下的类之间的访问,都需要加包的全路径。解决方法是导包。
import 包名;
权限修饰符是用来修饰类、成员变量、构造方法、成员方法的,不同的权限修饰符对应的功能不同
public | protected | 默认 | private | |
---|---|---|---|---|
同一个类中 | √ | √ | √ | √ |
同一个包中的子类或者其他类 | √ | √ | √ | |
不同包中的子类 | √ | √ | ||
不同包中的其他类(无关类) | √ |
集合是用来存储多个同类型数据的容器,其长度可变。
长度固定用数组,长度可变用集合。
集合分为Collection和Map。
集合的顶层都是接口。其中Collection是单列集合的顶层接口,Map接口是双列集合的顶层接口。
学习思路:学顶层,用底层。
顶层:是整个继承体系的共性内容
底层:具体的体现和实现
Collection:单列集合的顶层接口
因为Collection是接口,不能用new关键字来创建。通过多态的方式创建其子类对象。
Collection<String> list = new ArrayList<String>();
概述:指某种特定的数据类型
作用:泛型一般是结合集合来一起使用的,用来限定集合中存储什么类型的数据
注意事项:1.前后泛型必须保持一致。2.后边的泛型可以不写,这是jdk1.7的新特性,叫菱形泛型。
例如
List<String> list = new ArrayList<>();
public class Demo01 {
public static void main(String[] args) {
Collection<String> list = new ArrayList<String>();
//或者Collection list = new ArrayList();
list.add("Hello");
list.add("World");
System.out.println(list);
}
}
public boolean add(E e)添加元素//E指泛型
public boolean remove(Object obj)从集合中移除指定的元素
public void clear()清空集合
public boolean contains(Object obj)判断集合中是否包含指定的元素
public boolean isEmpty()判断集合是否为空
public int size()获取集合的长度
范型的小技巧:范型一般用字母E,T,K,V表示。E:Element;T:Type;Key Value
Iterator 是依赖集合存在,集合遍历的方式
public Iterator<E> iterator()//根据集合对象,获取其对应的迭代器对象
因为Iterrator是接口,所以在这里返回的是Iterator接口的子类对象。
迭代器是依赖于集合而存在的。
Iterator迭代器中的方法
public boolean hasNext()//判断迭代器中是否有下个元素
public E next()//获取迭代器中的下一个元素
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("Hello");
coll.add("World");
Iterator<String> it = coll.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
打印结果
Hello
World
总结:
集合遍历的使用分为四大步三小步
1.创建集合对象
2.创建元素对象
3.把元素对象添加到集合对象中
4.遍历集合
1.根据集合对象获取其对应的爹太器
2.判断迭代器中是否有下一个
3.有则获取
有序集合(俗称序列),该界面的用户可以精准控制列表中每个元素的插入位置,且用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。与Collection集合不同,列表通常允许重复的元素。
简单记忆:有序,可重复,元素有索引
void add(int i, E element)//使index为i的位置插入element
E set(int i, E element) //修改指定索引处的元素指定的值,并返回修改前的元素
E remove(int i)//根据索引移除指定元素,并返回此元素
E get(int i)//根据索引,获得相应的元素
列表迭代器指的是ListIterator接口,它是List集合特有的迭代器
该迭代器继承了Iterator迭代器,所以我们可以直接使用
ListIterator<E> listIterator()
ListIterator<E> listIterator(int index)
成员方法:
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Java");
//通过ListIterator正向遍历
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
//逆向遍历
while(it.hasPrevious()){
String s = it.previous();
System.out.println(s);
}
当使用普通迭代器(Iterator)遍历集合的同时,又往集合中添加了元素,就会报并发修改异常。
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Java");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
if("World".equals(s)){
it.add("JavaSE");//会报错并发修改异常
}
}
产生原因:
迭代器是依赖于集合而存在,当判断成功后,集合中添加了新的元素,而迭代器不知道,所以报错了。(计数器会计算剩下的元素个数,所以add报错而remove不报错)本质是,迭代器遍历集合中的元素是,不能使用集合对象去修改集合中的元素。
解决方案:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Java");
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String s = it.next();
if("World".equals(s)){
it.add("JavaSE");
}
}
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Java");
for(int i=0; i<list.size(); i++){
if("World".equals(list.get(i))){
list.add(i+1, "JavaSE");
}
}
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Hello");
list.add("World");
list.add("Java");
Iterator<String> it = list.iterator();//普通迭代器
while(it.hasNext()){
if("World".equals(it.next())){
list.add("JavaSE");//并发
}
}
面试题
什么是并发修改异常,怎么产生的,怎么解决
ConcurrentModificationException
增强for是JDK1.5的新特性,它是用来简化数组和Collection集合的遍历。
for(元素的数据类型 变量名:数组或Collection集合中的每一个元素)
for(int a: array)
for(Student l:list)
注意事项:数组或Collection集合不能为null;增强for的底层是迭代器
for(String elem : list){ }
数组:查询快,增删慢
链表:查询慢,增删快
List集合是一个接口,它的常用子类有两个ArrayList和LinkedList。
ArrayList的底层数据结构是数组,查询快,增删慢
LinkedList的底层数据结构是链表,查询慢,增删快
相同点:都是有序可重复的
void addFirst(E element)
void addLast(E element)
往链表的开头或末尾插入制定元素
E removeFirst()
E removeLast()
删除链表的开头或末尾元素,并返回这个元素
E getFirst()
E getLast()
返回链表的开头或末尾元素
Set集合是Collection集合的子体系,它的元素特点是无序、唯一
Set<String> set = new HashSet<>();//实际使用不用Set,直接用HashSet
set.add("Hello");
set.add("Java");
set.add("Hello");
for(String s : set){
System.out.println(s);
}
Java
Hello
HashSet<String> set = new HashSet<>();
set.add("Hello");
set.add("Java");
set.add("World");
for(String s : set){//遍历法1:增强for循环
System.out.println(s);
}
Iterator<String> it = set.iterator();
while(it.hasNext()){//遍历法2:普通迭代器
System.out.println(it.next());
}
可变参数又称参数个数可变,它用作方法的形参出现,那么方法参数个数就是可变的了。
格式
修饰符 返回值类型 方法名(数据类型... 变量名){}
public static void main(String[] args) {
System.out.println(getSum(1,2,3));
}
public static int getSum(int... nums){
return Arrays.stream(nums).sum();//可变参数的底层是数组
}
public interface Map<K,V>{}
特点:
public V put (K key, V value)