随笔- 23  文章- 0  评论- 0  阅读- 1893 

基本数据类型

boolean 8位,byte 8位,short 16位,char 16位,int 32位,float 32位,double 64位,long 64位

包装类:Integer、Long、Float、Double、Short、Byte、Character 、Void 和 Boolean

数值类型的级别从低到高分别为:
byte,char,short(这三个平级)—>int—>long—>float—>double
其中由低级别转到高级别,是属于自动类型转换;如果需要由高级别向低级别转换需要强制类型转换

大整数转换成浮点型时可能会丢失一定的精度,两种不同数值类型的数相加,将触发自动类型转换。

int x = 1;
x += 3.5;//这种情况将触发强制类型转换,结果4

java规定:如果对比java类型小的数据做运算,java编译时会强制将他们统一转换成int型,如果高于int,最终数据结果会取其中最高的一个

访问权限符

同一个类 同一个包 不同包的子类 不同包的非子类
Private
Default
Protected
Public

default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问;被protected修饰的类、属性以及方法不仅能在同一个包内进行访问,不同包的子类中也能够访问;

ArrayList.toArray

ArrayList.toArray();
ArrayList.toArray(T[]  a);
ArrayList.toArray(new Integer[5]);

Hashset.toArray();

对于第一个重载方法,是将list直接转为Object[] 数组;第二种方法是将list转化为你所需要类型【该类型不能是基础类型】的数组

输入输出

从文件、网络、内存中读取字节序列的对象称为输入流,而向其中写入字节序列的对象称为输出流。抽象类InputStream、OutputStream构成了输入输出类的基础。对于Unicode字符,可以使用Reader和Writer的子类

int read()//InputStream接口方法:读入一个字节,并返回读入的字节,或者在遇到结尾时返回-1
void write(int b)//OutputStream接口方法

read和write方法在执行时都将阻塞,直到字节读入或者写出。当完成输入输出流的读写时,应该调用close方法关闭它。

System.out.print将它的参数显示在命令窗口,并将输出光标定位在所显示的最后一个字符之后。

System.out.println 将它的参数显示在命令窗口,并在结尾加上换行符,将输出光标定位在下一行的开始。

System.out.printf是格式化输出的形式。

final

final变量只能被赋值一次;final类不允许被继承【final类所有方法都被隐藏地标识为final】;final方法不允许被子类覆写。

不可变字符串String

虽然导致操作字符串的效率变低,但是可以让编译器在字符串常量池中共享字符串【也只有常量池中的字符串是共享的】。==只是比较对象的地址或者元素的值,对于字符串的比较,要通过equals比较。

空串与Null串是不同的,可以用str.length==0或者str==null来检测是否是空串或者是NULL串。

如果需要由较短的字符串构建字符串,则考/987所有的字符串需要在一个单线程中编辑,StringBuffer允许采用多线程的方式添加或删除字符操作,但是效率比StringBuilder低。

String.join()
s.equals(t)//用于检测两个字符串的内容是否相等,而等于号只是检查两个引用是否引用的是同一个字符串
    
#截取
public String substring(int index);
public String substring(int begin, int end);
#转换
public char[] toCharArray();
public byte[] getBytes();
public String[] split(String regex);//正则表达式 如果要切割. 则必须使用//.

空串与Null串

空串 "" 是长度为 0 的字符串 , 空串是一个java对象,有自己的串长度和内容,而Null串表示没有与任何字符串关联

//检查方式
if (str.length() == 0)
if (str.equals(""))
    
//检查既不是空串也不是Null串
if(str!=null && str.length()!=0)

原码&补码&反码

通常,在计算机用一个数的最高位存放符号, 正数为0, 负数为1

原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:

[+1]原 = 0000 0001

[-1]原 = 1000 0001

负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.

[+1] = [00000001]原 = [00000001]反

[-1] = [10000001]原 = [11111110]反

负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

[+1] = [00000001]原 = [00000001]反 = [00000001]补

[-1] = [10000001]原 = [11111110]反 = [11111111]补

正数的反码和补码与原码相同

位运算符

位运算符是在补码的基础上操作的

value >> num:num 为value 移动的位数,符号位不变,左边补上符号位

value>>>num:只会用0填充高位

value<<num:用0填充低位

异或:^,同0异1

任何数和他本身进行异或为0;

条件控制

Java 提供了一种带标签的 break语句 ,标签必须放在希望跳出的最外层循环之前, 并且必须紧跟一个冒号 ; 对于任何使用break语句的代码都需要检测循环是正常结束, 还是由 break 跳出。

label:
for (int x = 0; x < 7; x++) 
    for (int i = 0; i < 4; i++) 
        if(i==2&&x==1) break label;

四大函数式接口

接口中的所有方法自动地属于 public,接口中声明方法不必提供关键字;而在实现接口时, 必须把方法声明为 public。

函数型接口:只要是函数型接口,就可以用lambda表达式简化

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);   
}

断定型接口:返回类型只能是boolean类型

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

消费者接口:只有输入没有返回值

@FunctionalInterface
public interface Consumer<T> {
        void accept(T t);
}
//or
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

供给型接口:只有返回值

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

应用举例

ArrayList<Integer> list = new ArrayList<>();
list.forEach(u->System.out.println(u));
//or
list.forEach(System.out::println);

覆写(override)

  • 覆写方法必须和被重写方法必须有相同的函数签名【方法名称和参数】。返回值可以是原返回类型的子类型。
  • 覆写方法的可见性要高于被覆写方法【被覆写方法为public时,子类方法一定要声明为public】
  • 覆写和被覆写的方法须同时为static的,或同时为非static的。
  • 子类方法抛出的异常范围不能大于被覆写方法的抛出的异常范围。如果父类没有抛出异常, 那么子类也不可以。

覆写Object.equals方法

显式参数命名为 otherObject,检测this与otherObject是否引用同一个对象【自反性】,检测 otherObject 是否为 null,比较 this 与 otherObject 是否属于同一个类,将 otherObject 转换为相应的类类型变量,对所有需要比较的域进行比较了。

Object.hashcode

public native int hashCode();

Object有一个默认的散列码函数,其值为对象的存储地址,String重写了散列码函数,其值由内容决定。

Equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true, 那么 x.hashCode( ) 就必须与 y.hashCode( ) 具有相同的值。

Object.toString

只要对象与一个字符串通过操作符“ +” 连接起来,Java 编译就会自动地调用该对象的toString方法。

重载

多个方法有相同的名字不同的参数就构成了重载;编译器通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法 。Java 允许重载任何方法 ,但不能有两个名字相同、 参数类型也相同却返回不同类型值的方法。

类与对象

Java 不对局部变量进行初始化, 但是会对对象的实例域进行初始化。如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值: 数值为 0、布尔值为 false、 对象引用为 null。

仅当类没有提供任何构造器的时候, 系统才会提供一个默认的构造器 。Java 支持finalize 方法,finalize 方法将在垃圾回收器标记对象为垃圾对象时使用,用于完成垃圾收集前的清理工作。

public Employee(double s)
{
// calls Employee(String, double)
this("Employee #" + nextld, s);
nextld++;
}

构造java对象的五种方式:通过new关键字、通过Class类的newInstance方法、通过Constructor类的newInstance、利用Object.Clone方法、反序列化。

一个类可以使用所属包中的所有类, 以及其他包中的公有类 。要想将一个类放入包中, 就必须将包的名字放在源文件的开头 。如果没有在源文件中放置 package 语句, 这个源文件中的类就被放置在一个默认包中,默认包是一个没有名字的包。

编译器对文件 (带有文件分隔符和扩展名 .java 的文件)进行操作。而 Java 解释器加载类(带有 . 分隔符 )

javac com/myconipany/Payrol1App.java
java com.mycompany.PayrollApp//类的路径必须与包名匹配

标记为 public 的部分可以被任意的类使用;标记为 private 的部分只能被定义它们的类使用。如果没有指定public或private, 这个部
分(类、方法或变量)可以被同一个包中的所有方法访问。

继承

Java 不支持多继承,但支持多重继承,并且在 Java 中, 所有的继承都是公有继承 。子类能够覆写父类方法。但是不建议使用多重继承,这增加了类之间的耦合性。

public class Manager extends Employee{}

//实现
public interface B {}
public class C implements A {
}

this&super

this 有两个用途:一是引用隐式参数,二是调用该类其他的构造器 , 同样,super 关键字也有两个用途:一是调用超类的方法,二是调用超类的构造器。且调用构造器的语句只能作为另一个构造器的第一条语句出现。

多态的三个必要条件:

多态存在要有3个必要条件:继承、方法覆写、父类引用指向子类对象。父类引用指向子类对象后,用该父类引用调用子类覆写的方法。

枚举类

public enuni Size { SMALL, MEDIUM, LARGE, EXTRAJARGE };

尽管这个声明定义的类型是一个类,但在比较两个枚举类型的值时, 永远不需要调用 equals, 而直接使用“ = =” 。

Class 类

虚拟机为每个类型管理一个 Class 对象。 Object 类中的 getClass( ) ,静态方法 Class.forName() 或者通过类名.class都可以获得Class类对象。Class 类实际上是一个泛型类,比如Employee.class 的类型是 Class

Class类中的 getFields、 getMethods 和 getConstructors 方 法 将 分 别 返 回 类 提 供 的public 域、 方法和构造器数组, 其中包括超类的公有成员。Class 类的 getDeclareFields、getDeclareMethods 和 getDeclaredConstructors 方法将分别返回类中声明的全部域、 方法和构造器, 其中包括私有和受保护成员,但不包括超类的成员。

void setAccessible(boolean flag)//为反射对象设置可访问标志
    
Filed[] getDeclaredFie1ds()//Field[] getFields(),获得公有域   
Method[] getDeclareMethods()//Method[] getMethods(),获得公有方法,或Employee.class.getMethod("getName") 
//(String) 函数对应的Method.invoke(对象,函数参数),如果是静态的,对象用null代替。    

Constructor[] getDeclaredConstructors()
getDeclaredConstructor()//返回指定参数类型的private和public构造器,需要先设置可访问,再实例化对象。    
getConstructor()//返回public的构造器
    
int getModifiers()//返回一个用于描述构造器、 方法或域的修饰符的整型数值
Class[ ] getParameterTypes ( )//返回一个用于描述参数类型的 Class 对象数组

内部类

内部类( inner class ) 是定义在另一个类中的类,内部类方法可以直接访问外部类的所有成员,外部类可以访问内部类的所有方法与属性,包括私有方法与属性。内部类中的this与其他类一样是指的本身。内部类中声明的所有静态域都必须是 final。

静态内部类是定义在类的内部,被static修饰的类,创建静态内部类并不需要外围类的对象,但是静态内部类不能访问外围类的非静态成员。在外部类加载时静态内部类并不会立即被加载,可以利用这种方式设计线程安全的单例模式。

局部内部类:可以在一个方法中定义局部类,局部类不能用 public 或 private 访问说明符进行声明。它的作用域被限定在声明这个局部
类的块中。

假如只创建这个类的一个对象,就不必命名了,这时候可以使用匿名内部类。由于匿名类没有类名, 所以, 匿名类不能有构造器。

new SuperType(construction parameters){}//superType可以是接口也可以是父类

Person queen = new Person("Mary");//构建Person类
Person count = new Person("Dracula") {}//构建Person类扩展的匿名内部类            

队列和栈的Java实现

Queue<Integer> queue = new LinkedList<>();//列队
queue.offer(elements);//如果队列没有满,将给定的元素添加到这个双端队列的尾部并返回 true。如果队列满了,返回 false
queue.poll();//假如队列不空,删除并返回这个队列头部的元素。如果队列是空的,返回 null。
Deque<Integer> deque = new LinkedList<>();//堆栈 operate in the first element of this list.
deque.push(elements);//头
deque.pop();//头
Deque.pollFirst();//取头
Deque.pollLast();//取尾
Deque.offerFirst(E e);//加头
Deque.offerLast(E e);//加尾

offer和pull在队列满和空的时候不会抛出异常,但是add和remove将会在队列满和队列空的时候抛出异常。

优先级队列(priority queue) 中的元素可以按照任意的顺序插人,却总是按照排序的顺序进行检索,底层的数据结构是堆。

异常

在 Java中, 异常对象都是派生于 Throwable 类的一个实例。

image-20210304172130118

Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误,应用程序不应该抛出这种类型的错误。

Exception 层次结构又分解为两个分支:由程序错误导致的异常属于 RuntimeException ; 而程序本身没有问题, 但由于像 I/O 错误这类
问题导致的异常属于IOException。

RuntimeException产生的原因:错误的类型转换、数组越界、空指针异常;IOException产生的原因:文件不存在、EOFException

异常的创建&try/catch/finally

定义一个派生于Exception 的类,或者派生于 Exception 子类的类,定义一个默认构造器和一个包含String的构造器,然后调用父类构造器super()

try/catch:异常发生的时候没有进行捕获,程序就会终止执行。如果在 try语句块中抛出了在 catch 子句中说明的异常类, 那么程序将跳过 try语句块的其余代码,并执行catch子句中的代码。如果没有异常,则跳过catch子句。如果想把异常传递给调用者,那么将声明这个方法可能产生的异常直接抛出即可。

无论在 try 语句块中是否遇到异常,异常是否被捕获,finally中的语句都会被执行。所以通常用于关闭必须要关闭的资源

假设利用 return语句从 try语句块中退出。在方法返回前, finally子句的内容将被执行。如果 finally 子句中也有一个 return 语句, 这个返回值将会覆盖原始的返回值。

泛型

java中通常使用变量 E 表示集合的元素类型, K 和 V 分别表示表的关键字与值的类型。T ( 需要时还可以用临近的字母 U 和 S ) 表示“ 任意类型。

public static <T> T getMiddle(T a)//泛型方法
public static <T extends Comparab1e> T min(T[] a)//指定T必须实现Comparab1e接口

T extends BoundingType 表示T应该是绑定类型的子类型,绑定类型可以是类,也可以是接口。

<T extends Comparable & Serializable> 表明T应该实现Comparable接口和Serializable接口,但但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。

通配符?可以指定一个超类型限定,例如 ? super Manager 限制为 Manager 的所有超类型。

包装类缓存池

Java 的数值类型的包装类型都有自己的缓存池,当用 valueOf 方法装箱数值的时候会优先返回缓存的值。介于-128 ~ 127之间的 short 、 int、long、byte 被包装到固定的对象中。编译器在生成类的字节码时, 插人装箱拆箱必要的方法调用,而虚拟机只负责执行这些字节码。

Integer a =1000;
Integer b = 1000;
System.out.println(a==b);//比较两个对象的地址,false,当在-128 ~ 127范围内时,则会被缓存起来,此时a和b时同一个对象的引用,此时返回true解决这个问题的办法是在两个包装器对象比较时调用 equals 方法

数组(引用数据类型)

特点:类型统一、长度不可更改、继承Object对象

初始化方法:动态初始化和静态初始化

dataType[] listName = new dataType[listLength]; //动态初始化

dataType[] listName = new dataType[]{elememt1,element2,element3};//静态初始化
dataType[] listName = {elememt1,element2,element3};//省略格式的静态初始化

Arrays.copyOf()//数组拷贝

java中的多维数组其实指的是数组的数组,java允许构造不规则数组,使得二维数组中每一行有不同的长度。要想打印多维数组(即, 数组的数组)则需要调用Arrays.deepToString 方法。 一维数组调用调用静态方法 Arrays.toString 即可。

int[][] odds = new int[N][];//构建一个N行的不规则数组

for (int n = 0; n < N; n++)
	odds[n] = new int[n + 1];

泛型数组 ArrayList

特点:底层基于Object数组,初始容量为10,扩容系数为1.5;Vector已经被抛弃,在一些老版本中可以见到。

int newCapacity = oldCapacity + (oldCapacity >> 1);//增加的容量大小等于oldCapacity//2

//基本使用方式
ArrayList<Interger> = new ArrayList<>(capacity);// 泛型是非基本类型,因为基本类型没有地址值,菱形语法,JAVA SE7后开始支持;另外,可以在初始化时确认初始容量
public boolean add(E e);
public E get(int index);
public E remove(int index);
public int size();//获取长度

集合框架(集合类的基本接口是Collection)

数组长度不可变,而集合长度可以发生变化,但是只能存储对象。

List 是一个有序集合,可以采用两种方式访问元素:使用迭代器访问, 或者使用一个整数索引来访问。后一种方法称为随机访问。

有序集合有两种实现方式,用链表实现的有序集合适合使用迭代器的方式访问,而用数组实现的适合利用下标随机访问。

vector中所有方法都是同步的,而ArrayList是不同步的,所以效率会比vector高;hashtable用数组和链表实现,桶满时会将链表转换为平衡二叉树 ;TreeSet是一个有序集合,当对集合进行遍历时,每个值将按照排序自动呈现,底层实现是红黑树; HashSet的底层实现是hash表+红黑树,底层是HashMap;

HashSet<String> staff = new HashSet(Arrays.asList(values));//实现数组到List再到集合的转换
staff.toArray(new String[staff.size()])//实现集合到数组的转换

Vector,HashTable中的方法都是同步的,但是效率不高,已经很少使用。如果不要求同步的话,一般使用ArrayList代替vector,使用HashMap代替HashTable,但是要求同步的话,一般使用ConcurrentHashMap代替HashTable,使用CopyOnWriteArrayList 去代替ArrayList,也可以使用视图工具Collections中的synchronizedMap、synchronizedList、synchronizedSet

Iterator

Collection<String> c = . . .
Iterator<String> iter = c.iteratorO
while (iter.hasNext())
{
String element = iter.next()//可以遍历集合中所有元素
}

for (String element : c)//for each循环可以和任何实现了Iterable接口的对象一起工作。
{
do something with element
}

iterator.forEachRemaining(element -> {});//将对迭代器的每一个元素调用这个 lambda 表达式,元素被访问的顺序取决于集合类型。 如果对 ArrayList 进行迭代, 迭代器将从索引 0开始,如果访问 HashSet 中的元素, 每个元素将会按照某种随机的次序出现	

iterator() 是collection中的一个方法,用于返回一个实现了 Iterator 接口的对象,可以使用这个迭代器对象依次访问集合中的元素。

通过反复调用 next 方法,可以逐个访问集合中的每个元素,但是,需要在调用 next 之前调用 hasNext方法,否则next 方法将抛出一个 NoSuchElementException。

Iterator 接口的 remove 方法将会删除上次调用 next 方法时返回的元素。

抽象类

  1. 抽象类和抽象方法都要被abstract修饰,抽象方法一定要定义在抽象类中
  2. 抽象类不可以直接创建对象
  3. 只有覆盖了抽象类中所有的抽象方法后,其子类才可以创建对象。否则该子类还是一个抽象类
  4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。

instanceof

可以用 instanceof 检查一个对象是否实现了某个特定的接口

//当实例对象是类的实例或者是该类子类的实例时,返回true
实例对象 instanceof//区别两个对象是否严格属于同一个类
b.getClass() == a.getClass()

Lambda表达式:JDK1.8新特性

(paraList) -> {Program};

参数类型可以省略。 但是有多个参数的情况下,不能只省略一个;如果参数有且仅有一个,且类型可推导,那么小括号可以省略;如果代码块的语句只有一 条,可以省略大括号和分号,甚至是return;如果没有参数,那么必须保留小括号;

函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口,而对于函数式接口,可以通过Lambad表达式创建接口对象

如果一个 lambda 表达式只在某些分支返回一个值, 而在另外一些分支不返回值,这是不合法的。

通过lambad表达式实现一个接口:

interface Inter{
    void say();
}//必须只有一个

Inter inter = ()->{	System.out.println("hello");};

方法引用&构造器引用

要用 :: 操作符分隔方法名与对象或类名

System.out::println等价于x 一> System.out.println(x)

object::instanceMethod, Class::staticMethod, Class::instanceMethod, 方法引用不能独立存在,总是会转换为函数式接口的实例

Person::new 是 Person 构造器的一个引用,引用哪一个构造器则取决于上下文。

接口&接口

包含一个或多个抽象方法的类本身必须被声明为抽象的,除了抽象方法之外,抽象类还可以包含具体数据和具体方法,也可以不包含抽象方法。抽象类不能够被实例化。

接口中所有域都默认是由public static final修饰的。 接口中的所有方法都默认是由public abstract修饰的,且实现接口的类中必须提供接口中所有方法的具体实现内容。

接口中可以实现静态方法,与抽象类相比,一个类可以实现多个接口,却只能继承一个抽象类。

在接口中,可以用default 修饰符为函数提供一个默认实现【Comparator】。这个时候,实现这个接口的类可以不用实现这个方法,从这个角度上讲,抽象类和接口都可以由具体的实现方法。

public interface Comparable<T>{
    default int compareTo(T other) { return 0; }
}

当父类中有相同签名的方法时,接口中的默认方法会被忽略,而当两个接口中的默认方法发生冲突时,则会报错。

Comparator&Comparable接口

对象数组排序的前提是这些对象是实现了Comparable 接口,然后用Arrays.sort()、Collections.sort方法进行排序。另一种是借助比较器Comparator实现。

对于实现了Comparable接口的对象数组,可以直接通过Arrays.sort()实现排序

static class Person implements Comparable<Person>{
    int age;
    String name;

    @Override
    public int compareTo(Person o) {
        return this.age-o.age;
    }
}

Person[] array = new Person[]{...};
Arrays.sort(array);

同样Arrays.sort也支持比较器

public static <T> void sort(T[] a, Comparator<? super T> c) 

或者通过比较器实现,这样对象数组中的对象不需要实现Comparable接口

ArrayList<Person> list = new ArrayList<>();
...
list.sort(Comparator.comparingInt(x->x.age));

Cloneable接口

通过实现Cloneable 接口,可以重新定义Object中的clone方法,Object中的clone方法是Protected,只能定义更大的权限范围

Object 类的 clone 方法只是逐个进行域拷贝【浅拷贝】,如果是基本类型则没有问题,如果是对象的引用,那么拷贝域就会得到相同对象的另一个引用。调用对象的 clone 方法,必须要实现 Cloneable 接口,并且覆写 clone 方法,可以重新定义权限范围,返回值必须时原返回值类型的子类型

即使 clone 的默认(浅拷贝)实现能够满足要求, 还是需要实现 Cloneable 接口, 将 clone重新定义为 public, 再调用 super.clone。

深拷贝可以借助 Java 对象的串行化特性,这种方式很安全,但效率不高。 注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient。

//深度拷贝
public Object deepClone() throws Exception{
    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);

    // 反序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}

另一种则是让类里的所有引用对象覆写clone方法,然后通过在clone方法中:

@Override
    protected Object clone() throws CloneNotSupportedException {
        Person p = (Person) super.clone();//完成了浅拷贝
        p.address = (Address) address.clone();//对对象类型中每个对象完成一次拷贝,而Address同样需要覆写clone
        return p;
    }

HashMap

put(key,value)//向字典中添加新元素。
get(key)//通过键查找特定的数值并返回。
has(key)//判断字典中是否存在键key。
delete(key)//通过键 key 从字典中移除对应的数据。

HashMap 底层原理是Hash表+红黑树+链表,当链表长度超过8时将列表转换为红黑树。

map.put (word, map.getOrDefault(word, 0)+ 1)//使用一个映射统计一个单词在文件中出现的频度

映射视图:Set keySet()、Collection values()、Set<Map.Entry<K, V>> entrySet()

HashMap的遍历

按照遍历因子来划分, 则有keySet()entrySet()两种, keySet()是直接取到keys, entrySet是首先取到每一个entry, 然后使用entry的getKey() 和 getValue()来完整的实现遍历, 速度要更好.

//通过keySet完成遍历
for(Object key:map.keySet()){   
}

//只遍历map的所有value
for(String v:map.values()){
}

//通过entrySet的迭代器实现遍历
Iterator map1it=map.entrySet().iterator();
while(map1it.hasNext())
{
 Map.Entry<String, String> entry=(Entry<String, String>) map1it.next();
 System.out.println("Key: "+entry.getKey()+" Value: "+entry.getValue());
}

//高效遍历HashMap方法,直接通过entrySet遍历HashMap
for (Map.Entry<K, V> entry : map.entrySet())
{
entry.getKey()
entry.getValue()
}

视图&包装器

视图有一些局限性, 即可能只可以读、 或者无法改变大小等。可以通过Collections中的几个方法生成不可修改的视图。

Collections类包含了很多实用工具,而Collection只是一个接口。Collections 还有几个方法, 用获取同步视图,类库的设计者使用视图机制来确保常规集合的线程安全, 而不是实现线程安全的集合类:

Map<String, Employee> map = Collections.synchronizedMap(new HashMap<String, Employee>())

同时Collections也支持binarySearch【二分查找】、sort【排序】

流库

流提供了更高级别的数据视图,并对计算进行了优化。流并不存储元素,并且不改变数据源,另外流的操作是尽可能惰性执行的。在Stream类中,有filter()、count()函数,分别对当前流过滤,转换成另一个流,另一个返回流中的元素数量。

Collection接口的stream方法可以将任意集合转换成一个流【集合实现了这个接口,直接通过集合对象调用】,另外,使用Array.stream(array,from,to)可以将数组中位于from和to之间的元素中创建一个流。

Stream<T> limit(long maxSize)//产生一个流,其中包含当前流中最初的maxSize个元素
Stream<T> skip(long n)//跳过前n个,并产生一个新流
Stream<T> concat()//合并流
Stream<T> distinct() //剔除相同元素,返回一个流
Stream<T> sorted()
Stream<T> map()//将mapper应用于当前流
Stream<T> flatMap()
Stream<T> filter()//产生一个流,包含当前流中满足断言条件的所有元素    

结果收集:iterator可以产生用于访问流元素的迭代器,也可以采用Steam.forEach()方法;若是希望收集到数据结构中,可以调用toArray() ,获得由当前流构成的数组。

stream.toArray(String[]::new)

或者使用collect方法将其收集到集合中,其接受一个Collector接口的实例;另外Collectors类中有大量的用于生成公共收集器的工厂方法。

TreeSet<String> res = stream.collect(Collectors.toCollection(TreeSet::new))
List<String> res = stream.collect(Collectors.toList())
Set<String> res = stream.collect(Collectors.toSet())    

Collectors.toMap()有两个函数引元,他们用来产生映射表的键和值。

Stream<Person> people;
people.collect(Collectors.toMap(Person::getId,Person::getName,lambad,TreeMap:new))//TreeMap<Integer,String>

基本类型流:IntStream、LongStream、DoubleStream;可以使用mapToInt、mapToDouble将类型流转换成基本类型流;基本类型流调用toArray将返回基本类型数组。

对于字符串数组、基本类型数组都可以使用Arrays.stream方法转换成流,再转换成ArrayList;

// int[] a = {1,2,3};数组到包装型数组,包装型数组再到普通类型的数组
Integer[] integers = Arrays.stream(a).boxed().toArray(Integer[]::new);
int[] ints = Arrays.stream(integers).mapToInt((x) -> x).toArray();

//int[] b = {3,4,5};数组装箱到ArrayList,ArrayList拆箱到数组
ArrayList<Integer> collect = Arrays.stream(b).boxed().collect(Collectors.toCollection(ArrayList::new));
int[] ints1 = collect.stream().mapToInt(x -> x).toArray();

并行流:可以用Collection接口中parallelStream方法获得任意集合中的并行流。Stream.parallel()可以将串行流并行化

编译

Java编译程序将java源程序编译成java字节码,将些符号引用信息保留在字节码中。运行jvm字符码的工作是由解释器来完成的。解释执行过程分三步进行:代码的装入、代码的校验、和代码的执行。

java字节码开头为cafe babe,称为魔数,包括jdk版本号,java版本号,然后是字节码指令。

javap -c #字节码分析工具,将字节码指令转换成字节码助记符
javac .java#编译成字节码
java# 直接运行字节码文件

补码和溢出

两个异号数相加或两个同号数相减不会发生溢出(不包括0,同号相减等于异号相加)

当确定两数大于零时,可以通过判断Interger.Max_Value-a<b来确定是否发生越界,同号相减保证前面的操作不会越界

当确定两数小于零时,可以通过判断Interger.Min_Value-a>b来确定是否发生越界

线程不安全:

ArrayList 多个线程调用add函数时,可能会把内容添加到同一个地方,导致线程不安全

使用Synchronize代码块,对需要增删改的object加锁,保证线程安全

synchronize(Object){}//悲观锁

//ReentrantLock可重用锁
Lock lock = new ReentrantLock();
lock.lock();lock.unlock();

进入synchronized代码块前后,线程会获得锁,清空工作内存,从主内存拷贝共享变量最新的值到工作内存成为副本,执行代码,将修改后的副本的值刷新回主内存中,线程释放锁。

系统类加载器【默认】

类加载器用于实现在JVM中的类加载动作,对于任何一个类,都由加载器和其本身来确定唯一性。

启动类加载器:【bootstrap、C++】、扩展类加载器【java】、应用程序类加载器【系统类加载器AppClassLoader】、自定义加载器

其负责加载从Classpath环境变量指定目录中的加载类,父类是扩展类加载器【其父类是根类加载器】

系统类加载器可以通过ClassLoader.getSystemClassLoader()获得;另一种获得方式:

ClassNameDefinedBySelf.class.getClassLoader(); 

双亲委派机制

如果能够通过父类加载器实现类的加载,则优先使用父类加载器;自定义加载器需要实现ClassLoader抽象类中的findclass()方法如果出现同名的类

双亲委派保证了加载器优先加载java内置的类,保证源码干净一致

UrlClassLoader://加载网络上的类

URL url = new URL("address");
URLClassLoader loader = new URLClassLoader(new URL[] {url});
Class clazz = loader.loadClass("name");
clazz.newInstance;

对象布局ClassLayout

对象在堆内存中的存储布局可以划分为三个部分:对象头、实例数据、对齐填充

依赖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

打印

Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());

对象头

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)      01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)      00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)      e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes//必须能被8整除,所以填充了4个字节
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对象头中包括,哈希码、GC分代年龄、锁状态标志位、指向类的指针、数组长度32bit

代理(动态代理)

通过Proxy.newProxyInstance方法创建代理对象;该方法主要包含三个参数:类加载器【null表示使用默认的类加载器】、一个Class对象数组、一个调用处理器

调用处理器需要实现InvovationHandler这个接口,并通过Proxy.newProxyInstance方法创建代理对象

public interface InvocationHandler {
	public Object invoke(Object proxy, Method method, Object[] args)
	        throws Throwable;
}

public class ProxyHandler implements InvocationHandler 
 {
  private Object target;//代理对象,需要通过构造方法初始化,这里省略
    
  public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable   
  {   
    //在调用具体目标对象之前,可以执行一些功能处理
	...
    //转调具体目标对象的方法
    return method.invoke(target, args);  
    //在转调具体目标对象之后,可以执行一些功能处理
  }    
Proxy.newProxyInstance(类加载器, 
     new Class[]{一组interface}, 
     new ProxyHandler(real)【调用处理器】);

代理原理:

1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入;

Interface Proxy =(Interface)constructor.newInstance(new Object[] (handler));为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))

注解

@SuppressWarnings用于抑制编译器产生警告信息

 posted on   春秋流千事  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示