Java泛型 泛型的本质是参数化类型,既所操作的数据类型被指定为一个参数。
Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { List<Integer> list = new ArrayList<>(); list.add(12 ); Class<? extends List> clazz = list.getClass(); Method add = clazz.getDeclaredMethod("add" , Object.class); add.invoke(list, "kl" ); for (Object obj : list) { System.out.println(obj + " " + obj.getClass().toString()); } System.out.println(list); }
输出:
1 2 3 12 class java .lang .Integer kl class java .lang .String [12, kl ]
泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
常用通配符: T,E,K,V,?
? 表示不确定的 java 类型
T (type) 表示具体的一个 java 类型
K V (key value) 分别代表 java 键值中的 Key Value
E (element) 代表 Element
泛型类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Generic <T > { private T key; public Generic () { } public Generic (T key) { this .key=key; } public void setKey (T key) { this .key = key; } public T getKey () {return this .key;} public static void main (String[] args) { Generic<Integer> generic = new Generic<>(); generic.setKey(1234 ); System.out.println(generic.getKey()+" " +generic.getKey().getClass().getName()); } }
泛型接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 interface generInter <T > { public T method () ; } class GenerInterfaceImpl2 <T > implements generInter <T > { private T key; public void setKey (T key) { this .key = key; } @Override public T method () { return this .key; } } public class GenerInterfaceImpl implements generInter <String > { @Override public String method () { return "Test 泛型接口" ; } public static void main (String[] args) { GenerInterfaceImpl generInterface = new GenerInterfaceImpl(); GenerInterfaceImpl2<String> generInterfaceImpl2 = new GenerInterfaceImpl2(); generInterfaceImpl2.setKey("1234" ); System.out.println(generInterface.method()); System.out.println(generInterfaceImpl2.method()); } }
输出:
泛型方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class GenerMethod { public static <E> void printArray (List<E> inputArray) { for (E element: inputArray){ System.out.printf("%s " ,element); } System.out.println(); } public static void main (String[] args) { List<String> list = new ArrayList<>(); list.add("123" ); list.add("456" ); list.add("789" ); list.add(0 ,"777" ); printArray(list); } }
输出
equals方法 equals()
作用不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()
方法存在于Object
类中,而Object
类是所有类的直接或间接父类。
Object
类 equals()
方法:
1 2 3 public boolean equals (Object obj) { return (this == obj); }
equals()
方法存在两种使用情况:
类没有覆盖equals()
方法:通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object
类equals()
方法。
类覆盖了equals()
方法:一般我们都覆盖 equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
String 中的equals
方法:
String
中的 equals
方法是被重写过的,因为 Object
的 equals
方法是比较的对象的内存地址,而 String
的 equals
方法比较的是对象的值。
当创建 String
类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String
对象。
hashCode与equals(): hashCode()
的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode()
定义在 JDK 的 Object
类中,这就意味着 Java 中的任何类都包含有 hashCode()
函数。
为什么要有hashCode 我们以“HashSet
如何检查重复”为例子来说明为什么要有 hashCode?
HashSet
先计算对象的hashCode
值来判断对象加入的位置,同时也与其他已经加入的对象进行比较。
如果没有重复的hashCode
则可以加入,如果有重复的hashCode
则再去调用equals()
方法去检查hashcode相等的对象内容是否相同。
如果相同则不让其加入,如果不同则,重新散列其位置。
为什么重写equals
时必须重写hashCode
方法 如果两个对象相等,则 hashcode 一定也是相同的。
两个对象相等,对两个对象分别调用 equals 方法都返回 true。
但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。
因此,equals 方法被覆盖过,则 hashCode
方法也必须被覆盖。
hashCode()
的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode()
,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
为什么两个对象有相同的 hashcode 值,它们也不一定是相等的? 因为 hashCode()
所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。
越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode
。
我们刚刚也提到了 HashSet
,如果 HashSet
在对比的时候,同样的 hashcode 有多个对象,它会使用 equals()
来判断是否真的相同。也就是说 hashcode
只是用来缩小查找成本。
静态方法和实例方法有何不同? 1. 调用方式 在外部调用静态方法时,可以使用 类名.方法名
的方式,也可以使用 对象.方法名
的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象 。
一般建议使用 类名.方法名
的方式来调用静态方法。
2. 访问类成员限制 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法) ,而实例方法不存在这个限制。
重载和重写 重载 重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理;
重写 重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法。
返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
如果父类方法访问修饰符为 private/final/static
则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
构造方法无法被重写
成员变量和局部变量有哪些区别
从语法形式上看:
成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;
成员变量可以被 public
,private
,static
等修饰符所修饰,而局部变量不能被访问控制修饰符及 static
所修饰 ;
成员变量和局部变量都能被 final
所修饰
从变量在内存中的存储形式看:
成员变量是使用 static
修饰的,那么这个成员变量是属于类的,如果没有使用 static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
从变量在内存中的生存时间上看:
成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
从变量是否有默认值来看
成员变量如果没有被赋初值,则会自动以类型的默认值而 赋值(一种情况例外:被 final
修饰的成员变量也必须显式地赋值)
局部变量则不会自动赋值。
继承与多态 继承
子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有 。
子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
多态 多态,顾名思义,表示一个对象具有多种的状态。具体表现为父类的引用指向子类的实例 。
特点:
对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
多态不能调用“只在子类存在但在父类不存在”的方法;
如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
依赖注入 定义数据提供接口与实现类 1 2 3 4 5 6 7 package DI.dao;public interface UserDao { void getUser () ; }
1 2 3 4 5 6 public class UserDaoImpl implements UserDao { @Override public void getUser () { System.out.println("默认获取用户的数据" ); } }
1 2 3 4 5 6 public class UserDaoMySqlImpl implements UserDao { @Override public void getUser () { System.out.println("获取MYSQL用户数据" ); } }
定义数据使用抽象类与实现类 1 2 3 4 5 6 7 public abstract class UserService { abstract void getUser () ; public static void main (String[] args) { System.out.println("--" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class UserServiceImpl extends UserService { private UserDao userDao; public void setUserDao (UserDao userDao) { this .userDao = userDao; } @Override public void getUser () { userDao.getUser(); } }
定义测试函数 1 2 3 4 5 6 7 public class Test { public static void main (String[] args) { UserServiceImpl userService = new UserServiceImpl(); userService.setUserDao(new UserDaoMySqlImpl()); userService.getUser(); } }
接口与抽象类 抽象类 在实现多态时,如果一个父类的方法本身不需要实现任何功能,仅仅为了定义方法签名,目的是让子类覆盖他,那么,可以把父类定义为抽象方法。
把一个方法声明为抽象方法,导致了其本身无法执行,所以含有抽象方法的类无法被实例化,那么也必须把这个类声明为abstract
,才能正确编译他。
抽象类有构造方法,是给子类创建对象的
抽象类中不一定有抽象方法,抽象方法一定在抽象类中
abstract关键字不可以与哪些关键字使用:
private冲突:private修饰的成员不能被继承,从而不可以被子类重写,而abstract修饰的是要求被重写的
final冲突:final修饰的成员是最终成员,不能被重写,所以冲突
static冲突:static修饰成员用类名可以直接访问,但是抽象方法没有方法体,故访问没有方法体的成员没有意义.
接口
接口不能被实例化
接口只能包含方法的声明
接口的成员方法包括:方法,属性,索引器,事件
接口中不能包含常量,字段,构造函数,静态成员
两者的区别
抽象类可以有构造方法,接口中不能有构造方法
抽象类中可以有普通成员变量,接口中没有普通成员变量
抽象类中可以包含静态方法,接口中不能包含静态方法
一个类可以实现多个接口,但只能继承一个抽象类
接口可以被多重实现,抽象类只能被单一继承
如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法
相同点:
都可以被继承
都不能被实例化
都可以包含方法声明
派生类必须实现未声明的方法
总结:
接口带来的最大好处就是避免了多继承带来的复杂性和低效性,并且同时可以提供多重继承的好处
接口和抽象类都可以提现多态性,但是抽象类对事物进行抽象,更多的是为了继承,为了扩展,为了实现代码的重用,子类和父类之间提现的是is-a关系,接口则更多的体现一种行为约束,一种规则,一旦实现了这个接口,就要给出这个接口中所以方法的具体实现,也就是实现类对于接口中所有的方法都是有意义是的
多重继承的实现:https://blog.csdn.net/qq_36599564/article/details/102579688
StringBuffer与StringBuilder 可变性
简单的来说:String
类中使用 final 关键字修饰字符数组来保存字符串,private final char value[]
,所以String
对象是不可变的。
而 StringBuilder
与 StringBuffer
都继承自 AbstractStringBuilder
类,在 AbstractStringBuilder
中也是使用字符数组保存字符串char[]value
但是没有用 final
关键字修饰,所以这两种对象都是可变的。
线程安全性 String
的对象是final,线程安全;
StringBuffer
中的方法加了同步锁,是线程安全的;
StringBuilder
没有对方法加同步锁,故不是安全的。
总结
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
字符串拼接,String
使用+
或者concat
,StringBuilder
和StringBuffer
使用append
。
异常
所有的异常都继承于Throwable
。
其两个子类:
Exceptions:能被程序本身处理(try-catch
)
Error:无法处理
Virtual Machine Error:java虚拟机错误
OutOfMemoryError:虚拟机内存不足
NoClassDefFoundError:类定义错误
受查异常(必须处理) :
除了RuntimeException
及其子类以外,其他的Exception
类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundException
、SQLException
…。
不受查异常(可以不处理)
Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException
及其子类都统称为非受检查异常,例如:NullPointerException
、NumberFormatException
(字符串转换为数字)、ArrayIndexOutOfBoundsException
(数组越界)、ClassCastException
(类型转换错误)、ArithmeticException
(算术错误)等。
try-catch-finally
try
块: 用于捕获异常。其后可接零个或多个 catch
块,如果没有 catch
块,则必须跟一个 finally
块。
catch
块:用于处理 try 捕获到的异常。
finally
块: 无论是否捕获或处理异常,finally
块里的语句都会被执行。当在 try
块或 catch
块中遇到 return
语句时,finally
语句块将在方法返回之前被执行。
finally块不会被执行的特殊情况
在 try
或 finally
块中用了 System.exit(int)
退出程序。但是,如果 System.exit(int)
在异常语句之后,finally
还是会被执行
程序所在的线程死亡
关闭 CPU
注意: 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。