1、静态方法好处
静态方法见名知意
private volatile static Cat cat;
public static Cat getInstanceSingleton() {
if (cat == null) {
synchronized (Cat.class) {
if (cat == null) {
cat = new Cat();
}
}
}
return cat;
}
不用重复的建对象-单例、Gson打日志
可以返回子类型
public static Animal getInstance() {
a = new Cat();
return a;
}
可以根据静态方法的入参,返回不同类型的对象
public static Animal getInstance(String name) {
if ("Dog".equals(name)) {
return new Dog();
}
if ("Cat".equals(name)) {
return new Cat();
}
return new Animal();
}
2、缺点
1、使用场景
类的属性太多,各种参数的有参构造器,人为都记不住属性名称了
setter方法,在构造过程中JavaBeans可能处于不一致的状态
User user = new User();
user.setName("mjp");
user.setAge(1);
有些对象从创建到销毁需要保持一致性,但是JavaBean对象不符合这点需求。
JavaBean对象的构造过程则先是通过创建对象,随后在通过setter方法来设置必要的参数。
直到销毁前,JavaBean对象都是可变的,或者说JavaBean一直在构造过程中。
在需要一致性对象的程序使用JavaBean对象,会可能导致失败。
2、链式操作,都需要结合@Data注解
3、builder 和 Accessors
4、lombok注解
@Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
@AllArgsConstructor : 注在类上,提供类的全参构造
@NoArgsConstructor : 注在类上,提供类的无参构造
private volatile static Cat cat; // 01.懒汉模式 + 03.对象创建过程中防止指令重排序
public static Cat getInstanceSingleton() {
if (cat == null) {
synchronized (Cat.class) { // 02.防止并发都进来
if (cat == null) {
cat = new Cat();
}
}
}
return cat;
}
volatile:防止指令重排序
* instance = new Singleton();分为三个动作
1.堆内存开辟一片空间,比如0X01
2.堆中创建Singleton,有初始化赋值的话,赋值
3.将对象内存地址返回给对象引用变量instance,instance存有0X01,instance在栈帧的局部变量表中
cpu为了提高吞吐量,会自动的改变cpu流水线,即指令的操作先后顺序改变,对单线程没有影响,多线程有
eg:
执行顺序 2,3变成让你3,2,导致返回给instance引用对象地址,虽然不为Null,但是根本没有完成赋值
导致t1执行同步代码执行结束的时候,t2判读,Instance!=null,但是instance本身没赋值;t2会直接返回instance
1、为什么私有化
@UtilityClass
public class DateUtil {
public Integer getInt() {
return 1;
}
}
//默认添加了,私有化的构造器
private DateUtil(){
}
别的地方无法通过new的形式创建
GsonUtil
@Slf4j
public class GsonUtil {
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
private GsonUtil() {
}
public static String toJson(Object object) {
try {
return GSON.toJson(object);
} catch (Exception e) {
log.error("序列化失败", e);
}
return StringUtils.EMPTY;
}
}
1、哪些方式会创建不必要的对象
new String、new Integer(建议使用Integr的valueOf()静态工厂方法)
循环中拆箱装箱
while循环,为了防止死循环、不断创建对象等。需要集合业务指定超过最大的循环次数
在高并发场景中,避免使用”等于”判断作为中断或退出的条件
2、包装类:占用更大的空间(但是包装类能表达 null 的语义)
正常情况下布尔值就是true和false,但是如果用户传递一个错误的skuId,那么此计算此skuId是否在灰度中,返回结果应该为null,因为true、false都不合适
交易额,异常的时候就因该为null,而非0
【推荐】自动转换(AutoBoxing)有一定成本,调用者与被调用函数间尽量使用同一类型,减少默认转换
//WRONG, sum 类型为Long, i类型为long,每次相加都需要AutoBoxing。
Long sum=0L;
for( long i = 0; i < 10000; i++) {
sum+=i;
}
1、sop
static变量属于类,类在变量在堆中内存就一致在。如果是集合,则元素对象也不会被回收,对象链GCRoot都不会被回收
尽量在方法内部,方法结束,变量也被回收了
数组元素,不用则及时置为null,array[i] = null,避免内存泄漏。(参考list的remove方法:elementData[–size] = null)
1、优点
使用正常的try-catch。在finally中关闭资源时,close方法也可以能出现异常导致资源关闭失败,所以需要再次try close方法。过于繁琐
FileInputStream fis = new FileInputStream("");
try {
int read = fis.read();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2、使用前提-资源类实现了Closeable接口
try(FileInputStream fis = new FileInputStream(new File("a"))) {
} catch (IOException e) {
}
1、为什么要重写
2、重写了equals的类
String
public boolean equals(Object anObject) {
if (this == anObject) { //01.地址相同,则一定“逻辑”相同
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) { //02.字符串的长度不同,则一定“逻辑”不同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { //03.每个字符对比
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
Integer
public boolean equals(Object obj) {
if (obj instanceof Integer) { //01.不是同类型的,肯定“逻辑”不同
return value == ((Integer)obj).intValue();//02.是Integer类型的则比较值的大小 【-128,127】则相同,否则不同
}
return false;
}
1、Integer使用equals来比较值的大小
2、但是【-128,127】可以直接使用 == 来比较大小
Integer a = 127;
Integer b = 127;
System.out.println(a == b); //true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); //false
3、约定规范注意事项
animal.equals(cat)//true
cat.equals(animal)//false
Cat的equals中
if(obj instanceof Cat){
//显然这里animal不是Cat类型
}
Animal的equals中
if(obj instanceof Animal){
// Cat extends Animal,所以cat 是Animal类型
}
x.equals(y) 为true: 比较的是x和y的a、b的属性
y.equals(z) 为true: 比较的是y和z的b、c的属性
x.equals(z)如果比较的是x和z的a、c属性,则不能保证传递性
如果x.equasl(y)中equals方法代码中涉及random随机数、时间戳等可变的元素,则无法保证equals方法每次都返回同样的值
1、sop
1、sop
a = b和c属性的计算结果,则hashCode方法中,要么使用a,要么使用b和c。不要a和另外两个一起决定hash值
2、为什么重写hashcode
String s1 = "majinpeng02";
String s2 = "majinpeng02";
s1.equals(s2); //true
但是hashcode没有重写,二者的hashcode值可能不一样,假如不一样。则hashmap.put(s1,1); hashmap.put(s2,1)则二者都能存入map。和我们hashmap约定的key冲突不一致了
所以,hashmap也需要重写hashcode
3、重写hashcode的作用/好处
符合equals相同则hashcode也相同
比较字符串是否为相同字符串的时候,首先可以使用hashcode值进行比较,hash值不一样则equals一定不同。如果hash值相同,再使用equals,一个一个字符进行对比判断。所以,可以提高效率
4、方法特点
5、String的hashcode方法:Object本身的hashcode是native方法
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
1、对象调用clone方法,可以跳过构造器的调用!
2、clone是浅copy,copy出来的对象和原对象指向同一块堆内存,一个修改了内存元素,会影响另一个。
3、不可变类,一定不要提供clone重写方法
1、什么时候需要实现此接口
2、String、Integer实现此接口重写compareTo方法
挨个字符比较大小
// 挨个字符比较大小
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
//自定义compare方法,不使用减法,避免int溢出
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
//这里没有使用x - y,而是使用了x > y进行比较。就是防止如果y是负数,则Integer.MAX_VALUE - 一个负数,结果溢出int值
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
3、使用注意事项
4、compareTo方法返回值含义
5、区别Comparator接口
Comparable是类的语言特性,表明这个类具有比较、排序等功能。本质上Comparable属于内部排序,Comparator是外部排序。
属于java.util的接口,一般用于集合元素的排序
是函数式接口,只有一个方法:int compare*(T o1, T o2) :* 0相同、1大于、-1小于;
集合元素排序
//根据Dict对象的sort字段降序排序
dictList.sort(Comparator.comparing(Dict::getSort).reversed());
//根据Dict对象的sort字段升序排序
dictList.sort(Comparator.comparing(Dict::getSort));
//按照字段降序,相同的话,再按照另外一个字段降序
skuCategoryRuleDOS = skuCategoryRuleDOS.stream().
sorted(Comparator.comparing(SKUCategoryRuleDO::getSkuCategoryId).reversed().
thenComparing(SKUCategoryRuleDO::getSkuTemperatureZone,Comparator.reverseOrder())
)
.collect(Collectors.toList());
Comparator接口中其它reversed()、thenComparing()方法,则调用了本身的compare()方法进行再排序
-优先考虑使用private
1、封装的好处:
2、类属性为什么不建议public修饰
3、使用public getter、setter代替public成员变量:public类的实例,绝不能是public的
原因
public class User {
public String name;
}
1、这个类的name是可以直接被访问的,当域被访问的时候,我们将失去对这个域的控制权。后续,想要将name字段改为nickName,那么使用方就全部报错
2、通常包含public属性的类,是线程不安全的
User user = new User();
user.setName(null);
public void setName(String name) {
if (StringUtils.isBlank(name)) {
throw new NullPointerException();
}
this.name = name;
}
好处1:可以在setter方法中,对set的值进行逻辑校验
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
好处2
//好处2:可以修改属性的名称而不用关注调用方
//现在我觉得,name字段描述的不准确,换个名称为nickName,直接在本类中修改就ok,不用关注调用方
public class User {
private String nickName;
public String getName() {
return nickName;
}
public void setName(String name) {
this.nickName = name;
}
}
4、如果想要数组、集合,对象地址不可变 && 内容元素也不可变,建议使用 private final修饰,
public static final String[] ARRAY = new String[];
影响:
虽然ARRAY引用不能修改,但是ARRAY内部的元素是可以被修改的
解决:
假的final,最好private修饰[数组、集合]
//假的final,最好private修饰[数组、集合]
对象不可变了,但是元素内容还是可以被操作改变的。如果不想被外部的操作影响,则必须private
private static final Map<String,Long> map = new HashMap<>;
private static final List<Long> list = new ArrayList<>;
private static final String[] ARRAY = new String[];
可以深copy成员变量,防止改变影响成员变量值
//可以深copy成员变量,防止改变影响成员变量值
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];//02.这个value数组,在String内部被很多其它地方使用。所以,不能改变它的值
//01. 这样对toCharArray的返回结果数组进行操作,不会影响原本的value数组的元素内容
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
}
特殊情况:真final不可变,可以在类中提供public成员变量
private修饰和public修饰效果一样
public statci final Longsize = 200;
public statci final Integer code= 0;
public statci final String birthday = "2022-06-01";
-优先考虑使用final
1、不可变类特点
2、final类优点 & 缺点
可以重复使用、共享。线程安全
安全:(1.8中DateTimeFormatter不可变,线程安全、SimpleDateFormat可变并发不安全(线程A定义的合适为:YYYYMMDD,可能被线程B改为YYYY_MM_DD))
共享:不需要进行保护性拷贝(拷贝始终等于原始的对象)。因此,不需要为不可变类提供clone方法
唯一缺点:每一个不同的值都需要创建一个新的对象。
3、不可变类,需要遵循的原则
保证类不会被继承,final无法被继承
既不要从外部拿,要不要返给外部
确保在该类的外部不会获取(get)到可变对象的引用、也不要从外部拿,然后set 可变对象。同时建议:使所有的域都是private final ,参考3
4、类属性是集合、数组。使用注意事项
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer age;
private Map<String, Long> map = new HashMap<>();
}
Map<String, Long> source = new HashMap<>();
source.put("mjp", 1L);
// 01.外部数据源source,给类关键集合map赋值
User user = new User(18, source);
Map<String, Long> target = user.getMap();
System.out.println(target);//{mjp=1}
// 02.外部数据源发生了变化
source.put("wxx", 1L);
// 03.类集合也会内容也会发生变化
System.out.println(target);//{mjp=1, wxx=1}
//正确做法
1、不要给关键数组、集合提供get、set方法。要想get则深copy返回。即不要使用@Data注解
2、建议private final
//补充
上述,基本都是针对final类。我们日常开发,还是不这样做的,所以,要清楚你提供了get、set方法则类中的集合、数组内容会收到外部数据源的内容变化而变化
注意⚠️:Map
尽可能使用对象组合而不是继承的方式达到复用的目的
1、复合
@Data
@Accessors(chain = true)
public class SmallName {
private String foreverName;
@Resource
private User user;//符合添加了user类,可以使用user对象的方法
}
2、继承的缺点
子类只会比父类更大,权限也是,只能放大。类会越来越大。无法收缩了
特例:接口的实现类中实现的方法的修饰符,全部都是public
父类的方法,子类也全部拥有。有时候继承某个类,并不是想拥有此类的所有方法!
跨包的继承,则更加危险
违背了封装的原则,子类需求去了解父类的实现,否则随着不同版本父类的代码发生了改变,即使子类完全没有改变代码,也有可能被破坏
覆盖了父类的方法后,结果不符合预期:典型的:add元素的count统计
public class MyCountSet extends HashSet<Integer> {
/** 统计"有史以来"向该集合中添加过的元素个数 */
private int count = 0;
@Override
public boolean add(Integer num) {
count++;
return super.add(num);
}
@Override
public boolean addAll(Collection<? extends Integer> nums) {
count += nums.size();
return super.addAll(nums);
}
public int getCount() {
return count;
}
public static void main(String[] args) {
MyCountSet countingSet = new MyCountSet();
countingSet.addAll(Lists.newArrayList(1));
System.out.println(countingSet.getCount());//2
}
}
1、现象:
addAll方法,添加了集合中1个元素,count应该是2,但是实际输出2
2、原因:
子类重写了addAll方法,但是不知道父HashSet的addAll的具体实现
父类HashSet的addAll,调用了自身的add方法。子类也重写了add方法,就导致:子类的addAll方法中count += nums.size();算了一次,同时,子类的add方法中count++又算了一次
3、执行步骤
a、子类addAll,count += nums.size();此时,count值为1
b、super.addAll,调用HashSet的addAll
c、HashSet的addAll,调用了自身的add()
d、HashSet的add(),被子类重写了,所以,调用子类的add
e、子类的add中,执行count++;此时,count值为2
f、再调用父类add完成将元素加入
4、解决
将子类的addAll中的count += nums.size();删除
HashSet的addAll方法
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
父类构造器绝不能直接/间接调用可被覆盖的方法,否则可能不符合预期(程序失败、npe等)
Sup
public class Super {
public Super() {
overrideMe ();//02-造器能调用可了被覆盖的方法❌
}
public void overrideMe () {
}
}
Sub
public final class Sub extends Super {
private final Instant instant;
Sub() {
super();//01.
instant = Instant.now();//05.完成赋值
}
@Override
public void overrideMe () {//03.进入子类的重写方法
System.out.println(instant);//04.此时instant为null,还没有值!!!
}
public static void main(String[] args) {
Sub sub = new Sub();//00.执行顺序如上
sub.overrideMe ();//06.子类方法,有值了
}
}
1、接口和抽象类的区别
接口,所有的方法默认是public,属性都是;类默认是default
java8-default方法
1、作用
为了扩展接口的功能。接口新增方法,如果是public的,则所有实现类都需要实现,这样就不符合向下兼容
实现类自动拥有和接口一样的default方法,直接用,不需要再实现
2、eg
List extend Collection接口,此接口自己实现了removeIf方法
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
3、最好不要在接口已经存在的情况下(可以在接口第一次创建的时候添加),再添加新的default方法,这对于接口来说非常危险
eg:removeIf方法,对于大多数Collection接口接口的实现类,都没有影响,但是对于已经实现了Collection接口的org.apache.commons.collections4.collection.SynchronizedCollection
则可能存在问题。
如果客户端在SynchronizedCollection的实例上调用removeIf方法,同时另外一个线程对集合进行修改,就会导致并发修复爱Exception
接口中只能有常量【但是不建议,接口只提供行为规范】、抽象类中可以有成员变量
常量型接口-是接口的错误使用
1、原因
类实现常量接口,这对于这个类的用户来讲并没有实际的价值。实际上,这样做返回会让他们感到更糊涂
2、建议
使用Enum
抽象类单继承【但是了类可以实现多个接口】; 接口多继承接口可以同时继承B、C接口【但是接口B和接口C,不能出现冲突方法】
接口多继承注意事项
public interface BInterface {
void eat();
}
public interface CInterface {
String eat();
}
//这样A接口,就不知道eat方法具体是哪个【have unrelated return types】
public interface AInterface extends BInterface, CInterface{
}
接口是行为规范,具体实现逻辑由实现类自己定义 ;
抽象类定义了大部分公有的具体行为,根据不同子类定义了不同的abstract方法,由子类根据自己的特点实现即可
抽象类优于标签类
//01.标签类-eg:它能够表示圆形或者矩形
public class Figure {
enum Shape { RECTANGLE,CIRCLE };
final Shape shape;
double length;
double width;
double radius;
public Figure(double redius){
shape=Shape.CIRCLE;
this.radius=radius;
}
public Figure (double lenght, double width) {
shape=Shape.RECTANGLE;
this.length=lenght;
this.width=width;
}
//计算不同形状的面积
public double area(){
switch (shape){
case RECTANGLE:return length*width;
case CIRCLE:return Math.PI*(radius * radius);
default:throw new AssertionError();
}
}
}
缺点:
违背了开闭原则
新增图形表示,求面积则需要:添加新的case;同时也有可能添加新的成员变量【梯形:(上底+下底)*高/2】
//02.抽象类
public abstract class AbstractFigure {
public abstract double area();
}
// 圆形子类
public class CircleFigure extends AbstractFigure {
private double radius;
public CircleFigure(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// 矩形子类
public class RectangleFigure extends AbstractFigure {
private double length;
private double width;
public RectangleFigure(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
优点:新增图形,则新增类即可,不需要修改原本的代码,遵行开闭原则
如何决定使用抽象类还是接口
要表示is-a(圆形是一个图形、三角形是图形----)的关系,并且是为了解决代码复用的问题,就用抽象类;
表示has-a关系,并且是为了解决抽象和解耦而非代码复用的问题,那就使用接口。
2、abstract方法注意事项
3、常用接口:
Cloneable克隆、Serializable可序列化、Comparable可比较、CharSequence、Runnable可执行
4、为什么接口优于抽象类
存在以下场景,A类即是可以克隆的、又是可以序列化的、又是可以比较大小的、又是可以作为任务执行的。如果上述常用接口都变成了抽象类,那么由于类的单继承,所以A类就不能同时具有以上功能。
除非把上述接口变成抽象类,而且彼此之间有父子继承关系。但是A类可以只可克隆 + 可序列化,不需要可比较大小 + 可执行
5、接口和抽象类混合
A extends B抽象类 implement C接口
HashMap和TreeMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
}
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable{
}
6、函数式编程
注意⚠️:这里的对象,本质是方法即策略【按age降序、按name降序、按skuId降序等等策略】
函数式接口特点:注解 + 单个方法
@FunctionalInterface//01.注解
public interface Comparator<T> {
int compare(T o1, T o2);//02.方法
//虽然接口还有其它Object对象的方法【equals等】
//以及接口本身的default方法【java8新增】
//以及静态方法static【java9新增】
}
除了default方法、static方法、Object类的方法外,有且仅有一个方法,这种就是函数式接口
方法作为对象传入方法eg1
//01.自定义函数式接口作为方法的入参对象:方法的作用是按照User的age降序排序方法
User u1 = new User(1, "mjp");
User u2 = new User(2, "wxx");
List<User> result = Lists.newArrayList(u1, u2);
//02.自定义函数式接口
Comparator<User> myComparator = new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o2.getAge().intValue() - o1.getAge().intValue();//按照age降序
}
};
result.sort(myComparator);//这里的myComparator,本质就是方法,当法的作用是按照age的大小降序
//02. 函数式接口,可以使用lamda表达式
Comparator<User> myComparator = (user1, user2) -> user2.getAge() - user1.getAge();
User u1 = new User(1, "mjp");
User u2 = new User(2, "wxx");
List<User> result = Lists.newArrayList(u1, u2);
result.sort(Comparator.comparing(User::getAge)); //03.这里的sort方法的参数对着,就是方法【函数式接口】,这个方法的内容按照User的age的大小升序排序
Lists.newArrayList(new User(1,"mjp")).stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
这里的sort方法就是传入一个比较大小的方法,然后返回一个Stream流
Stream<T> sorted(Comparator<? super T> comparator);
1、原因:
statci成员内部类属于Class类的;非static成员内部类属于对象的【必须先new出外部类对象】
static和非static
@Data
public class User {
private Integer age;
private String name;
private Emp emp;
@Data
public class Emp{
private Long skuId;
}
@Data
public static class Price{
private Double money;
}
}
@Test
public void t(){
Price price = new Price();
price.setMoney(1.0);//01.static成员内部类,可以直接new,不依赖外部类对象
User user = new User();
Emp emp = user.new Emp();//02.要想获得非static成员内部类的对象,必须先获取外部类对象
emp.setSkuId(1L);
}
非staic成员内部类对象,强绑定外部类对象【比如EntrySet对象和HashMap就是】,可能会影响GC
除此之外,非静态成员类的实例被创建的时候,它和外围类的关联关系也随之建立起来,这种关联关系,需要消耗非静态成员类实例的空间,并且增加构造的时间开销
Map-Entry
Entry的getKey、setValue等方法,都不需要访问Map,所以,使用非静态成员类表示Entry则会浪费
1、使用原生态可能存在的安全问题,因为缺少类型的检查。可能会在运行时导致异常
获取集合元素,并且强转时,会运行时才会报出ClassCastException异常【无法在编译时期IDEA就报出来】
原生态类型
List list = new ArrayList();
list.add(1);
String s = (String) list.get(0);
System.out.println(s);
@Test
public void t() {
List<Integer> list = new ArrayList();
add(list, "java");
for (Integer item : list) {// 02.遍历集合元素,使用Integr进行强转接收时,异常
System.out.println(item);
}
}
public static void add(List list, Object obj) { //01.方法入参,没有指定泛型
list.add(obj);
}
2、带有泛型的类型,传参到无泛型方法中,尽量只读区不写
public static void add(List list) {//为了接受参数的通用性,这里没有带泛型
//可以是原生态list,但是尽量只是读取list元素,不写(add方法等)
for (Object o : list) {
System.out.println(o);
}
}
3、泛型的擦除
本质:运行时期,都是class java.util.ArrayList
泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,即类型擦除。类型擦除主要是为了兼容之前没有泛型特性的代码
List<Integer> l1 = new ArrayList<>();
List<String> l2 = new ArrayList<>();
Class<? extends List> aClass1 = l1.getClass();
Class<? extends List> aClass2 = l2.getClass();
System.out.println(aClass1 == aClass2);//true
而数组在运行时,Integr[]和String[]对应的不同的class
在编译时期,List、List无任何关系。所以,他们作为方法的入参时,可以理解为方法的重载
1、@SuppressWarnings*(“unchecked”)*范围
1、原因
数组是协变的
ArrayStoreException
Object[] obj 是 String[]的父类型
List<Object>不是List<String>的父类型
数组 编译时期,不会报异常。运行时会
Object[] array = new String[3];
array[0] = 1;
System.out.println(array[0]);//运行时ArrayStoreException,编译时没问题
数组一但创建,大小不可变
2、泛型和可变参数一起使用注意事项
当调用可变参数时,将创建一个数组来保存参数
void foo(String... args);
void foo(String[] args); // 两种方法本质上没有区别
ArrayStoreException
@Test
public void t() {
func("mjp","wxx");
}
public static void func(String...args) {
String[] strArray = args; //01.可变参数,本质是数组
Object[] objArray = strArray;//02.数组的协变的,args、strArray、objArray三者都指向同一块堆内存地址
objArray[0] = 1; //03.堆地址内元素做了改变,相当于在字符串数组中添加了整型
String arg = args[0];// 04.ArrayStoreException
}
1、什么时候,使用泛型类方法
2、什么时候,建议直接使用Object
只读不写
eg:thrift中定义roc接口中BaseResponse中的数据Data
public class ThriftBaseTResponse {//02.删除T
public int code = Constants.SUCCESS;
public String message;
public T data;//01.这里,set给data值后,直接返回给前端了。后续,不再有读取操作了,其实可以直接使用Object
}
3、方法入参,不限制类型时,方法返回值返回Object还是泛型
- 外部交互:返回Object。由使用方自己强转换
```java
private final Map
private final Map<Object,Object> map = Maps.newHashMap();
public <T> T getValueByKey(Object key) {
return (T)map.get(key);
}
Integer res = getValueByKey("java");//这里就不用强转了。但是要求,内部使用方知道,key-“java”,对应的value类型
1、 ? extends A
则?代表A或者A的子类(类A被继承)或A的实现类(接口A被实现)
读(comparable 和 comparator都是读取)
List,则?可以是Integr、Double、Long都可以
只可以读
@Test
public void t() {
List<Integer> l1 = Lists.newArrayList(1,2,3);
List<Double> l2 = Lists.newArrayList(1.0,2.0,3.0);
sum(l1);
sum(l2);
}
private Double sum(List<? extends Number> list) {
Double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
2、 ? super A
则?代表 A或者A的父类
private void add(List<? super Number> list, Number num) { // 这里的list,必须是List或List