Java基础入门(下)
二、Java语言基础(下)
2.1 多线程
关于多线程,首先需要理解三个基本概念:
什么是程序?
程序是为完成特定任务、用某种语言编写的一组指令的集合。
什么是进程?
进程是程序的一次执行过程。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
什么是线程?
进程可进一步细化为线程,是一个程序内部的一条执行路径。线程是调度和执行的单位。
每个线程拥独立的运行栈和程序计数器(pc);多个线程,共享同一个进程中的方法区、堆。
线程切换的开销小,进程切换开销大,一个进程中可以有多个线程。
单核CPU与多核CPU的理解
- 单核CPU:其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费。)但是因为CPU时间单元特别短,因此感觉不出来。
- 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)。
- 一个Java应用程序java.exe,其实至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并行与并发的理解
- 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
2.1.1 创建多线程的方式
继承Thread类
实现Runnable接口
JDK5新增-实现Callable接口
//1.创建一个实现Callable的实现类 class NumThread implements Callable{//2.实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;} }public class ThreadNew {public static void main(String[] args) {//3.创建Callable接口实现类的对象NumThread numThread = new NumThread();//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask = new FutureTask(numThread);//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()new Thread(futureTask).start();try {//6.获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}
相较于实现Runnable接口,实现Callable接口的方式更加强大,具体体现在:
- call()可以返回值的。
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
JDK5新增-使用线程池
class NumberThread implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}} }class NumberThread1 implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}} }public class ThreadPool {public static void main(String[] args) {//1. 提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//设置线程池的属性 // System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime();//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//适合适用于Runnableservice.execute(new NumberThread1());//适合适用于Runnable// service.submit(Callable callable);//适合使用于Callable//3.关闭连接池service.shutdown();}}
线程池的优点:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没任务时最多保持多长时间后会终止
2.1.2 Thread类中的常用的方法
start():启动当前线程;调用当前线程的run()。
run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。
currentThread():静态方法,返回执行当前代码的线程。
getName():获取当前线程的名字。
setName():设置当前线程的名字。
yield():释放当前cpu的执行权,不会释放锁。
join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
stop():已过时。当执行此方法时,强制结束当前线程。
sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
isAlive():判断当前线程是否存活。
getPriority():获取线程的优先级。
setPriority(int p):设置线程的优先级。
线程的优先级:
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5 -->默认优先级
- 高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。
2.1.3 线程的生命周期
2.1.4 线程的同步机制
背景
例子:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式
问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
Java解决方案:同步机制
同步代码块
synchronized(同步监视器){//需要被同步的代码 } /* 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。 * 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。 * 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。 * 要求:多个线程必须要共用同一把锁。 * * 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。Runnable r = new MyRunnable();new Thread(r).start();new Thread(r).start();在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。 */
同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明同步的。
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
非静态的同步方法,同步监视器是:this。
静态的同步方法,同步监视器是:当前类本身。
Lock锁 — JDK5.0新增
synchronized 与 Lock的异同?
- 相同:二者都可以解决线程安全问题
- 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器;Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())。
使用的优先顺序:
Lock —> 同步代码块(已经进入了方法体,分配了相应资源 ) —> 同步方法(在方法体之外)
线程安全的单例模式:
public class SingleInstance {private volatile static SingleInstance uniqueInstance = null;public static SingleInstance getInstance() {if (uniqueInstance == null) {synchronized (SingleInstance.class) {if (uniqueInstance == null) {uniqueInstance = new SingleInstance();}}}return uniqueInstance;}
}
避免出现死锁
死锁,即不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。如下:
public static void main(String[] args) {StringBuffer s1 = new StringBuffer();StringBuffer s2 = new StringBuffer();new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}).start();
}
2.1.5 线程通信
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
备注:
wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常。
wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
sleep() 和 wait()的异同?
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:
1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
yield 和 sleep 的异同
1)yield, sleep 都能暂停当前线程,sleep 可以指定具体休眠的时间,而 yield 则依赖 CPU 的时间片划分。
2)yield, sleep 两个在暂停过程中,如已经持有锁,则都不会释放锁资源。
3)yield 不能被中断,而 sleep 则可以接受中断。
问题:yield()不会释放锁,只释放cpu的执行权,有啥意义,不是只有拿到锁的线程才能执行么?
答:谁告诉你只有一把锁了???共用一把锁的其他线程不能执行,但是用别的锁的线程可以执行!
2.2 Java常用类
2.2.1 java.lang.String类的使用
- String声明为final的,不可被继承
- String实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable接口:表示String可以比较大小。
- String内部定义了final char[] value用于存储字符串数据。
- 通过字面量的方式(区别于new给一个字符串赋值,此时的字符串值声明在字符串常量池中)。
- 字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。
String的不可变性
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
String类实例化方式
通过字面量定义的方式
此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE"; String s2 = "javaEE";
通过new + 构造器的方式
通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE"); String s4 = new String("javaEE");System.out.println(s1 == s2);//true System.out.println(s1 == s3);//false System.out.println(s1 == s4);//false System.out.println(s3 == s4);//false
问题:String s = new String("abc");
方式创建对象,在内存中创建了几个对象?
两个;一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”。
字符串拼接方式赋值的对比
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
- 只要其中一个是变量,结果就在堆中。
- 如果拼接的结果调用intern()方法,返回值就在常量池中
String s1 = "javaEE";
String s2 = "hadoop";String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//falseString s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
****************************
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//falsefinal String s4 = "javaEE";//s4:常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true
JVM中字符串常量池存放位置
- jdk 1.6 (jdk 6.0 ,java 6.0):方法区(永久区)
- jdk 1.7:堆空间
- jdk 1.8:方法区(元空间)
StringBuffer
初始化为一个长度为16的char[],扩容策略:2*原容量+2
2.2.2 时间API
2.2.2.1 获取系统当前时间
想获取系统当前时间,System类中的静态方法currentTimeMillis()想必大家都不会陌生,它返回的是当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差,也被称为时间戳。
- java.util.Date类与java.sql.Date类
关于这两个类,很多人可能不是特别清楚两者之间的区别与联系,接下来我们对此进行简单的梳理。
- 区别:
首先,从包名可以看出java.sql.Date类是与数据库相关的类,更准确地来说,它与数据库中的日期类型变量相对应。而java.util.Date类,就是我们平时使用的类了(事实上开发中也很少会用它,具体后面再说)。
那么除了根据包名能判断出的这些外,两者之间有什么联系呢?
- 联系:
通过查阅jdk源码可知:java.sql.Date继承了java.util.Date类,也就是说,java.sql.Date类是java.util.Date类的子类。
搞清楚了两者之间的区别和联系后,我们来回答两个问题:
第一个问题:如何将java.sql.Date对象转换为java.util.Date对象呢?
熟悉面向对象的读者肯定知道,子类对象可直接向上转型成父类对象(多态性)。据此,我们可以直接将java.sql.Date类的对象赋给java.util.Date对象,代码如下:
java.util.Date date = new java.sql.Date(136513463156L); //向java.sql.Date的构造器中传入时间戳
第二个问题:如何将java.util.Date对象转换为java.sql.Date对象呢?
进行强制类型转换?当然不可行。事实上,如果我们将父类对象强制转化为子类对象,将会抛出
Exception in thread "main" java.lang.ClassCastException: java.util.Date cannot be cast to java.sql.Date
异常。那么就只能从两者之间的联系下手,代码如下:java.util.Date date = new java.util.Date();// 创建java.util.Date对象 java.sql.Date sqlDate = new java.sql.Date(date.getTime());//getTime()方法会返回当前Date对象对应的毫秒数。(时间戳)
补充说明(了解即可):
- java.text.SimpleDataFormat类:主要用来格式化日期时间。简单介绍一下用法:
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format = sdf.format(date);
System.out.println(format);//2019-02-18 11:48:27
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
//否则,抛异常
Date date1 = sdf1.parse("2020-02-18 11:48:27");
System.out.println(date1);
Calendar类:该类是日历类,也是一个抽象类。简单介绍一下用法:
//1.创建Calendar类的对象有两种方式: //方式一:创建其子类(GregorianCalendar的对象) //方式二:调用其静态方法getInstance() Calendar calendar = Calendar.getInstance();//2.常用方法 //get() System.out.println(calendar.get(Calendar.DAY_OF_MONTH));//获取本月内的第几天,从1开始 System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//获取一年内的第几天,从1开始//set() //calendar可变性 calendar.set(Calendar.DAY_OF_MONTH,22); // 设置日期为本月内的第22天 System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); //输出22//add() calendar.add(Calendar.DAY_OF_MONTH,-3); // 设置日期为今天的日期-3天; System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 今天是11月8号,所以输出5//getTime():日历类---> Date Date date = calendar.getTime(); // 返回Date对象 System.out.println(date);//输出Sun Nov 08 17:49:57 CST 2020//setTime():Date ---> 日历类 Date date = new Date(); calendar.setTime(date); System.out.println(calendar.get(Calendar.YEAR)); // 输出2020 System.out.println(date.getYear()); // 输出120,此方法输出的是今年与1900年之差
Calendar类本意是为了弥补了Date类的缺陷而生的,但是随着在开发中的使用,渐渐的暴露出它们的不足。比如糟糕的可变性、偏移性等等。下面,我们一起来看看jdk1.8之后,关于日期的处理。
2.2.2.2 jdk1.8引入的关于时间处理的API
回顾Java对日期时间的迭代:
- 第一代:jdk 1.0 Date类
- 第二代:jdk 1.1 Calendar类,一定程度上替换Date类
- 第三代:jdk 1.8 提出了新的一套API
前两代存在的问题举例:
- 可变性:像日期和时间这样的类应该是不可变的。
- 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
- 格式化:格式化只对Date用,Calendar则不行。
- 此外,它们也不是线程安全的;不能处理闰秒等。
java 8 中新的日期时间API涉及到的包:
- 本地日期、本地时间、本地日期时间的使用:LocalDate / LocalTime / LocalDateTime
简介
① 分别表示使用 ISO-8601日历系统的日期、时间、日期时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
② LocalDateTime相较于LocalDate、LocalTime,使用频率要高
③ 类似于Calendar常用方法:
- 时间点:Instant
简介
① 时间线上的一个瞬时点。 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC开始的秒数。)
② 类似于 java.util.Date类常用方法:
- 日期时间格式化类:DateTimeFormatter
简介
① 格式化或解析日期、时间
② 类似于SimpleDateFormat实例化方式:
- 预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
- 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
- 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
常用方法:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); //格式化 String str = formatter.format(LocalDateTime.now()); System.out.println(str);//2020-11-08 06:48:13//解析 TemporalAccessor accessor = formatter.parse("2020-11-08 06:48:13"); System.out.println(accessor); //输出{SecondOfMinute=13, MinuteOfHour=48, MicroOfSecond=0, MilliOfSecond=0, NanoOfSecond=0, HourOfAmPm=6},ISO resolved to 2020-11-08
- 其他API
带时区的日期时间:ZonedDateTime / ZoneId
//获取“Asia/Tokyo”时区对应的时间 LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println(localDateTime);//2020-11-08T19:57:38.479 //now()/now(ZoneId id):获取当前时区/指定时区的ZonedDateTime对象 ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println(zonedDateTime);//2020-11-08T19:58:45.084+09:00[Asia/Tokyo]
时间间隔:Duration–用于计算两个“时间”间隔,以秒和纳秒为基准
//设置起始日期时间,获取当前日期时间 LocalDateTime localDateTime = LocalDateTime.of(2016, 11, 8, 19, 2, 00); LocalDateTime localDateTime1 = LocalDateTime.now();Duration duration = Duration.between(localDateTime, localDateTime1);//返回间隔对象 System.out.println(duration.toDays()); //输出1461
日期间隔:Period --用于计算两个“日期”间隔,以年、月、日衡量
LocalDate localDate = LocalDate.now(); LocalDate localDate1 = LocalDate.of(2030, 11, 8);Period period = Period.between(localDate, localDate1); System.out.println(period); //P10Y System.out.println(period.getYears()); // 10 System.out.println(period.getMonths()); // 0 System.out.println(period.getDays()); // 0
日期时间校正器:TemporalAdjuster
//获取当前日期的下一个周日是哪天? TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY); LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster); System.out.println(localDateTime); //2020-11-15T19:15:51.426 //获取下一个工作日是哪天?今天是2020-11-08 LocalDate localDate = LocalDate.now().with(new TemporalAdjuster(){@Overridepublic Temporal adjustInto(Temporal temporal) {LocalDate date = (LocalDate)temporal;if(date.getDayOfWeek().equals(DayOfWeek.FRIDAY)){return date.plusDays(3);//如果是周五,则+3}else if(date.getDayOfWeek().equals(DayOfWeek.SATURDAY)){return date.plusDays(2);//如果是周六,则+2}else{return date.plusDays(1);//其他情况,则+1}} }); System.out.println("下一个工作日是:" + localDate); //下一个工作日是:2020-11-09
2.2.3 比较器
- 引子
在比较Java对象时,正常情况下,我们只能使用==
或!=
进行比较,而不能使用>
或者<
这样的运算符。
那么问题来了,开发过程中我们经常要对多个对象排序,排序必然需要比较,那么如何实现呢?
其实实现也很简单,使用Comparable
或者Comparator
两个接口中的一个即可。
- Comparable接口的使用
先来看一段代码:
public static void main(String[] args) {String[] arr = new String[] {"dd","aa","xx","cc"};Arrays.sort(arr);System.out.println(Arrays.toString(arr)); // 输出[aa, cc, dd, xx]
}
执行上面的代码,输出结果为:[aa, cc, dd, xx]
。
为什么呢?查阅String类的源码可知,String类实现了Comparable接口的compareTo(obj)方法。
在重写compareTo(obj)方法时,我们需要满足三个规则:
- 如果当前对象this大于形参对象obj,则返回正整数,
- 如果当前对象this小于形参对象obj,则返回负整数,
- 如果当前对象this等于形参对象obj,则返回零。
那么让我们自定义的类实现比较就很简单了,只需要按照上面三个规则实现Comparable接口中的compareTo(obj)方法,再调用Arrays.sort(arr)或者Collections.sort(list)即可。
那么什么情况下都能够以实现Comparable接口的方式来定义对象的比较方式吗?
当然不是!实际上,虽然Comparable接口可以解决一部分业务需求,但是当我们无法使某个类实现Comparable接口时(比如第三方Jar包中的类),我们通常会使用另一种方式—实现Comparator接口。
- Comparator接口的使用
与实现Comparable接口类似,使用Comparator接口同样需要我们实现一个方法—compare(obj1,obj2)。
在重写compare(Object o1,Object o2)方法时,我们同样需要满足三个规则:
- 如果方法返回正整数,则表示o1大于o2;
- 如果返回0,表示相等;
- 返回负整数,表示o1小于o2。
示例代码如下:
public static void main(String[] args) {Comparator comparator = (o1, o2) -> {if (o1 instanceof Person && o2 instanceof Person) {Person preson1 = (Person) o1;Person preson2 = (Person) o1;return Integer.compare(preson1.getAge(),preson2.getAge());}throw new RuntimeException("输入的数据类型不一致");};Person p1 = new Person();p1.setAge(18);Person p2 = new Person();p2.setAge(20);Person[] people = new Person[]{p1, p2};Arrays.sort(people,comparator);System.out.println(Arrays.toString(people)); // 输出[Person{age=18}, Person{age=20}]
}
2.2.4 枚举类
使用
//使用enum关键字枚举类 enum Season1 {//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束SPRING("春天","春暖花开"),SUMMER("夏天","夏日炎炎"),AUTUMN("秋天","秋高气爽"),WINTER("冬天","冰天雪地");//2.声明Season对象的属性:private final修饰private final String seasonName;private final String seasonDesc;//2.私化类的构造器,并给对象属性赋值private Season1(String seasonName,String seasonDesc){this.seasonName = seasonName;this.seasonDesc = seasonDesc;}//4.其他诉求1:获取枚举类对象的属性public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;} }
常用方法
Season1 summer = Season1.SUMMER; //toString():返回枚举类对象的名称 System.out.println(summer.toString());//System.out.println(Season1.class.getSuperclass()); System.out.println("****************"); //values():返回所的枚举类对象构成的数组 Season1[] values = Season1.values(); for(int i = 0;i < values.length;i++){System.out.println(values[i]); } System.out.println("****************"); Thread.State[] values1 = Thread.State.values(); for (int i = 0; i < values1.length; i++) {System.out.println(values1[i]); }//valueOf(String objName):返回枚举类中对象名是objName的对象。 Season1 winter = Season1.valueOf("WINTER"); //如果没objName的枚举类对象,则抛异常:IllegalArgumentException // Season1 winter = Season1.valueOf("WINTER1"); System.out.println(winter);
枚举类对象分别实现接口
interface Info{void show(); }//使用enum关键字枚举类 enum Season1 implements Info{//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束SPRING("春天","春暖花开"){@Overridepublic void show() {System.out.println("春天在哪里?");}},SUMMER("夏天","夏日炎炎"){@Overridepublic void show() {System.out.println("宁夏");}},AUTUMN("秋天","秋高气爽"){@Overridepublic void show() {System.out.println("秋天不回来");}},WINTER("冬天","冰天雪地"){@Overridepublic void show() {System.out.println("大约在冬季");}}; }
2.2.5 注解
注解是什么?
Java注解是附加在代码中的一些元信息 ,简单来说,注解就算一个辅助工具,可以让我们的代码更加简洁等。
为什么要学习注解
首先,我们平时使用的框架,就是靠着注解才得以使我们开发更加方便。框架=注解+反射+设计模式
通过注解你可以将你的某个方法以注解的形式调用,就像Spring中的@Controller、@Component等注解一样。
首先说明一些概念:
什么是元注解?
元注解就是对现有注解进行说明的注解。
为此,我们接下来来了解一下JDK5.0提供的四个元注解:
@Retention:指定所修饰的 Annotation 的生命周期。参数:
RetentionPolicy.SOURCE—注解只在源文件中有效
RetentionPolicy.CLASS—注解在class文件中有效,当运行Java程序时,该注解不会被保留。默认值。
RetentionPolicy.RUNTIME—运行时有效。程序可以通过反射来获取该注解。
@Target:用于指定被修饰的 注解能用于修饰哪些程序元素
@Documented:表示所修饰的注解在被javadoc解析时,保留下来。
@Inherited:被它修饰的注解将具继承性。
怎么使用注解
了解了上面的概念后,我们可以来实现一个简单的注解,代码如下:
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取
@Target({ElementType.METHOD,ElementType.PARAMETER}) // 注解可用在方法和参数上。
@Inherited // 注解可继承
@Documented // javadoc解析时保留
@interface HelloAnnotation {String value() default "hello";
}@HelloAnnotation
public void setAge() {/*......*/
}
注解只是提供一个辅助信息,要跟反射结合在一起使用才可以实现某项功能!
补充知识:Java8中注解的新特性
可重复注解:(当需要在一个方法上使用多次注解时使用)
① 在HelloAnnotation上声明@Repeatable,成员值为HelloAnnotations.class
② HelloAnnotation的Target和Retention等元注解与HelloAnnotations相同。
代码示例如下:
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取 @Target(ElementType.METHOD) // 注解可用在方法上 @Inherited // 注解可继承 @Repeatable(HelloAnnotations.class)//可重复 @Documented // javadoc解析时保留 @interface HelloAnnotation {String value() default "hello"; } @Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取 @Target(ElementType.METHOD) // 注解可用在方法上 @Inherited // 注解可继承 @Documented // javadoc解析时保留 @interface HelloAnnotations {HelloAnnotation value()[]; }//使用如下: @HelloAnnotation @HelloAnnotation public static void main(String[] args) {}
类型注解:
①ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型声明。示例代码如下:
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取 @Target({ElementType.TYPE_PARAMETER }) // 注解可用在方法上 @interface HelloAnnotation {String value() default "hello"; }class TestAnnotation<@HelloAnnotation T> {}
②ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
示例代码如下:
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取 @Target({ElementType.TYPE_USE}) // 注解可用在方法上 @interface HelloAnnotation {String value() default "hello"; }public static void main(String[] args) throws @HelloAnnotation RuntimeException{@HelloAnnotation int i = 0;List<@HelloAnnotation String> list = null; }
2.3 Java集合
2.3.1 Collection接口
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。 -->“动态”数组
|----ArrayList、LinkedList、Vector
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet、LinkedHashSet、TreeSet
向Collection接口的实现类的对象中添加对象obj时,要求obj所在类要重写equals()。
遍历Collection的两种方式:
- 使用迭代器Iterator
- foreach循环(或增强for循环)
使用集合的remove()方法时需要注意:
如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。因为“指针”还没有下移。
2.3.2 Collection子接口:List接口
List存储有序的、可重复的数据。
2.3.2.1 List接口常用实现类
|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储,线程不安全的
|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
2.3.2.2 源码分析
ArrayList
jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementDatalist.add(123);//elementData[0] = new Integer(123);...list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
扩容机制:扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)。
jdk8情况下
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
扩容机制:与jdk 7 相同。
总结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList
LinkedList list = new LinkedList(); //内部声明了Node类型的first和last属性,默认值为null list.add(123);//将123封装到Node中,创建了Node对象。 //Node:LinkedList的双向链表的一个节点 private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;} }
Vector
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
扩容机制:默认扩容为原来的数组长度的2倍。
2.3.3 Collection子接口:Set接口
Set存储无序的、不可重复的元素。存储顺序是根据数据的哈希值确定的。
以HashSet为例,描述元素添加过程:
- 我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即索引位置);
- 判断数组此位置上是否已经元素:
- 如果此位置上没其他元素,则元素a添加成功。
- 如果此位置上其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
- 如果hash值不相同,则元素a添加成功。
- 如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。
在此位置已有元素的情况下,元素添加成功:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下,如下图
Java基础入门(下)相关推荐
- 叮!您收到一份超值Java基础入门资料!
摘要:Java语言有什么特点?如何最大效率的学习?深浅拷贝到底有何区别?阿里巴巴高级开发工程师为大家带来Java系统解读,带你掌握Java技术要领,突破重点难点,入门面向对象编程,以详细示例带领大家J ...
- java基础入门课后习题_《Java基础入门》课后习题及答案
<Java基础入门>课后习题及答案Java基础入门,课后习题,答案 博学谷--让IT教学更简单,让IT学习更有效 <Java基础入门>课后习题 第1章Java开发入门 一.填空 ...
- 叮!您收到一份超值Java基础入门资料! 1
Java语言有什么特点?如何最大效率的学习?深浅拷贝到底有何区别?阿里巴巴高级开发工程师为大家带来Java系统解读,带你掌握Java技术要领,突破重点难点,入门面向对象编程,以详细示例带领大家Java ...
- Qt学习之Qt基础入门(下)
1. 前言 前两篇博客简单的阐述了一下Qt的入门用法,这篇博客继续跟着视频学习. Qt入门系列: Qt学习之C++基础 Qt学习之Qt安装 Qt学习之Qt基础入门(上) Qt学习之Qt基础入门(中) ...
- JAVA中整型常量的长度,Java基础入门篇(三)——Java常量、变量,
Java基础入门篇(三)--Java常量.变量, 一.Java常量 (一)什么是常量 常量指的是在程序中固定不变的值,是不能改变的数据.在Java中,常量包括整型常量.浮点型常量.布尔常量.字符常量等 ...
- 大数据必学Java知识(一):Java基础入门语法和安装
Java基础入门语法和安装 1. Java概述 1.1 Java语言背景介绍(了解) 1.2 Java语言跨平台原理(理解) 1.3 JRE和JDK(记忆) 1.4 JDK的下载和安装(应用) 2. ...
- Java基础入门:IDEA软件安装和软件初始化设置
IDEA是一个专门针对Java的集成开发工具(IDE),由Java语言编写.所以,需要有JRE运行环境并配置好环境变量. 它可以极大地提升我们的开发效率.可以自动编译,检查错误.在公司中,使用的就是I ...
- Java基础入门--学习笔记
Java基础入门教程 itheima–java基础小白教程 学习配套软件:eclipse 1.基础知识 (1)常用DOS命令:切换盘–>E:,cd–>进入文件,可多级,cd - --> ...
- Java基础入门(六)
Java基础入门(六) 1 Debug调试 1.1 Debug概述 1.2 Debug操作流程 1.2.1 设置断点 1.2.2 运行加了断点的程序 1.2.3 Debugger窗口和Console窗 ...
- java基础入门了解
java基础入门了解 Java发展简史 java语言的用处(数据存储,数据分析,数据处理) java被运用的程度 java语言的特点 java语言体系的结构 java语言运行机制 课后知识补充 Jav ...
最新文章
- DC-RC加固修补型砂浆
- 【转】从源码分析Handler的postDelayed为什么可以延时?
- 【Python】Python入门-字符串初相识
- Swift之数组去重(去除重复元素)
- 2021企业直播新观察——市场升温蕴藏机会,消费场景左右未来
- matlab simulink_简单五步实现 MATLAB/Simulink 锂电池建模
- 问题 D: 求圆的面积和周长 山东科技大学oj c 语言
- 从 Web1.0 到 3.0 你不知道的互联网的演进史!
- Tricks(二十二) —— zip(python) 的实现及使用
- int main():声明指定了两个以上的数据类型
- php写2048,原生js编写2048小游戏实例代码
- STM32F103_DMA控制器
- 移动客户端谈百度分享经验
- 【19C】logmnr参考
- Python 量化分析——基本面选股模型
- MarkDown高阶语法手册
- Mendix与JEECG对比
- 关于openfire支持视频聊天
- obs多推流地址_最热门直播工具OBS的下载和设置教程,值得一看
- 基于Tensorflow2.x低阶API搭建神经网络模型并训练及解决梯度爆炸与消失方法实践