Young_

V2

2022/07/05阅读:30主题:山吹

设计模式总结(一)

享元模式(Flyweight Pattern)

主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

intent:
运用共享技术有效地支持大量细粒度的对象。

主要解决:
在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用:
1、系统中有大量对象。
2、这些对象消耗大量内存。
3、这些对象的状态大部分可以外部化。
4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
5、系统不依赖于这些对象身份,这些对象是不可分辨的。

如何解决:
唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

关键代码:
HashMap 存储这些对象。

应用实例:
1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。

优点: 大大减少对象的创建,降低系统的内存,使效率提高。

缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。

注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。

示例demo:

  • 定义 FlyWeight 接口类, 并且实现了FlyWeight接口实体类 ImplFlyWeight ; 核心类 FlyWeightFactory 类
  • 有一个包含 ImplFlyWeight的hashMap , 每次请求时 ,都会创建特定的ImplFlyWeight 如果 有ImplFlyWeight,
  • 返回改对象,没有;就创建一个新对象并且存储在hashMap中 ; 并把该对象返回客户端;
 interface InterFlyWeight {
 void readInformation();
}

class ImplFlyWeight implements InterFlyWeight {
 String str;

 public ImplFlyWeight(String str) {
  this.str = str;
 }

 @Override
 public void readInformation() {

  System.out.println("Young-------------->" + "ImplFlyWeight : readInformation" + str);
 }

}

核心类,定义享元工厂类

class FlyWeightFactory {
 // FlyWeightFactory 有一个 InterFlyWeight 的 HashMap,其中键名为integer, 值为
 // InterFlyWeight
 private HashMap<Object, InterFlyWeight> map = new HashMap<>();

 public InterFlyWeight getInterFlyWeight(Object object) {
  InterFlyWeight flyWeight = map.get(object);

  if (flyWeight == null) {
   flyWeight = new ImplFlyWeight((String) object);
   map.put(object, flyWeight);
  }

  return flyWeight;

 }

 public int getflyWeightSize() {
  return map.size();
 }
}

为了测试方便定义,定义TestFlyWeightPattern测试类:

class TestFlyWeightPattern {
 FlyWeightFactory flyWeightFactory = new FlyWeightFactory();
 ArrayList<InterFlyWeight> interFlyweightList = new ArrayList<>();
 int flyWeightSize = 0;

 public TestFlyWeightPattern() {
  InterFlyWeight flyWeightAA = flyWeightFactory.getInterFlyWeight("AA");
  InterFlyWeight flyWeightBB = flyWeightFactory.getInterFlyWeight("BB");
  InterFlyWeight flyWeightCC = flyWeightFactory.getInterFlyWeight("CC");
  interFlyweightList.add(flyWeightAA);
  interFlyweightList.add(flyWeightBB);
  interFlyweightList.add(flyWeightCC);
 }

 public void readInformation() {
  for (InterFlyWeight interFlyWeight : interFlyweightList) {
   interFlyWeight.readInformation();
  }
 }

 public void getflyWeightSize() {
  flyWeightSize = flyWeightFactory.getflyWeightSize();
  System.out.println(flyWeightSize);
 }
}

调用如下:

private static void testInterFlyWeight() {
  TestFlyWeightPattern testFlyWeightPattern = new TestFlyWeightPattern();
  testFlyWeightPattern.readInformation();
  testFlyWeightPattern.getflyWeightSize();
 }

output:

Young-------------->ImplFlyWeight : readInformationAA
Young-------------->ImplFlyWeight : readInformationBB
Young-------------->ImplFlyWeight : readInformationCC
3

完整代码如下:

package com.testing.design.pattern;

import java.util.ArrayList;
import java.util.HashMap;

public class FlyWeightPattern {

 public static void main(String[] args) {
  testInterFlyWeight();

 }

 private static void testInterFlyWeight() {
  TestFlyWeightPattern testFlyWeightPattern = new TestFlyWeightPattern();
  testFlyWeightPattern.readInformation();
  testFlyWeightPattern.getflyWeightSize();
 }

}

class TestFlyWeightPattern {
 FlyWeightFactory flyWeightFactory = new FlyWeightFactory();
 ArrayList<InterFlyWeight> interFlyweightList = new ArrayList<>();
 int flyWeightSize = 0;

 public TestFlyWeightPattern() {
  InterFlyWeight flyWeightAA = flyWeightFactory.getInterFlyWeight("AA");
  InterFlyWeight flyWeightBB = flyWeightFactory.getInterFlyWeight("BB");
  InterFlyWeight flyWeightCC = flyWeightFactory.getInterFlyWeight("CC");
  interFlyweightList.add(flyWeightAA);
  interFlyweightList.add(flyWeightBB);
  interFlyweightList.add(flyWeightCC);
 }

 public void readInformation() {
  for (InterFlyWeight interFlyWeight : interFlyweightList) {
   interFlyWeight.readInformation();
  }
 }

 public void getflyWeightSize() {
  flyWeightSize = flyWeightFactory.getflyWeightSize();
  System.out.println(flyWeightSize);
 }
}

/*
 * 定义 FlyWeight 接口类, 并且实现了FlyWeight接口实体类 ImplFlyWeight ; 核心类 FlyWeightFactory 类
 * ,有一个包含 ImplFlyWeight的hashMap , 每次请求时 ,都会创建特定的ImplFlyWeight 如果 有ImplFlyWeight,
 * 返回改对象,没有;就创建一个新对象并且存储在hashMap中 ; 并把该对象返回客户端;
 * 
 */
interface InterFlyWeight {
 void readInformation();
}

class ImplFlyWeight implements InterFlyWeight {
 String str;

 public ImplFlyWeight(String str) {
  this.str = str;
 }

 @Override
 public void readInformation() {

  System.out.println("Young-------------->" + "ImplFlyWeight : readInformation" + str);
 }

}

// 定义享元工厂类

class FlyWeightFactory {
 // FlyWeightFactory 有一个 InterFlyWeight 的 HashMap,其中键名为integer, 值为
 // InterFlyWeight
 private HashMap<Object, InterFlyWeight> map = new HashMap<>();

 public InterFlyWeight getInterFlyWeight(Object object) {
  InterFlyWeight flyWeight = map.get(object);

  if (flyWeight == null) {
   flyWeight = new ImplFlyWeight((String) object);
   map.put(object, flyWeight);
  }

  return flyWeight;

 }

 public int getflyWeightSize() {
  return map.size();
 }
}

END

策略模式

策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

intent:

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

主要解决:
在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

何时使用:
一个系统有许多许多类,而区分它们的只是他们直接的行为。

如何解决:
将这些算法封装成一个一个的类,任意地替换。

关键代码: 实现同一个接口。

应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

使用场景:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

注意事项: 如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题

示例代码:

定义一个接口类操作 :

interface InterStrategy {
 int doOperate(int num1, int num2);
}

使用 OperationAddcOperationPlus 去实现它 :

class OperationAdd implements InterStrategy {

 @Override
 public int doOperate(int num1, int num2) {
  // TODO Auto-generated method stub
  return num1 + num2;
 }
}

class OperationPlus implements InterStrategy {

 @Override
 public int doOperate(int num1, int num2) {
  // TODO Auto-generated method stub
  return num1 - num2;
 }

}

核心类:

class Context {
    //定义 实现的interStrategy 接口作为 变量
 private InterStrategy interStrategy;
    // 将来 Context()构造方法可以由 具体InterStrateg的子类去实现它
 public Context(InterStrategy interStrategy) {
  this.interStrategy = interStrategy;
 }
    //具体的执行操作
 public int executeStrategy(int num1, int num2) {
  return interStrategy.doOperate(num1, num2);
 }
}

测试类如下 ;

private static void testInterStrategy() {

  Context context = new Context(new OperationAdd());

  int executeStrategyAdd = context.executeStrategy(10, 20);

  System.out.println(executeStrategyAdd);

  Context contextPlus = new Context(new OperationPlus());

  int executeStrategyPlus = contextPlus.executeStrategy(10, 20);

  System.out.println(executeStrategyPlus);

 }


outPut :

30
-10

总结:

总结:策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。

END

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

intent:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

主要解决:
一些方法通用,却在每一个子类都重新写了这一方法。

何时使用:
有一些通用的方法。

如何解决:
将这些通用算法抽象出来。

关键代码:
在抽象类实现,其他步骤在子类实现。

应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

缺点: 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

注意事项: 为防止恶意操作,一般模板方法都加上 final 关键词。

步骤一 创建抽象类 ,并将模板方法 定义为final修饰

// 以抽象类 演示 具体场景 ;
// Junit4 单元测试中 有 setUp() ,test() ,tearDown() ;方法 , 我们手工模拟以此为例 ;

abstract class AbstractJunit4 {
 abstract void setUp();

 abstract void test();

 abstract void tearDown();

 final void completeJunit() {
  setUp();
  test();
  tearDown();
 }
}

步骤二
创建上述抽象类的扩展类实体

class GameTest extends AbstractJunit4 {

 @Override
 void setUp() {
  System.out.println("游戏开始 。。。。。");

 }

 @Override
 void test() {
  System.out.println("游戏进行中 。。。。。");

 }

 @Override
 void tearDown() {
  System.out.println("游戏结束 。。。。。");

 }

}

class LifeCycle extends AbstractJunit4 {

 @Override
 void setUp() {
  System.out.println("Young-----------> LifeCycle: setUp()");

 }

 @Override
 void test() {

  System.out.println("Young-----------> LifeCycle: test()");
 }

 @Override
 void tearDown() {

  System.out.println("Young-----------> LifeCycle: tearDown()");
 }

}

步骤三
使用模板类的方法completeJunit(),来演示LifeCycle的定义

private static void test() {
  
     GameTest gameTest = new GameTest();
     gameTest.completeJunit();
     
     LifeCycle lifeCycle = new LifeCycle();
     lifeCycle.completeJunit();
 }

步骤四
output :

游戏开始 。。。。。
游戏进行中 。。。。。
游戏结束 。。。。。
Young-----------> LifeCycle: setUp()
Young-----------> LifeCycle: test()
Young-----------> LifeCycle: tearDown()

总结

模板方法模式通过把不变的行为搬移到超类, 去除了子类中的重复代码。子类实现算法的某些细节,有助于算法的扩展。通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合”开放-封闭原则”

但同时每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。

END

观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

intent:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:
一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:
使用面向对象技术,可以将这种依赖关系弱化。

关键代码:
在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

示例demo

  • 步骤 1 创建接口 Watcher 类。
interface Watcher {
 void readInforamtion(String str);

}

  • 步骤 2 创建 Watched 类。

interface Watched {
 void addWatcher(Watcher watcher);

 void removeWatcher(Watcher watcher);

 void notifyWatcher(String str);
}

  • 步骤 3 创建实体观察者类。
class ImplWatcherObserver implements Watcher {

 @Override
 public void readInforamtion(String str) {
  System.out.println("Young------------>ImplWatcherObserver : readInforamtion" + str);
 }

}
class ImplWatcherClient implements Watcher {

 @Override
 public void readInforamtion(String str) {
  System.out.println("Young------------>ImplWatcherClient : readInforamtion" + str);
 }

}
  • 步骤 4 使用 BuildWatched 和实体观察者对象。
class BuildWatched implements Watched {
 private List<Watcher> list = new ArrayList<Watcher>();

 @Override
 public void addWatcher(Watcher watcher) {
  list.add(watcher);
 }

 @Override
 public void removeWatcher(Watcher watcher) {
  list.remove(watcher);
 }

 @Override
 public void notifyWatcher(String str) {

  for (Watcher watcher : list) {
   watcher.readInforamtion(str);
  }

 }

}


  • 步骤 5 执行程序,输出结果:
private static void test() {

  Watcher implWatcherObserver = new ImplWatcherObserver();

  Watcher implWatcherClient = new ImplWatcherClient();

  BuildWatched buildWatched = new BuildWatched();
  buildWatched.addWatcher(implWatcherObserver);
  buildWatched.addWatcher(implWatcherClient);

  buildWatched.notifyWatcher("Watcher Test");

 }

outPut:

Young------------>ImplWatcherObserver : readInforamtionWatcher Test
Young------------>ImplWatcherClient : readInforamtionWatcher Test

END

迭代器模式

迭代器模式(Iterator Pattern)是 Java.Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

迭代器模式属于行为型模式。

  • intent:
    提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
  • 主要解决:不同的方式来遍历整个整合对象。
  • 何时使用:遍历一个聚合对象
  • 如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。
  • 关键代码:定义接口:hasNext, next。
  • 应用实例:JAVA 中的 iterator。
  • 优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类迭代器类都很方便,无须修改原有代码。
  • 缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性
  • 使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。

注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

  • 步骤 1 创建接口:
Iterator.java
public interface Iterator {
   public boolean hasNext();
   public Object next();
}
Container.java
public interface Container {
   public Iterator getIterator();
}
  • 步骤 2 创建实现了 Container 接口的实体类。该类有实现了 Iterator 接口的内部类 NameIterator。
NameRepository.java
public class NameRepository implements Container {
   public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};
 
   @Override
   public Iterator getIterator() {
      return new NameIterator();
   }
 
   private class NameIterator implements Iterator {
 
      int index;
      //判断是否有下一个对象(如果到头了就false
      @Override
      public boolean hasNext() {
         if(index < names.length){
            return true;
         }
         return false;
      }
      //下一个元素
      @Override
      public Object next() {
         if(this.hasNext()){
            return names[index++];
         }
         return null;
      }     
   }
}
  • 步骤 3 使用 NameRepository 来获取迭代器,并打印名字。
IteratorPatternDemo.java
public class IteratorPatternDemo {
   
   public static void main(String[] args) {
      NameRepository namesRepository = new NameRepository();
 
      for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
         String name = (String)iter.next();
         System.out.println("Name : " + name);
      }  
   }
}
  • 步骤 4 执行程序,输出结果:
Name : Robert
Name : John
Name : Julie
Name : Lora

总结

迭代器本质是:要遍历的对象,即聚集对象,迭代器对象,用于对聚集对象进行遍历访问;

责任链模式

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

intent:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

主要解决职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

关键代码Handler 里面聚合它自己,在 HanleRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。

缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。

使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。

注意事项:在 JAVA WEB 中遇到很多应用。

  • 步骤 1

创建抽象的ChainResponsibilit类。

abstract class ChainResponsibilit {
 // 为了拿到下一个 责任链对象
 protected ChainResponsibilit chResponsibilit;

 abstract void handleRequest();

 ChainResponsibilit getChResponsibilit() {
  return chResponsibilit;

 }

 void setChResponsibilit(ChainResponsibilit chResponsibilit) {
  this.chResponsibilit = chResponsibilit;
 }

}

  • 步骤 2
    创建扩展了该ChainResponsibilit的实体类。
class conCreateChResponsibilit extends ChainResponsibilit {
 /**
  * 
  * 判断 是否有后继请求责任对象
  * 如果有, 就转发请求给后继的责任对象
  * 如果没有, 就继续处理该请求对象
  */
 protected String chResponsibilitName;

 public conCreateChResponsibilit(String chResponsibilitName) {
  this.chResponsibilitName = chResponsibilitName;
 }

 @Override
 void handleRequest() {
//  如果 getChResponsibilit()为空 , 就继续执行下一个方法
  if (getChResponsibilit() == null){
   System.out.println("Young----------------> conCreateChResponsibilit End .........");
  }else{
   //得到下一个 ,在执行 handleRequest
   System.out.println("Young----------------> conCreateChResponsibilit PASS ........." + chResponsibilitName);
   
   getChResponsibilit().handleRequest();
  }
 }

}
  • 步骤 3

创建不同类型的conCreateChResponsibilit。赋予它们不同的执行级别,并在每个记录器中设置下一conCreateChResponsibilit对象。每个conCreateChResponsibilit中的下一个conCreateChResponsibilit代表的是链的一部分。

private static void buildConCreateChResponsibilitObject() {

   conCreateChResponsibilit responsibilitAA = new conCreateChResponsibilit("AA");
   conCreateChResponsibilit responsibilitBB = new conCreateChResponsibilit("BB");
   conCreateChResponsibilit responsibilitCC = new conCreateChResponsibilit("CC");
   conCreateChResponsibilit responsibilitDD = new conCreateChResponsibilit("DD");
   
   responsibilitAA.setChResponsibilit(responsibilitBB);
   responsibilitBB.setChResponsibilit(responsibilitCC);
   responsibilitCC.setChResponsibilit(responsibilitDD);
   
   responsibilitAA.handleRequest();
   
 }
  • 步骤 4 执行程序,输出结果:
Young----------------> conCreateChResponsibilit PASS .........AA
Young----------------> conCreateChResponsibilit PASS .........BB
Young----------------> conCreateChResponsibilit PASS .........CC
Young----------------> conCreateChResponsibilit End .........

总结
本质 :增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。

END

命令模式

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

intent
将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

主要解决
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

何时使用
在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者""行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

如何解决
通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。

关键代码
定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口

应用实例
struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。

优点 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。

缺点 : 使用命令模式可能会导致某些系统有过多的具体命令类。

使用场景:认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。

注意事项:系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。

  • 步骤 1 创建一个命令接口。
// 定义一个InterfaceCommand接口,就一个execute抽象方法
interface InterfaceCommand {
 void execute();
}
  • 步骤 2 创建一个请求类。
class Receiver {

 public void action1() {

  System.out.println("Young--------------> command : action1");
 }

 public void action2() {

  System.out.println("Young--------------> command : action2");
 }

}
  • 步骤 3 创建实现了 InterfaceCommand 接口的实体类。
// ImplInterfaceCommand实现InterfaceCommand类,
class ImplInterfaceCommand implements InterfaceCommand {

 private Receiver receiver;

 public ImplInterfaceCommand(Receiver receiver) {
  this.receiver = receiver;
 }

 @Override
 public void execute() {

  this.receiver.action1();
  this.receiver.action2();
 }

}

  • 步骤 4 创建命令调用类。
class Invoke {

 private InterfaceCommand interfaceCommand;

 public Invoke(InterfaceCommand interfaceCommand) {
  this.interfaceCommand = interfaceCommand;

 }

 public void action() {
  interfaceCommand.execute();
 }
}


  • 步骤 5 使用 testInterfaceCommand 来接受并执行命令。
// 命令模式
 private static void testInterfaceCommand() {

  ImplInterfaceCommand interfaceCommand = new ImplInterfaceCommand(new Receiver());
  Invoke invoke = new Invoke(interfaceCommand);
  invoke.action();

 }

步骤 6 执行程序,输出结果:

Young--------------> command : action1
Young--------------> command : action2

END

备忘录模式

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

intent
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

主要解决
所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

何时使用:
很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。

如何解决:
通过一个备忘录类专门存储对象状态。

关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。

应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。

优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。

注意事项:
1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。

迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。英文简写为: LoD.

  • 步骤 1 创建 Memento 类。
// Memento 作为临时存储空间 ;存储 Originator对象
class Memento {

 private String state;

 public Memento(String state) {
  this.state = state;
 }

 public void setState(String state) {
  this.state = state;
 }

 public String getState() {
  return state;
 }

}


  • 步骤 2 创建 Originator 类。
// 创建 Originator原始类
class Originator {

 private String state;

 public String getState() {
  return state;
 }

 public Originator(String state) {
  this.state = state;
 }

 public void setState(String state) {
  this.state = state;
 }

 //  使用 MementosaveStateMemento()方法 保存 state的值到 Memento对象中;
 public Memento MementosaveStateMemento() {
  return new Memento(state);
 }

 //将来 state 被修改后 ,使用此方法 恢复
 public void restoreGetMementoState(Memento memento) {
  state = memento.getState();
 }

}


  • 步骤 3 创建 hardDisk 类。
class HardDisk {
 private Memento memento;
 //  Memento对象传递 保存到 HardDisk对象中 ;
 public HardDisk(Memento memento) {
  this.memento = memento;
 }

 public void setMemento() {
  this.memento = memento;
 }
   
 // 具体 Originator 恢复值 会调用此方法
 public Memento getMemento() {
  return memento;
 }
}

  • 步骤 4 使用 testMementoPattern 测试对象。
private static void testMementoPattern() {
  // 初始化, 保存原始值 
  Originator originator = new Originator("Young");
  //打印 未被修改前  输出的值
  System.out.println("Young-------------> init before" + originator.getState());
  // 使用 MementosaveStateMemento()方法具体保存
  Memento mementosaveStateMemento = originator.MementosaveStateMemento();
  
  //把 Memento对象整个再次保存到 HardDisk中
  HardDisk hardDisk = new HardDisk(mementosaveStateMemento);
  // originator中 State 修改后  的值
  originator.setState("gounY");
  System.out.println("Young-------------> edit after" + originator.getState());

  // 恢复保存的值 ,---》hardDisk.getMemento()
  originator.restoreGetMementoState(hardDisk.getMemento());
  System.out.println("Young--------------> resore after" + originator.getState());

 }

  • 步骤 5 验证输出。
Young-------------> init before:  Young
Young-------------> edit after:  gounY
Young--------------> resore after:  Young

完整代码如下 :

package com.testing.design.pattern;

/**
 * 备忘录模式
 * 
 * @version 1.0
 */
public class MementoPattern {

 public static void main(String[] args) {
  testMementoPattern();

 }

 private static void testMementoPattern() {
  // 初始化, 保存原始值
  Originator originator = new Originator("Young");
  // 打印 未被修改前 输出的值
  System.out.println("Young-------------> init before:  " + originator.getState());
  // 使用 MementosaveStateMemento()方法具体保存
  Memento mementosaveStateMemento = originator.MementosaveStateMemento();

  // 把 Memento对象整个再次保存到 HardDisk中
  HardDisk hardDisk = new HardDisk(mementosaveStateMemento);
  // originator中 State 修改后 的值
  originator.setState("gounY");
  System.out.println("Young-------------> edit after:  " + originator.getState());

  // 恢复保存的值 ,---》hardDisk.getMemento()
  originator.restoreGetMementoState(hardDisk.getMemento());
  System.out.println("Young--------------> resore after:  " + originator.getState());

 }

}

// Memento 作为临时存储空间 ;存储 Originator对象
class Memento {

 private String state;

 public Memento(String state) {
  this.state = state;
 }

 public void setState(String state) {
  this.state = state;
 }

 public String getState() {
  return state;
 }

}

// 创建 Originator原始类
class Originator {

 private String state;

 public String getState() {
  return state;
 }

 public Originator(String state) {
  this.state = state;
 }

 public void setState(String state) {
  this.state = state;
 }

 // 使用 MementosaveStateMemento()方法 保存 state的值到 Memento对象中;
 public Memento MementosaveStateMemento() {
  return new Memento(state);
 }

 // 将来 state 被修改后 ,使用此方法 恢复
 public void restoreGetMementoState(Memento memento) {
  state = memento.getState();
 }

}

// 增加 hardDisk类,

class HardDisk {
 private Memento memento;

 // Memento对象传递 保存到 HardDisk对象中 ;
 public HardDisk(Memento memento) {
  this.memento = memento;
 }

 public void setMemento() {
  this.memento = memento;
 }

 // 具体 Originator 恢复值 会调用此方法
 public Memento getMemento() {
  return memento;
 }
}


END

状态模式

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

intent: 允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

主要解决: 对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

何时使用
代码中包含大量与对象状态有关的条件语句。

如何解决:将各种具体的状态类抽象出来。

关键代码
通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。

应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超常状态。 2、曾侯乙编钟中,'钟是抽象接口','钟A'等是具体状态,'曾侯乙编钟'是具体环境(Context)。

优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。

注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。


  • 示例 demo :

创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。

StatePatternDemo,演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。

  • 步骤 1 创建一个接口。
State.java
public interface State {
   public void doAction(Context context);
}
  • 步骤 2 创建实现接口的实体类。
StartState.java
public class StartState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in start state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Start State";
   }
}
StopState.java
public class StopState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in stop state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Stop State";
   }
}
  • 步骤 3 创建 Context 类。
Context.java
public class Context {
   private State state;
 
   public Context(){
      state = null;
   }
 
   public void setState(State state){
      this.state = state;     
   }
 
   public State getState(){
      return state;
   }
}
  • 步骤 4 使用 Context 来查看当状态 State 改变时的行为变化。
StatePatternDemo.java
public class StatePatternDemo {
   public static void main(String[] args) {
      Context context = new Context();
 
      StartState startState = new StartState();
      startState.doAction(context);
 
      System.out.println(context.getState().toString());
 
      StopState stopState = new StopState();
      stopState.doAction(context);
 
      System.out.println(context.getState().toString());
   }
}
  • 步骤 5 执行程序,输出结果:
Player is in start state
Start State
Player is in stop state
Stop State

访问者模式

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

intent: 主要将数据结构与数据操作分离。

主要解决稳定数据结构易变的操作耦合问题。

何时使用:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。

如何解决在被访问的类里面加一个对外提供接待访问者的接口。

关键代码:在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。

应用实例:您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。

优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。

缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。英文简写为: LoD.

使用场景: 1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。

  • 步骤 1 定义一个表示元素的接口。
interface Theme {

 void accpet(Visitor visitor);

 String getTheme();

}
  • 步骤 2 创建扩展了上述类的实体类。
class ImplTheme implements Theme {

 @Override
 public void accpet(Visitor visitor) {
  visitor.addVisitor(this);
 }

 @Override
 public String getTheme() {
  // TODO Auto-generated method stub
  return "Young";
 }

}

  • 步骤 3 定义一个表示访问者的接口
//核心类定义访问者的操作 ; 
interface Visitor {
 void addVisitor(Theme theme);

 void removeVisitor(Theme theme);
}

  • 步骤 4 创建实现了上述类的实体访问者。
//  Theme 使用实体访问者来执行相应的动作。
class ImplVisitor implements Visitor {

 @Override
 public void addVisitor(Theme theme) {
  System.out.println(theme.getTheme());
 }

 @Override
 public void removeVisitor(Theme theme) {
  System.out.println(theme.getTheme());

 }

}
  • 步骤 5 执行程序,输出结果:
private static void testVisitorPattern() {
  ImplVisitor implVisitor = new ImplVisitor();

  ImplTheme implTheme = new ImplTheme();

  implTheme.accpet(implVisitor);
 }

output :

Young

完整代码如下;

package com.testing.design.pattern;

/**
 * @author Young
 * @Time:2018年7月1日 下午11:43:42
 * @version 1.0
 */
public class VisitorPattern {

 public static void main(String[] args) {
  testVisitorPattern();
 }

 private static void testVisitorPattern() {
  ImplVisitor implVisitor = new ImplVisitor();

  ImplTheme implTheme = new ImplTheme();

  implTheme.accpet(implVisitor);
 }

}

//
interface Theme {

 void accpet(Visitor visitor);

 String getTheme();

}

// 核心类定义访问者的操作 ;
interface Visitor {
 void addVisitor(Theme theme);

 void removeVisitor(Theme theme);
}

// Theme 使用实体访问者来执行相应的动作。
class ImplVisitor implements Visitor {

 @Override
 public void addVisitor(Theme theme) {
  System.out.println(theme.getTheme());
 }

 @Override
 public void removeVisitor(Theme theme) {
  System.out.println(theme.getTheme());

 }

}

class ImplTheme implements Theme {

 @Override
 public void accpet(Visitor visitor) {
  visitor.addVisitor(this);
 }

 @Override
 public String getTheme() {
  // TODO Auto-generated method stub
  return "Young";
 }

}

END


###Java工程师成神之路(2018修订版)

基础篇

JVM

JVM内存结构

堆、栈、方法区、直接内存、堆和栈区别

Java内存模型

内存可见性、重排序、顺序一致性、volatile、锁、final

垃圾回收

内存分配策略、垃圾收集器(G1)、GC算法、GC参数、对象存活的判定

JVM参数及调优

Java对象模型

oop-klass、对象头

HotSpot

即时编译器、编译优化

类加载机制

classLoader、类加载过程、双亲委派(破坏双亲委派)、模块化(jboss modules、osgi、jigsaw)

虚拟机性能监控与故障处理工具

jps, jstack, jmap、jstat, jconsole, jinfo, jhat, javap, btrace、TProfiler

编译与反编译

javac 、javap 、jad 、CRF

Java基础知识

阅读源代码

String、Integer、Long、Enum、BigDecimal、ThreadLocal、ClassLoader & URLClassLoader、ArrayList & LinkedList、 HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap、HashSet & LinkedHashSet & TreeSet

Java中各种变量类型

熟悉Java String的使用,熟悉String的各种函数

JDK 6和JDK 7中substring的原理及区别、 replaceFirst、replaceAll、replace区别、 String对“+”的重载、 String.valueOf和Integer.toString的区别、 字符串的不可变性

自动拆装箱

Integer的缓存机制

熟悉Java中各种关键字

transient、instanceof、volatile、synchronized、final、static、const 原理及用法。

集合类

常用集合类的使用 ArrayList和LinkedList和Vector的区别 SynchronizedList和Vector的区别 HashMap、HashTable、ConcurrentHashMap区别 Java 8中stream相关用法 apache集合处理工具类的使用 不同版本的JDK中HashMap的实现的区别以及原因 枚举

枚举的用法、枚举与单例、Enum类

Java IO&Java NIO,并学会使用

bio、nio和aio的区别、三种IO的用法与原理、netty

Java反射与javassist

反射与工厂模式、 java.lang.reflect.*

Java序列化

什么是序列化与反序列化、为什么序列化 序列化底层原理 序列化与单例模式 protobuf 为什么说序列化并不安全

注解

元注解、自定义注解、Java中常用注解使用、注解与反射的结合

JMS

什么是Java消息服务、JMS消息传送模型

JMX

java.lang.management.、 javax.management.

泛型

泛型与继承 类型擦除 泛型中K T V E
object等的含义、泛型各种用法

单元测试

junit、mock、mockito、内存数据库(h2)

正则表达式

java.lang.util.regex.*

常用的Java工具库

commons.lang, commons.*... guava-libraries netty

什么是API&SPI

异常

异常类型、正确处理异常、自定义异常

时间处理

时区、时令、Java中时间API

编码方式

解决乱码问题、常用编码方式

语法糖

Java中语法糖原理、解语法糖

Java并发编程

什么是线程,与进程的区别

阅读源代码,并学会使用

Thread、Runnable、Callable、ReentrantLock、ReentrantReadWriteLock、Atomic*、Semaphore、CountDownLatch、、ConcurrentHashMap、Executors

线程池

自己设计线程池、submit() 和 execute()

线程安全

死锁、死锁如何排查、Java线程调度、线程安全和内存模型的关系

CAS、乐观锁与悲观锁、数据库相关锁机制、分布式锁、偏向锁、轻量级锁、重量级锁、monitor、锁优化、锁消除、锁粗化、自旋锁、可重入锁、阻塞锁、死锁

死锁

volatile

happens-before、编译器指令重排和CPU指令重

synchronized

synchronized是如何实现的? synchronized和lock之间关系 不使用synchronized如何实现一个线程安全的单例 sleep 和 wait

wait 和 notify

notify 和 notifyAll

ThreadLocal

写一个死锁的程序

写代码来解决生产者消费者问题

守护线程

守护线程和非守护线程的区别以及用法

二、 进阶篇

Java底层知识

字节码、class文件格式

CPU缓存,L1,L2,L3和伪共享

尾递归

位运算

用位运算实现加、减、乘、除、取余

设计模式

了解23种设计模式

会使用常用设计模式

单例、策略、工厂、适配器、责任链。

实现AOP

实现IOC

不用synchronized和lock,实现线程安全的单例模式

nio和reactor设计模式

网络编程

tcp、udp、http、https等常用协议

三次握手与四次关闭、流量控制和拥塞控制、OSI七层模型、tcp粘包与拆包

http/1.0 http/1.1 http/2之前的区别

Java RMI,Socket,HttpClient

cookie 与 session

cookie被禁用,如何实现session

用Java写一个简单的静态文件的HTTP服务器

实现客户端缓存功能,支持返回304 实现可并发下载一个文件 使用线程池处理客户端请求 使用nio处理客户端请求 支持简单的rewrite规则 上述功能在实现的时候需要满足“开闭原则” 了解nginx和apache服务器的特性并搭建一个对应的服务器

用Java实现FTP、SMTP协议

进程间通讯的方式

什么是CDN?如果实现?

什么是DNS?

反向代理

框架知识

Servlet线程安全问题

Servlet中的filter和listener

Hibernate的缓存机制

Hiberate的懒加载

Spring Bean的初始化

Spring的AOP原理

自己实现Spring的IOC

Spring MVC

Spring Boot2.0

Spring Boot的starter原理,自己实现一个starter

Spring Security

应用服务器 JBoss

tomcat

jetty

Weblogic

工具 git & svn

maven & gradle

三、 高级篇

新技术 Java 8

lambda表达式、Stream API、

Java 9

Jigsaw、Jshell、Reactive Streams

Java 10

局部变量类型推断、G1的并行Full GC、ThreadLocal握手机制

Spring 5

响应式编程

Spring Boot 2.0

性能优化

使用单例、使用Future模式、使用线程池、选择就绪、减少上下文切换、减少锁粒度、数据压缩、结果缓存

线上问题分析

dump获取

线程Dump、内存Dump、gc情况

dump分析

分析死锁、分析内存泄露

自己编写各种outofmemory,stackoverflow程序

HeapOutOfMemory、 Young OutOfMemory、MethodArea OutOfMemory、ConstantPool OutOfMemory、DirectMemory OutOfMemory、Stack OutOfMemory Stack OverFlow

常见问题解决思路

内存溢出、线程死锁、类加载冲突

使用工具尝试解决以下问题,并写下总结

当一个Java程序响应很慢时如何查找问题、 当一个Java程序频繁FullGC时如何解决问题、 如何查看垃圾回收日志、 当一个Java应用发生OutOfMemory时该如何解决、 如何判断是否出现死锁、 如何判断是否存在内存泄露 编译原理知识

编译与反编译

Java代码的编译与反编译

Java的反编译工具

词法分析,语法分析(LL算法,递归下降算法,LR算法),语义分析,运行时环境,中间代码,代码生成,代码优化

操作系统知识

  • Linux的常用命令

  • 进程同步

  • 缓冲区溢出

  • 分段和分页

  • 虚拟内存与主存

数据库知识

MySql 执行引擎

MySQL 执行计划

如何查看执行计划,如何根据执行计划进行SQL优化

SQL优化

事务

事务的隔离级别、事务能不能实现锁的功能

数据库锁

行锁、表锁、使用数据库锁实现乐观锁、

数据库主备搭建

binlog

内存数据库

h2

常用的nosql数据库

redis、memcached

分别使用数据库锁、NoSql实现分布式锁

性能调优

数据结构与算法知识 简单的数据结构

栈、队列、链表、数组、哈希表、

二叉树、字典树、平衡树、排序树、B树、B+树、R树、多路树、红黑树

排序算法

各种排序算法和时间复杂度 深度优先和广度优先搜索 全排列、贪心算法、KMP算法、hash算法、海量数据处理

大数据知识 Zookeeper

基本概念、常见用法

Solr,Lucene,ElasticSearch

在linux上部署solr,solrcloud,,新增、删除、查询索引

Storm,流式计算,了解Spark,S4

在linux上部署storm,用zookeeper做协调,运行storm hello world,local和remote模式运行调试storm topology。

Hadoop,离线计算

HDFS、MapReduce

分布式日志收集flume,kafka,logstash

数据挖掘,mahout

网络安全知识

什么是XSS

XSS的防御

什么是CSRF

什么是注入攻击

SQL注入、XML注入、CRLF注入

什么是文件上传漏洞

加密与解密

MD5,SHA1、DES、AES、RSA、DSA

什么是DOS攻击和DDOS攻击

memcached为什么可以导致DDos攻击、什么是反射型DDoS

SSL、TLS,HTTPS

如何通过Hash碰撞进行DOS攻击

用openssl签一个证书部署到apache或nginx

四、架构篇

分布式 数据一致性、服务治理、服务降级

分布式事务

2PC、3PC、CAP、BASE、 可靠消息最终一致性、最大努力通知、TCC

Dubbo

服务注册、服务发现,服务治理

分布式数据库

怎样打造一个分布式数据库、什么时候需要分布式数据库、mycat、otter、HBase

分布式文件系统

mfs、fastdfs

分布式缓存

缓存一致性、缓存命中率、缓存冗余

微服务 SOA、康威定律

ServiceMesh

Docker & Kubernets

Spring Boot

Spring Cloud

高并发

分库分表

CDN技术

消息队列

ActiveMQ

监控

监控什么

CPU、内存、磁盘I/O、网络I/O等

监控手段

进程监控、语义监控、机器资源监控、数据波动

监控数据采集

日志、埋点

Dapper

负载均衡

tomcat负载均衡、Nginx负载均衡

DNS

DNS原理、DNS的设计

CDN

数据一致性

五、 扩展篇

云计算

IaaS、SaaS、PaaS、虚拟化技术、openstack、Serverlsess

搜索引擎

Solr、Lucene、Nutch、Elasticsearch

权限管理

Shiro

区块链

哈希算法、Merkle树、公钥密码算法、共识算法、Raft协议、Paxos 算法与 Raft 算法、拜占庭问题与算法、消息认证码与数字签名

比特币

挖矿、共识机制、闪电网络、侧链、热点问题、分叉

以太坊

超级账本

人工智能

数学基础、机器学习、人工神经网络、深度学习、应用场景。

常用框架

TensorFlow、DeepLearning4J

其他语言

Groovy、Python、Go、NodeJs、Swift、Rust

六、 推荐书籍

《深入理解Java虚拟机》

《Effective Java》

《深入分析Java Web技术内幕》

《大型网站技术架构》

《代码整洁之道》

《Head First设计模式》

《maven实战》

《区块链原理、设计与应用》

《Java并发编程实战》

《鸟哥的Linux私房菜》

《从Paxos到Zookeeper》

《架构即未来》

Java网络编程之TCP协议

TCP协议

客户端

1)创建Socket连接服务端(指定ip地址,端口号)通过ip地址找对应的服务器

2)调用Socket的getInputStream()和getOutputStream()方法获取和服务端相连的IO流

3)输入流可以读取服务端输出流写出的数据

4)输出流可以写出数据到服务端的输入流

服务端

1)创建ServerSocket(需要指定端口号)

2)调用ServerSocket的accept()方法接收一个客户端请求,得到一个Socket

3)调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的IO流

4)输入流可以读取客户端输出流写出的数据

5)输出流可以写出数据到客户端的输入流

注意:

在执行的时候,要添加全路径名就是com.rkhd.tcp这一段不然是会报错的

java com.rkhd.tcp.Demo1_Server  
javac com.rkhd.tcp.Demo1_Client  

TCP协议代码优化

实现了传输功能时候,再来优化下代码 :

客户端

服务端

服务端这边应该是多线程的,因为作为服务端不可能只服务一个客户

demo示例

1)客户端向服务器写字符串(键盘录入)

2)服务器(多线程)将字符串反转后写回

3)客户端再次读取到是反转后的字符串

客户端

服务端


Java 网络编程

网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。

java.net 包中提供了两种常见的网络协议的支持:

  • TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。

  • UDP:UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。

本教程主要阐述以下两个主题。

Socket 编程:这是使用最广泛的网络概念,它已被解释地非常详细。

URL 处理:这部分会在另外的篇幅里讲,

Socket 编程

套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。

当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。

java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

  • 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。

  • 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。

  • 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。

  • Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。

  • 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。

连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。

ServerSocket 类的方法

服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。

ServerSocket 类有四个构造方法:

序号 方法描述
1 public ServerSocket(int port) throws IOException
创建绑定到特定端口的服务器套接字。
2 public ServerSocket(int port, int backlog) throws IOException
利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
3 public ServerSocket(int port, int backlog, InetAddress address) throws IOException
使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
4 public ServerSocket() throws IOException
创建非绑定服务器套接字。

创建非绑定服务器套接字。 如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。

这里有一些 ServerSocket 类的常用方法:

序号 方法描述
1 public int getLocalPort()
返回此套接字在其上侦听的端口。
2 public Socket accept() throws IOException
侦听并接受到此套接字的连接。
3 public void setSoTimeout(int timeout)
通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
4 public void bind(SocketAddress host, int backlog)
将 ServerSocket 绑定到特定地址(IP 地址和端口号)。

Socket 类的方法

java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。

Socket 类有五个构造方法.

序号 方法描述
1 public Socket(String host, int port) throws UnknownHostException, IOException.
创建一个流套接字并将其连接到指定主机上的指定端口号。
2 public Socket(InetAddress host, int port) throws IOException
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
3 public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.
创建一个套接字并将其连接到指定远程主机上的指定远程端口。
4 public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.
创建一个套接字并将其连接到指定远程地址上的指定远程端口。
5 public Socket()
通过系统默认类型的 SocketImpl 创建未连接套接字

当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。

下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。

序号 方法描述
1 public void connect(SocketAddress host, int timeout) throws IOException
将此套接字连接到服务器,并指定一个超时值。
2 public InetAddress getInetAddress()
返回套接字连接的地址。
3 public int getPort()
返回此套接字连接到的远程端口。
4 public int getLocalPort()
返回此套接字绑定到的本地端口。
5 public SocketAddress getRemoteSocketAddress()
返回此套接字连接的端点的地址,如果未连接则返回 null。
6 public InputStream getInputStream() throws IOException
返回此套接字的输入流。
7 public OutputStream getOutputStream() throws IOException
返回此套接字的输出流。
8 public void close() throws IOException
关闭此套接字。

InetAddress 类的方法

这个类表示互联网协议(IP)地址。下面列出了 Socket 编程时比较有用的方法:

序号 方法描述
1 static InetAddress getByAddress(byte[] addr)
在给定原始 IP 地址的情况下,返回 InetAddress 对象。
2 static InetAddress getByAddress(String host, byte[] addr)
根据提供的主机名和 IP 地址创建 InetAddress。
3 static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
4 String getHostAddress()
返回 IP地址字符串(以文本表现形式)。
5 String getHostName()
获取此 IP 地址的主机名。
6 static InetAddressgetLocalHost()
返回本地主机。
7 String toString()
将此 IP 地址转换为 String。

Java泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

文章开始,先给大家奉上一道经典的测试题。

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());

请问,上面代码最终结果输出的是什么?不了解泛型的和很熟悉泛型的同学应该能够答出来,而对泛型有所了解,但是了解不深入的同学可能会答错。

正确答案是 true。

上面的代码中涉及到了泛型,而输出的结果缘由是类型擦除

泛型是什么?

泛型的英文是 generics,generic 的意思是通用,而翻译成中文,泛应该意为广泛,型是类型。所以泛型就是能广泛适用的类型。

但泛型还有一种较为准确的说法就是为了参数化类型,或者说可以将类型当作参数传递给一个类或者是方法。

那么,如何解释类型参数化?

public class Cache {
   Object value;

   public Object getValue() {
       return value;
   }

   public void setValue(Object value) {
       this.value = value;
   }

}

假设 Cache 能够存取任何类型的值,于是,我们可以这样使用它。

Cache cache = new Cache();
cache.setValue(134);
int value = (int) cache.getValue();
cache.setValue("hello");
String value1 = (String) cache.getValue();

使用的方法也很简单,只要我们做正确的强制转换就好了。

但是,泛型却给我们带来了不一样的编程体验。

public class Cache<T> {
   T value;

   public Object getValue() {
       return value;
   }

   public void setValue(T value) {
       this.value = value;
   }

}

这就是泛型,它将 value 这个属性的类型也参数化了,这就是所谓的参数化类型。再看它的使用方法。

Cache<String> cache1 = new Cache<String>();
cache1.setValue("123");
String value2 = cache1.getValue();

Cache<Integer> cache2 = new Cache<Integer>();
cache2.setValue(456);
int value3 = cache2.getValue();

最显而易见的好处就是它不再需要对取出来的结果进行强制转换了。但,还有另外一点不同。

泛型除了可以将类型参数化外,而参数一旦确定好,如果类似不匹配,编译器就不通过。 上面代码显示,无法将一个 String 对象设置到 cache2 中,因为泛型让它只接受 Integer 的类型。

所以,综合上面信息,我们可以得到下面的结论。

  1. 与普通的 Object 代替一切类型这样简单粗暴而言,泛型使得数据的类别可以像参数一样由外部传递进来。它提供了一种扩展能力。它更符合面向抽象开发的软件编程宗旨。
  2. 当具体的类型确定后,泛型又提供了一种类型检测的机制,只有相匹配的数据才能正常的赋值,否则编译器就不通过。所以说,它是一种类型安全检测机制,一定程度上提高了软件的安全性防止出现低级的失误。
  3. 泛型提高了程序代码的可读性,不必要等到运行的时候才去强制转换,在定义或者实例化阶段,因为 Cache 这个类型显化的效果,程序员能够一目了然猜测出代码要操作的数据类型。

下面的文章,我们正常介绍泛型的相关知识。

泛型的定义和使用

泛型按照使用情况可以分为 3 种。

  1. 泛型类。
  2. 泛型方法。
  3. 泛型接口。

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

我们可以这样定义一个泛型类。

public class Test<T> {
   T field1;
}

尖括号<>中的 T 被称作是类型参数,用于指代任何类型。事实上,T 只是一种习惯性写法,如果你愿意。你可以这样写。

public class Test<Hello> {
   Hello field1;
}

但出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如:

  1. T 代表一般的任何类。
  2. E 代表 Element 的意思,或者 Exception 异常的意思。
  3. K 代表 Key 的意思。
  4. V 代表 Value 的意思,通常与 K 一起配合使用。
  5. S 代表 Subtype 的意思,文章后面部分会讲解示意。

如果一个类被<T>的形式定义,那么它就被称为是泛型类。

那么对于泛型类怎么样使用?

Test<String> test1 = new Test<>();
Test<Integer> test2 = new Test<>();

只要在对泛型类创建实例的时候,在尖括号中赋值相应的类型便是。T 就会被替换成对应的类型,如 String 或者是 Integer。你可以相像一下,当一个泛型类被创建时,内部自动扩展成下面的代码。

public class Test<String> {
   String field1;
}

当然,泛型类不至接受一个类型参数,它还可以这样接受多个类型参数。

public class MultiType <E,T>{
   E value1;
   T value2;

   public E getValue1(){
       return value1;
   }

   public T getValue2(){
       return value2;
   }
}

泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 )。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
public class Test1 {

   public <T> void testMethod(T t){

   }
}

泛型方法与泛型类稍有不同的地方是,类型参数也就是尖括号那一部分是写在返回值前面的。<T> 中的 T 被称为类型参数,而方法中的 T 被称为参数化类型,它不是运行时真正的参数。

当然,声明的类型参数,其实也是可以当作返回值的类型的。

public  <T> T testMethod1(T t){
       return null;
}

泛型类与泛型方法的共存现象

public class Test1<T>{

   public  void testMethod(T t){
       System.out.println(t.getClass().getName());
   }
   public  <T> T testMethod1(T t){
       return t;
   }
}

上面代码中,Test1<T> 是泛型类,testMethod 是泛型类中的普通方法,而 testMethod1 是一个泛型方法。而泛型类中的类型参数与泛型方法中的类型参数是没有相应的联系的,泛型方法始终以自己定义的类型参数为准。

所以,针对上面的代码,我们可以这样编写测试代码。

Test1<String> t = new Test1();
t.testMethod("generic");
Integer i = t.testMethod1(new Integer(1));

泛型类的实际类型参数是 String,而传递给泛型方法的类型参数是 Integer,两者不想干。

但是,为了避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名。比如,Test1<T> 代码可以更改为这样

public class Test1<T>{

   public  void testMethod(T t){
       System.out.println(t.getClass().getName());
   }
   public  <E> E testMethod1(E e){
       return e;
   }
}

泛型接口

泛型接口和泛型类差不多,所以一笔带过。

public interface Iterable<T> {
}

通配符 ?

除了用 <T> 表示泛型外,还有 <?> 这种形式。 被称为通配符。

可能有同学会想,已经有了 的形式了,为什么还要引进 <?> 这样的概念呢?

class Base{}

class Sub extends Base{}

Sub sub = new Sub();
Base base = sub;

上面代码显示,BaseSub 的父类,它们之间是继承关系,所以 Sub 的实例可以给一个 Base 引用赋值,那么

List<Sub> lsub = new ArrayList<>();
List<Base> lbase = lsub;

最后一行代码成立吗?编译会通过吗?

答案是否定的。

编译器不会让它通过的。Sub 是 Base 的子类,不代表 List<Sub>List<Base>有继承关系。

但是,在现实编码中,确实有这样的需求,希望泛型能够处理某一范围内的数据类型,比如某个类和它的子类,对此 Java 引入了通配符这个概念。

所以,通配符的出现是为了指定泛型中的类型范围。

通配符有 3 种形式。

  1. <?> 被称作无限定的通配符。
  2. <? extends T> 被称作有上限的通配符。
  3. <? super T> 被称作有下限的通配符。

无限定通配符

public void testWildCards(Collection<?> collection){
}

上面的代码中,方法内的参数是被无限定通配符修饰的 Collection 对象,它隐略地表达了一个意图或者可以说是限定,那就是 testWidlCards() 这个方法内部无需关注 Collection 中的真实类型,因为它是未知的。所以,你只能调用 Collection 中与类型无关的方法。

我们可以看到,当 <?> 存在时,Collection 对象丧失了 add() 方法的功能,编译器不通过。 我们再看代码。

List<?> wildlist = new ArrayList<String>();
wildlist.add(123);// 编译不通过

有人说,<?> 提供了只读的功能,也就是它删减了增加具体类型元素的能力,只保留与具体类型无关的功能。它不管装载在这个容器内的元素是什么类型,它只关心元素的数量、容器是否为空?我想这种需求还是很常见的吧。

有同学可能会想,<?> 既然作用这么渺小,那么为什么还要引用它呢?

个人认为,提高了代码的可读性,程序员看到这段代码时,就能够迅速对此建立极简洁的印象,能够快速推断源码作者的意图。

<? extends T>

<?> 代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 A 及 类型 A 的子类都可以。

public void testSub(Collection<? extends Base> para){

}

上面代码中,para 这个 Collection 接受 Base 及 Base 的子类的类型。

但是,它仍然丧失了写操作的能力。也就是说

para.add(new Sub());
para.add(new Base());

仍然编译不通过。

没有关系,我们不知道具体类型,但是我们至少清楚了类型的范围。

<? super T>

这个和 <? extends T> 相对应,代表 T 及 T 的超类。

public void testSuper(Collection<? super Sub> para){
}

<? super T> 神奇的地方在于,它拥有一定程度的写操作的能力。

public void testSuper(Collection<? super Sub> para){
   para.add(new Sub());//编译通过
   para.add(new Base());//编译不通过
}

通配符与类型参数的区别

一般而言,通配符能干的事情都可以用类型参数替换。 比如

public void testWildCards(Collection<?> collection){}

可以被

public <T> void test(Collection<T> collection){}

取代。

值得注意的是,如果用泛型方法来取代通配符,那么上面代码中 collection 是能够进行写操作的。只不过要进行强制转换。

public <T> void test(Collection<T> collection){
   collection.add((T)new Integer(12));
   collection.add((T)"123");
}

需要特别注意的是,类型参数适用于参数之间的类别依赖关系,举例说明。

public class Test2 <T,E extends T>{
   T value1;
   E value2;
}
public <D,S extends D> void test(D d,S s){

   }

E 类型是 T 类型的子类,显然这种情况类型参数更适合。 有一种情况是,通配符和类型参数一起使用。

public <T> void test(T t,Collection<? extends T> collection){

}

如果一个方法的返回类型依赖于参数的类型,那么通配符也无能为力。

public T test1(T t){
   return value1;
}

类型擦除

泛型是 Java 1.5 版本才引进的概念,在这之前是没有泛型的概念的,但显然,泛型代码能够很好地和之前版本的代码很好地兼容。

这是因为,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

通俗地讲,泛型类和普通类在 java 虚拟机内是没有什么特别的地方。回顾文章开始时的那段代码

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());

打印的结果为 true 是因为 List<String> List<Integer> 在 jvm 中的 Class 都是 List.class。

泛型信息被擦除了。

可能同学会问,那么类型 String 和 Integer 怎么办?

答案是泛型转译。

public class Erasure <T>{
   T object;

   public Erasure(T object) {
       this.object = object;
   }

}

Erasure 是一个泛型类,我们查看它在运行时的状态信息可以通过反射。

Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());

打印的结果是

erasure class is:com.frank.test.Erasure

Class 的类型仍然是 Erasure 并不是 Erasure<T> 这种形式,那我们再看看泛型类中 T 的类型在 jvm 中是什么具体类型。

Field[] fs = eclz.getDeclaredFields();
for ( Field f:fs) {
   System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}

打印结果是

Field name object type:java.lang.Object

那我们可不可以说,泛型类被类型擦除后,相应的类型就被替换成 Object 类型呢?

这种说法,不完全正确。

我们更改一下代码。

public class Erasure <T extends String>{
//  public class Erasure <T>{
   T object;

   public Erasure(T object) {
       this.object = object;
   }

}

现在再看测试结果:

Field name object type:java.lang.String

我们现在可以下结论了,在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如<T>则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String> 则类型参数就被替换成类型上限。

所以,在反射中。

public class Erasure <T>{
   T object;

   public Erasure(T object) {
       this.object = object;
   }

   public void add(T object){

   }

}

add() 这个方法对应的 Method 的签名应该是 Object.class。

Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());

Method[] methods = eclz.getDeclaredMethods();
for ( Method m:methods ){
   System.out.println(" method:"+m.toString());
}

打印结果是

method:public void com.frank.test.Erasure.add(java.lang.Object)

也就是说,如果你要在反射中找到 add 对应的 Method,你应该调用 getDeclaredMethod("add",Object.class) 否则程序会报错,提示没有这么一个方法,原因就是类型擦除的时候,T 被替换成 Object 类型了。

类型擦除带来的局限性

类型擦除,是泛型能够与之前的 java 版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性。

理解类型擦除有利于我们绕过开发当中可能遇到的雷区,同样理解类型擦除也能让我们绕过泛型本身的一些限制。比如

正常情况下,因为泛型的限制,编译器不让最后一行代码编译通过,因为类似不匹配,但是,基于对类型擦除的了解,利用反射,我们可以绕过这个限制。

public interface List<E> extends Collection<E>{

    boolean add(E e);
}

上面是 List 和其中的 add() 方法的源码定义。

因为 E 代表任意的类型,所以类型擦除时,add 方法其实等同于

boolean add(Object obj);

那么,利用反射,我们绕过编译器去调用 add 方法。

public class ToolTest {


   public static void main(String[] args) {
       List<Integer> ls = new ArrayList<>();
       ls.add(23);
//      ls.add("text");
       try {
           Method method = ls.getClass().getDeclaredMethod("add",Object.class);


           method.invoke(ls,"test");
           method.invoke(ls,42.9f);
       } catch (NoSuchMethodException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       } catch (SecurityException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       } catch (IllegalArgumentException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       }

       for ( Object o: ls){
           System.out.println(o);
       }

   }

}

打印结果是:

23
test
42.9

可以看到,利用类型擦除的原理,用反射的手段就绕过了正常开发中编译器不允许的操作限制。

泛型中值得注意的地方
泛型类或者泛型方法中,不接受 8 种基本数据类型。

所以,你没有办法进行这样的编码。

List<int> li = new ArrayList<>();
List<boolean> li = new ArrayList<>();

需要使用它们对应的包装类。

List<Integer> li = new ArrayList<>();
List<Boolean> li1 = new ArrayList<>();

对泛型方法的困惑

public <T> T test(T t){
   return null;
}

有的同学可能对于连续的两个 T 感到困惑,其实 是为了说明类型参数,是声明,而后面的不带尖括号的 T 是方法的返回值类型。 你可以相像一下,如果 test() 这样被调用 test("123");

那么实际上相当于

public String test(String t);

Java 不能创建具体类型的泛型数组

这句话可能难以理解,代码说明。

List<Integer>[] li2 = new ArrayList<Integer>[];
List<Boolean> li3 = new ArrayList<Boolean>[];

这两行代码是无法在编译器中编译通过的。原因还是类型擦除带来的影响。

List<Integer> List<Boolean> 在 jvm 中等同于List<Object> ,所有的类型信息都被擦除,程序也无法分辨一个数组中的元素类型具体是 List<Integer>类型还是 List<Boolean> 类型。

但是,

List<?>[] li3 = new ArrayList<?>[10];
li3[1] = new ArrayList<String>();
List<?> v = li3[1];

借助于无限定通配符却可以,前面讲过 ? 代表未知类型,所以它涉及的操作都基本上与类型无关,因此 jvm 不需要针对它对类型作判断,因此它能编译通过,但是,只提供了数组中的元素因为通配符原因,它只能读,不能写。比如,上面的 v 这个局部变量,它只能进行 get() 操作,不能进行 add() 操作,这个在前面通配符的内容小节中已经讲过。


我们可以看到,泛型其实并没有什么神奇的地方,泛型代码能做的非泛型代码也能做。

而类型擦除,是泛型能够与之前的 java 版本代码兼容共存的原因。

可量也正因为类型擦除导致了一些隐患与局限。

但,我还是要建议大家使用泛型,如官方文档所说的,如果可以使用泛型的地方,尽量使用泛型。

毕竟它抽离了数据类型与代码逻辑,本意是提高程序代码的简洁性和可读性,并提供可能的编译时类型转换安全检测功能。

类型擦除不是泛型的全部,但是它却能很好地检测我们对于泛型这个概念的理解程度。

tip:

<? extends T>和<? super T>的区别

  • 表示该通配符所代表的类型是T类型的子类。
  • 表示该通配符所代表的类型是T类型的父类。

对于泛型,只是允许程序员在编译时检测到非法的类型而已。

但是在运行期时,其中的泛型标志会变化为 Object 类型。

一个 List:

List<Integer> list = new ArrayList<>();

list.add(12);
//这里直接添加会报错
list.add("a");
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
//但是通过反射添加,是可以的
add.invoke(list, "kl");

System.out.println(list)

分类:

后端

标签:

Java

作者介绍

Young_
V2

佛学代表、趟平系代言人、内卷领军人物