JavaSE小结

JavaSE

一、String常量池

常量池从1.7开始从方法区转移到了堆中
在这里插入图片描述

intern方法

intern的处理是 先判断字符串常量是否在字符串常量池中,如果存在直接返回该常量,
如果没有找到,把堆区该对象的引用加入到字符串常量池中,以后拿到的是该字符串常量的引用,实际存在堆中;

1. Java字符串常量池是什么?为什么要有这种常量池?

① 存储在Java堆内存中的字符串池;
② 为了让数据不冲突进行共享等

2. 字符串常量池设计思想

● 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。

● JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化

① 为字符串开辟一个字符串常量池,类似于缓存区

② 创建字符串常量时,首先坚持字符串常量池是否存在该字符串,存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中

● 实现的基础

① 实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享

② 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串 常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。

3. 思考: 与String类型的数据进行拼接(+)的底层原理是什么?

String s = new String(“abc”);

方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”。

3.1 String字符串的拼接方式(三种)

说明
① 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
② 只要其中一个是变量,结果就在堆中。

第一种 字面值常量拼接

在常量池中进行,在字符串常量池中查找需要的字符串,如果找到,则指向给String引用,找不到则新建所需字符串,将地址指向给String引用。因为字符串常量池中不能存在相同字符串,每次拼接都要新建字符串,不能在原有字符串上进行修改。
在这里插入图片描述
第二种 变量和常量拼接
在这里插入图片描述
第三种 变量和变量拼接
在这里插入图片描述
字符串使用+拼接时,一旦有变量参与,在堆中,底层会转成StringBuilder并使用append完成拼接。运算完成后,在转换成String返回。
而且,这个转换后的String存在堆区。
在这里插入图片描述

二、Java值传递和引用传递

1. 值传递

在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。

2. 引用传递

引用也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给响应的形参,在方法体内,形参和实参指向同一块内存地址,对形参的操作会影响真实内容。

但是

引用传递,在Java中并不存在。

无论是基本类型和是引用类型,在实参传入形参时,都是值传递,也就是说传递的都是一个副本,而不是内容本身。

所以,在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递

三、集合的特点、常用方法

在这里插入图片描述

1. Collection(单列集合)

1.1 list(有序,可重复)

● ArrayList

底层数据结构是数组,查询快,增删慢,线程不安全,效率高

● Vector

底层数据结构是数组查询快,增删慢,线程安全,效率低

● LinkedList

底层数据结构是链表,查询慢,增删快,线程不安全,效率高。

1.2 Set(无序,唯一)

● HashSet

底层数据结构是哈希表,线程不安全,效率高,存的是无序数据。

哈希表依赖两个方法:hashCode()和equals()
执行顺序:
	首先判断hashCode()值是否相同
		是:继续执行equals(),看其返回值
		是true:说明元素重复,不添加
		是false:就直接添加到集合
		否:就直接添加到集合
	最终:自动生成hashCode()和equals()即可

● LinkedHashSet

底层数据结构由链表和哈希表组成,由链表保证元素有序,由哈希表保证元素唯一。

TreeSet
底层数据结构是红黑树(是一种自平衡的二叉树),TreeSet中的数据是自动排好序的,不允许放入null值。

如何保证元素唯一性?
    根据比较的返回值是否是0来决定
如何保证元素的排序?
    两种方式
        自然排序(元素具备比较性)让元素所属的类实现Comparable接口
        比较器排序(集合具备比较性)让集合接受一个Comparator的实现类对象

针对Collection集合我们到底使用谁呢?

唯一吗?

是:Set

排序吗?

是:TreeSet或LinkedHashSet
否:HashSet
如果你知道是Set,但是不知道是哪个Set,就用HashSet。

否:List

要安全吗?

是:Vector
否:ArrayList或者LinkedList

查询多:ArrayList
增删多:LinkedList
如果你知道是List,但是不知道是哪个List,就用ArrayList。

2. Map(双列集合)

Map集合的数据结构仅仅针对键有效,与值无关
存储的是键值对形式的元素,键唯一,值可重复

● HashMap

底层数据结构是哈希表,线程不安全,效率高。

● LinkedHashMap

底层数据结构由链表和哈希表组成,由链表保证元素有序,由哈希表保证元素唯一。

● Hashtable

底层数据结构是哈希表,线程安全,效率低

● TreeMap

底层数据结构是红黑树

3. 集合常用方法

3.1Collection集合

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e):把给定的对象添加到当前集合中 。
  • public void clear() :清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。
3.1.1 Iterator迭代器

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.IteratorIterator接口也是Java集合中的一员,但它与CollectionMap接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:

  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。

下面介绍一下迭代的概念:

  • 迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

Iterator接口的常用方法如下:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。
3.1.2 泛型概述

在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。

3.2 ArrayList集合

java.util.ArrayList 是大小可变的数组的实现,存储在内的数据称为元素。此类提供一些方法来操作内部存储的元素。

ArrayList 中可不断添加元素,其大小也自动增长。

3.2.1 常用方法

对于元素的操作,基本体现在——增、删、查。常用的方法有:

public boolean add(E e) :将指定的元素添加到此集合的尾部。

public E remove(int index) :移除此集合中指定位置上的元素。返回被删除的元素。

public E get(int index) :返回此集合中指定位置上的元素。返回获取的元素。

public int indexOf(Object obj):返回obj在集合中首次出现的位置。

public int size() :返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。

public boolean isEmpty():如果列表不包含元素,则返回true。

可将对象添加到集合 ,例:ArrayList<Student> list = new ArrayList<Student>();

3.3 HashSet集合

java.util.HashSetSet接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。

java.util.HashSet底层的实现其实是一个java.util.HashMap支持。

public boolean add(E e) :如果此 set 中尚未包含指定元素,则添加指定元素。

public void clear() :从此 set 中移除所有元素。

public boolean isEmpty():如果列表不包含元素,则返回true。

public E remove(Object o)) : 如果指定元素存在于此 set 中,则将其移除。

public int size() :返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。

3.4 HashMap集合

public void clear() :从此映射中移除所有映射关系。

public Set<Map.Entry<K,V>> entrySet():返回此映射所包含的映射关系的 Set 视图。

public Set<K> keySet():返回此映射中所包含的键的 Set 视图。

public V put(K key,V value):在此映射中关联指定值与指定键。

public V remove(Object key) : 如果存在,则在此映射中移除指定键的映射关系。

public int size() :返回此映射中的键-值映射关系数。

四、== 和 equals的区别

● == 可以用于基本数据类型和引用数据类型,而equals只能作用于引用数据类型,Equals底层就是使用的 ==

● == 用于基本数据类型比较的是值,而作用于引用数据类型比较的是对象的地址

● Equals比较的也是对象的地址,但是由于String包装类等重写了equals方法,所以比较的也是对象的内容

//==比较的是两个变量的值是否相等,对于引用型变量比较的是对象在内存中的地址是否相同;
//equals比较的是变量是否是对同一个对象的引用,即堆中的内容是否相同。
//举个通俗的例子来说,==是判断两个人是不是住在同一个地址,而equals是判断同一个地址里住的人是不是同一个

public class EqualsTest {
    public static void main(String[] args) {
        
        Integer aaa=new Integer(5);
        Integer bbb=new Integer(5);
        
        int a=10;
        int b=10;
        String str1=new String("justice");
        String str2=new String("justice");
        String str3;
        str3=str1;
        
        System.out.println(aaa==bbb);//false
        System.out.println(aaa.equals(bbb));//true
        System.out.println(a==b);//true
        
        System.out.println(str1==str2);//false
        System.out.println(str1.equals(str2));//true
        
        System.out.println(str1==str3);//true
        System.out.println(str1.equals(str3));//true
    }
}

五、IO(基本) 输入流、输出流

1. IO的分类

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到其他设备上的流。

格局数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流。

2. 顶级父类们

输入流输出流
字节流字节输入流
InputStream
字节输出流
OutputStream
字符流字符输入流
Reader
字符输出流
Writer

3.字节流

3.1 字节输入流【InputStream】

java.io.InputStream抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
  • public abstract int read(): 从输入流读取数据的下一个字节。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

小贴士:

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

3.1.1 FileInputStream类

java.io.FileInputStream类是文件输入流,从文件中读取字节。

构造方法

  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException

  • 构造举例,代码如下:
public class FileInputStreamConstructor throws IOException{
    public static void main(String[] args) {
   	 	// 使用File对象创建流对象
        File file = new File("a.txt");
        FileInputStream fos = new FileInputStream(file);
        // 使用文件名称创建流对象
        FileInputStream fos = new FileInputStream("b.txt");
    }
}
3.2 字节输出流【OutputStream】

java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  • public abstract void write(int b) :将指定的字节输出流。

小贴士:

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

3.2.1 FileOutputStream类

OutputStream有很多子类,我们从最简单的一个子类开始。

java.io.FileOutputStream类是文件输出流,用于将数据写出到文件。

构造方法

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

  • 构造举例,代码如下:
public class FileOutputStreamConstructor throws IOException {
    public static void main(String[] args) {
   	 	// 使用File对象创建流对象
        File file = new File("a.txt");
        FileOutputStream fos = new FileOutputStream(file);   
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("b.txt");
    }
}

4.字符流

4.1 字符输入流【Reader】

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • public void close() :关闭此流并释放与此流相关联的任何系统资源。
  • public int read(): 从输入流读取一个字符。
  • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
4.1.1 FileReader类

java.io.FileReader类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

小贴士:

  1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。

    idea中UTF-8

  2. 字节缓冲区:一个字节数组,用来临时存储字节数据。

构造方法

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。

  • 构造举例,代码如下:
public class FileReaderConstructor throws IOException{
    public static void main(String[] args) {
   	 	// 使用File对象创建流对象
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("b.txt");
    }
}
4.2 字符输出流【Writer】

java.io.Writer抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • void write(int c) 写入单个字符。
  • void write(char[] cbuf)写入字符数组。
  • abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
  • void write(String str)写入字符串。
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
  • void flush()刷新该流的缓冲。
  • void close() 关闭此流,但要先刷新它。
4.2.1 FileWriter类

java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。

  • 构造举例,代码如下:
public class FileWriterConstructor {
    public static void main(String[] args) throws IOException {
   	 	// 使用File对象创建流对象
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);    
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("b.txt");
    }
}

六、Map集合有几种遍历方式,底层?

1. Map集合的遍历方式

Map集合主要有三种遍历方式:keySet()、entrySet()、values()。但是,如果从API层面上进行细分的话有7种。这三种各自都有两种形式,for循环和Iterator迭代。还有最后一种,如果你是JDK8以上版本,还可以使用Lambda表达式forEach遍历。

1.1 遍历方式的具体用法

1.1.1 keySet()方式遍历-------for循环

long keySetForStartTime = System.nanoTime();
for (String key : map.keySet()) {
        map.get(key);
}
long keySetForEndTime = System.nanoTime();

1.1.2 keySet()方式遍历-------Iterator迭代

long keySetIteratorStartTime = System.nanoTime();
Iterator<String> iterator1 = map.keySet().iterator();
while (iterator1.hasNext()) {
        String key = iterator1.next();
        map.get(key);
}
long keySetIteratorEndTime = System.nanoTime();

1.1.3 entrySet()方式遍历-------for循环

long entrySetForStartTime = System.nanoTime();
for (Map.Entry<String, String> entry : map.entrySet()) {
        entry.getKey();
        entry.getValue();
}
long entrySetForEndTime = System.nanoTime();

1.1.4 entrySet()方式遍历-------Iterator迭代

long entrySetIteratorStartTime = System.nanoTime();
Iterator<Map.Entry<String, String>> iterator2 = map.entrySet().iterator();
while (iterator2.hasNext()) {
Map.Entry<String, String> entry = iterator2.next();
        entry.getKey();
        entry.getValue();
}
long entrySetIteratorEndTime = System.nanoTime();

1.1.5 values()方式遍历-------for循环

long valuesForStartTime = System.nanoTime();
Collection<String> values = map.values();
for (String value : values) {
        //.....
}
long valuesForEndTime = System.nanoTime();

1.1.6 values()方式遍历-------Iterator迭代

long valuesIteratorStartTime = System.nanoTime();
Iterator<String> iterator3 = map.values().iterator();
while (iterator3.hasNext()) {
         String value = iterator3.next();
}
long valuesIteratorEndTime = System.nanoTime();

1.1.7 JDK8-------Lambda表达式forEach遍历

long forEachStartTime = System.nanoTime();
map.forEach((key, value) -> {
        //......
});
long forEachEndTime = System.nanoTime();

JDK8Lambda表达式的forEach方法,其实就是一种语法糖,让你的代码更加简洁,使用更加方便,深入源码,我们可以很轻易的发现,它其实就是对entrySet遍历方式的一种包装而已。不信你看下面我贴的forEach源码。

forEach源码。本方法since1.8版本

default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch(IllegalStateException ise) {
            // this usually means the entry is no longer in the map.
            throw new ConcurrentModificationException(ise);
        }
        action.accept(k, v);
    }
}

结果分析:
直接抛出结论,entrySet()方式比keySet()方式效率更高

在忽略其他条件下,对于同一种遍历方式而言,Iterator迭代比for循环效率高。

当然,上述的结论只是说出了一半。其实是分两种情况的。在元素数量大的情况下,entrySet()性能确实是优于keySet()的,越大越明显。同样的,在小数据量的情况下,keySet()效率更高一点。

为啥大数据量时,entrySet()效率高呢?

其实,keySet()遍历,其实是相当于遍历了两次,第一次是转换为Iterator对象,第二次才是根据key从Map中取出对应的value值。而entrySet()转成Entry对象,只遍历一次。
当然,还有其他的一些原因,比如,map.get(key),这一操作注定了是计算密集型操作,很耗费CPU,在此不再过多说明。

values()方式的说明

values(),顾名思义,它得到的是Map中value的集合,因此,想要获取value对应的key值比较困难,因此使用上还是看需求。

在日常的开发工作中推荐使用哪一种遍历方式呢?

直接说结论:推荐使用entrySet()遍历方式,这依然是不二之选。并不是很建议使用keySet方式。如果项目是JDK8以上的版本,直接使用forEach吧,底层原理一样,语法更好更简洁,何乐而不为呢?

2. Map集合的底层

2.1 HashMap 的底层原理

HashMap的数据结构

数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端。

数组:

​ ● 数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表

​ ● 链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

哈希表

​ ● 那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

​ ● 哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为“链表的数组” ,如图:
在这里插入图片描述
在这里插入图片描述

● 从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数 组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。

● 比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

● HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

● 首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的 一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

七、异常

1. 顶级父类Throwable

在这里插入图片描述

2. thorw与throws

2.1 throws

​ 1.throws用来声明一个方法可能产生的异常,不做任何处理,而是将异常往上传,谁调用就抛给谁。
​ 2.throws 可以声明多个,用逗号(,)隔开。
​ 3.throws表示出现异常的一种可能性,并不一定会发生这些异常。
​ 4.在方法的扩号后面使用throws声明此方法可能抛出的异常。
​ 5.如果调用此方法的地方想要解决此异常,就通过 try-catch捕获并处理此异常。
​ 6.若调用此方法的地方不想要解决此异常,就在调用这个方法的方法后面继续声明此异常。
​ 7.所到了main方法仍在继续声明异常的话,则此异常最后将交于JVM处理。

2.2 throw

​ 1.throw : 用来抛出一个具体的异常类型
​ 2.在方法体内部
​ 3.有两种方式要么是自己捕获异常try…catch代码块,要么是抛出一个异常(throws 异常),执行throw则一定抛出了某种异常。
在这里插入图片描述

八、对Class类对象(常用方法)、作用

public static Class<?> forName(String className):传入完整的“包.类”名称实例化Class对象

public Constructor[] getContructors() :得到一个类的全部的构造方法

public Field[] getDeclaredFields():得到本类中单独定义的全部属性

public Field[] getFields():得到本类继承而来的全部属性

public Method[] getMethods():得到一个类的全部方法

public Method getMethod(String name,Class..parameterType):返回一个Method对象,并设置一个方法中的所有参数类型

public Class[] getInterfaces() :得到一个类中锁实现的全部接口

public String getName() :得到一个类完整的“包.类”名称

public Package getPackage():得到一个类的包

获取Class类对象的三种方法

●  Class.forName("类的全类名"); //属于最为常用的获取方法,高频率用于JDBC中 

●  类名.class //充当方法的参数较多 

●  类对象.getClass() //在能创建类对象的情况下使用

九、线程(开启)

方法一:继承Thread

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 创建线程对象
  4. 开启线程
public static void main(String[] args) {
    //创建线程对象
    MyThread myThread = new MyThread();
    //开启线程
    myThread.start();
}
//定义一个类继承Thread
public static class MyThread extends Thread {
    //重写run方法
    @Override
    public void run() {
        //todo task
        super.run();
    }
}

方法二:实现Runnable接口

  1. 定义一个类实现Runnable接口
  2. 重写run方法
  3. 在main方法(线程)中,创建线程对象,并启动线程.
    ① 创建线程类对象:
    Thread thread = new Thread(myThread);
    ② 调用线程对象的start方法:
    thread.start();
public static void main(String[] args) {
    MyThread myThread = new MyThread();
    //创建线程对象
    Thread thread = new Thread(myThread);
    //调用start方法
    thread.start();
}
//定义一个类实现Runnable接口
public static class MyThread implements Runnable {
    //重写run方法
    @Override
    public void run() {
        //todo task
    }
}

方法三:实现Callable接口

  1. 定义一个类实现Callable接口

  2. 重写call方法

  3. 在main方法(线程)中,创建Callable对象,创建任务将Callable对象传进来,创建线程对象将任务传进来,并启动线程.

    ① 创建Callable对象:
    Callable c = new MyCallable();

    ② 创建任务将Callable对象传进来:
    FutureTask task = new FutureTask<>©;

    ③ 创建线程类对象:
    Thread thread = new Thread(task);

    ④ 调用线程对象的start方法:
    thread.start();

public static void main(String[] args) {
    //创建Callable对象
    Callable<Integer> c = new MyCallable();
    //创建任务
    FutureTask<Integer> task = new FutureTask<>(c);
    //创建线程对象
    Thread thread = new Thread(task);
    //调用start方法
    thread.start();
}
//定义一个类实现Callable<V>接口
public static class MyCallable implements Callable<Integer> {
    //重写call方法
    @Override
    public Integer call() throws Exception {
        //todo task
        return null;
    }
}

十、权限 static final

1. static

1.1 类变量

当 static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改

该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。

​ ● 类变量:使用 static关键字修饰的成员变量。

1.2 静态方法

静态方法调用的注意事项:

​ ● 静态方法可以直接访问类变量和静态方法。

​ ● 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。

​ ● 静态方法中,不能使用this关键字。

​ ● static修饰的方法不能被重写

小贴士:

静态方法只能访问静态成员。

调用格式

被static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。

格式:

// 访问类变量 
类名.类变量名; 
    
// 调用静态方法 
类名.静态方法名(参数)

2. final

不可改变。可以用于修饰类、方法和变量。

​ ● 类:被修饰的类,不能被继承。

​ ● 方法:被修饰的方法,不能被重写。

​ ● 变量:被修饰的变量,不能被重新赋值。

十一、修饰符 public protected default private

在这里插入图片描述

十二、继承、多态

1. 继承

1.1 定义

继承:就是子类继承父类的属性行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

1.2 好处

① 提高代码的复用性

② 类与类之间产生了关系,是多态的前提

1.3 继承的格式

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

class 父类 { 
	... 
}
class 子类 extends 父类 { 
	... 
}
1.4 成员变量重名

子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super关键字,修饰父类成员变量,类似于之前学过的this

1.5 成员方法重名——重写(Override)

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复 写。声明不变,重新实现

注意事项

① 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。

② 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。

父类空间优先于子类对象产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。

Java只支持单继承,不支持多继承。

2. 多态

2.1 定义

多态: 是指同一行为,具有多个不同表现形式。

2.2 多态体现的格式
父类类型 变量名 = new 子类对象; 
变量名.方法名(); 
Fu f = new Zi(); 
f.method();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。

2.2 好处

实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。

可以使程序编写的更简单,并有良好的扩展。

2.3 引用类型转换

向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。

当父类引用指向一个子类对象时,便是向上转型。

使用格式:

父类类型 变量名 = new 子类类型(); 
如:Animal a = new Cat();

向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。

一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。

使用格式:

子类类型 变量名 = (子类类型) 父类变量名;:Cat c =(Cat) a; 

十三、工厂设计模式

工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

1. 简单工厂模式

用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)

从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的工厂类。

interface Car {
	void run();
}
class Audi implements Car {
	public void run() {
		System.out.println("奥迪在跑");
	} 
}
class BYD implements Car {
	public void run() {
		System.out.println("比亚迪在跑");
	} 
}
//工厂类
class CarFactory {
    //方式一
	public static Car getCar(String type) {
		if ("奥迪".equals(type)) {
			return new Audi();
		} else if ("比亚迪".equals(type)) {
			return new BYD();
		} else {
			return null; 
		} 
    }
    //方式二
    // public static Car getAudi() {
    // 		return new Audi();
    // }
    // public static Car getByd() {
    // 		return new BYD();
    // }
}
public class Client02 {
	public static void main(String[] args) {
		Car a = CarFactory.getCar("奥迪");
		a.run();
		Car b = CarFactory.getCar("比亚迪");
		b.run();
	} 
}

调用者只要知道他要什么,从哪里拿,如何创建,不需要知道。分工,多出了一个专门生产 Car 的实现类对象的工厂类。把调用者与创建者分离。

总结:

简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的实例对象。

缺点:对于增加新产品,不修改代码的话,是无法扩展的。违反了开闭原则(对扩展开放;对修改封闭)。

2. 工厂方法模式

用来生产同一等级结构中的固定产品。(支持增加任意产品)

为了避免简单工厂模式的缺点,不完全满足 OCP(对扩展开放,对修改关闭)。工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立的模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。

interface Car{
	void run();
}
//两个实现类
class Audi implements Car{
	public void run() {
	System.out.println("奥迪在跑");
	} 
}
class BYD implements Car{
	public void run() {
		System.out.println("比亚迪在跑");
	} 
}
//工厂接口
interface Factory{
	Car getCar();
}
//两个工厂类
class AudiFactory implements Factory{
public Audi getCar(){
	return new Audi();
	} 
}
class BydFactory implements Factory{
	public BYD getCar(){
		return new BYD();
	} 
}
public class Client {
	public static void main(String[] args) {
		Car a = new AudiFactory().getCar();
		Car b = new BydFactory().getCar();
		a.run();
		b.run();
	} 
}

总结:

简单工厂模式与工厂方法模式真正的避免了代码的改动了?没有。在简单工厂模式中,新产品的加入要修改工厂角色中的判断语句;而在工厂方法模式中,要么将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就像上面的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。面对这种情况,Java 的反射机制与配置文件的巧妙结合突破了限制——这在Spring 中完美的体现了出来。

3. 抽象工厂模式

用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。

而且抽象工厂模式是三个里面最为抽象、最具一般性的。抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。

而且使用抽象工厂模式还要满足一下条件:

① 系统中有多个产品族,而系统一次只可能消费其中一族产品。

② 同属于同一个产品族的产品以其使用。

看过了前两个模式,对这个模式各个角色之间的协调情况应该心里有个数了,我就不举具体的例子了。

十四、单例设计模式

1. 饿汉式

顾名思义,饿汉式就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。代码如下:

public class Singleton { 
	private static Singleton = new Singleton(); 
	private Singleton() {
    } 
	public static getSignleton(){ 
		return singleton; 
	}
}

这样做的好处是编写简单,但是无法做到延迟创建对象。但是我们很多时候都希望对象可以尽可能地延迟加载,从而减小负载,

所以就需要下面的懒汉式

2. 懒汉式

单线程写法

这种写法是最简单的,由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,如果是null就new一个来,最后返回singleton对象。这种方法可以实现延时加载,但是有一个致命弱点:线程不安全。如果有两条线程同时调用getSingleton()方法,就有很大可能导致重复创建对象。

public class Singleton { 
    private static Singleton singleton = null; 
    private Singleton(){
    } 
    public static Singleton getSingleton() 
    { 
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton; 
    }
}

考虑线程安全的写法

这种写法考虑了线程安全,将对singleton的null判断以及new的部分使用synchronized进行加锁。同时,对singleton对象使用volatile关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。如此即可从语义上保证这种单例模式写法是线程安全的。注意,这里说的是语义上,实际使用中还是存在小坑的。

public class Singleton { 
	private static volatile Singleton singleton = null; 
	private Singleton(){
	} 
	public static Singleton getSingleton(){ 
		synchronized (Singleton.class){ 
			if(singleton == null){ 
				singleton = new Singleton(); 
			}
		}
		return singleton; 
	}
}

十五、静态代理

1. 什么是静态代理模式

​ 代理的概念:生活中的代理是很常见的,比如代购、律师、中介等,他们都有一个共性就是帮助被代理人处理一些前前后后的事情。而被代理人只需要专注做自己要做的那部分事情就可以了。
​ Java中的代理也是类似的,代理模式可以实现帮助被代理者完成一些前期的准备工作和后期的善后工作,但是核心的业务逻辑仍然是由被代理者完成。

2. 静态代理模式的构成

静态代理模式由三个部分构成:

① 一个公共的接口

② 一个代理角色

③ 一个被代理角色

代码示例:

①:创建一个公共的接口,然后定义一个方法

//结婚的接口,接口中定义一个happyMarry的方法
public interface Marry{
    void happyMarry();//愉快的结婚
}

②:创建一个代理角色

//创建一个代理角色(婚庆公司),婚庆公司帮你布置婚礼现场以及婚礼后的收尾工作
class WeddingCompany implements Marry{
	private Marry target;
	public WeddingCompany(Marry target) {
    this.target = target;
}
   @Override
   public void happyMarry() {
    	ready();
		this.target.happyMarry();
		after();
   }
   private void ready(){
   		System.out.println("婚礼策划");
   }
   private void after(){
        System.out.println("收尾工作");
   }
}

③:创建一个被代理角色

class You implements Marry{
    @Override
    public void happyMarry() {
        System.out.println("下个月我要去和我的爱人结婚了。。。哈哈哈");
    }
}

优点:被代理类只需要专注于自己的核心代码即可,剩下的交给代理类就可以了,使得代码更加简洁,分工明确。
缺点:一个真实角色就会产生一个代理角色,代码量翻倍了。

十六、反射

反射:框架设计的灵魂

  • 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
  • 反射:将类的各个组成部分封装为其他对象,这就是反射机制
    • 好处:
      1. 可以在程序运行过程中,操作这些对象。
      2. 可以解耦,提高程序的可扩展性。

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小成同学_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值