JavaSE
==与equals()方法的区别
== | equals() | |
---|---|---|
基本数据类型 | 值是否相等 | 不适用 |
引用数据类型 | 地址值是否相等 | 对于Object类下的比较的是地址,对于一些特定的类,如String,Date,File等,它们都重写了equals()方法比较的是字面内容 |
数组陷阱
char型数组返回的不是地址值
public class ArrayPrintTest {
public static void main(String[] args) {
int[] arr = new int[] { 1, 2, 3 };
char[] arr1 = new char[] { 'a', 'b', 'c' };
System.out.println(arr);
System.out.println(arr1);
}
}
打印内容为abc
static关键字
1、static:静态的
2、static可以用来修饰,属性,方法,代码块,内部类(不能修饰构造器)
3、使用static修饰属性:静态变量(或类变量)
属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。
当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致
其他对象调用此静态变量时,是修改过了的。
static修饰属性的其他说明。
①静态变量随着类的加载而加载。可以通过类.静态变量的方式进行调用
②静态变量的加载要早于对象的创建
③由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
类变量 | 实例变量 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
静态属性举例
4、使用static修饰方法:静态方法
①随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
②
静态方法 | 非静态方法 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
③静态方法中只能调用静态的方法或属性
非静态方法中,既可以调用非静态方法或属性,也可以调用静态的方法或属性
5、static注意点:
在静态的方法内,不能使用this关键字,super关键字
6、开发中,如何确定一个属性是否要声明为static?
属性可以被多个对象所共享的,不会随着对象的不同而不同的。
开发中,如何确定一个方法是否要声明为static?
操作静态属性的方法通常设置为静态的
工具类中的方法,习惯上声明为static的。
单例设计模式
定义:采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
饿汉式
public class PersonTest{
Person p1 = Person.getPerson;
Person p2 = Person.getPerson;
System.out.println(p1 == p2);
}
class Person{
//1、私有化构造器
private Person(){
}
//2、内部创建类的对象
//4、对象声明为静态
private static Person person = new Person();
//3、公共的返回对象的方法
public static Person getPerson(){
return person;
}
}
懒汉式
public class PersonTest{
}
class Person{
private Person(){
}
private static Person person = null;
public static Person getPerson(){
if(person == null){
return new Person();
}
return person;
}
饿汉式 | 懒汉式 | |
---|---|---|
对象加载时间 | 长 | 短 |
线程安全 | 安全 | 目前的写法不安全 |
代码块
1、作用:用来初始化类,对象
2、代码块如果有修饰的话,只能使用static
3、分类:静态代码块和非静态代码块
4、静态代码块
内部可以有输出语句
随着类的加载而执行,并且只会执行一次
5、非静态代码块
内部可以有输出语句
随着对象的创建而执行
每创建一个对象,就执行一次非静态代码块
作用:可以在创建对象时对对象的属性等进行初始化
对属性可以复制的位置:
- ①默认初始化
- ②显示初始化
- ③构造器初始化
- ④有了对象后,通过"对象.属性"或"对象.方法"的方式,进行赋值
- ⑤在代码块中赋值
final关键字
final:最终的
1、final可以用来修饰的结构:类,方法,变量
2、final用来修饰一个类:这个类不能被其他类继承。如String类,System类,StringBuffer类
3、final用来修饰的方法:称为最终方法,不能够被重写。如Object类中的getClass();
4、final用来修饰变量:称为常量
- final修饰属性:可以考虑赋值的位置有:显式初始化,代码块中初始化,构造器中初始化
- final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
static final 用来修饰属性:全局常量,
static final 用来修饰方法:
abstract关键字
abstarct:抽象的
abstract修饰类:抽象类
此类不能实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作
abstarct修饰方法:抽象方法
只有方法的声明,没有方法体
包含抽象方法的类一定是抽象类。反之,抽象类中可以没有抽象方法。
若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写了父类中的所有的抽象方法后,则此子类也是一个抽象类,需要使用abstract修饰
注意点:
1、abstract不能用来修饰,属性,构造器等结构
2、abstract不能用来修饰私有方法、静态方法、final的方法,final的类
接口(interface)的使用
1、接口使用interface来定义
2、Java中,接口和类是并列的两个结构
3、如何定义接口:定义接口中的成员
-
JDK7及以前:只能够定义全局常量和抽象方法
全局常量:public static final的,但是书写时,可以省略不写
抽象方法:public abstract的
-
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
4、接口中不能定义构造器!意味着接口不可以实例化
5、Java开发中,接口通过让类去实现(implements)的方式来使用。如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。如果实现类没有覆盖接口中所有的抽象方法。则此实现类仍为一个抽象类。
6、Java类可以实现多个接口 ---->弥补Java单继承性的局限性的问题
JDK8,除了定义全局常量和抽象方法之外,还可以定义静态方法,默认方法
抽象类和接口的共同点和区别
抽象类 | 接口 | |
---|---|---|
相同点 | 不能实例化,都可以被继承 | 不能实例化,都可以被继承 |
不同点 | 有构造器,单继承。只能修饰类或者方法 | 不能声明构造器,多继承。JDK7及以前,能修饰全局常量和抽象方法。JDK8,还能修饰静态方法和默认方法。 |
代理模式
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
public void browse() {
check();
work.browse();
}
}
内部类
1、Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
2、内部类的分类:成员内部类 (静态、非静态)vs 局部内部类(方法内,代码块内,构造器内)
3、成员内部类:
一方面,作为外部类的成员
调用外部类的结构
可以被static修饰
另一方面,作为一个类
类内可以定义属性,方法,构造器等
可以被final修饰,表示此类不能被继承
可以被abstract修饰
多线程
多线程的创建,方式一:继承于Thread类
1、创建一个继承于Thread类的子类
2、重写Thread类的run()方法 —>将此线程执行的操作声明在run()中
3、创建Thread类的子类的对象
4、通过此对象调用start()方法
例:遍历100以内所有的偶数
public class ThreadTest {
public static void main(String[] args) {
//3、创建Thread类的对象
MyThread t1 = new MyThread();
//4、通过此对象调用start()
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
//1、创建一个继承于Thread类的子类
class MyThread extends Thread{
//2、重写Thread类的run()
public void run(){
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
我们不能通过直接调用run()的方式启动线程
Thread类中的常用方法
方法名 | 作用 |
---|---|
start() | 启动当前线程:调用当前线程的run() |
run() | 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 |
currentThread() | 静态方法,返回执行当前代码的线程 |
getName() | 获取当前线程的名字 |
setName() | 设置当前线程的名字 |
yield() | 释放当前CPU的执行权 |
join() | 在线程A中调用线程B的join(),此时线程A就进入了阻塞状态。直到线程B完全执行完以后,线程A才结束阻塞状态。 |
stop() | 已过时。当执行此方法时,强制结束当前线程。 |
sleep(long millitime) | 让当前线程"睡眠"指定的millitime毫秒,在指定的millitime毫秒时间内,当前线程是阻塞状态。 |
isAlive | 判断当前线程是否存活 |
线程的优先级
1、MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 ---->默认优先级
2、如何获取和设置当前线程的优先级:getPriority():获取线程的优先级,setPriority():设置线程的优先级
高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才被执行。
/**
* 目前存在线程不安全问题
* 创建三个窗口,总票数为100张
*/
class Window extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
if (ticket > 0){
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
多线程的创建,方式二:实现Runnable接口
1、创建一个实现了Runnable接口的类
2、实现类去实现Runnable中的抽象方法
3、创建实现类的对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5、通过Thread类的对象调用start()
/**
* 实现多线程的方式二:实现Runnable接口
*/
public class ThreadTest1 {
public static void main(String[] args) {
//3、创建实现类的对象
MThread mThread = new MThread();
//4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
//5、通过Thread类的对象调用start()
t1.setName("线程1");
/**
* ①启动线程
* ②调用当前线程的run()---->调用了Runnable类型的target
*/
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t1.start();
t2.start();
}
}
//1、实现Runnable接口
class MThread implements Runnable{
int count = 0;
//2、重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.print(Thread.currentThread().getName() + ":" + i + " ");
count ++;
if (count % 5 == 0){
System.out.println();
}
}
}
}
}
/**
* 实现Runnable接口的方式
*/
class Window1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket --;
}else{
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 window1 = new Window1();
Thread t1 = new Thread(window1);
Thread t2 = new Thread(window1);
Thread t3 = new Thread(window1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
比较创建线程的两种方式
开发中优先选择实现Runnable接口的方式
原因:1、实现的方式没有类的单继承的局限性
2、实现的方式更适合来处理多个线程有共享数据的情况。
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IAB8xhp0-1610351481404)(C:\Users\ASUS\Desktop\ssm\多线程\QQ截图20201226164144.jpg)]
解决线程安全问题
售票出现的问题
-
卖票过程中,出现了重票,错票—>出现了线程的安全问题
-
原因:当某个线程操作车票过程中,尚未操作完成时,其他线程参与进来,也操作车票。
-
解决办法:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程A操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程A出现了阻塞,也不能被改变。
-
在Java中,通过同步机制,来解决线程安全问题。
-
同步的方式,解决了线程的安全问题。-----好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。----局限性
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:操作共享数据的代码,即为需要被同步的代码
共享数据:多个线程共同操作的变量。比如:ticket就是共享的数据。
同步监视器:俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
解决线程安全问题:
class Window implements Runnable{
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true){
synchronized(obj){
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
ticket --;
}
else{
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window);
t1.setName("窗口1");
Thread t2 = new Thread(window);
t2.setName("窗口2");
Thread t3 = new Thread(window);
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
关于同步方法的总结
1、同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2、非静态的同步方法,同步方法是:this
静态的同步方法,同步监视器是:当前类本身。
方式三: ReentrantLock
/**
* 解决线程安全问题的方式三:Lock锁-----JDK5.0新增
*/
class Windows implements Runnable{
private int ticket = 100;
//1、实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);//公平锁
@Override
public void run() {
while(true){
try{
//2、调用锁定方法lock()
lock.lock();
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket --;
}else{
break;
}
}finally {
//3、调用解锁方法unlock()
lock.unlock();
}
}
}
}
@SuppressWarnings("all")
public class LockTest {
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
线程安全的懒汉式:
方式一:效率稍差
public class BankTest{
}
class Bank{
private Bank(){}
private static Bank bank = null;
public static Bank getBank(){
synchronized(Bank.class){
if(bank == null){
bank = new Bank();
}
return bank;
}
}
}
方式二:效率更高
if(bank == null){
synchronized(Bank.class){
if(bank == null){
bank = new Bank();
}
}
}
return bank;
synchronized和lock有什么异同?
synchronized | lock(JDK5.0新增的) | |
---|---|---|
相同 | 二者都可以解决线程安全问题 | 二者都可以解决线程安全问题 |
不同 | 在执行完相应的同步代码以后,自动的释放同步监视器 | 需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock()) |
优先使用顺序:
Lock——>同步代码块——>同步方法
死锁
演示线程的死锁问题
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public 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() {
@Override
public 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();
}
}
死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
说明:
1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
2)使用同步时,要避免出现死锁。
线程的通信
例:使用两个线程打印1-100.线程1,线程2交替打印
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while(true){
synchronized (this){
try{
notify();
if (number <= 100){
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + ":" + number);
number ++;
wait();//使得调用wait()的线程进入阻塞状态,并且会释放锁
}else{
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
涉及到的三个方法:
1、wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
2、notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
3、notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
1、wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2、wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
否则会出现IllegalMonitorStateException异常
3、wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中的。
sleep()和wait()的异同?
sleep() | wait() | |
---|---|---|
相同点 | 一旦执行方法,都可以使得当前的线程进入阻塞状态 | 一旦执行方法,都可以使得当前的线程进入阻塞状态 |
不同点 | Thread类中声明sleep();sleep()可以在任何需要的场景下调用;sleep()不会释放锁 | Object类中声明wait();wait()必须使用在同步代码块或同步方法中;wait()会释放锁 |
经典的生产者消费者问题
//店员
class Clerk{
private int productCount = 0;
//消费产品
public synchronized void customerProduct(){
if (productCount > 0){
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
productCount --;
notify();
}else{
System.out.println("柜台没有了....");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//生产产品
public synchronized void produceProduct(){
if (productCount < 20){
productCount ++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify();
}else{
System.out.println("柜台满了....");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始生产产品.......");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
//消费者
class Customer extends Thread{
private Clerk clerk;
public Customer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始消费产品.......");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.customerProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Customer c1 = new Customer(clerk);
c1.setName("消费者1");
Customer c2 = new Customer(clerk);
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
多线程的创建,方式三:实现Callable接口
JDK5.0新增
步骤:
1、创建一个实现Callable的实现类
2、实现call()方法,将此线程需要执行的操作声明在call()中
3、创建Callable接口实现类的对象
4、将此对象传递到FutureTask构造器中,创建FutureTask的对象
5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并start()调用。
6、获取Callable中call方法的返回值。
class NumThread implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
NumThread numThread = new NumThread();
FutureTask futureTask = new FutureTask(numThread);
new Thread(futureTask).start();
try {
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解实现Callable接口的方式比实现Runnable接口创建多线程方式更强大?
1、它可以有返回值
2、方法可以抛出异常,被外面的操作捕获,获取异常的信息
3、支持泛型的返回值
4、需要借助FutureTask类,比如获取返回结果
多线程的创建,方式四:使用线程池
JDK5.0新增
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。
好处:
1、提高响应速度
2、降低资源消耗
3、便于线程管理
步骤:
1、提供指定线程数量的线程池
2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Callable{
@Override
public Object call() throws Exception {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + "实现callable接口" + i);
}
}
return null;
}
}
public class ThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池的属性
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(15);
//service1.setKeepAliveTime();
service.execute(new NumberThread());//适合使用于Runnable
Future submit = service.submit(new NumberThread1());//适合适用于Callable
try {
Object o = submit.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
Java常用类
字符串相关的类
String类
String字符串:使用一对""引起来表示。
1、String声明为final的,不可被继承
2、String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
3、String内部定义了final char[] value用于存储字符串数据
4、String:==代表一个不可变的字符序列。==简称:不可变性
体现:1、当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
2、当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value值进行赋 值
3、当调用String的replace()修改字符或字符串时,也必须重新指定内存区域赋值,不能使用原有的value值进行赋值
5、通过字面量的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中。
6、字符串常量池中是不会存储相同内容的字符串的。
JDK1.6在方法区;JDK1.7在堆空间;JDK1.8在方法区。
方式一:通过字面量定义的方式
@Test
public void test1(){
String s1 = "abc";//字面量的定义方式
String s2 = "abc";
//s1 = "hello";
System.out.println(s1 == s2);//true
System.out.println(s1);//abc
System.out.println(s2);//abc
System.out.println("********************");
String s3 = "abc";
s3 += "def";
System.out.println(s3);//abcdef
System.out.println("********************");
String s4 = "abc";
String s5 = s4.replace('a','m');
System.out.println(s4);//abc
System.out.println(s5);//mbc
}
方式二:通过new+构造器的方式
@Test
public void test2(){
//此时的s1和s2的数据都是声明在方法区的常量池中
String s1 = "javaEE";
String s2 = "javaEE";
//s3和s4声明在堆空间中
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
Person p1 = new Person("Tom", 12);
Person p2 = new Person("Tom", 12);
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.name == p2.name);//true
}
使用String s = new String(“abc”)的方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
结论:1、常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
2、只要其中有一个是变量,结果就在堆中。
3、如果拼接的结果调用intern()方法,返回值就在常量池中
@Test
public void test3(){
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;
String s8 = s5.intern();
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);//false
System.out.println(s3 == s8);//true
}
面试题
public class Test {
String str = "good";
char[] ch = {'t','e','s','t'};
public void change(String str,char ch[]){
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
Test test = new Test();
test.change(test.str,test.ch);
System.out.println(test.str);//good
System.out.println(test.ch);//best
}
}
常用方法
int length():返回字符串长度
char charAt(int index):返回某索引处的字符
boolean isEmpty():判断是否是空字符串
String toLowerCase():使用默认语言环境,将所有字符转换为小写
String toUpperCase():使用默认语言环境,将所有字符转换为大写
String trim():返回字符串副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。等价于用"+"
int compareTo(String anotherString):比较两个字符串大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的beginIndex开始截取
String substring(int beginIndex,int endIndex):返回一个新字符串,它是此字符串从beginIndex截取到endIndex
@Test
public void test1(){
String s1 = "helloWorld";
System.out.println(s1.length());//10
System.out.println(s1.charAt(0));//h
System.out.println(s1.charAt(9));//d
//System.out.println(s1.charAt(10));//java.lang.StringIndexOutOfBoundsException
System.out.println(s1.isEmpty());//false
System.out.println(s1.toLowerCase());//helloworld
System.out.println(s1.toUpperCase());//HELLOWORLD
String s3 = " he l l o ";
System.out.println(s3.trim());//he l l o
System.out.println(s1.equals(s1.toUpperCase()));//false
System.out.println(s1.equalsIgnoreCase(s1.toUpperCase()));//true
System.out.println(s1.concat("123"));//helloWorld123
System.out.println(s1.compareTo(s1.concat("1")));//涉及到字符串排序;-1
System.out.println(s1.concat("13123").substring(11));//3123
System.out.println(s1.concat("2312321321").substring(12,18));//123213
}
类型转换
String类与其他结构之间的转换
String与基本数据类型、包装类之间的转换
String---->基本数据类型、包装类:调用包装类的静态方法Integer.parseInt()
基本数据类型、包装类---->String:调用String重载的valueOf(xxx)
String与char[]之间的转换
String------>char[]:调用String的toCharArray()
char[]------>String:调用String的构造器
String与byte[]之间的转换
String--->byte[]:调用String的getBytes()
byte--->String:调用String的构造器
编码:字符串 --->字节
解码:编码的逆过程,字节--->字符串
说明:解码时,要求解码使用的字符集必须要与编码时使用的字符集一致,否则会出现乱码。
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();
System.out.println(Arrays.toString(bytes));
byte[] bytes1 = str1.getBytes("gbk");//使用gbk字符集进行编码
System.out.println(Arrays.toString(bytes1));
String str2 = new String(bytes);//使用默认的字符串,进行解码
System.out.println(str2);
String str3 = new String(bytes1,"gbk");
System.out.println(str3);
}
StringBuffer和StringBuilder的使用
String | StringBuffer | StringBuilder | |
---|---|---|---|
相同点 | 底层使用char[]存储 | 底层使用char[]存储 | 底层使用char[]存储 |
不同点 | 不可变的字符序列 | 可变的字符序列,线程安全,效率低 | 可变的字符序列,线程不安全,效率高;JDK5.0新增 |
源码分析:
String str = new String();//new char[0];
String str1 = new String(“abc”);//new char[]{‘a’,‘b’,‘c’};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
sb1.append(‘a’);//value[0] = ‘a’;
sb1.append(‘b’);//value[1]=‘b’;
StringBuffer sb2 = new StringBuffer(“abc”);//char[] value = new char[“abc”.length() + 16]
JDK8之前日期时间API
System类中的currentTimeMillis(),返回时间戳
@Test
public void test1(){
long time = System.currentTimeMillis();
System.out.println(time);
}
Date类
java.util.Date类
两个构造器使用
构造器一:Date():创建一个对应当前时间的Date对象
构造器二:创建指定毫秒数的Date对象
两个方法的使用
toString():显示当前的年,月,日,时,分,秒
getTime():获取当前Date对象对应的毫秒数。(时间戳)
java.sql.Date类对应着数据库中的日期类型的变量
@Test
public void test2(){
//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());
System.out.println(date1.getTime());
//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(1609141206869L);
System.out.println(date2.toString());
//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(1609141206869L);
System.out.println(date3.toString());
}
java.text.SimpleDateFormat
SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
1、两个操作
格式化:日期---->字符串
解析:格式化的逆过程---->日期
2、SimpleDateFormat的实例化
@Test
public void testSimpleDateFormat() throws ParseException {
//实例化SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化日期
Date date = new Date();
String format = sdf.format(date);
System.out.println(format);
//解析 格式化逆过程:字符串---->日期
String str = "2017-01-01 上午11:43";//不方便
Date date1 = sdf.parse(str);
System.out.println(date1);
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format1 = sdf1.format(date);
System.out.println(format1);
}
练习题:将字符串"2020-09-08"转换为java.sql.Date
@Test
public void test1(){
String birth = "2020-09-08";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(birth);
java.sql.Date birthDate = new java.sql.Date(date.getTime());
System.out.println(birthDate);
}
java.util.Calendar
两种实例化方式:1、实例化它的子类GregorianCalendar
2、调用这个类的getInstance()静态方法
JDK8中新日期API
LocalDate、LocalTime、LocalDateTime的使用
@Test
public void test1(){
//now():获取当前日期,时间,日期+时间
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(date);
System.out.println(time);
System.out.println(dateTime);
//of():设置指定的年月日,时分秒
LocalDateTime localDateTime = LocalDateTime.of(2020, 12, 28, 16, 51, 20);
System.out.println(localDateTime);
//getXxx()
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getDayOfWeek());
System.out.println(localDateTime.getDayOfYear());
}
后续请参考-------->尚硅谷Java基础484-486
Java比较器
一、java中对象,正常情况下,只能进行比较:== 或!=。不能使用>或<的
但是在开发场景中,我们需要对多个对象进行排序。需要比较对象的大小
使用两个接口中任何一个:Comparable或Comparator
二、Comparable接口的使用(自然排序)
Comparable接口的使用举例
1、像String,包装类等实现了Comparable接口,重写了compareTo()方法,给出了比较两个对象大小的方式
2、像String,包装类重写compareTo()方法以后,进行了从小到大的排列
3、重写compareTo()的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
4、对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写CompareTo()方法
在CompareTo(obj)方法中指明如何排序
String类
@Test
public void test1(){
String[] arr = new String[]{"AA","CC","MM","GG","KK","JJ","DD"};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
当需要进行比较的类是自定义类时,让自定义类实现Comparable接口,重写CompareTo()方法
public class Goods implements Comparable{
private String name;
private double price;
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public Goods() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//指明商品比较大小的方式:按照价格从低到高排序
@Override
public int compareTo(Object o) {
//方式一:
if (o instanceof Goods){
Goods goods = (Goods)o;
if (this.price > goods.price){
return 1;
}else if (this.price < goods.price){
return -1;
}else{
//return 0;
return this.name.compareTo(goods.name);//价格一样时,按名称比较
}
//方式二:
//return Double.compare(this.price,goods.price);
}
throw new RuntimeException("传入的数据类型不一致!");
}
}
三、java.util.Comparator(定制排序)
1、Comparator接口(定制排序):当元素的类型没有实现Comparable接口而又不方便修改代码
或者排序规则不适合当前的操作,就可以考虑使用Comparator的对象来排序。
2、重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2
如果返回0,表示相等;
返回负整数,表示o1小于o2.
String类
@Test
public void test3(){
String[] arr = new String[]{"AA","CC","MM","GG","KK","JJ","DD"};
Arrays.sort(arr, new Comparator() {
//按照字符串从大到小的顺序排序
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
自定义类
@Test
public void test2(){
Goods[] arr = new Goods[5];
arr[0] = new Goods("lenovo",34);
arr[1] = new Goods("dell",43);
arr[2] = new Goods("xiaomi",12);
arr[3] = new Goods("huaWei",65);
arr[4] = new Goods("lenovo",21);
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Goods && o2 instanceof Goods){
Goods g1 = (Goods) o1;
Goods g2 = (Goods) o2;
if (g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
Comparable和Comparator的使用对比
Comparable接口的方式一旦确定,保证Comparable接口实现类的对象在任何位置都可以比较大小
Comparator接口属于临时性的比较。
System类
三个属性:in,out,err
方法:
void exit(int status);//0表示正常退出,非0表示异常退出。
void gc();//请求系统进行垃圾回收,至于是否回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况
String getProperty(String key);//获得系统中属性名为key的属性对应的值。
Math类
abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt平方根
pow(double a,double b)a的b次幂
log自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random()返回0.0到1.0的随机数
long round(double a)double转换为long
toDegrees(double angrad)弧度---->角度
toRadians(double angdeg)角度---->弧度
BigInteger和BigDecimal
BigInteger可以表示不可变的任意精度的整数
BigInteger可以表示不可变的任意精度的浮点型
枚举类和注解
枚举类
一、枚举类的使用
1、枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
2、当需要定义一组常量时,强烈建议使用枚举类
3、如果枚举类中只有一个对象,则可以作为单例模式的实现方式
二、如何定义枚举类
方式一:JDK5.0之前,自定义枚举类
方式二:JDK5.0时,可以使用enum关键字定义枚举类
自定义枚举类
public class SeasonTest {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
//自定义枚举类
class Season{
//1、声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2、私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonDesc = seasonDesc;
this.seasonName = seasonName;
}
//3、提供当前枚举类的多个对象:public static final的
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");
//4、其他:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
enum枚举类
enum Season{
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
三、Enum类中的常用方法
values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象。
toString():返回当前枚举类对象常量的名称
注解
如何自定义注解?
①注解声明为:@interface
②内部定义成员,通常使用value表示
③可以指定成员的默认值,使用default定义
④如果自定义注解没有成员,表明是一个标识作用
Java集合
一、集合框架的概述
1、集合,数组都是对多个数据进行存储操作的结构,简化JAVA容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
2.1、数组在存储多个数据方面的特点:
①一旦初始化以后,其长度就确定了。
②数组一旦定义好,其元素的类型也就确定好了。也就只能操作指定类型的数据了。
比如:String[] arr;int[] arr1;Object[] arr2;
2.2、数组在存储多个数据方面的缺点:
①一旦初始化以后,其长度就不可修改。
②数组中提供的方法非常有限,对于添加,删除,插入数据等操作,非常不便,同时效率也不高。
③获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
④数组存储数据的特点:有序,可重复。对于无序、不可重复的需求,不能满足。
二、集合框架
|Collection接口:单列集合,用来存储一个一个的对象*******|
|********List接口:存储有序的,可重复的数据。"动态"数组|
|**********ArrayList,LinkedList,Vector|
|***************Set接口:存储无序的,不可重复的数据。***************|
|**HashSet,LinkedHashSet,TreeSet|
|Map接口:双列集合,用来存储一对(key-value)一对的数据|
|HashMap,LinkedHashMap,TreeMap,Hashtable,Properties|
三、Collection接口中的方法的使用
Collection接口
Collection接口中声明的方法的测试
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
@Test
public void test1(){
Collection coll = new ArrayList();
//add(Object e):将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
//size():获取添加元素的个数
System.out.println(coll.size());
//addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1);
System.out.println("添加之后:" + coll.size());//6
//clear():清空集合元素
coll.clear();
//isEmpty():判断集合是否为空
System.out.println(coll.isEmpty());//true
}
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(new String("Tom"));
coll.add(false);
coll.add(456);
coll.add(new Person("邱周童", 19));
//contains(Object obj):判断当前集合中是否包含obj
System.out.println(coll.contains(123));
System.out.println(coll.contains(new String("Tom")));
System.out.println(coll.contains(new Person("邱周童", 19)));//false
//containsAll():判断当前集合是否包含多个元素
//remove(Object obj):移除元素
coll.remove(123);
System.out.println(coll);
//removeAll(Collection coll1):从当前集合中移除coll1中所有元素
//retainAll(Collection coll):求交集
//equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同
//hashcode():返回当前对象的哈希值
System.out.println(coll.hashCode());
//集合----->数组:toArray()
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//数组----->集合:调用Arrays类的静态方法asList()
List<String> list = Arrays.asList(new String[]{"aa", "bb", "cc"});
System.out.println(list);
//iterator():返回Iterator接口的实例,用于遍历集合元素
}
iterator()方法
集合元素的遍历操作,使用迭代器Iterator接口
1、内部的方法:hasNext():判断是否还有下一个元素和next():指针下移。将下移元素返回
2、集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
3、内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove().
@Test
publiv void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(new String("Tom"));
coll.add(new Person("邱周童",19));
coll.add(456);
coll.add(false);
//遍历元素
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//删除元素
iterator = coll.iterator();
while(iterator.hasNext()){
Object obj = coll.next();
if("Tom".equals(obj)){
iterator.remove();
}
}
//重新遍历元素,因为已经遍历过了一次,iterator的指针已经指到下面了,所以需要重新获取iterator
iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
JDK5.0新增了foreach循环,用于遍历集合,数组
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(new String("Tom"));
coll.add(new Person("邱周童",19));
coll.add(456);
coll.add(false);
for(Object obj:coll){
System.out.println(o);
}
}
List接口
ArrayList,LinkedList,Vector三者的异同?
相同点:三个类都实现了List接口,存储数据的特点相同:存储有序的,可以重复的数据
不同点:
ArrayList(主要实现类,JDK1.2):线程不安全,效率高,底层使用Object[] elementData存储
LinkedList:底层使用双向链表存储,对于频繁的插入和删除操作使用它效率比ArrayList高
Vector(古老实现类,JDK1.0):线程安全,效率低,底层使用Object[] elementData存储
Arraylist的源码分析:JDK7
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
…
list.add(11);//如果此次的添加导致elementData数组容量不够,则扩容。
默认情况下。扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
Arraylist的源码分析:JDK8
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{},并没有创建初始化长度为10的数组
list.add();//第一次调用add()方法时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
…
后续的添加和扩容操作与jdk7无异
小结:jdk7中的ArrayList的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式
LinkedList的源码分析
Linkedlist list = new LinkedList();内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中:Node定义为:
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倍。
List接口中的常用方法
void add(int index,Object ele):在index位置插入ele元素
boolean addAll(int index,Collection eles):从index位置开始将eles中所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在当前集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index,Object ele):设置指定index位置的元素为ele
List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合
public class ListTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom",12));
System.out.println(list);
//void add(int index,Object ele)
list.add(1,"BB");
System.out.println(list);
//boolean addAll(int index,Collection eles)
List<Integer> list1 = Arrays.asList(1, 2, 3);
list.addAll(list1);
System.out.println(list.size());
//Object get()
System.out.println(list.get(7));
//indexOf():如果不存在返回-1
System.out.println(list.indexOf(456));
//lastIndexOf()
System.out.println(list.lastIndexOf(123));
//Object remove(int index):是Collection接口方法Object remove(Object obj)的重载方法
list.remove(0);
System.out.println("删除后" + list);
//Object set()
list.set(1,"AA");
System.out.println(list);
//List subList(int fromIndex,int toIndex):返回从fromIndex位置的左闭右开的子集合
System.out.println(list.subList(2, 4));
}
}
总结:常用方法
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index,Object ele)
查:get(int index)
插:add(int index,Object ele)
长度:size()
遍历:增强for循环,迭代器,普通的循环
@Test
public void test(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
//方式一:Iterator迭代器
Iterator it = list.iterator
while(it.hasNext()){
System.out.println(it.next());
}
//方式二:增强for循环
for(Object obj:list){
System.out.println(obj);
}
//方式三:普通for循环
for(int i = 0;i < list.size();i ++){
System.out.println(list.get(i));
}
}
面试题
@Test
public void test(){
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);//1 2
}
private static void updateList(List list) {
list.remove(2);//按照索引删
list.remove(new Integer(2));//使用Collection接口中的remove方法返回结果为1 3
}
Set接口
存储无序的,不可重复的数据
HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
LinkedHashSet:HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
TreeSet:可以按照添加对象指定属性,进行排序。
Set接口中没有额外定义新的方法,使用的都是Collection中声明的方法。
要求:
向Set中添加的数据,其所在的类一定要重写hashcode()和equals()
重写的hashcode()和equals()尽可能保持一致性。相等的对象必须具有相等的散列码。
重写两个方法的小技巧:对象中用作equals()方法比较的Field,都应该用来计算hashcode
HashSet添加元素过程
Set:存储无序的,不可重复的数据
以HashSet为例说明:
1、无序性:不等于随机性,存储的数据在底层中并非按照数组索引的顺序添加,而是根据数据的哈希值
2、不可重复性:保证添加的元素按照equals()判断时,不能返回true;即:相同的元素只能添加一个。
添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashcode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中存放位置(即为:索引位置),判断
如果此位置上没有其他元素,则元素a添加成功。--------->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值;如果hash值不相同,则元素a添加成功。--------->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法;
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。-------->情况2
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk7:元素a放到数组中,指向原来的元素。
jdk8:原来的元素在数组中,指向元素a
总结:七上八下
LinkedHashSet
LinkedHashSet是作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
对于频繁的遍历操作,LinkedHashSet效率高于HashSet。
TreeSet
1、向TreeSet中添加的数据,要求是相同类的对象。
2、两种排序方式:自然排序(Comparable)和定制排序(Comparator)
set.add(34);
set.add(12);
set.add(43);
set.add(-10);
添加自定义属性(自然排序)
package com.cs.java;
import java.util.Objects;
public class User implements Comparable {
String name;
int age;
public User(){
}
public User(String name,int age){
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
//按照姓名从小到大排序,年龄从大到小排列
@Override
public int compareTo(Object o) {
if( o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if (compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入类型不匹配!");
}
}
}
@Test
public void test2(){
TreeSet treeSet = new TreeSet();
treeSet.add(new User("Tom",12));
treeSet.add(new User("Jack",12));
treeSet.add(new User("Jim",12));
treeSet.add(new User("Mike",12));
treeSet.add(new User("Jerry",12));
Iterator it = treeSet.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
定制排序
@Test
public void test3(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User){
User u1 = (User) o1;
User u2 = (User) o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jack",123));
set.add(new User("Jim",1));
set.add(new User("Mike",10));
set.add(new User("Jerry",15));
Iterator it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
Map接口
双列数据,存储key-value对的数据 -----类似于高中的函数:y = f(x)
HashMap
HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
底层:数组+链表(jdk7及之前)
数组+链表+红黑树(jdk8)
Map结构的理解:
Map中的key:无序的、不可重复的、使用Set存储所有的key —>key所在的类要重写equals()和hashcode()
Map中的value:无序的、不可重复的、使用Collection存储所有的value—>value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
HashMap底层实现原理(以jdk7为例)
HashMap map = new HashMap();
在实例化以后,底层创建了一个长度是16的一维数组Entry[] table.
…可能执行过多次put…
map.put(key1,value1):首先,调用key1所在类的hashcode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。如果此位置上的数据为空,此时的key1-value1添加成功。-------情况1
如果此位置上的数据不为空(意味着此位置上存放一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。-------情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals()方法,比较:如果equals()返回false:此时key1-value1添加成功。-------情况3
如果equals()返回true:使用value1替换相同key的value值。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题:扩容为原来的2倍,并将原有的数据复制过来。
jdk8相较于jdk7在底层实现方面的不同:
1、new HashMap():底层没有创建一个长度为16的Entry数组
2、jdk8底层的数组是:Node[],而非Entry[]
3、首次调用put()方法时,底层创建长度为16的数组
4、jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。
DEFAULT_INITIAL_CAPACITY:HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子: 16✖0.75 =>12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
LinkedHashMap
HashMap的子类
LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序来实现遍历。
原因:在原有的HashMap底层结构基础上,添加上了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
底层实现原理:
static class Entry<K,V> extends HashMap,Node<K,V>{
Entry<K,V> before,after;//能够记录添加的元素的先后顺序
Entry(int hash,K key,V value,Node<K,V> next){
super(hash,key,value,next);
}
}
TreeMap
TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
底层使用红黑树
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要按照key进行排序:自然排序、定制排序
Hashtable
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
Properties
常用来处理配置文件。key和value都是String类型
面试题
1、HashMap的底层实现原理?
2、HashMap和Hashtable的异同?
3、CurrentHashMap与Hashtable的区别?
Map接口中常用方法
增:Object put(Object key,Object value)
删除:Object remove(Object key)
清空:void clear()
Collections工具类
Collections:操作Collection、Map的工具类
reverse():反转list中的元素
shuffle():对list集合元素进行随机排序
sort():根据元素的自然顺序对指定List集合元素按升序排序
sort():根据指定的Comparator产生的顺序对list集合元素进行排序
swap():将指定list集合中的i处元素和j处元素进行交换
max():返回最大元素
min():返回最小元素
frequency():返回指定集合中指定元素的出现次数
copy():将src中的内容复制到dest中
replaceAll():使用新值替换
synchronizedxxx():返回线程安全的xxx
面试题:Collection和Collections的区别?
泛型和File
泛型的使用
jdk5新增的特性
/**
* 泛型的使用
*/
public class GenericTest {
@Test
public void test(){
ArrayList list = new ArrayList();
list.add(78);
list.add(79);
list.add(80);
list.add(81);
//问题一:类型不安全
//list.add("Tom");
for (Object o : list) {
//问题二:ClassCastException
int i = (Integer) o;
System.out.println(i);
}
}
@Test
//在集合中使用泛型的情况
public void test1(){
ArrayList<Integer> list = new ArrayList<>();
list.add(88);
list.add(89);
list.add(90);
list.add(91);
//方式一
for (Integer score : list) {
int i = score;
System.out.println(i);
}
//方式二
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
File类的使用
File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
File类声明在java.io包下
File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
后续File类的对象常会作为参数传递给流的构造器中,指明读取或写入的"终点".
1、如何创建File类的实例?
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)
2、相对路径:相较于某个路径下,指明的路径
绝对路径:包含盘符在内的文件或文件目录的路径
3、路径分隔符
windows:\
unix:/
@Test
public void test() {
//构造器1
File file1 = new File("hello.txt");
File file2 = new File("E:\\java\\Java、base\\JavaSenlor\\day08");
//构造器2
File file3 = new File("E:\\java\\Java、base\\JavaSenlor\\day08", "JavaSenior");
//构造器3
File file4 = new File(file3, "hi.txt");
}
常用方法
/**
* 常用方法
* getAbsolutePath():获取绝对路径
* getPath():获取路径
* getName():获取名称
* getParent():获取上层文件目录路径。若无,返回null
* length():获取文件长度
* lastModified():获取最后一次的修改时间,毫秒值
* <p>
* 如下两个方法适用于文件目录:
* String[] list():获取指定目录下的所有文件或者文件目录的名称数组
* File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组
*/
@Test
public void test1() {
File file1 = new File("hello.txt");
File file2 = new File("E:\\java\\Java、base\\JavaSenlor\\day08");
System.out.println(file1.getAbsolutePath());
System.out.println(file1.getPath());
System.out.println(file1.getName());
System.out.println(file1.getParent());
System.out.println(file1.length());
System.out.println(file1.lastModified());
System.out.println();
System.out.println(file2.getAbsolutePath());
System.out.println(file2.getPath());
System.out.println(file2.getName());
System.out.println(file2.getParent());
System.out.println(file2.length());
System.out.println(file2.lastModified());
}
@Test
public void test2() {
File file = new File("E:\\java\\Java、base\\JavaSenlor");
String[] list = file.list();
for (String s : list) {
System.out.println(s);
}
File[] files = file.listFiles();
for (File file1 : files) {
System.out.println(file1);
}
}
/**
* boolean renameTo(File dest):把文件重命名为指定的文件路径
* 比如:file1.renameTo(file2)为例:
* 要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
*/
@Test
public void test3() {
File file = new File("hello.txt");
File file1 = new File("E:\\java\\Java、base\\JavaSenlor");
boolean b = file.renameTo(file1);
System.out.println(b);
}
/**
* 判断功能
* isDirectory():判断是否是文件目录
* isFile():判断是否是文件
* exists():判读是否存在
* canRead():判断是否可读
* canWrite():判断是否可写
* isHidden():判断是否隐藏
*/
@Test
public void test5() {
File file1 = new File("hello.txt");
System.out.println(file1.isDirectory());
System.out.println(file1.isFile());
System.out.println(file1.exists());
System.out.println(file1.canRead());
System.out.println(file1.canWrite());
System.out.println(file1.isHidden());
}
/**
* 创建硬盘中对应的文件或文件目录
* createNewFile():创建文件,若文件存在,则不创建,返回false
* mkdir():创建文件目录。如果此文件目录不存在,就不创建了。如果此文件目录的上层
* mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
* <p>
* 删除磁盘文件或文件目录
* public boolean delete():删除文件或者文件夹
* 删除注意事项:
* Java中的删除不走回收站。
*/
@Test
public void test6() throws IOException {
File file = new File("hello.txt");
if (!file.exists()) {
file.createNewFile();
System.out.println("创建成功!");
}else{
file.delete();
System.out.println("删除成功!");
}
}
@Test
public void test7(){
//文件目录的创建
File file = new File("E:\\java\\Java、base\\JavaSenlor\\小邱邱");
boolean mkdir = file.mkdir();
if (mkdir){
System.out.println("创建成功!");
}
}
IO流
一、流的分类
按操作的数据单位不同分为:字节流,字符流
按数据流的流向不同分为:输入流,输出流
按流的角色的不同分为:节点流,处理流
【抽象基类】 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
二、流的体系结构
抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream | BufferedInputStream |
OutputStream | FileOutputStream | BufferedOutputStream |
Reader | FileReader | BufferedReader |
Writer | FileWriter | BufferedWriter |
字符流
文件的读入
public class FileReaderWriterTest {
/**
* 说明点:
* 1、read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
* 2、异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
*/
@Test
public void testFileReader(){
FileReader fr = null;
try {
//1、实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");
//2、提供具体的流
fr = new FileReader(file);
//3、数据的读入
//read():返回读入的一个字符。如果达到文件末尾,返回-1
//方式一
/* int data = fr.read();
while(data != -1){
System.out.print((char) data);
data = fr.read();
}*/
//方式二
int data;
while ((data = fr.read()) != -1){
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
//4、流的关闭操作
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//对read()操作升级:使用read的重载方法
@Test
public void testFileReader1(){
FileReader fileReader = null;
try {
//1、File类的实例化
File file = new File("hello.txt");
//2、FileReader流的实例化
fileReader = new FileReader(file);
//3、读入的操作
//read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾返回-1
char[] cbuf = new char[4];
int len;
while((len = fileReader.read(cbuf)) != -1){
//方式一
//错误的写法
/*for (int i = 0; i < cbuf.length; i++) {
System.out.print(cbuf[i]);
}*/
/* for (int i = 0; i < len; i++) {
System.out.print(cbuf[i]);
}*/
//方式二
String str = new String(cbuf, 0, len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileReader != null){
//4、资源的关闭
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件的写出
/**
* 从内存中写出数据到硬盘的文件里
* 说明
* 1、输出操作,对应的File可以不存在的。并不会报异常
* 2、
* File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
* File对应的硬盘中的文件如果存在:
* 如果流使用的构造器是:FileWriter(file,false)/FileWriter(file):对原有文件的覆盖
* 如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容。
*/
@Test
public void testFileWriter() throws IOException {
//1、提供File类的对象,指明写出到的文件
File file = new File("hello.txt");
//2、提供FileWriter的对象,用于数据的写出
FileWriter fw = new FileWriter(file,true);
//3、写出的操作
fw.write("I have a dream!\n");
fw.write("you need to have a dream!");
//4、流资源的关闭
fw.close();
}
实现文件的复制
@Test
public void testFileReaderFileWriter(){
FileReader fr = null;
FileWriter fw = null;
try {
//1、创建File类的对象,指明读入和写出的文件
File srcFile = new File("hello.txt");
File destFile = new File("hello2.txt");
//2、创建输入流和输出流的对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3、数据的读入和写出操作
char[] cbuf = new char[5];
int len;//j记录每次读入到cbuf数组中的字符的个数
while((len = fr.read(cbuf)) != -1){
//每次写出len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4、关闭流资源
try {
if (fw != null) {
fw.close();
}
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
不能使用字符流处理图片
字节流
/**
* 测试FileInputStream和FileOutputStream的使用
*
* 结论:
* 1、对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
* 2、对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
*
* @author ASUS
* date: 2021/1/7 15:54
* @since JDK 1.8
*/
@Test
public void testFileInputStream(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//
File srcFile = new File("face.jpg");
File destFile = new File("face1.jpg");
//
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[5];
int len;
while ((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null){
fos.close();
}
if (fis != null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//指定路径下文件的复制
public void copyFile(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null){
fos.close();
}
if (fis != null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void testCopyFile(){
long start = System.currentTimeMillis();
String srcPath = "";
String destPath = "";
copyFile(srcPath,destPath);
long end = System.currentTimeMillis();
System.out.println("复制操作花费的时间为:" + (end - start));
}
缓冲流(处理流的一种)
作用:提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区
处理流:就是"套接"在已有的流的基础上。
public class BufferedTest {
/**
* 实现非文本文件的复制
*/
@Test
public void BufferedStreamTest() {
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1、造文件
File srcFile = new File("face.jpg");
File destFile = new File("face1.jpg");
//2、造流
//2.1 造节点流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3、复制的细节:读取,写入
byte[] buffer = new byte[10];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4、资源关闭
//要求:先关闭外层的流,再关闭内层的流
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
转换流(处理流的一种)
/**
* 处理流之二:转换流的使用
* 1、转换流:属于字符流
* InputStreamReader:将一个字节的输入流转化为字符的输入流
* OutputStreamWriter:将一个字符的输出流转化为字节的输出流
* 2、作用:提供字节流与字符流之间的转换
* 3、解码:字节、字节数组----->字符数组、字符串
* 编码:字符数组、字符串----->字节、字节数组
* 4、字符集
*
* @author ASUS
* date: 2021/1/7 17:02
* @since JDK 1.8
*/
/**
* InputStreamReader的使用,实现了字节的输入流到字符的输入流的转换
* @throws IOException
*/
@Test
public void test1() throws IOException {
FileInputStream fis = new FileInputStream("hello.txt");
//参数2指定了字符集,具体使用了那个字符集,取决于文件hello.txt保存时使用的字符集
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
char[] cbuf = new char[20];
int len;
while ((len = isr.read(cbuf)) != -1){
String str = new String(cbuf, 0, len);
System.out.print(str);
}
isr.close();
}
标准的输入输出流
/**
* 1、标准的输入,输出流
* 1.1
* System.in:标准的输入流,默认从键盘输入
* System.out:标准的输出流,默认从控制台输出
* 1.2
* System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流
* <p>
* 1.3练习
* 从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入"e"
* 或者"exit"时,退出程序。
* <p>
* 方法一:使用Scanner实现,调用next()返回一个字符串
* 方法二:使用System.in实现。System.in---->转换流 ---->BufferedReader的readLine()
*
* @author ASUS
* date: 2021/1/7 17:23
* @since JDK 1.8
*/
public class OtherStreamTest {
public static void main(String[] args) {
BufferedReader br = null;
try {
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);
while (true) {
System.out.println("请输入字符串:");
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束");
break;
}
String u = data.toUpperCase();
System.out.println(u);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
打印流
/**
* 打印流:PrintStream和PrintWriter
* 1、提供了一系列重载的print()和println()
*
*/
@Test
public void test(){
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(new File("hello.txt"));
//创建打印输出流,设置为自动刷新模式
ps = new PrintStream(fos,true);
if (ps != null){
System.setOut(ps);
}
for (int i = 0; i <= 255; i++) {//输出ASCII字符
System.out.print((char)i);
if (i % 50 == 0){
System.out.println();//换行
}
}
}catch (FileNotFoundException e){
e.printStackTrace();
}finally {
if (ps != null){
ps.close();
}
}
}
数据流
/**
* 数据流
* DataInputStream和DataOutputStream
* 作用:用于读取或写出基本数据类型的变量或字符串
*/
@Test
public void test1() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("邱周童");
dos.flush();//刷新操作会将内存中的数据写入文件
dos.writeInt(23);
dos.flush();
dos.writeBoolean(true);
dos.flush();
dos.close();
}
/**
* 将文件中存储的基本数据类型和字符串读取到内存中,保存在变量中
* 注意点:读取不同类型的数据的顺序要当初写入文件时,保存的数据顺序要一致。
* @throws IOException
*/
@Test
public void test2() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
System.out.println("name=" + name);
System.out.println("age=" + age);
System.out.println("isMale = " + isMale);
dis.close();
}
对象流
/**
* 序列化过程:将内存中的java对象存到磁盘中或通过网络传输出去
* 使用ObjectOutPutStream实现
*/
@Test
public void test(){
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("我爱北京天安门"));
oos.flush();//刷新操作
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 反序列化:将磁盘文件中的对象还原为内存中的一个java对象
* 使用ObjectInputStream
*/
@Test
public void test1(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = ois.readObject();
String str = (String) obj;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
自定义类
/**
* Person需要满足如下的要求,方可序列化:
* 1、需要实现接口Serializable接口
* 2、当前类提供一个全局常量:serialVersionUID
* 3、除了当前Person类需要实现Serializable接口之外,
* 还必须保证其内部所有属性也必须是可序列化的。(默认情况下。基本数据类型都是可序列化的)
* 补充:ObjectInputStream和ObjectOutputStream不能序列化static和transient修饰的成员变量
*
*
* @author ASUS
* date: 2021/1/8 14:38
* @since JDK 1.8
*/
public class Person implements Serializable {
public static final long serialVersionUID = 34234232234L;
private String name;
private int age;
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
}
public class ObjectInputOutputStreamTest {
/**
* 序列化过程:将内存中的java对象存到磁盘中或通过网络传输出去
* 使用ObjectOutPutStream实现
*/
@Test
public void test(){
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("我爱北京天安门"));
oos.flush();//刷新操作
oos.writeObject(new Person("王铭",23));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 反序列化:将磁盘文件中的对象还原为内存中的一个java对象
* 使用ObjectInputStream
*/
@Test
public void test1(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = ois.readObject();
String str = (String) obj;
Person p = (Person) ois.readObject();
System.out.println(str);
System.out.println(p);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
随机存取文件流
/**
*RandomAccessFile的使用
*1、RandomAccessFile直接继承于java.lang.Object类,实现了DataInput,DatsOutput接口
*2、RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
*3、如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建
* 如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
*4、可以通过相关的操作seek(),实现RandomAccessFile“插入”数据的效果
*
*/
public class RandomAccessFileTest{
@Test
public void test(){
RandomAccessFile raf1 = new RandomAccessFile(new File("face.jpg","r"));
RandomAccessFile raf2 = new RandomAccessFile(new File("face.jpg","rw"));
byte[] buffer = new byte[1024];
int len;
while((len = raf1.read(buffer) != -1)){
raf2.write(buffer,0,len);
}
raf1.close();
raf2.close();
}
@Test
public void test(){
RandomAccessFile raf1 new RandomAccessFile("hello.txt","rw");q
raf1.write("xyz".getBytes());
raf1.close();
}
}
网络编程
网络编程中有两个主要的问题:
1、如何准确的定位一台或多台主机;定位主机上的特定的应用
2、找到主机后如何可靠高效的进行数据传输
网络编程中的两个要素:
1、对应问题一:IP和端口号
2、对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
通信要素一:IP和端口号
1、IP:唯一的标识Internet上的计算机(通信实体)
2、在Java中使用InetAddress类代表IP
3、IP分类:IPv4和IPv6;万维网和局域网
4、域名
5、本地回路地址:127.0.0.1对应着:localhost
6、如何实例化InetAddress:两个方法:getByName(String host)、getLocalHost()
两个常用方法:getHostName()/getHostAddress()
7、端口号:正在计算机上运行的进程。
要求:不同的进程有不同的端口号
范围:被规定为一个16位的整数0-65535.
8、端口号与IP地址的组合得出一个网络套接字:Socket
TCP的网络编程
/**
* 实现TCP的网络编程
* 例题:客户端发送文件给服务端,服务端将文件保存在本地。并返回发送成功给客户端。
*
* @author ASUS
* date: 2021/1/9 13:48
* @since JDK 1.8
*/
public class TCPTest2 {
@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("face.jpg"));
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();
//接收来自于服务器端的数据,并显示到控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len1;
while((len1 = is.read(buffer)) != -1){
baos.write(buffer,0,len1);
}
fis.close();
os.close();
socket.close();
}
@Test
public void server() throws IOException {
ServerSocket ss = new ServerSocket(9090);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("hello.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
//服务端给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("你好,已收到照片!".getBytes());
fos.close();
is.close();
socket.close();
ss.close();
}
}
UDP的网络编程
/**
* UDPd协议的网络编程
*
* @author ASUS
* date: 2021/1/9 14:23
* @since JDK 1.8
*/
public class UDPTest {
//发送端
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inetAddress = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inetAddress,9090);
socket.send(packet);
socket.close();
}
//接收端
@Test
public void receiver() throws IOException{
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
}
URL的网络编程
/**
* URL网络编程
* 1、URL:统一资源定位符,对应着互联网的某一资源地址
* 2、格式:
* http://localhost:8080/xxx
*
* @author ASUS
* date: 2021/1/9 14:43
* @since JDK 1.8
*/
public class URLTest {
public static void main(String[] args) throws IOException {
URL url = new URL("http://localhost:8080/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
InputStream is = urlConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("beauty3,jpg");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
//关闭资源
is.close();
fos.close();
urlConnection.disconnect();
}
}
反射
Java反射机制提供的功能
1、在运行时判断任意一个对象所属的类
2、在运行时构造任意一个类的对象
3、在运行时判断任意一个类所具有的成员变量和方法
4、在运行时获取泛型信息
5、在运行时调用任意一个对象的成员变量和方法
6、在运行时处理注解
7、生成动态代理
/**
* @author ASUS
* date: 2021/1/10 13:08
* @since JDK 1.8
*/
public class ReflectionTest {
//反射之前,对于Person类的操作
@Test
public void test(){
//1、创建Person类的对象
Person p1 = new Person("Tom",12);
p1.age = 10;
System.out.println(p1.toString());
p1.show();
//在Person类外部,不可以通过Person类的对象调用其内部私有结构。
}
//反射以后,对于Person的操作
@Test
public void test1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class clazz = Person.class;
//1、通过反射,创建Person类的对象
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("Tom", 12);
Person p = (Person) obj;
System.out.println(p.toString());
//2、通过反射,调用对象指定的属性,方法
Field age = clazz.getDeclaredField("age");
age.set(p,10);
System.out.println(p.toString());
//调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);
System.out.println("*******************************");
//通过反射,可以调用Person类的私有结构,比如:私有的构造器,方法,属性
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p1 = (Person) cons1.newInstance("Jerry");
System.out.println(p1);
//通过私有的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"QZT");
System.out.println(p1);
//调用私有的方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
showNation.invoke(p1,"中国");
}
}
关于java.lang.Class类的理解
1、类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾),接着使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
2、换句话说,Class的实例就对应着一个运行时类。
3、加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
获取Class的实例的方式
//获取Class的实例的方式
@Test
public void test2() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.cs.java.Person");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
//方式四:使用类的加载器:ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.cs.java.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz4);
}
类的加载器
/**
* 读取配置文件
* @throws IOException
*/
@Test
public void test3() throws IOException {
Properties prop = new Properties();
//此时的文件默认在当前的module下
//方式一
/*FileInputStream fis = new FileInputStream("jdbc.properties");
prop.load(fis);*/
//方式二:使用ClassLoader
//配置文件默认识别为:当前module的src下
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
prop.load(is);
String user = prop.getProperty("user");
String password = prop.getProperty("password");
System.out.println("user=" + user + "password=" + password);
}
通过反射创建类的对象
/**
* 通过反射创建对应的运行时类的对象
*
*
* @author ASUS
* date: 2021/1/11 13:47
* @since JDK 1.8
*/
public class NewInstanceTest {
@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class clazz = Person.class;
/**
* newInstance():调用此方法,创建对应的运行时类的对象
*
* 要想此方法正常的创建运行时类的对象,要求:
* 1、运行时类必须提供空参的构造器
* 2、空参的构造器的访问权限得够。通常,设置为public。
*
* 在javabean中要求提供一个public的空参构造器。原因:
* 1、便于通过反射,创建运行时类的对象
* 2、便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
*/
Person obj = (Person) clazz.newInstance();
System.out.println(obj);
}
反射的动态性
@Test
public void test2(){
for (int i = 0; i < 100; i++) {
int num = new Random().nextInt(3);//0,1,2
String classpath = "";
switch (num){
case 0:
classpath = "java.util.Date";
break;
case 1:
classpath = "java.lang.Object";
break;
case 2:
classpath = "com.cs.java.Person";
break;
}
try {
Object obj = getInstance(classpath);
System.out.println(obj);
}catch (Exception e){
e.printStackTrace();
}
}
}
/*
* 创建一个指定类的对象
* */
public Object getInstance(String classpath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName(classpath);
return clazz.newInstance();
}
动态代理
静态代理
/**
* 静态代理举例
* 特点:代理类和被代理类在编译期间,就确定下来了。
*
*
* @author ASUS
* date: 2021/1/11 14:49
* @since JDK 1.8
*/
public interface ClothFactory {
void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续收尾工作");
}
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类的对象
NikeClothFactory nike = new NikeClothFactory();
//创建代理类的对象
ProxyClothFactory clothFactory = new ProxyClothFactory(nike);
clothFactory.produceCloth();
}
}
动态代理
/**
* 动态代理举例
*
* @author ASUS
* date: 2021/1/11 15:02
* @since JDK 1.8
*/
interface Human {
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("超人喜欢吃" + food);
}
}
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
//当我们通过代理类的对象,调用方法a时,就会自动调用如下的方法:invoke()
//将被代理类要执行的方法a的功能声明在invoke()中
private Object obj;//需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("粑粑");
}
}
()时,保证父类有此构造器
*/
Person obj = (Person) clazz.newInstance();
System.out.println(obj);
}
## 反射的动态性
```java
@Test
public void test2(){
for (int i = 0; i < 100; i++) {
int num = new Random().nextInt(3);//0,1,2
String classpath = "";
switch (num){
case 0:
classpath = "java.util.Date";
break;
case 1:
classpath = "java.lang.Object";
break;
case 2:
classpath = "com.cs.java.Person";
break;
}
try {
Object obj = getInstance(classpath);
System.out.println(obj);
}catch (Exception e){
e.printStackTrace();
}
}
}
/*
* 创建一个指定类的对象
* */
public Object getInstance(String classpath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName(classpath);
return clazz.newInstance();
}
动态代理
静态代理
/**
* 静态代理举例
* 特点:代理类和被代理类在编译期间,就确定下来了。
*
*
* @author ASUS
* date: 2021/1/11 14:49
* @since JDK 1.8
*/
public interface ClothFactory {
void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续收尾工作");
}
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类的对象
NikeClothFactory nike = new NikeClothFactory();
//创建代理类的对象
ProxyClothFactory clothFactory = new ProxyClothFactory(nike);
clothFactory.produceCloth();
}
}
动态代理
/**
* 动态代理举例
*
* @author ASUS
* date: 2021/1/11 15:02
* @since JDK 1.8
*/
interface Human {
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("超人喜欢吃" + food);
}
}
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
//当我们通过代理类的对象,调用方法a时,就会自动调用如下的方法:invoke()
//将被代理类要执行的方法a的功能声明在invoke()中
private Object obj;//需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("粑粑");
}
}