天天看点

Java 基础

目录

  • 基础中的基础
    • 常见java名词以及区别(JDK、SDK、JRE)
    • java与C++的区别或好处
    • java和javax
    • “== ”与equals( )的区别
      • 这里提供一个重写了的equals( )方法
      • Java中String类的equals( )的源码
    • hashCode( )与 equals( )
      • 为什么在重写equals( )的时候,要同时重写hashCode( )方法?这里用一个例子来说明这个问题;
    • 包装类等相关知识
    • 关于静态方法与普通方法
    • java类的加载执行顺序
    • String、StringBuffer、StringBuilder区别
      • String类的实例化方式:
      • 话不多说,直接上例子!
      • 涉及到拼接字符串问题的例子
      • 好的,进入正题,三者的区别
      • 关于扩容的问题,会拉一个专项单说!
    • Object类的常见方法
      • native的方法 用于返回当前运行时对象的Class对象,使用了final关键字,不允许重写
      • 返回对象的哈希值
      • 用于比较两个对象的地址值是否相等,但是可以经过重写以达到比较值的目的
      • 用于创建当前对象的一份拷贝
      • 返回对象实例的哈希码的16进制的字符串,建议所有Object的子类都重写该方法
      • native方法,final方法,任意一个在此对象监视器上的线程(解锁),有多个的情况下就任意唤醒一个
      • 跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程
      • native方法,final方法,暂停线程的执行。
      • 实例被垃圾回收器回收的时候触发的操作
    • 反射
    • 异常
      • Throwable类的常用方法
    • I/O流
      • 节点流:直接从数据源或者目的地读写数据;
      • 处理流:不能直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能;
      • InputStream 和Reader 是所有输入流的基类
      • OutputStream和Writer则为相对应的字节输出流和字符输出流
      • 节点流(文件流)
      • 处理流之缓冲流
      • 处理流之转换流
      • 处理流之对象流

  • JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
  • JRE是Java运行时环境,它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
  • SDK是Software Development Kit 一般指软件开发包,可以包括函数库、编译程序等。
OpenJDK与OracleJDK的区别是,OpenJDK是开源的,但是他是不稳定的,发布频率相对比较高,OracleJDK可以理解为定制版的OpenJDK,更加稳定,但是不是完全免费的,因为其中加入了商业的部分。在响应性和 JVM 性能方面,Oracle JDK 提供了更好的性能;

这里就用java的好处来介绍了

  • java简单易学,java没有提供指针操作内存,从而保证了内存的安全;
  • 两者都支持封装、继承、多态;
  • Java有自己的垃圾回收机制,程序员不需要自己通过编程进行内存回收;
  • Java只支持单继承(接口支持多实现),C++支持多继承;

java和javax都只是一个名字,没有区别,都是javaAPI的组成部分,只是先来后到的关系;

  • == 比较的是基本类型的时候,会比较其值,如果比较的是引用类型的时候会比较其地址值(对于java中只有值传递这个说法来看,== 比较的都是值,只是说引用变量的值为地址值);
  • equals( )方法的情况就是比较复杂的了。
    • 类没有覆盖 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
    • 类覆盖了 equals()方法 :一般我们都覆盖 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
@Override
public boolean equals(Object o) {
    if (this == o) {
        return true; // 提高效率
    }
    if (o == null || getClass() != o.getClass()){
        return false;  // 反射  提高效率
    }
    Person person = (Person) o; // 向下强转型
    return age == person.age && Objects.equals(name, person.name); // 进行比较 并返回布尔值
}
           
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
           

  • hashCode( )

    的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。

    hashCode()

    定义在 JDK 的

    Object

    类中,这就意味着 Java 中的任何类都包含有

    hashCode( )

    函数。另外需要注意的是:

    Object

    hashcode

    方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。(散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!
  • equals( )就是另一个方法了,我在上边也详细的描述了equals( )的用法及其作用,这里不作赘述;
为什么在重写

equals( )

的时候,要同时重写

hashCode( )

方法?这里用一个例子来说明这个问题;

当我们向HashSet中存放一个值得时候,他就会首先会计算一下他的哈希值,从而确定它将被存到的位置。如果这个哈希值对应的位置上是有对象存在的,那么

equals( )

就会被调用出来比较两个对象的地址值,如果

equals( )

返回的值为

FALSE

,则成功让其加入

HashSet

; 如果equals( )返回为

true

,那么就不允许加入

HashSet

(hashset不允许存放重复的对象);

包装类的自动封装、拆箱;

  • 装箱:将基本类型转换为包装类
  • 拆箱:将包装类转换为基本类型
包装类中,大部分都实现了常量池技术,Byte、Short、Integer、Long等四种包装类,都有默认数值对象存在于常量池中【-128127】;Character则是存在有【0127】范围内的缓存数据。在这种常量池存在的背景下,我们只要是直接赋值的,那么就会首先去常量池查看有没有,如果没有才会创建对象,如果有的话就会直接取出来赋值。如果是通过new出来的包装类型,那么就不会从常量池查看,而是直接创建对象。

静态方法、静态变量会在类加载的时候就进行分配内存,随着类的加载而加载;而普通方法会在创建对象的时候才进行初始化,所以这就是为什么静态方法不能够调用非静态变量以及非静态方法的原因。

  • 有继承关系的加载顺序
    1. 首先会加载父类的静态字段或者静态代码块
    2. 子类的静态字段或者静态代码块
    3. 父类的普通变量、普通代码块
    4. 父类的构造方法
    5. 子类的普通变量、普通代码块
    6. 子类的构造方法
  • 没有继承关系的类加载顺序
    1. 加载类的静态变量、静态代码块
    2. 类的构造器

String是一个非常常见且特殊的类。String是一个数据类型,代表字符串;其底层是通过 final修饰的字符数组来保存字符串的,所以String是不可变的,赋值被改变时,只会创建一个新的引用!

在Java9之后,String、StringBuilder、StringBuffer等的底层实现都是字节数组byte[] value;
  1. 直接通过字面量赋值;这种方式是java中唯一一个不需要new就产生的对象,在java中他被叫做直接量;
  2. 通过new创建对象的方式;
两个方法创造的对象,终将指向常量池中的同一个方法区里的常量池中的数据的地址;形式赋值产生的直接量在创建的时候,会在常量池中先查看是否存在内容相同的字符串,有的话会直接将他指向常量池存在的已有的相同字符串,如果常量池没有的话,就会创建一个字符串放入常量池;
String s1 = "abc";
String s2 = "abc";

String s3 = new String("abc");
String s4 = new String("abc");

System.out.println(s1 == s2); // true 
System.out.println(s1 == s3); // false
System.out.println(s3 == s4); // false
           
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	只要有一个值是来自于变量的,那么拼接结果就会存在于堆中,在堆空间中new一块空间出来
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
           

intern():在调用”ab”.intern()方法的时候会返回”ab”,但是这个方法会首先检查字符串池中是否有”ab”这个字符串,如果存在则返回这个字符串的引用,否则就将这个字符串添加到字符串池中,然会返回这个字符串的引用。

  • String:底层final的,不可变的字符序列
  • StringBuffer:底层非final的,可变的字符序列,线程安全,但是效率偏低
  • StringBuilder:底层非final的,可变的字符序列,效率高,但是线程不安全

上述三者的共同点是:java8之前包括8,他们的底层实现都是的字符数组

char [ ]

,在java9以及之后的版本中改为了 字节数组;

StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类

三者的效率:StringBuilder > StringBuilder > String

  1. 操作少量的数据: 适用 String
  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

Object是祖宗类!

public final native Class<?> getClass(); 
           
public native int hashCode();
           
public boolean equals(Object obj);
           

当Object没有实现Cloneable接口时,没有实现clone( )方法并且进行调用的时候,就会抛出异常CloneNotSupportException;

对于任何对象的表达式

a.clone( ) != a

为true,且

a.clone().getClass() == a.getClass()

为ture

protected native Object clone() throws CloneNotSupportedException ;
           
public String toString();
           
public final native void notify();
           
public final native void notifyAll();
           
注意:sleep方法没有释放锁,而wait方法释放了锁 ,timeout是等待时间。
public final native void wait(long timeout) throws InterruptedException
           
protected void finalize() throws Throwable;
           

通过反射可以获得、调用一个类的所有属性和方法;他可以帮助我们获得在运行时分析类、执行类中方法的能力;

优缺点:

  • 优点:写代码时更加灵活,为各种框架提供了开箱即用的便捷;
  • 缺点:具有安全问题,比如可以无视泛型的安全检查,其次反射也会影响性能,但是在框架中是可以忽略不计的;

其中动态代理就用到了反射原理!(待梳理动态代理)

public class DebugInvocationHandler implements InvocationHandler {
    
    // 代理类中的真实对象    
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}
           

Spring中的bean的注解注册,也是得益于反射原理!

异常的祖宗类是

java,lang

包下的

Throwable

类。Theowable类有两个重要的子类,我们从图中就可以得知是Error和Exception,他们又各自拥有很多子类;Error是虚拟机无法处理的(只能尽量避免),而Exception可以认为的处理或者交给虚拟机进行处理。

其中有编译检查异常和编译不检查异常(运行时异常)
// 返回异常发生时的简要概述
public String getMessage( );

// 返回异常对象本地化的信息
public String getLocalizedMessage( );

// 在控制台上打印Throwable对象封装的异常信息
public void printStackTrace();
           

按照流向可以分为输入流和输出流;按照操作单元来分的话,可以分为字节流和字符流;按照流的角色来划分的话,可以划分为节点流和处理流;

  • InputStream(输入)/Reader(读入): 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream(输出)/Writer(写出): 所有输出流的基类,前者是字节输出流,后者是字符输出流。

InputStream的经典实现是FileInputStream,存在的方法有

int read( )

以及他的重载形式

int read(byte [ ] b)

int read(byte [ ] b,int off,int len )

等,用来读取字节(非文本文件);

Reader的经典实现就是FileReader,存在方法有

int read( )

int read(char [ ] c)

int read( char [ ] c,int off,int len )

;用来读取字符;

先flush( ) 刷新后,再关闭流;flush操作会刷新输出流并强制写出所有缓冲的输出字节或字符

FileOutputStream,用于写出字节文件(非文本类型文件);

FileWriter,用于写出字符类型的文件(文本文件);

节点流的作用在于讲一个已存在的文件,加载到程序中,从而进行操作。

读取文件:

1、建立一个流对象,将一个已经存在的文件加载进流
FileReader fr = new FileReader("hello.txt");
2、创建一个临时存放数据的数组
char [] buffer = new char[1024];
3、用数组盛放入进来的文件流
fr.read(buffer);
4、关闭资源
fr.close();
           

代码:

FileReader fr = null;
try{
    fr = new FileReader("C:\\hello.txt");
    char [] buffer = new char[1024];
    int len;
    while ((len = fr.read()) != -1){
        System.out.println(new String(buffer,0,len));
    }catch(IOException e){
        System.out.println("捕获异常:" + e.getMessage());
    }finally{
        if(fr != null){
            try{
                fr.close();
            }catch(IOException e)
                System.out.println("捕获异常:" + e.getMessage());
        }
    }
}
           

写入文件:

定义文件路径时,可以使用"/"或者"\"。

在写入一个文件时,使用构造器FileOutputStream(file),则会覆盖写文件;使用构造器FileOutputStream(file,true),则会追加文件内容。

1、创建流对象,建立数据存放文件
FileWriter fw = new FileWriter("hello.txt");
2、调用流对象的写入方法,将文件写入流
fw.write("hello java,hello world");
3、关闭资源文件,并将流中的数据清空到文件中
// fw.flus();
fw.close();
           
FileWriter fw = null;
try{
    fw = new FileWriter("hello.txt");
    fw.write("hello world");
}catch(Exception e){
    e.printStackTrace();    
}finally{
    try{
        if(fw != null){
            fw.close();
        }
    }catch(IOException e){
        System.out.println(e.getMessage());
    }
}
           

缓冲流要套接在相应的节点流之上,根据数据操作单位可以把缓冲流分为

BufferedInputStream

BufferedOutputStream

BufferedReader

BufferedWriter

。为了提高读写的速度,在使用这些流类的时候,会创建一个内部缓冲区数组,默认使用8k大小的缓冲区。缓冲流要套接在相应的节点流上。

  • 当使用BufferedInputStream读取文件的时候,他会一次性在文件中读取8K大小的字节,存到缓冲区,缓冲区满的时候,才从文件中读取下一个8K;
  • 向文件中写入数据字节时,不会直接写入,而是先写满缓冲区,再将缓冲区的数据一次性的写到文件中。使用flush( )方法可以一强制将缓冲区的内容全部写入输出流;
  • 关闭流时,只需要关闭最外层流即可,关闭最外层流也会关闭相对应的内层节点流;
  • 如果是带有缓冲区的流对象的close( ),方法,会在关闭流之前进行刷新缓存区。关闭之后就不能进行写出操作;

图解:

BufferedReader br = null;
bufferedWriter bw = null;
try{
    br = new BufferedReader(new FileReader("D:\\hello.txt"));
    bw = new BufferedWriter(new FileWriter("D:\\hi.txt"));
    String str = null;
    while((str = br.readLine()) != null){
        bw.write(str);
        bw.newLine(); // 写入分隔符
    }
    bw.flush(); // 刷新缓存区,将缓存区的内容一次性全部写入文件
}catch(IOException e){
    System.out.println(e.getMessage());
}finally{
    // 关闭资源 在这里关掉最外层的即可
    try{
        if(br != null){
            br.close();
        }
    }catch(IOException e){
        System.out.println(e.getMessage());            
    }
    try{
        if(bw != null){
            bw.close();
        }
    }catch(IOexception e){
        System.out.println(e.getMessage());
    }
}
           

转换流,提供了在字节流和字符流之间的转换方法;

  • InputStreamReader:将InputStream转换为Reader
  • OutputStreamWriter:将Writer装换为OutputStream

字节流中的数据都是字符时,转成字符流操作更高效,我们会经常使用转换流处理文件乱码问题。实现编码和解码功能;

// 构造器

// 输入
public InputStreamReader(InputStream is);
public InputStreamReader(InputStream is,"gbk"); // 指定字符集

// 输出
public OutputStreamWirter(OutputStream os);
public OutputStreamWirter(OutputStream os,"utf-8"); // 指定字符集
           

代码

// 文件输入输出流
FileInputStream fis = new FileInputStream("hello.txt");
FileOutputStream fos = new FileOutputStream("helloCopy.txt");
// 处理流-转换流
InputStreamReader isr = new InputStreamReader(fis,"gbk");
OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
// 处理流-缓冲流
BufferedReader br = new BufferedReader(isr);
BufferedWriter bw = new BufferedWriter(osw);

String str = null;
while((str = br.readLine()) != null){
    bw.write(str);
    bw.newLine();
    bw.flush();
}
// 关闭流操作
try{
    if(br != null){
        br.close();
    }	
}catch(IOException e){
    System.out.println(r.getMessage());
}

try{
    if(bw != null){
        bw.close();
    }
}catch(IOException e){
    System.out.println(e.getMessage());
}

           
System.in和System.out分别代表了系统标准的输入和输出设备,System.in的类型是InputStream;System.out的类型是PrintStream,是OutputStream的子类

ObjectInputStream和ObjectOutputStream,分别用于反序列化读取和序列化保存基本数据类型或者对象。但是不能序列化static或transient修饰的成员变量

序列化:可以将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。(实现对象平台无关性)

序列化:写出

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.txt"));
Person person = new Person("zhangsan",15);
oos.write(p);
oss.flush();
oss.close();
           

反序列化:读入

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.txt"));
Person person = (Person) ois.readObject();
String str = person.toString();
System.out.println(str);
ois.close();
           
上一篇: Java 异常