目录
单例设计模式
饿汉式:线程安全,随着类的加载方便,快,内存中占用时间较长
懒汉式:线程不安全,需要的时候进行创建,节省内存空间
饿汉式
类加载会执行静态变量和静态代码块,静态方法不会在类加载的过程中自动调用
public class test {
public static void main(String[] args) {
//通过方法获取对象
//两个是同一个对象,构造器只会被调用一次
GirlFriend gf1 = GirlFriend.getInstance();
GirlFriend gf2 = GirlFriend.getInstance();
System.out.println(gf1);
System.out.println(gf2);
//gf1 == gf2
}
}
class GirlFriend {
private String name;
//在内部创建对象
private static GirlFriend gf = new GirlFriend("小红");
//构造器私有化
private GirlFriend(String name) {
System.out.println("构造器被调用");
this.name = name;
}
//静态方法只能调用静态属性
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
懒汉式
1、构造器私有化
2、在类的内部创建当前类的实例
3、提供一个public的static方法,可以返回一个对象
4、懒汉式,只有当用户使用getinstance时,才返回对象,后面再次调用时,会返回上次创建的cat对象,从而保证了单例
public class test {
public static void main(String[] args) {
Cat c1 = Cat.getInstance();
Cat c2 = Cat.getInstance();
//两个是同一个对象,构造器只会被调用一次
System.out.println(c1);
System.out.println(c2);
}
}
class Cat {
private String name;
private static Cat cat;
//private static Cat cat = null;
public Cat(String name) {
System.out.println("构造器被调用");
this.name = name;
}
public static Cat getInstance() {
if(cat == null)
cat = new Cat("小可爱");//此时构造器被调用
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
枚举类
本质上也是类,这个类的对象是有限的,不能让用户随意创建
如果针对某个类,其实例是确定个数的,则推荐将此类声明为枚举类
如果枚举类的实例只有一个,则可以看做是单例的实现方式
自定义类实现枚举
public class test {
public static void main(String[] args) {
System.out.println(Season.AUTUMN);
System.out.println(Season.SPRING);
System.out.println(Season.AUTUMN.getName());
}
}
//演示字定义枚举实现
class Season {//类
//声明当前类的对象的实例变量
private final String name;
private final String desc;//描述
//创建当前类的实例
//加上public static 可以直接调用
//加上final不允许修改,Season.SPRING = null;
public static final Season SPRING = new Season("春天", "温暖");
public static final Season WINTER = new Season("冬天", "寒冷");
public static final Season AUTUMN = new Season("秋天", "凉爽");
public static final Season SUMMER = new Season("夏天", "炎热");
//1. 将构造器私有化,目的防止 直接 new
//2. 去掉setXxx 方法, 防止属性被修改
//3. 在 Season 内部,直接创建固定的对象
//4. 优化,可以加入 final 修饰符
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
使用enum关键字实现枚举
当我们使用enum关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类
使用enum关键字定义的枚举类,默认其父类时java.lang.Enum类,不要再显示定义其父类,否则报错
public class t {
public static void main(String[] args) {
System.out.println();
}
}
enum Season {
//如果使用了enum 来实现枚举类
//1. 使用关键字 enum 替代 class
//2. public static final Season SPRING = new Season("春天", "温暖") 直接使用
//SPRING("春天", "温暖") 解读 常量名(实参列表)
//3. 如果有多个常量(对象), 使用 ,号间隔即可
//4. 如果使用enum 来实现枚举,要求将定义常量对象,写在前面
//5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
SPRING("春天", "温暖"),WINTER("冬天", "寒冷"),AUTUMN("秋天", "凉爽"),
SUMMER("夏天", "炎热");//What()调用无参构造器
private String name;
private String desc;//描述
//无参构造器
private Season() {}
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
enum常用方法
public class t {
public static void main(String[] args) {
Season autumn = Season.AUTUMN;
//ordinal()输出的是该枚举对象的次序/编号,从0开始编号
System.out.println(autumn.ordinal());
//含有定义的所有枚举对象
Season[] values = Season.values();
System.out.println("===遍历取出枚举对象(增强 for)====");
for (Season i : values) {//增强 for 循环
System.out.println(i);
}
//valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
//执行流程
//1. 根据你输入的 "AUTUMN" 到 Season2的枚举对象去查找
//2. 如果找到了,就返回,如果没有找到,就报错
Season autumn1 = Season.valueOf("AUTUMN");
System.out.println("autumn1=" + autumn1);
System.out.println(autumn == autumn1);
//compareTo:比较两个枚举常量,比较的就是编号
//就是把 Season2.AUTUMN 枚举对象的编号 和 Season2.SUMMER枚举对象的编号比较
/*
public final int compareTo(E o) {
returnself.ordinal-other.ordinal;
}
*/
System.out.println(Season.AUTUMN.compareTo(Season.SUMMER));
}
}
//演示字定义枚举实现
enum Season {
//如果使用了enum 来实现枚举类
//1. 使用关键字 enum 替代 class
//2. public static final Season SPRING = new Season("春天", "温暖") 直接使用
//SPRING("春天", "温暖") 解读 常量名(实参列表)
//3. 如果有多个常量(对象), 使用 ,号间隔即可
//4. 如果使用enum 来实现枚举,要求将定义常量对象,写在前面
//5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
SPRING("春天", "温暖"),WINTER("冬天", "寒冷"),AUTUMN("秋天", "凉爽"),
SUMMER("夏天", "炎热");//What()调用无参构造器
private String name;
private String desc;//描述
//无参构造器
private Season() {}
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
enum实现接口
1、枚举类实现接口,在枚举类中重写接口中的抽象方法。当通过不同的枚举类对象调用此方法时,执行的是同一个方法
public class test {
public static void main(String[] args) {
//通过枚举类的对象调用重写接口中的方法
Season.SPRING.show();
}
}
interface Info {
void show();
}
enum Season implements Info{
SPRING("春天", "温暖"),
WINTER("冬天", "寒冷"),
AUTUMN("秋天", "凉爽"),
SUMMER("夏天", "炎热");
private final String name;
private final String desc;
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public void show() {
System.out.println("这是一个季节");
}
}
2、让枚举类的每一个对象重写接口中的方法。当通过不同的枚举类对象调用此方法时,执行的时不同的实现方法
interface Info {
void show();
}
enum Season implements Info{
SPRING("春天", "温暖") {
@Override
public void show() {
System.out.println(1);
}
},
WINTER("冬天", "寒冷") {
@Override
public void show() {
System.out.println(2);
}
},
AUTUMN("秋天", "凉爽") {
@Override
public void show() {
System.out.println(3);
}
},
SUMMER("夏天", "炎热") {
@Override
public void show() {
System.out.println(4);
}
};
}
异常
java.lang.Throwable:异常体系的根父类
1、java.lang.Error:错误。
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。
|-----StackOverflowError (栈内存溢出)
|-----OutOfMemoryError (堆内存溢出,简称OOM)
2、java.lang.Exception:异常。
我们可以编写针对性的代码进行处理。
编译时异常:(受检异常) 在执行javac.exe命令时,出现的异常。
|----- ClassNotFoundException(类找不到异常)
|----- FileNotFoundException(文件找不到异常)
|----- IOException(输入输出异常)
运行时异常:(非受检异常) 在执行java.exe命令时,出现的异常。
|---- ArrayIndexOutOfBoundsException (数组越界–用非法索引访问数组时抛出的异常)
|---- NullPointerException (空指针异常)
|---- ClassCastException (类转换异常)
|---- NumberFormatException (数据格式化异常)
|---- InputMismatchException (输入类型不一致)
|---- ArithmeticException (算术异常)
Java异常处理的方式:
方式1:捕获异常(try-catch-finally)
基本格式
try{
...... //可能产生异常的代码,此程序就不执行其后的代码
}
catch( 异常类型1 e ){
...... //当产生异常类型1型异常时的处置措施,只会执行一个catch
}
catch( 异常类型2 e ){
...... //当产生异常类型2型异常时的处置措施
}
finally{
...... //无论是否发生异常,都无条件执行的语句
}
细节
如果声明了多个catch结构,不同的异常类型在不存在子父类关系的情况下,谁声明在上面,谁声明在下面都可以。如果多个异常类型满足子父类的关系,则必须将子类声明在父类结构的上面。否则,报错。
catch中常用异常处理的方式
1、自己编写输出的语句
2、public String getMessage()
:获取异常的描述信息,返回字符串(推荐)
3、public void printStackTrace()
:获取发生异常的原因。打印异常的跟踪栈信息并输出到控制台。包含了异常的类型、异常的原因、还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace()。
catch(NumberFormatException e)
e.printStackTrace();
try中声明的变量,出了try结构之后,就不可以进行调用了。
try-catch结构是可以嵌套使用的。
对于运行时异常:修改代码
对于编译时异常:必须处理,否则编译不通过
finally的使用
一定要被执行的代码声明在finally结构中,无论是否存在return,都要执行
多个return,返回finally中的return
finally语句和catch语句是可选的,但finally不能单独使用
方式2:throws
throws基本格式
声明异常格式:在方法的声明处,使用"throws 异常类型1,异常类型2,...
"
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
public void test() throws 异常类型1,异常类型2,.. {
//可能存在编译时异常 (运行时异常不用管它)
}
关于编译时异常
必须声明所有可能抛出的检查型异常
到main
方法就结束了,最好不要再抛出去(不要在main方法里面写throws)
编译时异常必须try catch处理
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ThrowsTest {
public static void main(String[] args){
method3();
try {
method2();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
public static void method3() {
try {
method2();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
public static void method2() throws FileNotFoundException, IOException{
method1();
}
public static void method1() throws FileNotFoundException, IOException {
File file = new File("D:\\hello.txt");
//可能报FileNotFoundException 文件找不到异常
FileInputStream fis = new FileInputStream(file); //流直接操作文件
//把文件内容直接读到内存中输出
int data = fis.read(); //可能报IOException 输入输出异常
while (data != -1) { //data为-1的时候退出,就是读完了
System.out.print((char) data);
data = fis.read(); //可能报IOException 输入输出异常
}
//资源关闭
fis.close(); //可能报IOException 输入输出异常
}
}
关于运行时异常
throws后面也可以写运行时异常类型,只是运行时异常类型,写或不写对于编译器和程序执行来说都没有任何区别。如果写了,唯一的区别就是调用者调用该方法后,使用try…catch结构时,IDEA可以获得更多的信息,需要添加哪种catch分支。
程序中如果没有处理,默认就是throws的方式处理
package com.atguigu.keyword;
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestThrowsRuntimeException {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
try {
System.out.print("请输入第一个整数:");
int a = input.nextInt();
System.out.print("请输入第二个整数:");
int b = input.nextInt();
int result = divide(a,b);
System.out.println(a + "/" + b +"=" + result);
} catch (ArithmeticException | InputMismatchException e) {
e.printStackTrace();
} finally {
input.close();
}
}
public static int divide(int a, int b)throws ArithmeticException{
return a/b;
}
}
方法重写中throws的要求
方法重写要求
(1)方法名必须相同
(2)形参列表必须相同
(3)返回值类型
- 基本数据类型和void:必须相同
- 引用数据类型:<=
(4)权限修饰符:>=,而且要求父类被重写方法在子类中是可见的
(5)不能是static,final修饰的方法
throws要求(编译时异常)(运行时异常类型无要求)
子类的异常不能大于父类的异常,父类没有抛异常子类就不能抛。
(多态,编译看左边,运行看右边,但是想要调用子类独有的方法,需要将父类强转成子类才可以调用,俗称“向下转型”。)
class Father{
public void method()throws Exception{
System.out.println("Father.method");
}
}
class Son extends Father{
@Override
public void method() throws IOException,ClassCastException {
System.out.println("Son.method");
}
}
两种异常处理方式的选择
1、如果程序代码中,涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用try-catch-finally来处理,保证不出现内存泄漏。
2、如果父类被重写的方法没有throws异常类型,则子类重写的方法中如果出现异常,只能考虑使用try-catch-finally进行处理,不能throws。(父类没有throws,子类也不能throws)
3、开发中,方法a中依次调用了方法b,c,d等方法,方法b,c,d之间是递进关系。此时,如果方法b,c,d中有异常,我们通常选择使用throws,而方法a中通常选择使用try-catch-finally。
throw手动抛出异常
使用格式
throw new 异常类名(参数);
throw语句抛出的异常对象,和JVM自动创建和抛出的异常对象一样。
如果是编译时异常类型的对象,同样需要使用throws或者try…catch处理,否则编译不通过。
如果是运行时异常类型的对象,编译器不提示。
可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:
自定义异常
public class t {
public static void main(String[] args) /*throws AgeException*/ {
int age = 180;
//要求范围在 18–120 之间,否则抛出一个自定义异常
if(!(age >= 18 && age <= 120)) {
//这里我们可以通过构造器,设置信息
throw new AgeException("年龄需要在 18~120 之间");
}
System.out.println("你的年龄范围正确.");
}
}
//自定义异常
//把自定义异常做成 运行时异常,好处时,我们可以使用默认的处理机制
class AgeException extends RuntimeException {
public AgeException(String message) {//构造器
super(message);
}
}
String
final
:String是不可被继承的,赋值后不可修改(地址不可以修改,内容可以变化)。
Serializable
:可序列化的接口。凡是实现此接口的类的对象就可以通过网络或本地流进行数据的传输(序列化)。
Comparable
:凡是实现此接口的类,其对象都可以比较大小。在这个接口里面有抽象方法,String类实现了这个接口里面的抽象方法,指明了怎么算叫“大”,怎么算叫“小”,给了一种标准(重写了方法)。
CharSequence
:字符序列。
可以看到String类实现了相关的接口,没有明显地写父类,也就是说它是继承Object类的。
两种创建String对象的区别
字符串常量池不允许存放两个相同的字符串常量。
方式一:直接赋值 String s = "hsp";
String可以通过字面量的方式来定义。字符串放在了字符串常量池当中,如果有直接指向,没有则重新创建然后指向,最终指向的是常量池的空间地址
方式二:调用构造器 String s = new String("hsp");
先在堆中创建空间,里面维护了value属性,指向常量池的abc地址。如果没有则重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址
String的不可变性
1、当对字符串变量重新赋值时,需要重新指定一个字符串常量的位置进行赋值,不能在原有的位置修改。
2、当对现有的字符串进行拼接操作时,需要重新开辟空间保存拼接以后的字符串,不能在原有的位置修改。
String a = "hello" + "abc"; ==> String a = "helloabc" 只创建了一个对象
常量 + 常量 并没有创建新的对象
3、当调用字符串的replace()
替换现有的某个字符时,需要重新开辟空间保存修改以后的字符串,不能在原有的位置修改。
String与基本数据类型、包装类之间的转换
//基本数据类型 => String
int num = 10;
String s1 = num + "";
String s2 = String.valueOf(num);
//String => 基本数据类型
String s3 = "1234"'
int num = Integer.parseInt(s3);
//String => char[]
char[] arr = str.toCharArray();
//char[] => String
String str = new String(arr);
String的常见方法
StringBuffer
public class StringBuffer01 {
public static void main(String[] args) {
//1. StringBuffer 的直接父类 是 AbstractStringBuilder
//2. StringBuffer 实现了 Serializable, 即StringBuffer的对象可以串行化
//3. 在父类中 AbstractStringBuilder 有属性 char[] value,不是final
// 该 value 数组存放 字符串内容,引出存放在堆中的
//4. StringBuffer 是一个 final类,不能被继承
//5. 因为StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)
// 不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String
StringBuffer stringBuffer = new StringBuffer("hello");
}
}
String 和 StringBuffer 相互转换
public class StringAndStringBuffer {
public static void main(String[] args) {
//看 String——>StringBuffer
String str = "hello tom";
//方式1 使用构造器
//注意: 返回的才是StringBuffer对象,对str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式2 使用的是append方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//看看 StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
//方式1 使用StringBuffer提供的 toString方法
String s = stringBuffer3.toString();
//方式2: 使用构造器来搞定
String s1 = new String(stringBuffer3);
}
}
StringBuffer 类常见方法
public class StringBufferMethod {
public static void main(String[] args) {
StringBuffer s = new StringBuffer("hello");
//增
s.append(',');// "hello,"
s.append("张三丰");//"hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏100true10.5"
System.out.println(s);//"hello,张三丰赵敏100true10.5"
//删
/*
* 删除索引为>=start && <end 处的字符
* 解读: 删除 11~14的字符 [11, 14)
*/
s.delete(11, 14);
System.out.println(s);//"hello,张三丰赵敏true10.5"
//改
//使用 周芷若 替换 索引9-11的字符 [9,11)
s.replace(9, 11, "周芷若");
System.out.println(s);//"hello,张三丰周芷若true10.5"
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);//6
//插
//在索引为9的位置插入 "赵敏",原来索引为9的内容自动后移
s.insert(9, "赵敏");
System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
//长度
System.out.println(s.length());//22
System.out.println(s);
}
}
比较
① StringBuilder 和StringBuffer 非常相似,均代表可变的字符序列,而且方法也一样;
② String:不可变字符序列,效率低,但是复用率高;
③ StringBuffer:可变字符序列、效率较高(增删)、线程安全(适合多线程);
④ StringBuilder:可变字符序列、效率最高、线程不安全(适合单线程);
⑤ 效率:StringBuilder > StringBuffer > String
选择
① 如果字符串存在大量的修改操作,一般男使用StringBuffer和StringBuilder;
② 如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder;
③ 如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer;
④ 如果字符串存在很少量的修改操作,被多个对象引用,使用String,比如配置信息等。
日期
第一代
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.SimpleFormatter;
public class Main {
public static void main(String[] args) throws ParseException {
//两种构造器的使用
//1、获取当前系统时间
Date d1 = new Date();
//默认输出的日期格式是国外的方式,需要对格式进行转换
System.out.println(d1.toString());
//获取对应的毫秒数 getTime()
long millTimes = d1.getTime();
//2、通过指定毫秒数获取时间
Date d2 = new Date(9234567);
System.out.println(d2);
//指定相应的格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = sdf.format(d1);
System.out.println(format);
//可以把一个格式化的String转成对应的Date
//得到Date在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//在把String => Date, 使用的sdf格式需要和你给的String的格式一样,否则会抛出转换异常
String s = "1996年01月01日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println(parse);
}
}
第二代Calendar(日历)
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class Main {
public static void main(String[] args) {
//1、Calendar是一个抽象类,并且构造器是protected
//2、可以通过getInstance()来获取实例
//3、没有专门的格式化类,需要自己组合显示
//4、按照24小时进制来获取时间 Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance();//创建日历类对象
System.out.println(c);//输出有很多字段
//获取日历对象的某个日历字段
System.out.println("年 " + c.get(Calendar.YEAR));
//返回月的时候是按照 0 开始编号
System.out.println("月 " + (c.get(Calendar.MONTH) + 1));
System.out.println("日 " + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时 " + c.get(Calendar.HOUR));
System.out.println("分钟 " + c.get(Calendar.MINUTE));
System.out.println("秒 " + c.get(Calendar.SECOND));
}
}
第三代
LocalDate(年月日)
LocalTime(时分秒)
LocalDateTime(日期+时间)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
//1、使用now()返回表示当前日期时间的对象
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
System.out.println(ldt.getYear());
System.out.println(ldt.getMonth());//月,英文单词
System.out.println(ldt.getMonthValue());//月,具体的数
System.out.println(ldt.getDayOfMonth());//日
//2、of() 获取指定的时间
LocalDateTime localDateTime = LocalDateTime.of(2022,12,5,11,23,45);
System.out.println(localDateTime);
//3、使用DateTimeFormatter对象进行格式化
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH小时mm分钟ss秒");
String format = dtf.format(ldt);
System.out.println(format);
}
}