JAVA数据结构 & Java语法完善补充
1. 包装类
包装类就是基本数据类型的“对象化版,用于适应泛型”
特殊的,int, char --> Integer, Character
其他基本数据类型仅仅将首字母变大写, boolean --> Boolean
1.1 装箱
显式装箱
int i = 10; Integer i1 = Integer.valueOf(); Integer i2 = Integer.valueOf("10"); Integer i3 = new Integer(i); Integer i4 = new Integer(10);
隐式装箱
int i = 10; Integer i1 = i; Integer i1 = (Integer)i; System.sout.println(i);//这里相当于用了装箱成Integer, 用了Integer重写的toString()方法 //等等传参赋值一般都会隐式装箱 //但是!int[] ---/---> Integer[] //在集合的toArray()里面特别注意
1.2 拆箱
显式拆箱
Integer i = new Integer(10); int i1 = i.intValue();
隐式拆箱
Integer i = new Integer(10); int i1 = i; int i1 = (int)i; //但是!Integer[] ---/---> int[]
1.3 Integer包装类的比较
如果用 <=, <, >=, >, ==, != 判断的话,只能判断-128 ~ 127,的整数
范围外就得用equals()和compareTo()
2. 泛型
类型参数化
适用于许多类型
泛型只能是引用类型,不能是基本数据类型
对于裸类型的使用,在讲编译器时会继续讨论哦【兼容老版本】
2.1 泛型语法
class 泛型类名称<类型列表T1, T2...> {
...
}
2.1.1 泛型在类中构造数组
在系统自带的很多泛型类中,很多都用到的技巧,就是先构造Object,在返回之前强转
Object[] elementData; elementData = new new Object[initialCapacity]; elementData[0] = e;//(E e)Object 接收一切,在这完成了向上转型,等一下就不会出错 //用的时候向上转型 return (E)elementData[0]; //出的时候向下转型(因为是泛型向上转型过来的,所以没问题)
2.1.2 泛型方法
这个与泛型类的泛型无关
关键字之后,返回类型之前
public static <T> int fun(T t) {
...
}
最重要的是,传参必须是包含泛型的,才能人我知道是什么类型,有类型推导1
2.2 擦除机制
其实在查看字节码文件后,可以得知所有的泛型都被擦除成Object了
并且 ( ).class文件名中不含泛型
泛型只存在于编译阶段
能做到的就是,根据指定类型,传值自动向上转型以及返回值强制类型转化
2.3 通配符 —> ?
class B <T> { public void p() { System.out.println("打印成功"); } } public class Test { public static void print(B<?> b) { b.p(); } public static void main(String[] args) { B<String> b = new B<>(); print(b); } } public static <T> void print(B<T> b) { b.p(); }
通配符是可以用泛型方法模拟的
通配符额外的特点
但是通配符更简约,?问号表示未知类型待填充
通配符上界下界都有
上界:<? extends T> :> 代表填充的类型必须是T或者其子类【? <= T】
下界:<? super T> :> 代表填充的类型必须是T或者其父类【? >= T】
3. String常量池
在JVM中其实就是一个StringTable类,是一个固定大小的HashTable【哈希表】
接下来的内容如果听不懂,可以在学习完HashMap和HashSet后学习
hash默认为0,当求哈希值时才会改变(哈希表的哈希方法有关)
char[] 类型数组不用多说
public static void main(String[] args) { String str1 = "ahasdsadhh"; String str2 = "ahasdsadhh"; String str3 = new String("ahasdsadhh" ); System.out.println(str1 == str2); System.out.println(str1 == str3); }
接下来就是一些【规矩】
俩个或多个字面量直接相加,是在编译期间进行的,只会将结果存放在字符串常量池。
这个也很好理解,因为一条式子只用一个StringBuilder,最终StringBuilder,在把结果放进常量池
这也是体现了多次使用 【+= string】会徒增很多麻烦
String()构造方法
如果括号内已存在于常量池,则只需要拷贝一份
如果没有,则先在常量池中备份,再拷贝一份
不带参数,为空字符串。(当然也是从常量池拷贝的)
“” 空字符串也是字符串(应该放进常量池),就像空数组也是数组,只不过里面的一些成员属性空空如也
= “ … ”
如果“ … ”已存于常量池,则直接引用
如果没有,在常量池里面备份,直接引用
intern()方法【native方法,底层由C++写的】
理论上任何字符串应该都是存在于常量池里的,但是有一种情况没有
就是,String( char[] ),用char[]直接构造的字符串,拷贝一份直接给String,这样就不需要在常量池里拿了
public static void main(String[] args) { char[] ch = {'1', '2', '3', '4'}; String str1 = new String(ch); String str2 = "1234"; System.out.println(str1 == str2); }
public static void main(String[] args) { char[] ch = {'1', '2', '3', '4'}; String str1 = new String(ch); str1.intern(); String str2 = "1234"; System.out.println(str1 == str2); }
通过手动导入常量池,刚才用字符数组构建的字符串放进了字符串常量池,该引用也顺理成章的指向了常量池。
4. 反射
照妖镜一样,反射到的类,所有东西都无处遁形
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。
这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
在这个运行状态中,可以临时使用private属性以及方法(除了枚举里的,最强大常量的威严)
在学习框架之前,我们只需要了解反射即可
4.1 反射相关的类
重要的类 对应
Class类 类与接口
Field类 成员属性
Method类 成员方法
Constructor类 构造方法
【Class】 --> 【获取一个类】 —> 【制作成一个Class类】 ----> 【一面镜子 c1】
c1.getname(),可以获取到制作这面镜子的对应类的全路径
c1.newInstance() ,可以直接调用 不带参数的 符合权限 构造方法
Class.forname(不同的包的话具体路径),(制作镜子常见方法)
T.class(不在同个包中需要导包)
(new T()).getClass()or 具体引用.getClass()(提取引用变量的类制作成镜子)**(有“Declared”,则打包成数组)
以上三种制作出同一面镜子。
通过镜子c1反射到
Field 属性
一般用 c1.getDeclaredField(String name) 和 c1.getDeclaredFields() 属性(可以获取任何方法)
其中前者为单一成员属性,后者为全部打包出来的成员属性数组。
少了“Declared” 就只能用符合权限的成员属性
.set(具体引用, 传参...)
Method 方法
一般用 c1.getDeclaredMethod(String name, 匹配的类型.class ... ) 和 c1.getDeclaredMethods() 属性(可以获取任何方法)
其中前者为单一成员方法,后者为全部打包出来的成员方法数组。
少了“Declared” 就只能用符合权限的成员方法
.invoke(具体引用, 传参...)
Constructor 构造方法
一般用 c1.getDeclaredConstructor(匹配的类型.class ...) 和 c1.getDeclaredConstructors() 属性(可以获取任何方法)
其中前者为单一构造方法,后者为全部打包出来的构造方法数组。
少了“Declared” 就只能用符合权限的构造方法
.newInstance(传参...)
4.2 一个实例加深理解
总的来看,这是异常的处理,不然跑不过编译
可以用下面的 try & catch法(JVM处理不了就会被打印)
也可以直接抛给 JVM 处理
在操作的时候会抛很多编译时异常,所以要做好处理
public static void main(String[] args){
//一个类只有一个class对象
try {
区域一:
区域二:
区域三:
区域四:
}catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
4.2.1 区域一:【镜子制作】
Class<?> c1 = Class.forName("Student"); Class<?> c2 = Student.class; Class<?> c3 = (new Student()).getClass(); System.out.println(c1 == c2); System.out.println(c1 == c3); System.out.println(c3 == c2); Student student1 = (Student) c1.newInstance();//调用构造方法
4.2.2 区域二:【构造方法】
setAccessible(true) //getDeclared....s (加s,对应的所有东西都打包在数组里) Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class); //确认一下是否使用private东西(私有仍可用) constructor.setAccessible(true); Student student2 = (Student) constructor.newInstance("小卡拉", 19); System.out.println(student2); constructor.setAccessible(false);
4.2.3 区域三:【成员属性】
Field field = c1.getDeclaredField("name"); field.setAccessible(true); field.set(student1, "小霹雳"); System.out.println(student1); constructor.setAccessible(false);
4.2.4 区域四:【成员方法】
Method method = c1.getDeclaredMethod("print", String.class); method.setAccessible(true); method.invoke(student1, "啊哈,我在调用这个私有方法"); constructor.setAccessible(false);
4.2.5 测试:
4.3 优缺点
优点:可以运行时使用类里的所有方法,常用于一些框架
缺点:效率不高,要调用个private的东西
5. 枚举Enum抽象类
5.1 建立枚举类
对于枚举类而言,我们自己定义的枚举类默认继承系统的 Enum抽象类。
并且枚举类的private属性方法(其实默认的都是,绝对不能改成public)都不能被反射到
在Java中,枚举类型更加体现其“常量性”,而不是C语言枚举对应的是一个整型数字
体现在打印枚举类型,打印的是常量名(除非你重写了方法)
public enum TestEnum { //默认继承 Enum 这个抽象类 //必须放在最上面,这是特殊的规定 RED, GREEN(),//括号可省略 BLACK; private String color; private int ordinal; }
实际上,每个常量都是这个类的实例化常量!
索引就是这个类定义的前后顺序(0开始),索引没法变
并且用这个类实例化其他常量!
5.2 枚举类的常见使用场景
5.2.1 switch语句分支
public static void main(String[] args) { TestEnum testEnum = RED; //只能在枚举类内部使用,且不能自己实例化,只能用已有的赋值 switch (testEnum) { case RED: System.out.println(RED);//打印常量名,除非你重写了toString(); break; case BLACK: System.out.println(BLACK); break; case GREEN: System.out.println(GREEN); break; default: System.out.println("error"); break; } }
5.2.2 构造方法
public enum TestEnum { //默认继承 Enum 这个抽象类 //必须放在最上面,这是特殊的规定 RED("red", 1), GREEN(), BLACK; private String color; private int ordinal; private TestEnum(String color, int ordinal) { this.color = color; this.ordinal = ordinal; } TestEnum() { //不带参数构造方法,自己写了构造方法后一定要补上 } }
5.2.4 常见的方法
public static void main1(String[] args) { TestEnum[] testEnum = TestEnum.values(); System.out.println(RED.color); System.out.println(RED.ordinal); for(TestEnum t : testEnum) { System.out.print(t + " " + t.ordinal() + " "); //获取索引 } TestEnum testEnum1 = TestEnum.valueOf("GREEN");//类似于 字符串转化具体值 System.out.println(testEnum1); System.out.println(RED.compareTo(GREEN));//默认重写了,因为默认继承 Enum }
values():依照索引先后构建枚举常量数组
ordinal():获取枚举常量对应的索引
valueof():字符串转化(如果不是该枚举类型的任何一个常量,报异常)(大写的O)
枚举常量本身就是实例化后的,并且本身就重写了compareTo()和toString();
6. λ表达式(lambda)
6.1 lambda表达式的用途
重写方法(一般是抽象类,或者接口)
有一个要求,这个(抽象类or接口)只有一个可被重写的抽象方法
原因随后便知
6.2 lambda表达式的语法
(parameters) -> expression 或者 (parameters) -> { statements;}
parameters:参数列表
-> :【形参被用于】
express:表达式,作为返回值
statems:方法体,单语句或者多语句
显然,如果这个抽象类或者接口有多个抽象方法的话,是绝对用不了这个语法的(不支持重载)
使用这个表达式重点在于你很熟悉这个抽象类或者接口
() -> 2; x -> 2 * x; (x, y) -> x + y; (int x, int y) -> x + y; (String s) -> System.out.println(s); //只有在单一参数,单一语句,单一表达式时是可以省略括号的,其他不行
6.3 函数式接口
就是一个接口只有一个抽象方法(@FunctionlInterface,这个注解可以用来警示我们写代码,规范行为)
下面这个是可以的,default修饰的方法不是抽象方法
这样这个接口的目的就相当于【传了一个方法过去】
@FunctionlInterface interface A { void test(); default void test2() { System.out.println("0.0"); } }
使用实例:
public class Test { public static void main(String[] args) { A a = () -> { System.out.println("yes"); }; a.test(); } }
其他情况举一反三!
因为重写的方法只有一个,所以简化成这样
6.4 变量捕获
匿名内部类:可以捕获到该语句以上的”局部常量“(在整个局部范围中都没有变过的常量),捕获之前之后都不能改变它!
如果是成员变量等全局性质的变量,那一定能访问到~
引用类型不得更改指向~
lambda表达式:同上
int x = 1; x = 1//error A a = new A() { x = 2;//error System.out.println(x);//yes } int x = 1; A a = () -> { System.out.println(x);//yes }
6.5 lambda 表达式在集合类以及Map类中的使用
这些接口很大概率是需要重写比较方法之类的…
6.5.1 典例1:
重写Collection / List 的extends的Iterable接口的forEach()所先需要的Consumer接口的accept()方法 public interface Iterable<T> { Iterator<T> iterator(); default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } } @FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
熟悉接口很重要
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); List.forEach(s -> { System.out.print(s + " "); }); //如果不重写,这个方法压根用不了 //结果:1 2 3 4
6.5.2 典例2:
重写Collections工具类的sort()方法,List的sort(),所需要的Comparator接口的compare( , )方法
default void sort(Comparator<? super E> c) { Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e); } }
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); } //这里很特殊,有两个抽象方法,但是并无大碍,可以这么理解为equals()在Object中本就有了 //或者不要在意这一点,记住就好 List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); List.sort((s1, s2)-> s2.compareTo(s1));//逆序 System.out.println(list); //结果:[4, 3, 2, 1]
6.5.3 典例3:
HashMap对应的forEach();
TreeMap的话还需要比较方法才能构建树
这次是重写BiConsumer接口的accept()
它的forEach(),是两个参数
Map<Integer, String> hashMap = new HashMap<>(); hashMap.put(1, "11"); hashMap.put(2, "22"); hashMap.put(3, "33"); hashMap.put(4, "44"); hashMap.put(5, "55"); hashMap.forEach((k, v) -> { System.out.println(k + "=" + v); }) //结果: //1 = 11...
6.6 优缺点
显然,代码层次上,lambda表达式很简洁
但是代码不易读不易调试
文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆!
这是我的代码仓库!(在马拉圈的23.2里)代码仓库 代码具体位置
就是在一些情况写编译器会识别你的泛型是什么,从而省略<>中的泛型甚至不写<>。