第十三到十七章分析阅读
十三、字符串
1 知识
String对象是不可变的。String对象方法等之间的传递实际上是引用的一个拷贝。而该引用所指向的对象一直待在单一的物理位置上,从未动过。
String对象具有只读特性,所以指向它的任何引用都不可能改变它的值,也不会对其他的引用有什么影响。
在使用字符串连接时如果拿不准用哪种方式,可以用javap来分析程序代码。查看java代码是如何工作的可以用javap反编译代码:
javap -c Demo //(类名) -c表示将生成JVM字节码
重载“+” 与StringBuilder
通过javap反编译可看到,在使用重载“+”时,编译器或自动帮我们创建一个StringBuilder对象来构造连接出最终的String对象。但是要注意的时,在循环体里如果还是用重载“+”来连接String对象的话,编译器自动创建的StringBuilder是在循环体内产生的,这意味着每一次循环都会创建一个新的StringBuilder对象。因此,在连接循环体内的String对象时,要自行创建StringBuilder对象在循环体外append()拼接,并且在使用append()方法连接时禁用append(a+ ":" +c)这种投机取巧的方式,否则编译器又会自动创建一个StringBuilder对象来连接内部字符串操作;如果是简单的字符串连接(特别是不是循环体的连接的话),可以信任编译器来处理。
StringBuilder是线程不安全的,但也因此效率比StringBuffer快一点。
@Test
public void stringTest() {
StringBuffer sb = new StringBuffer();
sb.append("a").append("+").append("b").append("=").append("c").append(" ok");
//delete(int start, int end) 可以用于删除不想要的字符串连接。
sb.delete(sb.length()-2,sb.length());
System.out.println(sb);
}
无意识递归
public class InfiniteRecursion {
@Override
public String toString() {
//String"+"会自动类型转换为Sting,会想把InfiniteRecursion转化成String,这时候调用toString方法,又会到了原点,造成无意识递归,所以禁用this
//return "InfiniteRecursion{}"+this;
//正确的做法是用super.toString()
return "InfiniteRecursion{}"+super.toString();
}
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i =0;i<5;i++){
list.add(new InfiniteRecursion());
}
System.out.println(list);
}
}
String的基本方法:P321
@Test
public void stringTest1() {
String 啊 = "我是一个大头兵";
char c = 啊.charAt(6);
System.out.println(c);
String a = "i am iron man";
char c1 = a.charAt(6);
System.out.println(c1);
//char是字符形式
char[] b = new char[50];
a.getChars(0,a.length(),b,0);
System.out.println(b);
//byte 是字节形式
byte[] bytes = a.getBytes();
System.out.println(Arrays.toString(bytes));
//生成字符串的所以字符数组
char[] chars = a.toCharArray();
System.out.println(chars);
boolean we = a.contentEquals("i am iron man");
boolean b1 = a.contentEquals(new StringBuffer("i am iron man"));
System.out.println(we +" "+ b1);
//比较string是否相等
boolean regionMatches = a.regionMatches(3, "am iron man", 0, "am iron man".length());
System.out.println(regionMatches);
System.out.format("test%d",5);
}
Java intern() 方法
参考
Formatter 是个解释器
正则表达式
一般来说,正则表达式就是以某种方式来描述字符串。
java语言对反斜线\的处理与其它语言不同。
如: \d = java : \ \d .
\ \ \ \ :java用此表示一条普通的反斜线。
斜线是:/ : 左正右反 :\
正则表达式中有特殊意义的字符都要通过\ \ 双反斜线来转义。如+ :\ \ +
如果正则表达式不是只使用一次的话,非String对象的正则表达式具备更加的性能。如用于匹配手机号格式等的工具类。
匹配规则:
CharSequence
接口CharSequence是从CharBuffer、String、StringBuffer、StringBuilder类之中抽象出了字符序列的一般化定义。所以这些实现了CharSequence的类都可以用于接收CharSequence参数的方法的使用;多数正则表达式操作也都接收CharSequence类型的参数。
Pattern和Matcher
使用Pattern pattern = Pattern.compile("^[A-Z].*。$");编译正则表达式。
组(Groups)
组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为0表示整个表达式,组号为1表示被第一对括号括起来的组,以此类推。
//三个组 0:ABCD 1: BC 2:C
A(B(C))D
组的概念用得少,先不仔细了解。
例子:
@Test
public void matchTest() {
//通过String的方法如matches是应用正则表达式最简单的途径
boolean b = "-1234".matches("-?\d+");
System.out.println(b);
//330
//在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。 定义为私有静态类常量
// private static Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+");
// 说明:不要在方法体内定义:Pattern pattern = Pattern.compile(规则)
//首字母大写,句号结束。注意.都是特殊有特殊含义的,不需要转义,转义了就变成普通字符了
Pattern pattern = Pattern.compile("^[A-Z].。)
String s = pattern.matcher("SSSSW水电费个胜多负。").replaceAll("WWW");
System.out.println(s);
String[] split = pattern.split("SSSSW水电W费个wW胜多负W。");
System.out.println(Arrays.toString(split));
//限制分割字符串数量最多3个
String[] split1 = pattern.split("SSSSW水电W费个wW胜多负W。", 3);
System.out.println(Arrays.toString(split1));
pattern = Pattern.compile(",");
String target = "i want to say , you either die hero, or live long enough to see youself become a villain,";
StringBuffer sb = new StringBuffer();
Matcher matcher = pattern.matcher(target);
while (matcher.find()) {
//) 将当前匹配子串替换为指定字符串,并且将替换后的子串以及其之前到上次匹配子串之后的字符串段添加到一个StringBuffer对象里
//appendReplacement可以对匹配到的数据做不同类型的处理,通过方法提供不同的replacement即可。
matcher.appendReplacement(sb, returnRandom());
System.out.println(sb);
}
//将最后一次匹配工作后剩余的字符串添加到一个StringBuffer对象里。
System.out.println(matcher.appendTail(sb));
//将matcher对象重新设置到当前字符序列的起始位置
matcher.reset();
System.out.println( matcher.replaceAll(""));
//应用新的字符序列
matcher.reset("happy ending , ok?");
System.out.println( matcher.replaceAll(""));
}
private static Random random = new Random(47);
private String returnRandom() {
int i = random.nextInt(20);
return String.valueOf(i);
}
正则表达式可用于如日志搜索,如linux系统中的grep 命令。
如下,通过对每一行reset匹配对象,然后如果find为true,则通过group打印出值和start打印出位置。
Scanner
Scanner类大大减轻了扫描输入的工作负担。Scanner构造器可以接受任何类型的输入对象,包括File、String、InputStream、Readable等对象。所有的输入、分词以及翻译操作都隐藏在不同类型的next方法中。
例子:
/**
- 防火墙日志文件扫描
*/
private static String log =
"10.25.253.123@31/12/2018\n"+
"11.25.253.123@31/11/2018\n"+
"12.25.253.123@31/10/2018\n"+
"13.25.253.123@31/09/2018\n";
@Test
public void fireScannerTest(){
Scanner scanner = new Scanner(log);
String pattern = "(\d+[.]\d+[.]\d+[.]\d+)@(\d{2}/\d{2}/\d{4})";
while (scanner.hasNext(pattern)){
//scanner.next(pattern)如果不需要可以不返回值,但一定要调用,如果不调用scanner.next(pattern),那么匹配指针位置就会一直不动保持在第一位,在while循环模式下一直循序下去不结束
String next = scanner.next(pattern);
//返回匹配值
System.out.println(next);
MatchResult match = scanner.match();
//match.group(0)指整个匹配值,1是第一个括号的值;2是第二个括号的
System.out.println("group0="+match.group(0));
System.out.println("ip="+match.group(1)+"===date="+match.group(2));
}
}
2 疑问
javap反编译出来的程序代码是汇编语言么?怎么看?
StringBuilder、StringBuffer对象的除append()方法外的操作都有哪些用法?
String.contentEquals(CharSequence cs)方法中的CharSequence 是什么类型?
A:接口CharSequence是从CharBuffer、String、StringBuffer、StringBuilder类之中抽象出了字符序列的一般化定义。所以这些实现了CharSequence的类都可以用于接收CharSequence参数的方法的使用;多数正则表达式操作也都接收CharSequence类型的参数。
收集正则表达式中的一些生活常用语句,如邮箱、手机号等匹配规则。
正则表达式中有特殊意义的字符都有哪些?
Pattern(正则表达式的完整构造子列表)类的使用?
P342里的练习还未尝试
定界符?
A:就是 设定界限的 符号。 比如字符 a,就需要用单引号做定界符 'a'; 比如字符串 abc,就需要用双引号做定界符 "abc"。 就是 告诉计算机: 字符开始了a字符结束了。 字符串开始了abc字符串结束了。
在正则表达式中定界符就是(^.***$)
3 思想总结
总的来说一般字符串的操作就是一些普通常用的操作,只要熟练就行,如果需要字符串拼接的话看情况选用拼接方法。正则表达式的匹配一般是用在像手机号,电子邮箱这种的验证上,比较简单;而如果是用在日志搜索匹配解析的话就比较复杂,针对解析日志等文件需要Scanner来处理更方便灵活。
十四、类型信息
1 知识
RTTI:在运行时识别一个对象的类型。
多态:同一个行为具有多个不同表现形式或形态的能力。
运行时类型信息使得你可以在程序运行时发现和使用类型信息。将我们从智能在编译期执行面向类型的操作禁锢中解脱出来。
运行时识别对象和类的信息方式有二:
RTTI.RTTI假设我们在编译时已经知道了所以的类型。
反射机制。反射允许我们在运行时发现和使用类的信息。
面向对象编程的基本目的:让代码只操作对基类的引用。这样如果要添加新类扩展程序变不会影响原代码(多态)。我们希望大部分代码都用多态调用,尽可能少地了解对象的具体类型,使得代码更容易读、写和维护;设计更容易实现、理解和改变。(多态时,虽然我们在写代码时只让这个类或对象表现出其父类的类型,但是在运行时这个对象一直是他该有的具体类型,只不过是他不停的换着面具表示他是这一类,那一类,甚至是Object(God))
我们对抽象的基类用抽象类或者接口表示可以防止对实例化无意义的基类实例化。
Class对象
要理解RTTI在java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作由特殊对象——Class对象来完成,它包含了与类有关的信息。
Class对象就是用来创建类的所以的“常规”(程序要运行使用的)对象的。java使用Class对象来执行其RTTI,像转型这样的操作也是。每个类就是一个Class对象,经过虚拟机编译后就是个 类名.class文件,通过类加载器加载。
所以类都是在对其第一次使用时才动态加载到JVM中的。第一次使用指程序创建第一个对类的静态成员的引用时(构造器也是类的静态方法,因此在使用new创建类的新对象时也是对类的静态成员的引用),就会加载这个类。
如果尚未加载,默认的类加载器就会根据类名查找.class文件(也可能会在数据库中查找字节码)。
使用newInstance()创建的类,必须带有默认的构造器。
//不带默认构造器会报实例化异常
java.lang.InstantiationException: com.fcar.thinkjava.duotai.Sandwich
at java.lang.Class.newInstance(Class.java:364)
at com.fcar.guava.GuavaTest.classTest(GuavaTest.java:385)
生成Class对象的引用方式:
Class> name = Class.forName("com.fcar.thinkjava.duotai.Sandwich");
类初始化步骤:
加载。通过类加载器查找字节码创建Class对象。
链接。验证字节码,为静态域分配存储空间,如有需要将解析这个类创建的对其他类的所以引用。
初始化。如果有父类,先对父类初始化,执行静态初始化器(包括构造器)和静态初始化块(static 修饰的代码块或域)
Java初始化的原则是尽可能的“惰性”。
仅使用.class语法来获得对类的引用不会引发初始化。
编译器常量的读取不需要对类进行初始化。
如果是一个被static final 修饰的值(编译器常量),那么用类调用读取值不需要对类进行初始化(该类的static代码块什么的不会执行,因为没有初始化)。但是如果被static final 修饰的值 不是编译器常量(如值是通过random调用获得的,那么读取该值还是会执行初始化)。如果只是static修饰(没有final),则肯定要执行初始化。
泛化的Class引用
Class引用总是指向某个Class对象,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
通过使用泛型语法,可以让编译器强制执行额外的类型检查。(否则普通的类引用是可以被重新赋值的)
通配符 “?” 表示“任何事物”。
//表示获取的是Cycle的父类的类引用
Class Super Cycle> = Cycle.getSuperClass();
//表示获取的是Cycle的子类的类引用
Class extend Cycle> = someClass;
//转型方式有2(也叫向下转型):
Class cycleT = Cycle.class;
//1
Cycle c = cycleT.cast(a);
//2 直接强转,一般都这样
(Cycle)a;
RTTI形式包括:
传统的类型转换。如(Shape)cycle。
代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
关键字 instanceof 。用于判断是否特定类型的实例。
instanceof 只可与命名类型进行比较,而不能与Class对象作比较。注意:如果程序中编写了许多的instanceof 表达式,就说明设计存在瑕疵。
Class> name = Class.forName("com.fcar.thinkjava.duotai.Sandwich");
Object o1 = name.newInstance();
//name.isInstance等同于instanceof
System.out.println("isInstance="+name.isInstance(o1));
构造器就算一个工厂方法模式。
P364 工厂方法实例待实践。
instanceof 与Class的等价性
instanceof 与isInstance()保持了类型的概念,指的是你是这个类或者这个类的子类么?
而用==或equal()比较时不考虑继承,比较的是确切的类型是否相同。
反射:运行时类信息
参考
Class类与java.lang.reflect(Field,Method,Constructor)类库一起对反射的概念进行了支持。
通过反射,我们可以使用Constructor创建新的对象,用get()和set()方法(调用Method的方法)读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。还可以调用Class.getMethods(),getFields() ,getConstructors()返回表示方法、字段以及构造器的对象的数组。
RTTI与反射的真正区别只在于:对于RTTI,编译器在编译时打开和检查.class文件;而对于反射机制,编译器在运行时打开和检查.class文件(因为编译时不可获取);
反射在java中是用来支持其他特性的,例如对象序列化和JavaBean。
运行时才获取类信息的动机:
需要获取一个指向某个并不在你的程序空间中的对象的引用(如磁盘文件、网络连接中)。
希望提供在跨网络的远程平台上创建和运行对象的能力(远程方法调用RMI)。
反射测试类:
@Test
public void reflectTest() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class> clazz = Class.forName("com.fcar.thinkjava.type.ReflectTest");
System.out.println("===========================获取所有公用的构造方法==============================");
Constructor>[] constructors = clazz.getConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println( constructors[i]);
}
System.out.println("===========================获取所有的构造方法==============================");
Constructor>[] declaredConstructors = clazz.getDeclaredConstructors();
for (int i = 0; i < declaredConstructors.length; i++) {
System.out.println( declaredConstructors[i]);
}
System.out.println("=============================获取公有 & 有参的构造方法============================");
Constructor> constructor = clazz.getConstructor(String.class);
System.out.println(constructor);
System.out.println("=============================获取私有 & 有参 构造方法============================");
Constructor> declaredConstructor = clazz.getDeclaredConstructor(Integer.class);
System.out.println(declaredConstructor);
System.out.println("=============================获取公有 & 无参的构造方法============================");
Constructor> constructor1 = clazz.getConstructor(null);
System.out.println(constructor1);
System.out.println("=============================构建有参构造器对象============================");
Object o = constructor.newInstance("constructorTest");
System.out.println(o);
}
@Test
public void reflectFieldTest() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class> clazz = Class.forName("com.fcar.thinkjava.type.ReflectTest");
System.out.println("===========================获取所有公用的域==============================");
Field[] fields = clazz.getFields();
for (int i = 0; i < fields.length; i++) {
System.out.println( fields[i]);
}
System.out.println("===========================获取所有的域==============================");
Field[] declaredFields = clazz.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
System.out.println( declaredFields[i]);
}
System.out.println("=====获取公有字段并使用=====");
Object instance = clazz.getConstructor().newInstance();
Field publicField = clazz.getField("publicField");
publicField.set(instance,"publicTest");
//有个疑问?如果我们代码里没有这个类,那么又如何强转调用方法和域呢?
ReflectTest reflectTest = (ReflectTest)instance;
//获取字段名称
//直接get
Object publicField1 = publicField.get(instance);
System.out.println("publicField.get+++"+publicField1);
//通过多态获取
System.out.println( reflectTest.getPublicField());
GetSet getSet1 = (GetSet)instance;
System.out.println("=====通过多态+反射获取域值=====");
System.out.println(getSet1.getField(clazz,publicField,instance));
System.out.println("=====获取私有字段并使用=====");
Field privateField = clazz.getDeclaredField("privateField");
//设置私有域要暴利反射
//在获取私有属性的时候如果没有进行暴力反射,那么会抛出异常。
//java.lang.IllegalAccessException: Class com.fcar.guava.GuavaTest can not access a member of class com.fcar.thinkjava.type.ReflectTest with modifiers "private"
privateField.setAccessible(true);
System.out.println("====before====="+reflectTest.getPrivateField());
privateField.set(instance,"privateTest");
System.out.println( reflectTest.getPrivateField());
}
@Test
public void reflectMethodTest() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, InterruptedException {
Class> clazz = Class.forName("com.fcar.thinkjava.type.ReflectTest");
System.out.println("===========================获取所有公用的方法==============================");
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println( methods[i]);
}
//否则会走下去
Thread.sleep(2000);
System.out.println("===========================获取所有的方法==============================");
Method[] declaredMethods = clazz.getDeclaredMethods();
for (int i = 0; i < declaredMethods.length; i++) {
System.out.println( declaredMethods[i]);
}
Thread.sleep(5000);
System.err.println("======获取特定方法(带参)并使用=====");
Object instance = clazz.getConstructor().newInstance();
Method commonActionWithParam = clazz.getMethod("commonActionWithParam", String.class);
System.out.println(commonActionWithParam);
commonActionWithParam.invoke(instance,"METHODPARAM");
Thread.sleep(2000);
System.err.println("======获取特定方法(不带参)并使用=====");
Method commonAction = clazz.getMethod("commonAction");
System.out.println(commonAction);
commonAction.invoke(instance);
Thread.sleep(2000);
System.err.println("======获取私有方法(不带参)并使用=====");
//Method privateAction = clazz.getMethod("privateAction");
Method privateAction = clazz.getDeclaredMethod("privateAction");
//调用私有方法一定要设置权限
privateAction.setAccessible(true);
System.out.println(privateAction);
privateAction.invoke(instance);
}
@Test
public void reflectStaticTest() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, InterruptedException {
Class> clazz = Class.forName("com.fcar.thinkjava.type.ReflectTest");
System.out.println("===========================反射执行STATIC方法==============================");
Method staticAction = clazz.getMethod("staticAction");
staticAction.invoke(null);
}
@Test
public void reflectMainTest() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, InterruptedException {
Class> clazz = Class.forName("com.fcar.guava.GuavaTest");
System.out.println("===========================反射执行main==============================");
Method main = clazz.getMethod("main", String[].class);
//Object o = clazz.getConstructor().newInstance();
//main 是static方法不需要创建对象
main.invoke(null,(Object) new String[]{"A"});
}
public static void main(String[] args) {
System.out.println("I AM MAIN");
}
动态代理
参考文章1只说明jdk的
参考文章2包含cglib
参考文章3包含cglib
使用场景:
你希望跟踪目标对象中的方法的调用,或者希望度量这些调用的开销,这些代码并不希望将它们合并到应用的代码中,因此代理使得你可以很容易的添加或移除它们。
在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Spring的AOP(面向切面编程时)。
举个例子:为什么@Transactional 注解只有public权限的才能生效呢?
A:因为@Transactional注解使用时是通体Spring的AOP(面向切面编程)实现的,这时候就用到了动态代理,而动态代理时因为生成的动态代理类实际上是个实现了被代理类的接口的类,而接口中的方法默认都是public的,所以是不可能有其他访问权限的方法生效的,因为动态代理代理不到这个方法。
动态代理的好处是比较灵活,可以在运行的时候才切入改变类的方法,而不需要预先定义它。
JDK动态代理:
@Test
public void dynamicProxyTest() {
TeaMan teaMan = new WuYiShanTeaMan();
InvocationHandler guangZhou13Hang = new GuangZhou13Hang(teaMan);
TeaMan guangZhou13HangProxy =(TeaMan) Proxy.newProxyInstance(teaMan.getClass().getClassLoader(), teaMan.getClass().getInterfaces(), guangZhou13Hang);
guangZhou13HangProxy.sellTea(5000);
guangZhou13HangProxy.buyOpium(3000);
}
public class WuYiShanTeaMan implements TeaMan {
@Override
public void sellTea(Integer hearvy) {
System.out.println("WuYiShanTeaMan sellTea "+hearvy+" KG");
}
@Override
public void buyOpium(Integer hearvy) {
System.out.println("WuYiShanTeaMan buyOpium "+hearvy+" KG, what a shame");
}
}
//接口
public interface TeaMan {
public void sellTea(Integer hearvy);
public void buyOpium(Integer hearvy);
}
//实现InvocationHandler
public class GuangZhou13Hang implements InvocationHandler {
private TeaMan teaMan;
public GuangZhou13Hang(TeaMan teaMan) {
this.teaMan = teaMan;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("===================before====================");
Object invoke;
if (method.getName().equals("buyOpium")) {
System.out.println("官府收账款放行鸦片");
invoke = method.invoke(teaMan, args);
System.out.println("官老爷逛窑子");
} else {
invoke = method.invoke(teaMan, args);
}
System.out.println("===================after====================");
return invoke;
}
}
cglib动态代理:JDK的动态代理一定要继承一个接口,如果要基于POJO类的动态代理 ,那么可以用cglib。
@Test
public void cglibDynamicProxyTest() {
System.setProperty( DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"F:\mywork\fcarfi-starter");
AmericanBusinessMan businessMan = new AmericanBusinessMan();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(EastIndiaCompany.class);
enhancer.setCallback(businessMan);
EastIndiaCompany o = (EastIndiaCompany)enhancer.create();
o.buyTea(10000);
o.plantOpium(30000);
}
//代理要实现MethodInterceptor
public class AmericanBusinessMan implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("===================before Opium====================");
Object invoke;
if (method.getName().equals("plantOpium")) {
System.out.println("India MI FANS IN DANGER");
invoke = methodProxy.invokeSuper(o,objects);
System.out.println("india MI FANS GOODBYE");
} else {
invoke = methodProxy.invokeSuper(o, objects);
}
System.out.println("===================after Opium====================");
return invoke;
}
}
//被代理类
public class EastIndiaCompany {
public void buyTea(Integer hearvy) {
System.out.println("EastIndiaCompany buyTea "+hearvy+" KG");
}
public void plantOpium(Integer hearvy) {
System.out.println("EastIndiaCompany plantOpium "+hearvy+" AREA");
}
}
空对象
引入空对象后,我们可以假设所有的对象都是有效的,而不必浪费编程精力去检查null。
2 疑问
以下的报错实际上是因为我们用Class.forName()后并没有创建对象,这时候报错是因为对象还没有创建,getClass()要用实例去调用才能保证该class已经加装到虚拟机中了。
//成功
GuavaTest test = new GuavaTest();
Class instance1 = test.getClass();
Object o = instance1.newInstance();
//报错
Class> name2 = Class.forName("com.fcar.thinkjava.duotai.Sandwich");
Class instance2 = name.getClass();
Object o2 = instance2.newInstance();
java.lang.IllegalAccessException: Can not call newInstance() on the Class for java.lang.Class
at java.lang.Class.newInstance(Class.java:344)
at com.fcar.guava.GuavaTest.classTest(GuavaTest.java:390)
为什么1.7版本通过 Class.forName获取类的引用不会立即进行初始化了呢(编程思想里的会。)?
Class> initable3 = Class.forName("com.fcar.thinkjava.type.Initable");
Class引用总是指向某个Class对象,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。这句话怎么理解?
Member接口的作用?
在什么情况下我们喜欢用反射?(毕竟自己拼接方法来调用乏味费时)
有个疑问?如果我们代码里没有这个类,那么又如何强转调用方法和域呢?
Class> clazz = Class.forName("com.fcar.thinkjava.type.ReflectTest");
Object instance = clazz.getConstructor().newInstance();
Field publicField = clazz.getField("publicField");
publicField.set(instance,"publicTest");
//有个疑问?如果我们代码里没有这个类,那么又如何强转调用方法和域呢?
ReflectTest reflectTest = (ReflectTest)instance;
//获取字段名称
//System.out.println( publicField.getName());
System.out.println( reflectTest.getPublicField());
A:如果我们代码里没有这个类,那么我们可以约定一个基类接口,定义get/set域方法,让我们从远处,比如说数据库获取的类实现这个基类,这样我们就可以在获取域值时,只强转为基类接口类型,然后通过多态加反射来拼接get方法来获得域的值。
public class ReflectTest implements GetSet{
@Override
public Object getField(Class clazz, Field publicField, Object instance) throws InvocationTargetException, IllegalAccessException {
Method commonAction = null;
try {
commonAction = clazz.getMethod("get"+(publicField.getName().substring(0, 1).toUpperCase() + publicField.getName().substring(1)));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return commonAction.invoke(instance);
}
public interface GetSet {
public void setField(Field publicField, Object val);
public Object getField(Class clazz,Field publicField, Object instance) throws InvocationTargetException, IllegalAccessException;
}
//調用
GetSet getSet1 = (GetSet)instance;
System.out.println("=====通过多态+反射获取域值=====");
System.out.println(getSet1.getField(clazz,publicField,instance));
反射方法的调用是通过调用新的线程处理的么?否则怎么测试类会输出不一致?
多态与RTTI的区别和联系?面向对象编程语言的目的是让我们在凡是可以使用的地方都使用多态机制,只在必需的时候使用RTTI。这句话怎么理解?
A:我的理解是多态其实就是通过RTTI来实现的,只是多态是基于父类类型来调用子类的具体方法,可是多态是不能调用子类的扩展方法的,因为发现不到;但如果通过RTTI判断出其子类类型,便能强转为子类类型,也就可以调用子类相对于父类的扩展方法了。
现实代码中很少使用空对象?最多便是google的通过Optional判空,空对象用处不大?
3 思想总结
所有的类型修饰符修饰的方法和域、构造器都可以被反射访问到。但是final域在遭遇修改时是安全的。它不会发生任何修改。
RTTI允许通过匿名基类的引用来发现类型信息。
面向对象编程语言的目的是让我们在凡是可以使用的地方都使用多态机制,只在必需的时候使用RTTI。
如果使用多态时基类未包含我们想要的方法,那么我们可以使用RTTI,继承一个新类,添加自己需要的方法。
反射这一块与动态代理在项目框架中用得比较多,需要熟悉使用。
十五、泛型
1 知识
理解了边界所在,你才能成为程序高手。因为只有知道了某个技术不能做到什么,你才能更好地做到所能做的。
Java泛型的优点与局限:
一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类(可以是基类或接口)。如果要编写可以应用于多种类型的代码,可以使用泛型。
一个类,如果我们在需要说明类型的地方都使用基类或接口,这样确实能够具备更好的灵活性,但也会有一些性能损耗;并且即使使用了接口,对程序的约束还是太强,因为这要求写的代码必须使用特定的接口。而我们通过使用泛型,可以是代码能够应用于“某种不具体的类型(在用该类时传进来类型
泛型实现了参数化类型的概念。使代码可以应用于多种类型。
使用场景:用于创建容器类。
JAVA泛型的核心概念:告诉编译器想要使用什么类型,然后编译器帮你处理一切细节。
@Test
public void typeTest() {
Holder holder = new Holder<>("I","LOVE","U");
System.out.println(holder);
holder.setFirst("she");
holder.setSecond("is");
holder.setThird("mine");
System.out.println(holder);
Holder intHolder = new Holder<>(1,2,3);
System.out.println(intHolder);
}
//泛型类
public class Holder {
private T first;
private T second;
private T third;
public Holder(T first, T second, T third) {
this.first = first;
this.second = second;
this.third = third;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
public T getThird() {
return third;
}
public void setThird(T third) {
this.third = third;
}
@Override
public String toString() {
return "Holder{" +
"first=" + first +
", second=" + second +
", third=" + third +
'}';
}
}
元组类库
元组:它是将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中元素,但是不允许向其中存放新的对象。(也称数据传送对象、信使。不允许修改是因为属性值被final修饰)。
利用继承机制可以实现长度更长的元组。
@Test
public void tupleTest() {
Map map= new HashMap();
map.put("where","home");
ThreeTuple> threeTuple = new ThreeTuple<>("baobao",40,map);
System.out.println(threeTuple.toString());
System.out.println(threeTuple.third);
//threeTuple.first = "xiaobao";
}
//二维元组
public class TwoTuple {
public final A first;
public final B second;
public TwoTuple(A first, B second) {
this.first = first;
this.second = second;
}
@Override
public String toString() {
return "TwoTuple{" +
"first=" + first +
", second=" + second +
'}';
}
}
//三维元组继承自二维
public class ThreeTuple extends TwoTuple {
public final C third;
public ThreeTuple(A first, B second, C third) {
super(first, second);
this.third = third;
}
@Override
public String toString() {
return "ThreeTuple{" +
"third=" + third +
", first=" + first +
", second=" + second +
'}';
}
}
泛型接口
泛型接口就是对接口进行泛型,等实现类实现接口时传入想要的类型即可。
public interface Generator {
T getNext();
}
public class CoffeeGenerator implements Generator {
}
泛型方法
我们可以在类中包含参数化方法(参数化方法的意思就是使用泛型),而这个方法所在的类可以是泛型类,也可以不是。是否拥有泛型方法,与其所在的类是否泛型没有关系。
泛型方法使得该方法能够独立于类而产生变化。
基本指导规则:无论何时,只有你能做到,就应该尽量使用泛型方法。如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为更清楚明白。
使用泛型方法不要指明参数类型,因为编译器会进行类型参数推断。(像可以无数次重载的方法)。如果传入的是基本类型,那么自动包装机制会介入自动包装。
可变参数与泛型方法
泛型方法与可变参数列表能够很好地并存。
参考 Arrays.asList(); 方法
@Test
public void TypeTest1() {
List a = makeList("A");
System.out.println(a);
//Serializable[3]@750 类型不同则泛型会找出他们通用的接口或父类作为泛型容器类型
System.out.println(makeList(1,2,"3"));
System.out.println(makeList("A","B","C"));
System.out.println(makeList("I LOVE MORTY,AND I HOPE MORTY LOVE ME".split("")));
}
public static List makeList(T ... args){
List list = new ArrayList<>();
for (T t : args) {
list.add(t);
}
return list;
}
泛型还可以应用于内部类以及匿名内部类。
构建复杂模型
泛型的一个重要好处是能够简单而安全地创建复杂的模型。
泛型擦除
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
Java泛型是使用擦除来实现的。这意味着在使用泛型时,具体的类型信息都被擦除了,List
当我们希望代码能够跨多个类工作时,使用泛型才有所帮助。
@Test
public void TypeTest2() {
HasF hasF = new HasF();
Manipulator manipulator = new Manipulator<>(hasF);
manipulator.simulator();
}
//普通对象
public class HasF {
public void f(){
System.out.println("HasF f()");
}
}
//泛型类。边界 声明T 必须具有类型HasF或者是其子类,这时候我们便能使用obj.f();方法了。
//
public class Manipulator {
private T obj;
public Manipulator(T obj) {
this.obj = obj;
}
public void simulator(){
obj.f();
}
}
java泛型的缺点在于用擦除实现泛型:
擦除减少了泛型的泛化性,如果不使用擦除来实现,而是使用具体化,使类型参数保持为第一类实体,那么他将能够在类型参数上执行基于类型的语言操作和反射操作。
泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。如List
擦除的核心动机是为了“迁移的兼容性”,使得泛化的客户端可以用非泛化的类库来使用。
用擦除来实现java泛型的优缺点:
优点是兼容非泛化代码,使我们可以从容的把非泛化代码转变为泛化代码。
缺点是擦除实现的泛型参数类型信息丢失,不能用于显示地引用运行时类型的操作之中,如转型、instanceof操作和new表达式。
擦除和迁移兼容性意味着使用泛型并不是强制的。
P412对于泛型类型的反编译可以看到,两种写法是一样的。
泛型中的所有动作都发生在边界处——对传递进来的值进行额外的编译器检查,并插入对传递出去的值的转型。
擦除的补偿
new T()无法实现的原因:一是因为泛型的类型擦除;二是因为编译器不能验证T具有默认(无参)构造器。
java解决无法创建泛型实例( 无法 new T() )的方案是传递一个工厂对象,使用它来创建新的实例。最便利的工厂对象就是Class对象,使用类型标签(构造器使用 Class
P414 工厂的选用。
成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其转型。
//因为我们不能声明
T[] array = new T[sz];
//只能强转
T[] array = (T[])new Object[sz];
因为有了擦除,数组的运行时类型就只能是Object[].如果我们立即将其转型为T[],那么在编译器该数组的实际类型就将丢失,而编译器可能会错过某些潜在的错误检查。正因为这样,最后是在集合内部使用Object[],然后当你使用数组元素时,添加一个对T的转型。
边界
设置边界的目的是为了可以按照自己的边界类型来调用方法,而不是只能调用Objcet的方法。
java使用extends关键字来限制泛型的边界。
实例:
public class EpicBattle {
static void useSuperHearing(SuperHero hero){
hero.getPower().hearSubtleNoises();
}
static void useFind(SuperHero hero){
hero.getPower().hearSubtleNoises();
hero.getPower().trackBySmell();
}
public static void main(String[] args) {
DogBoy dogBoy = new DogBoy();
useSuperHearing(dogBoy);
System.out.println("===================");
useFind(dogBoy);
List extends SuperHearing> dogBoyes1;
// extends SuperHearing & SuperSmell> 之所以不能编译通过是因为&这个是用于定义泛型时写的,使用泛型时不能这样写。
//List extends SuperHearing & SuperSmell> dogBoyes2;
}
}
interface SuperPower{
}
interface XRayVision extends SuperPower{
void seeThroughWalls();
}
interface SuperHearing extends SuperPower{
void hearSubtleNoises();
}
interface SuperSmell extends SuperPower{
void trackBySmell();
}
class SuperHero< POWER extends SuperPower>{
POWER power;
public SuperHero(POWER power) {
this.power = power;
}
POWER getPower(){
return power;
}
}
class SuperSleuth extends SuperHero{
public SuperSleuth(POWER power) {
super(power);
}
void see(){
power.seeThroughWalls();
}
}
class CanineHero extends SuperHero{
public CanineHero(power power) {
super(power);
}
void hear(){
power.hearSubtleNoises();
}
void smell(){
power.trackBySmell();
}
}
class SuperHearSmell implements SuperSmell,SuperHearing{
@Override
public void hearSubtleNoises() {
System.out.println("hearSubtleNoises");
}
@Override
public void trackBySmell() {
System.out.println("trackBySmell");
}
}
class DogBoy extends CanineHero{
public DogBoy() {
//子类可以是默认构造器,只要调用了父类的构造器即可,表示能构造出父类来
super(new SuperHearSmell());
}
}
通配符
在创建多态数组时,该数组只能存放具体的子类型的数组的类型,如 Fruit[] f = new Apple[10]; 只能存放Apple及其子类的类型,否则虽然编译期可以通过,运行时却会报错。
通配符引用的是明确的类型,因此下面这个例子里( extends Fruit>)的通配符意味着该引用没有指定的具体类型。这种情况下该泛型容器设置不能添加Object类型的值。
使用泛型容器必须指定具体的泛型容器类型。
@Test
public void fruitTest() {
List extends Fruit> list = new ArrayList();
//add (capture extends com.fcar.thinkjava.Types.fruit.Fruit>)
//in List cannot be applied to (com.fcar.thinkjava.Types.fruit.Apple)
//list.add(new Apple());
//add (capture extends com.fcar.thinkjava.Types.fruit.Fruit>)
//in List cannot be applied to (com.fcar.thinkjava.Types.fruit.Fruit)
//list.add(new Fruit());
//list.add( new Object());
list.add(null);
}
extends Fruit> 只能确认get获得的值的有效边界为Fruit类型,不能确认set设置的值,因为这样意味着他可以是任何事物,比如Object也可以设置,因为它是最基本的父类,这样编译器就无法验证“任何事物”的类型安全性,因此无法set。
超类型通配符: super T> 或 super Apple> 通过超类型通配符使我们可以确定边界是Apple,该容器的存放的元素都是Apple及其子类,因为适配的最大父类类型就是Apple。
无界通配符:>
一般用于如声明这段代码是用java的泛型来编写,并不是要用原生类型编写;还有就是在处理多个泛型参数时,通过无界通配符来运行一个参数是任意类型的,而其他参数为特定类型,这种使用场景特别重要。
如Map
使用确切类型来替代通配符类型的好处是,可以用泛型参数来做更多的事,但是使用通配符使得你必须接受范围更宽的参数化类型作为参数。因此,必须逐个情况地权衡利弊,找到更适合你的需求的方法。
泛型问题
任何基本类型都不能作为类型参数,但可以使用基本类型的包装类来作为泛型类型参数。
注意:自动包装机制不能应用于数组。
一个类不能实现同一个泛型接口的两种变体(如一个类实现了该泛型接口又继承了实现了这个泛型接口的类),由于擦除的原因,这两个变体会成为相同的接口。(去掉泛型参数便可以了)
转型和警告
使用带有泛型类型参数的转型或instanceof不会有任何效果。(因为运行时是类型擦除的)。
正确的转型方式是通过泛型类来转型:
Listlist = List.class.cast(in.readObject());
自限定的类型
?
动态类型安全检查
通过Collections.checkedList()这些工具类方法使得List、Map、Set等这类集合可以安全地存入泛型类型的元素。
@Test
public void fruitTest() {
Listlist1 = new ArrayList ();
add(list1);//正常运行,只有把Orange从容器取出时才会报错。
Listapples = Collections.checkedList(new ArrayList (), Apple.class);
add(apples);//报错
}
异常
由于检查型异常的缘故,将不能编写出泛化的代码。
混型
混型:值混合多个类的能力,以产生一个可以表示混型中所有类型的类。混型使组装多个类变得简单易行。
一般就是多重继承,在java这边是实现多个接口,然后在创建域创建出接口对应的实现类,在调用中通过代理调用域中实现类的方法,将方法调用转发给恰当的对象。
潜在类型机制
java的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。
java想实现类似潜在类型机制则编译器会强制要求这些类实现某个接口,以便通过泛型方法< ? extends Perform>来实现这种效果。
对前置类型机制的补偿
反射。通过反射可以在实现或继承任何接口的情况下调用其方法。
将一个方法应用于序列。(能够实现在编译器类型检查)
用适配器仿真潜在类型机制。
2 疑问
p390 末端哨兵怎么实现的,看不明白?
无论何时,只有你能做到,就应该尽量使用泛型方法。这是对的么?首先有个效率问题吧,如非需要适应多种不同类型,没有必要这样处理吧?
Java泛型是使用擦除来实现的,怎么理解?是指都编程Object类型?
类型标签?指的是ClassL类型的属性标签么?
工厂对象?指的是用于创建工厂的东西,比如class、字符串?
什么叫显式的工厂?
p416,成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其转型。这句怎么理解?(数据泛型转型需重新看)
15.10通配符这块不知道其具体用处,工作中有用到吗?
p429例子待研究
15.16 潜在类型机制?Python和C++ 是支持潜在类型机制的语言实例。
泛型还需重新理解一遍,不够全面。
3 思想总结
泛型的最大用处便是在使用容器类集合时(如List、Map等)可以设置某种泛型版本的容器,而不会因为存取时向上转型为Object而需要向下强转。但这只是个方便的编译期检查且使用时减少了强转的代码量,实际上由于泛型是后面添加到java中的,导致泛型的实现为了兼容以前非泛型的代码而使用类型擦除来实现泛型,导致java无法在运行时获取真正的类型信息,无法实现更加泛化的代码。
十六、数组
1 知识
数组的基本认识:数组通过整型索引值访问元素,并且数组的尺寸不能改变。
数组为什么特殊
数组与其他种类的容器之间的区别有三方面:
效率。在java中,数组是效率最高的存储和随机访问对象引用序列的方式。(数组是个简单的线性序列)代价是数组对象的大小被固定(比如与ArrayList对比,因为ArrayList可以实现自动分配空间,这种弹性开销时其效率比数组低),且在其生命周期中不可改变。
类型。在泛型之前,数组可以创建某种具体类型通过编译期检查来防止插入错误类型,而其他容器不行。泛型后都可以了。
保存基本类型的能力。在泛型之前,数组可以持有基本类型,而其他容器不行。泛型后都可以了,因为有字段包装机制。
数组和ArrayList之间的相似性是有意设计的,这使得两者之间的切换比较容易。随着泛型和自动包装机制的出现,数组相比于容器的仅有优点便是效率,而一般情况下,容器比数组拥有更多的功能方法,且数组限制更多,所以一般优先使用容器。
数组标识符只是一个引用(数组是个对象),指向在堆中创建的一个真实对象,这个数组对象用以保存指向其他对象的引用。length方法(length是数组的大小,而不是实际保存的元素个数。)和“[]”语法是访问数组对象唯一的方式。
对象数组与基本类型数组的区别是对象数组保存的是引用(默认初始化为null),而基本类型数组直接保存基本类型的值(按各基本类型的值初始化,如布尔型是false,int是0)。
数组中构成矩阵的每个向量都可以具有任意的长度(这被称为粗糙数组)。
数组与泛型(待重新了解)
数组与泛型不能很好地结合,不能实例化具有参数化类型的数组,我们不能创建泛型数组。因为擦除会移除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。但是我们可以参数化数组本身的类型。
一般而言,泛型在类或方法的边界处很有效,而在类或方法的内部,擦除通常会使泛型变得不适用。
//作用十分有限,只能用同一个值填充各个位置,针对对象而言,就是复制一个引用进行填充。
int[] t = new int[5];
Arrays.fill(t,3);
System.out.println(Arrays.toString(t));
Arrays.fill(t,3,5,5);//填充下标3-4的值
System.out.println(Arrays.toString(t));
//复制数组
//注意复制对象数组时,复制的是对象的引用,而不是对象本身的拷贝,是浅复制。
//System.arraycopy(t,0,y,0,y.length);
//参数依次是:源数组,从源数组的什么位置开始复制的偏移量,目标数组,从目标数组的什么位置开始复制的偏移量,复制元素个数
System.arraycopy(t,0,y,0,y.length);
System.out.println(Arrays.toString(y));
数组的比较
//用来比较整个数组,比较数组元素个数和对应位置上的元素是否相等。注意比较的是元素的内容,不是位置。
Arrays.equals(y,t);
数组元素的比较
java有两种方式提供比较功能:
实现 java.lang.Comparable接口,重写compareTo()方法。
创建一个实现了Comparator接口的单独的类。传入Comparator比较对象。
Arrays.sort(u,Collections.reverseOrder());
针对已排序的数组,可以使用Arrays.binarySearch(t,5);查找元素的位置。如果查不到,返回负值,表示若要保持数组的排序状态次目标元素所应该插入的位置。负值的计算方式:-(插入点)-1
插入点指(以从小到大排序为例),第一个大于查找对象元素在数组中的位置,如果数组中所有的元素都小于要查找的对象,也就是说查找的值是最大的,那么插入点就是a.size().
@Test
public void arrayTest() {
int[] a = {1, 2, 3, 4, 5};
int[] b = a.clone();
int[] c = a;//两个引用指向同一个数组对象
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
System.out.println(a.equals(b));//false
System.out.println(a == b);//false
System.out.println(a.equals(c));//true
System.out.println(a == c);//true
int[][] aa = {{1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}};
//Arrays.deepToString(aa)用于多维数组展示
System.out.println(Arrays.deepToString(aa));
boolean[][][] flagArr = new boolean[3][3][3];
System.out.println(Arrays.deepToString(flagArr));
System.out.println(Arrays.deepToString(fillArray(2, 3, 4)));
int[] t = new int[5];
Arrays.fill(t, 3);
System.out.println(Arrays.toString(t));
Arrays.fill(t, 3, 5, 5);
System.out.println(Arrays.toString(t));
int[] y = new int[5];
//复制数组
System.arraycopy(t, 0, y, 0, y.length);
System.out.println(Arrays.toString(y));
System.out.println(Arrays.equals(y, t));
Arrays.sort(t);
Integer[] u = new Integer[5];
Arrays.fill(u, 6);
u[2] = 7;
u[4] = 1;
Arrays.sort(u, Collections.reverseOrder());
System.out.println(Arrays.toString(u));
System.out.println(Arrays.binarySearch(t, 5));
//如果使用Comparator排序某个对象数组(基本类型数组无法使用Comparator),在使用binarySearch()时必须提供同样的Comparator。
System.out.println(Arrays.binarySearch(u, 1, Collections.reverseOrder()));//4
//因为-1小于所以的值,对于颠倒排序来说,-1就是数据里面最大的,应该是-size-1 = -6
System.out.println(Arrays.binarySearch(u, -1, Collections.reverseOrder()));//-6
}
2 疑问
数组是个简单的线性序列,什么叫线性序列?为什么线性序列的元素访问非常快速?
16.6 忽略了创建测试数据这节。
为什么未排序数组上执行二分法查找结果是不可预知的?我测试结果是可知不变的,不过是错误的。
@Test
public void array1Test() {
int[] a = {1, 2, 3, 4, 5, 8, 1, 3, 2};
//Arrays.sort(a);//7
int i = Arrays.binarySearch(a, 3);
System.out.println(i);
}
3 思想总结
优先选择容器而不是数组,只有当性能成为问题且切换到数组对性能提高有所帮助时,才应该重构成数组。