Java基础
1.基本数据类型
1.1 8种基本类型
1.2 包装类型
将简单类型包装为类,含有以下用途
- 提供类的对象操作,如类型转换,进制转换等
- 集合不允许存放基本数据类型,使用包装类
- 包装类含有基本类型的相关属性,如最大值,最小值等
包装类都为final不可继承
1.3 装箱与拆箱
Integer x = 2; // 装箱 调用了 Integer.valueOf(2)
int y = x; // 拆箱 调用了 X.intValue()
new Integer(123) 和 Integer.valueOf(123) 区别
new Interger 新建了一个对象
.valueOf()会调用缓存池的对象,多次调用会取得同一对象的引用.
包装类型内存使用 private static class IntegerCache,声明一个内部使用的缓存池
如Integer中有个静态内部类IntegerCache,里面有个cache[],也就是Integer常量池,常量池的大小为一个字节(-128~127)
为啥把缓存设置为[-128,127]区间?性能和资源之间的权衡。 在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax= 来指定这个缓冲池的大小。
基本类型对应的缓冲池如下:
boolean values: true and false
all byte values
short values: between -128 and 127
int values: between -128 and 127
char: in the range \u0000 to \u007F
1.4 String
String 被声明为final,不可继承,线程安全
intern() 方法:
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;
否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。运行效率
StringBuilder > StringBuffer > String
但是String str = “hello”+ “world”的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高使用总结
少量数据: String
单线程操作,字符缓冲区大量数据: Stringbuilder
多线程操作,字符缓冲区大量数据: StringBuffer
4. 线程安全
String:对象定义后不可变,线程安全。
StringBuffer:是线程安全的(对调用方法加入同步锁),执行效率较慢,适用于多线程下操作字符串缓冲区大量数据。
StringBuilder:是线程不安全的,适用于单线程下操作字符串缓冲区大量数据。
5. 共同点
StringBuilder和StringBuffer有共同的父类AbstractStringBuilder(抽象类)。
StringBuilder和StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(…),只是StringBuffer会在方法上加上synchronized关键字进行同步。
String a = "hello2";
String b = "hello";
String c = b + 2;
System.out.println((a == c));
输出结果为:false。由于有符号引用的存在,所以 String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的
String a = "hello2";
final String b = "hello";
String c = b + 2;
System.out.println((a == c));
输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问
2.关键字
2.1 final
- 数据:: 声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能改变的常量
- 基本类型:, 数据不变
- 引用类型:, 是引用不变,但被引用的对象可以被改变
- 被修饰的数据在jvm常量池中
- 被修饰的类不能被继承,方法不能被重写
2.2 static
- 修饰类的话只能修饰内部类:, 非静态内部类编译完成之后会隐含一个引用,指向创建他的外围类,但是静态内部类没有. 所以,静态内部类的创建不需要依赖外围类,不能使用任何外围类的非static成员变量和方法
- 修饰成员变量和成员方法:: 被类中所有的对象共享,存放在JVM方法区(1.8为Metaspace),类加载后可直接调用,aclass.staticMethod(),aclass.staticValue
- 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
单例模式:
class Singleton{
private volatile static Singleton uniqueInstance;
private Sigleton(){};
public static Singleton getSingleton(){
if(uniqueInstance==null){
synchronized(Singleton.class){
if(uniqueInstance==null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
2.3 super关键字
用于子类从父类访问变量和方法
class Animal{
String color = "white";
}
class Dog extends Animal{
String color = "black";
void printColor(){
System.out.println(color); //打印狗的color
System.out.println(super.color);//打印Animal 的color
}
}
3.面向对象
3.1 三大特性
- 封装: 指把对象的属性封装在对象内部,不允许外部对象直接访问对象的内部信息
- 把对象属性私有化,对每个属性提供getter,setter方法
- 如果有带参的构造函数,需要再写一个不带参的构造函数
- 建议重写toString方法
- 内部细节对外部调用透明,外部调用无需修改或者关心内部实现
- 继承: 是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的属性或新的方法,可以使用父类的功能,不能选择性继承.
通过继承,可以提高代码的复用率,提高开发效率.
- 子类拥有父类的属性和方法,但是私有方法和属性无法访问,只能拥有.
- 子类可以拥有自己的方法,在父类的基础上拓展.
- 可以用自己的方法实现父类的方法?
- 多态: 指一个对象拥有多种状态. 具体表现为父类的引用指向子类的实例.
多态的特点:
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
public void eat(){ System.out.println("eat fish");}
}
class Dog extends Animal{
public void eat(){System.out.println("eat bones");}
}
class DuoTaiDemo{
public static void main(String[] args){
function(new Cat());
function(new Dog());
Animal a = new Cat();
a.eat();
Cat c = (Cat)a;
c.eat();
}
public static void function(Animal a){
a.eat();
if(a instanceof Cat){Cat c = (Cat)a;c.catchMouse();}
else if(a instanceof Dog){Dog c = (Dog)a;c.kanJia();}
}
}
3.2 重载和重写
重载: 发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同.比如多个构造器.
重写: 发生在运行期,子类对父类允许访问的方法进行重新编写.
- 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
- 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
- 构造方法无法被重写
public class Hero {
public String name() {
return "超级英雄";
}
}
public class SuperMan extends Hero{
@Override
public String name() {
return "超人";
}
public Hero hero() {
return new Hero();
}
}
public class SuperSuperMan extends SuperMan {
public String name() {
return "超级超级英雄";
}
@Override
public SuperMan hero() {
return new SuperMan();
}
}
3.3 泛型
泛型提供了编译时类型安全检测机制. 本质是参数化类型,也就是说操作的数据类型被指定为一个参数.
类型擦除: 在编译期间,所有的泛型信息都会被擦掉.
- 泛型方法
public static <E> void printArray(E() inputArray){
for(E element: inputArray){
System.out.println("%s",element);
}
} //传入不同类型数组: Interger,Double 和 Charcter
// 打印不同数组
- 泛型类
public class Generic<T>{
private T key;
public Generic(T key){
this.key = key;
}
public T getKey(){return key;}
}
Generic<Integer> genericInteger = new Generic<Integer>(123456);
- 泛型接口
public interface Generic<T>{ public T method();}
//实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
Copy to clipboardErrorCopied
//实现泛型接口,指定类型:
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "hello";
}
}
常用的通配符为: T,E,K,V,?
- ? 表示不确定的 java 类型
- T (type) 表示具体的一个 java 类型
- K V (key value) 分别代表 java 键值中的 Key Value
- E (element) 代表 Element
3.4 接口和抽象类
- 接口
- 接口是抽象类的延伸
- 从Java8开始,可以拥有默认的方法实现
- 接口的字段+方法默认都是public的,且不允许定义为private或protected
- 接口的字段默认都是static 和 final的
public class Main {
public static void main(String[] args) {
Person p = new Student("Xiao Ming");
p.run();
}
}
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
//Xiao ming run
- 抽象类
- 如果一个类中包含抽象方法,那么这个类必须声明为抽象类。
- 抽象类和抽象方法都使用 abstract 关键字进行声明
抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。
比较
- 抽象类的目的是实现代码复用,一种模板设计的方法,可以让这些类都派生于一个抽象类。
- 接口的设计目的是对类的行为进行约束,提供一种机制,使不同的类可以继承相同的功能。
- 接口的字段只能是static 和final,抽象类的字段没有这些要求。
- 接口的成员只能是public,抽象类可以有不同的访问权限。
3.5 反射
- JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息。
- 通过Class实例获取 class信息的方法成为反射。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
- 当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
- 类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。
- 也可以使用 Class.forName(“com.mysql.jdbc.Driver”) 这种方式来控制类的加载,该方法会返回一个 Class 对象。
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:
- Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
- Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
- Constructor :可以用 Constructor 的 newInstance() 创建新的对象。
通过反射创建对象
Class clazz = Class.forName("reflection.Person");//获取 Person 类的 Class 对象
Person p = (Person)class.newInstance();//使用.newInstane 方法创建对象
Constructor c = class.getDeclaredConstructor(String.class,String.class,int.class);//获取构造方法并创建对象
Person p1 = (Person)c.newInstance("李四","男","20");//创建对象并设置属性
4.Java8新特性
4.1 streams(流)
- Fliter
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。
stringList
.stream()
.filter((s)->s.startsWith("a"))
.forEach(System.out::println);
- Sorted
stringList
.stream()
.sorted()
.filter((s)->s.startsWith("a"))
.forEach(System.out::println);
需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
- Map
中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
下面的示例展示了将字符串转换为大写字符串。你也可以通过map来将对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
stringList
.stream()
.map(String::toUpperCase)
.sorted((a,b)->b.compareTo(a))
.forEach(System.out::println);
- Match
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 最终操作 ,并返回一个 boolean 类型的值。
// 测试 Match (匹配)操作
boolean anyStartsWithA =
stringList
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringList
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringList
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
- Count
计数是一个 最终操作,返回Stream中元素的个数,返回值类型是 long。
//测试 Count (计数)操作
long startsWithB =
stringList
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
- Reduce(规约)
这是一个 最终操作 ,允许通过指定的函数来将stream中的多个元素规约为一个元素,规约后的结果是通过Optional 接口表示的:
//测试 Reduce (规约)操作
Optional<String> reduced =
stringList
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
译者注: 这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于Integer sum = integers.reduce(0, (a, b) -> a+b);也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);