JAVA
1.JAVA概述
1.什么是编程语言?
人与计算机沟通交流的一种特殊语言
詹姆斯·高斯林 java语言创始者
配置环境变量:
jdk
java开发工具包
jre
java运行时环境
jvm
java虚拟机
java程序执行过程:
1.先编写java源代码 javac.exe
2.对java源代码进行编译 javac
源文件名称
编译完成后会生成当前源文件所生成的字节码文件.class
字节码文件是开发人员看不懂的, 只有jvm
虚拟机才能识别
3.运行java代码 java.exe
java 类名
java语言是支持跨平台的:用java编写的源代码可以在不同的系统 上运行
使用JDK开发完成的java程序,交给JRE去运行
2.java入门
java语法规范
1.类和接口要声名一个标识符用大驼峰命名法
2.变量和方法要声名标识符用小驼峰命名法
注释:
#快捷键 ctrl + shift + /
单行注释://注释文字
多行注释:/* 注释文字 */
文档注释:/** 注释文字 */
class Hello{
public static void main(String[] args){
System.out.println("Hello world");
}
}
//java最基本的Hello world
常量
在程序执行的过程中其值不可以发生改变
在方法外面定义,全大写,单词与单词之间用下划线分隔开
java是一门强类型语言,对于每一种数据都定义了明确的具体数据类型
在内存总分配了不同大小的内存空间
数据类型
1.基本数据类型:
a.数值型:整数类型(byte,short,int,long
)
浮点类型(float,double
)
b.字符型:(char
)
c.布尔型:(boolean
)
2.引用数据类型:
a.类: (class
)
b.接口:(interface
)
c.数组: ([ ]
)
double d = 3.14;
float f = 3.14f;
char c = 'q';
String s = "hello world";
//String是引用数据类型
boolean b = false;
有几个数据类型关键字就开辟几份内存空间
强制转换:
double d =3.14;
int i = (int)d;
运算符+ - * / %(取余)
num++;
//后置++,先取值再赋值
--num;
//前置--,先赋值再取值
d1 += d2 -> d1 = d1 + d2;
"Hello" instanceof String //结果true,检查是否是类的对象
//与运算 两者同时为真结果为真
if (1 > 2 & 1 == 2);
//不短路 false & false
if (1 > 2 && 1 == 2);
//短路运算 false && 不运算
//或运算 两者只要有一者为真,结果为真
if (1 < 2 | 2 < 3);
if (1 <2 || 2 < 3);
//非运算,取反
(1 > 2) -> false !(1 > 2) -> true
变量
成员变量:实例变量,静态变量
在JVM虚拟机的栈内存开辟空间,但静态变量在方法区空间开辟内存
局部变量:方法体内部的变量(包括参数中的变量)
JAVA对类的定义:
java本身是面向对象的语言,如果需要使用java去开发一个软件**
那么,软件中的每一个模块都是一个个体,每一个个体会拆分为很多单元,每个单元本身
是一个模糊的概念,针对这样的一个概念,我们一般用类去定义,然后在具体执行的时候,
需要让模块之间相互协作,正常的去模拟现实生活中场景的时候,就需要创建这个类
所对应的实例
包
将鼠标光标放置于未被导入的包名称上,按住alt + Enter 可以快速import包 (IntelliJ IDEA支持)
创建类
new关键字 创建类的实例
创建类实例的对象
对象.调用方法
方法
方法是完成特定功能的代码块
格式:
修饰符 返回值类型 方法名(参数类型 参数名1, 参数类型 参数名2..){
函数体;
return 返回值;
}
package com.tly.test;
/**
* 第一个简单java小程序,输入三个浮点数,求最小值
*/
import java.util.Scanner;
public class Test02 {
public static void main(String[] args) {
//创建Test02的对象
Test02 getMinNum = new Test02();
//接收输入的浮点数据
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个浮点数据类型:");
//创建一个浮点型变量接收返回的参数
double num1 = scanner.nextDouble();
System.out.println("请输入第二个浮点数据类型:");
double num2 = scanner.nextDouble();
System.out.println("请输入第三个浮点数据类型:");
double num3 = scanner.nextDouble();
//调用对象的方法并传输传输
double numMin = getMinNum.getMinNum(num1, num2, num3);
System.out.println("最小值为:"+numMin);
}
//创建类的getMinNum方法
public double getMinNum(double num1,double num2, double num3){
double[] doubles = new double[3];
//将数据传输给列表
doubles[0] = num1;
doubles[1] = num2;
doubles[2] = num3;
//创建中间值
double numMin = num1;
for (int i = 0; i < doubles.length; i++) {
if(doubles[i] < numMin){
numMin = doubles[i];
}
}
return numMin;
}
}
方法特性:
递归:
使用递归会严重损耗系统资源,因此要尽量避免使用递归
方法的重载:
在一个区间内,java语法中运行方法名称相同,参数列表不同的语法格式,这种现象我们称之为重载
数组
new创建的容器存放在堆内存中
数组是一个引用数据类型
可以通过new的方式去创建数组
在不赋值的情况下,取到的是默认值,整型默认值是0
//创建数组
int[] ints = new int[10];
//给数组赋值
ints[0] = 100;
//遍历数组
for (int i = 0; i < ints.length; i++){
System.out.println(ints[i]);
}
//创建字符串数组
String[] strings = new String[5];
//赋值
String[0] = "hello";
//foreach遍历
//for (数据的数据类型 遍历的每一项 : 容器对象)
for (String str : strings){
System.out.println(str);
}
数组操作常见的小问题:
数组索引越界
-ArrayIndexOutOfBoundsException
-访问到了数组中的不存在的索引时发生。
空指针异常
-NullPointerException
-数组引用没有指向实体,却在操作实体中的元素时。
字符串
字符串常用函数
//定义字符串
String str = "hello world";
//str.equals() 判断字符是否完全匹配
System.out.println(str.equals("hello world"));
//输出true
//str.contains()判断是否包含某一字符串
System.out.println(str.contains("hello"));
//输出true
//str.substring()切割字符串 int beginIndex
System.out.println(str.substring(6));
//输出world 切割[0,6)
System.out.println(str.substring(6,11));
//输出world 切割(6,11]
//str.toLowerCase()将大写字母转成小写
System.out.println(str.toLowerCase());
//str.length() 获取字符串字符个数
System.out.println(str.length());
//输出11
//str.endsWith()判断字符串是否以某个字符串结尾
System.out.println(str.endsWith("d"));
//输出true
//str.split()字符串的分割
String[] strings = str.split(" ");
//遍历数组
for (String s:strings){
System.out.println(s);
}
//输出hello
//输出world
//将字符串转为字符数组
char[] chars = str.toCharArray();
for (String c:chars){
System.out.println(c);
}
//str.replace()字符串替换方法
String str4 = str.replace("world","java");
System.out.println(str4);
//输出hello java
3.面向对象(万物皆对象)
面向对象特征:
类与对象关系
以后我们开发项目的时候,要不断的去声明类,在类中需要去封装属性和行为,
但是针对某一些属性,我们需要将它保护起来,不希望直接对外界提供访问。
所以我们会将这些属性用private关键字进行修饰,private代表的是私有的访问权限修饰符。
被private修饰的属性只能在本类中才能访问到
alt + INS键快速选择创建类的get方法与set方法
public class User {
private String userName;
private String address;
private String idCard;
private String phoneID;
//提供一个idCard对应的get方法,没有参数,但是有返回值
public String getIdCard(){
return idCard;
}
//提供一个idCard对应的set方法,有参数,但是没有返回值
public void setIdCard(String idCard){
this.idCard = idCard;
}
}
JVM内存分配
栈内存:主函数,定义的普通函数,基本数据类型,数据地址(引用数据类型的地址),所有的方法
堆内存:new出来的对象,应用数据类型的值
方法区内存:方法区内存中一般存放的是代码片段(java源文件编译之后的字节码文件),以及常量池
静态方法存在栈内存中,静态变量存在于方法区内存中
定义在类中的实例变量一定是在堆内存中开辟的空间,因为new出来的部分一定存在于堆内存中。
在JVM内存中,所有的方法,无论是静态方法还是普通方法(实例方法),都是在栈中分配空间。
局部变量和实例变量的区别:
局部变量是存在于方法体内部的变量(包含了参数),局部变量如果是基本数据类型,则直接在栈中开辟空间,如果是引用数据类型,变量声明部分在栈中,实例创建部分在堆中,如果是实例遍历,所有的内存都会在堆中去开辟。
常量池:
常量池一般存储字符串常量
Java中通过 == ,如果比较的是基本数据类型,比的是值,如果比较的是引用数据类型,则比较的是内存地址
private关键字:私有的
是一个权限修饰符
可以修饰成员(成员变量和成员方法)
被private修饰的成员只能在本类中才能访问
this关键词
this:代表所在类的对象的引用(方法被哪个对象调用,this就代表那个对象)
this.变量名 访问的是本类中的实例变量
构造方法(Constructor)
作用:给对象的数据进行初始化
方法名与类名相同,没有返回值类型,连void也没,没有具体的返回值
如果不提供构造方法,那么系统会给出默认的构造方法,如果提供了构造方法(不管是否带参数),系统将不再提供默认无参数的构造方法
构造方法可以重载
构造方法的作用:
无参构造:实例化对象,但是实例化对象后,所有的属性都是没有被赋值的
有参构造:实例化对象,可以在创建对象时,就给实例的属性赋值
静态关键字 static
可以修饰方法,可以修饰属性,但是不能修饰类
被静态关键字修饰的属性和方法,会成为一个类级别的元素,会随着类的加载而加载
局部变量不能被static修饰
被static修饰的变量我们称之为静态变量
静态变量会随着类的加载而加载,在JVM解析字节码文件时,该变量的内存空间就会被开辟出来
静态变量的内存空间会被开辟在该方法区中
静态方法:
public class StaticTest01{
private static String name; //静态变量
public static void main(String[] args){
//调用静态方法
StaticTest01.getMethod();
StaticTest01 staticTest01 = new StaticTest01();
//调用实例方法
staticTest01.getMethod02();
}
//声明一个静态方法
public static void getMethod(){
System.out.println("我是一个静态方法!");
}
public void getMethod02(){
System.out.println("我是一个静态方法!");
}
}
静态代码块:
在类加载的时候将会被执行的语句体,而且只加载一次(在主函数执行前执行)
static{
System.out.println("static 执行了!!");
}
一个类基本类的标准代码写法
- 类
- 成员变量
- 构造方法
- 无参构造方法
- 带参构造方法
- 成员方法
- getXxx()
- setXxx()
- 给成员变量赋值的方法
- 无参构造方法+setXxx()
- 带参构造方法
继承
概述:多个类中存在相同的属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
通过extends关键词可以实现类与类的继承
class 子类名 extends 父类名 {}
单独的这个类称为父类,基类,或者超类;这多个类可以称为子类或者派生类
继承可以让子类去继承父类,这样子类会继承父类所有的属性和方法,在java中是不允许有多继承的
Object类:是java中的祖宗类,当一个类在代码层面没有去继承任何的父类,那么这个类
默认会继承Object类
继承的重写(覆盖):
子类继承父类以后,可以获取到父类所有的方法(包含被private修饰的私有方法,但是访问不到)
如果父类方法的功能不能够满足子类的需求,这个时候,子类可以针对这个方法进行重写
父类中私有方法不能被重写
子类重写父类方法时,访问权限不能更低
父类静态方法,子类也必须通过静态方法进行重写
继承的好处:
提高了代码的复用性
- 多个相同的成员可以放到同一个类中
提高了代码的维护性
- 如果功能的代码需要修改,修改一处即可
让类与类之间产生了关系,是多态的前提
- 其实这也是继承的一个弊端:类的耦合性很强
不要为了部分功能而去继承 ,继承中类之间的体现是“is a”的关系
继承中成员变量的关系
在子类方法中访问一个变量:
- 首先在子类局部范围找
- 然后在子类成员范围找
- 最后在父类成员范围找(不能访问父类局部范围)
- 如果还是没有就报错
Super关键字
子类继承父类之后,子类可以通过super关键字去指向父类的元素(属性,普通方法,构造方法)
如果指向父类的构造用super() 如果指向子类的构造用 this()
public class Student extends Person{
public Student(){
//指向的是父类的无参构造,不写则默认存在
super();
}
@Override
public void sleep(){
//指向父类的普通方法,只能存在于子类构造中
super.sleep();
}
public void getMethod(){
//this指向当前类实例的属性
System.out.println(this.name);
//super指向父类的属性
System.out.println(super.name);
}
}
继承中的构造方法的关系
子类中所有的构造方法默认都会访问父类中的空参数的构造方法
因为子类会继承父类中的数据,可能还会使用父类的数据
所以,子类初始化之前,一定要先完成父类数据的初始化
每一个构造方法的第一条语句默认都是:super()
方法重写与方法重载的区别?方法重载能改变返回值类型吗?
答:方法重写建立在类的继承关系上,是对父类同名方法的覆盖。方法重载是一个类中,多个同名方法,但是参数列表不同。方法重载可以改变返回值类型。
多态
某一事物,在不同时刻表现出来的不同状态
多态是面向对象特征之一,也是最重要的一个特征
多态思想:
为了解除类与类之间的耦合(模块与模块之间的耦合),不能面向具体编程,而要面向抽象编程(面向接口编程)
首先多态必须要有父子关系的体现(继承,实现),是在不同时刻展现出不同的状态
父类的引用接收子类对象(向上转型),或者是向下转型都是多态的体现
多态在程序执行时分为编译阶段和运行阶段:
普通方法:
编译阶段看左边:编译时要检查调用的方法在父类中是否存在
运行阶段看右边:因为我们实际new出来的对象是子类对象,所以最终执行的方法也是子类的方法
静态方法:
静态方法可以用类名去调用,类名.静态方法名()
静态方法也可以用实例去调用,对象引用.静态方法名(),本质也是通过类名去调用
静态方法是一个类级别的方法,所以它已经脱离了实例,和对象没有关系
编译看左边,运行看左边
class PloyTest01{
public static void main(String[] args){
Cat cat = new Cat();
//通过多态机制来创建猫的实例
Animal animal = new Cat();
animal.eat();
//引用数据类型的强转就是多态的一种,向上转型
Animal animal1 = (Animal)cat;
//面试题:
Animal animal = new Rabbit();
//判断animal1是否属于Cat,属于则进行强转
if (animal1 instanceof Cat){
//向下转型
Cat cat1 = (Cat)animal1;
cat1.eat();
}
}
}
多态前提和体现
有继承关系
有方法重写
有父类引用指向子类对象
多态在开发中的作用
主任喂养宠物的案例:
有一个主人是master喜欢喂养宠物,喂养的宠物有很多种(猫,狗,兔子,鸟),然后随着时间的推 移,主人会喂养越来越多的宠物,针对于这样的场景我们通过java程序去模拟一下(体现出多态)
重载算不算多态?不算多态(没有体现父子关系)
结论:如果按照传统的方式进行开发(不使用多态):会发现程序进行迭代的时候,需要去修改主人类(需要在原有的代码继承上进行修改),这样做违背了设计模式中的开辟原则(OCP 对扩展开发,对修改关闭):在写程序的时候,尽量避免在原有的代码之上进行修改,这样的话修改的部分容易对别的类(或者说别的模块造成影响)
public class Master {
//这是一个喂养的方法,用父类的引用去接收子类的对象
public void feed(Pet pet){
pet.eat();
}
}
//创建抽象类Pet
public class Pet {
public void eat(){
System.out.println("所有的宠物都有吃的特性");
}
}
public class Cat extends Pet{
public void eat(){
System.out.println("猫吃鱼");
}
}
public class Rabbit extends Pet{
public void eat(){
System.out.println("兔子爱吃萝卜");
}
}
public class Dog extends Pet{
public void eat(){
System.out.println("狗爱啃骨头");
}
}
public class MasterTest {
public static void main(String[] args) {
Master master = new Master();
master.feed(new Cat());
}
}
抽象类
一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类
抽象类是java中最顶级的一种表现形式,接下来就是类,再接下来就是对象
抽象类用abstract修饰 > 类class > new 对象
我们如果想创建对象的话,只能通过类class去创建,抽象类是不能创建对象的
因为抽象类的下一级是类
格式:abstract class 类名{}
- 抽象类指的是class(本身就是抽象的事物)往上继续抽象就得到了抽象类
- 抽象类中可以有普通方法,也可以有抽象方法
- 抽象方法一定存在于抽象类中
- 抽象类是不能实例化对象的,因为抽象类的下一级是类class
- 抽象类不能实例化对象,他的作用是用来被子类继承的
- 抽象类中可以定义普通的成员变量、静态变量、静态方法
- 抽象方法是没有方法体的,不用提供具体的实现,当子类去继承了抽象类之后,抽象类中的所有抽象方法必须要在子类中重写
public abstract class Animal{
private String name;
private static int age;
//定义抽象方法,抽象方法是没有方法体的
//为什么抽象类没有方法体?因为抽象方法本身就是模糊的,不用去提供具体的实现
public abstract String eat();
public void move(){
System.out.println("动物会移动")
}
}
public class Cat extends Animal{
@Override
public String eat(){
return "猫喜欢吃鱼";
}
}
public class AbstractTest01{
public static void main(String[] args){
Animal animal = new Cat();
System.out.println(animal.eat());
}
}
抽象类中是有构造方法的(抽象类不可以实例化对象,但是却存在构造函数)
抽象类父类构造函数是通过子类构造去调用的,通过super关键字调用父类构造
public class Cat extends Animal{
public Cat(String name,int age){
super(name,age);
}
}
final关键字
final关键字:可以修饰类,成员变量,局部变量,方法(静态方法,实例方法)
被final修饰的类不能用来被继承
被final修饰的属性,如果是基本数据类型,一定在定义变量的时候就初始化,如果是引用数据类型,在程序运行
过程中,变量的引用不能指向别的内存地址
被final修饰的方法在子类继承父类之后,不能被重写
用final关键字能不能修饰抽象类?不可以,因为抽象类的作用就是用来被子类继承的,但是被final修饰的类是无法被子类继承的,final和abstract是不能同时出现的
如何去声明一个常量?
常量是程序运行过程中不可以发生改变的量
对于那些无论创建多少次对象,值都亘古不变的数据,我们把该属性定义为常量
public static final 属性类型 常量名称(所有字母都大写,单词之间用_分隔) = 值
用static修饰常量是因为想让改常量变成一个类级别的,然后随着类加载,该常量就初始化
避免多次开辟空间,浪费系统资源
用final修饰常量,是因为不希望在程序运行时,该量的值发生变化,final代表最终的
接口
接口:接口是一种规范,在进行程序设计的时候,都是基于接口进行开发
因为在声明方法的时候,我们需要这个方法存在多变性,而且可以随着业务的改变,针对这个接口做具体的实现
构造方法:没有,因为接口主要是扩展功能的,而没有具体存在
接口的定义:
- 接口其实就是一个特殊的抽象类,接口中的方法只能有抽象方法
- 通过interface来定义接口
- 接口也是一种引用数据类型,在类加载的时候会生成字节码文件
格式: interface 接口名 {}
package com.os467.interface_test;
//声明一个接口
public interface MyInterface01{
int I = 100;
Object O = new Object();
static String ADDRESS = "南京"
//以上都是常量,忽略了public static final,不是变量
public static final String NAME = "Tom";
//可以省略public static final
//抽象方法
//public abstract void getMethod();接口中下面的代码等同于这行代码
void getMethod();
}
- 接口中的成员:抽象方法,常量
- 由于接口中所有的方法都是抽象的,所以关键字public abstract 是可以省略掉的
- 在接口中是允许有多继承的
- 接口不能实例化对象,它的作用是用来被子类实现的
接口在开发中的作用:
设计模式中提到在设计程序时,我们要面向接口开发,接口+多态,可以实现程序的可拓展性,灵活性
implements关键字:
作用:子类实现接口的关键字(类似extends关键字)
接口中定义的抽象方法,在子类实现之后一定要进行重写
格式: class 类名 implements 接口名{}
public class MyInterfaceImpl implements MyInterface01{
@Override
public void getMethod(){
System.out.println("子类实现接口之后重写的方法")
}
}
public class InterfaceTest01{
public static void main(String[] args){
//通过多态的方式来创建实例
MyInterface01 myInterface01 = new MyInterfaceImpl();
myInterface01.getMethod();
}
}
接口案例
package com.os467.interfaceTest.person;
public interface Message {
void getMessage();
}
--------------------------------------------------------
package com.os467.interfaceTest.person;
public class QQ implements Message{
@Override
public void getMessage() {
System.out.println("接收的是QQ消息");
}
}
--------------------------------------------------------
package com.os467.interfaceTest.person;
public class Email implements Message{
@Override
public void getMessage() {
System.out.println("接收的是电子邮件");
}
}
---------------------------------------------------------
package com.os467.interfaceTest.person;
public class QQ implements Message{
@Override
public void getMessage() {
System.out.println("接收的是QQ消息");
}
}
---------------------------------------------------------
package com.os467.interfaceTest.person;
public class Person {
//接收消息的方法
public void receive(Message message){
//调用接收消息的方法
message.getMessage();
}
}
----------------------------------------------------------
package com.os467.interfaceTest.person;
public class Test {
public static void main(String[] args) {
Person person = new Person();
//调用接收消息的方法
person.receive(new QQ());
person.receive(new WeChat());
person.receive(new Email());
}
}
按照多态的方式,由具体的子类实例化,这其实也是多态的一种,接口多态
类对多个接口方法的实现
//一个类的多接口实现,接口与接口之间用逗号隔开
public class MyImpl implements MyInterface01, Myinterface02{
@Override
public void getMethod(){
}
@Override
public String getMethod02(){
return null;
}
}
接口与接口之间的多继承
//继承接口02,接口01的所有抽象方法与常量
public interface MyInterface03 extends MyInterface02, MyInterface01(){
}
总结:抽象类与接口的区别:
抽象类有成员变量,以及抽象方法(没有方法体)与普通方法,拥有自己的构造函数,继承抽象类的子类需要重写抽象类中的所有抽象方法,抽象类没有实例对象,因为其下一级是类
抽象类被继承体现的是”is a“的关系,共性功能
接口可以说是特殊的抽象类,只是接口内只有抽象方法和常量,接口不具有构造函数,实现接口的类需要重写接口中的所有抽象方法,接口没有实例对象,它的作用是用来被子类实现的
接口被实现体现的是:”like a“的关系,扩展功能
类与类,类与接口,接口与接口之间的关系
类与类:
继承关系,只能单继承,但是可以多层继承
类与接口:
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
接口与接口之间:
继承关系,可以单继承,也可以多继承
接口和抽象类都可以作为一个方法的返回值,参数,也可以作为类的成员变量
public static void main(String[] args) {
public Myinterface02 getFun(){
//返回的是一个接口
return new MyImpl();
}
包
package只能存在工程的第一行
包其实就是文件夹
作用:对类进行分类管理
格式:package 包名;
多级包用”.“分开即可
访问权限修饰符区别
访问权限修饰符 | 本类 | 同一包下 | 子类(同一包下) | 子类(不同包下) | 不同包下非子类 |
---|---|---|---|---|---|
private | √ | × | × | × | × |
default(不写修饰符) | √ | √ | √ | × | × |
protected | √ | √ | √ | √ | × |
public | √ | √ | √ | √ | √ |
内部类
把类定义在其他类的内部,这个类就被称为内部类
内部类的访问特点:
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象
//创建内部类的实例,B是类A的内部类
A.B b = new A().new B();
//调用内部类中实例的方法
b.getMethod();
内部类中不允许写静态方法
匿名内部类
就是内部类的简化写法
前提:存在一个类或接口
这里的类可以是具体类也可以是抽象类
格式:
new 类名或者接口名( ){重写方法;}
本质:
创建一个父类所对应的匿名对象
是一个继承了
类或者实现了接口的子类匿名对象
//给接口创建一个匿名对象,可以把 new MyInterface(){}视为接口的匿名子类
MyInterface myInterface = new MyInterface(){
@Override
public double getSum(double num1, double num2){
return num1 + num2;
}
};
double sum = myInterface.getSum(100,200);
System.out.println("结果为"+sum);
//结果为 300.0
4.异常
当程序出现异常时,java会创建对应的异常对象,并且抛出(throw)
当异常抛出之后,会影响接下来代码的执行(打断)
Throwable类
在 Java 中,所有的异常都有一个共同的祖先类Throwable(可抛出)
Throwable有两个子类 Error 和 Exception
Error类
错误(Error): JVM系统内部错误,资源耗尽等严重情况
Error是Throwable的子类,用于指示合理的应用程序不应该试图捕获的严重问题
无法从代码层面解决此异常
Exception类
异常(Exception): 其它因编程错误或偶然的外在因素导致的一般性问题
Exception(异常):是程序本身可以处理的异常,Exception 类有一个重要的子类RuntimeException
常见异常:
RuntimeException
ArithmeticException:数学计算异常
NullPointerException:空指针异常
NegativeArraySizeException:数组索引越界异常
ClassNotFoundException:类文件未找到异常
ClassCastException:造型异常
IOException
FileNotFoundException:文件未找到异常
EOFException:读写文件尾异常
异常:
编译时异常:当JVM进行编译的时候就会出现报错,对于那些大概率会发生的异常,我们会定义成编译时异常,编译时异常要求开发人员必须对其进行处理
直接或间接继承了Exception类的异常就是编译时异常
运行时异常:程序在运行时,才会出现的异常,小概率会出现的异常,我们会定义成运行时异常,开发人员可以不用对其进行处理
直接或者是间接的继承了**RuntimeException
**类,该异常就是运行时异常
异常处理机制
1.通过throws关键字将异常往上抛
//throws关键字为方法添加方法体内可能出现的异常,如文件找不到异常,不添加则无法运行方法体中的代码
public static void main(String[] args) throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("D:\\test\\test.txt");
System.out.println(fileInputStream);
}
2.通过异常的捕获try{}catch{}语句块
异常处理的try - catch - finally语句
语句块机制:
try{
//可能会抛出特定异常的代码段
}catch(MyExceptionType myException){
//如果myException被抛出,则执行这段代码
}catch(Exception otherException){
//如果另外的异常otherException被抛出,则执行这段代码
}finally{
//用于资源回收,回收的资源一般是指会造成对系统资源浪费的对象
//无条件执行的语句!!一定会执行
}
/*
通过这种方式去处理异常,在捕获到异常之后,还是一样会创建该异常对象,但是该对象会
被catch语句块所捕获,具体用异常类来接收该异常对象,然后在catch语句块中可以针对
该异常进行相关的处理,程序会继续往下执行
*/
注意:用try{…}catch{…}捕捉了异常之后一定要对在catch{…}中对其进行处理,那怕是最简单的一句输出语句,或打印日志来显示整个调用流程
catch(Exception e){
//栈输入,打印日志
e.printStackTrace();
}
throws关键字
throws : 是方法可能抛出异常的声明
用在声明方法时,表示该方法可能要抛出异常,交给上层调用它的方法程序处理
通过throws关键字(写在方法后面的)把异常向上抛(抛给该方法的调用处)
不会影响程序正常运行
//throws后面为方法中可能出现的异常,如空指针异常
public void getMethod01() throws NullPointerException{
}
//抛出编译时异常
public FileInputStream getMethod02() throws FileNotFoundException{
return new FileInputStream("D:\\file\\demo.java")
}
自定义异常
java给我们提供了很多种异常,但是在做项目的时候,这些异常是不够用的,所以需要我们自定义异常
自定义异常方式:
- 直接声明一个类
- 如果想定义成编译时异常让该类去继承Exception
- 如果想定义成运行时异常,让该类去继承**
RuntimeException
** - 然后需要提供一个构造,通过super关键字,把异常信息告诉父类即可
public class ExceptionDemmo extends Exception{
//提供一个构造,把异常信息告诉父类
public ExceptionDemmo(String message){
super(message);
}
}
public class RuntimeExceptionDemmo extends RuntimeException{
//提供一个构造,把异常信息告诉父类
public RuntimeExceptionDemmo(String message){
super(message);
}
}
throw关键字
系统在遇到异常时会自动生成一个 throw new 异常类(); 来抛出异常
throw是语句抛出一个异常,一般是在方法体的内部,当程序出现某种逻辑错误时由程序员主动抛出某种特定类型的异常
public static void main(String[] args) throws Exception{
ExceptionDemo exceptionDemo = new ExceptionDemo("这是我提供的自定义异常信息");
throw exceptionDemo;
//也可写成throw new ExceptionDemmo("这是我提供的自定义异常信息");
}
public static void main(String[] args){
RuntimeExceptionDemo runtimeExceptionDemo = new RuntimeExceptionDemo("这是我提供的自定义异常信息");
throw exceptionDemo;
//也可写成throw new RuntimeExceptionDemmo("这是我提供的自定义异常信息");
}
字符串比较的方法:
空指针异常时以后我们写程序经常会遇见的一种异常,接收过来的引用为空,然后拿着该引用去调用方法
空指针异常的解决方案:我们要对接收过来的引用数据类型做非空校验
public boolean getEquals(String str1, String str2){
boolean b = false;
if (str1 == null || str == null){
System.out.println("接收的引用为空")
}else{
b = str1.equals(str2);//new NullPointerException();
return b;
}
return b;
}
5.集合
集合:
- 集合是容器,只能存放引用数据类型
- 每个集合的实现类底层都封装了一种数据结构
- 每个集合都可以去获取迭代器接口提供的迭代方法
JAVA中的自动装箱和自动拆箱:
java中所有的基本数据类型都有对应的封装类(引用数据类型),我们学习的集合是只能存放引用数据类型的容器。
int i = 100;
//创建一个集合对象
ArrayList arrayList = new ArrayList();
//添加元素的方法,自动装箱:int ---> Integer
arrayList.add(i);
for (int j = 0; j < arrayList.size(); j++){
//根据下标来取元素
System.out.println(arrayList.get(j));
}
Java中有自动装箱机制,会把基本类型转化为其对应的封装类:
- int → Integer
- double → Double
- float → Float
- char → Character
- byte → Byte
- short → Short
- long → Long
- boolean → Boolean
将引用数据类型转为基本数据类型就是自动拆箱
Collection
Iterator接口:Collection继承的超级接口, 只有一个返回迭代器的方法
Collection : 集合层次中的根接口,JDK没有提供这个接口直接的实现类
将一组对象以集合元素的形式组织到一起,在其子接口中分别实现不同的组织方法
//创建一个集合对象
Collection collection = new ArrayList();
//添加的是基本数据类型 会自动装箱
collection.add(1000);
//添加浮点型 自动装箱
collection.add(3.14);
//添加引用数据类型
collection.add(new Object());
System.out.println("集合的长度为"+collection.size());
//遍历Collection接口方法 遍历集合中的元素
//获取迭代器对象方法
Iterator iterator = collection.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
//判断集合中是否包含指定元素
System.out.println("是否包含元素?"collection.contains(1000));
//比较集合是否相等
System.out.println(collection.equals(collection1));
//返回哈希码值
System.out.println("hash码值为"+collection.hashCode());
//判断集合是否为空
System.out.println("集合是否为空?"+collection.isEmpty());
//根据指定元素,删除集合中所对应的元素
collection.remove(1000);
//返回集合元素个数
System.out.println("集合元素个数:"+collection.size());
//将集合转成数组
Object[] objects = collection.toArray();
for (Object object : objects){
System.out.println(object)
}
//清空集合中的元素
collection.clear();
Collection的两个子接口:
1.List接口:
有序集合的三种访问方式
是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式
public class CollectionTest01 {
public static void main(String[] args) {
List list1 = new ArrayList();
List list2 = new Vector();
list1.add(100);
list1.add(100);
list1.add("hello");
list1.add(new Object());
list1.add(list2);
//List接口提供按索引访问的方法
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
}
}
迭代器方法访问元素:
Iterator接口定义了对Collection类型对象中所含元素的遍历等增强处理功能
可以通过Collection接口中定义的iterator()方法
获得一个对应的Iterator(实现类)对象
Set(实现类)对象对应的Iterator仍然是无序的
List(实现类)对象对应的ListIterator
对象可以实现对所含元素的双向遍历:
使用next()方法和previous()方法
//获取迭代器对象,注意:获取迭代器对象之后不允许对集合元素进行修改或删除,否则会报错
Iterator iterator = list1.iterator();
/*遍历集合
iterator.hasNext()是一个指针,判断集合中下一个
单元中是否存在元素,如果存在指针向下走一个单元*/
while(iterator.hasNext()){
//返回当前指针指向的元素
Object next = iterator.next();
//迭代器的删除方法,删除的是当前遍历的元素
//iterator.remove();
//最好不要在迭代器中用
//List.remove(next)
System.out.println(next);
}
forEach循环(加强版for循环):
for (Object obj : list1){
System.out.println(obj);
}
案例分析(关于集合当中的方法):
List的方法:
contains 方法: 底层重写了equals方法,判断集合中是否包含某一个元素,如果该元素重写了equals方法,比较的就是内容,如果没有重写equals方法,默认会采用Object类中的equals方法,这时候比较的就是地址
remove方法: 底层重写了equals方法,删除集合中的元素,但是永远只能删除一个元素,因为在底层删除之后就return了
/**
* 创建两个学生对象(属性有 姓名,年龄,身份证号),然后
* 重写equals方法,在年龄姓名身份证号相同的清空下,两个比较的实例才是相同的!
*
*/
public class ListDemo {
public static void main(String[] args) {
//创建集合对象
List list = new ArrayList();
//创建两个学生实例
Student student1 = new Student("Tom",20,"1232312312");
Student student2 = new Student("Tom",20,"1232312312");
list.add(student1);
//因为在contains的底层调用了equals方法,student在contains中用Object类接收
//而父类Object中的equals方法不能满足需求,因此需要在Student类中去重写equals方法
System.out.println(list.contains(student2));
}
}
public class Student {
private String name;
private int age;
private String idCard;
public Student(String name, int age, String idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
//重写equals方法
@Override
public boolean equals(Object obj) {
//如果接收到为空 或者 obj不是Student类的对象 返回false
if (obj == null || !(obj instanceof Student)){
return false;
}
//对obj向下转型,用Student类的对象student接收
Student student = (Student)obj;
//满足 对象与student的姓名,年龄,身份证号相等 返回true
if(this.name.equals(student.name) && this.age == student.age && this.idCard.equals(student.idCard)){
return true;
}
//不满足则返回false
return false;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", idCard='" + idCard + '\'' +
'}';
}
}
ArrayList
和 vector
集合区别:
这两个集合底层都封装了数组这一种数据结构,ArrayList
是线程不安全的,Vector
是线程安全的
所有封装的方法都是一样的,还有就是扩容机制有一点不一样,ArrayList
和 vector
最开始的时候只能存放10个元素,默认初始容量是10,当元素超过10个之后,就会触发底层的扩容机制
ArrayList
的扩容因子是1.5 10 * 1.5 = 15
Vector
的扩容因子是2.0 10 * 2.0 = 20
ArrayList
和 LinkedList
集合区别:
ArrayList
底层是数组,LinkedList
底层是链表结构
数组查询效率高,随机增删效率低
链表随机增删效率高,查询效率低
集合的泛型
JDK1.5版本之后引入的新特性,强制让集合只能存放某一种数据类型
<数据类型>钻石表达式
//创建只能存放字符串类型的数组
ArrayList<String> string = new ArrayList<>();
自定义泛型:
//<>内部的类容可以是任意的,例如可以是Collection<? extends E> var1,即传入数据类型必须是Collection的子类
//<E>的存在用来接收Person类内部的E的具体引用数据类型
public class Person<E> {
//e这个成员变量的类型为E,E的类型由外部指定
private E e;
//泛型构造方法形参e的类型也为E,E的类型由外部指定
public void getE(E e){
System.out.println(e);
}
}
------------------------------------------------------------
public class Test {
public static void main(String[] args) {
Person<String> stringPerson = new Person<>();
stringPerson.getE("abc");
}
}
2.Set接口:
Set接口的实现类:HashSet
底层封装了HashMap
HashSet
中不允许出现重复元素,不保证集合中元素的顺序(存入的顺序与取出的不同)
HashSet
中允许包含值为null的元素,但最多只能由一个null元素
HashSet<String> hashSet = new HashSet<>();
hashSet.add("abc");
hashSet.add("efg");
hashSet.add("xyz");
hashSet.add("xyz");
for (String s : hashSet) {
System.out.println(s);
}
Set接口的实现类:TreeSet
底层封装了TreeMap
//创建TreeSet集合
TreeSet treeSet = new TreeSet();
treeSet.add(123);
treeSet.add(124);
treeSet.add(120);
treeSet.add(128);
treeSet.add(129);
for (Object o : treeSet) {
System.out.println(o);
}
Map接口:
map集合是存放键值对的容器
value是key的一个替代品,我们以后操作元素时,是根据key去操作
map集合的key是不允许重复的,如果在添加元素时,key的值重复,那么后添加的元素将会把前一个覆盖掉
value的值可以重复
Map:包含了key-value对, Map不能包含重复的key, SortedMap
是一个按照升序排列key的Map
映射(map)是一个存储关键字和值的关联或者说是关键字/值对的对象,给定一个关键字,可以得到它的值
关键字和值都是对象,关键字必须是唯一的,但值是可以被复制的
有些映射可以接收null关键字和null值,而有的则不行
- 一个Map中的key值是唯一的,不重复(如,不要用员工姓名作为key)
- 一个Map中一个key只能对应一个value(可以为空),但一个value可以有多个key与之对应
- Map能让你通过key快速查找到相应的对象并获得它对应的value的引用(如存储员工资料并用员工ID作为key来查找某一员工的信息)
//创建集合对象
Map map = new HashMap();
Map map2 = new HashMap();
//添加元素
map.put("name","jack");
map.put("age",20);
map.put("address","北京市");
map.put("idCard","123456");
//查看集合的长度
System.out.println("集合的长度为"+map.size());
//查询集合中是否包含某一个key
System.out.println("是否包含key"+map.containsKey("name"));
//是否包含value
System.out.println("是否包含value"+map.containsValue(20));
//判断集合对象是否相等
System.out.println(map.equals(map2));
//根据key来获取value
System.out.println("name的值为"+map.get("name"));
//判断集合是否为空
System.out.println("是否为空"+map.isEmpty);
//将map集合中的key部分转成set集合
Set set = map.keySet();
for(Object o : set){
System.out.println(o);
}
//复制集合中所有的元素,将map的内容复制给map2
map2.putAll(map);
//根据key来删除整个键值,返回的是删除的key所对应的value
Object age = map.remove("age");
//将map集合所有的value部分转成collection集合
Collection collection = map.values();
for(Object o : collection){
System.out.println(o);
}
如何遍历Map集合(两种方法):
1.keySet方法:将map集合所有key部分转成set集合,再根据get()获取对应value的值
//创建map集合对象
Map map = new HashMap();
//添加元素
map.put("name","Tom");
map.put("age",20);
map.put("address","北京市");
map.put("idCard","123456");
//将所有的key转成set集合
Set set = map.keySet();
//获取迭代器对象
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object key = iterator.next();
//根据key获取value
Object value = map.get(key);
System.out.println(key+" = "+value);
}
2.Set<Map.Entry<K,V> entrySet()
将map集合转成set集合(效率比第一种高)
Set<Map.Entry<String,String>> entries = map.entrySet();
//这里的Entry<k,V>为Map的内部接口
for (Map.Entry<String,String> entry : entries){
//获取key的值
String key = entry.getKey();
//获取value的值
String value = entry.getValue();
System.out.println(key+" = "+value);
}
Map接口实现类:HashMap
:
hashMap
集合的存储过程:
hashCode()
随机生成哈希码值
我们添加到集合的元素是引用数据类型
那一定会继承Object类中的hashCode
方法
首先map集合会调用put()方法来添加元素,
这个时候hashMap
底层会生成hash值(如果在该引用数据类型中重写了hashCode方法,那么生成的不再是随机值,数据便会存储在同一单元中)
根据这个hash值来计算该元素的位置应该存放在数组的哪个单元下面
确定了存放的单元,这个时候就会去拿着key的引用
与该单元下所有的node节点进行一个equals比较(该引用数据类型有没有重写equals方法如果重写了,比较的是对应属性的内容,如果没有重写,那比较的就是内存地址)
如果发下equals的返回值是true,新添加进来的节点将会把旧的节点覆盖掉
如果equals的返回值是false,该节点将会存放在数组单元中的单向链表的最下面(成为链表尾节点)
//创建集合对象
HashMap<Student,Object> hashMap = new HashMap<>();
//添加元素,key和value
hashMap.put(new Student("jack",20),100);
hashMap.put(new Student("jack",20),101);
hashMap
案例:
//根据hashMap统计字符出现的个数
String str = "djaiwj jdjia1dj dj";
char[] chars = str.toCharArray();
//创建hashMap集合
HashMap<Character,Integer> hashMap = new HashMap<>();
for (char aChar : chars) {
//在遍历字符数组的过程中判断map集合中是否有该字符,如果有在当前个数基础上加一
//如果不存在,则把该字符重新添加到集合中,个数赋值为1
if (hashMap.containsKey(aChar)){
//个数在当前基础上加一
hashMap.put(aChar,hashMap.get(aChar)+1);
}else{
hashMap.put(aChar,1);
}
}
//遍历map集合
Set<Map.Entry<Character, Integer>> entries = hashMap.entrySet();
for (Map.Entry<Character, Integer> entry : entries) {
System.out.println(entry.getKey()+"===>"+entry.getValue());
}
Map接口实现类:HashTable
:
HashTable
和HashMap
的区别:
HashMap
key-value可以为一次空
HashTable
不可以为空
HashTable
是线程同步的,也是线程安全的
其实hashtable
就是线程安全版的hashmap
,所有的方法以及使用方式都是一样的
hashtable
的key和value不允许为空
跟arrayList
和vector
是一样的,hashmap
的执行效率要比hashtable
高,因为hashtable
所有的方法都添加了线程同步关键字,这样的话在多线程场景下,执行效率是不高,但是以后开发中我们可以通过别的途径来解决线程安全问题,所以hashMap
这个集合是我们以后使用的最多key-value集合
Hashtable<Object,Object> hashtable = new Hashtable<>();
Map接口实现类:TreeMap
:
treeMap
是一个可以按照key自动排序的集合
//创建集合对象
Treemap<Integer,Object> treeMap = new TreeMap<>();
treeMap.put(110,101);
treeMap.put(100,101);
treeMap.put(102,101);
//map集合转set集合
Set<Map.Entry<String,String>> entries = treeMap.entrySet();
//forEach
for (Map.Entry<Integer, Object> entry : entries){
System.out.println(entry.getKey()+" = "+entry.getValue());
}
//输出结果:
//100 = 101
//102 = 101
//110 = 101
//对自定义的引用数据类型使用treeMap,treeSet需要在类中实现Comparatable接口
public class Goods implements Comparable<Goods> {
private Double price;
//重写compareTo方法,二叉树根据返回的值的正负来存放数据
@Override
public int compareTo(Goods goods){
return (int)(this.price - goods.price);
}
}
案例:
用hashMap
统计字符出现的个数
package com.os467.hashMapDemo;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapTest {
public static void main(String[] args) {
//统计每个字符出现的次数
getCharNum();
}
/**统计字符出现的个数
*s.matches()正则表达式方法,用于匹配符合规则的字符串
*/
public static void getCharNum(){
System.out.println("---------统计每个字符出现的次数----------");
//创建HashMap集合
HashMap<Character,Integer> hashMap = new HashMap<>();
//将字符串切割成字符数组
char[] chars = getString().toCharArray();
//遍历字符串数组
for (char c: chars) {
if (hashMap.containsKey(c)) {
//个数在当前基础上加一
hashMap.put(c, hashMap.get(c) + 1);
} else {
hashMap.put(c, 1);
}
}
//将map集合转成set集合
Set<Map.Entry<Character, Integer>> entries = hashMap.entrySet();
//遍历set集合
for (Map.Entry<Character, Integer> entry : entries) {
System.out.println(entry.getKey()+" ===> "+entry.getValue());
}
}
//获取字符串的方法
public static String getString(){
return "adaw diicj123";
}
}
Properties集合:
继承了hashtable
//创建集合对象
Properties properties = new Properties();
//创建流对象
FileReader fileReader = new FileReader("jdbc");
//通过流对象去解析数据,并把数据放到集合
properties.load(fileReader);
//私有的方法
properties.setProperty("username","root");
properties.setProperty("password","root");
properties.setProperty("url","jdbc:mysql://localhost:3306");
properties.setProperty("driver","com.mysql.jdbc.driver");
//取出集合中的数据
String username = properties.getProperty("username");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
System.out.println(username);
System.out.println(password);
System.out.println(url);
System.out.println(driver);
集合总结
Collection体系:
List接口:
ArrayList
:
有序可重复的集合,底层封装了数组这种数据结构,所以有数组所有的特性,可以通过下标索引来获取元素,查询数据的效率会很高,但是随机增删的效率会比较低,本身是一个线程不安全的集合,默认初始容量是10,如果存放元素超过10会触发扩容机制,扩容因子是1.5,每触发一次扩容机制,加载为原来的1.5倍
Vector
:
Vector就是一个线程安全版的Arraylist
,这个集合的所有方法都添加了线程同步关键字,在多线程场景下,效率是很低的,我们一般不会使用这个集合,默认初始容量是10,如果存放元素超过10会触发扩容机制,扩容因子是2.0,每触发一次扩容机制,加载为原来的2倍
LinkedList
:
这个集合在底层封装了单向链表这种数据结构,所以该集合拥有链表的所有特性,查询效率是不高的,但是随机增删效率很高,而且在LinkedList
底层还为我们封装了队列,我们可以去调用入队和出队的方法
Set接口:
HashSet
:
在底层为我们聚合了HashMap
,在添加元素的时候,实际上调用了HashMap
的put方法,元素存放在put方法的key部分
HashSet
是一个无序不可重复的集合,存入数据的顺序和取出数据的顺序是不同的
TreeSet
:
在底层为我们聚合了TreeMap
,在添加元素的时候,实际上调用了TreeMap
的put方法,元素存放在put方法的key部分
TreeSet
是一个可以按照排序规则自动排序的方法,如果存入元素的引用时我们自定义的,该引用必须要实现比较器接口Comparable<泛型>,重写比较规则,不然会报类型转换异常
Map体系:
HashMap
:
可以以key-value的形式去存放数据,key和value都是允许为空的,不是线程安全的集合,但是执行效率很高,在底层封装的是哈希散列
HashTable
:
线程安全版的HashMap
,执行效率不是很高,key和value都不允许重复
TreeMap
:
是可以根据key来完成自动排序的key-value集合,如果存入元素的引用是我们自定义的,该引用必须要实现比较器接口Comparable<泛型>,重写比较规则,不然会报类型转换异常
Properties
:
这个集合继承了HashTable
,所以该集合是一个线程安全的集合,而且提供了私有的方法SetProper()
,getProper()
,该集合可以集合流对象高效的去读取配置文件,把数据解析到集合中
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com