u007f商城网站源码分享

大家好,今天来为大家分享u007f商城网站源码分享的一些知识点,和的问题解析,大家要是都明白,那么可以忽略,如果不太清楚的话可以看看本篇文章,相信很大概率可以解决您的问题,接下来我们就一起来看看吧!

Java基础篇

Java有哪些特点

并发性的:你可以在其中执行许多语句,而不必一次执行它面向对象的:基于类和面向对象的编程语言。独立性的:支持一次编写,到处运行的独立编程语言,即编译后的代码可以在支持Java的所有平台上运行。

Java的特性

Java的特性有如下这几点

简单,Java会让你的工作变得更加轻松,使你把关注点放在主要业务逻辑上,而不必关心指针、运算符重载、内存回收等与主要业务无关的功能。便携性,Java是平台无关性的,这意味着在一个平台上编写的任何应用程序都可以轻松移植到另一个平台上。安全性,编译后会将所有的代码转换为字节码,人类无法读取。它使开发无病毒,无篡改的系统/应用成为可能。动态性,它具有适应不断变化的环境的能力,它能够支持动态内存分配,从而减少了内存浪费,提高了应用程序的性能。分布式,Java提供的功能有助于创建分布式应用。使用远程方法调用(RMI),程序可以通过网络调用另一个程序的方法并获取输出。您可以通过从互联网上的任何计算机上调用方法来访问文件。这是革命性的一个特点,对于当今的互联网来说太重要了。健壮性,Java有强大的内存管理功能,在编译和运行时检查代码,它有助于消除错误。高性能,Java最黑的科技就是字节码编程,Java代码编译成的字节码可以轻松转换为本地机器代码。通过JIT即时编译器来实现高性能。解释性,Java被编译成字节码,由Java运行时环境解释。多线程性,Java支持多个执行线程(也称为轻量级进程),包括一组同步原语。这使得使用线程编程更加容易,Java通过管程模型来实现线程安全性。

描述一下值传递和引用传递的区别

要想真正理解的话,可以参考这篇文章:https://www.zhihu.com/question/31203609

简单理解的话就是

值传递是指在调用函数时将实际参数复制一份到函数中,这样的话如果函数对其传递过来的形式参数进行修改,将不会影响到实际参数

引用传递是指在调用函数时将对象的地址直接传递到函数中,如果在对形式参数进行修改,将影响到实际参数的值。

==和equals区别是什么

==是Java中一种操作符,它有两种比较方式

对于基本数据类型来说,==判断的是两边的值是否相等

publicclassDoubleCompareAndEquals{\n\nPersonperson1=newPerson(24,&34;);\nPersonperson2=newPerson(24,&34;);\nintc=10;\n\nprivatevoiddoubleCompare(){\n\ninta=10;\nintb=10;\n\nSystem.out.println(a==b);\nSystem.out.println(a==c);\nSystem.out.println(person1.getId()==person2.getId());\n\n}\n}\n

对于引用类型来说,==判断的是两边的引用是否相等,也就是判断两个对象是否指向了同一块内存区域。

privatevoidequals(){\n\nSystem.out.println(person1.getName().equals(person2.getName()));\n}\n

equals是Java中所有对象的父类,即Object类定义的一个方法。它只能比较对象,它表示的是引用双方的值是否相等。所以记住,并不是说==比较的就是引用是否相等,equals比较的就是值,这需要区分来说的。

equals用作对象之间的比较具有如下特性

自反性:对于任何非空引用x来说,x.equals(x)应该返回true。对称性:对于任何非空引用x和y来说,若x.equals(y)为true,则y.equals(x)也为true。传递性:对于任何非空引用的值来说,有三个值,x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。一致性:对于任何非空引用x和y来说,如果x.equals(y)相等的话,那么它们必须始终相等。非空性:对于任何非空引用的值x来说,x.equals(null)必须返回false。

String中的equals是如何重写的

String代表的是Java中的字符串,String类比较特殊,它整个类都是被final修饰的,也就是说,String不能被任何类继承,任何修改String字符串的方法都是创建了一个新的字符串。

equals方法是Object类定义的方法,Object是所有类的父类,当然也包括String,String重写了equals方法,下面我们来看看是怎么重写的

首先会判断要比较的两个字符串它们的引用是否相等。如果引用相等的话,直接返回true,不相等的话继续下面的判断然后再判断被比较的对象是否是String的实例,如果不是的话直接返回false,如果是的话,再比较两个字符串的长度是否相等,如果长度不想等的话也就没有比较的必要了;长度如果相同,会比较字符串中的每个字符是否相等,一旦有一个字符不相等,就会直接返回false。

下面是它的流程图

这里再提示一下,你可能有疑惑什么时候是

if(this==anObject){\nreturntrue;\n}\n

这个判断语句如何才能返回true?因为都是字符串啊,字符串比较的不都是堆空间吗,猛然一看发现好像永远也不会走,但是你忘记了String.intern()方法,它表示的概念在不同的JDK版本有不同的区分

在JDK1.7及以后调用intern方法是判断运行时常量池中是否有指定的字符串,如果没有的话,就把字符串添加到常量池中,并返回常量池中的对象。

验证过程如下

privatevoidStringOverrideEquals(){\n\nStrings1=&34;;\nStrings2=&34;+newString(&34;);\nStrings3=newString(&34;);\n\nSystem.out.println(s1.intern().equals(s1));\nSystem.out.println(s1.intern().equals(s2));\nSystem.out.println(s3.intern().equals(s1));\n\n}\n

首先s1.intern.equals(s1)这个无论如何都返回true,因为s1字符串创建出来就已经在常量池中存在了。然后第二条语句返回false,因为s1返回的是常量池中的对象,而s2返回的是堆中的对象第三条语句s3.intern.equals(s1),返回true,因为s3对象虽然在堆中创建了一个对象,但是s3中的&34;返回的是常量池中的对象。

为什么重写equals方法必须重写hashcode方法

equals方法和hashCode都是Object中定义的方法,它们经常被一起重写。

equals方法是用来比较对象大小是否相等的方法,hashcode方法是用来判断每个对象hash值的一种方法。如果只重写equals方法而不重写hashcode方法,很可能会造成两个不同的对象,它们的hashcode也相等,造成冲突。比如

Stringstr1=&34;;\nStringstr2=&34;;\n

它们两个的hashcode相等,但是equals可不相等。

我们来看一下hashCode官方的定义

总结起来就是

如果在Java运行期间对同一个对象调用hashCode方法后,无论调用多少次,都应该返回相同的hashCode,但是在不同的Java程序中,执行hashCode方法返回的值可能不一致。如果两个对象的equals相等,那么hashCode必须相同如果两个对象equals不相等,那么hashCode也有可能相同,所以需要重写hashCode方法,因为你不知道hashCode的底层构造(反正我是不知道,有大牛可以传授传授),所以你需要重写hashCode方法,来为不同的对象生成不同的hashCode值,这样能够提高不同对象的访问速度。hashCode通常是将地址转换为整数来实现的。

Strings1=newString(&34;)在内存中创建了几个对象

一个或者两个,Strings1是声明了一个String类型的s1变量,它不是对象。使用new关键字会在堆中创建一个对象,另外一个对象是abc,它会在常量池中创建,所以一共创建了两个对象;如果abc在常量池中已经存在的话,那么就会创建一个对象。

详细请翻阅笔者的另外一篇文章一篇与众不同的String、StringBuffer、StringBuilde详解

String为什么是不可变的、jdk源码中的String如何定义的、为什么这么设计。

首先了解一下什么是不可变对象,不可变对象就是一经创建后,其对象的内部状态不能被修改,啥意思呢?也就是说不可变对象需要遵守下面几条原则

不可变对象的内部属性都是final的不可变对象的内部属性都是private的不可变对象不能提供任何可以修改内部状态的方法、setter方法也不行不可变对象不能被继承和扩展

与其说问String为什么是不可变的,不如说如何把String设计成不可变的。

String类是一种对象,它是独立于Java基本数据类型而存在的,String你可以把它理解为字符串的集合,String被设计为final的,表示String对象一经创建后,它的值就不能再被修改,任何对String值进行修改的方法就是重新创建一个字符串。String对象创建后会存在于运行时常量池中,运行时常量池是属于方法区的一部分,JDK1.7后把它移到了堆中。

不可变对象不是真的不可变,可以通过反射来对其内部的属性和值进行修改,不过一般我们不这样做。

static关键字是干什么用的?谈谈你的理解

static是Java中非常重要的关键字,static表示的概念是静态的,在Java中,static主要用来

修饰变量,static修饰的变量称为静态变量、也称为类变量,类变量属于类所有,对于不同的类来说,static变量只有一份,static修饰的变量位于方法区中;static修饰的变量能够直接通过类名.变量名来进行访问,不用通过实例化类再进行使用。修饰方法,static修饰的方法被称为静态方法,静态方法能够直接通过类名.方法名来使用,在静态方法内部不能使用非静态属性和方法static可以修饰代码块,主要分为两种,一种直接定义在类中,使用static{},这种被称为静态代码块,一种是在类中定义静态内部类,使用staticclassxxx来进行定义。static可以用于静态导包,通过使用importstaticxxx来实现,这种方式一般不推荐使用static可以和单例模式一起使用,通过双重检查锁来实现线程安全的单例模式。

详情请参考这篇文章一篇static还能难得住我?

final关键字是干什么用的?谈谈你的理解

final是Java中的关键字,它表示的意思是不可变的,在Java中,final主要用来

修饰类,final修饰的类不能被继承,不能被继承的意思就是不能使用extends来继承被final修饰的类。修饰变量,final修饰的变量不能被改写,不能被改写的意思有两种,对于基本数据类型来说,final修饰的变量,其值不能被改变,final修饰的对象,对象的引用不能被改变,但是对象内部的属性可以被修改。final修饰的变量在某种程度上起到了不可变的效果,所以,可以用来保护只读数据,尤其是在并发编程中,因为明确的不能再为final变量进行赋值,有利于减少额外的同步开销。修饰方法,final修饰的方法不能被重写。final修饰符和Java程序性能优化没有必然联系

抽象类和接口的区别是什么

抽象类和接口都是Java中的关键字,抽象类和接口中都允许进行方法的定义,而不用具体的方法实现。抽象类和接口都允许被继承,它们广泛的应用于JDK和框架的源码中,来实现多态和不同的设计模式。

不同点在于

抽象级别不同:类、抽象类、接口其实是三种不同的抽象级别,抽象程度依次是接口>抽象类>类。在接口中,只允许进行方法的定义,不允许有方法的实现,抽象类中可以进行方法的定义和实现;而类中只允许进行方法的实现,我说的方法的定义是不允许在方法后面出现{}使用的关键字不同:类使用class来表示;抽象类使用abstractclass来表示;接口使用interface来表示变量:接口中定义的变量只能是公共的静态常量,抽象类中的变量是普通变量。

重写和重载的区别

在Java中,重写和重载都是对同一方法的不同表现形式,下面我们针对重写和重载做一下简单的区分

子父级关系不同,重写是针对子级和父级的不同表现形式,而重载是在同一类中的不同表现形式;概念不同,子类重写父类的方法一般使用@override来表示;重写后的方法其方法的声明和参数类型、顺序必须要与父类完全一致;重载是针对同一类中概念,它要求重载的方法必须满足下面任何一个要求:方法参数的顺序,参数的个数,参数的类型任意一个保持不同即可。

byte的取值范围是多少,怎么计算出来的

byte的取值范围是-128->127之间,一共是256位。一个byte类型在计算机中占据一个字节,那么就是8bit,所以最大就是2^8=11111111。

Java中用补码来表示二进制数,补码的最高位是符号位,最高位用0表示正数,最高位1表示负数,正数的补码就是其本身,由于最高位是符号位,所以正数表示的就是01111111,也就是127。最大负数就是11111111,这其中会涉及到两个0,一个+0,一个-0,+0归为正数,也就是0,-0归为负数,也就是-128,所以byte的范围就是-128-127。

HashMap和HashTable的区别

相同点

HashMap和HashTable都是基于哈希表实现的,其内部每个元素都是key-value键值对,HashMap和HashTable都实现了Map、Cloneable、Serializable接口。

不同点

父类不同:HashMap继承了AbstractMap类,而HashTable继承了Dictionary类空值不同:HashMap允许空的key和value值,HashTable不允许空的key和value值。HashMap会把Nullkey当做普通的key对待。不允许nullkey重复。线程安全性:HashMap不是线程安全的,如果多个外部操作同时修改HashMap的数据结构比如add或者是delete,必须进行同步操作,仅仅对key或者value的修改不是改变数据结构的操作。可以选择构造线程安全的Map比如Collections.synchronizedMap或者是ConcurrentHashMap。而HashTable本身就是线程安全的容器。性能方面:虽然HashMap和HashTable都是基于单链表的,但是HashMap进行put或者get操作,可以达到常数时间的性能;而HashTable的put和get操作都是加了synchronized锁的,所以效率很差。初始容量不同:HashTable的初始长度是11,之后每次扩充容量变为之前的2n+1(n为上一次的长度)而HashMap的初始长度为16,之后每次扩充变为原来的两倍。创建时,如果给定了容量初始值,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。

HashMap和HashSet的区别

HashSet继承于AbstractSet接口,实现了Set、Cloneable,、java.io.Serializable接口。HashSet不允许集合中出现重复的值。HashSet底层其实就是HashMap,所有对HashSet的操作其实就是对HashMap的操作。所以HashSet也不保证集合的顺序,也不是线程安全的容器。

HashMap的底层结构

JDK1.7中,HashMap采用位桶+链表的实现,即使用链表来处理冲突,同一hash值的链表都存储在一个数组中。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。

所以,与JDK1.7相比,JDK1.8在底层结构方面做了一些改变,当每个桶中元素大于8的时候,会转变为红黑树,目的就是优化查询效率。

HashMap的长度为什么是2的幂次方

这道题我想了几天,之前和群里小伙伴们探讨每日一题的时候,问他们为什么length%hash==(n-1)&hash,它们说相等的前提是length的长度2的幂次方,然后我回了一句难道length还能不是2的幂次方吗?其实是我没有搞懂因果关系,因为HashMap的长度是2的幂次方,所以使用余数来判断在桶中的下标。如果length的长度不是2的幂次方,小伙伴们可以举个例子来试试

例如长度为9时候,3&(9-1)=0,2&(9-1)=0,都在0上,碰撞了;

这样会增大HashMap碰撞的几率。

HashMap多线程操作导致死循环问题

HashMap不是一个线程安全的容器,在高并发场景下,应该使用ConcurrentHashMap,在多线程场景下使用HashMap会造成死循环问题(基于JDK1.7),出现问题的位置在rehash处,也就是

do{\nEntry<K,V>next=e.next;//<–假设线程一执行到这里就被调度挂起了\ninti=indexFor(e.hash,newCapacity);\ne.next=newTable[i];\nnewTable[i]=e;\ne=next;\n}while(e!=null);\n

这是JDK1.7的rehash代码片段,在并发的场景下会形成环。

JDK1.8也会造成死循环问题。

HashMap线程安全的实现有哪些

因为HashMap不是一个线程安全的容器,所以并发场景下推荐使用ConcurrentHashMap,或者使用线程安全的HashMap,使用Collections包下的线程安全的容器,比如说

Collections.synchronizedMap(newHashMap());\n

还可以使用HashTable,它也是线程安全的容器,基于key-value存储,经常用HashMap和HashTable做比较就是因为HashTable的数据结构和HashMap相同。

上面效率最高的就是ConcurrentHashMap。

讲一下HashMapput的过程

首先会使用hash函数来计算key,然后执行真正的插入方法

finalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,\nbooleanevict){\nNode<K,V>[]tab;Node<K,V>p;intn,i;\n//如果table为null或者没有为table分配内存,就resize一次\nif((tab=table)==null||(n=tab.length)==0)\nn=(tab=resize()).length;\n//指定hash值节点为空则直接插入,这个(n-1)&hash才是表中真正的哈希\nif((p=tab[i=(n-1)&hash])==null)\ntab[i]=newNode(hash,key,value,null);\n//如果不为空\nelse{\nNode<K,V>e;Kk;\n//计算表中的这个真正的哈希值与要插入的key.hash相比\nif(p.hash==hash&&\n((k=p.key)==key||(key!=null&&key.equals(k))))\ne=p;\n//若不同的话,并且当前节点已经在TreeNode上了\nelseif(pinstanceofTreeNode)\n//采用红黑树存储方式\ne=((TreeNode<K,V>)p).putTreeVal(this,tab,hash,key,value);\n//key.hash不同并且也不再TreeNode上,在链表上找到p.next==null\nelse{\nfor(intbinCount=0;;++binCount){\nif((e=p.next)==null){\n//在表尾插入\np.next=newNode(hash,key,value,null);\n//新增节点后如果节点个数到达阈值,则进入treeifyBin()进行再次判断\nif(binCount>=TREEIFY_THRESHOLD-1)//-1for1st\ntreeifyBin(tab,hash);\nbreak;\n}\n//如果找到了同hash、key的节点,那么直接退出循环\nif(e.hash==hash&&\n((k=e.key)==key||(key!=null&&key.equals(k))))\nbreak;\n//更新p指向下一节点\np=e;\n}\n}\n//map中含有旧值,返回旧值\nif(e!=null){//existingmappingforkey\nVoldValue=e.value;\nif(!onlyIfAbsent||oldValue==null)\ne.value=value;\nafterNodeAccess(e);\nreturnoldValue;\n}\n}\n//map调整次数+1\n++modCount;\n//键值对的数量达到阈值,需要扩容\nif(++size>threshold)\nresize();\nafterNodeInsertion(evict);\nreturnnull;\n}\n

HashMapput方法的核心就是在putval方法,它的插入过程如下

首先会判断HashMap中是否是新构建的,如果是的话会首先进行resize然后判断需要插入的元素在HashMap中是否已经存在(说明出现了碰撞情况),如果不存在,直接生成新的k-v节点存放,再判断是否需要扩容。如果要插入的元素已经存在的话,说明发生了冲突,这就会转换成链表或者红黑树来解决冲突,首先判断链表中的hash,key是否相等,如果相等的话,就用新值替换旧值,如果节点是属于TreeNode类型,会直接在红黑树中进行处理,如果hash,key不相等也不属于TreeNode类型,会直接转换为链表处理,进行链表遍历,如果链表的next节点是null,判断是否转换为红黑树,如果不转换的话,在遍历过程中找到key完全相等的节点,则用新节点替换老节点

ConcurrentHashMap底层实现

ConcurrentHashMap是线程安全的Map,它也是高并发场景下的首选数据结构,ConcurrentHashMap底层是使用分段锁来实现的。

Integer缓存池

Integer缓存池也就是IntegerCache,它是Integer的静态内部类。

它的默认值用于缓存-128-127之间的数字,如果有-128-127之间的数字的话,使用newInteger不用创建对象,会直接从缓存池中取,此操作会减少堆中对象的分配,有利于提高程序的运行效率。

例如创建一个Integera=24,其实是调用Integer的valueOf,可以通过反编译得出这个结论

然后我们看一下valueOf方法

如果在指定缓存池范围内的话,会直接返回缓存的值而不用创建新的Integer对象。

缓存的大小可以使用XX:AutoBoxCacheMax来指定,在VM初始化时,java.lang.Integer.IntegerCache.high属性会设置和保存在sun.misc.VM的私有系统属性中。

UTF-8和Unicode的关系

由于每个国家都有自己独有的字符编码,所以Unicode的发展旨在创建一个新的标准,用来映射当今使用的大多数语言中的字符,这些字符有一些不是必要的,但是对于创建文本来说却是不可或缺的。Unicode统一了所有字符的编码,是一个CharacterSet,也就是字符集,字符集只是给所有的字符一个唯一编号,但是却没有规定如何存储,不同的字符其存储空间不一样,有的需要一个字节就能存储,有的则需要2、3、4个字节。

UTF-8只是众多能够对文本字符进行解码的一种方式,它是一种变长的方式。UTF-8代表8位一组表示Unicode字符的格式,使用1-4个字节来表示字符。

U+0000~U+007F:0XXXXXXX\nU+0080~U+07FF:110XXXXX10XXXXXX\nU+0800~U+FFFF:1110XXXX10XXXXXX10XXXXXX\nU+10000~U+1FFFF:11110XXX10XXXXXX10XXXXXX10XXXXXX\n

可以看到,UTF-8通过开头的标志位位数实现了变长。对于单字节字符,只占用一个字节,实现了向下兼容ASCII,并且能和UTF-32一样,包含Unicode中的所有字符,又能有效减少存储传输过程中占用的空间。

项目为UTF-8环境,charc=&39;,是否合法

可以,因为Unicode编码采用2个字节的编码,UTF-8是Unicode的一种实现,它使用可变长度的字符集进行编码,charc=&39;是两个字节,所以能够存储。合法。

Arrays.asList获得的List应该注意什么

Arrays.asList是Array中的一个静态方法,它能够实现把数组转换成为List序列,需要注意下面几点

Arrays.asList转换完成后的List不能再进行结构化的修改,什么是结构化的修改?就是不能再进行任何List元素的增加或者减少的操作。

publicstaticvoidmain(String[]args){\nInteger[]integer=newInteger[]{1,2,3,4};\nListintegetList=Arrays.asList(integer);\nintegetList.add(5);\n}\n

结果会直接抛出

Exceptioninthread&34;java.lang.UnsupportedOperationException\n

我们看一下源码就能发现问题

//这是java.util.Arrays的内部类,而不是java.util.ArrayList\nprivatestaticclassArrayList<E>extendsAbstractList<E>\nimplementsRandomAccess,java.io.Serializable\n

继承AbstractList中对add、remove、set方法是直接抛异常的,也就是说如果继承的子类没有去重写这些方法,那么子类的实例去调用这些方法是会直接抛异常的。

下面是AbstractList中方法的定义,我们可以看到具体抛出的异常:

publicvoidadd(intindex,Eelement){\nthrownewUnsupportedOperationException();\n}\npublicEremove(intindex){\nthrownewUnsupportedOperationException();\n}\npublicEset(intindex,Eelement){\nthrownewUnsupportedOperationException();\n}\n

虽然set方法也抛出了一场,但是由于内部类ArrayList重写了set方法,所以支持其可以对元素进行修改。

Arrays.asList不支持基础类型的转换

Java中的基础数据类型(byte,short,int,long,float,double,boolean)是不支持使用Arrays.asList方法去转换的

Collection和Collections的区别

Collection和Collections都是位于java.util包下的类

Collection是集合类的父类,它是一个顶级接口,大部分抽象类比如说AbstractList、AbstractSet都继承了Collection类,Collection类只定义一节标准方法比如说add、remove、set、equals等,具体的方法由抽象类或者实现类去实现。

Collections是集合类的工具类,Collections提供了一些工具类的基本使用

sort方法,对当前集合进行排序,实现Comparable接口的类,只能使用一种排序方案,这种方案叫做自然比较比如实现线程安全的容器Collections.synchronizedList、Collections.synchronizedMap等reverse反转,使用reverse方法可以根据元素的自然顺序对指定列表按降序进行排序。fill,使用指定元素替换指定列表中的所有元素。

有很多用法,读者可以翻阅Collections的源码查看,Collections不能进行实例化,所以Collections中的方法都是由Collections.方法直接调用。

你知道fail-fast和fail-safe吗

fail-fast是Java中的一种快速失败机制,java.util包下所有的集合都是快速失败的,快速失败会抛出ConcurrentModificationException异常,fail-fast你可以把它理解为一种快速检测机制,它只能用来检测错误,不会对错误进行恢复,fail-fast不一定只在多线程环境下存在,ArrayList也会抛出这个异常,主要原因是由于modCount不等于expectedModCount

fail-safe是Java中的一种安全失败机制,它表示的是在遍历时不是直接在原集合上进行访问,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。java.util.concurrent包下的容器都是安全失败的,可以在多线程条件下使用,并发修改。

ArrayList、LinkedList和Vector的区别

这也是一道老生常谈的问题了

ArrayList、LinkedList、Vector都是位于java.util包下的工具类,它们都实现了List接口。

ArrayList的底层是动态数组,它是基于数组的特性而演变出来的,所以ArrayList遍历访问非常快,但是增删比较慢,因为会涉及到数组的拷贝。ArrayList是一个非线程安全的容器,在并发场景下会造成问题,如果想使用线程安全的容器的话,推荐使用Collections.synchronizedList;ArrayList在扩容时会增加50%的容量。LinkedList的底层是双向链表,所以LinkedList的增加和删除非常快,只需把元素删除,把各自的指针指向新的元素即可。但是LinkedList遍历比较慢,因为只有每次访问一个元素才能知道下一个元素的值。LinkedList也是一个非线程安全的容器,推荐使用Collections.synchronizedListVector向量是最早出现的集合容器,Vector是一个线程安全的容器,它的每个方法都粗暴的加上了synchronized锁,所以它的增删、遍历效率都很低。Vector在扩容时,它的容量会增加一倍。

Exception和Error有什么区别

Exception泛指的是异常,Exception主要分为两种异常,一种是编译期出现的异常,称为checkedException,一种是程序运行期间出现的异常,称为uncheckedException,常见的checkedException有IOException,uncheckedException统称为RuntimeException,常见的RuntimeException主要有NullPointerException、IllegalArgumentException、ArrayIndexOutofBoundException等,Exception可以被捕获。

Error是指程序运行过程中出现的错误,通常情况下会造成程序的崩溃,Error通常是不可恢复的,Error不能被捕获。

详细可以参考这篇文章看完这篇Exception和Error,和面试官扯皮就没问题了

String、StringBuilder和StringBuffer有什么区别

String特指的是Java中的字符串,String类位于java.lang包下,String类是由final修饰的,String字符串一旦创建就不能被修改,任何对String进行修改的操作都相当于重新创建了一个字符串。String字符串的底层使用StringBuilder来实现的

StringBuilder位于java.util包下,StringBuilder是一非线程安全的容器,StringBuilder的append方法常用于字符串拼接,它的拼接效率要比String中+号的拼接效率高。StringBuilder一般不用于并发环境

StringBuffer位于java.util包下,StringBuffer是一个线程安全的容器,多线程场景下一般使用StringBuffer用作字符串的拼接

StringBuilder和StringBuffer都是继承于AbstractStringBuilder类,AbstractStringBuilder类实现了StringBuffer和StringBuilder的常规操作。

动态代理是基于什么原理

代理一般分为静态代理和动态代理,它们都是代理模式的一种应用,静态代理指的是在程序运行前已经编译好,程序知道由谁来执行代理方法。

而动态代理只有在程序运行期间才能确定,相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。可以说动态代理是基于反射实现的。通过反射我们可以直接操作类或者对象,比如获取类的定义,获取声明的属性和方法,调用方法,在运行时可以修改类的定义。

动态代理是一种在运行时构建代理、动态处理方法调用的机制。动态代理的实现方式有很多,Java提供的代理被称为JDK动态代理,JDK动态代理是基于类的继承。

int和Integer的区别

int和Integer区别可就太多了

int是Java中的基本数据类型,int代表的是整型,一个int占4字节,也就是32位,int的初始值是默认值是0,int在Java内存模型中被分配在栈中,int没有方法。Integer是Java中的基本数据类型的包装类,Integer是一个对象,Integer可以进行方法调用,Integer的默认值是null,Integer在Java内存模型中被分配在堆中。int和Integer在计算时可以进行相互转换,int->Integer的过程称为装箱,Integer->int的过程称为拆箱,Integer还有IntegerCache,会自动缓存-128-127中的值

Java提供了哪些I/O方式

JavaI/O方式有很多种,传统的I/O也称为BIO,主要流有如下几种

JavaI/O包的实现比较简单,但是容易出现性能瓶颈,传统的I/O是基于同步阻塞的。

JDK1.4之后提供了NIO,也就是位于java.nio包下,提供了基于channel、Selector、Buffer的抽象,可以构建多路复用、同步非阻塞I/O程序。

JDK1.7之后对NIO进行了进一步改进,引入了异步非阻塞的方式,也被称为AIO(AsynchronousIO)。可以用生活中的例子来说明:项目经理交给手下员工去改一个bug,那么项目经理不会一直等待员工解决bug,他肯定在员工解决bug的期间给其他手下分配bug或者做其他事情,员工解决完bug之后再告诉项目经理bug解决完了。

谈谈你知道的设计模式

一张思维导图镇场

比如全局唯一性可以用单例模式。

可以使用策略模式优化过多的if…else…

制定标准用模版模式

接手其他人的锅,但不想改原来的类用适配器模式

使用组合而不是继承

使用装饰器可以制作加糖、加奶酪的咖啡

代理可以用于任何中间商……

Comparator和Comparable有什么不同

Comparable更像是自然排序Comparator更像是定制排序

同时存在时采用Comparator(定制排序)的规则进行比较。

对于一些普通的数据类型(比如String,Integer,Double…),它们默认实现了Comparable接口,实现了compareTo方法,我们可以直接使用。

而对于一些自定义类,它们可能在不同情况下需要实现不同的比较策略,我们可以新创建Comparator接口,然后使用特定的Comparator实现进行比较。

Object类中一般都有哪些方法

Object类是所有对象的父类,它里面包含一些所有对象都能够使用的方法

hashCode():用于计算对象的哈希码equals():用于对象之间比较值是否相等toString():用于把对象转换成为字符串clone():用于对象之间的拷贝wait():用于实现对象之间的等待notify():用于通知对象释放资源notifyAll():用于通知所有对象释放资源finalize():用于告知垃圾回收器进行垃圾回收getClass():用于获得对象类

Java泛型和类型擦除

关于Java泛型和擦除看着一篇就够了。

反射的基本原理,反射创建类实例的三种方式是什么

反射机制就是使Java程序在运行时具有自省(introspect)的能力,通过反射我们可以直接操作类和对象,比如获取某个类的定义,获取类的属性和方法,构造方法等。

创建类实例的三种方式是

对象实例.getClass();通过Class.forName()创建对象实例.newInstance()方法创建

强引用、若引用、虚引用和幻象引用的区别

我们说的不同的引用类型其实都是逻辑上的,而对于虚拟机来说,主要体现的是对象的不同的可达性(reachable)状态和对垃圾收集(garbagecollector)的影响。

可以通过下面的流程来对对象的生命周期做一个总结

对象被创建并初始化,对象在运行时被使用,然后离开对象的作用域,对象会变成不可达并会被垃圾收集器回收。图中用红色标明的区域表示对象处于强可达阶段。

JDK1.2介绍了java.lang.ref包,对象的生命周期有四个阶段:强可达(StronglyReachable)、软可达(SoftReachable)、弱可达(WeakReachable)、幻象可达(PhantomReachable)。

如果只讨论符合垃圾回收条件的对象,那么只有三种:软可达、弱可达和幻象可达。

软可达:软可达就是我们只能通过软引用才能访问的状态,软可达的对象是由SoftReference引用的对象,并且没有强引用的对象。软引用是用来描述一些还有用但是非必须的对象。垃圾收集器会尽可能长时间的保留软引用的对象,但是会在发生OutOfMemoryError之前,回收软引用的对象。如果回收完软引用的对象,内存还是不够分配的话,就会直接抛出OutOfMemoryError。弱可达:弱可达的对象是WeakReference引用的对象。垃圾收集器可以随时收集弱引用的对象,不会尝试保留软引用的对象。幻象可达:幻象可达是由PhantomReference引用的对象,幻象可达就是没有强、软、弱引用进行关联,并且已经被finalize过了,只有幻象引用指向这个对象的时候。

除此之外,还有强可达和不可达的两种可达性判断条件

强可达:就是一个对象刚被创建、初始化、使用中的对象都是处于强可达的状态不可达(unreachable):处于不可达的对象就意味着对象可以被清除了。

下面是一个不同可达性状态的转换图

判断可达性条件,也是JVM垃圾收集器决定如何处理对象的一部分考虑因素。

所有的对象可达性引用都是java.lang.ref.Reference的子类,它里面有一个get()方法,返回引用对象。如果已通过程序或垃圾收集器清除了此引用对象,则此方法返回null。也就是说,除了幻象引用外,软引用和弱引用都是可以得到对象的。而且这些对象可以人为拯救,变为强引用,例如把this关键字赋值给对象,只要重新和引用链上的任意一个对象建立关联即可。

final、finally和finalize()的区别

这三者可以说是没有任何关联之处,我们上面谈到了,final可以用来修饰类、变量和方法,可以参考上面final的那道面试题。

finally是一个关键字,它经常和try块一起使用,用于异常处理。使用try…finally的代码块种,finally部分的代码一定会被执行,所以我们经常在finally方法中用于资源的关闭操作。

JDK1.7中,推荐使用try-with-resources优雅的关闭资源,它直接使用try(){}进行资源的关闭即可,就不用写finally关键字了。

finalize是Object对象中的一个方法,用于对象的回收方法,这个方法我们一般不推荐使用,finalize是和垃圾回收关联在一起的,在Java9中,将finalize标记为了deprecated,如果没有特别原因,不要实现finalize方法,也不要指望他来进行垃圾回收。

内部类有哪些分类,分别解释一下

在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

内部类的分类一般主要有四种

成员内部类局部内部类匿名内部类静态内部类

静态内部类就是定义在类内部的静态类,静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;

成员内部类就是定义在类内部,成员位置上的非静态类,就是成员内部类。成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。

定义在方法中的内部类,就是局部内部类。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。

匿名内部类就是没有名字的内部类,除了没有名字,匿名内部类还有以下特点:

匿名内部类必须继承一个抽象类或者实现一个接口匿名内部类不能定义任何静态成员和静态方法。当所在的方法的形参需要被匿名内部类使用时,必须声明为final。匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

说出几种常用的异常

NullPointerException:空指针异常NoSuchMethodException:找不到方法IllegalArgumentException:不合法的参数异常IndexOutOfBoundException:数组下标越界异常IOException:由于文件未找到、未打开或者I/O操作不能进行而引起异常ClassNotFoundException:找不到文件所抛出的异常NumberFormatException:字符的UTF代码数据格式有错引起异常;InterruptedException:线程中断抛出的异常

静态绑定和动态绑定的区别

一个Java程序要经过编写、编译、运行三个步骤,其中编写代码不在我们讨论的范围之内,那么我们的重点自然就放在了编译和运行这两个阶段,由于编译和运行阶段过程相当繁琐,下面就我的理解来进行解释:

Java程序从源文件创建到程序运行要经过两大步骤:

1、编译时期是由编译器将源文件编译成字节码的过程

2、字节码文件由Java虚拟机解释执行

绑定

绑定就是一个方法的调用与调用这个方法的类连接在一起的过程被称为绑定。

绑定主要分为两种:

静态绑定和动态绑定

绑定的其他叫法

静态绑定==前期绑定==编译时绑定

动态绑定==后期绑定==运行时绑定

为了方便区分:下面统一称呼为静态绑定和动态绑定

静态绑定

在程序运行前,也就是编译时期JVM就能够确定方法由谁调用,这种机制称为静态绑定

识别静态绑定的三个关键字以及各自的理解

如果一个方法由private、static、final任意一个关键字所修饰,那么这个方法是前期绑定的

构造方法也是前期绑定

private:private关键字是私有的意思,如果被private修饰的方法是无法由本类之外的其他类所调用的,也就是本类所特有的方法,所以也就由编译器识别此方法是属于哪个类的

publicclassPerson{\n\nprivateStringtalk;\n\nprivateStringcanTalk(){\nreturntalk;\n}\n}\n\nclassAnimal{\n\npublicstaticvoidmain(String[]args){\nPersonp=newPerson();\n//private修饰的方法是Person类独有的,所以Animal类无法访问(动物本来就不能说话)\n//p.canTalk();\n}\n}\n

final:final修饰的方法不能被重写,但是可以由子类进行调用,如果将方法声明为final可以有效的关闭动态绑定

publicclassFruit{\n\nprivateStringfruitName;\n\nfinalStringeatingFruit(Stringname){\nSystem.out.println(&34;+name);\nreturnfruitName;\n}\n}\n\nclassAppleextendsFruit{\n\n//不能重写final方法,eatingFruit方法只属于Fruit类,Apple类无法调用\n//StringeatingFruit(Stringname){\n//super.eatingFruit(name);\n//}\n\nStringeatingApple(Stringname){\nreturnsuper.eatingFruit(name);\n}\n}\n

static:static修饰的方法比较特殊,不用通过new出某个类来调用,由类名.变量名直接调用该方法,这个就很关键了,new很关键,也可以认为是开启多态的导火索,而由类名.变量名直接调用的话,此时的类名是确定的,并不会产生多态,如下代码:

publicclassSuperClass{\n\npublicstaticvoidsayHello(){\n\nSystem.out.println(&34;);\n}\n}\n\npublicclassSubClassextendsSuperClass{\n\npublicstaticvoidsayHello(){\nSystem.out.println(&34;);\n}\n\npublicstaticvoidmain(String[]args){\nSuperClass.sayHello();\nSubClass.sayHello();\n}\n}\n

SubClass继承SuperClass后,在

是无法重写sayHello方法的,也就是说sayHello()方法是对子类隐藏的,但是你可以编写自己的sayHello()方法,也就是子类SubClass的sayHello()方法,由此可见,方法由static关键词所修饰,也是编译时绑定

动态绑定

在运行时根据具体对象的类型进行绑定

除了由private、final、static所修饰的方法和构造方法外,JVM在运行期间决定方法由哪个对象调用的过程称为动态绑定

如果把编译、运行看成一条时间线的话,在运行前必须要进行程序的编译过程,那么在编译期进行的绑定是前期绑定,在程序运行了,发生的绑定就是后期绑定

publicclassFather{\n\nvoiddrinkMilk(){\nSystem.out.println(&34;);\n}\n}\n\npublicclassSonextendsFather{\n\n@Override\nvoiddrinkMilk(){\nSystem.out.println(&34;);\n}\n\npublicstaticvoidmain(String[]args){\nFatherson=newSon();\nson.drinkMilk();\n}\n}\n

Son类继承Father类,并重写了父类的dringMilk()方法,在输出结果得出的是儿子喜欢喝牛奶。那么上面的绑定方式是什么呢?

上面的绑定方式称之为动态绑定,因为在你编写Fatherson=newSon()的时候,编译器并不知道son对象真正引用的是谁,在程序运行时期才知道,这个son是一个Father类的对象,但是却指向了Son的引用,这种概念称之为多态,那么我们就能够整理出来多态的三个原则:

继承重写父类引用指向子类对象

也就是说,在Fatherson=newSon(),触发了动态绑定机制。

动态绑定的过程

虚拟机提取对象的实际类型的方法表;虚拟机搜索方法签名;调用方法。

动态绑定和静态绑定的特点

静态绑定

静态绑定在编译时期触发,那么它的主要特点是

1、编译期触发,能够提早知道代码错误

2、提高程序运行效率

动态绑定

1、使用动态绑定的前提条件能够提高代码的可用性,使代码更加灵活。

2、多态是设计模式的基础,能够降低耦合性。

好了,文章到这里就结束啦,如果本次分享的u007f商城网站源码分享和问题对您有所帮助,还望关注下本站哦!

Published by

风君子

独自遨游何稽首 揭天掀地慰生平