链接:https://github.com/Snailclimb/JavaGuide
1、char和string的区别
字符型常量和字符串常量的区别?
- 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的0个或若干个字符
- 之前一直没注意char类型是不能为''的
- 之前一直没注意char类型是不能为''的
- 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
- 占内存大小 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意: char 在 Java 中占两个字节),
自增自减运算符,面试遇到过这种题,还愣了一下
泛型
链接:https://github.com/Snailclimb/JavaGuide
链接:https://juejin.im/post/6844903917835419661
重中之重
Java泛型了解么?什么是类型擦除?介绍一下常用的通配符?
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型好处
在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。
Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。 更多关于类型擦除的问题,可以查看这篇文章https://www.cnblogs.com/wuqinglong/p/9456193.html
泛型的一个通俗易懂的概念:看的黑马程序员视频
泛型类
在不指定泛型类型时,使用Object类型,默认使用所有类型的父类,什么都能存。
package string.test;
class MyGerneric <E>{
E name;
public MyGerneric(E name) {
this.name = name;
}
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
@Override
public String toString() {
return "MyGerneric{" +
"name=" + name +
'}';
}
}
//1.泛型类:
public class GernericTest{
public static void main(String[] args) {
MyGerneric myGerneric = new MyGerneric(new Object());
System.out.println(myGerneric.toString());
}
}
泛型方法:
1、普通类+泛型方法
class MyGerneric {
public <T> void printMyGerneric(T t){
System.out.println(t);
}
}
//2.泛型方法:
public class GernericTest{
public static void main(String[] args) {
MyGerneric myGerneric = new MyGerneric();
myGerneric.printMyGerneric(1);
myGerneric.printMyGerneric("a");
myGerneric.printMyGerneric('c');
myGerneric.printMyGerneric(new Object());
}
}
2、泛型类+泛型方法(泛型方法中声明了该方法使用的泛型,那么类声明的那个泛型T和方法声明的泛型T可以不同)
应用时,泛型类声明的T可以和泛型方法声明的T不一样
class MyGerneric <T>{
public <T> void printMyGerneric(T t){
System.out.println(t);
}
}
//2.泛型方法:
public class GernericTest{
public static void main(String[] args) {
MyGerneric<String> myGerneric = new MyGerneric<>();
myGerneric.printMyGerneric(1);
myGerneric.printMyGerneric("a");
myGerneric.printMyGerneric('c');
myGerneric.printMyGerneric(new Object());
}
}
3、泛型类+泛型方法(泛型方法中没有声明使用的泛型)
4、普通类+静态泛型方法
class MyTest2{
static <T> void print(T t){
System.out.println(t);
}
}
public class Test2 {
public static void main(String[] args) {
MyTest2.print("a");
MyTest2.print(1);
}
}
顺便在这里写一下前几天的一个静态变量的选择题,突然想起来的
class MyTest2{
static int a=10;
}
public class Test2 {
public static void main(String[] args) {
MyTest2 myTest2 = new MyTest2();
myTest2.a++;
MyTest2.a++;
System.out.println(MyTest2.a);//打印结果是12
}
}
泛型接口:
含有泛型的接口,使用方式:2种
1、定义接口的实现类,实现类中指定接口的泛型
2、定义接口的实现类,实现类中不指定接口的泛型,创建实现类的对象时,指定类型
package string.test;
interface MyGerneric <T>{
//含有泛型的接口,使用方式:
// 1、定义接口的实现类,指定接口的泛型
// 2、定义接口的实现类,不指定接口的泛型,创建实现类的对象时,指定类型
//顺便再复习一下,泛型方法中声明<T>与不声明的区别
<T> void printMyGerneric(T t);
void printMyGerneric2(T t);
}
//第一种实现类,类中就定义了数据类型
class GernericTestImpl implements MyGerneric<String>{
@Override
public <T> void printMyGerneric(T t) {
System.out.println(t);
}
@Override
public void printMyGerneric2(String s) {
System.out.println(s);
}
}
class GernericTestImpl2<T> implements MyGerneric<T>{
@Override
public <T> void printMyGerneric(T t) {
System.out.println(t);
}
@Override
public void printMyGerneric2(T t) {
System.out.println(t);
}
}
public class MyGernericTest {
public static void main(String[] args) {
GernericTestImpl gernericTestImpl = new GernericTestImpl();
GernericTestImpl2<String> gernericTestImpl2 = new GernericTestImpl2<>();
gernericTestImpl.printMyGerneric(new Object());
gernericTestImpl.printMyGerneric("a");
gernericTestImpl2.printMyGerneric(new Object());
gernericTestImpl2.printMyGerneric("a");
gernericTestImpl2.printMyGerneric2("a");
//gernericTestImpl2.printMyGerneric2(new Object());//编译不通过,只能是String类型的参数
}
}
通配符:
我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V 等等,这些通配符又都是什么意思呢?
本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样约定的:
- ? 表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
?通配符
<?>:这个通配符不需要声明,可以直接使用,可以代表任意的数据类型。 当返回的类型未知时,可以使用这个通配符。只能接受数据,不能往集合中存储数据。
这几种试图存数据的都是非法的
这种合法得写法:不写<>,但是他的意思是使用默认的类型Object,也相当于指定了类型的。
?的使用
public class Main{
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
list1.add(1);
list1.add(2);
list2.add("a");
list2.add("b");
printList(list1);
printList(list2);
}
private static void printList(ArrayList<?> list) {
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
}
泛型是没有继承的概念的,不能试图使用Object类接受对象,但是可以使用上限下限的限定。
泛型的受限:上限限定和下限限定(面试中好像不常问)
使用:
泛型上限,用? extends E,使用的泛型必须是E的子类或本身 泛型下限,用? super E,使用的泛型必须是E的父类或本身
package string.test;
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
import java.util.ArrayList;
import java.util.Iterator;
//1.泛型类:
public class Main{
public static void main(String[] args) {
//泛型上限,用? extends E,使用的泛型必须是E的子类或本身
//泛型下限,用? super E,使用的泛型必须是E的父类或本身
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
ArrayList<Number> list3 = new ArrayList<>();
ArrayList<Object> list4 = new ArrayList<>();
printList1(list1);//正确 list1是Integer类型,printList1泛型的上限为Number类型
// printList1(list2);//编译不通过,list2是String类型,printList1泛型的上限为Number类型
printList1(list3);//正确 list3是Number类型,printList1泛型的上限为Number类型
// printList1(list4);//编译不通过 list4是Object类型,printList1泛型的上限为Number类型
// printList2(list1);//编译不通过, list1是Integer类型,printList泛型的下限为String
printList2(list2);//正确, list2是String类型,printList泛型的下限为String
// printList2(list3);//编译不通过, list3是Number类型,printList泛型的下限为String
printList2(list4);//正确, list4是Object类型,printList泛型的下限为String
}
private static void printList1(ArrayList<? extends Number> list) {
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
private static void printList2(ArrayList<? super String> list) {
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
}
? 和 T 的区别
答:
区别1:通过 T 来 确保 泛型参数的一致性。
区别2:T可以多重限定而?不行。
区别3:?可以使用超类限定而T不行。
详细说明
1、?是不确定的类型,T是确定的类型。
T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
个人理解:(可能有错)
下面这个例子中,使用泛型T时候,可以保证list1集合中的数据类型是一致的都是T,而?不能确定唯一的数据类型,可以联想一下?extends T这种用法,?可以是多种类型。
2、
使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子类型,此时变量 t 就具有了所有限定的方法和属性。对于?通配符来说,因为它不是一个确定的类型,所以不能进行多重限定。
3、
类型参数 T 只具有 一种 类型限定方式:
T extends A
复制代码
但是通配符 ? 可以进行 两种限定:
? extends A
? super A
class<T>和class<?>的区别
最常见的是在反射场景下的使用,这里以用一段发射的代码来说明下。
// 通过反射的方式生成 multiLimit
// 对象,这里比较明显的是,我们需要使用强制类型转换
MultiLimit multiLimit = (MultiLimit)Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance();
对于上述代码,在运行期,如果反射的类型不是 MultiLimit 类,那么一定会报 java.lang.ClassCastException 错误。
对于这种情况,则可以使用下面的代码来代替,使得在在编译期就能直接 检查到类型的问题:
Class<T>
在实例化的时候,T 要替换成具体类。Class<?>
它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。比如,我们可以这样做声明:
// 可以
public Class<?> clazz;
// 不可以,因为 T 需要指定类型
public Class<T> clazzT;
所以当不知道定声明什么类型的 Class 的时候可以定义一 个Class<?>。
那如果也想 public Class<T> clazzT;
这样的话,就必须让当前的类也指定 T ,
public class Test3<T> {
public Class<?> clazz;
// 不会报错
public Class<T> clazzT;
==与equals
==:基本数据类型==比较的是值,引用数据类型==比较的是内存地址
equals():
它的作用也是判断两个对象是否相等,它不能用于比较基本数据类型的变量。equals()
方法存在于Object
类中,而Object
类是所有类的直接或间接父类。Object的equals方法:
public boolean equals(Object obj) {
return (this == obj);
}
equals使用时,要看一下该类有没有重写这个方法。
hashCode()与 equals()
面试官可能会问你:“你重写过 hashcode
和 equals
么,为什么重写 equals
时必须重写 hashCode
方法?”
1)hashCode()介绍:
hashCode()
的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()
定义在 JDK 的 Object
类中,这就意味着 Java 中的任何类都包含有 hashCode()
函数。另外需要注意的是: Object
的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
public native int hashCode();
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
我们以“HashSet” 如何检查重复”为例子来说明为什么要有 hashCode 计算hashCode比equals执行快,然后先计算出hashCode后,与集合中的每个元素来比较,没有重复的,就说明元素不在集合中,有重复的,再equals是否相等,来判断,减少equals的次数,提升效率。