java一些精干知识点分享
2. java小干货
2.1循环遍历
数组、list、map都需要遍历循环,有下面集中循环方式
1.for ecah
list可以是一个数组、list、set
// list可以是一个数组、list、set
for(bject o :list)
{
}
2.Iterator迭代器
list可以是list、set类的子类
Iterator iter = list.iterator();
while(iter.hasNext()){
Object o = iter.next();
}
3.loop with size
可以是数组、list能得到索引长度的类及子类
for(int i=0;i<list.size();i++){
Object o= list.get(i);
}
4.lambda表达式
list.forEach(
(value)->{
System.out.println(value);
}
);
代码示例
public class GanHuo {
@Test
public void t1(){
//1.for-each遍历,最简单,但无法获得索引
System.out.println("=======for(Object:list)循环=========");
String arrayStr[]=new String[]{"1","2","3"};
//1-1 数组
System.out.println("for-each遍历数组-----------");
for (String s : arrayStr) {
System.out.println("arrayStr:"+s);
}
//1-2 list
List<Integer> list= Arrays.asList(1,2,3,4);
System.out.println("for-each遍历list-----------");
for (Integer i : list) {
System.out.println("list:"+i);
}
//1-3 遍历map
Map<String,String> map=new HashMap<>();
System.out.println("for-each遍历Map-----------");
map.put("1", "111");
map.put("2", "222");
map.put("3", "222");
//得到key的collecton
Set<String> keySet=map.keySet();
for(String key:keySet){
System.out.println("map["+key+"]="+map.get(key));
}
}
@Test
public void t2(){
System.out.println("=======Iterator循环================");
//1-2 list
List<Integer> list= Arrays.asList(1,2,3,4);
System.out.println("Iterator 遍历list-----------");
Iterator iter=list.iterator();
while (iter.hasNext()) {
System.out.println("list:"+iter.next());
}
//1-3 遍历map
Map<String,String> map=new HashMap<>();
System.out.println("Iterator 遍历Map-----------");
map.put("1", "111");
map.put("2", "222");
map.put("3", "222");
//得到key的collecton
Set<String> keySet=map.keySet();
iter=keySet.iterator();
while (iter.hasNext()) {
String key= (String) iter.next();
System.out.println("map["+key+"]="+map.get(key));
}
}
@Test
public void t3(){
System.out.println("=======for。size循环================");
//1-1 数组
String arrayStr[]=new String[]{"1","2","3"};
System.out.println("loop size 遍历数组-----------");
for (int i = 0; i < arrayStr.length; i++) {
System.out.println("arrayStr["+i+"]="+arrayStr[i]);
}
//1-2 list
List<Integer> list= Arrays.asList(1,2,3,4);
System.out.println("loop size 遍历list-----------");
for (int i = 0; i < list.size(); i++) {
System.out.println("list["+i+"]="+list.get(i));
}
}
@Test
public void t4(){
System.out.println("=======lambda表达式================");
//list的lambda表达式
List<Integer> list= Arrays.asList(1,2,3,4);
System.out.println("lambda表达式 遍历list-----------");
list.forEach(
(value)->{
System.out.println(value);
}
);
//Map的lambda表达式
Map<String,String> map=new HashMap<>();
System.out.println("lambda表达式 遍历Map-----------");
map.put("1", "111");
map.put("2", "222");
map.put("3", "222");
map.forEach((key, value)->{
System.out.print("key = " + key);
System.out.println(", value = " + value);
});
}
}
2.2可变参数
可变参数是指不指定参数的个数
public class ChangeParam {
public void param(Integer... val){
System.out.println("val包含:"+val.length+"个参数");
for (Integer i : val) {
System.out.println(i);
}
}
/***
如果一个方法里包含常规参数和可变参数,则可变参数必须放置到最后一个
*/
public void param1(String name,Integer age,String... likes){
System.out.println("name:"+name);
System.out.println("age:"+age);
System.out.println("likes-----");
String temp="";
for (String like : likes) {
temp+=like+",";
}
System.out.println(temp);
}
@Test
public void t1(){
//可以传递不受限制的个数
param(1,2,3);
//可以不传参数
param();
//也可传递数组
Integer[] array={1,2,3};
param(array);
//常规和可变参数混合
param1("蒋增奎",20,"火锅","串串","烤鸭");
}
}
2.3 list和数组转化
2.3.1 数组转list
@Test
public void array2List(){
System.out.println("=========Arrays.asList=======");
//1---直接用Arrays.asList后不能再新增
System.out.println("1---直接用Arrays.asList后不能再新增");
//基本类型数组
Integer[] intArray={1,2,3};
List<Object> list1= Arrays.asList(intArray);
// list1.add(4); //不能再新增,否则要报异常
printList(list1);
//自定义数组
TestVO[] objArray={new TestVO(1,"jzk"),new TestVO(2,"张三")};
list1=Arrays.asList(objArray);
printList(list1);
//2--Arrays.asList作为构造参数传入,可新增,但性能差
System.out.println("2--Arrays.asList作为构造参数传入,可新增,但性能差");
list1=new ArrayList<>(Arrays.asList(intArray));
list1.add(4);
printList(list1);
//3--Collections.addAll(list,数组);能新增删除,性能好
System.out.println("3--Collections.addAll(list,数组);能新增删除,性能好");
list1=new ArrayList<>();
Collections.addAll(list1, intArray);
list1.add(4);
printList(list1);
//4--使用 Stream;能新增删除,性能好
System.out.println("4--使用 Stream;能新增删除,性能好");
list1= Stream.of(intArray).collect(Collectors.toList());
list1.add(4);
printList(list1);
}
private void printList(List<Object> list){
for (Object o : list) {
System.out.println(o);
}
}
2.3.2 list转数组
测试代码
@Test
public void list2array(){
List<TestVO> list=getTestVO();
System.out.println("方法1:list.toArray()===========");
//注意:只能用转换成Object[]数组,不能强制转化
Object[] arrays=list.toArray();
for (Object array : arrays) {
System.out.println((TestVO)array);
}
System.out.println("方法2:list.toArray(数组对象)===========");
//这样可获得实际的类
TestVO[] vos=new TestVO[list.size()];
list.toArray(vos );
for (TestVO vo : vos) {
System.out.println(vo);
}
//或者这样写
System.out.println("方法3:list.toArray(数组对象)简写===========");
vos=list.toArray(new TestVO[list.size()]);
for (TestVO vo : vos) {
System.out.println(vo);
}
System.out.println("方法4:String[] strings = list.stream().toArray(String[]::new);===");
TestVO[] vos2=list.stream().toArray( TestVO[]::new);
for (TestVO vo : vos2) {
System.out.println(vo);
}
}
private void printList(List<Object> list){
for (Object o : list) {
System.out.println(o);
}
}
private List<TestVO> getTestVO(){
List<TestVO> list=new ArrayList<>();
list.add(new TestVO(1,"jzk"));
list.add(new TestVO(2,"张三"));
return list;
}
2.4 值传递和地址传递
在学习 Java 编程语言的过程中,我们经常会听到“值传递”和“地址传递”这两个概念。它们是用来描述参数传递方式的术语,而理解它们的区别对于编写高效的代码非常重要。在本文中,我们将详细介绍这两种传递方式,并通过代码示例来说明它们的差异。
2.4.1值传递
在 Java 中,基本数据类型(如整数、布尔值等)都是以值传递的方式进行参数传递。这意味着当我们将一个基本数据类型作为参数传递给一个方法时,方法内部会创建一个新的变量来存储这个参数的值,而不会影响原始的变量。
- 首先,在调用方法时,将实际参数的值复制一份,并将这份副本传递给方法进行操作。
- 在方法内部,这个副本的值被赋给一个新的局部变量。
- 在方法执行过程中,对该局部变量的任何改动都不会影响原始的变量,因为它们指向的是不同的内存空间。
- 当方法执行完毕后,这个局部变量和方法的栈帧都会被销毁,而原始的变量的值保持不变。
基本数据类型的传递过程中,传入的值被复制到方法内部,并在方法内部进行操作,但不会影响原始变量的值。
@Test
public void valPass(){
String str="a";
double db= Double.parseDouble("34.5");
Double db1=45.2;
Integer zs=Integer.valueOf(100);
int zs1=200;
boolean b=false;
Boolean b1= true;
System.out.println("str="+str);
System.out.println("db="+db);
System.out.println("db1="+db1);
System.out.println("zs="+zs);
System.out.println("zs1="+zs1);
System.out.println("b="+b);
System.out.println("b1="+b1);
change1(str,db,db1,zs,zs1);
System.out.println("str="+str);
System.out.println("db="+db);
System.out.println("db1="+db1);
System.out.println("zs="+zs);
System.out.println("zs1="+zs1);
}
private void change1(String str,double db,Double db1,int zs,Integer zs1){
System.out.println("============change==========");
str="b";
db=-45.2;
db1=-22.4;
zs=-1;
zs1=-2;
}
执行效果,可以看出这些都是值传递,被引用后并没有改变以前值:
str=a
db=34.5
db1=45.2
zs=100
zs1=200
b=false
b1=true
============change==========
str=a
db=34.5
db1=45.2
zs=100
zs1=200
2.4.2 地址传递
与基本数据类型不同,Java 中的对象类型(如数组、集合、自定义类等)则是以地址传递的方式进行参数传递。这意味着当我们将一个对象作为参数传递给一个方法时,方法内部使用的是这个对象的引用,而不是对象本身。
-
创建一个对象并将其引用赋值给一个变量。
-
将这个变量作为参数传递给一个方法。
-
在方法内部,参数变量接收到了对原始对象的引用。
-
在方法内部修改参数变量所指向的对象时,原始对象也会受到影响。
-
方法执行完毕后,返回到原始调用处,可以通过原始变量访问到被修改后的对象。
对象的引用传递意味着传递的是对象的引用,通过引用可以访问和修改原始对象的属性。
测试代码:
@Test
public void addressPass(){
StringBuffer sb=new StringBuffer("aa");
TestVO vo=new TestVO(1,"蒋增奎");
String[] array={"1","2","3"};
List<Integer> list=new ArrayList<>();
list.add(1);list.add(2);
Map<String,Integer> map=new HashMap<>();
map.put("1", 11);
map.put("2", 22);
System.out.println("sb:"+sb);
System.out.println("vo:"+vo);
System.out.println("array:"+ Arrays.toString(array));
System.out.println("list:"+Arrays.toString(list.toArray()));
System.out.println("map:"+map.toString());
System.out.println("================");
change2(sb,vo,array,list,map);
System.out.println("sb:"+sb);
System.out.println("vo:"+vo);
System.out.println("array:"+ Arrays.toString(array));
System.out.println("list:"+Arrays.toString(list.toArray()));
System.out.println("map:"+map.toString());
}
private void change2( StringBuffer sb, TestVO vo,String[] array,
List<Integer> list,Map<String,Integer> map){
sb.append("dd");
vo.setName("蒋大爷");
array[0]="改值11";
list.add(3);
map.put("3",333);
}
效果,地址引用值都会被改变
sb:aa
vo:TestVO(id=1, name=蒋增奎)
array:[1, 2, 3]
list:[1, 2]
map:{1=11, 2=22}
================
sb:aadd
vo:TestVO(id=1, name=蒋大爷)
array:[改值11, 2, 3]
list:[1, 2, 3]
map:{1=11, 2=22, 3=333}
代码2:
@Test
public void t2() {
TestVO vo1=new TestVO(1, "jzk");
TestVO vo2=vo1;
vo2.setName("奎哥");
System.out.println(vo1); //打印TestVO(id=1, name=奎哥),不是jzk
System.out.println(vo2);//TestVO(id=1, name=奎哥)
}
首先创建一个对象TestVO vo1,接着申请另一款空间,用来创建TestVO vo2,且vo2=vo1,说明两个数组都指向同一块空间,修改vo2中的字段也就相当于修改了vo1中对应的元素。
代码3
@Test
public void t1(){
TestVO vo=new TestVO(1, "jzk");
System.out.println(vo);
change3(vo);
System.out.println(vo);
change4(vo);
System.out.println(vo);
}
private void change3(TestVO vo){
TestVO vo1=vo; //把vo赋值给vo1,两个对象是指向同一个地址
vo1.setName("奎哥");
}
private void change4(TestVO vo){
vo.setName("鸡哥");
}
打印效果:
TestVO(id=1, name=jzk)
TestVO(id=1, name=奎哥)
TestVO(id=1, name=鸡哥)
代码说明:
change3()和change4()效果一样,change3()虽然通过了赋值,但两个对象指向的同一个地址
2.4.3易错点总结
易错点
java对象:String ,int等对应的封装类Integer,java.util.Date,BigDecimal是值传递,不是地址传递,虽然他们是对象
值传递VS地址传递
2.5 数据类型
2.5.1基础知识
Java基本类型共有八种,基本类型可以分为三类,字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。数值类型又可以分为整数类型byte、short、int、long和浮点数类型float、double。JAVA中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。实际上,JAVA中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,不过我们无法直接对它们进行操作
代码说明
//基础数据类型
@Test
public void t1(){
//1.整数类型
byte b=100;
short s=10000;
int i=1000000000;
//注意:长整型long要加L后缀,才能最大化
long l=900000000000000000L;
System.out.println("byte:"+b);
System.out.println("short:"+s);
System.out.println("int:"+i);
System.out.println("long:"+l);
//2.浮点型
//注意:小数默认是double,如果要用float,需要再数字后面加f或者F
float f=34.67f;
double d=34.67;
//char类型,char必须用单引号包围,只能一个字符
char c1='A';
char c2='2';
//boolean
boolean b1=false;
}
注意事项:
默认值和初始化
每一种类型都有一个默认值,除基本类型外,其他的类型的默认值都是 null,因为它们都是引用类型。整数默认为 int 类型,浮点数默认为 double 类型。
代码
public float f;
public double d;
public boolean b;
private int i;
private Boolean b3;
@Test
public void t2(){
//静态方法有默认值
System.out.println(f); //0
System.out.println(d); //0
System.out.println(i); //0
System.out.println(b); //false
System.out.println(b3); //null
boolean b1;
System.out.println(b1); //编译不通过
}
注意事项:
2.5.2 基础数据和包装类
Java 是面向对象的语言,但是为了便于开发者的使用,Java 中却沿用了 C 语言的基本数据类型,在进行基本的数据计算时,开发者可以直接使用基础类。但是基本数据类型是不具备对象的特征的,不能调用方法,而且基本数据类型不能存入集合中,所以就需要将基础数据类型实例封装为 Java 对象,使其具有了对象的属性和方法。
基础类型和包装类的区别
-
存储位置不同:
基本数据类型直接将值放在栈中;
包装类型是把对象放在堆中,然后通过对象的引用来调用他们 ; -
初始值不同:
int的初始值为 0 、 boolean的初始值为false ;
包装类型的初始值为null ; -
使用方式不同:
基本数据类型直接赋值使用就好;
在集合如 coolectionMap 中只能使用包装类型;
在应用场景中MVC模式的form-vo,Dto,PO对象的属性最好都用包装类,因为基础类型都有默认值0或者false,在实际应用中,null和0,false是有实际意义的 -
包装类是java对象,封装有相关方法,而基础类型没有
比如:Integer.valueOf()等
为什么还要保留基础类型?
- 在 Java 中,使用 new 关键字创建的对象存储在堆中,并且通过栈中的引用来使用这些对象,所以对象本身来说是比较消耗资源的。
- 基本类型存储在栈里,因为栈的效率高,所以保留了基本类型。变量的值存储在栈中,方法执行时创建,结束时销毁,因此更加高效。
- 使用基本数据类型参与计算时的性能要比使用包装类的高。
基础类型之间的转换
(1)基础类型自动转化
示例代码:
@Test
public void t3(){
System.out.println("1.自动转换===========");
byte i1=2;
short i2=i1;
int i3=i2;
long i4=i3;
float f1=3.45f;
double f2=f1;
float f3=i3;
double f4=i4;
}
(2)强制执行
@Test
public void t4(){
System.out.println("2.强制转换===========");
double f1=34.56565;
float f2=(float) f1;
int i=(int)f1;
System.out.println(i);
//范围小的转换也可以用强制转化,和自动转换效果一样
int i1=34;
float f3=(float)i1;
}
基本类型和包装类的转换
基本类型与包装类之间的转换需要使用到自动装箱和拆箱的操作。
@Test
public void t5(){
System.out.println("3.基本类型和封装类转换===========");
int i=10;
Integer i1=i; //自动装箱
Integer i2=30;
int i3=i2;//自动拆箱
}
包装类的常用方法
@Test
public void t6() {
System.out.println("4.包装类的常用方法===========");
Integer i = Integer.valueOf(2);
int i1 = Integer.parseInt("344"); //字符串转成整数
Integer i2 = Integer.parseInt("34444"); //实际是转化成int,int在装箱成Integer
System.out.println(i);
System.out.println(i1);
System.out.println(i2);
}
2.6 字符串
2.6.1 char/String区别
1.char和String的区别
char是字符类型,是基础数据类型,长度固定,用单引号表示 如 c=‘谢’;
String是字符串类型,不是基础数据类型,长度无法确定,用双引号表示 str=“傻啊”。
关于String类。
(1)、String类时final类,所以是不可继承的;
( 2)、String类是的本质是字符数组char[];
( 3)、Java运行时会维护一个String Pool(String池),JavaDoc翻译很模糊“字符串缓冲区”。String池用来存放运行时中产 生的各种字符串,并且池中的字符串的内容不重复。而一般对象不存在这个缓冲池,并且创建的对象仅仅存在于方法的堆 栈区。
4.String虽然是对象,但传参也是值传递,不是地址传递
@Test
public void t7() {
String s="abc";
Character c='中';
char[] cs= s.toCharArray();
for (char c1 : cs) {
System.out.println(c1);
}
char[] cs2={'天','地','人'};
String s1=new String(cs2);
System.out.println(s1);
}
2.6.2 .关于String的创建方式
字符串有两种创建方式,String s1=“a”; String s=new String(“a”)其逻辑是不一样的
String str1 = "Java";
String str2 = "Java";
String str3 = str2 + "";
String str4=new String("Java");
String str5=new String("Java");
String str6=str1;
String str7=str4
System.out.println(str1 == str2); // Output: true
System.out.println(str2 == str3); // Output: false
System.out.println(str4== str5); // Output: false
System.out.println(str1== str5); // Output: false
System.out.println(str1== str6); // Output: true
System.out.println(str4== str7); // Output: true
代码说明:
-
(str1 == str2)== true
直接创建String,其实就是放到常量池里的对象,直接创建时,先去常量池寻找,如果有,则引用,所以这两个时一个东西 -
(str2 == str3)==false
str3 = str2 + “”;代码实际上是创建了一个StringBuild对象,已经是两个对象了 -
(str4 == str4)==false
两个字符串都是new String(“Java”),是两个对象,所以为false
2.6.3 String StringBuffer StringBuild区别
1.字符串String
字符串类是final修饰,不可继承和修改的,
String s="a";
s=s+"b";
其实是产生了两个字符串,执行s=s+“b”;后,s="a"就被丢弃在公共常量池里,如果进行大量的修改,
会导致性能很差。
2.字符串StringBuffer
在 Java1.0 的时候,若要对字符串进行大量修改,应当使用 StringBuffer,它是可修改的,同时,当时的开发人员考虑到多个线程对一个字符串的修改可能出现线程不安全的问题,于是让 StringBuffer 在拥有可修改字符串的功能的情况下,又给它加上了线程安全的机制。看到这里是不是觉得还挺好,挺正常的?但是要知道一个前提,那就是在 Java5 之前的 Java 在处理字符串的速度上一直被别人诟病,原因出在哪里?原因就在于这个 StringBuffer 上面。
StringBuffer 本来是为了实现大量修改字符串的功能而出现的,但却因为 Java 的开发人员给它加了个线程安全的功能,导致它执行效率极大地下降。这个线程安全的功能的实现并不是像我们现在用的方法,当时只是保证没有异常抛出,程序可以正常运行下去而已。在 Java 中,要实现字符串的相加,用加法运算符将两个字符串相加即可。但在这个过程中,Java5 之前是有 String 自动隐含地转换成 StringBuffer,再进行操作这一个步骤的(毕竟 String 类不可直接修改)。只要有这些步骤,就可以实现字符串的修改,但是呢,StringBuffer 有个线程安全的功能,它会在上面提到的步骤中还额外的执行一些功能,以保证线程的安全,而且,这里实现线程安全的方式和我们现在用锁的方式是不一样的!它这里的实现线程安全的方式极为繁琐且复杂,这就大大降低了 StringBuffer 的执行效率,以至于后来被广大程序员诟病。
3.字符串StringBuilder
我们仔细地想一下,实际上也并没有多少地方需要在修改字符串的同时保证线程安全,就算有,我们给它加个锁就行。基于这种想法,在 StringBuffer 出现 10 年之后,Java 的开发人员回过头看这个问题,才发现 StringBuffer 的实现是多么的愚蠢,于是后来在 Java5 就有了 StringBuilder。StringBuilder 同样可以快速高效地修改字符串,同时不是线程安全的。虽然它不是线程安全的,但是它的执行效率却比 StringBuffer 要高上了不少。在 Java5 之后的版本中,字符串相加隐含的转化过程中,不再将 String 转化为 StringBuffer,而是转化成 StringBuilder。
总结:
在大量操作字符串时,java已经优化了,不推荐使用StringBuffer
2.7数组
2.7.1 数组定义
数组有多种定义方式
@Test
public void t1() {
//第一种定义数组的方法:
int[] array1 = {1, 2, 3};//直接赋值(静态初始化)
//int[]是数组的类型,array为数组名,随意取名字
//第二种定义数组的方法:
int[] array2 = new int[]{1, 2, 3, 4};//数组的动态初始化
//第三种定义数组的方法:
int[] array3 = new int[10];//只是分配了内存,但是没有进行赋值,默认值都是0,
System.out.println(array3[2]); //0
//第四种定义数组的方法:
int[] array4;
array4 = new int[]{1, 2, 3};//一定要为数组符初值,不然编译器会报错,
int[] array5=null;//等价int[] array4
}
4.注意事项
- 静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
- 静态初始化时, {}中数据类型必须与[]前数据类型一致。
- 静态初始化可以简写,省去后面的new T[]。T可以为任意数据类型。
- 如果数组中存储元素类型为引用类型,默认值为null。
- 初始化后,数组的长度是固定的,不能动态扩容,这个是和list最大的区别
- 数组参数是地址引用,不是值引用
地址引用:
int[] array1 = {1,2,3,4};
System.out.print(Arrays.toString(array1));//打印[1,2,3,4]
int[] array2 = array1;
array2[1] = 99;
System.out.print(Arrays.toString(array1));//打印[1,99,3,4],不是[1,2,3,4]
System.out.print(Arrays.toString(array2));//打印[1,99,3,4]
首先创建一个数组array1,并初始化赋值为1,2,3,4,然后打印数组array1,接着申请另一款空间,用来创建array2,且array2=array1,说明两个数组都指向同一块空间,修改array2中的第二个元素也就相当于修改了array1中对应的元素。
2.7.2 数组帮助类Arrays
Arrays提供了几个很有用的数组帮助方法:
直接看代码,一目了然
@Test
public void t3(){
System.out.println("Arrays类的常见用法");
int[] array={5,4,2,6,3};
//1.打印
String str=Arrays.toString(array);
System.out.println(str);//[5, 4, 2, 6, 3]
//2.数组大小排序
Arrays.sort(array);
System.out.println(Arrays.toString(array));//[2, 3, 4, 5, 6]
//3.判断两个数组内容是否相等
int[] array1={1,2,3};
int[] array2={1,2,3};
System.out.println(Arrays.equals(array1, array2)); //true
System.out.println(array1.equals(array2));//false,不能采用这个方法判断数组内容
//3.查找某个元素的索引
int[] array3={1,2,3};
int index=Arrays.binarySearch(array3,2);
System.out.println(index); //1
//注意事项,如果没有,则返回小于0,如果多个,则返回第一个
int[] array4={1,2,3,2};
System.out.println(Arrays.binarySearch(array4, 2));//1
System.out.println("ddd:"+Arrays.binarySearch(array4, 4));//小于零
//4.转化成list数据类型
Integer[] array5={1,2,3};
//如果是对象,则直接List<对应的数组元素类型>
List<Integer> list=Arrays.asList(array5);
for (Integer i : list) {
System.out.println(i);
}
// list.add(9); //运行报错,不能新增和删除
// list.remove(0);//运行报错,不能新增和删除
/***
注意如果数组是基本数据类型,用Array.asList是无法转化的。
需要借助第三方先把基本类型数组转化成对应的包装类数组
如:Apache Commons Lang3
*/
int[] array6={1,2,3,5};
Integer[] array7= ArrayUtils.toObject(array6);
//4.copy值
int[] arr1 = {1, 2, 3};
int[] arr2 = Arrays.copyOf(arr1, 5);
System.out.println(Arrays.toString(arr2)); //[1, 2, 3, 0, 0]
//注意:copyOf是值copy,不是地址引用
arr2[3]=20;
System.out.println(Arrays.toString(arr2));//[1, 2, 3, 20, 0]
System.out.println(Arrays.toString(arr1));//[1, 2, 3]
//copyOfRange方法提供了开始索引
int[] arr3 = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] arr4 = new int[5];
arr4 = Arrays.copyOfRange(arr3,3,5);
System.out.println(Arrays.toString(arr4));//[4, 5]
}
2.7.3 Apache Commons Lang3
org.apache.commons.lang3.ArrayUtils这个类也很大扩展了数组的操作,具体查看apache文档
2.8 equals和==、compareTo区别
2.8.1 ==和equals
- == 是一个运算符,用于比较两个对象的引用是否相同,即它们是否指向内存中的相同位置。
- equals 是一个方法,通常在Object类中定义,它用于比较两个对象的内容是否相等。默认情况下,equals方法执行与==相同的引用比较,但它可以被子类重写以提供自定义的相等性逻辑
“== ”在哪些情况下比较的是对象内容而不是引用
- 在Java中,== 运算符通常比较的是对象的引用。但在以下情况下,== 可以比较对象的内容而不是引用:
- 对于基本数据类型(如int、char等),== 比较的是它们的值,而不是引用。
- 字符串常量池:对于字符串字面值,就是String s=“123”,不是String s=new String(“123”);,Java使用常量池来存储它们,因此相同的字符串字面值使用==比较通常会返回true。
代码1
@Test
public void t1(){
//基本类型数据(数值、字符、布尔)的值一样,则==为true
char c1='a';
char c2='a';
float f1=3.5f;
float f2=3.5f;
double f3=3.5;
int i1=1;
int i2=1;
long i3=1l;
System.out.println(c1==c2);//true
System.out.println(f1==f2);//true
System.out.println(f1==f3);//true
System.out.println(i1==i2);//true
System.out.println(i1==i3);//true
}
代码2: 字符串是一个对象又有基础数据的特殊性,可参考2.6.2
@Test
public void t2(){
String s1="123";
String s2="123";
String s3=s1;
String s4=s2+"";
String s5=new String("123");
String s6=new String("123");
String s7=s5;
//s="xx"是直接声明,String是final常量,性质和基础数据类型一样,s1,s2都指向同一个内存地址
System.out.println(s1==s2); //true
//所有=变量赋值的语法,指针都是一样
System.out.println(s1==s3); //true
//因为String是final类型,s2+""实际是创建一个对象
System.out.println(s1==s4); //false
//两个不同对象,内存地址不一样
System.out.println(s5==s6); //false
//所有=变量赋值的语法,指针都是一样
System.out.println(s5==s7); //true
//如果是euqal比较,这几个都返回true
System.out.println( //true
s1.equals(s2) && s1.equals(s3) && s1.equals(s4) && s5.equals(s6)
);
}
}
代码3:标准vo对象
@Test
public void t3(){
TestVO vo1=new TestVO(1,"jzk");
TestVO vo2=vo1;
TestVO vo3=new TestVO(1,"jzk");
//内存指针一样
System.out.println(vo1==vo2);//true
//两个不同对象
System.out.println(vo1==vo3);//false
//三个对象值都是一样的
System.out.println(vo1.equals(vo2) && vo2.equals(vo3));//true
//vo2修改了值,因为vo1指针和他一致,所以vo1也会改变,最后他们都是一致的
vo2.setName("奎哥");
System.out.println(vo1==vo2);//true
//值已经改变了
System.out.println(vo3.equals(vo1));//false
}
equals 和 hashCode 之间有什么关系?
equals 和 hashCode 在Java中通常一起使用,以维护对象在散列集合(如HashMap和HashSet)中的正确行为。
如果两个对象相等(根据equals方法的定义),那么它们的hashCode值应该相同。
也就是说,如果重写了一个类的equals方法,通常也需要重写hashCode方法,以便它们保持一致。
这是因为散列集合使用对象的hashCode值来确定它们在内部存储结构中的位置。
2.8.2 compareTo
仅仅知道两个字符串是否相同是不够的。对于排序应用来说,必须知道一个字符串是大于、等于还是小于另一个。一个字符串小于另一个指的是它在字典中先出现。而一个字符串大于另一个指的是它在字典中后出现。字符串(String)的 compareTo() 方法实现了这种功能。
- compareTo() 方法用于按字典顺序比较两个字符串的大小,该比较是基于字符串各个字符的 Unicode 值。
- 如果两个字符串调用 equals() 方法返回 true,那么调用 compareTo() 方法会返回 0,如果前者大则大于0,否则小于0
- compareTo只能用于对象,不能用于基础数据类型,他实际上是Comparable接口类的方法,实现这个接口的有:String、基础数据类型的包装类、Date、BigDecimal 等
代码:
@Test
public void t4(){
//字符串比较
String s1="a";
String s2="abc";
System.out.println(s1.compareTo(s2));
//基本类型的包装类比较
Integer i1=3;
Integer i2=5;
System.out.println(i1.compareTo(i2));//<0
System.out.println(i1<i2);//true
//日期Date,BigDecimal
Date d1= DateUtil.getDateByStr("2003-10-01", DateUtil.date_gs);
Date d2= DateUtil.getDateByStr("2004-09-01", DateUtil.date_gs);
System.out.println(d1.compareTo(d2));//<0
BigDecimal bg1=new BigDecimal("1.0");
BigDecimal bg2=new BigDecimal("1.00");
System.out.println(bg1.equals(bg2)); //返回的是false,所以比较BigDecimal不要用equals
System.out.println(bg1.compareTo(bg2));//0
}
}
2.9代码块、内部类和匿名类
2.9.1代码块
类里用{}包裹起来的代码称为代码块,代码块有三种类型
1.静态代码块
用static关键字修饰叫静态代码块,
2.同步代码块
使用synchronize关键字修饰,并使用"{}“括起来的代码片段.它表示在同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制.
3.非静态代码块
在类中没与任何的前缀或后缀,并使用”{}"括起来的代码片段叫非静态代码块,也叫构造代码块
//非静态代码块
{
int i=0;
System.out.println("非静态代码块........");
}
//静态代码块
static {
int i1=0;
System.out.println("静态代码块........");
}
- 静态代码块使用static关键字进行修饰
- 静态代码块的内部可以有输出语句
- 静态代码块在类加载时执行,并且只会执行一次(因为一个类只会加载一次)
- 静态代码块用于初始化类,但是初始化的是类的静态属性,类的非静态属性是无法在静态代码块中被初始化的
- 若是在一个类中存在多个静态代码块,那么执行时会按照这些静态代码块的定义顺序来执行
- 若是在静态代码块中存在调用,那么只能调用静态的属性和方法,不能调用非静态的属性和方法
- 非静态代码块不使用static关键字进行修饰
- 非静态代码块的内部可以有输出语句
- 非静态代码块随着对象的创建而执行
- 每创建一个对象便会调用一次非静态代码块(因为非静态代码块是用于初始化对象的,因此,每次使用new关键字创建一个对象都会新增一个对象并使用非静态代码块对其初始化)
- 非静态代码块可以在创建对象时,对对象的属性进行初始化
- 若是在一个类中定义了多个非静态代码块,则这些非静态代码块会按照定义的先后顺序来执行
- 非静态代码块中,可以调用该类的静态属性、静态方法、非静态属性、非静态方法(因为静态的内容是随着类的加载而加载的,而非静态内容是在静态内容之后被加载的,因此非静态代码块调用已经存在的静态内容是完全没有问题的)
1.首先执行静态代码块,在类初始化执行。静态代码块只执行一次
2.其实执行非静态代码块,在对象实例化new时执行,每次实例化都会执行一次
3.最后执行构造函数
代码:
对象类
public class CodePiece {
public static final String s="dd";
//分静态代码块
{
int i=0;
System.out.println("非静态代码块.......2");
}
//静态代码块
static {
int i1=0;
System.out.println("静态代码块........1");
}
private int i=2;
public CodePiece(){
System.out.println("构造函数.............3");
// System.out.println("i="+i);
}
}
测试
Test
public void t1() throws ClassNotFoundException {
// System.out.println( CodePiece.s);
//第一次要执行静态代码块
Class cls=Class.forName("com.jsoft.ref.CodePiece");
//第二次将不会执行静态代码块
Class cls2=Class.forName("com.jsoft.ref.CodePiece");
CodePiece v1=new CodePiece(); //执行非静态代码代码和构造函数
CodePiece v2=new CodePiece();//执行非静态代码代码和构造函数
}
效果
静态代码块........1
非静态代码块.......2
构造函数.............3
非静态代码块.......2
构造函数.............3
2.9.2内部类
把一个类定义在令一个类里叫内部类,内部类分为静态内部类和非静态内部类两种,静态内部类用 static关键字修饰;内部类也可以定义成只有外部类能访问的私有类
public class MuCls {
private Integer id;
//非静态内部内类
public class InnerClass1{
}
//静态内部类
public static class InnerClass2{
}
//如果一个内部类不想让外部访问,可以声明一个私有的内部类
privae class InnerClass3{
}
}
A{ class B{} }
注意事项:
1.非静态内部类里不能有静态的字段和方法
2.内外部类通信,和 private public无关,因为都在同一个类里
3.一般情况下代码外部类为主,所以非静态内部类使用多一些
public class MuCls {
private Integer id;
public static String type;
public void outM1(){
System.out.println("外部类方法");
}
//操作非静态内部类
public void outM2(){
//访问非静态内部类,必须用new实例化
InnerClass1 innerClass1=new InnerClass1();
//访问内部类字段
Integer i=innerClass1.id; //不管内部类字段是private还是public都可以访问
//调用内部类方法
innerClass1.innerM1();
}
//访问静态内部类
public void outM3(){
//访问静态内部类的静态字段和属性,可以直接访问
Integer sex=InnerClass2.sex;
InnerClass2.help();
//访问非静态字段和属性,还是要new 内部类
InnerClass2 innerClass2=new InnerClass2();
//访问内部类字段
String name=innerClass2.name; //不管内部类字段是private还是public都可以访问
//调用内部类方法
innerClass2.innerM2();
}
//外部静态方法
public static void outM4(){
System.out.println("外部类静态方法");
}
//非静态内部内类
//注意非静态类里不能用static修饰字段和方法
public class InnerClass1{
private Integer id;
public void innerM1(){
System.out.println("非静态类方法innerM1()");
}
//访问外部类
public void innerM2(){
// 外部类非静态字段或者方法:外部类名.this.字段或者方法
Integer outId=MuCls.this.id; //非静态字段
MuCls.this.outM1();//非静态方法
//外部类静态字段或方法:外部名类.字段或者方法
String outType=MuCls.type;//静态字段
MuCls.outM4();//静态方法
}
}
//静态内部类
//静态类里可以用static修饰字段和方法
public static class InnerClass2{
//内部类静态字段
private static Integer sex;
private String name;
//内部类静态方法
public static void help(){
}
public void innerM2(){
System.out.println("非静态类方法innerM2()");
}
//访问外部类
public void innerM3(){
// 静态类不能直接访问外部类的非静态字段和方法,需要new 外部类
MuCls muCls=new MuCls();
Integer outId=muCls.id;
muCls.outM1();
//外部类静态字段或方法:外部名类.字段或者方法
String outType=MuCls.type;//静态字段
MuCls.outM4();//静态方法
}
}
}
A{ B{} }
public class InnerTest {
/***
非静态内部类测试
1.外部调用必须先实例化外部类获得外部类的对象
2.获得内部类对象=外部类的对象.new 内部类名
*/
@Test
public void t3(){
MuCls muCls=new MuCls();
//得到内部类对象
MuCls.InnerClass1 innerClass1=muCls.new InnerClass1();
//调用内部类方法
innerClass1.innerM1();
}
/***
静态内部类调用测试
1.内部静态类里面的静态属性和方法可以直接访问
2.内部静态类的实例化和非静态类不一样,可以直接new
*/
@Test
public void t4() {
//1.访问静态内部类的静态字段和方法
MuCls.InnerClass2.help();
//2.访问静态内部类的非静态字段和方法
//静态内部类,实际上是独立的一个类,可直接new生成对象,和非静态内部内不一样
MuCls.InnerClass2 innerClass2=new MuCls.InnerClass2();
innerClass2.innerM2();
}
}
有时候一个类太复杂,我们把一个类里面的一些属性和方法抽取成一个内部类,隐藏复杂逻辑
,而且内部方法需要互相方便的访问,比抽取成一个外部类更方便。
2.9.3匿名类
- 使用 Java 编程时,常常需要创建不会被再次使用的对象。
在这种情况下,非常适合使用一种特殊的内部类:匿名内部类。
这种类没有名称,是在同一条语句中声明和创建的。
要使用匿名内部类,可将引用对象的代码替换为关键字 new、对构造函数的调用以及用大括号({和})括起的类定义。
既然是匿名类,所以你无法在别的地方实例化和使用这个类。 - 匿名内部类也可用于接口(interface)的实现
准备类:
public class NoName {
public void play(){
System.out.println("执行NoName.play()");
}
public void eat(){
System.out.println("执行NoName.eat()");
}
}
public interface NoNameInterface {
public void play();
public void eat();
}
@Test
public void t1(){
//正常声明对象
NoName noName=new NoName();
noName.play();
//如果我们这个noName不在其他地方使用,而且只调用一个方法,就可以简写如下:
new NoName().play();
}
new 类名或者接口名() {
重写方法;
}
//在方法体里重写匿名类的方法
@Test
public void t2() {
new NoName() { //重写NoName类的play()方法
@Override
public void play() {
System.out.println("重写方法play()");
}
}.play();
}
//重写参数类里的方法
@Test
public void t3() {
param(new NoName() {
@Override
public void play() {
System.out.println("重写参数类的play()");
}
}
);
/*** 等价于
public class A extends NoName{
public void play() {
System.out.println("重写参数类的play()");
}
}
NoName a=new A();
param(a);
*/
}
private void param(NoName noName) {
noName.play();
}
代码
//接口实现匿名类的写法
@Test
public void t4(){
new NoNameInterface(){
@Override
public void play(){
System.out.println("接口实现匿名类.play()");
}
@Override
public void eat(){
System.out.println("接口实现匿名类.eat()");
}
}.play();
}
//如果接口里有多个方法被调用,则可以用下面这种方式
@Test
public void t5(){
NoNameInterface service= new NoNameInterface(){
@Override
public void play(){
System.out.println("接口实现匿名类.play()");
}
@Override
public void eat(){
System.out.println("接口实现匿名类.eat()");
}
};
service.play();
service.eat();
}
//方法参数是接口的匿名实现类写法
@Test
public void t6(){
param2(new NoNameInterface(){
@Override
public void play() {
System.out.println("参数接口实现匿名类.play()");
}
@Override
public void eat() {
System.out.println("参数接口实现匿名类.eat()");
}
});
}
private void param2(NoNameInterface service) {
service.play();
}
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。
3、匿名内部类中不能存在任何的静态成员变量和静态方法。
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。