目录
裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型
一、运算符
1、算术运算符
1.1基本四则运算符:加减乘除模(+ - * / %)
public class test {
public static void main(String[] args) {
int a = 10;
int b = 20;
//a = 10,b=20
System.out.println("a = "+a+" ,b= "+b);
System.out.println("a+b = "+a+b);
System.out.println("a+b = "+(a+b));
System.out.println(a+b+"hahaheiheihei!");
}
输出结果:
a = 10 ,b= 20
a+b = 1020
a+b = 30
30hahaheiheihei!
public static void main(String[] args)
{
System.out.println(10%3);
System.out.println(10%-3);
System.out.println(-10%3);
System.out.println(-10%-3);
}
输出结果:1 1 -1 -1
public class test{
public static void main(String[] args) {
System.out.println(5/2);
System.out.println((float)5/2);
System.out.println(5/(float)2);
System.out.println((float)(5/2));
}
}
输出结果:2 2.5 2.5 2.0
注意: int / int 结果还是int 类型,而且会向下取整。 做除法和取模时,右操作数不能为0。
% 不仅可以对整形取模,也可以对double类型取模,但是没有意义,一般都是对整形取模的
System . out . println ( 11.5 % 2.0 );// 运行结果1.5
两侧操作数类型不一致时,向类型大的提升
System . out . println ( 1 + 0.2 ); // + 的左侧是 int ,右侧是 double ,在加之前 int 被提升为 double// 故:输出 1.2
1.2增量运算符 += -= *= %=
该种类型运算符操作完成后,会将操纵的结果赋值给左操作数。
int a = 1 ;a += 2 ; // 相当于 a = a + 2System . out . println ( a ); // 输出 3a -= 1 ; // 相当于 a = a - 1System . out . println ( a ); // 输出 2a *= 3 ; // 相当于 a = a * 3System . out . println ( a ); // 输出 6a /= 3 ; // 相当于 a = a / 3System . out . println ( a ); // 输出 2a %= 3 ; // 相当于 a = a % 2System . out . println ( a ); // 输出 2
注意:只有变量才能使用该运算符,常量不能使用。
1.3自增/自减运算符 ++ --
int a = 1 ;a ++ ; // 后置 ++ 表示给 a 的值加 1 ,此时 a 的值为 2System . out . println ( a ++ ); // 注意:后置 ++ 是先使用变量原来值,表示式结束时给变量 +1 ,因此输出 2System . out . println ( a ); // 输出 3++ a ; // 前置 ++ 表示给 a 的值加 1System . out . println ( ++ a ); // 注意:前置 ++ 是先给变量 +1 ,然后使用变量中的值,因此输出 5System . out . println ( a ); // 输出 5// -- 操作符给操作 -1 ,与 ++ 含义类似
2、关系运算符
关系运算符主要有六个: == != < > <= >= ,其计算结果是 true 或者 false 。
int a = 10 ;int b = 20 ;System . out . println ( a == b ); // falseSystem . out . println ( a != b ); // trueSystem . out . println ( a < b ); // trueSystem . out . println ( a > b ); // falseSystem . out . println ( a <= b ); // trueSystem . out . println ( a >= b ); // false
注意:当需要多次判断时,不能连着写,比如:3 < a < 5 , Java 程序与数学中是有区别的
3、逻辑运算符
逻辑运算符主要有三个: && || ! ,运算结果都是 boolean 类型。
3.1逻辑与 &&
语法规则:表达式1 && 表达式 2。两个表达式都为真,结果才是真,只要有一个是假,结果就是假。
3.2逻辑或 ||
语法规则:表达式1 || 表达式 2。
3.3逻辑非 !
语法规则:! 表达式 真变假,假变真。
3.4短路求值
&& 和 || 遵守短路求值的规则 .
System . out . println ( 10 > 20 && 10 / 0 == 0 ); // 打印 falseSystem . out . println ( 10 < 20 || 10 / 0 == 0 ); // 打印 true
我们都知道, 计算 10 / 0 会导致程序抛出异常 . 但是上面的代码却能正常运行 , 说明 10 / 0 并没有真正被求值 .
注意:
对于 && , 如果 左侧表达式值为 false, 则表达式结果一定是 false, 无需计算右侧表达式 .
对于 ||, 如果 左侧表达式值为 true, 则表达式结果一定是 true, 无需计算右侧表达式 .
& 和 | 如果表达式结果为 boolean 时 , 也表示逻辑运算 . 但与 && || 相比 , 它们不支持短路求值
System . out . println ( 10 > 20 & 10 / 0 == 0 ); // 程序抛出异常System . out . println ( 10 < 20 | 10 / 0 == 0 ); // 程序抛出异常
4、位运算符
Java 中数据存储的最小单位是字节,而数据操作的最小单位是比特位 . 字节是最小的存储单位,每个字节是由 8 个二进制比特位组成的,多个字节组合在一起可以表示各种不同的数据。
位运算符主要有四个 : & |
~ ^ ,除 ~ 是一元运算符外,其余都是二元运算符。
位操作表示 按二进制位运算 . 计算机中都是使用二进制来表示数据的 (01 构成的序列 ), 按位运算就是在按照二进制位的每一位依次进行计算.
4.1 按位与 &
如果两个二进制位都是 1, 则结果为 1, 否则结果为 0.
int a = 10 ;int b = 20 ;System . out . println ( a & b );
进行按位运算, 需要先把 10 和 20 转成二进制 , 分别为 1010 和 10100
4.2按位或 |
如果两个二进制位都是 0, 则结果为 0, 否则结果为 1.
int a = 10 ;int b = 20 ;System . out . println ( a | b );
运算方式和按位与类似.
注意: 当 & 和 | 的操作数为整数 (int, short, long, byte) 的时候 , 表示按位运算 , 当操作数为 boolean 的时候 , 表示逻辑运算。
4.3按位取反 ~
如果该位为 0 则转为 1, 如果该位为 1 则转为 0
int a = 0xf ;System . out . printf ( "%x\n" , ~a )
注意 :
0x 前缀的数字为 十六进制 数字 . 十六进制可以看成是二进制的简化表示方式 . 一个十六进制数字对应 4个二进制位.
0xf 表示 10 进制的 15, 也就是二进制的 1111
printf 能够格式化输出内容 , %x 表示按照十六进制输出 .
\n 表示换行符
4.4 按位异或 ^:
如果两个数字的二进制位相同 , 则结果为 0, 相异则结果为 1.
int a = 0x1 ;int b = 0x2 ;System . out . printf ( "%x\n" , a ^ b );
注意:如果两个数相同,则异或的结果为0
5、移位运算
移位运算符有三个: << >> >>> ,都是二元运算符,且都是按照二进制比特位来运算的。
5.1.左移 <<
最左侧位不要了 , 最右侧补 0.
int a = 0x10 ;System . out . printf ( "%x\n" , a << 1 );// 运行结果 ( 注意 , 是按十六进制打印的 )20
注意:向左移位时,丢弃的是符号位,因此正数左移可能会编程负数。
5.2 右移 >>
最右侧位不要了, 最左侧补符号位 ( 正数补 0, 负数补 1)
int a = 0x10 ;System . out . printf ( "%x\n" , a >> 1 );// 运行结果 ( 注意 , 是按十六进制打印的 )8int b = 0xffffffff0000 ;System . out . printf ( "%x\n" , b >> 1 );// 运行结果 ( 注意 , 是按十六进制打印的 )ffffffff8000
5.3无符号右移 >>>
最右侧位不要了, 最左侧补 0.
int a = 0xffffffffffffffff ;System . out . printf ( "%x\n" , a >>> 1 );// 运行结果 ( 注意 , 是按十六进制打印的 )7f ffffffffffff
注意 :
1. 左移 1 位 , 相当于原数字 * 2. 左移 N 位 , 相当于原数字 * 2 的 N 次方 .
2. 右移 1 位 , 相当于原数字 / 2. 右移 N 位 , 相当于原数字 / 2 的 N 次方 .
3. 由于计算机计算移位效率高于计算乘除 , 当某个代码正好乘除 2 的 N 次方的时候可以用移位运算代替 .
4. 移动负数位或者移位位数过大都没有意义 .
6、条件运算符
表达式 1 ? 表达式 2 : 表达式 3
当 表达式 1 的值为 true 时 , 整个表达式的值为 表达式 2 的值 ;
当 表达式 1 的值为 false 时 , 整个表达式的值为 表达式 3 的值 .
也是 Java 中唯一的一个 三目运算符 , 是条件判断语句的简化写法 .
注意:
1. 表达式 2 和表达式 3 的结果要是同类型的,除非能发生类型隐式类型转换
int a = 10 ;int b = 20 ;int c = a > b ? 1 : 2.0 ;
2. 表达式不能单独存在,其产生的结果必须要被使用。
int a = 10 ;int b = 20 ;a > b ? a : b ; // 报错: Error:(15, 14) java: 不是语句
二、泛型
1、概念
一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----- 来源《 Java 编程思想》对泛型的介绍。泛型是在JDK1.5 引入的新的语法,通俗讲,泛型: 就是适用于许多许多类型 。从代码上讲,就是对类型实现了参数化。
2、引进泛型
实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?
思路:
1. 我们以前学过的数组,只能存放指定类型的元素,例如: int[] array = new int[10]; String[] strs = new String[10];
2. 所有类的父类,默认为 Object 类。数组是否可以创建为 Object?
class MyArray
{
public Object[] array = new Object[10];
public Object getPos(int pos)
{
return this.array[pos];
}
public void setVal(int pos,int val)
{
this.array[pos] = val;
}
}
public class TestDemo
{
public static void main(String[] args)
{
MyArray myArray = new MyArray();
myArray.setVal(0,10);
myArray.setVal(1,"hello");//字符串也可以存放
String ret = myArray.getPos(1);//编译报错
System.out.println(ret);
}
}
问题:以上代码实现后发现
1. 任何类型数据都可以存放
2. 1号下标本身就是字符串,但是确编译报错。必须进行强制类型转换
虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类型。而不是同时持有这么多类型。所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。 此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。
2.1 语法
class 泛型类名称<类型形参列表>
{
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn>
{
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */
{
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1>
{
// 可以只使用部分类型参数
}
class MyArray<T>
{
public T[] array = (T[])new Object[10];//1
public T getPos(int pos)
{
return this.array[pos];
}
public void setVal(int pos,T val)
{
this.array[pos] = val;
}
}
public class TestDemo
{
public static void main(String[] args)
{
MyArray<Integer> myArray = new MyArray<>();//2
myArray.setVal(0,10);
myArray.setVal(1,12);
int ret = myArray.getPos(1);//3
System.out.println(ret);
myArray.setVal(2,"bit");//4
}
代码解释:
1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类
了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 ElementK 表示 KeyV 表示 ValueN 表示 NumberT 表示 TypeS, U, V 等等 - 第二、第三、第四个类型
2. 注释1处,不能new泛型类型的数组
意味着:
T [] ts = new T [ 5 ]; // 是不对的
课件当中的代码:T[] array = (T[])new Object[10];是否就足够好,答案是未必的。这块问题一会儿介绍。
3. 注释2处,类型后加入 <Integer> 指定当前类型
4. 注释3处,不需要进行强制类型转换
5. 注释4处,代码编译报错,此时因为在注释2处指定类当前的类型,此时在注释4处,编译器会在存放元素的时候帮助我们进行类型检查。
3、泛型类的使用
3.1 语法
泛型类 < 类型实参 > 变量名 ; // 定义一个泛型类引用new 泛型类 < 类型实参 > ( 构造方法实参 ); // 实例化一个泛型类对象
MyArray < Integer > list = new MyArray < Integer > ();
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
3.2 类型推导(Type Inference)
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArray < Integer > list = new MyArray <> (); // 可以推导出实例化需要的类型实参为 String
3.3. 裸类型(Raw Type)
裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型
MyArray list = new MyArray ();
注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制
下面的类型擦除部分,我们也会讲到编译器是如何使用裸类型的。
小结:
1. 泛型是将数据类型参数化,进行传递
2. 使用 <T> 表示当前类是一个泛型类。
3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
4、泛型如何编译的
4.1 擦除机制
那么,泛型到底是怎么编译的?这个问题,也是曾经的一个面试问题。泛型本质是一个非常难的语法,要理解好他还是需要一定的时间打磨。
通过命令:javap -c 查看字节码文件,所有的 T 都是 Object 。
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
有关泛型擦除机制的文章截介绍: https://zhuanlan.zhihu.com/p/51452375
提出问题:1 、那为什么, T[] ts = new T[5]; 是不对的,编译的时候,替换为 Object ,不是相当于: Object[] ts = new Object[5]吗?2 、类型擦除,一定是把 T 变成 Object 吗?
4.2 为什么不能实例化泛型类型数组
class MyArray<T>
{
public T[] array = (T[])new Object[10];
public T getPos(int pos)
{
return this.array[pos];
}
public void setVal(int pos,T val)
{
this.array[pos] = val;
}
public T[] getArray()
{
return array;
}
}
public static void main(String[] args)
{
MyArray<Integer> myArray1 = new MyArray<>();
Integer[] strings = myArray1.getArray();
}
/*Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; at TestDemo.main(TestDemo.java:31)
*/
原因:替换后的方法为:将Object[] 分配给 Integer[] 引用,程序报错。
public Object[] getArray()
{
return array;
}
数组和泛型之间的一个重要区别是它们如何强制执行类型检查。具体来说,数组在运行时存储和检查类型信息。然而,泛型在编译时检查类型错误。
通俗讲就是:返回的Object 数组里面,可能存放的是任何的数据类型,可能是 String ,可能是 Person ,运行的时候,直接转给Intefer 类型的数组,编译器认为是不安全的。
正确的方式:
class MyArray<T>
{
public T[] array;
public MyArray() { }
/**
* 通过反射创建,指定类型的数组
* @param clazz
* @param capacity
*/
public MyArray(Class<T> clazz, int capacity)
{
array = (T[])Array.newInstance(clazz, capacity);
}
public T getPos(int pos)
{
return this.array[pos];
}
public void setVal(int pos,T val)
{
this.array[pos] = val;
}
public T[] getArray()
{
return array;
}
}
public static void main(String[] args)
{
MyArray<Integer> myArray1 = new MyArray<>(Integer.class,10);
Integer[] integers = myArray1.getArray();
}
5、泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
5.1 语法
class 泛型类名称 < 类型形参 extends 类型边界 >{...}
public class MyArray<E extends Number>
{
...
}
只接受 Number 的子类型作为 E 的类型实参
MyArray < Integer > l1 ; // 正常,因为 Integer 是 Number 的子类型MyArray < String > l2 ; // 编译错误,因为 String 不是 Number 的子类型
error: type argument String is not within bounds of type-variable EMyArrayList<String> l2;^where E is a type-variable:E extends Number declared in class MyArrayList
了解: 没有指定类型边界 E ,可以视为 E extends Object
5.2 复杂示例
public class MyArray<E extends Comparable<E>>
{
...
}
E必须是实现了 Comparable 接口的
6、泛型方法
6.1 定义语法
方法限定符 < 类型形参列表 > 返回值类型 方法名称 ( 形参列表 ) { ... }
public class Util
{
//静态的泛型方法 需要在static后用<>声明泛型类型参数
public static <E> void swap(E[] array, int i, int j)
{
E t = array[i];
array[i] = array[j];
array[j] = t;
}
}
6.2 使用示例-可以类型推导
Integer [] a = { ... };swap ( a , 0 , 9 );String [] b = { ... };swap ( b , 0 , 9 );
6.3 使用示例-不使用类型推导
Integer [] a = { ... };Util . < Integer > swap ( a , 0 , 9 );String [] b = { ... };Util . < String > swap ( b , 0 , 9 );
7、通配符
? 用于在泛型的使用,即为通配符
7.1通配符解决什么问题
通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Student 是 Person 的子类,那么 List<Student> 也应该是 List<Person> 的子类。但是泛型是不支持这样的父子类关系的。
泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围.
package www.bit.java.test;
class Message<T>
{
private T message ;
public T getMessage()
{
return message;
}
public void setMessage(T message)
{
this.message = message;
}
}
public class TestDemo
{
public static void main(String[] args)
{
Message<String> message = new Message() ;
message.setMessage("比特就业课欢迎您"); fun(message);
}
public static void fun(Message<String> temp)
{
System.out.println(temp.getMessage());
}
}
以上程序会带来新的问题,如果现在泛型的类型设置的不是String ,而是 Integer.
public class TestDemo
{
public static void main(String[] args)
{
Message<Integer> message = new Message() ;
message.setMessage(99);
fun(message); // 出现错误,只能接收String
}
public static void fun(Message<String> temp)
{
System.out.println(temp.getMessage());
}
}
我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符"?" 来处理
范例:使用通配符
public class TestDemo
{
public static void main(String[] args)
{
Message<Integer> message = new Message() ;
message.setMessage(55); fun(message);
}// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<?> temp)
{
//temp.setMessage(100); 无法修改!
System.out.println(temp.getMessage());
}
}
在"?"的基础上又产生了两个子通配符:
? extends 类:设置泛型上限
? super 类:设置泛型下限
7.2 通配符上界
<? extends 上界 ><? extends Number > // 可以传入的实参类型是 Number 或者 Number 的子类
class Food
{
}
class Fruit extends Food
{
}
class Apple extends Fruit
{
}
class Banana extends Fruit
{
}
class Message<T>
{
// 设置泛型上限
private T message ;
public T getMessage()
{
return message;
}
public void setMessage(T message)
{
this.message = message;
}
}
public class TestDemo
{
public static void main(String[] args)
{
Message<Apple> message = new Message<>() ;
message.setMessage(new Apple());
fun(message);
Message<Banana> message2 = new Message<>() ;
message2.setMessage(new Banana());
fun(message2);
}// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<? extends Fruit> temp)
{
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
System.out.println(temp.getMessage());
}
}
此时无法在fun 函数中对 temp 进行添加元素,因为 temp 接收的是 Fruit 和他的子类,此时存储的元素应该是哪个子类无法确定。所以添加会报错!但是可以获取元素。
public static void fun(Message<? extends Fruit> temp)
{
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
Fruit b = temp.getMessage();
System.out.println(b);
}
通配符的上界,不能进行写入数据,只能进行读取数据。
7.3 通配符下界
<? super 下界 ><? super Integer > // 代表 可以传入的实参的类型是 Integer 或者 Integer 的父类类型
class Food
{
}
class Fruit extends Food
{
}
class Apple extends Fruit
{
}
class Message<T>
{
private T message ;
public T getMessage()
{
return message;
}
public void setMessage(T message)
{
this.message = message;
}
}
public class TestDemo
{
public static void main(String[] args)
{
Message<Fruit> message = new Message<>() ;
message.setMessage(new Fruit()); fun(message);
Message<Food> message2 = new Message<>() ;
message2.setMessage(new Food());
fun(message2);
}//temp 接收Fruit及其子类的一个Message
public static void fun(Message<? super Fruit> temp)
{
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
temp.setMessage(new Apple());//这个是Fruit的子类
temp.setMessage(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类
System.out.println(temp.getMessage());//只能直接输出
}
}
通配符的下界,不能进行读取数据,只能写入数据。
8、包装类
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java 给每个基本类型都对应了一个包装类型。
8.1 基本数据类型和对应的包装类
除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写
8.2 装箱和拆箱
int i = 10 ;// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中Integer ii = Integer . valueOf ( i );Integer ij = new Integer ( i );// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中int j = ii . intValue ();
8.3 自动装箱和自动拆箱
可以看到在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制。
int i = 10 ;Integer ii = i ; // 自动装箱Integer ij = ( Integer ) i ; // 自动装箱int j = ii ; // 自动拆箱int k = ( int ) ii ; // 自动拆箱
下列代码输出什么,为什么?
public static void main(String[] args)
{
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
}
三、List的介绍
1、什么是List
在集合框架中,List 是一个接口,继承自 Collection 。
Collection也是一个接口,该接口中规范了后序容器中常用的一些方法,具体如下所示:
Iterable也是一个接口,表示实现该接口的类是可以逐个元素进行遍历的,具体如下:
List的官方文档List (Java Platform SE 8 ) (oracle.com)https://docs.oracle.com/javase/8/docs/api/java/util/List.html
站在数据结构的角度来看,List 就是一个线性表,即 n 个具有相同类型元素的有限序列,在该序列上可以执行增删 改查以及变量等操作 。
【 面试题 】 Collection 中有那些方法?
2、常见接口介绍
List中提供了好的方法,具体如下:
虽然方法比较多,但是常用方法如下:
3、List的使用
注意:List 是个接口,并不能直接用来实例化 。
如果要使用,必须去实例化List 的实现类。在集合框架中, ArrayList 和 LinkedList 都实现了 List 接口 。
本文含有隐藏内容,请 开通VIP 后查看